http-parser.c (21635B)
1 // vim:fdm=marker:fdl=0 2 3 #ifdef __cplusplus 4 extern "C" { 5 #endif 6 7 #include <stdlib.h> 8 #include <stdio.h> 9 #include <string.h> 10 11 #include "finwo/asprintf.h" 12 #include "finwo/strnstr.h" 13 #include "tidwall/buf.h" 14 15 #include "http-parser.h" 16 #include "http-parser-statusses.h" 17 18 const int _HTTP_PARSER_STATE_INIT = 0; 19 const int _HTTP_PARSER_STATE_HEADER = 1; 20 const int _HTTP_PARSER_STATE_BODY = 2; 21 const int _HTTP_PARSER_STATE_BODY_CHUNKED = 3; 22 const int _HTTP_PARSER_STATE_DONE = 4; 23 const int _HTTP_PARSER_STATE_PANIC = 666; 24 25 #ifndef NULL 26 #define NULL ((void*)0) 27 #endif 28 29 #if defined(_WIN32) || defined(_WIN64) 30 #ifndef strcasecmp 31 #define strcasecmp _stricmp 32 #endif 33 #endif 34 35 // xtoi {{{ 36 /** 37 * Convert hexidecimal string to int 38 */ 39 int xtoi(char *str) { 40 char *p = str; 41 int i = 0; 42 int sign = 1; 43 while(*p) { 44 if ( *p == '-' ) sign = -1; 45 46 if ( *p >= '0' && *p <= '9' ) { 47 i *= 16; 48 i += (*p) - '0'; 49 } 50 51 if ( *p >= 'a' && *p <= 'f' ) { 52 i *= 16; 53 i += 10 + (*p) - 'a'; 54 } 55 56 if ( *p >= 'A' && *p <= 'F' ) { 57 i *= 16; 58 i += 10 + (*p) - 'A'; 59 } 60 61 p = p+1; 62 } 63 64 return sign * i; 65 } 66 // }}} 67 68 // non-exported structs {{{ 69 struct http_parser_meta { 70 char *key; 71 char *value; 72 }; 73 // }}} 74 75 // Meta management {{{ 76 77 static int fn_meta_cmp(const void *a, const void *b, void *udata) { 78 const struct http_parser_meta *ta = (struct http_parser_meta *)a; 79 const struct http_parser_meta *tb = (struct http_parser_meta *)b; 80 return strcasecmp(ta->key, tb->key); 81 } 82 83 static void fn_meta_purge(void *item, void *udata) { 84 struct http_parser_meta *subject = (struct http_parser_meta *)item; 85 free(subject->key); 86 free(subject->value); 87 free(subject); 88 } 89 90 struct http_parser_meta * _http_parser_meta_get(struct http_parser_message *subject, const char *key) { 91 struct http_parser_meta pattern = { .key = (char*)key }; 92 return mindex_get(subject->meta, &pattern); 93 } 94 const char * http_parser_meta_get(struct http_parser_message *subject, const char *key) { 95 struct http_parser_meta *found = _http_parser_meta_get(subject, key); 96 if (!found) return NULL; 97 return found->value; 98 } 99 100 void _http_parser_meta_set(struct http_parser_message *subject, const char *key, const char *value) { 101 struct http_parser_meta *meta = calloc(1, sizeof(struct http_parser_meta)); 102 meta->key = strdup(key); 103 meta->value = strdup(value); 104 mindex_set(subject->meta, meta); 105 } 106 void http_parser_meta_set(struct http_parser_message *subject, const char *key, const char *value) { 107 _http_parser_meta_set(subject, key, value); 108 } 109 110 void _http_parser_meta_del(struct http_parser_message *subject, const char *key) { 111 struct http_parser_meta pattern = { .key = (char*)key }; 112 mindex_delete(subject->meta, &pattern); 113 } 114 void http_parser_meta_del(struct http_parser_message *subject, const char *key) { 115 _http_parser_meta_del(subject, key); 116 } 117 // }}} 118 119 // Header management {{{ 120 121 struct http_parser_meta * _http_parser_header_get(struct http_parser_message *subject, const char *key) { 122 char *meta = calloc(strlen(key) + 8, sizeof(char)); 123 strcat(meta, "header:"); 124 strcat(meta, key); 125 struct http_parser_meta *result = _http_parser_meta_get(subject, meta); 126 free(meta); 127 return result; 128 } 129 130 /** 131 * Searches for the given key in the list of headers 132 * Returns the header's value or NULL if not found 133 */ 134 const char *http_parser_header_get(struct http_parser_message *subject, const char *key) { 135 struct http_parser_meta *header = _http_parser_header_get(subject, key); 136 if (!header) return NULL; 137 return header->value; 138 } 139 140 void _http_parser_header_set(struct http_parser_message *subject, const char *key, const char *value) { 141 char *meta = calloc(strlen(key) + 8, sizeof(char)); 142 strcat(meta, "header:"); 143 strcat(meta, key); 144 _http_parser_meta_set(subject, meta, value); 145 free(meta); 146 } 147 148 /** 149 * Write a header into the subject's list of headers 150 */ 151 void http_parser_header_set(struct http_parser_message *subject, const char *key, const char *value) { 152 return _http_parser_header_set(subject, key, value); 153 } 154 155 void _http_parser_header_del(struct http_parser_message *subject, const char *key) { 156 char *meta = calloc(strlen(key) + 8, sizeof(char)); 157 strcat(meta, "header:"); 158 strcat(meta, key); 159 _http_parser_meta_del(subject, meta); 160 free(meta); 161 } 162 163 void http_parser_header_del(struct http_parser_message *subject, const char *key) { 164 _http_parser_header_del(subject, key); 165 } 166 167 // }}} 168 169 /** 170 * Frees everything in a http_message that was malloc'd by http-parser 171 */ 172 void http_parser_message_free(struct http_parser_message *subject) { 173 if (subject->method ) free(subject->method); 174 if (subject->path ) free(subject->path); 175 if (subject->version) free(subject->version); 176 if (subject->body ) { buf_clear(subject->body); free(subject->body); } 177 if (subject->meta ) mindex_free(subject->meta); 178 if (subject->buf ) free(subject->buf); 179 free(subject); 180 } 181 182 /** 183 * Frees everything in a http pair that was malloc'd by http-parser 184 */ 185 void http_parser_pair_free(struct http_parser_pair *pair) { 186 if (pair->request) http_parser_message_free(pair->request); 187 if (pair->response) http_parser_message_free(pair->response); 188 free(pair); 189 } 190 191 /** 192 * Initializes a http_message as request 193 */ 194 struct http_parser_message * http_parser_request_init() { 195 struct http_parser_message *message = calloc(1, sizeof(struct http_parser_message)); 196 message->chunksize = -1; 197 message->meta = mindex_init( 198 fn_meta_cmp, 199 fn_meta_purge, 200 NULL 201 ); 202 return message; 203 } 204 205 /** 206 * Initializes a http_message as reponse 207 */ 208 struct http_parser_message * http_parser_response_init() { 209 struct http_parser_message *message = http_parser_request_init(); 210 message->status = 200; 211 message->version = calloc(1,4); 212 strcpy(message->version, "1.1"); 213 return message; 214 } 215 216 /** 217 * Initialize a http_pair with userdata 218 */ 219 struct http_parser_pair * http_parser_pair_init(void *udata) { 220 struct http_parser_pair *pair = calloc(1, sizeof(struct http_parser_pair)); 221 pair->request = http_parser_request_init(); 222 pair->response = http_parser_response_init(); 223 pair->udata = udata; 224 return pair; 225 } 226 227 228 /** 229 * Removed N bytes from the beginning of the message body 230 */ 231 static void http_parser_message_remove_body_bytes(struct http_parser_message *message, int bytes) { 232 struct buf *newbuf = calloc(1, sizeof(struct buf)); 233 int size = message->body->len - bytes; 234 235 if (size > 0) { 236 buf_append(newbuf, message->body->data + bytes, size); 237 } 238 239 if (message->body) { 240 buf_clear(message->body); 241 free(message->body); 242 } 243 message->body = newbuf; 244 } 245 246 /** 247 * Removes the first string from a http_message's body 248 */ 249 static void http_parser_message_remove_body_string(struct http_parser_message *message) { 250 int length = strlen(message->body->data); 251 http_parser_message_remove_body_bytes(message, length + 2); 252 } 253 254 /** 255 * Reads a header from a message's body and removes that lines from the body 256 * 257 * Caution: does not support multi-line headers yet 258 */ 259 static int http_parser_message_read_header(struct http_parser_message *message) { 260 char *index; 261 262 // Require more data if no line break found 263 index = strnstr(message->body->data, "\r\n", message->body->len); 264 if (!index) return 1; 265 *(index) = '\0'; 266 267 // Detect end of headers 268 if (!strlen(message->body->data)) { // Using strlen, due to possible \r\n replacement 269 http_parser_message_remove_body_string(message); 270 return 0; 271 } 272 273 // Detect colon 274 index = strstr(message->body->data, ": "); 275 if (!index) { 276 http_parser_message_remove_body_string(message); 277 return 1; 278 } 279 280 // Split by the found colon & skip leading whitespace 281 *(index) = '\0'; 282 index++; 283 while(*(index) == ' ') index++; 284 285 // Insert the header in our map 286 _http_parser_header_set(message, message->body->data, index); 287 288 // Remove the header remainder 289 // Twice, because we split the string 290 http_parser_message_remove_body_string(message); 291 http_parser_message_remove_body_string(message); 292 return 2; 293 } 294 295 /** 296 * Reads chunked data 297 */ 298 static int http_parser_message_read_chunked(struct http_parser_message *message) { 299 char *aChunkSize; 300 char *index; 301 struct http_parser_event *ev; 302 303 // Attempt reading the chunk size 304 if (message->chunksize == -1) { 305 306 // Check if we have a line 307 index = strnstr(message->body->data, "\r\n", message->body->len); 308 if (!index) { 309 return 1; 310 } 311 *(index) = '\0'; 312 313 // Empty line = skip 314 if (!strlen(message->body->data)) { 315 http_parser_message_remove_body_string(message); 316 return 2; 317 } 318 319 // Read hex chunksize 320 aChunkSize = calloc(1, 17); 321 sscanf(message->body->data, "%16s", aChunkSize); 322 message->chunksize = xtoi(aChunkSize); 323 free(aChunkSize); 324 325 // Remove chunksize line 326 http_parser_message_remove_body_string(message); 327 328 // 0 = EOF 329 if (message->chunksize == 0) { 330 return 0; 331 } 332 333 // Signal we're still reading 334 return 2; 335 } 336 337 // Create buffer if not present yet 338 if (!message->buf) { 339 message->buf = calloc(1,sizeof(struct buf)); 340 } 341 342 // Ensure the body has enough data 343 if (message->body->len < message->chunksize) { 344 return 1; 345 } 346 347 // Either call onChunk method OR copy into message buffer 348 if (message->onChunk) { 349 // Call onChunk if the message has that set 350 ev = calloc(1,sizeof(struct http_parser_event)); 351 ev->udata = message->udata; 352 ev->chunk = &((struct buf){ 353 .len = message->chunksize, 354 .cap = message->chunksize, 355 .data = message->body->data, 356 }); 357 message->onChunk(ev); 358 free(ev); 359 } else { 360 buf_append(message->buf, message->body->data, message->chunksize); 361 } 362 363 // Remove chunk from receiving data and reset chunking 364 http_parser_message_remove_body_bytes(message, message->chunksize); 365 message->chunksize = -1; 366 367 // No error or end encountered 368 return 2; 369 } 370 371 const char * http_parser_status_message(int status) { 372 int i; 373 for(i=0; http_parser_statusses[i].status; i++) { 374 if (http_parser_statusses[i].status == status) { 375 return http_parser_statusses[i].message; 376 } 377 } 378 return NULL; 379 } 380 381 struct buf * http_parser_sprint_pair_response(struct http_parser_pair *pair) { 382 return http_parser_sprint_response(pair->response); 383 } 384 385 struct buf * http_parser_sprint_pair_request(struct http_parser_pair *pair) { 386 return http_parser_sprint_request(pair->request); 387 } 388 389 // Caution: headers should fit in 64k 390 struct buf * http_parser_sprint_response(struct http_parser_message *response) { 391 struct buf *result = calloc(1, sizeof(struct buf)); 392 result->cap = 65536; 393 result->data = calloc(1, result->cap); 394 395 // Status 396 sprintf(result->data, 397 "HTTP/%s %d %s\r\n" 398 , response->version 399 , response->status 400 , response->statusMessage ? response->statusMessage : http_parser_status_message(response->status) 401 ); 402 403 // Headers 404 int i; 405 struct http_parser_meta *meta; 406 if (response->meta) { 407 for(i=0; i<mindex_length(response->meta); i++) { 408 meta = mindex_nth(response->meta, i); 409 if (strncmp("header:", meta->key, 7)) continue; 410 strcat(result->data, (meta->key + 7)); 411 strcat(result->data, ": "); 412 strcat(result->data, meta->value); 413 strcat(result->data, "\r\n"); 414 } 415 } 416 417 strcat(result->data, "\r\n"); 418 419 // Treat result as buffer from here 420 result->len = strlen(result->data); 421 422 if (response->body) { 423 buf_append(result, response->body->data, response->body->len); 424 } 425 426 return result; 427 } 428 429 // Caution: headers should fit in 64k 430 struct buf * http_parser_sprint_request(struct http_parser_message *request) { 431 struct buf *result = calloc(1, sizeof(struct buf)); 432 result->cap = 65536; 433 result->data = calloc(1, result->cap); 434 435 char *tmppath; 436 char *path = request->path; 437 int isPathAllocced = 0; 438 if (!path) path = "/"; 439 if (strstr(path, "/") != path) { 440 tmppath = path; 441 path = NULL; 442 isPathAllocced = true; 443 asprintf(&path, "/%s", tmppath); 444 } 445 446 // Status 447 sprintf(result->data, 448 "%s %s" 449 , request->method 450 , path 451 ); 452 453 // Query 454 if (request->query) { 455 strcat(result->data, "?"); 456 strcat(result->data, request->query); 457 } 458 459 // HTTP version 460 strcat(result->data, " HTTP/"); 461 strcat(result->data, request->version); 462 strcat(result->data, "\r\n"); 463 464 // Headers 465 int i; 466 struct http_parser_meta *meta; 467 if (request->meta) { 468 for(i=0; i<mindex_length(request->meta); i++) { 469 meta = mindex_nth(request->meta, i); 470 if (strncmp("header:", meta->key, 7)) continue; 471 strcat(result->data, (meta->key + 7)); 472 strcat(result->data, ": "); 473 strcat(result->data, meta->value); 474 strcat(result->data, "\r\n"); 475 } 476 } 477 478 strcat(result->data, "\r\n"); 479 480 // Handle chunked header 481 const char *aTransferEncoding = http_parser_header_get(request, "transfer-encoding"); 482 int isChunked = 0; 483 if (aTransferEncoding && strcasecmp(aTransferEncoding, "chunked")) { 484 isChunked = 1; 485 sprintf(result->data + strlen(result->data), "%lx\r\n", request->body->len); 486 } 487 488 // Treat result as buffer from here 489 result->len = strlen(result->data); 490 491 if (request->body) { 492 buf_append(result, request->body->data, request->body->len); 493 } 494 495 if (isChunked) { 496 buf_append(result, "0\r\n\r\n", 5); 497 } 498 499 if (isPathAllocced) { 500 free(path); 501 } 502 503 return result; 504 } 505 506 /** 507 * Pass data into the pair's request 508 * 509 * Triggers onRequest if set 510 */ 511 void http_parser_pair_request_data(struct http_parser_pair *pair, const struct buf *data) { 512 struct http_parser_event *ev; 513 http_parser_request_data(pair->request, data); 514 if (pair->request->ready && pair->onRequest) { 515 ev = calloc(1,sizeof(struct http_parser_event)); 516 ev->request = pair->request; 517 ev->response = pair->response; 518 ev->pair = pair; 519 ev->udata = pair->udata; 520 pair->onRequest(ev); 521 free(ev); 522 pair->onRequest = NULL; 523 } 524 } 525 526 /** 527 * Pass data into the pair's request 528 * 529 * Triggers onResponse if set 530 */ 531 void http_parser_pair_response_data(struct http_parser_pair *pair, const struct buf *data) { 532 struct http_parser_event *ev; 533 http_parser_response_data(pair->response, data); 534 if (pair->response->ready && pair->onResponse) { 535 ev = calloc(1,sizeof(struct http_parser_event)); 536 ev->request = pair->request; 537 ev->response = pair->response; 538 ev->pair = pair; 539 ev->udata = pair->udata; 540 pair->onResponse(ev); 541 free(ev); 542 pair->onResponse = NULL; 543 } 544 } 545 546 /** 547 * Insert data into a http_message, acting as if it's a request 548 */ 549 void http_parser_request_data(struct http_parser_message *request, const struct buf *data) { 550 char *index; 551 const char *aContentLength; 552 int iContentLength; 553 const char *aChunkSize; 554 int res; 555 556 // Add event data to buffer 557 if (!request->body) request->body = calloc(1, sizeof(struct buf)); 558 buf_append(request->body, data->data, data->len); 559 560 while(1) { 561 switch(request->_state) { 562 case _HTTP_PARSER_STATE_PANIC: 563 return; 564 case _HTTP_PARSER_STATE_INIT: 565 566 // Wait for more data if not line break found 567 index = strstr(request->body->data, "\r\n"); 568 if (!index) return; 569 *(index) = '\0'; 570 571 // Read method and path 572 request->method = calloc(1, 16); 573 request->path = calloc(1, 8192); 574 request->version = calloc(1, 4); 575 if (sscanf(request->body->data, "%15s %8191s HTTP/%3s", request->method, request->path, request->version) != 3) { 576 request->_state = _HTTP_PARSER_STATE_PANIC; 577 return; 578 } 579 580 // Remove method line 581 http_parser_message_remove_body_string(request); 582 583 // Detect query 584 // No need to malloc, already done by sscanf 585 index = strstr(request->path, "?"); 586 if (index) { 587 *(index) = '\0'; 588 request->query = index + 1; 589 } 590 591 // Signal we're now reading headers 592 request->_state = _HTTP_PARSER_STATE_HEADER; 593 break; 594 595 case _HTTP_PARSER_STATE_HEADER: 596 if (!http_parser_message_read_header(request)) { 597 if ( 598 http_parser_header_get(request, "content-length") || 599 http_parser_header_get(request, "transfer-encoding") 600 ) { 601 request->_state = _HTTP_PARSER_STATE_BODY; 602 } else { 603 request->_state = _HTTP_PARSER_STATE_DONE; 604 } 605 } 606 break; 607 608 case _HTTP_PARSER_STATE_BODY: 609 610 // Detect chunked encoding 611 if (request->chunksize == -1) { 612 aChunkSize = http_parser_header_get(request, "transfer-encoding"); 613 if (aChunkSize) { 614 if (!strcasecmp(aChunkSize, "chunked")) { 615 request->_state = _HTTP_PARSER_STATE_BODY_CHUNKED; 616 break; 617 } 618 } 619 } 620 621 // Fetch the content length 622 aContentLength = http_parser_header_get(request, "content-length"); 623 if (!aContentLength) { 624 request->_state = _HTTP_PARSER_STATE_DONE; 625 break; 626 } 627 iContentLength = atoi(aContentLength); 628 629 // Not enough data = skip 630 if (request->body->len < iContentLength) { 631 return; 632 } 633 634 // Change size to indicated size 635 request->_state = _HTTP_PARSER_STATE_DONE; 636 break; 637 638 case _HTTP_PARSER_STATE_BODY_CHUNKED: 639 res = http_parser_message_read_chunked(request); 640 641 if (res == 0) { 642 // Done 643 request->_state = _HTTP_PARSER_STATE_DONE; 644 } else if (res == 1) { 645 // More data needed 646 return; 647 } else if (res == 2) { 648 // Still reading 649 } 650 651 break; 652 653 case _HTTP_PARSER_STATE_DONE: 654 655 // Temporary buffer > direct buffer 656 if (request->buf) { 657 if (request->body) { 658 buf_clear(request->body); 659 free(request->body); 660 } 661 request->body = request->buf; 662 request->buf = NULL; 663 } 664 665 // Mark the request as ready 666 request->ready = 1; 667 return; 668 } 669 } 670 } 671 672 /** 673 * Insert data into a http_message, acting as if it's a request 674 */ 675 void http_parser_response_data(struct http_parser_message *response, const struct buf *data) { 676 char *index; 677 char *aStatus; 678 int iContentLength; 679 const char *aContentLength; 680 const char *aChunkSize; 681 int res; 682 683 // Add event data to buffer 684 if (!response->body) response->body = calloc(1, sizeof(struct buf)); 685 buf_append(response->body, data->data, data->len); 686 687 while(1) { 688 switch(response->_state) { 689 case _HTTP_PARSER_STATE_PANIC: 690 return; 691 case _HTTP_PARSER_STATE_INIT: 692 // Wait for more data if not line break found 693 index = strstr(response->body->data, "\r\n"); 694 if (!index) return; 695 *(index) = '\0'; 696 697 // Read version and status 698 response->version = calloc(1, 8); 699 response->statusMessage = calloc(1, 64); 700 aStatus = calloc(1, 8); 701 if (sscanf(response->body->data, "HTTP/%7s %7s %63[^\r\n]", response->version, aStatus, response->statusMessage) != 3) { 702 response->_state = _HTTP_PARSER_STATE_PANIC; 703 return; 704 } 705 706 // Turn the text status into a number 707 response->status = atoi(aStatus); 708 free(aStatus); 709 710 // Remove status line 711 http_parser_message_remove_body_string(response); 712 713 // Signal we're now reading headers 714 response->_state = _HTTP_PARSER_STATE_HEADER; 715 break; 716 717 case _HTTP_PARSER_STATE_HEADER: 718 if (!http_parser_message_read_header(response)) { 719 if ( 720 http_parser_header_get(response, "content-length") || 721 http_parser_header_get(response, "transfer-encoding") 722 ) { 723 response->_state = _HTTP_PARSER_STATE_BODY; 724 } else { 725 response->_state = _HTTP_PARSER_STATE_DONE; 726 } 727 } 728 break; 729 730 case _HTTP_PARSER_STATE_BODY: 731 // Detect chunked encoding 732 if (response->chunksize == -1) { 733 aChunkSize = http_parser_header_get(response, "transfer-encoding"); 734 if (aChunkSize) { 735 if (!strcasecmp(aChunkSize, "chunked")) { 736 response->_state = _HTTP_PARSER_STATE_BODY_CHUNKED; 737 break; 738 } 739 } 740 } 741 742 // Fetch the content length 743 aContentLength = http_parser_header_get(response, "content-length"); 744 if (!aContentLength) { 745 response->_state = _HTTP_PARSER_STATE_DONE; 746 break; 747 } 748 iContentLength = atoi(aContentLength); 749 750 // Not enough data = skip 751 if (response->body->len < iContentLength) { 752 return; 753 } 754 755 // Change size to indicated size 756 response->_state = _HTTP_PARSER_STATE_DONE; 757 break; 758 759 case _HTTP_PARSER_STATE_BODY_CHUNKED: 760 res = http_parser_message_read_chunked(response); 761 762 if (res == 0) { 763 // Done 764 response->_state = _HTTP_PARSER_STATE_DONE; 765 } else if (res == 1) { 766 // More data needed 767 return; 768 } else if (res == 2) { 769 // Still reading 770 } 771 772 break; 773 774 case _HTTP_PARSER_STATE_DONE: 775 // Temporary buffer > direct buffer 776 if (response->buf) { 777 if (response->body) { 778 buf_clear(response->body); 779 free(response->body); 780 } 781 response->body = response->buf; 782 response->buf = NULL; 783 } 784 785 // Mark the request as ready 786 response->ready = 1; 787 return; 788 } 789 } 790 } 791 792 #ifdef __cplusplus 793 } // extern "C" 794 #endif