introduce urlencode decode and sql escape public apis
authorAndy Green <andy@warmcat.com>
Wed, 8 Jun 2016 02:07:02 +0000 (10:07 +0800)
committerAndy Green <andy@warmcat.com>
Tue, 14 Jun 2016 04:04:38 +0000 (12:04 +0800)
This adds

 - simple lws_urlencode()
 - simple lws_urldecode()
 - simple lws_sql_purify

Those expect the data to all be there and process it up until
the first '\0'.

There is also a larger opaque apis for handling POST_BODY urldecode.  To
enable these, you need to give cmake -DLWS_WITH_STATEFUL_URLDECODE=1 (or
arrange any larger feature that relies on it sets that in CMakeLists.txt)

 - stateful urldecode with parameter array

These have create / process / destroy semantics on a struct that maintains
decode state.

Stateful urldecode is capable of dealing with large POST data in multiple
POST_BODY callbacks cleanly, eg, file transfer by POST.

Stateful urldecode with parameter array wraps the above with a canned
callback that stores the urldecoded data and indexes them in a pointer
array matching an array of parameter names.

You may also pass it an optional callback when creating it, that will recieve
uploaded file content.

The test html is updated to support both urlencoded and multipart forms,
with some javascript to do clientside validation of an arbitrary 100KB
file size limit (there is no file size limit in the apis).

Signed-off-by: Andy Green <andy@warmcat.com>
CMakeLists.txt
lib/libwebsockets.c
lib/libwebsockets.h
lib/server.c
libwebsockets-api-doc.html
lws_config.h.in
plugins/protocol_post_demo.c
test-server/test-server-http.c
test-server/test-server.h
test-server/test.html

index ad694cb..eceddac 100644 (file)
@@ -100,6 +100,7 @@ option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" OFF)
 option(LWS_WITH_LEJP "With the Lightweight JSON Parser" OFF)
 option(LWS_WITH_LEJP_CONF "With LEJP configuration parser as used by lwsws" OFF)
 option(LWS_WITH_SMTP "Provide SMTP support" OFF)
+option(LWS_WITH_STATEFUL_URLDECODE "Provide stateful URLDECODE apis" OFF)
 
 if (LWS_WITH_LWSWS)
  message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV")
@@ -111,6 +112,10 @@ if (LWS_WITH_LWSWS)
  set(LWS_WITH_LEJP_CONF 1)
 endif()
 
+if (LWS_WITH_PLUGINS)
+ set(LWS_WITH_STATEFUL_URLDECODE 1)
+endif()
+
 if (LWS_WITH_PLUGINS AND NOT LWS_WITH_LIBUV)
 message(STATUS "LWS_WITH_PLUGINS --> Enabling LWS_WITH_LIBUV")
  set(LWS_WITH_LIBUV 1)
@@ -1526,6 +1531,8 @@ message(" LWS_WITH_SERVER_STATUS = ${LWS_WITH_SERVER_STATUS}")
 message(" LWS_WITH_LEJP = ${LWS_WITH_LEJP}")
 message(" LWS_WITH_LEJP_CONF = ${LWS_WITH_LEJP_CONF}")
 message(" LWS_WITH_SMTP = ${LWS_WITH_SMTP}")
+message(" LWS_WITH_STATEFUL_URLDECODE = ${LWS_WITH_STATEFUL_URLDECODE}")
+
 message("---------------------------------------------------------------------")
 
 # These will be available to parent projects including libwebsockets using add_subdirectory()
index 4403967..e229ab3 100755 (executable)
@@ -208,7 +208,7 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
            wsi->u.http.fd != LWS_INVALID_FILE) {
                lws_plat_file_close(wsi, wsi->u.http.fd);
                wsi->u.http.fd = LWS_INVALID_FILE;
-               wsi->vhost->protocols[0].callback(wsi,
+               wsi->vhost->protocols->callback(wsi,
                        LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0);
        }
        if (wsi->socket_is_permanently_unusable ||
@@ -247,9 +247,14 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
            wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE)
                goto just_kill_connection;
 
-       if (wsi->mode == LWSCM_HTTP_SERVING)
-               wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
+       if (wsi->mode == LWSCM_HTTP_SERVING) {
+               if (wsi->user_space)
+                       wsi->vhost->protocols->callback(wsi,
+                                               LWS_CALLBACK_HTTP_DROP_PROTOCOL,
                                               wsi->user_space, NULL, 0);
+               wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
+                                              wsi->user_space, NULL, 0);
+       }
        if (wsi->mode == LWSCM_HTTP_CLIENT)
                wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_CLIENT_HTTP,
                                               wsi->user_space, NULL, 0);
@@ -483,7 +488,7 @@ just_kill_connection:
                                        wsi->user_space, NULL, 0);
        } else if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED) {
                lwsl_debug("calling back CLOSED_HTTP\n");
-               wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
+               wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
                                               wsi->user_space, NULL, 0 );
        } else if (wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY ||
                   wsi->mode == LWSCM_WSCL_WAITING_CONNECT) {
@@ -1688,10 +1693,11 @@ lws_socket_bind(struct lws_vhost *vhost, int sockfd, int port,
        return port;
 }
 
-LWS_VISIBLE LWS_EXTERN int
-lws_urlencode(const char *in, int inlen, char *out, int outlen)
+static const char *hex = "0123456789ABCDEF";
+
+static int
+urlencode(const char *in, int inlen, char *out, int outlen)
 {
-       const char *hex = "0123456789ABCDEF";
        char *start = out, *end = out + outlen;
 
        while (inlen-- && out < end - 4) {
@@ -1701,13 +1707,18 @@ lws_urlencode(const char *in, int inlen, char *out, int outlen)
                    *in == '-' ||
                    *in == '_' ||
                    *in == '.' ||
-                   *in == '~')
+                   *in == '~') {
                        *out++ = *in++;
-               else {
-                       *out++ = '%';
-                       *out++ = hex[(*in) >> 4];
-                       *out++ = hex[(*in++) & 15];
+                       continue;
+               }
+               if (*in == ' ') {
+                       *out++ = '+';
+                       in++;
+                       continue;
                }
+               *out++ = '%';
+               *out++ = hex[(*in) >> 4];
+               *out++ = hex[(*in++) & 15];
        }
        *out = '\0';
 
@@ -1717,6 +1728,140 @@ lws_urlencode(const char *in, int inlen, char *out, int outlen)
        return out - start;
 }
 
