mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00
588 lines
11 KiB
C
588 lines
11 KiB
C
/*
|
|
* libwebsockets - Stateful urldecode for POST
|
|
*
|
|
* Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
|
|
*
|
|
* 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 < 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 >= 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 < 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 < sizeof(s->temp) - 1 &&
|
|
(*in != ' ' || s->inside_quote))
|
|
s->temp[s->mp++] = *in;
|
|
goto done;
|
|
}
|
|
|
|
if (!s->temp[0]) {
|
|
if (s->mp < 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 < 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 < 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 < 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;
|
|
}
|