mirror of
https://gitlab.xiph.org/xiph/icecast-common.git
synced 2024-12-04 14:46:31 -05:00
1043 lines
29 KiB
C
1043 lines
29 KiB
C
/* 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>,
|
|
** Copyright (C) 2012-2018 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>
|
|
#ifdef HAVE_STRINGS_H
|
|
#include <strings.h>
|
|
#endif
|
|
|
|
#include <igloo/avl.h>
|
|
#include <igloo/httpp.h>
|
|
|
|
#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);
|
|
|
|
static const struct http_method {
|
|
const char *name;
|
|
const igloo_httpp_request_type_e req;
|
|
const igloo_httpp_request_info_t info;
|
|
} igloo_httpp__methods[] = {
|
|
/* RFC 7231 */
|
|
{"GET", igloo_httpp_req_get,
|
|
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
|
|
},
|
|
{"HEAD", igloo_httpp_req_head,
|
|
igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_IS_CACHEABLE
|
|
},
|
|
{"POST", igloo_httpp_req_post,
|
|
igloo_HTTPP_REQUEST_IS_CACHEABLE|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY
|
|
},
|
|
{"PUT", igloo_httpp_req_put,
|
|
igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY
|
|
},
|
|
{"DELETE", igloo_httpp_req_delete,
|
|
igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY
|
|
},
|
|
{"CONNECT", igloo_httpp_req_connect,
|
|
igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY
|
|
},
|
|
{"OPTIONS", igloo_httpp_req_options,
|
|
igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY
|
|
},
|
|
{"TRACE", igloo_httpp_req_trace,
|
|
igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_IS_IDEMPOTENT|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY
|
|
},
|
|
/* RFC 5789 */
|
|
{"PATCH", igloo_httpp_req_patch,
|
|
igloo_HTTPP_REQUEST_HAS_REQUEST_BODY|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY
|
|
},
|
|
/* Icecast specific */
|
|
{"SOURCE", igloo_httpp_req_source,
|
|
igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_REQUEST_BODY
|
|
},
|
|
{"PLAY", igloo_httpp_req_play,
|
|
igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY
|
|
},
|
|
{"STATS", igloo_httpp_req_stats,
|
|
igloo_HTTPP_REQUEST_IS_SAFE|igloo_HTTPP_REQUEST_HAS_RESPONSE_BODY|igloo_HTTPP_REQUEST_HAS_OPTIONAL_REQUEST_BODY
|
|
},
|
|
|
|
/* Other RFCs */
|
|
/*
|
|
* tr -d \\r < www.iana.org/assignments/http-methods/methods.csv | while IFS=, read m s i r; do printf " {\"%-21s igloo_httpp_req_%s,\n /%c Safe: %s, Idempotent: %s, Reference: %s %c/\n igloo_HTTPP_REQUEST_INVALID\n },\n" "$m"\", $(tr A-Z- a-z_ <<<"$m") '*' "$s" "$i" "$r" '*'; done
|
|
*/
|
|
{"ACL", igloo_httpp_req_acl,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3744, Section 8.1]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"BASELINE-CONTROL", igloo_httpp_req_baseline_control,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 12.6]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"BIND", igloo_httpp_req_bind,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC5842, Section 4]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"CHECKIN", igloo_httpp_req_checkin,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 4.4, Section 9.4]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"CHECKOUT", igloo_httpp_req_checkout,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 4.3, Section 8.8]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"COPY", igloo_httpp_req_copy,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4918, Section 9.8]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"LABEL", igloo_httpp_req_label,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 8.2]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"LINK", igloo_httpp_req_link,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC2068, Section 19.6.1.2]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"LOCK", igloo_httpp_req_lock,
|
|
/* Safe: no, Idempotent: no, Reference: "[RFC4918, Section 9.10]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"MERGE", igloo_httpp_req_merge,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 11.2]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"MKACTIVITY", igloo_httpp_req_mkactivity,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 13.5]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"MKCALENDAR", igloo_httpp_req_mkcalendar,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4791, Section 5.3.1][RFC8144, Section 2.3]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"MKCOL", igloo_httpp_req_mkcol,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4918, Section 9.3][RFC5689, Section 3][RFC8144, Section 2.3]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"MKREDIRECTREF", igloo_httpp_req_mkredirectref,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4437, Section 6]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"MKWORKSPACE", igloo_httpp_req_mkworkspace,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 6.3]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"MOVE", igloo_httpp_req_move,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4918, Section 9.9]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"ORDERPATCH", igloo_httpp_req_orderpatch,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3648, Section 7]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"PRI", igloo_httpp_req_pri,
|
|
/* Safe: yes, Idempotent: yes, Reference: "[RFC7540, Section 3.5]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"PROPFIND", igloo_httpp_req_propfind,
|
|
/* Safe: yes, Idempotent: yes, Reference: "[RFC4918, Section 9.1][RFC8144, Section 2.1]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"PROPPATCH", igloo_httpp_req_proppatch,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4918, Section 9.2][RFC8144, Section 2.2]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"REBIND", igloo_httpp_req_rebind,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC5842, Section 6]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"REPORT", igloo_httpp_req_report,
|
|
/* Safe: yes, Idempotent: yes, Reference: "[RFC3253, Section 3.6][RFC8144, Section 2.1]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"SEARCH", igloo_httpp_req_search,
|
|
/* Safe: yes, Idempotent: yes, Reference: "[RFC5323, Section 2]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"UNBIND", igloo_httpp_req_unbind,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC5842, Section 5]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"UNCHECKOUT", igloo_httpp_req_uncheckout,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 4.5]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"UNLINK", igloo_httpp_req_unlink,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC2068, Section 19.6.1.3]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"UNLOCK", igloo_httpp_req_unlock,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4918, Section 9.11]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"UPDATE", igloo_httpp_req_update,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 7.1]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"UPDATEREDIRECTREF", igloo_httpp_req_updateredirectref,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC4437, Section 7]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
{"VERSION-CONTROL", igloo_httpp_req_version_control,
|
|
/* Safe: no, Idempotent: yes, Reference: "[RFC3253, Section 3.5]" */
|
|
igloo_HTTPP_REQUEST_INVALID
|
|
},
|
|
};
|
|
|
|
igloo_httpp_request_info_t igloo_httpp_request_info(igloo_httpp_request_type_e req)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(igloo_httpp__methods)/sizeof(*igloo_httpp__methods)); i++) {
|
|
if (igloo_httpp__methods[i].req == req) {
|
|
return igloo_httpp__methods[i].info;
|
|
}
|
|
}
|
|
|
|
return igloo_HTTPP_REQUEST_INVALID;
|
|
}
|
|
|
|
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) {
|
|
const char *method = igloo_httpp_method_to_str(parser->req_type);
|
|
if (method) {
|
|
igloo_httpp_setvar(parser, igloo_HTTPP_VAR_REQ_TYPE, method);
|
|
}
|
|
} 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)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(igloo_httpp__methods)/sizeof(*igloo_httpp__methods)); i++) {
|
|
if (strcasecmp(igloo_httpp__methods[i].name, method) == 0) {
|
|
return igloo_httpp__methods[i].req;
|
|
}
|
|
}
|
|
|
|
return igloo_httpp_req_unknown;
|
|
}
|
|
|
|
const char * igloo_httpp_method_to_str(igloo_httpp_request_type_e method)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(igloo_httpp__methods)/sizeof(*igloo_httpp__methods)); i++) {
|
|
if (igloo_httpp__methods[i].req == method) {
|
|
return igloo_httpp__methods[i].name;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|