+/**
+ * lws_sql_purify() - like strncpy but with escaping for sql quotes
+ *
+ * @escaped: output buffer
+ * @string: input buffer ('/0' terminated)
+ * @len: output buffer max length
+ *
+ * Because escaping expands the output string, it's not
+ * possible to do it in-place, ie, with escaped == string
+ */
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_sql_purify(char *escaped, const char *string, int len)
+{
+       const char *p = string;
+       char *q = escaped;
+
+       while (*p && len-- > 2) {
+               if (*p == '\'') {
+                       *q++ = '\\';
+                       *q++ = '\'';
+                       len --;
+                       p++;
+               } else
+                       *q++ = *p++;
+       }
+       *q = '\0';
+
+       return escaped;
+}
+
+/**
+ * lws_urlencode() - like strncpy but with urlencoding
+ *
+ * @escaped: output buffer
+ * @string: input buffer ('/0' terminated)
+ * @len: output buffer max length
+ *
+ * Because urlencoding expands the output string, it's not
+ * possible to do it in-place, ie, with escaped == string
+ */
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_urlencode(char *escaped, const char *string, int len)
+{
+       const char *p = string;
+       char *q = escaped;
+
+       while (*p && len-- > 3) {
+               if (*p == ' ') {
+                       *q++ = '+';
+                       p++;
+                       continue;
+               }
+               if ((*p >= '0' && *p <= '9') ||
+                   (*p >= 'A' && *p <= 'Z') ||
+                   (*p >= 'a' && *p <= 'z')) {
+                       *q++ = *p++;
+                       continue;
+               }
+               *q++ = '%';
+               *q++ = hex[(*p >> 4) & 0xf];
+               *q++ = hex[*p & 0xf];
+
+               len -= 2;
+               p++;
+       }
+       *q = '\0';
+
+       return escaped;
+}
+
+/**
+ * lws_urldecode() - like strncpy but with urldecoding
+ *
+ * @string: output buffer
+ * @escaped: input buffer ('\0' terminated)
+ * @len: output buffer max length
+ *
+ * This is only useful for '\0' terminated strings
+ *
+ * Since urldecoding only shrinks the output string, it is possible to
+ * do it in-place, ie, string == escaped
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_urldecode(char *string, const char *escaped, int len)
+{
+       int state = 0, n;
+       char sum = 0;
+
+       while (*escaped && len) {
+               switch (state) {
+               case 0:
+                       if (*escaped == '%') {
+                               state++;
+                               escaped++;
+                               continue;
+                       }
+                       if (*escaped == '+') {
+                               escaped++;
+                               *string++ = ' ';
+                               len--;
+                               continue;
+                       }
+                       *string++ = *escaped++;
+                       len--;
+                       break;
+               case 1:
+                       n = char_to_hex(*escaped);
+                       if (n < 0)
+                               return -1;
+                       escaped++;
+                       sum = n << 4;
+                       state++;
+                       break;
+
+               case 2:
+                       n = char_to_hex(*escaped);
+                       if (n < 0)
+                               return -1;
+                       escaped++;
+                       *string++ = sum | n;
+                       len--;
+                       state = 0;
+                       break;
+               }
+
+       }
+       *string = '\0';
+
+       return 0;
+}
+
 LWS_VISIBLE LWS_EXTERN int
 lws_finalize_startup(struct lws_context *context)
 {
@@ -1902,7 +2047,7 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len
                                *p++ = *t++;
                        if (*t == '=')
                                *p++ = *t++;
-                       i = lws_urlencode(t, i- (t - tok), p, end - p);
+                       i = urlencode(t, i- (t - tok), p, end - p);
                        if (i > 0) {
                                p += i;
                                *p++ = '&';
index 254d572..bb7c630 100644 (file)
@@ -475,6 +475,7 @@ enum lws_callback_reasons {
        LWS_CALLBACK_RECEIVE_CLIENT_HTTP                        = 46,
        LWS_CALLBACK_COMPLETED_CLIENT_HTTP                      = 47,
        LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ                   = 48,
+       LWS_CALLBACK_HTTP_DROP_PROTOCOL                         = 49,
 
        /****** add new things just above ---^ ******/
 
@@ -1779,6 +1780,69 @@ lws_add_http_header_status(struct lws *wsi,
                           unsigned int code, unsigned char **p,
                           unsigned char *end);
 
