Commit 0db820c9 authored by Philipp Schafft's avatar Philipp Schafft 🦁

Feature: Added parser for Accept*:-headers

parent 3b55f169
......@@ -777,6 +777,450 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset,
return ret;
}
#define __SELECT_BEST_MAX_ARGS 8
struct __args {
const char *comp;
char *group;
int best_comp;
int best_group;
int best_all;
};
static inline int __fill_arg(struct __args *arg, const char *str)
{
char *delm;
size_t len;
arg->comp = str;
arg->best_comp = 0;
arg->best_group = 0;
arg->best_all = 0;
len = strlen(str);
arg->group = malloc(len + 2);
if (!arg->group)
return -1;
memcpy(arg->group, str, len + 1);
delm = strstr(arg->group, "/");
if (delm) {
delm[0] = '/';
delm[1] = '*';
delm[2] = 0;
}
return 0;
}
static inline void __free_args(struct __args *arg, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
free(arg->group);
}
}
// Accept: text/html, application/xhtml+xml, */*
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
static inline int __parse_q(const char *str)
{
int ret = 0;
int mul = 1000;
for (; *str; str++) {
if (*str >= '0' && *str <= '9') {
ret += mul * (*str - '0');
mul /= 10;
} else if (*str == '.') {
mul = 100;
} else {
ICECAST_LOG_ERROR("Badly formated quality parameter found.");
return -1;
}
}
return ret;
}
static inline int __find_q_in_index(icecast_kva_t *kv, size_t idx)
{
size_t i = kv->index[idx] + 1;
size_t last;
if (kv->indexlen <= (idx + 1)) {
last = kv->kvlen - 1;
} else {
last = kv->index[idx + 1] - 1;
}
for (; i <= last; i++) {
if (kv->kv[i].key && kv->kv[i].key && strcasecmp(kv->kv[i].key, "q") == 0) {
return __parse_q(kv->kv[i].value);
}
}
return 1000;
}
const char *util_http_select_best(const char *input, const char *first, ...)
{
struct __args arg[__SELECT_BEST_MAX_ARGS];
icecast_kva_t *kv;
const char *p;
size_t arglen = 1;
size_t i, h;
va_list ap;
int q;
int best_q = 0;
if (__fill_arg(&(arg[0]), first) == -1) {
ICECAST_LOG_ERROR("Can not allocate memory. Selecting first option.");
return first;
}
va_start(ap, first);
while ((p = (const char*)va_arg(ap, const char*))) {
if (arglen == __SELECT_BEST_MAX_ARGS) {
ICECAST_LOG_ERROR("More arguments given than supported. Currently %zu args are supported.", (size_t)__SELECT_BEST_MAX_ARGS);
break;
}
if (__fill_arg(&(arg[arglen]), p) == -1) {
ICECAST_LOG_ERROR("Can not allocate memory. Selecting first option.");
__free_args(arg, arglen);
return first;
}
arglen++;
}
va_end(ap);
kv = util_parse_http_cn(input);
if (!kv) {
ICECAST_LOG_ERROR("Input string does not parse as KVA. Selecting first option.");
__free_args(arg, arglen);
return first;
}
ICECAST_LOG_DEBUG("--- DUMP ---");
for (i = 0; i < kv->kvlen; i++) {
ICECAST_LOG_DEBUG("kv[%zu] = {.key='%H', .value='%H'}", i, kv->kv[i].key, kv->kv[i].value);
}
for (i = 0; i < kv->indexlen; i++) {
ICECAST_LOG_DEBUG("index[%zu] = %zu", i, kv->index[i]);
}
ICECAST_LOG_DEBUG("--- END OF DUMP ---");
for (h = 0; h < arglen; h++) {
for (i = 0; i < kv->indexlen; i++) {
p = kv->kv[kv->index[i]].key;
if (!p) {
continue;
}
q = __find_q_in_index(kv, i);
if (best_q < q) {
best_q = q;
}
if (strcasecmp(p, arg[h].comp) == 0) {
if (arg[h].best_comp < q) {
arg[h].best_comp = q;
}
}
if (strcasecmp(p, arg[h].group) == 0) {
if (arg[h].best_group < q) {
arg[h].best_group = q;
}
}
}
}
util_kva_free(kv);
p = NULL;
for (h = 0; p == NULL && h < arglen; h++) {
if (arg[h].best_comp == best_q) {
p = arg[h].comp;
}
}
for (h = 0; p == NULL && h < arglen; h++) {
if (arg[h].best_group == best_q) {
p = arg[h].comp;
}
}
__free_args(arg, arglen);
if (p == NULL) {
p = first;
}
return p;
}
static inline void __skip_space(char **p)
{
for (; **p == ' '; (*p)++);
}
static inline int __is_token(const char p)
{
return (p >= 'a' && p <= 'z') || (p >= 'A' && p <= 'Z') || (p >= '0' && p <= '9') ||
p == '!' || p == '#' || p == '$' || p == '%' || p == '&' || p == '\'' ||
p == '*' || p == '+' || p == '-' || p == '.' || p == '^' || p == '_' ||
p == '|' || p == '~' || p == '/';
}
enum __tokenizer_result {
__TOKENIZER_RESULT_COMMA,
__TOKENIZER_RESULT_EQ,
__TOKENIZER_RESULT_SEMICOLON,
__TOKENIZER_RESULT_ILSEQ,
__TOKENIZER_RESULT_EOS
};
static inline enum __tokenizer_result __tokenizer_res_from_char(const char p)
{
switch (p) {
case 0:
return __TOKENIZER_RESULT_EOS;
break;
case ',':
return __TOKENIZER_RESULT_COMMA;
break;
case '=':
return __TOKENIZER_RESULT_EQ;
break;
case ';':
return __TOKENIZER_RESULT_SEMICOLON;
break;
default:
return __TOKENIZER_RESULT_ILSEQ;
break;
}
}
static enum __tokenizer_result __tokenizer_str(char **out, char **in)
{
char *p, *o;
char c;
__skip_space(in);
p = *in;
if (*p != '"')
return __TOKENIZER_RESULT_ILSEQ;
p++;
o = p;
for (; (c = *p); p++) {
if (c == '\t' || c == ' ' || c == 0x21 || (c >= 0x23 && c <= 0x5B) || (c >= 0x5D && c <= 0x7E) || (c >= 0x80 && c <= 0xFF)) {
*(o++) = c;
} else if (c == '\\') {
p++;
c = *p;
if (c == 0) {
return __TOKENIZER_RESULT_ILSEQ;
} else {
*(o++) = c;
}
} else if (c == '"') {
*o = 0;
break;
} else {
return __TOKENIZER_RESULT_ILSEQ;
}
}
if (*p == '"') {
p++;
*in = p + 1;
__skip_space(in);
return __tokenizer_res_from_char(*p);
} else {
return __TOKENIZER_RESULT_ILSEQ;
}
}
static enum __tokenizer_result __tokenizer(char **out, char **in)
{
char *p;
char c = 0;
__skip_space(in);
p = *in;
switch (*p) {
case '=':
/* fall through */
case ',':
/* fall through */
case ';':
return __TOKENIZER_RESULT_ILSEQ;
break;
case 0:
return __TOKENIZER_RESULT_EOS;
break;
case '"':
return __tokenizer_str(out, in);
break;
}
*out = p;
for (; __is_token(*p); p++);
*in = p;
if (*p) {
__skip_space(in);
c = **in;
if (c != 0) {
(*in)++;
}
}
*p = 0;
return __tokenizer_res_from_char(c);
}
#define HTTP_CN_INDEX_INCREMENT 8
#define HTTP_CN_KV_INCREMENT 8
static inline int __resize_array(void **also_ptr, void **array, size_t size, size_t *len, size_t revlen, size_t inc)
{
void *n;
if (*len > revlen)
return 0;
n = realloc(*array, size*(*len + inc));
if (!n) {
return -1;
}
memset(n + size * *len, 0, size * inc);
*also_ptr = *array = n;
*len += inc;
return 0;
}
icecast_kva_t * util_parse_http_cn(const char *cnstr)
{
icecast_kva_t *ret;
char *in;
int eos = 0;
size_t indexphylen = HTTP_CN_INDEX_INCREMENT;
size_t kvphylen = HTTP_CN_KV_INCREMENT;
if (!cnstr || !*cnstr)
return NULL;
ret = calloc(1, sizeof(*ret));
if (!ret)
return NULL;
ret->_tofree[0] = in = strdup(cnstr);
ret->_tofree[1] = ret->index = calloc(HTTP_CN_INDEX_INCREMENT, sizeof(*(ret->index)));
ret->_tofree[2] = ret->kv = calloc(HTTP_CN_KV_INCREMENT, sizeof(*(ret->kv)));
if (!ret->_tofree[0] || !ret->_tofree[1] || !ret->_tofree[2]) {
util_kva_free(ret);
return NULL;
}
/* we have at minimum one token */
ret->indexlen = 1;
ret->kvlen = 1;
while (!eos) {
char *out = NULL;
enum __tokenizer_result res = __tokenizer(&out, &in);
switch (res) {
case __TOKENIZER_RESULT_ILSEQ:
ICECAST_LOG_DEBUG("Illegal byte sequence error from tokenizer.");
util_kva_free(ret);
return NULL;
break;
case __TOKENIZER_RESULT_EOS:
/* fall through */
case __TOKENIZER_RESULT_COMMA:
/* fall through */
case __TOKENIZER_RESULT_EQ:
/* fall through */
case __TOKENIZER_RESULT_SEMICOLON:
ICECAST_LOG_DEBUG("OK from tokenizer.");
/* no-op */
break;
}
if (__resize_array(&(ret->_tofree[2]), (void**)&(ret->kv), sizeof(*(ret->kv)), &kvphylen, ret->kvlen, HTTP_CN_KV_INCREMENT) == -1 ||
__resize_array(&(ret->_tofree[1]), (void**)&(ret->index), sizeof(*(ret->index)), &indexphylen, ret->indexlen, HTTP_CN_INDEX_INCREMENT) == -1) {
util_kva_free(ret);
return NULL;
}
if (ret->kv[ret->kvlen-1].key == NULL) {
ret->kv[ret->kvlen-1].key = out;
} else if (ret->kv[ret->kvlen-1].value == NULL) {
ret->kv[ret->kvlen-1].value = out;
} else {
util_kva_free(ret);
return NULL;
}
switch (res) {
case __TOKENIZER_RESULT_EOS:
ICECAST_LOG_DEBUG("End of string from tokenizer.");
eos = 1;
continue;
break;
case __TOKENIZER_RESULT_COMMA:
ICECAST_LOG_DEBUG("Comma from tokenizer.");
ret->index[ret->indexlen++] = ret->kvlen;
ret->kvlen++;
break;
case __TOKENIZER_RESULT_EQ:
ICECAST_LOG_DEBUG("Eq from tokenizer.");
/* no-op */
break;
case __TOKENIZER_RESULT_SEMICOLON:
ICECAST_LOG_DEBUG("Semicolon from tokenizer.");
ret->kvlen++;
break;
default:
util_kva_free(ret);
return NULL;
break;
}
ICECAST_LOG_DEBUG("next...");
}
return ret;
}
void util_kva_free(icecast_kva_t *kva)
{
size_t i;
if (!kva)
return;
for (i = 0; i < (sizeof(kva->_tofree)/sizeof(*(kva->_tofree))); i++)
free(kva->_tofree[i]);
free(kva);
}
util_dict *util_dict_new(void)
{
......
......@@ -88,6 +88,24 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset,
struct source_tag * source,
struct _client_tag * client);
const char *util_http_select_best(const char *input, const char *first, ...);
typedef struct icecast_kv_tag {
char *key;
char *value;
} icecast_kv_t;
typedef struct icecast_kva_tag {
void *_tofree[3];
size_t kvlen;
size_t indexlen;
size_t *index;
icecast_kv_t *kv;
} icecast_kva_t;
icecast_kva_t * util_parse_http_cn(const char *cnstr);
void util_kva_free(icecast_kva_t *kva);
/* String dictionary type, without support for NULL keys, or multiple
* instances of the same key */
typedef struct _util_dict {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment