diff options
Diffstat (limited to 'lib/libshout-idjc/src/common/httpp/httpp.c')
-rw-r--r-- | lib/libshout-idjc/src/common/httpp/httpp.c | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/lib/libshout-idjc/src/common/httpp/httpp.c b/lib/libshout-idjc/src/common/httpp/httpp.c new file mode 100644 index 0000000000..4c320d378f --- /dev/null +++ b/lib/libshout-idjc/src/common/httpp/httpp.c @@ -0,0 +1,611 @@ +/* Httpp.c +** +** http parsing engine +** +** Copyright (C) 2014 Michael Smith <msmith@icecast.org>, +** Ralph Giles <giles@xiph.org>, +** Ed "oddsock" Zaleski <oddsock@xiph.org>, +** Karl Heyes <karl@xiph.org>, +** Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org> +** +** 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 <config.h> +#endif + +#include <stdio.h> + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <avl/avl.h> +#include "httpp.h" + +#if _WIN32 +#include <os.h> +#endif + +#define MAX_HEADERS 32 + +/* internal functions */ + +/* misc */ +static char *_lowercase(char *str); + +/* for avl trees */ +static int _compare_vars(void *compare_arg, void *a, void *b); +static int _free_vars(void *key); + +http_parser_t *httpp_create_parser(void) +{ + return (http_parser_t *)malloc(sizeof(http_parser_t)); +} + +void httpp_initialize(http_parser_t *parser, http_varlist_t *defaults) +{ + http_varlist_t *list; + + parser->req_type = httpp_req_none; + parser->uri = NULL; + parser->vars = avl_tree_new(_compare_vars, NULL); + parser->queryvars = avl_tree_new(_compare_vars, NULL); + + /* now insert the default variables */ + list = defaults; + while (list != NULL) { + httpp_setvar(parser, list->var.name, list->var.value); + list = list->next; + } +} + +static int 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 parse_headers(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) { + httpp_setvar(parser, _lowercase(name), value); + name = NULL; + value = NULL; + } + } +} + +int httpp_parse_response(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 = 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; + } + + httpp_setvar(parser, HTTPP_VAR_ERROR_CODE, resp_code); + code = atoi(resp_code); + if(code < 200 || code >= 300) { + httpp_setvar(parser, HTTPP_VAR_ERROR_MESSAGE, message); + } + + httpp_setvar(parser, HTTPP_VAR_URI, uri); + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "NONE"); + + parse_headers(parser, line, lines); + + free(data); + + return 1; +} + +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 *url_escape(const char *src) +{ + int len = strlen(src); + unsigned char *decoded; + int 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; +} + +/** TODO: This is almost certainly buggy in some cases */ +static void parse_query(http_parser_t *parser, char *query) +{ + int len; + int i=0; + char *key = query; + char *val=NULL; + + if(!query || !*query) + return; + + len = strlen(query); + + while(i<len) { + switch(query[i]) { + case '&': + query[i] = 0; + if(val && key) + httpp_set_query_param(parser, key, val); + key = query+i+1; + break; + case '=': + query[i] = 0; + val = query+i+1; + break; + } + i++; + } + + if(val && key) { + httpp_set_query_param(parser, key, val); + } +} + +int httpp_parse(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 = 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; + } + } + } + } + + parser->req_type = httpp_str_to_method(req_type); + + if (uri != NULL && strlen(uri) > 0) { + char *query; + if((query = strchr(uri, '?')) != NULL) { + httpp_setvar(parser, HTTPP_VAR_RAWURI, uri); + httpp_setvar(parser, HTTPP_VAR_QUERYARGS, query); + *query = 0; + query++; + parse_query(parser, 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)) { + httpp_setvar(parser, HTTPP_VAR_PROTOCOL, version); + httpp_setvar(parser, HTTPP_VAR_VERSION, &tmp[1]); + } else { + free(data); + return 0; + } + } else { + free(data); + return 0; + } + + if (parser->req_type != httpp_req_none && parser->req_type != httpp_req_unknown) { + switch (parser->req_type) { + case httpp_req_get: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "GET"); + break; + case httpp_req_post: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "POST"); + break; + case httpp_req_put: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "PUT"); + break; + case httpp_req_head: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "HEAD"); + break; + case httpp_req_options: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "OPTIONS"); + break; + case httpp_req_delete: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "DELETE"); + break; + case httpp_req_trace: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "TRACE"); + break; + case httpp_req_connect: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "CONNECT"); + break; + case httpp_req_source: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "SOURCE"); + break; + case httpp_req_play: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "PLAY"); + break; + case httpp_req_stats: + httpp_setvar(parser, HTTPP_VAR_REQ_TYPE, "STATS"); + break; + default: + break; + } + } else { + free(data); + return 0; + } + + if (parser->uri != NULL) { + httpp_setvar(parser, HTTPP_VAR_URI, parser->uri); + } else { + free(data); + return 0; + } + + parse_headers(parser, line, lines); + + free(data); + + return 1; +} + +void httpp_deletevar(http_parser_t *parser, const char *name) +{ + http_var_t var; + + if (parser == NULL || name == NULL) + return; + var.name = (char*)name; + var.value = NULL; + avl_delete(parser->vars, (void *)&var, _free_vars); +} + +void httpp_setvar(http_parser_t *parser, const char *name, const char *value) +{ + http_var_t *var; + + if (name == NULL || value == NULL) + return; + + var = (http_var_t *)malloc(sizeof(http_var_t)); + if (var == NULL) return; + + var->name = strdup(name); + var->value = strdup(value); + + if (httpp_getvar(parser, name) == NULL) { + avl_insert(parser->vars, (void *)var); + } else { + avl_delete(parser->vars, (void *)var, _free_vars); + avl_insert(parser->vars, (void *)var); + } +} + +const char *httpp_getvar(http_parser_t *parser, const char *name) +{ + http_var_t var; + http_var_t *found; + void *fp; + + if (parser == NULL || name == NULL) + return NULL; + + fp = &found; + var.name = (char*)name; + var.value = NULL; + + if (avl_get_by_key(parser->vars, &var, fp) == 0) + return found->value; + else + return NULL; +} + +void httpp_set_query_param(http_parser_t *parser, const char *name, const char *value) +{ + http_var_t *var; + + if (name == NULL || value == NULL) + return; + + var = (http_var_t *)malloc(sizeof(http_var_t)); + if (var == NULL) return; + + var->name = strdup(name); + var->value = url_escape(value); + + if (httpp_get_query_param(parser, name) == NULL) { + avl_insert(parser->queryvars, (void *)var); + } else { + avl_delete(parser->queryvars, (void *)var, _free_vars); + avl_insert(parser->queryvars, (void *)var); + } +} + +const char *httpp_get_query_param(http_parser_t *parser, const char *name) +{ + http_var_t var; + http_var_t *found; + void *fp; + + fp = &found; + var.name = (char *)name; + var.value = NULL; + + if (avl_get_by_key(parser->queryvars, (void *)&var, fp) == 0) + return found->value; + else + return NULL; +} + +void httpp_clear(http_parser_t *parser) +{ + parser->req_type = httpp_req_none; + if (parser->uri) + free(parser->uri); + parser->uri = NULL; + avl_tree_free(parser->vars, _free_vars); + avl_tree_free(parser->queryvars, _free_vars); + parser->vars = NULL; +} + +void httpp_destroy(http_parser_t *parser) +{ + httpp_clear(parser); + free(parser); +} + +static char *_lowercase(char *str) +{ + char *p = str; + for (; *p != '\0'; p++) + *p = tolower(*p); + + return str; +} + +static int _compare_vars(void *compare_arg, void *a, void *b) +{ + http_var_t *vara, *varb; + + vara = (http_var_t *)a; + varb = (http_var_t *)b; + + return strcmp(vara->name, varb->name); +} + +static int _free_vars(void *key) +{ + http_var_t *var; + + var = (http_var_t *)key; + + if (var->name) + free(var->name); + if (var->value) + free(var->value); + free(var); + + return 1; +} + +httpp_request_type_e httpp_str_to_method(const char * method) { + if (strcasecmp("GET", method) == 0) { + return httpp_req_get; + } else if (strcasecmp("POST", method) == 0) { + return httpp_req_post; + } else if (strcasecmp("PUT", method) == 0) { + return httpp_req_put; + } else if (strcasecmp("HEAD", method) == 0) { + return httpp_req_head; + } else if (strcasecmp("OPTIONS", method) == 0) { + return httpp_req_options; + } else if (strcasecmp("DELETE", method) == 0) { + return httpp_req_delete; + } else if (strcasecmp("TRACE", method) == 0) { + return httpp_req_trace; + } else if (strcasecmp("CONNECT", method) == 0) { + return httpp_req_connect; + } else if (strcasecmp("SOURCE", method) == 0) { + return httpp_req_source; + } else if (strcasecmp("PLAY", method) == 0) { + return httpp_req_play; + } else if (strcasecmp("STATS", method) == 0) { + return httpp_req_stats; + } else { + return httpp_req_unknown; + } +} + |