+LWS_VISIBLE LWS_EXTERN const char *
+lws_urlencode(char *escaped, const char *string, int len);
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_sql_purify(char *escaped, const char *string, int len);
+
+
+
+/*
+ * URLDECODE 1 / 2
+ *
+ * This simple urldecode only operates until the first '\0' and requires the
+ * data to exist all at once
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_urldecode(char *string, const char *escaped, int len);
+
+
+/*
+ * URLDECODE 2 / 2
+ *
+ * These apis let you manage a form data parser that is capable of handling
+ * both urlencoded POST bodies and multipart ones (including file upload)
+ *
+ * Since it's stateful, and the decoded area is malloc'd, this is robust
+ * enough to handle the form data coming in multiple POST_BODY packets without
+ * having to get into any special code.
+ */
+
+enum lws_spa_fileupload_states {
+       LWS_UFS_CONTENT,
+       LWS_UFS_FINAL_CONTENT,
+       LWS_UFS_OPEN
+};
+
+typedef int (*lws_spa_fileupload_cb)(void *data, const char *name,
+                       const char *filename, char *buf, int len,
+                       enum lws_spa_fileupload_states state);
+
+struct lws_spa;
+
+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);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_process(struct lws_spa *ludspa, const char *in, int len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_finalize(struct lws_spa *ludspa);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_get_length(struct lws_spa *ludspa, int n);
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_spa_get_string(struct lws_spa *ludspa, int n);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_destroy(struct lws_spa *ludspa);
+
+
 LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
 lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len,
                  unsigned char **p, unsigned char *end);
index ce885de..e2456b1 100644 (file)
@@ -716,14 +716,6 @@ lws_http_action(struct lws *wsi)
                        goto after;
                }
 
-               /* deferred cleanup and reset to protocols[0] */
-
-               if (wsi->protocol != &wsi->vhost->protocols[0])
-                       if (!wsi->user_space_externally_allocated)
-                               lws_free_set_NULL(wsi->user_space);
-
-               wsi->protocol = &wsi->vhost->protocols[0];
-
 #ifdef LWS_WITH_CGI
                /* did we hit something with a cgi:// origin? */
                if (hit->origin_protocol == LWSMPRO_CGI) {
@@ -1279,10 +1271,17 @@ lws_http_transaction_completed(struct lws *wsi)
                return 1;
        }
 
+       n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL,
+                                   wsi->user_space, NULL, 0);
+
+       if (!wsi->user_space_externally_allocated)
+               lws_free_set_NULL(wsi->user_space);
+
+       wsi->protocol = &wsi->vhost->protocols[0];
+
        /* otherwise set ourselves up ready to go again */
        wsi->state = LWSS_HTTP;
        wsi->mode = LWSCM_HTTP_SERVING;
-       /* reset of non [0] protocols (and freeing of user_space) is deferred */
        wsi->u.http.content_length = 0;
        wsi->hdr_parsing_completed = 0;
 #ifdef LWS_WITH_ACCESS_LOG
@@ -1921,3 +1920,597 @@ lws_server_get_canonical_hostname(struct lws_context *context,
        (void)context;
 #endif
 }
