/* * libwebsockets - Stateful urldecode for POST * * Copyright (C) 2010-2017 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation: * version 2.1 of the License. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include "private-libwebsockets.h" #define LWS_MAX_ELEM_NAME 32 enum urldecode_stateful { US_NAME, US_IDLE, US_PC1, US_PC2, MT_LOOK_BOUND_IN, MT_HNAME, MT_DISP, MT_TYPE, MT_IGNORE1, MT_IGNORE2, }; static const char * const mp_hdr[] = { "content-disposition: ", "content-type: ", "\x0d\x0a" }; typedef int (*lws_urldecode_stateful_cb)(void *data, const char *name, char **buf, int len, int final); struct lws_urldecode_stateful { char *out; void *data; char name[LWS_MAX_ELEM_NAME]; char temp[LWS_MAX_ELEM_NAME]; char content_type[32]; char content_disp[32]; char content_disp_filename[256]; char mime_boundary[128]; int out_len; int pos; int hdr_idx; int mp; int sum; unsigned int multipart_form_data:1; unsigned int inside_quote:1; unsigned int subname:1; unsigned int boundary_real_crlf:1; enum urldecode_stateful state; lws_urldecode_stateful_cb output; }; static struct lws_urldecode_stateful * lws_urldecode_s_create(struct lws *wsi, char *out, int out_len, void *data, lws_urldecode_stateful_cb output) { struct lws_urldecode_stateful *s = lws_zalloc(sizeof(*s), "stateful urldecode"); char buf[200], *p; int m = 0; if (!s) return NULL; s->out = out; s->out_len = out_len; s->output = output; s->pos = 0; s->sum = 0; s->mp = 0; s->state = US_NAME; s->name[0] = '\0'; s->data = data; if (lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_CONTENT_TYPE) > 0) { /* multipart/form-data; boundary=----WebKitFormBoundarycc7YgAPEIHvgE9Bf */ if (!strncmp(buf, "multipart/form-data", 19)) { s->multipart_form_data = 1; s->state = MT_LOOK_BOUND_IN; s->mp = 2; p = strstr(buf, "boundary="); if (p) { p += 9; s->mime_boundary[m++] = '\x0d'; s->mime_boundary[m++] = '\x0a'; s->mime_boundary[m++] = '-'; s->mime_boundary[m++] = '-'; while (m < (int)sizeof(s->mime_boundary) - 1 && *p && *p != ' ') s->mime_boundary[m++] = *p++; s->mime_boundary[m] = '\0'; lwsl_notice("boundary '%s'\n", s->mime_boundary); } } } return s; } static int lws_urldecode_s_process(struct lws_urldecode_stateful *s, const char *in, int len) { int n, m, hit = 0; char c, was_end = 0; while (len--) { if (s->pos == s->out_len - s->mp - 1) { if (s->output(s->data, s->name, &s->out, s->pos, 0)) return -1; was_end = s->pos; s->pos = 0; } switch (s->state) { /* states for url arg style */ case US_NAME: s->inside_quote = 0; if (*in == '=') { s->name[s->pos] = '\0'; s->pos = 0; s->state = US_IDLE; in++; continue; } if (*in == '&') { s->name[s->pos] = '\0'; if (s->output(s->data, s->name, &s->out, s->pos, 1)) return -1; s->pos = 0; s->state = US_IDLE; in++; continue; } if (s->pos >= (int)sizeof(s->name) - 1) { lwsl_notice("Name too long\n"); return -1; } s->name[s->pos++] = *in++; break; case US_IDLE: if (*in == '%') { s->state++; in++; continue; } if (*in == '&') { s->out[s->pos] = '\0'; if (s->output(s->data, s->name, &s->out, s->pos, 1)) return -1; s->pos = 0; s->state = US_NAME; in++; continue; } if (*in == '+') { in++; s->out[s->pos++] = ' '; continue; } s->out[s->pos++] = *in++; break; case US_PC1: n = char_to_hex(*in); if (n < 0) return -1; in++; s->sum = n << 4; s->state++; break; case US_PC2: n = char_to_hex(*in); if (n < 0) return -1; in++; s->out[s->pos++] = s->sum | n; s->state = US_IDLE; break; /* states for multipart / mime style */ case MT_LOOK_BOUND_IN: retry_as_first: if (*in == s->mime_boundary[s->mp] && s->mime_boundary[s->mp]) { in++; s->mp++; if (!s->mime_boundary[s->mp]) { s->mp = 0; s->state = MT_IGNORE1; if (s->pos || was_end) if (s->output(s->data, s->name, &s->out, s->pos, 1)) return -1; s->pos = 0; s->content_disp[0] = '\0'; s->name[0] = '\0'; s->content_disp_filename[0] = '\0'; s->boundary_real_crlf = 1; } continue; } if (s->mp) { n = 0; if (!s->boundary_real_crlf) n = 2; memcpy(s->out + s->pos, s->mime_boundary + n, s->mp - n); s->pos += s->mp; s->mp = 0; goto retry_as_first; } s->out[s->pos++] = *in; in++; s->mp = 0; break; case MT_HNAME: m = 0; c =*in; if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; for (n = 0; n < (int)ARRAY_SIZE(mp_hdr); n++) if (c == mp_hdr[n][s->mp]) { m++; hit = n; } in++; if (!m) { s->mp = 0; continue; } s->mp++; if (m != 1) continue; if (mp_hdr[hit][s->mp]) continue; s->mp = 0; s->temp[0] = '\0'; s->subname = 0; if (hit == 2) s->state = MT_LOOK_BOUND_IN; else s->state += hit + 1; break; case MT_DISP: /* form-data; name="file"; filename="t.txt" */ if (*in == '\x0d') { if (s->content_disp_filename[0]) if (s->output(s->data, s->name, &s->out, s->pos, LWS_UFS_OPEN)) return -1; s->state = MT_IGNORE2; goto done; } if (*in == ';') { s->subname = 1; s->temp[0] = '\0'; s->mp = 0; goto done; } if (*in == '\"') { s->inside_quote ^= 1; goto done; } if (s->subname) { if (*in == '=') { s->temp[s->mp] = '\0'; s->subname = 0; s->mp = 0; goto done; } if (s->mp < (int)sizeof(s->temp) - 1 && (*in != ' ' || s->inside_quote)) s->temp[s->mp++] = *in; goto done; } if (!s->temp[0]) { if (s->mp < (int)sizeof(s->content_disp) - 1) s->content_disp[s->mp++] = *in; s->content_disp[s->mp] = '\0'; goto done; } if (!strcmp(s->temp, "name")) { if (s->mp < (int)sizeof(s->name) - 1) s->name[s->mp++] = *in; else s->mp = (int)sizeof(s->name) - 1; s->name[s->mp] = '\0'; goto done; } if (!strcmp(s->temp, "filename")) { if (s->mp < (int)sizeof(s->content_disp_filename) - 1) s->content_disp_filename[s->mp++] = *in; s->content_disp_filename[s->mp] = '\0'; goto done; } done: in++; break; case MT_TYPE: if (*in == '\x0d') s->state = MT_IGNORE2; else { if (s->mp < (int)sizeof(s->content_type) - 1) s->content_type[s->mp++] = *in; s->content_type[s->mp] = '\0'; } in++; break; case MT_IGNORE1: if (*in == '\x0d') s->state = MT_IGNORE2; in++; break; case MT_IGNORE2: s->mp = 0; if (*in == '\x0a') s->state = MT_HNAME; in++; break; } } return 0; } static int lws_urldecode_s_destroy(struct lws_urldecode_stateful *s) { int ret = 0; if (s->state != US_IDLE) ret = -1; if (!ret) if (s->output(s->data, s->name, &s->out, s->pos, 1)) ret = -1; lws_free(s); return ret; } struct lws_spa { struct lws_urldecode_stateful *s; lws_spa_fileupload_cb opt_cb; const char * const *param_names; int count_params; char **params; int *param_length; void *opt_data; char *storage; char *end; int max_storage; char finalized; }; static int lws_urldecode_spa_lookup(struct lws_spa *spa, const char *name) { int n; for (n = 0; n < spa->count_params; n++) if (!strcmp(spa->param_names[n], name)) return n; return -1; } static int lws_urldecode_spa_cb(void *data, const char *name, char **buf, int len, int final) { struct lws_spa *spa = (struct lws_spa *)data; int n; if (spa->s->content_disp_filename[0]) { if (spa->opt_cb) { n = spa->opt_cb(spa->opt_data, name, spa->s->content_disp_filename, *buf, len, final); if (n < 0) return -1; } return 0; } n = lws_urldecode_spa_lookup(spa, name); if (n == -1 || !len) /* unrecognized */ return 0; if (!spa->params[n]) spa->params[n] = *buf; if ((*buf) + len >= spa->end) { lwsl_notice("%s: exceeded storage\n", __func__); return -1; } spa->param_length[n] += len; /* move it on inside storage */ (*buf) += len; *((*buf)++) = '\0'; spa->s->out_len -= len + 1; return 0; } LWS_VISIBLE LWS_EXTERN struct lws_spa * lws_spa_create(struct lws *wsi, const char * const *param_names, int count_params, int max_storage, lws_spa_fileupload_cb opt_cb, void *opt_data) { struct lws_spa *spa = lws_zalloc(sizeof(*spa), "spa"); if (!spa) return NULL; spa->param_names = param_names; spa->count_params = count_params; spa->max_storage = max_storage; spa->opt_cb = opt_cb; spa->opt_data = opt_data; spa->storage = lws_malloc(max_storage, "spa"); if (!spa->storage) goto bail2; spa->end = spa->storage + max_storage - 1; spa->params = lws_zalloc(sizeof(char *) * count_params, "spa params"); if (!spa->params) goto bail3; spa->s = lws_urldecode_s_create(wsi, spa->storage, max_storage, spa, lws_urldecode_spa_cb); if (!spa->s) goto bail4; spa->param_length = lws_zalloc(sizeof(int) * count_params, "spa param len"); if (!spa->param_length) goto bail5; lwsl_info("%s: Created SPA %p\n", __func__, spa); return spa; bail5: lws_urldecode_s_destroy(spa->s); bail4: lws_free(spa->params); bail3: lws_free(spa->storage); bail2: lws_free(spa); return NULL; } LWS_VISIBLE LWS_EXTERN int lws_spa_process(struct lws_spa *ludspa, const char *in, int len) { if (!ludspa) { lwsl_err("%s: NULL spa\n", __func__); return -1; } /* we reject any junk after the last part arrived and we finalized */ if (ludspa->finalized) return 0; return lws_urldecode_s_process(ludspa->s, in, len); } LWS_VISIBLE LWS_EXTERN int lws_spa_get_length(struct lws_spa *ludspa, int n) { if (n >= ludspa->count_params) return 0; return ludspa->param_length[n]; } LWS_VISIBLE LWS_EXTERN const char * lws_spa_get_string(struct lws_spa *ludspa, int n) { if (n >= ludspa->count_params) return NULL; return ludspa->params[n]; } LWS_VISIBLE LWS_EXTERN int lws_spa_finalize(struct lws_spa *spa) { if (spa->s) { lws_urldecode_s_destroy(spa->s); spa->s = NULL; } spa->finalized = 1; return 0; } LWS_VISIBLE LWS_EXTERN int lws_spa_destroy(struct lws_spa *spa) { int n = 0; lwsl_notice("%s: destroy spa %p\n", __func__, spa); if (spa->s) lws_urldecode_s_destroy(spa->s); lwsl_debug("%s %p %p %p %p\n", __func__, spa->param_length, spa->params, spa->storage, spa); lws_free(spa->param_length); lws_free(spa->params); lws_free(spa->storage); lws_free(spa); return n; }