1
0
mirror of https://gitlab.xiph.org/xiph/icecast-common.git synced 2024-09-22 04:15:55 -04:00
icecast-common/httpp/httpp.c
Thomas B. Ruecker 8685e6ed5a Explicitly license httpp log net thread and timing
This change was ACKd by all authors and explicitly states the license
to be LGPLv2 in all files to match COPYING. This removes ambiguity
when using these files e.g. as part of libshout.

For a list of authors, pease refer to the copyright headers of each
file.
2014-12-05 09:31:08 +00:00

595 lines
14 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>,
** 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 <avl/avl.h>
#include "httpp.h"
#if defined(_WIN32) && !defined(HAVE_STRCASECMP)
#define strcasecmp stricmp
#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_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("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;
}
}