+
+#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;
+
+       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));
+       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->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 sum = 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;
+
+                       s->pos = 0;
+               }
+
+               switch (s->state) {
+
+               /* states for url arg style */
+
+               case US_NAME:
+                       //lwsl_notice("US_NAME: %c\n", *in);
+                       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:
+                       //lwsl_notice("US_IDLE: %c\n", *in);
+                       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++;
+                       sum = n << 4;
+                       s->state++;
+                       break;
+
+               case US_PC2:
+                       n = char_to_hex(*in);
+                       if (n < 0)
+                               return -1;
+
+                       in++;
+                       s->out[s->pos++] = sum | n;
+                       s->state = US_IDLE;
+                       break;
+
+
+               /* states for multipart / mime style */
+
+               case MT_LOOK_BOUND_IN:
+                       // lwsl_notice("MT_LOOK_BOUND_IN: %02x (%d)\n", *in, s->mp);
+                       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)
+                                               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->out[s->pos++] = *in;
+                       in++;
+                       s->mp = 0;
+                       break;
+
+               case MT_HNAME:
+
+                       m = 0;
+                       for (n = 0; n < ARRAY_SIZE(mp_hdr); n++)
+                               if (tolower(*in) == mp_hdr[n][s->mp]) {
+                                       m++;
+                                       hit = n;
+                               }
+
+                       in++;
+                       if (m > 1) {
+                               s->mp++;
+                               continue;
+                       }
+                       if (!m) {
+                               s->mp = 0;
+                               continue;
+                       }
+
+                       s->mp++;
+                       if (mp_hdr[hit][s->mp])
+                               continue;
+
+                       /* ie, m == 1 */
+
+                       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') {
+                               lwsl_debug("disp: '%s', '%s', '%s'\n",
+                                  s->content_disp, s->name,
+                                  s->content_disp_filename);
+
+                               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;
+                               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;
+
+       lwsl_notice("%s\n", __func__);
+
+       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;
+};
+
+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_spa_create() - create urldecode parser
+ *
+ * @wsi: lws connection (used to find Content Type)
+ * @param_names: array of form parameter names, like "username"
+ * @count_params: count of param_names
+ * @max_storage: total amount of form parameter values we can store
+ * @opt_cb: NULL, or callback to receive file upload data.
+ * @opt_data: NULL, or user pointer provided to opt_cb.
+ *
+ * Creates a urldecode parser and initializes it.
+ *
+ * @opt_cb can be NULL if you just want normal name=value parsing, however
+ * if one or more entries in your form are bulk data (file transfer), you
+ * can provide this callback and filter on the name callback parameter to
+ * treat that urldecoded data separately.  The callback should return -1
+ * in case of fatal error, and 0 if OK.
+ */
+
+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_malloc(sizeof(*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);
+       if (!spa->storage)
+               goto bail2;
+       spa->end = spa->storage + max_storage - 1;
+
+       spa->params = lws_zalloc(sizeof(char *) * count_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);
+       if (!spa->param_length)
+               goto bail5;
+
+       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_spa_process() - parses a chunk of input data
+ *
+ * @ludspa: the parser object previously created
+ * @in: incoming, urlencoded data
+ * @len: count of bytes valid at @in
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_process(struct lws_spa *ludspa, const char *in, int len)
+{
+       return lws_urldecode_s_process(ludspa->s, in, len);
+}
+
+/**
+ * lws_spa_get_length() - return length of parameter value
+ *
+ * @ludspa: the parser object previously created
+ * @n: parameter ordinal to return length of value for
+ */
+
+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_spa_get_string() - return pointer to parameter value
+ *
+ * @ludspa: the parser object previously created
+ * @n: parameter ordinal to return pointer to value for
+ */
+
+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_spa_finalize() - indicate incoming data completed
+ *
+ * @ludspa: the parser object previously created
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_finalize(struct lws_spa *spa)
+{
+       if (spa->s) {
+               lws_urldecode_s_destroy(spa->s);
+               spa->s = NULL;
+       }
+
+       return 0;
+}
+
+/**
+ * lws_spa_destroy() - destroy parser object
+ *
+ * @ludspa: the parser object previously created
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_destroy(struct lws_spa *spa)
+{
+       int n = 0;
+
+       if (spa->s)
+               lws_urldecode_s_destroy(spa->s);
+
+       lwsl_debug("%s\n", __func__);
+
+       lws_free(spa->param_length);
+       lws_free(spa->params);
+       lws_free(spa->storage);
+       lws_free(spa);
+
+       return n;
+}
index 3500041..73fab95 100644 (file)
@@ -412,6 +412,30 @@ unused.
 You will not need this unless you are doing something special
 </blockquote>
 <hr>
+<h2>lws_get_urlarg_by_name - return pointer to arg value if present</h2>
+<i>LWS_EXTERN const char *</i>
+<b>lws_get_urlarg_by_name</b>
+(<i>struct lws *</i> <b>wsi</b>,
+<i>const char *</i> <b>name</b>,
+<i>char *</i> <b>buf</b>,
+<i>int</i> <b>len</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>wsi</b>
+<dd>the connection to check
+<dt><b>name</b>
+<dd>the arg name, like "token="
+<dt><b>buf</b>
+<dd>the buffer to receive the urlarg (including the name= part)
+<dt><b>len</b>
+<dd>the length of the buffer to receive the urlarg
+</dl>
+<h3>Description</h3>
+<blockquote>
+Returns NULL if not found or a pointer inside <tt><b>buf</b></tt> to just after the
+name= part.
+</blockquote>
+<hr>
 <h2>lws_get_peer_simple - Get client address information without RDNS</h2>
 <i>const char *</i>
 <b>lws_get_peer_simple</b>
@@ -739,6 +763,164 @@ Notice it does so by dropping '\0' into input string
 and the leading / on the path is consequently lost
 </blockquote>
 <hr>
+<h2>lws_sql_purify - like strncpy but with escaping for sql quotes</h2>
+<i>LWS_EXTERN const char *</i>
+<b>lws_sql_purify</b>
+(<i>char *</i> <b>escaped</b>,
+<i>const char *</i> <b>string</b>,
+<i>int</i> <b>len</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>escaped</b>
+<dd>output buffer
+<dt><b>string</b>
+<dd>input buffer ('/0' terminated)
+<dt><b>len</b>
+<dd>output buffer max length
+</dl>
+<h3>Description</h3>
+<blockquote>
+Because escaping expands the output string, it's not
+possible to do it in-place, ie, with escaped == string
+</blockquote>
+<hr>
+<h2>lws_urlencode - like strncpy but with urlencoding</h2>
+<i>LWS_EXTERN const char *</i>
+<b>lws_urlencode</b>
+(<i>char *</i> <b>escaped</b>,
+<i>const char *</i> <b>string</b>,
+<i>int</i> <b>len</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>escaped</b>
+<dd>output buffer
+<dt><b>string</b>
+<dd>input buffer ('/0' terminated)
+<dt><b>len</b>
+<dd>output buffer max length
+</dl>
+<h3>Description</h3>
+<blockquote>
+Because urlencoding expands the output string, it's not
+possible to do it in-place, ie, with escaped == string
+</blockquote>
+<hr>
+<h2>lws_urldecode - like strncpy but with urldecoding</h2>
+<i>LWS_EXTERN int</i>
+<b>lws_urldecode</b>
+(<i>char *</i> <b>string</b>,
+<i>const char *</i> <b>escaped</b>,
+<i>int</i> <b>len</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>string</b>
+<dd>output buffer
+<dt><b>escaped</b>
+<dd>input buffer ('\0' terminated)
+<dt><b>len</b>
+<dd>output buffer max length
+</dl>
+<h3>Description</h3>
+<blockquote>
+This is only useful for '\0' terminated strings
+<p>
+Since urldecoding only shrinks the output string, it is possible to
+do it in-place, ie, string == escaped
+</blockquote>
+<hr>
+<h2>lws_urldecode_spa_create - create urldecode parser</h2>
+<i>LWS_EXTERN struct lws_urldecode_stateful_param_array *</i>
+<b>lws_urldecode_spa_create</b>
+(<i>struct lws *</i> <b>wsi</b>,
+<i>const char *const *</i> <b>param_names</b>,
+<i>int</i> <b>count_params</b>,
+<i>int</i> <b>max_storage</b>,
+<i>lws_urldecode_fileupload_cb</i> <b>opt_cb</b>,
+<i>void *</i> <b>opt_data</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>wsi</b>
+<dd>lws connection (used to find Content Type)
+<dt><b>param_names</b>
+<dd>array of form parameter names, like "username"
+<dt><b>count_params</b>
+<dd>count of param_names
+<dt><b>max_storage</b>
+<dd>total amount of form parameter values we can store
+<dt><b>opt_cb</b>
+<dd>NULL, or callback to receive file upload data.
+<dt><b>opt_data</b>
+<dd>NULL, or user pointer provided to opt_cb.
+</dl>
+<h3>Description</h3>
+<blockquote>
+Creates a urldecode parser and initializes it.
+<p>
+<tt><b>opt_cb</b></tt> can be NULL if you just want normal name=value parsing, however
+if one or more entries in your form are bulk data (file transfer), you
+can provide this callback and filter on the name callback parameter to
+treat that urldecoded data separately.  The callback should return -1
+in case of fatal error, and 0 if OK.
+</blockquote>
+<hr>
+<h2>lws_urldecode_spa_process - parses a chunk of input data</h2>
+<i>LWS_EXTERN int</i>
+<b>lws_urldecode_spa_process</b>
+(<i>struct lws_urldecode_stateful_param_array *</i> <b>ludspa</b>,
+<i>const char *</i> <b>in</b>,
+<i>int</i> <b>len</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>ludspa</b>
+<dd>the parser object previously created
+<dt><b>in</b>
+<dd>incoming, urlencoded data
+<dt><b>len</b>
+<dd>count of bytes valid at <tt><b>in</b></tt>
+</dl>
+<hr>
+<h2>lws_urldecode_spa_get_length - return length of parameter value</h2>
+<i>LWS_EXTERN int</i>
+<b>lws_urldecode_spa_get_length</b>
+(<i>struct lws_urldecode_stateful_param_array *</i> <b>ludspa</b>,
+<i>int</i> <b>n</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>ludspa</b>
+<dd>the parser object previously created
+<dt><b>n</b>
+<dd>parameter ordinal to return length of value for
+</dl>
+<hr>
+<h2>lws_urldecode_spa_get_string - return pointer to parameter value</h2>
+<i>LWS_EXTERN const char *</i>
+<b>lws_urldecode_spa_get_string</b>
+(<i>struct lws_urldecode_stateful_param_array *</i> <b>ludspa</b>,
+<i>int</i> <b>n</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>ludspa</b>
+<dd>the parser object previously created
+<dt><b>n</b>
+<dd>parameter ordinal to return pointer to value for
+</dl>
+<hr>
+<h2>lws_urldecode_spa_finalize - indicate incoming data completed</h2>
+<i>LWS_EXTERN int</i>
+<b>lws_urldecode_spa_finalize</b>
+(<i>struct lws_urldecode_stateful_param_array *</i> <b>spa</b>)
+<h3>Arguments</h3>
+<dl>
+</dl>
+<hr>
+<h2>lws_urldecode_spa_destroy - destroy parser object</h2>
+<i>LWS_EXTERN int</i>
+<b>lws_urldecode_spa_destroy</b>
+(<i>struct lws_urldecode_stateful_param_array *</i> <b>spa</b>)
+<h3>Arguments</h3>
+<dl>
+</dl>
+<hr>
 <h2>lws_cgi - connected cgi process</h2>
 <i>LWS_EXTERN int</i>
 <b>lws_cgi</b>
@@ -1231,6 +1413,55 @@ would call it with a timeout_ms of 0, so it returns immediately if
 nothing is pending, or as soon as it services whatever was pending.
 </blockquote>
 <hr>
+<h2>lws_email_init - Initialize a struct lws_email</h2>
+<i>LWS_EXTERN int</i>
+<b>lws_email_init</b>
+(<i>struct lws_email *</i> <b>email</b>,
+<i>uv_loop_t *</i> <b>loop</b>,
+<i>int</i> <b>max_content</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>email</b>
+<dd>struct lws_email to init
+<dt><b>loop</b>
+<dd>libuv loop to use
+<dt><b>max_content</b>
+<dd>max email content size
+</dl>
+<h3>Description</h3>
+<blockquote>
+Prepares a struct lws_email for use ending SMTP
+</blockquote>
+<hr>
+<h2>lws_email_check - Request check for new email</h2>
+<i>LWS_EXTERN void</i>
+<b>lws_email_check</b>
+(<i>struct lws_email *</i> <b>email</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>email</b>
+<dd>struct lws_email context to check
+</dl>
+<h3>Description</h3>
+<blockquote>
+Schedules a check for new emails in 1s... call this when you have queued an
+email for send.
+</blockquote>
+<hr>
+<h2>lws_email_destroy - stop using the struct lws_email</h2>
+<i>LWS_EXTERN void</i>
+<b>lws_email_destroy</b>
+(<i>struct lws_email *</i> <b>email</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>email</b>
+<dd>the struct lws_email context
+</dl>
+<h3>Description</h3>
+<blockquote>
+Stop sending email using <tt><b>email</b></tt> and free allocations
+</blockquote>
+<hr>
 <h2>struct lws_plat_file_ops - Platform-specific file operations</h2>
 <b>struct lws_plat_file_ops</b> {<br>
 &nbsp; &nbsp; <i>lws_filefd_type (*</i><b>open</b>) <i>(struct lws *wsi, const char *filename,unsigned long *filelen, int flags)</i>;<br>
@@ -1860,6 +2091,9 @@ header.
 &nbsp; &nbsp; <i>const struct lws_http_mount *</i> <b>mounts</b>;<br>
 &nbsp; &nbsp; <i>const char *</i> <b>server_string</b>;<br>
 &nbsp; &nbsp; <i>unsigned int</i> <b>pt_serv_buf_size</b>;<br>
+&nbsp; &nbsp; <i>unsigned int</i> <b>max_http_header_data2</b>;<br>
+&nbsp; &nbsp; <i>long</i> <b>ssl_options_set</b>;<br>
+&nbsp; &nbsp; <i>long</i> <b>ssl_options_clear</b>;<br>
 };<br>
 <h3>Members</h3>
 <dl>
@@ -1987,6 +2221,15 @@ various service related features including file serving, it
 defines the max chunk of file that can be sent at once.
 At the risk of lws having to buffer failed large sends, it
 can be increased to, eg, 128KiB to improve throughput.
+<dt><b>max_http_header_data2</b>
+<dd>if <tt><b>max_http_header_data</b></tt> is 0 and this
+is nonzero, this will be used in place of the default.  It's
+like this for compatibility with the original short version,
+this is unsigned int length.
+<dt><b>ssl_options_set</b>
+<dd>VHOST: Any bits set here will be set as SSL options
+<dt><b>ssl_options_clear</b>
+<dd>VHOST: Any bits set here will be cleared as SSL options
 </dl>
 <h3>Description</h3>
 <blockquote>
index 21f1b53..9e61d6f 100644 (file)
@@ -96,6 +96,8 @@
 #cmakedefine LWS_WITH_ACCESS_LOG
 #cmakedefine LWS_WITH_SERVER_STATUS
 
+#cmakedefine LWS_WITH_STATEFUL_URLDECODE
+
 /* Maximum supported service threads */
 #define LWS_MAX_SMP ${LWS_MAX_SMP}
 
index e2e0b73..9f0cab8 100644 (file)
 #include <string.h>
 
 struct per_session_data__post_demo {
-       char post_string[256];
-       char result[500 + LWS_PRE];
+       struct lws_spa *spa;
+       char result[LWS_PRE + 500];
        int result_len;
+
+       char filename[256];
+       long file_length;
+       int fd;
+};
+
+static const char * const param_names[] = {
+       "text",
+       "send",
+       "file",
+       "upload",
+};
+
+enum enum_param_names {
+       EPN_TEXT,
+       EPN_SEND,
+       EPN_FILE,
+       EPN_UPLOAD,
 };
 
 static int
+file_upload_cb(void *data, const char *name, const char *filename,
+              char *buf, int len, enum lws_spa_fileupload_states state)
+{
+       struct per_session_data__post_demo *pss =
+                       (struct per_session_data__post_demo *)data;
+       int n;
+
+       switch (state) {
+       case LWS_UFS_OPEN:
+               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               /* we get the original filename in @filename arg, but for
+                * simple demo use a fixed name so we don't have to deal with
+                * attacks  */
+               pss->fd = open("/tmp/post-file",
+                              O_CREAT | O_TRUNC | O_RDWR, 0600);
+               break;
+       case LWS_UFS_FINAL_CONTENT:
+       case LWS_UFS_CONTENT:
+               if (len) {
+                       pss->file_length += len;
+
+                       /* if the file length is too big, drop it */
+                       if (pss->file_length > 100000)
+                               return 1;
+
+                       n = write(pss->fd, buf, len);
+                       lwsl_notice("%s: write %d says %d\n", __func__, len, n);
+               }
+               if (state == LWS_UFS_CONTENT)
+                       break;
+               close(pss->fd);
+               pss->fd = LWS_INVALID_FILE;
+               break;
+       }
+
+       return 0;
+}
+
+static int
 callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
                   void *user, void *in, size_t len)
 {
@@ -41,34 +98,47 @@ callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
        int n;
 
        switch (reason) {
-
        case LWS_CALLBACK_HTTP_BODY:
-               lwsl_debug("LWS_CALLBACK_HTTP_BODY: len %d\n", (int)len);
-               strncpy(pss->post_string, in, sizeof (pss->post_string) -1);
-               pss->post_string[sizeof(pss->post_string) - 1] = '\0';
-
-               if (len < sizeof(pss->post_string) - 1)
-                       pss->post_string[len] = '\0';
+               /* create the POST argument parser if not already existing */
+               if (!pss->spa) {
+                       pss->spa = lws_spa_create(wsi, param_names,
+                                       ARRAY_SIZE(param_names), 1024,
+                                       file_upload_cb, pss);
+                       if (!pss->spa)
+                               return -1;
+
+                       pss->filename[0] = '\0';
+                       pss->file_length = 0;
+               }
+
+               /* let it parse the POST data */
+               if (lws_spa_process(pss->spa, in, len))
+                       return -1;
                break;
 
-       case LWS_CALLBACK_HTTP_WRITEABLE:
-               lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len);
-               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
-                             pss->result_len, LWS_WRITE_HTTP);
-               if (n < 0)
-                       return 1;
-               goto try_to_reuse;
-
        case LWS_CALLBACK_HTTP_BODY_COMPLETION:
                lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
