- Timestamp:
- Sep 27, 2010 8:35:08 AM (14 years ago)
- Location:
- pjproject/trunk
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
pjproject/trunk/pjlib-util/include/pjlib-util/http_client.h
r3227 r3321 50 50 51 51 /** 52 * HTTP header representation. 53 */ 54 typedef 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 /** 52 61 * This structure describes http request/response headers. 53 62 * Application should call #pj_http_headers_add_elmt() to … … 56 65 typedef struct pj_http_headers 57 66 { 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]; 64 72 } pj_http_headers; 73 74 /** 75 * Structure to save HTTP authentication credential. 76 */ 77 typedef 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 65 122 66 123 /** … … 126 183 /**< will be provided later */ 127 184 } reqdata; 185 186 /** 187 * Authentication credential needed to respond to 401/407 response. 188 */ 189 pj_http_auth_cred auth_cred; 190 128 191 } pj_http_req_param; 192 193 /** 194 * HTTP authentication challenge, parsed from WWW-Authenticate header. 195 */ 196 typedef 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; 129 207 130 208 /** … … 137 215 pj_str_t reason; /**< Reason phrase */ 138 216 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. */ 144 221 void *data; /**< Data received */ 145 222 pj_size_t size; /**< Data size */ … … 151 228 typedef struct pj_http_url 152 229 { 230 pj_str_t username; /**< Username part */ 231 pj_str_t passwd; /**< Password part */ 153 232 pj_str_t protocol; /**< Protocol used */ 154 233 pj_str_t host; /**< Host name */ -
pjproject/trunk/pjlib-util/src/pjlib-util-test/http_client.c
r3262 r3321 238 238 239 239 240 pj_status_t parse_url(const char *url )240 pj_status_t parse_url(const char *url, pj_http_url *hurl) 241 241 { 242 242 pj_str_t surl; 243 pj_http_url hurl;244 243 pj_status_t status; 245 244 246 245 pj_cstr(&surl, url); 247 status = pj_http_req_parse_url(&surl, &hurl);246 status = pj_http_req_parse_url(&surl, hurl); 248 247 #ifdef VERBOSE 249 248 if (!status) { 250 249 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)); 253 252 } else { 254 253 } … … 257 256 } 258 257 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; 258 static 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 } 283 359 284 360 return 0; -
pjproject/trunk/pjlib-util/src/pjlib-util/http_client.c
r3236 r3321 21 21 #include <pj/activesock.h> 22 22 #include <pj/assert.h> 23 #include <pj/ctype.h> 23 24 #include <pj/errno.h> 24 25 #include <pj/except.h> … … 26 27 #include <pj/string.h> 27 28 #include <pj/timer.h> 29 #include <pjlib-util/base64.h> 28 30 #include <pjlib-util/errno.h> 31 #include <pjlib-util/md5.h> 29 32 #include <pjlib-util/scanner.h> 33 #include <pjlib-util/string.h> 30 34 31 35 #if 0 … … 40 44 #define HTTP_1_0 "1.0" 41 45 #define HTTP_1_1 "1.1" 42 #define HTTP_SEPARATOR "://"43 46 #define CONTENT_LENGTH "Content-Length" 44 47 /* Buffer size for sending/receiving messages. */ … … 94 97 READING_COMPLETE, 95 98 ABORTING, 99 }; 100 101 enum 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. */ 96 106 }; 97 107 … … 110 120 pj_str_t buffer; /* Buffer to send/receive msgs */ 111 121 enum http_state state; /* State of the HTTP request */ 122 enum auth_state auth_state; /* Authentication state */ 112 123 pj_timer_entry timer_entry;/* Timer entry */ 113 124 pj_bool_t resolved; /* Whether URL's host is resolved */ … … 143 154 void *data, pj_size_t size, 144 155 pj_size_t *remainder); 156 /* Restart the request with authentication */ 157 static void restart_req_with_auth(pj_http_req *hreq); 158 /* Parse authentication challenge */ 159 static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, 160 pj_http_auth_chal *chal); 145 161 146 162 static pj_uint16_t get_http_default_port(const pj_str_t *protocol) … … 319 335 hreq->response.size = size - rem; 320 336 } 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 321 388 /* We already received the response header, call the 322 * appropriate callback. 389 * appropriate callback. 323 390 */ 324 391 if (hreq->cb.on_response) … … 385 452 (status == PJ_EEOF && hreq->response.content_length == -1)) 386 453 { 387 454 /* Finish reading */ 388 455 http_req_end_request(hreq); 389 456 hreq->response.size = hreq->tcp_state.current_read_size; … … 434 501 } 435 502 503 /* Parse authentication challenge */ 504 static 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 436 595 /* The same as #pj_http_headers_add_elmt() with char * as 437 596 * its parameters. … … 465 624 pj_size_t i; 466 625 char *cptr; 467 char * newdata;626 char *end_status, *newdata; 468 627 pj_scanner scanner; 469 628 pj_str_t s; 629 const pj_str_t STR_CONTENT_LENGTH = { CONTENT_LENGTH, 14 }; 470 630 pj_status_t status; 471 631 … … 518 678 PJ_END; 519 679 680 end_status = scanner.curptr; 681 pj_scan_fini(&scanner); 682 520 683 /* Parse the response headers. */ 521 size = i - 2 - ( scanner.curptr- newdata);684 size = i - 2 - (end_status - newdata); 522 685 if (size > 0) { 523 status = http_headers_parse( scanner.curptr + 1, size,686 status = http_headers_parse(end_status + 1, size, 524 687 &response->headers); 525 688 } else { … … 529 692 /* Find content-length header field. */ 530 693 for (i = 0; i < response->headers.count; i++) { 531 if (!pj_stricmp 2(&response->headers.header[i].name,532 CONTENT_LENGTH))694 if (!pj_stricmp(&response->headers.header[i].name, 695 &STR_CONTENT_LENGTH)) 533 696 { 534 697 response->content_length = … … 545 708 } 546 709 } 547 548 pj_scan_fini(&scanner);549 710 550 711 return status; … … 615 776 if (!len) return -1; 616 777 778 pj_bzero(hurl, sizeof(*hurl)); 617 779 pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error); 618 780 … … 635 797 } 636 798 637 if (pj_scan_strcmp(&scanner, HTTP_SEPARATOR, 638 pj_ansi_strlen(HTTP_SEPARATOR))) 639 { 799 if (pj_scan_strcmp(&scanner, "://", 3)) { 640 800 PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name 641 801 } 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 } 643 815 644 816 /* Parse the host and port number (if any) */ 645 817 pj_scan_get_until_chr(&scanner, ":/", &s); 646 818 pj_strassign(&hurl->host, &s); 819 if (hurl->host.slen==0) 820 PJ_THROW(PJ_EINVAL); 647 821 if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') { 648 822 /* No port number specified */ … … 693 867 pj_pool_t *own_pool; 694 868 pj_http_req *hreq; 869 char *at_pos; 695 870 pj_status_t status; 696 871 … … 737 912 738 913 /* 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); 740 916 return PJ_ENOMEM; 917 } 741 918 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); 743 921 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 } 744 962 745 963 *http_req = hreq; … … 771 989 PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY); 772 990 991 /* Reset few things to make sure restarting works */ 773 992 http_req->error = 0; 993 http_req->response.headers.count = 0; 994 pj_bzero(&http_req->tcp_state, sizeof(http_req->tcp_state)); 774 995 775 996 if (!http_req->resolved) { … … 838 1059 } 839 1060 840 #define STR_PREC(s) s.slen, s.ptr 1061 /* Respond to basic authentication challenge */ 1062 static 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 */ 1114 static 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 1123 static 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 */ 1211 static 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 */ 1242 static 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 1361 static 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 1408 on_error: 1409 hreq->auth_state = AUTH_DONE; 1410 } 1411 841 1412 842 1413 /* snprintf() to a pj_str_t struct with an option to append the -
pjproject/trunk/pjsip-apps/src/samples/httpdemo.c
r3245 r3321 46 46 static void on_response(pj_http_req *http_req, const pj_http_resp *resp) 47 47 { 48 PJ_UNUSED_ARG(http_req); 48 unsigned i; 49 50 PJ_UNUSED_ARG(http_req); 49 51 PJ_LOG(3,(THIS_FILE, "%.*s %d %.*s", (int)resp->version.slen, resp->version.ptr, 50 52 resp->status_code, 51 53 (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 } 52 66 } 53 67
Note: See TracChangeset
for help on using the changeset viewer.