translate and protect uri test sever use uri path
authorAndy Green <andy.green@linaro.org>
Sun, 10 Nov 2013 07:15:21 +0000 (15:15 +0800)
committerAndy Green <andy.green@linaro.org>
Sun, 10 Nov 2013 07:15:21 +0000 (15:15 +0800)
This translates %xx in the GET uri and removes /.. and /... type sequences along with
translating // or /// etc to /.

Since the result is hopefully secure, it also changes the test server to actually use
the uri path pasted on a resource directory without whitelisting.

Signed-off-by: Andy Green <andy.green@linaro.org>
lib/parsers.c
lib/private-libwebsockets.h
test-server/test-server.c

index ba10899..f6d7120 100644 (file)
@@ -144,6 +144,33 @@ int lws_hdr_simple_create(struct libwebsocket *wsi,
        return 0;
 }
 
+static char char_to_hex(const char c)
+{
+       if (c >= '0' && c <= '9')
+               return c - '0';
+
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+
+       return -1;
+}
+
+static int issue_char(struct libwebsocket *wsi, unsigned char c)
+{
+       if (wsi->u.hdr.ah->pos == sizeof(wsi->u.hdr.ah->data)) {
+               lwsl_warn("excessive header content\n");
+               return -1;
+       }
+       wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = c;
+       if (c)
+               wsi->u.hdr.ah->frags[wsi->u.hdr.ah->next_frag_index].len++;
+
+       return 0;
+}
+
 int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c)
 {
        int n;
@@ -188,12 +215,105 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c)
                                      wsi->u.hdr.parser_state]].len && c == ' ')
                        break;
 
-               /* special case space terminator for get-uri */
-               if (wsi->u.hdr.parser_state == WSI_TOKEN_GET_URI && c == ' ') {
+               if (wsi->u.hdr.parser_state != WSI_TOKEN_GET_URI)
+                       goto check_eol;
+
+               /* special URI processing... end at space */
+
+               if (c == ' ') {
+                       /* enforce starting with / */
+                       if (!wsi->u.hdr.ah->frags[wsi->u.hdr.ah->next_frag_index].len)
+                               if (issue_char(wsi, '/') < 0)
+                                       return -1;
                        c = '\0';
                        wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING;
+                       goto spill;
                }
 
+               /* special URI processing... convert %xx */
+
+               switch (wsi->u.hdr.ues) {
+               case URIES_IDLE:
+                       if (c == '%') {
+                               wsi->u.hdr.ues = URIES_SEEN_PERCENT;
+                               goto swallow;
+                       }
+                       break;
+               case URIES_SEEN_PERCENT:
+                       if (char_to_hex(c) < 0) {
+                               /* regurgitate */
+                               if (issue_char(wsi, '%') < 0)
+                                       return -1;
+                               wsi->u.hdr.ues = URIES_IDLE;
+                               /* continue on to assess c */
+                               break;
+                       }
+                       wsi->u.hdr.esc_stash = c;
+                       wsi->u.hdr.ues = URIES_SEEN_PERCENT_H1;
+                       break;
+                       
+               case URIES_SEEN_PERCENT_H1:
+                       if (char_to_hex(c) < 0) {
+                               /* regurgitate */
+                               issue_char(wsi, '%');
+                               wsi->u.hdr.ues = URIES_IDLE;
+                               /* regurgitate + assess */
+                               if (libwebsocket_parse(wsi, wsi->u.hdr.esc_stash) < 0)
+                                       return -1;
+                               /* continue on to assess c */
+                               break;
+                       }
+                       c = (char_to_hex(wsi->u.hdr.esc_stash) << 4) |
+                                       char_to_hex(c);
+                       break;
+               }
+
+               /*
+                * special URI processing... 
+                *  convert /.. or /... etc to /
+                *  convert // or /// etc to /
+                *  leave /.dir or whatever alone
+                */
+
+               switch (wsi->u.hdr.ups) {
+               case URIPS_IDLE:
+                       /* issue the first / always */
+                       if (c == '/')
+                               wsi->u.hdr.ups = URIPS_SEEN_SLASH;
+                       break;
+               case URIPS_SEEN_SLASH:
+                       /* swallow subsequent slashes */
+                       if (c == '/')
+                               goto swallow;
+                       /* track and swallow the first . after / */
+                       if (c == '.') {
+                               wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT;
+                               goto swallow;
+                       } else
+                               wsi->u.hdr.ups = URIPS_IDLE;
+                       break;
+               case URIPS_SEEN_SLASH_DOT:
+                       /* swallow second . */
+                       if (c == '.') {
+                               wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT_DOT;
+                               goto swallow;
+                       }
+                       /* it was like /.dir ... regurgitate the . */
+                       wsi->u.hdr.ups = URIPS_IDLE;
+                       issue_char(wsi, '.');
+                       break;
+                       
+               case URIPS_SEEN_SLASH_DOT_DOT:
+                       /* swallow prior .. chars and any subsequent . */
+                       if (c == '.')
+                               goto swallow;
+                       else /* last we issued was / so SEEN_SLASH */
+                               wsi->u.hdr.ups = URIPS_SEEN_SLASH;
+                       break;
+               }
+
+check_eol:
+
                /* bail at EOL */
                if (wsi->u.hdr.parser_state != WSI_TOKEN_CHALLENGE &&
                                                                  c == '\x0d') {
@@ -202,15 +322,10 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c)
                        lwsl_parser("*\n");
                }
 