-               /*
-                * the whole of the sent body arrived,
-                * respond to the client with a redirect to show the
-                * results
-                */
-               pss->result_len = sprintf((char *)pss->result + LWS_PRE,
-                           "<html><body><h1>Form results</h1>'%s'<br>"
-                           "</body></html>", pss->post_string);
+               /* call to inform no more payload data coming */
+               lws_spa_finalize(pss->spa);
+
+               p = (unsigned char *)pss->result + LWS_PRE;
+               end = p + sizeof(pss->result) - LWS_PRE - 1;
+               p += sprintf((char *)p,
+                       "<html><body><h1>Form results (after urldecoding)</h1>"
+                       "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>");
+
+               for (n = 0; n < ARRAY_SIZE(param_names); n++)
+                       p += snprintf((char *)p, end - p,
+                                   "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>",
+                                   param_names[n],
+                                   lws_spa_get_length(pss->spa, n),
+                                   lws_spa_get_string(pss->spa, n));
+
+               p += snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld",
+                               pss->filename, pss->file_length);
+
+               p += snprintf((char *)p, end - p, "</body></html>");
+               pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE);
 
                p = buffer + LWS_PRE;
                start = p;
@@ -89,13 +159,26 @@ callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
                if (n < 0)
                        return 1;
 
