libwebsockets/lib/roles/http/server/lws-spa.c
2022-07-04 14:23:02 +01:00

726 lines
14 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "private-lib-core.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,
MT_IGNORE3,
MT_COMPLETED,
};
static struct mp_hdr {
const char * const hdr;
uint8_t hdr_len;
} mp_hdrs[] = {
{ "content-disposition: ", 21 },
{ "content-type: ", 14 },
{ "\x0d\x0a", 2 }
};
struct lws_spa;
typedef int (*lws_urldecode_stateful_cb)(struct lws_spa *spa,
const char *name, char **buf, int len, int final);
struct lws_urldecode_stateful {
char *out;
struct lws_spa *data;
struct lws *wsi;
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;
uint8_t matchable;
uint8_t multipart_form_data:1;
uint8_t inside_quote:1;
uint8_t subname:1;
uint8_t boundary_real_crlf:1;
enum urldecode_stateful state;
lws_urldecode_stateful_cb output;
};
struct lws_spa {
struct lws_urldecode_stateful *s;
lws_spa_create_info_t i;
int *param_length;
char finalized;
char **params;
char *storage;
char *end;
};
static struct lws_urldecode_stateful *
lws_urldecode_s_create(struct lws_spa *spa, struct lws *wsi, char *out,
int out_len, lws_urldecode_stateful_cb output)
{
struct lws_urldecode_stateful *s;
char buf[205], *p;
int m = 0;
if (spa->i.ac)
s = lwsac_use_zero(spa->i.ac, sizeof(*s), spa->i.ac_chunk_size);
else
s = lws_zalloc(sizeof(*s), "stateful urldecode");
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 = spa;
s->wsi = wsi;
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) ||
!strncmp(buf, "multipart/related", 17)) {
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++] = '-';
if (*p == '\"')
p++;
while (m < (int)sizeof(s->mime_boundary) - 1 &&
*p && *p != ' ' && *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, hit;
char c;
while (len--) {
if (s->pos == s->out_len - s->mp - 1) {
if (s->output(s->data, s->name, &s->out, s->pos,
LWS_UFS_CONTENT))
return -1;
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, LWS_UFS_FINAL_CONTENT))
return -1;
s->pos = 0;
s->state = US_IDLE;
in++;
continue;
}
if (s->pos >= (int)sizeof(s->name) - 1) {
lwsl_hexdump_notice(s->name, (size_t)s->pos);
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, LWS_UFS_FINAL_CONTENT))
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++] = (char)(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->output(s->data, s->name,
&s->out, s->pos,
LWS_UFS_FINAL_CONTENT))
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;
if (s->mp >= n) {
memcpy(s->out + s->pos,
s->mime_boundary + n,
(unsigned int)(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:
c =*in;
if (c >= 'A' && c <= 'Z')
c = (char)(c + 'a' - 'A');
if (!s->mp)
/* initially, any of them might match */
s->matchable = (1 << LWS_ARRAY_SIZE(mp_hdrs)) - 1;
hit = -1;
for (n = 0; n < (int)LWS_ARRAY_SIZE(mp_hdrs); n++) {
if (!(s->matchable & (1 << n)))
continue;
/* this guy is still in contention... */
if (s->mp >= mp_hdrs[n].hdr_len) {
/* he went past the end of it */
s->matchable &= (uint8_t)~(1 << n);
continue;
}
if (c != mp_hdrs[n].hdr[s->mp]) {
/* mismatched a char */
s->matchable &= (uint8_t)~(1 << n);
continue;
}
if (s->mp + 1 == mp_hdrs[n].hdr_len) {
/* we have a winner... */
hit = n;
break;
}
}
in++;
if (hit == -1 && !s->matchable) {
/* We ruled them all out */
s->state = MT_IGNORE1;
s->mp = 0;
continue;
}
s->mp++;
if (hit < 0)
continue;
/* we matched the one in hit */
s->mp = 0;
s->temp[0] = '\0';
s->subname = 0;
if (hit == 2)
s->state = MT_LOOK_BOUND_IN;
else
s->state += (unsigned int)hit + 1u;
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 = !!((s->inside_quote ^ 1) & 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;
if (s->mp < (int)sizeof(s->content_disp))
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;
if (*in == '-')
s->state = MT_IGNORE3;
in++;
break;
case MT_IGNORE2:
s->mp = 0;
if (*in == '\x0a')
s->state = MT_HNAME;
in++;
break;
case MT_IGNORE3:
if (*in == '\x0d')
s->state = MT_IGNORE2;
if (*in == '-') {
s->state = MT_COMPLETED;
s->wsi->http.rx_content_remain = 0;
}
in++;
break;
case MT_COMPLETED:
break;
}
}
return 0;
}
static int
lws_urldecode_s_destroy(struct lws_spa *spa, 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,
LWS_UFS_FINAL_CONTENT))
ret = -1;
if (s->output(s->data, s->name, NULL, 0, LWS_UFS_CLOSE))
return -1;
if (!spa->i.ac)
lws_free(s);
return ret;
}
static int
lws_urldecode_spa_lookup(struct lws_spa *spa, const char *name)
{
const char * const *pp = spa->i.param_names;
int n;
for (n = 0; n < spa->i.count_params; n++) {
if (!*pp && !spa->i.param_names_stride && spa->i.ac) {
unsigned int len = (unsigned int)strlen(name);
char **ptr = (char**)spa->i.param_names;
/* Use NULLs at end of list to dynamically create
* unknown entries */
ptr[n] = lwsac_use(spa->i.ac, len + 1, spa->i.ac_chunk_size);
if (!ptr[n])
return -1;
memcpy(ptr[n], name, len);
ptr[n][len] = '\0';
return n;
}
if (*pp && !strcmp(*pp, name))
return n;
if (spa->i.param_names_stride)
pp = (const char * const *)(((char *)pp) + spa->i.param_names_stride);
else
pp++;
}
return -1;
}
static int
lws_urldecode_spa_cb(struct lws_spa *spa, const char *name, char **buf, int len,
int final)
{
int n;
if (final == LWS_UFS_CLOSE || spa->s->content_disp_filename[0]) {
if (spa->i.opt_cb) {
n = spa->i.opt_cb(spa->i.opt_data, name,
spa->s->content_disp_filename,
buf ? *buf : NULL, len, (enum lws_spa_fileupload_states)final);
if (n < 0)
return -1;
}
return 0;
}
n = lws_urldecode_spa_lookup(spa, name);
if (n == -1 || !len) /* unrecognized */
return 0;
if (!spa->i.ac) {
if (!spa->params[n])
spa->params[n] = *buf;
if ((*buf) + len >= spa->end) {
lwsl_info("%s: exceeded storage\n", __func__);
return -1;
}
/* move it on inside storage */
(*buf) += len;
*((*buf)++) = '\0';
spa->s->out_len -= len + 1;
} else {
spa->params[n] = lwsac_use(spa->i.ac, (unsigned int)len + 1,
spa->i.ac_chunk_size);
if (!spa->params[n])
return -1;
memcpy(spa->params[n], *buf, (unsigned int)len);
spa->params[n][len] = '\0';
}
spa->param_length[n] += len;
return 0;
}
struct lws_spa *
lws_spa_create_via_info(struct lws *wsi, const lws_spa_create_info_t *i)
{
struct lws_spa *spa;
if (i->ac)
spa = lwsac_use_zero(i->ac, sizeof(*spa), i->ac_chunk_size);
else
spa = lws_zalloc(sizeof(*spa), "spa");
if (!spa)
return NULL;
spa->i = *i;
if (!spa->i.max_storage)
spa->i.max_storage = 512;
if (i->ac)
spa->storage = lwsac_use(i->ac, (unsigned int)spa->i.max_storage,
i->ac_chunk_size);
else
spa->storage = lws_malloc((unsigned int)spa->i.max_storage, "spa");
if (!spa->storage)
goto bail2;
spa->end = spa->storage + i->max_storage - 1;
if (i->count_params) {
if (i->ac)
spa->params = lwsac_use_zero(i->ac,
sizeof(char *) * (unsigned int)i->count_params, i->ac_chunk_size);
else
spa->params = lws_zalloc(sizeof(char *) * (unsigned int)i->count_params,
"spa params");
if (!spa->params)
goto bail3;
}
spa->s = lws_urldecode_s_create(spa, wsi, spa->storage, i->max_storage,
lws_urldecode_spa_cb);
if (!spa->s)
goto bail4;
if (i->count_params) {
if (i->ac)
spa->param_length = lwsac_use_zero(i->ac,
sizeof(int) * (unsigned int)i->count_params, i->ac_chunk_size);
else
spa->param_length = lws_zalloc(sizeof(int) * (unsigned int)i->count_params,
"spa param len");
if (!spa->param_length)
goto bail5;
}
// lwsl_notice("%s: Created SPA %p\n", __func__, spa);
return spa;
bail5:
lws_urldecode_s_destroy(spa, spa->s);
bail4:
if (!i->ac)
lws_free(spa->params);
bail3:
if (!i->ac)
lws_free(spa->storage);
bail2:
if (!i->ac)
lws_free(spa);
if (i->ac)
lwsac_free(i->ac);
return NULL;
}
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)
{
lws_spa_create_info_t i;
memset(&i, 0, sizeof(i));
i.count_params = count_params;
i.max_storage = max_storage;
i.opt_cb = opt_cb;
i.opt_data = opt_data;
i.param_names = param_names;
return lws_spa_create_via_info(wsi, &i);
}
int
lws_spa_process(struct lws_spa *spa, const char *in, int len)
{
if (!spa) {
lwsl_err("%s: NULL spa\n", __func__);
return -1;
}
/* we reject any junk after the last part arrived and we finalized */
if (spa->finalized)
return 0;
return lws_urldecode_s_process(spa->s, in, len);
}
int
lws_spa_get_length(struct lws_spa *spa, int n)
{
if (n >= spa->i.count_params)
return 0;
return spa->param_length[n];
}
const char *
lws_spa_get_string(struct lws_spa *spa, int n)
{
if (n >= spa->i.count_params)
return NULL;
return spa->params[n];
}
int
lws_spa_finalize(struct lws_spa *spa)
{
if (!spa)
return 0;
if (spa->s) {
lws_urldecode_s_destroy(spa, spa->s);
spa->s = NULL;
}
spa->finalized = 1;
return 0;
}
int
lws_spa_destroy(struct lws_spa *spa)
{
int n = 0;
lwsl_info("%s: destroy spa %p\n", __func__, spa);
if (spa->s)
lws_urldecode_s_destroy(spa, spa->s);
if (spa->i.ac)
lwsac_free(spa->i.ac);
else {
lws_free(spa->param_length);
lws_free(spa->params);
lws_free(spa->storage);
lws_free(spa);
}
return n;
}