Changeset 3321 for pjproject/trunk


Ignore:
Timestamp:
Sep 27, 2010 8:35:08 AM (14 years ago)
Author:
bennylp
Message:

Implemented and closed #1136: added HTTP authentication support

Location:
pjproject/trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjlib-util/include/pjlib-util/http_client.h

    r3227 r3321  
    5050 
    5151/** 
     52 * HTTP header representation. 
     53 */ 
     54typedef struct pj_http_header_elmt 
     55{ 
     56    pj_str_t name;      /**< Header name */ 
     57    pj_str_t value;     /**< Header value */ 
     58} pj_http_header_elmt; 
     59 
     60/** 
    5261 * This structure describes http request/response headers. 
    5362 * Application should call #pj_http_headers_add_elmt() to 
     
    5665typedef struct pj_http_headers 
    5766{ 
    58     unsigned     count;                   /**< Number of header fields */ 
    59     struct pj_http_header_elmt 
    60     { 
    61         pj_str_t name; 
    62         pj_str_t value; 
    63     } header[PJ_HTTP_HEADER_SIZE];        /**< Header elements/fields */ 
     67    /**< Number of header fields */ 
     68    unsigned     count; 
     69 
     70    /** Header elements/fields */ 
     71    pj_http_header_elmt header[PJ_HTTP_HEADER_SIZE]; 
    6472} pj_http_headers; 
     73 
     74/** 
     75 * Structure to save HTTP authentication credential. 
     76 */ 
     77typedef struct pj_http_auth_cred 
     78{ 
     79    /** 
     80     * Specify specific authentication schemes to be responded. Valid values 
     81     * are "basic" and "digest". If this field is not set, any authentication 
     82     * schemes will be responded. 
     83     * 
     84     * Default is empty. 
     85     */ 
     86    pj_str_t    scheme; 
     87 
     88    /** 
     89     * Specify specific authentication realm to be responded. If this field 
     90     * is set, only 401/407 response with matching realm will be responded. 
     91     * If this field is not set, any realms will be responded. 
     92     * 
     93     * Default is empty. 
     94     */ 
     95    pj_str_t    realm; 
     96 
     97    /** 
     98     * Specify authentication username. 
     99     * 
     100     * Default is empty. 
     101     */ 
     102    pj_str_t    username; 
     103 
     104    /** 
     105     * The type of password in \a data field. Currently only 0 is 
     106     * supported, meaning the \a data contains plain-text password. 
     107     * 
     108     * Default is 0. 
     109     */ 
     110    unsigned    data_type; 
     111 
     112    /** 
     113     * Specify authentication password. The encoding of the password depends 
     114     * on the value of \a data_type field above. 
     115     * 
     116     * Default is empty. 
     117     */ 
     118    pj_str_t    data; 
     119 
     120} pj_http_auth_cred; 
     121 
    65122 
    66123/** 
     
    126183                                   /**< will be provided later  */ 
    127184    } reqdata; 
     185 
     186    /** 
     187     * Authentication credential needed to respond to 401/407 response. 
     188     */ 
     189    pj_http_auth_cred   auth_cred; 
     190 
    128191} pj_http_req_param; 
     192 
     193/** 
     194 * HTTP authentication challenge, parsed from WWW-Authenticate header. 
     195 */ 
     196typedef struct pj_http_auth_chal 
     197{ 
     198    pj_str_t    scheme;         /**< Auth scheme.               */ 
     199    pj_str_t    realm;          /**< Realm for the challenge.   */ 
     200    pj_str_t    domain;         /**< Domain.                    */ 
     201    pj_str_t    nonce;          /**< Nonce challenge.           */ 
     202    pj_str_t    opaque;         /**< Opaque value.              */ 
     203    int         stale;          /**< Stale parameter.           */ 
     204    pj_str_t    algorithm;      /**< Algorithm parameter.       */ 
     205    pj_str_t    qop;            /**< Quality of protection.     */ 
     206} pj_http_auth_chal; 
    129207 
    130208/** 
     
    137215    pj_str_t        reason;         /**< Reason phrase */ 
    138216    pj_http_headers headers;        /**< Response headers */ 
    139     /** 
    140      * The value of content-length header field. -1 if not 
    141      * specified. 
    142      */ 
    143     pj_int32_t      content_length;  
     217    pj_http_auth_chal auth_chal;    /**< Parsed WWW-Authenticate header, if 
     218                                         any. */ 
     219    pj_int32_t      content_length; /**< The value of content-length header 
     220                                         field. -1 if not specified. */ 
    144221    void            *data;          /**< Data received */ 
    145222    pj_size_t       size;           /**< Data size */ 
     
    151228typedef struct pj_http_url 
    152229{ 
     230    pj_str_t    username;           /**< Username part */ 
     231    pj_str_t    passwd;             /**< Password part */ 
    153232    pj_str_t    protocol;           /**< Protocol used */ 
    154233    pj_str_t    host;               /**< Host name */ 
  • pjproject/trunk/pjlib-util/src/pjlib-util-test/http_client.c

    r3262 r3321  
    238238 
    239239 
    240 pj_status_t parse_url(const char *url) 
     240pj_status_t parse_url(const char *url, pj_http_url *hurl) 
    241241{ 
    242242    pj_str_t surl; 
    243     pj_http_url hurl; 
    244243    pj_status_t status; 
    245244 
    246245    pj_cstr(&surl, url); 
    247     status = pj_http_req_parse_url(&surl, &hurl); 
     246    status = pj_http_req_parse_url(&surl, hurl); 
    248247#ifdef VERBOSE 
    249248    if (!status) { 
    250249        printf("URL: %s\nProtocol: %.*s\nHost: %.*s\nPort: %d\nPath: %.*s\n\n", 
    251                url, STR_PREC(hurl.protocol), STR_PREC(hurl.host),  
    252                hurl.port, STR_PREC(hurl.path)); 
     250               url, STR_PREC(hurl->protocol), STR_PREC(hurl->host), 
     251               hurl->port, STR_PREC(hurl->path)); 
    253252    } else { 
    254253    } 
     
    257256} 
    258257 
    259 int parse_url_test() 
    260 { 
    261     /* Simple URL without '/' in the end */ 
    262     if (parse_url("http://www.google.com.sg") != PJ_SUCCESS) 
    263         return -11; 
    264     /* Simple URL with port number but without '/' in the end */ 
    265     if (parse_url("http://www.example.com:8080") != PJ_SUCCESS) 
    266         return -13; 
    267     /* URL with path */ 
    268     if (parse_url("http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6") 
    269         != PJ_SUCCESS) 
    270         return -15; 
    271     /* URL with port and path */ 
    272     if (parse_url("http://teluu.com:81/about-us/") != PJ_SUCCESS) 
    273         return -17; 
    274     /* unsupported protocol */ 
    275     if (parse_url("ftp://www.teluu.com") != PJ_ENOTSUP) 
    276         return -19; 
    277     /* invalid format */ 
    278     if (parse_url("http:/teluu.com/about-us/") != PJLIB_UTIL_EHTTPINURL) 
    279         return -21; 
    280     /* invalid port number */ 
    281     if (parse_url("http://teluu.com:xyz/") != PJLIB_UTIL_EHTTPINPORT) 
    282         return -23; 
     258static int parse_url_test() 
     259{ 
     260    struct test_data 
     261    { 
     262        char *url; 
     263        pj_status_t result; 
     264        const char *username; 
     265        const char *passwd; 
     266        const char *host; 
     267        int port; 
     268        const char *path; 
     269    } test_data[] = 
     270    { 
     271        /* Simple URL without '/' in the end */ 
     272        {"http://www.pjsip.org", PJ_SUCCESS, "", "", "www.pjsip.org", 80, "/"}, 
     273 
     274        /* Simple URL with port number but without '/' in the end */ 
     275        {"http://pjsip.org:8080", PJ_SUCCESS, "", "", "pjsip.org", 8080, "/"}, 
     276 
     277        /* URL with path */ 
     278        {"http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6", 
     279                PJ_SUCCESS, "", "", "127.0.0.1", 280, 
     280                "/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6"}, 
     281 
     282        /* URL with port and path */ 
     283        {"http://pjsip.org:81/about-us/", PJ_SUCCESS, "", "", "pjsip.org", 81, "/about-us/"}, 
     284 
     285        /* unsupported protocol */ 
     286        {"ftp://www.pjsip.org", PJ_ENOTSUP, "", "", "", 80, ""}, 
     287 
     288        /* invalid format */ 
     289        {"http:/pjsip.org/about-us/", PJLIB_UTIL_EHTTPINURL, "", "", "", 80, ""}, 
     290 
     291        /* invalid port number */ 
     292        {"http://pjsip.org:xyz/", PJLIB_UTIL_EHTTPINPORT, "", "", "", 80, ""}, 
     293 
     294        /* with username and password */ 
     295        {"http://user:pass@pjsip.org", PJ_SUCCESS, "user", "pass", "pjsip.org", 80, "/"}, 
     296 
     297        /* password only*/ 
     298        {"http://:pass@pjsip.org", PJ_SUCCESS, "", "pass", "pjsip.org", 80, "/"}, 
     299 
     300        /* user only*/ 
     301        {"http://user:@pjsip.org", PJ_SUCCESS, "user", "", "pjsip.org", 80, "/"}, 
     302 
     303        /* empty username and passwd*/ 
     304        {"http://:@pjsip.org", PJ_SUCCESS, "", "", "pjsip.org", 80, "/"}, 
     305 
     306        /* Invalid URL */ 
     307        {"http://:", PJ_EINVAL, "", "", "", 0, ""}, 
     308 
     309        /* Invalid URL */ 
     310        {"http://@", PJ_EINVAL, "", "", "", 0, ""}, 
     311 
     312        /* Invalid URL */ 
     313        {"http", PJ_EINVAL, "", "", "", 0, ""}, 
     314 
     315        /* Invalid URL */ 
     316        {"http:/", PJ_EINVAL, "", "", "", 0, ""}, 
     317 
     318        /* Invalid URL */ 
     319        {"http://", PJ_EINVAL, "", "", "", 0, ""}, 
     320 
     321        /* Invalid URL */ 
     322        {"http:///", PJ_EINVAL, "", "", "", 0, ""}, 
     323 
     324        /* Invalid URL */ 
     325        {"http://@/", PJ_EINVAL, "", "", "", 0, ""}, 
     326 
     327        /* Invalid URL */ 
     328        {"http://:::", PJ_EINVAL, "", "", "", 0, ""}, 
     329    }; 
     330    unsigned i; 
     331 
     332    for (i=0; i<PJ_ARRAY_SIZE(test_data); ++i) { 
     333        struct test_data *ptd; 
     334        pj_http_url hurl; 
     335        pj_status_t status; 
     336 
     337        ptd = &test_data[i]; 
     338 
     339        PJ_LOG(3, (THIS_FILE, ".. %s", ptd->url)); 
     340        status = parse_url(ptd->url, &hurl); 
     341 
     342        if (status != ptd->result) { 
     343            PJ_LOG(3,(THIS_FILE, "%d", status)); 
     344            return -11; 
     345        } 
     346        if (status != PJ_SUCCESS) 
     347            continue; 
     348        if (pj_strcmp2(&hurl.username, ptd->username)) 
     349            return -12; 
     350        if (pj_strcmp2(&hurl.passwd, ptd->passwd)) 
     351            return -13; 
     352        if (pj_strcmp2(&hurl.host, ptd->host)) 
     353            return -14; 
     354        if (hurl.port != ptd->port) 
     355            return -15; 
     356        if (pj_strcmp2(&hurl.path, ptd->path)) 
     357            return -16; 
     358    } 
    283359 
    284360    return 0; 
  • pjproject/trunk/pjlib-util/src/pjlib-util/http_client.c

    r3236 r3321  
    2121#include <pj/activesock.h> 
    2222#include <pj/assert.h> 
     23#include <pj/ctype.h> 
    2324#include <pj/errno.h> 
    2425#include <pj/except.h> 
     
    2627#include <pj/string.h> 
    2728#include <pj/timer.h> 
     29#include <pjlib-util/base64.h> 
    2830#include <pjlib-util/errno.h> 
     31#include <pjlib-util/md5.h> 
    2932#include <pjlib-util/scanner.h> 
     33#include <pjlib-util/string.h> 
    3034 
    3135#if 0 
     
    4044#define HTTP_1_0                "1.0" 
    4145#define HTTP_1_1                "1.1" 
    42 #define HTTP_SEPARATOR          "://" 
    4346#define CONTENT_LENGTH          "Content-Length" 
    4447/* Buffer size for sending/receiving messages. */ 
     
    9497    READING_COMPLETE, 
    9598    ABORTING, 
     99}; 
     100 
     101enum auth_state 
     102{ 
     103    AUTH_NONE,          /* Not authenticating */ 
     104    AUTH_RETRYING,      /* New request with auth has been submitted */ 
     105    AUTH_DONE           /* Done retrying the request with auth. */ 
    96106}; 
    97107 
     
    110120    pj_str_t                buffer;     /* Buffer to send/receive msgs */ 
    111121    enum http_state         state;      /* State of the HTTP request */ 
     122    enum auth_state         auth_state; /* Authentication state */ 
    112123    pj_timer_entry          timer_entry;/* Timer entry */ 
    113124    pj_bool_t               resolved;   /* Whether URL's host is resolved */ 
     
    143154                                       void *data, pj_size_t size, 
    144155                                       pj_size_t *remainder); 
     156/* Restart the request with authentication */ 
     157static void restart_req_with_auth(pj_http_req *hreq); 
     158/* Parse authentication challenge */ 
     159static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, 
     160                                   pj_http_auth_chal *chal); 
    145161 
    146162static pj_uint16_t get_http_default_port(const pj_str_t *protocol) 
     
    319335                hreq->response.size = size - rem; 
    320336            } 
     337 
     338            /* If code is 401 or 407, find and parse WWW-Authenticate or 
     339             * Proxy-Authenticate header 
     340             */ 
     341            if (hreq->response.status_code == 401 || 
     342                hreq->response.status_code == 407) 
     343            { 
     344                const pj_str_t STR_WWW_AUTH = { "WWW-Authenticate", 16 }; 
     345                const pj_str_t STR_PROXY_AUTH = { "Proxy-Authenticate", 18 }; 
     346                pj_http_resp *response = &hreq->response; 
     347                pj_http_headers *hdrs = &response->headers; 
     348                unsigned i; 
     349 
     350                status = PJ_ENOTFOUND; 
     351                for (i = 0; i < hdrs->count; i++) { 
     352                    if (!pj_stricmp(&hdrs->header[i].name, &STR_WWW_AUTH) || 
     353                        !pj_stricmp(&hdrs->header[i].name, &STR_PROXY_AUTH)) 
     354                    { 
     355                        status = parse_auth_chal(hreq->pool, 
     356                                                 &hdrs->header[i].value, 
     357                                                 &response->auth_chal); 
     358                        break; 
     359                    } 
     360                } 
     361 
     362                /* Check if we should perform authentication */ 
     363                if (status == PJ_SUCCESS && 
     364                    hreq->auth_state == AUTH_NONE && 
     365                    hreq->response.auth_chal.scheme.slen && 
     366                    hreq->param.auth_cred.username.slen && 
     367                    (hreq->param.auth_cred.scheme.slen == 0 || 
     368                     !pj_stricmp(&hreq->response.auth_chal.scheme, 
     369                                 &hreq->param.auth_cred.scheme)) && 
     370                    (hreq->param.auth_cred.realm.slen == 0 || 
     371                     !pj_stricmp(&hreq->response.auth_chal.realm, 
     372                                 &hreq->param.auth_cred.realm)) 
     373                    ) 
     374                    { 
     375                    /* Yes, authentication is required and we have been 
     376                     * configured with credential. 
     377                     */ 
     378                    restart_req_with_auth(hreq); 
     379                    if (hreq->auth_state == AUTH_RETRYING) { 
     380                        /* We'll be resending the request with auth. This 
     381                         * connection has been closed. 
     382                         */ 
     383                        return PJ_FALSE; 
     384                    } 
     385                } 
     386            } 
     387 
    321388            /* We already received the response header, call the  
    322              * appropriate callback.  
     389             * appropriate callback. 
    323390             */ 
    324391            if (hreq->cb.on_response) 
     
    385452        (status == PJ_EEOF && hreq->response.content_length == -1))  
    386453    { 
    387         /* Finish reading */ 
     454        /* Finish reading */ 
    388455        http_req_end_request(hreq); 
    389456        hreq->response.size = hreq->tcp_state.current_read_size; 
     
    434501} 
    435502 
     503/* Parse authentication challenge */ 
     504static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, 
     505                                   pj_http_auth_chal *chal) 
     506{ 
     507    pj_scanner scanner; 
     508    const pj_str_t REALM_STR    =    { "realm", 5}, 
     509                NONCE_STR       =    { "nonce", 5}, 
     510                ALGORITHM_STR   =    { "algorithm", 9 }, 
     511                STALE_STR       =    { "stale", 5}, 
     512                QOP_STR         =    { "qop", 3}, 
     513                OPAQUE_STR      =    { "opaque", 6}; 
     514    pj_status_t status = PJ_SUCCESS; 
     515    PJ_USE_EXCEPTION ; 
     516 
     517    pj_scan_init(&scanner, input->ptr, input->slen, PJ_SCAN_AUTOSKIP_WS, 
     518                 &on_syntax_error); 
     519    PJ_TRY { 
     520        /* Get auth scheme */ 
     521        if (*scanner.curptr == '"') { 
     522            pj_scan_get_quote(&scanner, '"', '"', &chal->scheme); 
     523            chal->scheme.ptr++; 
     524            chal->scheme.slen -= 2; 
     525        } else { 
     526            pj_scan_get_until_chr(&scanner, " \t\r\n", &chal->scheme); 
     527        } 
     528 
     529        /* Loop parsing all parameters */ 
     530        for (;;) { 
     531            const char *end_param = ", \t\r\n;"; 
     532            pj_str_t name, value; 
     533 
     534            /* Get pair of parameter name and value */ 
     535            value.ptr = NULL; 
     536            value.slen = 0; 
     537            pj_scan_get_until_chr(&scanner, "=, \t\r\n", &name); 
     538            if (*scanner.curptr == '=') { 
     539                pj_scan_get_char(&scanner); 
     540                if (!pj_scan_is_eof(&scanner)) { 
     541                    if (*scanner.curptr == '"' || *scanner.curptr == '\'') { 
     542                        int quote_char = *scanner.curptr; 
     543                        pj_scan_get_quote(&scanner, quote_char, quote_char, 
     544                                          &value); 
     545                        value.ptr++; 
     546                        value.slen -= 2; 
     547                    } else if (!strchr(end_param, *scanner.curptr)) { 
     548                        pj_scan_get_until_chr(&scanner, end_param, &value); 
     549                    } 
     550                } 
     551                value = pj_str_unescape(pool, &value); 
     552            } 
     553 
     554            if (!pj_stricmp(&name, &REALM_STR)) { 
     555                chal->realm = value; 
     556 
     557            } else if (!pj_stricmp(&name, &NONCE_STR)) { 
     558                chal->nonce = value; 
     559 
     560            } else if (!pj_stricmp(&name, &ALGORITHM_STR)) { 
     561                chal->algorithm = value; 
     562 
     563            } else if (!pj_stricmp(&name, &OPAQUE_STR)) { 
     564                chal->opaque = value; 
     565 
     566            } else if (!pj_stricmp(&name, &QOP_STR)) { 
     567                chal->qop = value; 
     568 
     569            } else if (!pj_stricmp(&name, &STALE_STR)) { 
     570                chal->stale = value.slen && 
     571                              (*value.ptr != '0') && 
     572                              (*value.ptr != 'f') && 
     573                              (*value.ptr != 'F'); 
     574 
     575            } 
     576 
     577            /* Eat comma */ 
     578            if (!pj_scan_is_eof(&scanner) && *scanner.curptr == ',') 
     579                pj_scan_get_char(&scanner); 
     580            else 
     581                break; 
     582        } 
     583 
     584    } 
     585    PJ_CATCH_ANY { 
     586        status = PJ_GET_EXCEPTION(); 
     587        pj_bzero(chal, sizeof(*chal)); 
     588        TRACE_((THIS_FILE, "Error: parsing of auth header failed")); 
     589    } 
     590    PJ_END; 
     591    pj_scan_fini(&scanner); 
     592    return status; 
     593} 
     594 
    436595/* The same as #pj_http_headers_add_elmt() with char * as 
    437596 * its parameters. 
     
    465624    pj_size_t i; 
    466625    char *cptr; 
    467     char *newdata; 
     626    char *end_status, *newdata; 
    468627    pj_scanner scanner; 
    469628    pj_str_t s; 
     629    const pj_str_t STR_CONTENT_LENGTH = { CONTENT_LENGTH, 14 }; 
    470630    pj_status_t status; 
    471631 
     
    518678    PJ_END; 
    519679 
     680    end_status = scanner.curptr; 
     681    pj_scan_fini(&scanner); 
     682 
    520683    /* Parse the response headers. */ 
    521     size = i - 2 - (scanner.curptr - newdata); 
     684    size = i - 2 - (end_status - newdata); 
    522685    if (size > 0) { 
    523         status = http_headers_parse(scanner.curptr + 1, size,  
     686        status = http_headers_parse(end_status + 1, size, 
    524687                                    &response->headers); 
    525688    } else { 
     
    529692    /* Find content-length header field. */ 
    530693    for (i = 0; i < response->headers.count; i++) { 
    531         if (!pj_stricmp2(&response->headers.header[i].name,  
    532                          CONTENT_LENGTH))  
     694        if (!pj_stricmp(&response->headers.header[i].name, 
     695                        &STR_CONTENT_LENGTH)) 
    533696        { 
    534697            response->content_length =  
     
    545708        } 
    546709    } 
    547  
    548     pj_scan_fini(&scanner); 
    549710 
    550711    return status; 
     
    615776    if (!len) return -1; 
    616777     
     778    pj_bzero(hurl, sizeof(*hurl)); 
    617779    pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error); 
    618780 
     
    635797        } 
    636798 
    637         if (pj_scan_strcmp(&scanner, HTTP_SEPARATOR, 
    638                            pj_ansi_strlen(HTTP_SEPARATOR)))  
    639         { 
     799        if (pj_scan_strcmp(&scanner, "://", 3)) { 
    640800            PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name 
    641801        } 
    642         pj_scan_advance_n(&scanner, pj_ansi_strlen(HTTP_SEPARATOR), PJ_FALSE); 
     802        pj_scan_advance_n(&scanner, 3, PJ_FALSE); 
     803 
     804        if (pj_memchr(url->ptr, '@', url->slen)) { 
     805            /* Parse username and password */ 
     806            pj_scan_get_until_chr(&scanner, ":@", &hurl->username); 
     807            if (*scanner.curptr == ':') { 
     808                pj_scan_get_char(&scanner); 
     809                pj_scan_get_until_chr(&scanner, "@", &hurl->passwd); 
     810            } else { 
     811                hurl->passwd.slen = 0; 
     812            } 
     813            pj_scan_get_char(&scanner); 
     814        } 
    643815 
    644816        /* Parse the host and port number (if any) */ 
    645817        pj_scan_get_until_chr(&scanner, ":/", &s); 
    646818        pj_strassign(&hurl->host, &s); 
     819        if (hurl->host.slen==0) 
     820            PJ_THROW(PJ_EINVAL); 
    647821        if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') { 
    648822            /* No port number specified */ 
     
    693867    pj_pool_t *own_pool; 
    694868    pj_http_req *hreq; 
     869    char *at_pos; 
    695870    pj_status_t status; 
    696871 
     
    737912 
    738913    /* Parse the URL */ 
    739     if (!pj_strdup(hreq->pool, &hreq->url, url)) 
     914    if (!pj_strdup_with_null(hreq->pool, &hreq->url, url)) { 
     915        pj_pool_release(hreq->pool); 
    740916        return PJ_ENOMEM; 
     917    } 
    741918    status = pj_http_req_parse_url(&hreq->url, &hreq->hurl); 
    742     if (status != PJ_SUCCESS) 
     919    if (status != PJ_SUCCESS) { 
     920        pj_pool_release(hreq->pool); 
    743921        return status; // Invalid URL supplied 
     922    } 
     923 
     924    /* If URL contains username/password, move them to credential and 
     925     * remove them from the URL. 
     926     */ 
     927    if ((at_pos=pj_strchr(&hreq->url, '@')) != NULL) { 
     928        pj_str_t tmp; 
     929        char *user_pos = pj_strchr(&hreq->url, '/'); 
     930        int removed_len; 
     931 
     932        /* Save credential first, unescape the string */ 
     933        tmp = pj_str_unescape(hreq->pool, &hreq->hurl.username);; 
     934        pj_strdup(hreq->pool, &hreq->param.auth_cred.username, &tmp); 
     935 
     936        tmp = pj_str_unescape(hreq->pool, &hreq->hurl.passwd); 
     937        pj_strdup(hreq->pool, &hreq->param.auth_cred.data, &tmp); 
     938 
     939        hreq->hurl.username.ptr = hreq->hurl.passwd.ptr = NULL; 
     940        hreq->hurl.username.slen = hreq->hurl.passwd.slen = 0; 
     941 
     942        /* Remove "username:password@" from the URL */ 
     943        pj_assert(user_pos != 0 && user_pos < at_pos); 
     944        user_pos += 2; 
     945        removed_len = at_pos + 1 - user_pos; 
     946        pj_memmove(user_pos, at_pos+1, hreq->url.ptr+hreq->url.slen-at_pos-1); 
     947        hreq->url.slen -= removed_len; 
     948 
     949        /* Need to adjust hostname and path pointers due to memmove*/ 
     950        if (hreq->hurl.host.ptr > user_pos && 
     951            hreq->hurl.host.ptr < user_pos + hreq->url.slen) 
     952        { 
     953            hreq->hurl.host.ptr -= removed_len; 
     954        } 
     955        /* path may come from a string constant, don't shift it if so */ 
     956        if (hreq->hurl.path.ptr > user_pos && 
     957            hreq->hurl.path.ptr < user_pos + hreq->url.slen) 
     958        { 
     959            hreq->hurl.path.ptr -= removed_len; 
     960        } 
     961    } 
    744962 
    745963    *http_req = hreq; 
     
    771989    PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY); 
    772990 
     991    /* Reset few things to make sure restarting works */ 
    773992    http_req->error = 0; 
     993    http_req->response.headers.count = 0; 
     994    pj_bzero(&http_req->tcp_state, sizeof(http_req->tcp_state)); 
    774995 
    775996    if (!http_req->resolved) { 
     
    8381059} 
    8391060 
    840 #define STR_PREC(s) s.slen, s.ptr 
     1061/* Respond to basic authentication challenge */ 
     1062static pj_status_t auth_respond_basic(pj_http_req *hreq) 
     1063{ 
     1064    /* Basic authentication: 
     1065     *      credentials       = "Basic" basic-credentials 
     1066     *      basic-credentials = base64-user-pass 
     1067     *      base64-user-pass  = <base64 [4] encoding of user-pass> 
     1068     *      user-pass         = userid ":" password 
     1069     * 
     1070     * Sample: 
     1071     *       Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== 
     1072     */ 
     1073    pj_str_t user_pass; 
     1074    pj_http_header_elmt *phdr; 
     1075    int len; 
     1076 
     1077    /* Use send buffer to store userid ":" password */ 
     1078    user_pass.ptr = hreq->buffer.ptr; 
     1079    pj_strcpy(&user_pass, &hreq->param.auth_cred.username); 
     1080    pj_strcat2(&user_pass, ":"); 
     1081    pj_strcat(&user_pass, &hreq->param.auth_cred.data); 
     1082 
     1083    /* Create Authorization header */ 
     1084    phdr = &hreq->param.headers.header[hreq->param.headers.count++]; 
     1085    pj_bzero(phdr, sizeof(*phdr)); 
     1086    if (hreq->response.status_code == 401) 
     1087        phdr->name = pj_str("Authorization"); 
     1088    else 
     1089        phdr->name = pj_str("Proxy-Authorization"); 
     1090 
     1091    len = PJ_BASE256_TO_BASE64_LEN(user_pass.slen) + 10; 
     1092    phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len); 
     1093    phdr->value.slen = 0; 
     1094 
     1095    pj_strcpy2(&phdr->value, "Basic "); 
     1096    len -= phdr->value.slen; 
     1097    pj_base64_encode((pj_uint8_t*)user_pass.ptr, (int)user_pass.slen, 
     1098                     phdr->value.ptr + phdr->value.slen, &len); 
     1099    phdr->value.slen += len; 
     1100 
     1101    return PJ_SUCCESS; 
     1102} 
     1103 
     1104/** Length of digest string. */ 
     1105#define MD5_STRLEN 32 
     1106/* A macro just to get rid of type mismatch between char and unsigned char */ 
     1107#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, len) 
     1108 
     1109/* Transform digest to string. 
     1110 * output must be at least PJSIP_MD5STRLEN+1 bytes. 
     1111 * 
     1112 * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED! 
     1113 */ 
     1114static void digest2str(const unsigned char digest[], char *output) 
     1115{ 
     1116    int i; 
     1117    for (i = 0; i<16; ++i) { 
     1118        pj_val_to_hex_digit(digest[i], output); 
     1119        output += 2; 
     1120    } 
     1121} 
     1122 
     1123static void auth_create_digest_response(pj_str_t *result, 
     1124                                        const pj_http_auth_cred *cred, 
     1125                                        const pj_str_t *nonce, 
     1126                                        const pj_str_t *nc, 
     1127                                        const pj_str_t *cnonce, 
     1128                                        const pj_str_t *qop, 
     1129                                        const pj_str_t *uri, 
     1130                                        const pj_str_t *realm, 
     1131                                        const pj_str_t *method) 
     1132{ 
     1133    char ha1[MD5_STRLEN]; 
     1134    char ha2[MD5_STRLEN]; 
     1135    unsigned char digest[16]; 
     1136    pj_md5_context pms; 
     1137 
     1138    pj_assert(result->slen >= MD5_STRLEN); 
     1139 
     1140    TRACE_((THIS_FILE, "Begin creating digest")); 
     1141 
     1142    if (cred->data_type == 0) { 
     1143        /*** 
     1144         *** ha1 = MD5(username ":" realm ":" password) 
     1145         ***/ 
     1146        pj_md5_init(&pms); 
     1147        MD5_APPEND( &pms, cred->username.ptr, cred->username.slen); 
     1148        MD5_APPEND( &pms, ":", 1); 
     1149        MD5_APPEND( &pms, realm->ptr, realm->slen); 
     1150        MD5_APPEND( &pms, ":", 1); 
     1151        MD5_APPEND( &pms, cred->data.ptr, cred->data.slen); 
     1152        pj_md5_final(&pms, digest); 
     1153 
     1154        digest2str(digest, ha1); 
     1155 
     1156    } else if (cred->data_type == 1) { 
     1157        pj_assert(cred->data.slen == 32); 
     1158        pj_memcpy( ha1, cred->data.ptr, cred->data.slen ); 
     1159    } else { 
     1160        pj_assert(!"Invalid data_type"); 
     1161    } 
     1162 
     1163    TRACE_((THIS_FILE, "  ha1=%.32s", ha1)); 
     1164 
     1165    /*** 
     1166     *** ha2 = MD5(method ":" req_uri) 
     1167     ***/ 
     1168    pj_md5_init(&pms); 
     1169    MD5_APPEND( &pms, method->ptr, method->slen); 
     1170    MD5_APPEND( &pms, ":", 1); 
     1171    MD5_APPEND( &pms, uri->ptr, uri->slen); 
     1172    pj_md5_final(&pms, digest); 
     1173    digest2str(digest, ha2); 
     1174 
     1175    TRACE_((THIS_FILE, "  ha2=%.32s", ha2)); 
     1176 
     1177    /*** 
     1178     *** When qop is not used: 
     1179     ***    response = MD5(ha1 ":" nonce ":" ha2) 
     1180     *** 
     1181     *** When qop=auth is used: 
     1182     ***    response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) 
     1183     ***/ 
     1184    pj_md5_init(&pms); 
     1185    MD5_APPEND( &pms, ha1, MD5_STRLEN); 
     1186    MD5_APPEND( &pms, ":", 1); 
     1187    MD5_APPEND( &pms, nonce->ptr, nonce->slen); 
     1188    if (qop && qop->slen != 0) { 
     1189        MD5_APPEND( &pms, ":", 1); 
     1190        MD5_APPEND( &pms, nc->ptr, nc->slen); 
     1191        MD5_APPEND( &pms, ":", 1); 
     1192        MD5_APPEND( &pms, cnonce->ptr, cnonce->slen); 
     1193        MD5_APPEND( &pms, ":", 1); 
     1194        MD5_APPEND( &pms, qop->ptr, qop->slen); 
     1195    } 
     1196    MD5_APPEND( &pms, ":", 1); 
     1197    MD5_APPEND( &pms, ha2, MD5_STRLEN); 
     1198 
     1199    /* This is the final response digest. */ 
     1200    pj_md5_final(&pms, digest); 
     1201 
     1202    /* Convert digest to string and store in chal->response. */ 
     1203    result->slen = MD5_STRLEN; 
     1204    digest2str(digest, result->ptr); 
     1205 
     1206    TRACE_((THIS_FILE, "  digest=%.32s", result->ptr)); 
     1207    TRACE_((THIS_FILE, "Digest created")); 
     1208} 
     1209 
     1210/* Find out if qop offer contains "auth" token */ 
     1211static pj_bool_t auth_has_qop( pj_pool_t *pool, const pj_str_t *qop_offer) 
     1212{ 
     1213    pj_str_t qop; 
     1214    char *p; 
     1215 
     1216    pj_strdup_with_null( pool, &qop, qop_offer); 
     1217    p = qop.ptr; 
     1218    while (*p) { 
     1219        *p = (char)pj_tolower(*p); 
     1220        ++p; 
     1221    } 
     1222 
     1223    p = qop.ptr; 
     1224    while (*p) { 
     1225        if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') { 
     1226            int e = *(p+4); 
     1227            if (e=='"' || e==',' || e==0) 
     1228                return PJ_TRUE; 
     1229            else 
     1230                p += 4; 
     1231        } else { 
     1232            ++p; 
     1233        } 
     1234    } 
     1235 
     1236    return PJ_FALSE; 
     1237} 
     1238 
     1239#define STR_PREC(s) (int)(s).slen, (s).ptr 
     1240 
     1241/* Respond to digest authentication */ 
     1242static pj_status_t auth_respond_digest(pj_http_req *hreq) 
     1243{ 
     1244    const pj_http_auth_chal *chal = &hreq->response.auth_chal; 
     1245    const pj_http_auth_cred *cred = &hreq->param.auth_cred; 
     1246    pj_http_header_elmt *phdr; 
     1247    char digest_response_buf[MD5_STRLEN]; 
     1248    int len; 
     1249    pj_str_t digest_response; 
     1250 
     1251    /* Check algorithm is supported. We only support MD5 */ 
     1252    if (chal->algorithm.slen!=0 && 
     1253        pj_stricmp2(&chal->algorithm, "MD5")) 
     1254    { 
     1255        TRACE_((THIS_FILE, "Error: Unsupported digest algorithm \"%.*s\"", 
     1256                  chal->algorithm.slen, chal->algorithm.ptr)); 
     1257        return PJ_ENOTSUP; 
     1258    } 
     1259 
     1260    /* Add Authorization header */ 
     1261    phdr = &hreq->param.headers.header[hreq->param.headers.count++]; 
     1262    pj_bzero(phdr, sizeof(*phdr)); 
     1263    if (hreq->response.status_code == 401) 
     1264        phdr->name = pj_str("Authorization"); 
     1265    else 
     1266        phdr->name = pj_str("Proxy-Authorization"); 
     1267 
     1268    /* Allocate space for the header */ 
     1269    len = 8 + /* Digest */ 
     1270          16 + hreq->param.auth_cred.username.slen + /* username= */ 
     1271          12 + chal->realm.slen + /* realm= */ 
     1272          12 + chal->nonce.slen + /* nonce= */ 
     1273          8 + hreq->hurl.path.slen + /* uri= */ 
     1274          16 + /* algorithm=MD5 */ 
     1275          16 + MD5_STRLEN + /* response= */ 
     1276          12 + /* qop=auth */ 
     1277          8 + /* nc=.. */ 
     1278          30 + /* cnonce= */ 
     1279          0; 
     1280    phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len); 
     1281 
     1282    /* Configure buffer to temporarily store the digest */ 
     1283    digest_response.ptr = digest_response_buf; 
     1284    digest_response.slen = MD5_STRLEN; 
     1285 
     1286    if (chal->qop.slen == 0) { 
     1287        const pj_str_t STR_MD5 = { "MD5", 3 }; 
     1288 
     1289        /* Server doesn't require quality of protection. */ 
     1290        auth_create_digest_response(&digest_response, cred, 
     1291                                    &chal->nonce, NULL, NULL,  NULL, 
     1292                                    &hreq->hurl.path, &chal->realm, 
     1293                                    &hreq->param.method); 
     1294 
     1295        len = pj_ansi_snprintf( 
     1296                phdr->value.ptr, len, 
     1297                "Digest username=\"%.*s\", " 
     1298                "realm=\"%.*s\", " 
     1299                "nonce=\"%.*s\", " 
     1300                "uri=\"%.*s\", " 
     1301                "algorithm=%.*s, " 
     1302                "response=\"%.*s\"", 
     1303                STR_PREC(cred->username), 
     1304                STR_PREC(chal->realm), 
     1305                STR_PREC(chal->nonce), 
     1306                STR_PREC(hreq->hurl.path), 
     1307                STR_PREC(STR_MD5), 
     1308                STR_PREC(digest_response)); 
     1309        if (len < 0) 
     1310            return PJ_ETOOSMALL; 
     1311        phdr->value.slen = len; 
     1312 
     1313    } else if (auth_has_qop(hreq->pool, &chal->qop)) { 
     1314        /* Server requires quality of protection. 
     1315         * We respond with selecting "qop=auth" protection. 
     1316         */ 
     1317        const pj_str_t STR_MD5 = { "MD5", 3 }; 
     1318        const pj_str_t qop = pj_str("auth"); 
     1319        const pj_str_t nc = pj_str("1"); 
     1320        const pj_str_t cnonce = pj_str("b39971"); 
     1321 
     1322        auth_create_digest_response(&digest_response, cred, 
     1323                                    &chal->nonce, &nc, &cnonce, &qop, 
     1324                                    &hreq->hurl.path, &chal->realm, 
     1325                                    &hreq->param.method); 
     1326        len = pj_ansi_snprintf( 
     1327                phdr->value.ptr, len, 
     1328                "Digest username=\"%.*s\", " 
     1329                "realm=\"%.*s\", " 
     1330                "nonce=\"%.*s\", " 
     1331                "uri=\"%.*s\", " 
     1332                "algorithm=%.*s, " 
     1333                "response=\"%.*s\", " 
     1334                "qop=%.*s, " 
     1335                "nc=%.*s, " 
     1336                "cnonce=\"%.*s\"", 
     1337                STR_PREC(cred->username), 
     1338                STR_PREC(chal->realm), 
     1339                STR_PREC(chal->nonce), 
     1340                STR_PREC(hreq->hurl.path), 
     1341                STR_PREC(STR_MD5), 
     1342                STR_PREC(digest_response), 
     1343                STR_PREC(qop), 
     1344                STR_PREC(nc), 
     1345                STR_PREC(cnonce)); 
     1346        if (len < 0) 
     1347            return PJ_ETOOSMALL; 
     1348        phdr->value.slen = len; 
     1349 
     1350    } else { 
     1351        /* Server requires quality protection that we don't support. */ 
     1352        TRACE_((THIS_FILE, "Error: Unsupported qop offer %.*s", 
     1353                chal->qop.slen, chal->qop.ptr)); 
     1354        return PJ_ENOTSUP; 
     1355    } 
     1356 
     1357    return PJ_SUCCESS; 
     1358} 
     1359 
     1360 
     1361static void restart_req_with_auth(pj_http_req *hreq) 
     1362{ 
     1363    pj_http_auth_chal *chal = &hreq->response.auth_chal; 
     1364    pj_http_auth_cred *cred = &hreq->param.auth_cred; 
     1365    pj_status_t status; 
     1366 
     1367    if (hreq->param.headers.count >= PJ_HTTP_HEADER_SIZE) { 
     1368        TRACE_((THIS_FILE, "Error: no place to put Authorization header")); 
     1369        hreq->auth_state = AUTH_DONE; 
     1370        return; 
     1371    } 
     1372 
     1373    /* If credential specifies specific scheme, make sure they match */ 
     1374    if (cred->scheme.slen && pj_stricmp(&chal->scheme, &cred->scheme)) { 
     1375        status = PJ_ENOTSUP; 
     1376        TRACE_((THIS_FILE, "Error: auth schemes mismatch")); 
     1377        goto on_error; 
     1378    } 
     1379 
     1380    /* If credential specifies specific realm, make sure they match */ 
     1381    if (cred->realm.slen && pj_stricmp(&chal->realm, &cred->realm)) { 
     1382        status = PJ_ENOTSUP; 
     1383        TRACE_((THIS_FILE, "Error: auth realms mismatch")); 
     1384        goto on_error; 
     1385    } 
     1386 
     1387    if (!pj_stricmp2(&chal->scheme, "basic")) { 
     1388        status = auth_respond_basic(hreq); 
     1389    } else if (!pj_stricmp2(&chal->scheme, "digest")) { 
     1390        status = auth_respond_digest(hreq); 
     1391    } else { 
     1392        TRACE_((THIS_FILE, "Error: unsupported HTTP auth scheme")); 
     1393        status = PJ_ENOTSUP; 
     1394    } 
     1395 
     1396    if (status != PJ_SUCCESS) 
     1397        goto on_error; 
     1398 
     1399    http_req_end_request(hreq); 
     1400 
     1401    status = pj_http_req_start(hreq); 
     1402    if (status != PJ_SUCCESS) 
     1403        goto on_error; 
     1404 
     1405    hreq->auth_state = AUTH_RETRYING; 
     1406    return; 
     1407 
     1408on_error: 
     1409    hreq->auth_state = AUTH_DONE; 
     1410} 
     1411 
    8411412 
    8421413/* snprintf() to a pj_str_t struct with an option to append the  
  • pjproject/trunk/pjsip-apps/src/samples/httpdemo.c

    r3245 r3321  
    4646static void on_response(pj_http_req *http_req, const pj_http_resp *resp) 
    4747{ 
    48         PJ_UNUSED_ARG(http_req); 
     48    unsigned i; 
     49 
     50    PJ_UNUSED_ARG(http_req); 
    4951    PJ_LOG(3,(THIS_FILE, "%.*s %d %.*s", (int)resp->version.slen, resp->version.ptr, 
    5052                                           resp->status_code, 
    5153                                           (int)resp->reason.slen, resp->reason.ptr)); 
     54 
     55    for (i=0; i<resp->headers.count; ++i) { 
     56        const pj_http_header_elmt *h = &resp->headers.header[i]; 
     57 
     58        if (!pj_stricmp2(&h->name, "Content-Length") || 
     59            !pj_stricmp2(&h->name, "Content-Type")) 
     60        { 
     61            PJ_LOG(3,(THIS_FILE, "%.*s: %.*s", 
     62                      (int)h->name.slen, h->name.ptr, 
     63                      (int)h->value.slen, h->value.ptr)); 
     64        } 
     65    } 
    5266} 
    5367 
Note: See TracChangeset for help on using the changeset viewer.