-               /*
-                *  send the payload next time, in case would block after
-                * headers
-                */
                lws_callback_on_writable(wsi);
                break;
 
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
+                          pss->result_len);
+               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
+                             pss->result_len, LWS_WRITE_HTTP);
+               if (n < 0)
+                       return 1;
+               goto try_to_reuse;
+
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               break;
+
        default:
                break;
        }
index 215dc22..c7b4264 100644 (file)
@@ -121,6 +121,60 @@ const char * get_mimetype(const char *file)
        return NULL;
 }
 
+
+static const char * const param_names[] = {
+       "text",
+       "send",
+       "file",
+       "upload",
+};
+
+enum enum_param_names {
+       EPN_TEXT,
+       EPN_SEND,
+       EPN_FILE,
+       EPN_UPLOAD,
+};
+
+static int
+file_upload_cb(void *data, const char *name, const char *filename,
+              char *buf, int len, enum lws_spa_fileupload_states state)
+{
+       struct per_session_data__http *pss =
+                       (struct per_session_data__http *)data;
+       int n;
+
+       switch (state) {
+       case LWS_UFS_OPEN:
+               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               /* we get the original filename in @filename arg, but for
+                * simple demo use a fixed name so we don't have to deal with
+                * attacks  */
+               pss->post_fd = open("/tmp/post-file",
+                              O_CREAT | O_TRUNC | O_RDWR, 0600);
+               break;
+       case LWS_UFS_FINAL_CONTENT:
+       case LWS_UFS_CONTENT:
+               if (len) {
+                       pss->file_length += len;
+
+                       /* if the file length is too big, drop it */
+                       if (pss->file_length > 100000)
+                               return 1;
+
+                       n = write(pss->post_fd, buf, len);
+                       lwsl_notice("%s: write %d says %d\n", __func__, len, n);
+               }
+               if (state == LWS_UFS_CONTENT)
+                       break;
+               close(pss->post_fd);
+               pss->post_fd = LWS_INVALID_FILE;
+               break;
+       }
+
+       return 0;
+}
+
 /* this protocol server (always the first one) handles HTTP,
  *
  * Some misc callbacks that aren't associated with a protocol also turn up only
@@ -226,39 +280,6 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
                }
 #endif
 
-               if (!strncmp(in, "/postresults", 12)) {
-                       m = sprintf(buf, "<html><body>Form results: '%s'<br>"
-                                       "</body></html>", pss->post_string);
-
-                       p = buffer + LWS_PRE;
-                       start = p;
-                       end = p + sizeof(buffer) - LWS_PRE;
-
-                       if (lws_add_http_header_status(wsi, 200, &p, end))
-                               return 1;
-                       if (lws_add_http_header_by_token(wsi,
-                                       WSI_TOKEN_HTTP_CONTENT_TYPE,
-                                       (unsigned char *)"text/html",
-                                       9, &p, end))
-                               return 1;
-                       if (lws_add_http_header_content_length(wsi, m, &p,
-                                                              end))
-                               return 1;
-                       if (lws_finalize_http_header(wsi, &p, end))
-                               return 1;
-
-                       n = lws_write(wsi, start, p - start,
-                                     LWS_WRITE_HTTP_HEADERS);
-                       if (n < 0)
-                               return 1;
-
-                       n = lws_write(wsi, (unsigned char *)buf, m, LWS_WRITE_HTTP);
-                       if (n < 0)
-                               return 1;
-
-                       goto try_to_reuse;
-               }
-
                /* if a legal POST URL, let it continue and accept data */
                if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
                        return 0;
