diff --git a/src/util.c b/src/util.c index b80cb708..d815edf5 100644 --- a/src/util.c +++ b/src/util.c @@ -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) { diff --git a/src/util.h b/src/util.h index 8ba76fa5..469aca72 100644 --- a/src/util.h +++ b/src/util.h @@ -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 {