httpp-benchmark

httpp-benchmark.git
git clone git://git.lenczewski.org/httpp-benchmark.git
Log | Files | Refs | README | LICENSE

httpp.h (15235B)


      1 /*
      2  * Tiny header only http parser library for
      3  * http 1/1 version (enums don't respect your namespace much, sorry)
      4  * Pipelined requests are not supported because nobody realy cares about them 
      5  * (https://en.wikipedia.org/wiki/HTTP_pipelining#Implementation_status)
      6  * 
      7  * TODO Chunked transfer is not really supported
      8  * TODO Folded headers aren't handled. 
      9  */
     10 
     11 #include <stddef.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 
     16 #define HTTPP_INITIAL_HEADERS_ARR_CAP 20
     17 #define HTTPP_HEADERS_ARR_LIMIT -1
     18 
     19 #define HTTPP_MAX_METHOD_LENGTH (10 + 1)
     20 #define HTTPP_VERSION_BUFSIZE (10 + 1)
     21 #define HTTPP_SUPPORTED_VERSION "HTTP/1.1"
     22 
     23 #define _HTTP_DELIMITER "\r\n"
     24 #define _HTTP_DELIMITER_SIZE 2
     25 #define _HTTP_MAX_STATUS_CODE_SIZE 3
     26 
     27 #define httpp_string_to_method(s) (strcmp(s, "GET")    == 0 ? 0 : \
     28                                    strcmp(s, "POST")   == 0 ? 1 : \
     29                                    strcmp(s, "DELETE") == 0 ? 2 : -1)
     30 
     31 typedef enum {
     32     GET,
     33     POST,
     34     DELETE,
     35     UNKNOWN = -1
     36 } httpp_method_t;
     37 
     38 typedef enum {
     39     Continue = 100,
     40     Switching_Protocols,
     41 
     42     Ok = 200,
     43     Created,
     44     Accepted,
     45 
     46     Multiple_Choices = 300,
     47     Moved_Permanently,
     48     Found,
     49     See_Other,
     50     Not_Modified,
     51     Use_Proxy,
     52     Temporary_Redirect = 307,
     53     Permanent_Redirect,
     54 
     55     Bad_Request = 400,
     56     Unauthorized,
     57     Payment_Required, // Ahahaha
     58     Forbidden,
     59     Not_Found,
     60 
     61     Internal_Server_Error = 500,
     62     Not_Implemented,
     63     Bad_Gateway,
     64     Service_Unavailable,
     65     Gateway_Timeout,
     66     HTTP_Version_Not_Supported,
     67 
     68     Unspecified = -1
     69 } httpp_status_t;
     70 
     71 typedef struct {
     72     char* name;
     73     char* value;
     74 } httpp_header_t;
     75 
     76 typedef struct {
     77     httpp_header_t* arr;
     78     size_t capacity;
     79     size_t length;
     80 } httpp_headers_arr_t;
     81 
     82 typedef struct {
     83     httpp_method_t method;
     84     httpp_headers_arr_t* headers;
     85     char* route;
     86     char* body;
     87 } httpp_req_t;
     88 
     89 typedef struct {
     90     httpp_headers_arr_t* headers;
     91     httpp_status_t code;
     92     char* body;
     93 } httpp_res_t;
     94 
     95 const char* httpp_method_to_string(httpp_method_t m);
     96 const char* httpp_status_to_string(httpp_status_t s);
     97 
     98 httpp_req_t* httpp_req_new();
     99 void httpp_req_free(httpp_req_t* req);
    100 
    101 httpp_req_t* httpp_parse_request(char* raw);
    102 httpp_header_t* httpp_parse_header(httpp_headers_arr_t* hs, char* line, size_t content_len);
    103 
    104 httpp_headers_arr_t* httpp_headers_arr_new();
    105 httpp_header_t* httpp_headers_arr_append(httpp_headers_arr_t* hs, httpp_header_t header);
    106 httpp_header_t* httpp_headers_arr_add(httpp_headers_arr_t* hs, char* name, char* value); // stdrup and httpp_headers_arr_append
    107 httpp_header_t* httpp_headers_arr_find(httpp_headers_arr_t* hs, char* name);
    108 void httpp_headers_arr_free(httpp_headers_arr_t* hs);
    109 
    110 httpp_res_t* httpp_res_new();
    111 int httpp_res_set_body(httpp_res_t* res, char* body); // strdup body and set it
    112 char* httpp_res_to_raw(httpp_res_t* res);
    113 void httpp_res_free(httpp_res_t* res);
    114 
    115 #define httpp_add_header(req_or_res, name, value) \
    116     (httpp_headers_arr_add((req_or_res)->headers, name, value))
    117 
    118 #define httpp_find_header(req_or_res, name) \
    119     (httpp_headers_arr_find((req_or_res)->headers, name))
    120 
    121 #ifdef HTTPP_IMPLEMENTATION
    122 
    123 #define ltrim(str) do {                                                         \
    124     while ((*(str) && *(str) == ' ') || (*(str) == '\r') || (*(str) == '\n')) { \
    125         (str)++;                                                                \
    126     }                                                                           \
    127     } while (0)
    128 
    129 
    130 #define ltrim_buf(str, len) do {                                                \
    131     while ((*(str) && *(str) == ' ') || (*(str) == '\r') || (*(str) == '\n')) { \
    132         (str)++;                                                                \
    133         (len)--;                                                                \
    134     }                                                                           \
    135     } while (0)
    136 
    137 static int chop_until(char c, char** src, char* dest, size_t n) 
    138 {
    139     char* pos = strchr(*src, c);
    140     if (!pos)
    141         return 1;
    142 
    143     size_t chopped_size = pos - *src;
    144     if (chopped_size >= n)
    145         return 1;
    146 
    147     memcpy(dest, *src, chopped_size);
    148     dest[chopped_size] = '\0';
    149 
    150     *src = pos + 1;
    151     return 0;
    152 }
    153 
    154 static char* dchop_until(char c, char** src) 
    155 {
    156     char* pos = strchr(*src, c);
    157     if (!pos)
    158         return NULL;
    159 
    160     size_t chopped_size = pos - *src;
    161     char* out = (char*) malloc(chopped_size + 1);
    162     if (!out)
    163         return NULL;
    164 
    165     memcpy(out, *src, chopped_size);
    166     out[chopped_size] = '\0';
    167 
    168     *src = pos + 1;
    169     return out;
    170 }
    171 
    172 const char* httpp_method_to_string(httpp_method_t m) 
    173 {
    174     switch (m) {
    175         case GET:       return "GET";
    176         case POST:      return "POST"; 
    177         case DELETE:    return "DELETE"; 
    178         default:        return "UNKNOWN";
    179     }
    180 }
    181 
    182 const char* httpp_status_to_string(httpp_status_t s) 
    183 {
    184     switch (s) {
    185         case Continue:                   return "Continue";
    186         case Switching_Protocols:        return "Switching Protocols";
    187 
    188         case Ok:                         return "OK";
    189         case Created:                    return "Created";
    190         case Accepted:                   return "Accepted";
    191 
    192         case Multiple_Choices:           return "Multiple Choices";
    193         case Moved_Permanently:          return "Moved Permanently";
    194         case Found:                      return "Found";
    195         case See_Other:                  return "See Other";
    196         case Not_Modified:               return "Not Modified";
    197         case Use_Proxy:                  return "Use Proxy";
    198         case Temporary_Redirect:         return "Temporary Redirect";
    199         case Permanent_Redirect:         return "Permanent Redirect";
    200 
    201         case Bad_Request:                return "Bad Request";
    202         case Unauthorized:               return "Unauthorized";
    203         case Payment_Required:           return "Payment Required";
    204         case Forbidden:                  return "Forbidden";
    205         case Not_Found:                  return "Not Found";
    206 
    207         case Internal_Server_Error:      return "Internal Server Error";
    208         case Not_Implemented:            return "Not Implemented";
    209         case Bad_Gateway:                return "Bad Gateway";
    210         case Service_Unavailable:        return "Service Unavailable";
    211         case Gateway_Timeout:            return "Gateway Timeout";
    212         case HTTP_Version_Not_Supported: return "HTTP Version Not Supported";
    213 
    214         case Unspecified:
    215         default:                         return "Unspecified";
    216     }
    217 }
    218 
    219 httpp_headers_arr_t* httpp_headers_arr_new()
    220 {
    221     httpp_headers_arr_t* out = (httpp_headers_arr_t*) malloc(sizeof(httpp_headers_arr_t));
    222 
    223     out->arr = (httpp_header_t*) malloc(sizeof(httpp_header_t) * HTTPP_INITIAL_HEADERS_ARR_CAP);
    224     if (!out->arr) {
    225         free(out);
    226         return NULL;
    227     }
    228 
    229     out->capacity = HTTPP_INITIAL_HEADERS_ARR_CAP;
    230     out->length = 0;
    231     return out;
    232 }
    233 
    234 httpp_req_t* httpp_req_new() 
    235 {
    236     httpp_req_t* out = (httpp_req_t*) malloc(sizeof(httpp_req_t));
    237     if (!out)
    238         return NULL;
    239 
    240     out->headers = httpp_headers_arr_new();
    241     if (!out->headers) {
    242         free(out);
    243         return NULL;
    244     }
    245 
    246     out->route = NULL;
    247     out->body = NULL;
    248     return out;
    249 }
    250 
    251 void httpp_headers_arr_free(httpp_headers_arr_t* hs)
    252 {
    253     if (!hs) 
    254         return;
    255 
    256     for (size_t i = 0; i < hs->length; i++) {
    257         free(hs->arr[i].name);
    258         free(hs->arr[i].value);
    259     }
    260 
    261     free(hs->arr);
    262     free(hs);
    263 }
    264 
    265 void httpp_req_free(httpp_req_t* req)
    266 {
    267     if (!req) 
    268         return;
    269 
    270     httpp_headers_arr_free(req->headers);
    271     free(req->route);
    272     free(req->body);
    273     free(req);
    274 }
    275 
    276 httpp_header_t* httpp_headers_arr_append(httpp_headers_arr_t* hs, httpp_header_t header)
    277 {
    278     if (hs->length >= hs->capacity) {
    279         size_t new_cap = hs->capacity * 2;
    280         
    281         if (new_cap <= hs->capacity) {
    282             free(hs->arr);
    283             return NULL;
    284         }
    285 
    286         hs->arr = (httpp_header_t*) realloc(hs->arr, new_cap * sizeof(httpp_header_t));
    287         if (!hs->arr) {
    288             free(hs->arr);
    289             return NULL;
    290         }
    291         
    292         hs->capacity = new_cap;
    293     }
    294 
    295     if (!header.name || !header.value) 
    296         return NULL;
    297 
    298     hs->arr[hs->length++] = header;
    299     return &hs->arr[hs->length - 1];
    300 }
    301 
    302 httpp_header_t* httpp_headers_arr_find(httpp_headers_arr_t* hs, char* name)
    303 {
    304     // For the sake of simplicity and minimalism, it's just a for loop. No hash table here.
    305     for (size_t i = 0; i < hs->length; i++) {
    306         if (strcmp(hs->arr[i].name, name) == 0)
    307             return &hs->arr[i];
    308     }
    309 
    310     return NULL;
    311 }
    312 
    313 
    314 httpp_header_t* httpp_headers_arr_add(httpp_headers_arr_t* hs, char* name, char* value)
    315 {
    316     if (!name || !value) 
    317         return NULL;
    318 
    319     httpp_header_t h = {
    320         strdup(name), 
    321         strdup(value)
    322     };
    323 
    324     httpp_header_t* out = httpp_headers_arr_append(hs, h);
    325     
    326     if (out == NULL) {
    327         free(h.name);
    328         free(h.value);
    329     } 
    330 
    331     return out;
    332 }
    333 
    334 httpp_header_t* httpp_parse_header(httpp_headers_arr_t* hs, char* line, size_t content_len)
    335 {
    336     // RFC says that header starting with whitespace or any other non printable ascii should be rejected.
    337     if (*line == ' ' || *line == '\r' || *line == '\n')
    338         return NULL;
    339 
    340     char* colon = (char*) memchr(line, ':', content_len);
    341     if (!colon)
    342         return NULL;
    343 
    344     size_t name_len = colon - line;
    345 
    346     char* name = (char*) malloc(name_len + 1);
    347     if (!name)
    348         return NULL;
    349     
    350     memcpy(name, line, name_len);
    351     name[name_len] = '\0';
    352 
    353     char* value_start = colon + 1;
    354     size_t value_len = content_len - name_len - 1;
    355 
    356     ltrim_buf(value_start, value_len);
    357 
    358     char* value = (char*) malloc(value_len + 1);
    359     if (!value) {
    360         free(name);
    361         return NULL;
    362     }
    363 
    364     memcpy(value, value_start, value_len);
    365     value[value_len] = '\0';
    366 
    367     return httpp_headers_arr_append(hs, (httpp_header_t){name, value});
    368 }
    369 
    370 static int parse_start_line(char** itr, httpp_req_t* dest) 
    371 {
    372     char  method_buf[HTTPP_MAX_METHOD_LENGTH];
    373     char  version_buf[HTTPP_VERSION_BUFSIZE];
    374     char* route;
    375 
    376     if (chop_until(' ', itr, method_buf, HTTPP_MAX_METHOD_LENGTH) != 0)
    377         return 1; 
    378 
    379     if ((route = dchop_until(' ', itr)) == NULL)
    380         return 1;
    381 
    382     if (chop_until('\r', itr, version_buf, HTTPP_VERSION_BUFSIZE) != 0) {
    383         free(route);
    384         return 1;
    385     }
    386 
    387     if (strcmp(version_buf, HTTPP_SUPPORTED_VERSION) != 0) {
    388         free(route);
    389         return 1;
    390     }
    391 
    392     dest->method = (httpp_method_t) httpp_string_to_method(method_buf);
    393     dest->route = route;
    394 
    395     ltrim(*itr);
    396     return 0;
    397 }
    398 
    399 httpp_req_t* httpp_parse_request(char* raw)
    400 {
    401     httpp_req_t* out = httpp_req_new();
    402 
    403     char* itr = raw;
    404     char* end = raw + strlen(raw);
    405 
    406     if (parse_start_line(&itr, out) != 0) {
    407         httpp_req_free(out);
    408         return NULL;
    409     }
    410 
    411     while (itr < end) {
    412         char* del_pos = strstr(itr, _HTTP_DELIMITER);
    413         if (!del_pos)
    414             break;
    415         
    416         size_t line_size = del_pos - itr;
    417         if (line_size == 0) {
    418             itr = del_pos + _HTTP_DELIMITER_SIZE;
    419             break;
    420         }
    421 
    422         if (httpp_parse_header(out->headers, itr, line_size) == NULL) {
    423             httpp_req_free(out);
    424             return NULL;
    425         }
    426 
    427         if (HTTPP_HEADERS_ARR_LIMIT != -1 && out->headers->length >= HTTPP_HEADERS_ARR_LIMIT) {
    428             httpp_req_free(out); // Limit reached
    429             return NULL;
    430         }
    431 
    432         itr = del_pos + _HTTP_DELIMITER_SIZE;
    433     }
    434 
    435     out->body = strdup(itr);
    436     if (!out->body) {
    437         httpp_req_free(out);
    438         return NULL;
    439     }
    440 
    441     return out;
    442 }
    443 
    444 // --- Responses part ---
    445 httpp_res_t* httpp_res_new()
    446 {
    447     httpp_res_t* out = (httpp_res_t*) malloc(sizeof(httpp_res_t));
    448     if (!out)
    449         return NULL;
    450 
    451     out->headers = httpp_headers_arr_new();
    452     if (!out->headers) 
    453         return NULL;
    454     
    455     out->code = Unspecified;
    456     return out;
    457 }
    458 
    459 int httpp_res_set_body(httpp_res_t* res, char* body) 
    460 {
    461     char* content = strdup(body);
    462     if (!content)
    463         return 1;
    464 
    465     res->body = content;
    466     return 0;
    467 }
    468 
    469 void httpp_res_free(httpp_res_t* res) 
    470 {
    471     if (!res)
    472         return;
    473 
    474     httpp_headers_arr_free(res->headers);
    475     free(res->body);
    476 }
    477 
    478 char* httpp_res_to_raw(httpp_res_t* res)
    479 {
    480     if (res == NULL || res->headers == NULL)
    481         return NULL; 
    482 
    483     if (res->code == -1 || (int) res->code > 999)
    484         return NULL;
    485 
    486     const char* status_msg = httpp_status_to_string(res->code);
    487     size_t out_size =  
    488         strlen(HTTPP_SUPPORTED_VERSION) 
    489         + 1 // Space
    490         + _HTTP_MAX_STATUS_CODE_SIZE 
    491         + 1 // Space
    492         + strlen(status_msg)
    493         + _HTTP_DELIMITER_SIZE;
    494 
    495     for (size_t i = 0; i < res->headers->length; i++) {
    496         httpp_header_t header = res->headers->arr[i];
    497         if (!header.name || !header.value)
    498             continue;
    499 
    500         out_size += strlen(header.name) + 2 // ": "
    501                   + strlen(header.value) 
    502                   + _HTTP_DELIMITER_SIZE;
    503     }
    504 
    505     out_size += _HTTP_DELIMITER_SIZE;
    506     if (res->body) 
    507         out_size += strlen(res->body);
    508 
    509     out_size += 1; // '\0'
    510     char* out = (char*) malloc(out_size);
    511     if (!out)
    512         return NULL;
    513 
    514     int written = snprintf(out, out_size, "%s %d %s\r\n", HTTPP_SUPPORTED_VERSION, res->code, status_msg);
    515     if (written < 0 || (size_t) written >= out_size) {
    516         free(out);
    517         return NULL;
    518     }
    519 
    520     size_t offset = written;
    521 
    522     for (size_t i = 0; i < res->headers->length; i++) {
    523         httpp_header_t header = res->headers->arr[i];
    524         if (!header.name || !header.value)
    525             continue;
    526 
    527         int n = snprintf(out + offset, out_size - offset, 
    528                     "%s: %s\r\n", header.name, header.value);
    529 
    530         if (n < 0 || (size_t) n >= out_size - offset) {
    531             free(out);
    532             return NULL;
    533         }
    534 
    535         offset += n;
    536     }
    537 
    538     strcat(out, _HTTP_DELIMITER);
    539     if (res->body)
    540         strcat(out, res->body);
    541 
    542     return out;
    543 }
    544 
    545 #endif // HTTPP_IMPLEMENTATION
    546 
    547 /*
    548 * MIT License
    549 * 
    550 * Copyright (c) 2025 Mint
    551 * 
    552 * Permission is hereby granted, free of charge, to any person obtaining a copy
    553 * of this software and associated documentation files (the "Software"), to deal
    554 * in the Software without restriction, including without limitation the rights
    555 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    556 * copies of the Software, and to permit persons to whom the Software is
    557 * furnished to do so, subject to the following conditions:
    558 *
    559 * The above copyright notice and this permission notice shall be included in all
    560 * copies or substantial portions of the Software.
    561 *
    562 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    563 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    564 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    565 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    566 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    567 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    568 * SOFTWARE.
    569 */