-               if (wsi->u.hdr.ah->pos == sizeof(wsi->u.hdr.ah->data)) {
-                       lwsl_warn("excessive header content\n");
+spill:
+               if (issue_char(wsi, c) < 0)
                        return -1;
-               }
-               wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = c;
-               if (c)
-                       wsi->u.hdr.ah->frags[
-                                       wsi->u.hdr.ah->next_frag_index].len++;
-
+swallow:
                /* per-protocol end of headers management */
 
                if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE)
index 7cf8899..5d2b470 100644 (file)
@@ -299,6 +299,18 @@ struct libwebsocket_context {
        void *user_space;
 };
 
+enum uri_path_states {
+       URIPS_IDLE,
+       URIPS_SEEN_SLASH,
+       URIPS_SEEN_SLASH_DOT,
+       URIPS_SEEN_SLASH_DOT_DOT,
+};
+
+enum uri_esc_states {
+       URIES_IDLE,
+       URIES_SEEN_PERCENT,
+       URIES_SEEN_PERCENT_H1,
+};
 
 /*
  * This is totally opaque to code using the library.  It's exported as a
@@ -335,6 +347,9 @@ struct _lws_header_related {
        struct allocated_headers *ah;
        short lextable_pos;
        unsigned char parser_state; /* enum lws_token_indexes */
+       enum uri_path_states ups;
+       enum uri_esc_states ues;
+       char esc_stash;
 };
 
 struct _lws_websocket_related {
index dacc22f..2c01f53 100644 (file)
@@ -99,18 +99,29 @@ struct serveable {
        const char *mimetype;
 }; 
 
-static const struct serveable whitelist[] = {
-       { "/favicon.ico", "image/x-icon" },
-       { "/libwebsockets.org-logo.png", "image/png" },
-
-       /* last one is the default served if no match */
-       { "/test.html", "text/html" },
-};
-
 struct per_session_data__http {
        int fd;
 };
 
+const char * get_mimetype(const char *file)
+{
+       int n = strlen(file);
+
+       if (n < 5)
+               return NULL;
+
+       if (!strcmp(&file[n - 4], ".ico"))
+               return "image/x-icon";
+
+       if (!strcmp(&file[n - 4], ".png"))
+               return "image/png";
+
+       if (!strcmp(&file[n - 5], ".html"))
+               return "text/html";
+
+       return NULL;
+}
+
 /* this protocol server (always the first one) just knows how to do HTTP */
 
 static int callback_http(struct libwebsocket_context *context,
@@ -133,6 +144,7 @@ static int callback_http(struct libwebsocket_context *context,
        struct stat stat_buf;
        struct per_session_data__http *pss =
                        (struct per_session_data__http *)user;
+       const char *mimetype;
 #ifdef EXTERNAL_POLL
        int fd = (int)(long)in;
 #endif
@@ -197,12 +209,17 @@ static int callback_http(struct libwebsocket_context *context,
                }
 
                /* if not, send a file the easy way */
-
-               for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
-                       if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
-                               break;
-
-               sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath);
+               strcpy(buf, resource_path);
+               if (strcmp(in, "/"))
+                       strncat(buf, in, sizeof(buf) - strlen(resource_path));
+               else /* default file to serve */
+                       strcat(buf, "/test.html");
+               buf[sizeof(buf) - 1] = '\0';
+               mimetype = get_mimetype(buf);
+               if (!mimetype) {
+                       lwsl_err("Unknown mimetype for %s\n", buf);
+                       return -1;
+               }
 
                /* demostrates how to set a cookie on / */
 
@@ -223,7 +240,7 @@ static int callback_http(struct libwebsocket_context *context,
                }
 
                if (libwebsockets_serve_http_file(context, wsi, buf,
-                                       whitelist[n].mimetype, other_headers))
+                                               mimetype, other_headers))
                        return -1; /* through completion or error, close the socket */
 
                /*