/* Httpp.c ** ** http parsing engine ** ** Copyright (C) 2014 Michael Smith , ** Ralph Giles , ** Ed "oddsock" Zaleski , ** Karl Heyes , ** Copyright (C) 2012-2018 Philipp "ph3-der-loewe" Schafft ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Library General Public ** License as published by the Free Software Foundation; either ** version 2 of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Library General Public License for more details. ** ** You should have received a copy of the GNU Library General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, ** Boston, MA 02110-1301, USA. ** */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #include #include #define MAX_HEADERS 32 /* internal functions */ /* misc */ static char *_lowercase(char *str); /* for avl trees */ static int igloo__compare_vars(void *compare_arg, void *a, void *b); static int igloo__free_vars(void *key); /* For avl tree manipulation */ static void parse_query(igloo_avl_tree *tree, const char *query, size_t len); static const char *_httpp_get_param(igloo_avl_tree *tree, const char *name); static void _httpp_set_param_nocopy(igloo_avl_tree *tree, char *name, char *value, int replace); static void _httpp_set_param(igloo_avl_tree *tree, const char *name, const char *value); static igloo_http_var_t *igloo__httpp_get_param_var(igloo_avl_tree *tree, const char *name); igloo_httpp_request_info_t igloo_httpp_request_info(igloo_httpp_request_type_e req) { #if 0 #define igloo_HTTPP_REQUEST_IS_SAFE ((igloo_httpp_request_info_t)0x0001U) #define igloo_HTTPP_REQUEST_IS_IDEMPOTENT ((igloo_httpp_request_info_t)0x0002U) #define igloo_HTTPP_REQUEST_IS_CACHEABLE ((igloo_httpp_request_info_t)0x0004U) #define igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY ((igloo_httpp_request_info_t)0x0010U) #define igloo_HTTPP_REQUEST_HAS_REQUEST_BODY ((igloo_httpp_request_info_t)0x0100U) #define igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY ((igloo_httpp_request_info_t)0x0200U) #endif switch (req) { /* offical methods */ case igloo_httpp_req_get: return igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_IS_CACHEABLE|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY; break; case igloo_httpp_req_post: return igloo_HTTPP_REQUEST_IS_CACHEABLE|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY; break; case igloo_httpp_req_put: return igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY; break; case igloo_httpp_req_head: return igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_IS_CACHEABLE; break; case igloo_httpp_req_options: return igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY; break; case igloo_httpp_req_delete: return igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY; break; case igloo_httpp_req_trace: return igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY; break; case igloo_httpp_req_connect: return igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY; break; /* Icecast specific methods */ case igloo_httpp_req_source: return igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY; break; case igloo_httpp_req_play: return igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY; break; case igloo_httpp_req_stats: return igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY; break; /* Virtual and other methods */ case igloo_httpp_req_none: case igloo_httpp_req_unknown: default: return igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY; break; } } igloo_http_parser_t *igloo_httpp_create_parser(void) { igloo_http_parser_t *parser = calloc(1, sizeof(igloo_http_parser_t)); parser->refc = 1; parser->req_type = igloo_httpp_req_none; parser->uri = NULL; parser->vars = igloo_avl_tree_new(igloo__compare_vars, NULL); parser->queryvars = igloo_avl_tree_new(igloo__compare_vars, NULL); parser->postvars = igloo_avl_tree_new(igloo__compare_vars, NULL); return parser; } void igloo_httpp_initialize(igloo_http_parser_t *parser, igloo_http_varlist_t *defaults) { igloo_http_varlist_t *list; /* now insert the default variables */ list = defaults; while (list != NULL) { size_t i; for (i = 0; i < list->var.values; i++) { igloo_httpp_setvar(parser, list->var.name, list->var.value[i]); } list = list->next; } } static int igloo_split_headers(char *data, unsigned long len, char **line) { /* first we count how many lines there are ** and set up the line[] array */ int lines = 0; unsigned long i; line[lines] = data; for (i = 0; i < len && lines < MAX_HEADERS; i++) { if (data[i] == '\r') data[i] = '\0'; if (data[i] == '\n') { lines++; data[i] = '\0'; if (lines >= MAX_HEADERS) return MAX_HEADERS; if (i + 1 < len) { if (data[i + 1] == '\n' || data[i + 1] == '\r') break; line[lines] = &data[i + 1]; } } } i++; while (i < len && data[i] == '\n') i++; return lines; } static void igloo_parse_headers(igloo_http_parser_t *parser, char **line, int lines) { int i, l; int whitespace, slen; char *name = NULL; char *value = NULL; /* parse the name: value lines. */ for (l = 1; l < lines; l++) { whitespace = 0; name = line[l]; value = NULL; slen = strlen(line[l]); for (i = 0; i < slen; i++) { if (line[l][i] == ':') { whitespace = 1; line[l][i] = '\0'; } else { if (whitespace) { whitespace = 0; while (i < slen && line[l][i] == ' ') i++; if (i < slen) value = &line[l][i]; break; } } } if (name != NULL && value != NULL) { igloo_httpp_setvar(parser, _lowercase(name), value); name = NULL; value = NULL; } } } int igloo_httpp_parse_response(igloo_http_parser_t *parser, const char *http_data, unsigned long len, const char *uri) { char *data; char *line[MAX_HEADERS]; int lines, slen,i, whitespace=0, where=0,code; char *version=NULL, *resp_code=NULL, *message=NULL; if(http_data == NULL) return 0; /* make a local copy of the data, including 0 terminator */ data = (char *)malloc(len+1); if (data == NULL) return 0; memcpy(data, http_data, len); data[len] = 0; lines = igloo_split_headers(data, len, line); /* In this case, the first line contains: * VERSION RESPONSE_CODE MESSAGE, such as HTTP/1.0 200 OK */ slen = strlen(line[0]); version = line[0]; for(i=0; i < slen; i++) { if(line[0][i] == ' ') { line[0][i] = 0; whitespace = 1; } else if(whitespace) { whitespace = 0; where++; if(where == 1) resp_code = &line[0][i]; else { message = &line[0][i]; break; } } } if(version == NULL || resp_code == NULL || message == NULL) { free(data); return 0; } igloo_httpp_setvar(parser, igloo_HTTPP_VAR_ERROR_CODE, resp_code); code = atoi(resp_code); if(code < 200 || code >= 300) { igloo_httpp_setvar(parser, igloo_HTTPP_VAR_ERROR_MESSAGE, message); } igloo_httpp_setvar(parser, igloo_HTTPP_VAR_URI, uri); igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "NONE"); igloo_parse_headers(parser, line, lines); free(data); return 1; } int igloo_httpp_parse_postdata(igloo_http_parser_t *parser, const char *body_data, size_t len) { const char *header = igloo_httpp_getvar(parser, "content-type"); if (strcasecmp(header, "application/x-www-form-urlencoded") != 0) { return -1; } parse_query(parser->postvars, body_data, len); return 0; } static int hex(char c) { if(c >= '0' && c <= '9') return c - '0'; else if(c >= 'A' && c <= 'F') return c - 'A' + 10; else if(c >= 'a' && c <= 'f') return c - 'a' + 10; else return -1; } static char *igloo_url_unescape(const char *src, size_t len) { unsigned char *decoded; size_t i; char *dst; int done = 0; decoded = calloc(1, len + 1); dst = (char *)decoded; for(i=0; i < len; i++) { switch(src[i]) { case '%': if(i+2 >= len) { free(decoded); return NULL; } if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) { free(decoded); return NULL; } *dst++ = hex(src[i+1]) * 16 + hex(src[i+2]); i+= 2; break; case '+': *dst++ = ' '; break; case '#': done = 1; break; case 0: free(decoded); return NULL; break; default: *dst++ = src[i]; break; } if(done) break; } *dst = 0; /* null terminator */ return (char *)decoded; } static void parse_query_element(igloo_avl_tree *tree, const char *start, const char *mid, const char *end) { size_t keylen; char *key; size_t valuelen; char *value; if (start >= end) return; if (!mid) return; keylen = mid - start; valuelen = end - mid - 1; /* REVIEW: We ignore keys with empty values. */ if (!keylen || !valuelen) return; key = malloc(keylen + 1); memcpy(key, start, keylen); key[keylen] = 0; value = igloo_url_unescape(mid + 1, valuelen); _httpp_set_param_nocopy(tree, key, value, 0); } static void parse_query(igloo_avl_tree *tree, const char *query, size_t len) { const char *start = query; const char *mid = NULL; size_t i; if (!query || !*query) return; for (i = 0; i < len; i++) { switch (query[i]) { case '&': parse_query_element(tree, start, mid, &(query[i])); start = &(query[i + 1]); mid = NULL; break; case '=': mid = &(query[i]); break; } } parse_query_element(tree, start, mid, &(query[i])); } int igloo_httpp_parse(igloo_http_parser_t *parser, const char *http_data, unsigned long len) { char *data, *tmp; char *line[MAX_HEADERS]; /* limited to 32 lines, should be more than enough */ int i; int lines; char *req_type = NULL; char *uri = NULL; char *version = NULL; int whitespace, where, slen; if (http_data == NULL) return 0; /* make a local copy of the data, including 0 terminator */ data = (char *)malloc(len+1); if (data == NULL) return 0; memcpy(data, http_data, len); data[len] = 0; lines = igloo_split_headers(data, len, line); /* parse the first line special ** the format is: ** REQ_TYPE URI VERSION ** eg: ** GET /index.html HTTP/1.0 */ where = 0; whitespace = 0; slen = strlen(line[0]); req_type = line[0]; for (i = 0; i < slen; i++) { if (line[0][i] == ' ') { whitespace = 1; line[0][i] = '\0'; } else { /* we're just past the whitespace boundry */ if (whitespace) { whitespace = 0; where++; switch (where) { case 1: uri = &line[0][i]; break; case 2: version = &line[0][i]; break; case 3: /* There is an extra element in the request line. This is not HTTP. */ free(data); return 0; break; } } } } parser->req_type = igloo_httpp_str_to_method(req_type); if (uri != NULL && strlen(uri) > 0) { char *query; if((query = strchr(uri, '?')) != NULL) { igloo_httpp_setvar(parser, igloo_HTTPP_VAR_RAWURI, uri); igloo_httpp_setvar(parser, igloo_HTTPP_VAR_QUERYARGS, query); *query = 0; query++; parse_query(parser->queryvars, query, strlen(query)); } parser->uri = strdup(uri); } else { free(data); return 0; } if ((version != NULL) && ((tmp = strchr(version, '/')) != NULL)) { tmp[0] = '\0'; if ((strlen(version) > 0) && (strlen(&tmp[1]) > 0)) { igloo_httpp_setvar(parser, igloo_HTTPP_VAR_PROTOCOL, version); igloo_httpp_setvar(parser, igloo_HTTPP_VAR_VERSION, &tmp[1]); } else { free(data); return 0; } } else { free(data); return 0; } if (parser->req_type != igloo_httpp_req_none && parser->req_type != igloo_httpp_req_unknown) { switch (parser->req_type) { case igloo_httpp_req_get: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "GET"); break; case igloo_httpp_req_post: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "POST"); break; case igloo_httpp_req_put: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "PUT"); break; case igloo_httpp_req_head: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "HEAD"); break; case igloo_httpp_req_options: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "OPTIONS"); break; case igloo_httpp_req_delete: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "DELETE"); break; case igloo_httpp_req_trace: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "TRACE"); break; case igloo_httpp_req_connect: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "CONNECT"); break; case igloo_httpp_req_source: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "SOURCE"); break; case igloo_httpp_req_play: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "PLAY"); break; case igloo_httpp_req_stats: igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, "STATS"); break; default: break; } } else { free(data); return 0; } if (parser->uri != NULL) { igloo_httpp_setvar(parser, igloo_HTTPP_VAR_URI, parser->uri); } else { free(data); return 0; } igloo_parse_headers(parser, line, lines); free(data); return 1; } void igloo_httpp_deletevar(igloo_http_parser_t *parser, const char *name) { igloo_http_var_t var; if (parser == NULL || name == NULL) return; memset(&var, 0, sizeof(var)); var.name = (char*)name; igloo_avl_delete(parser->vars, (void *)&var, igloo__free_vars); } void igloo_httpp_setvar(igloo_http_parser_t *parser, const char *name, const char *value) { igloo_http_var_t *var; if (name == NULL || value == NULL) return; var = (igloo_http_var_t *)calloc(1, sizeof(igloo_http_var_t)); if (var == NULL) return; var->value = calloc(1, sizeof(*var->value)); if (!var->value) { free(var); return; } var->name = strdup(name); var->values = 1; var->value[0] = strdup(value); if (igloo_httpp_getvar(parser, name) == NULL) { igloo_avl_insert(parser->vars, (void *)var); } else { igloo_avl_delete(parser->vars, (void *)var, igloo__free_vars); igloo_avl_insert(parser->vars, (void *)var); } } const char *igloo_httpp_getvar(igloo_http_parser_t *parser, const char *name) { igloo_http_var_t var; igloo_http_var_t *found; void *fp; if (parser == NULL || name == NULL) return NULL; fp = &found; memset(&var, 0, sizeof(var)); var.name = (char*)name; if (igloo_avl_get_by_key(parser->vars, &var, fp) == 0) { if (!found->values) return NULL; return found->value[0]; } else { return NULL; } } static void _httpp_set_param_nocopy(igloo_avl_tree *tree, char *name, char *value, int replace) { igloo_http_var_t *var, *found; char **n; if (name == NULL || value == NULL) return; found = igloo__httpp_get_param_var(tree, name); if (replace || !found) { var = (igloo_http_var_t *)calloc(1, sizeof(igloo_http_var_t)); if (var == NULL) { free(name); free(value); return; } var->name = name; } else { free(name); var = found; } n = realloc(var->value, sizeof(*n)*(var->values + 1)); if (!n) { if (replace || !found) { free(name); free(var); } free(value); return; } var->value = n; var->value[var->values++] = value; if (replace && found) { igloo_avl_delete(tree, (void *)found, igloo__free_vars); igloo_avl_insert(tree, (void *)var); } else if (!found) { igloo_avl_insert(tree, (void *)var); } } static void _httpp_set_param(igloo_avl_tree *tree, const char *name, const char *value) { if (name == NULL || value == NULL) return; _httpp_set_param_nocopy(tree, strdup(name), igloo_url_unescape(value, strlen(value)), 1); } static igloo_http_var_t *igloo__httpp_get_param_var(igloo_avl_tree *tree, const char *name) { igloo_http_var_t var; igloo_http_var_t *found; void *fp; fp = &found; memset(&var, 0, sizeof(var)); var.name = (char *)name; if (igloo_avl_get_by_key(tree, (void *)&var, fp) == 0) return found; else return NULL; } static const char *_httpp_get_param(igloo_avl_tree *tree, const char *name) { igloo_http_var_t *res = igloo__httpp_get_param_var(tree, name); if (!res) return NULL; if (!res->values) return NULL; return res->value[0]; } void igloo_httpp_set_query_param(igloo_http_parser_t *parser, const char *name, const char *value) { return _httpp_set_param(parser->queryvars, name, value); } const char *igloo_httpp_get_query_param(igloo_http_parser_t *parser, const char *name) { return _httpp_get_param(parser->queryvars, name); } void igloo_httpp_set_post_param(igloo_http_parser_t *parser, const char *name, const char *value) { return _httpp_set_param(parser->postvars, name, value); } const char *igloo_httpp_get_post_param(igloo_http_parser_t *parser, const char *name) { return _httpp_get_param(parser->postvars, name); } const igloo_http_var_t *igloo_httpp_get_param_var(igloo_http_parser_t *parser, const char *name) { igloo_http_var_t *ret = igloo__httpp_get_param_var(parser->postvars, name); if (ret) return ret; return igloo__httpp_get_param_var(parser->queryvars, name); } const igloo_http_var_t *igloo_httpp_get_any_var(igloo_http_parser_t *parser, igloo_httpp_ns_t ns, const char *name) { igloo_avl_tree *tree = NULL; if (!parser || !name) return NULL; switch (ns) { case igloo_HTTPP_NS_VAR: if (name[0] != '_' || name[1] != '_') return NULL; tree = parser->vars; break; case igloo_HTTPP_NS_HEADER: if (name[0] == '_' && name[1] == '_') return NULL; tree = parser->vars; break; case igloo_HTTPP_NS_QUERY_STRING: tree = parser->queryvars; break; case igloo_HTTPP_NS_POST_BODY: tree = parser->postvars; break; } if (!tree) return NULL; return igloo__httpp_get_param_var(tree, name); } char ** igloo_httpp_get_any_key(igloo_http_parser_t *parser, igloo_httpp_ns_t ns) { igloo_avl_tree *tree = NULL; igloo_avl_node *avlnode; char **ret; size_t len; size_t pos = 0; if (!parser) return NULL; switch (ns) { case igloo_HTTPP_NS_VAR: case igloo_HTTPP_NS_HEADER: tree = parser->vars; break; case igloo_HTTPP_NS_QUERY_STRING: tree = parser->queryvars; break; case igloo_HTTPP_NS_POST_BODY: tree = parser->postvars; break; } if (!tree) return NULL; ret = calloc(8, sizeof(*ret)); if (!ret) return NULL; len = 8; for (avlnode = igloo_avl_get_first(tree); avlnode; avlnode = igloo_avl_get_next(avlnode)) { igloo_http_var_t *var = avlnode->key; if (ns == igloo_HTTPP_NS_VAR) { if (var->name[0] != '_' || var->name[1] != '_') { continue; } } else if (ns == igloo_HTTPP_NS_HEADER) { if (var->name[0] == '_' && var->name[1] == '_') { continue; } } if (pos == (len-1)) { char **n = realloc(ret, sizeof(*ret)*(len + 8)); if (!n) { igloo_httpp_free_any_key(ret); return NULL; } memset(n + len, 0, sizeof(*n)*8); ret = n; len += 8; } ret[pos] = strdup(var->name); if (!ret[pos]) { igloo_httpp_free_any_key(ret); return NULL; } pos++; } return ret; } void igloo_httpp_free_any_key(char **keys) { char **p; if (!keys) return; for (p = keys; *p; p++) { free(*p); } free(keys); } const char *igloo_httpp_get_param(igloo_http_parser_t *parser, const char *name) { const char *ret = _httpp_get_param(parser->postvars, name); if (ret) return ret; return _httpp_get_param(parser->queryvars, name); } static void httpp_clear(igloo_http_parser_t *parser) { parser->req_type = igloo_httpp_req_none; if (parser->uri) free(parser->uri); parser->uri = NULL; igloo_avl_tree_free(parser->vars, igloo__free_vars); igloo_avl_tree_free(parser->queryvars, igloo__free_vars); igloo_avl_tree_free(parser->postvars, igloo__free_vars); parser->vars = NULL; } int igloo_httpp_addref(igloo_http_parser_t *parser) { if (!parser) return -1; parser->refc++; return 0; } int igloo_httpp_release(igloo_http_parser_t *parser) { if (!parser) return -1; parser->refc--; if (parser->refc) return 0; httpp_clear(parser); free(parser); return 0; } static char *_lowercase(char *str) { char *p = str; for (; *p != '\0'; p++) *p = tolower(*p); return str; } static int igloo__compare_vars(void *compare_arg, void *a, void *b) { igloo_http_var_t *vara, *varb; vara = (igloo_http_var_t *)a; varb = (igloo_http_var_t *)b; return strcmp(vara->name, varb->name); } static int igloo__free_vars(void *key) { igloo_http_var_t *var = (igloo_http_var_t *)key; size_t i; free(var->name); for (i = 0; i < var->values; i++) { free(var->value[i]); } free(var->value); free(var); return 1; } igloo_httpp_request_type_e igloo_httpp_str_to_method(const char * method) { if (strcasecmp("GET", method) == 0) { return igloo_httpp_req_get; } else if (strcasecmp("POST", method) == 0) { return igloo_httpp_req_post; } else if (strcasecmp("PUT", method) == 0) { return igloo_httpp_req_put; } else if (strcasecmp("HEAD", method) == 0) { return igloo_httpp_req_head; } else if (strcasecmp("OPTIONS", method) == 0) { return igloo_httpp_req_options; } else if (strcasecmp("DELETE", method) == 0) { return igloo_httpp_req_delete; } else if (strcasecmp("TRACE", method) == 0) { return igloo_httpp_req_trace; } else if (strcasecmp("CONNECT", method) == 0) { return igloo_httpp_req_connect; } else if (strcasecmp("SOURCE", method) == 0) { return igloo_httpp_req_source; } else if (strcasecmp("PLAY", method) == 0) { return igloo_httpp_req_play; } else if (strcasecmp("STATS", method) == 0) { return igloo_httpp_req_stats; } else { return igloo_httpp_req_unknown; } }