@@ -404,27 +425,76 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
                break;
 
        case LWS_CALLBACK_HTTP_BODY:
-               lwsl_notice("LWS_CALLBACK_HTTP_BODY: len %d\n", (int)len);
-               strncpy(pss->post_string, in, sizeof (pss->post_string) -1);
-               pss->post_string[sizeof(pss->post_string) - 1] = '\0';
-               if (len < sizeof(pss->post_string) - 1)
-                       pss->post_string[len] = '\0';
+               /* create the POST argument parser if not already existing */
+               if (!pss->spa) {
+                       pss->spa = lws_spa_create(wsi, param_names,
+                                       ARRAY_SIZE(param_names), 1024,
+                                       file_upload_cb, pss);
+                       if (!pss->spa)
+                               return -1;
+
+                       pss->filename[0] = '\0';
+                       pss->file_length = 0;
+               }
+
+               /* let it parse the POST data */
+               if (lws_spa_process(pss->spa, in, len))
+                       return -1;
                break;
 
        case LWS_CALLBACK_HTTP_BODY_COMPLETION:
-               lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+               lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
                /*
                 * the whole of the sent body arrived,
                 * respond to the client with a redirect to show the
                 * results
                 */
-               p = (unsigned char *)buf + LWS_PRE;
-               n = lws_http_redirect(wsi,
-                                     HTTP_STATUS_SEE_OTHER, /* 303 */
-                                     (unsigned char *)"/postresults", 12, /* location + len */
-                                     &p, /* temp buffer to use */
-                                     p + sizeof(buf) - 1 - LWS_PRE /* buffer len */
-                       );
+
+               /* call to inform no more payload data coming */
+               lws_spa_finalize(pss->spa);
+
+               p = (unsigned char *)pss->result + LWS_PRE;
+               end = p + sizeof(pss->result) - LWS_PRE - 1;
+               p += sprintf((char *)p,
+                       "<html><body><h1>Form results (after urldecoding)</h1>"
+                       "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>");
+
+               for (n = 0; n < ARRAY_SIZE(param_names); n++)
+                       p += snprintf((char *)p, end - p,
+                                   "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>",
+                                   param_names[n],
+                                   lws_spa_get_length(pss->spa, n),
+                                   lws_spa_get_string(pss->spa, n));
+
+               p += snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld",
+                               pss->filename, pss->file_length);
+
+               p += snprintf((char *)p, end - p, "</body></html>");
+               pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE);
+
+               p = buffer + LWS_PRE;
+               start = p;
+               end = p + sizeof(buffer) - LWS_PRE;
+
+               if (lws_add_http_header_status(wsi, 200, &p, end))
+                       return 1;
+
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                               (unsigned char *)"text/html", 9, &p, end))
+                       return 1;
+               if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
+                       return 1;
+               if (lws_finalize_http_header(wsi, &p, end))
+                       return 1;
+
+               n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
+               if (n < 0)
+                       return 1;
+
+               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
+                             pss->result_len, LWS_WRITE_HTTP);
+               if (n < 0)
+                       return 1;
                goto try_to_reuse;
 
        case LWS_CALLBACK_HTTP_FILE_COMPLETION:
index 1118a02..9547402 100644 (file)
@@ -70,7 +70,6 @@ extern void test_server_unlock(int care);
 
 struct per_session_data__http {
        lws_filefd_type fd;
-       char post_string[256];
 #ifdef LWS_WITH_CGI
        struct lws_cgi_args args;
 #endif
@@ -78,6 +77,15 @@ struct per_session_data__http {
        int reason_bf;
 #endif
        unsigned int client_finished:1;
+
+
+       struct lws_spa *spa;
+       char result[500 + LWS_PRE];
+       int result_len;
+
+       char filename[256];
+       long file_length;
+       int post_fd;
 };
 
 /*
index ebf01de..c7ae069 100644 (file)
                -moz-border-radius:8px;
                border-radius:8px;
                color:#404000; }
+       .tdform { vertical-align:middle; width:200px; height:50px;
+               text-align:center;
+               background:#f0f0d0; padding:6px;
+               -webkit-border-radius:8px;
+               -moz-border-radius:8px; margin:10px;
+               border-radius:8px;
+               border: 1px solid black;
+               border-collapse: collapse;font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; 
+               color:#404000; }
+               
        td.l { vertical-align:middle;
                text-align:center;
                background:#d0d0b0; 
@@ -258,7 +268,7 @@ whenever the information changes server-side.
 
        <div class="content">
 <div id="ot" class="group2">
-      <table>
+      <table width=100%>
        <tr>
                <td colspan=1>
 <span class="title">POST Form testing</span>
@@ -268,14 +278,53 @@ whenever the information changes server-side.
 This tests POST handling in lws.
 </td></tr>
        <tr>
-       <td align=center colspan=2><div id=postinfo>
+       <td align=center colspan=2 class=tdform><div id=postinfo style=form>
+       FORM 1: send with urlencoded POST body args<br>
        <form action="formtest" method="post">
-  Some text:<br>
-  <input type="text" name="Text" value="Give me some text"><br>
-  <input type="submit" value="Send the form">
+ <span style="font-size:12pt;">Some text: </span>
+  <input type="text" name="text" value="Give me some text"><br>
+  <input type="submit" name="send" value="Send the form">
+       </form>
+       </div></td>
+       </tr>
+
+<script>
+function check_file()
+{
+       var f = document.getElementById('file').files[0];
+       var max_len = 100000;
+       var dis = 0;
+       
+       if (f) {
+               if (f.size >= max_len) {
+                       dis = 1;
+                       document.getElementById('file_info').innerHTML =
+                               "<span style=\"color:red;font-weight:bold\">File larger than "+max_len+"</span>";
+               } else
+                       document.getElementById('file_info').innerHTML =
+                               "File length "+f.size;
+       } else
+               dis = 1;
+       
+       document.getElementById('upload').disabled = dis;
+}
+</script>
+
+       <tr>
+       <td align=center colspan=2 class=tdform><div id=postinfo style=form>
+       FORM 2: send with multipart/form-data<br>
+       (can handle file upload, test limited to 100KB)<br>
+       <form name=multipart action="formtest" method="post" enctype="multipart/form-data">
+  <span style="font-size:12pt;">Some text: </span>
+  <input type="text" name="text" value="Give me some text">
+<br>
+  <input type="file" name="file" id="file" size="20"
+     onchange="check_file();">&nbsp;<span id=file_info style="font-size:12pt;"></span><br>
+    <input type="submit" id="upload" name="upload" disabled=1 value="Upload">
        </form>
        </div></td>
        </tr>
+       
 </table>
 </div>
        </div>