Switch make_safe_url() to more efficient implementation.
authorEmil Mikulic <emikulic@gmail.com>
Sun, 9 Dec 2018 12:18:00 +0000 (23:18 +1100)
committerEmil Mikulic <emikulic@gmail.com>
Sun, 9 Dec 2018 12:44:36 +0000 (23:44 +1100)
Contributed by: Bert Gijsbers

darkhttpd.c

index 951d689..05afe91 100644 (file)
@@ -481,118 +481,62 @@ static char *split_string(const char *src,
     return dest;
 }
 
-/* Consolidate slashes in-place by shifting parts of the string over repeated
- * slashes.
- */
-static void consolidate_slashes(char *s) {
-    size_t left = 0, right = 0;
-    int saw_slash = 0;
-
-    assert(s != NULL);
-    while (s[right] != '\0') {
-        if (saw_slash) {
-            if (s[right] == '/')
-                right++;
-            else {
-                saw_slash = 0;
-                s[left++] = s[right++];
-            }
-        } else {
-            if (s[right] == '/')
-                saw_slash++;
-            s[left++] = s[right++];
-        }
-    }
-    s[left] = '\0';
-}
-
 /* Resolve /./ and /../ in a URL, in-place.  Also strip out query params.
  * Returns NULL if the URL is invalid/unsafe, or the original buffer if
  * successful.
  */
-static char *make_safe_url(char *url) {
-    struct {
-        char *start;
-        size_t len;
-    } *chunks;
-    unsigned int num_slashes, num_chunks;
-    size_t urllen, i, j, pos;
-    int ends_in_slash;
-
-    /* strip query params */
-    for (pos=0; url[pos] != '\0'; pos++) {
-        if (url[pos] == '?') {
-            url[pos] = '\0';
-            break;
-        }
-    }
+static char *make_safe_url(char *const url) {
+    char *src = url, *dst;
+    #define ends(c) ((c) == '/' || (c) == '\0')
 
-    if (url[0] != '/')
+    /* URLs not starting with a slash are illegal. */
+    if (*src != '/')
         return NULL;
 
-    consolidate_slashes(url);
-    urllen = strlen(url);
-    if (urllen > 0)
-        ends_in_slash = (url[urllen-1] == '/');
-    else
-        ends_in_slash = 1;
-
-    /* count the slashes */
-    for (i=0, num_slashes=0; i < urllen; i++)
-        if (url[i] == '/')
-            num_slashes++;
-
-    /* make an array for the URL elements */
-    assert(num_slashes > 0);
-    chunks = xmalloc(sizeof(*chunks) * num_slashes);
-
-    /* split by slashes and build chunks array */
-    num_chunks = 0;
-    for (i=1; i<urllen;) {
-        /* look for the next slash */
-        for (j=i; j<urllen && url[j] != '/'; j++)
-            ;
-
-        /* process url[i,j) */
-        if ((j == i+1) && (url[i] == '.'))
-            /* "." */;
-        else if ((j == i+2) && (url[i] == '.') && (url[i+1] == '.')) {
-            /* ".." */
-            if (num_chunks == 0) {
-                /* unsafe string so free chunks */
-                free(chunks);
-                return (NULL);
-            } else
-                num_chunks--;
-        } else {
-            chunks[num_chunks].start = url+i;
-            chunks[num_chunks].len = j-i;
-            num_chunks++;
+    /* Fast case: skip until first double-slash or dot-dir. */
+    for ( ; *src && *src != '?'; ++src) {
+        if (*src == '/') {
+            if (src[1] == '/')
+                break;
+            else if (src[1] == '.') {
+                if (ends(src[2]))
+                    break;
+                else if (src[2] == '.' && ends(src[3]))
+                    break;
+            }
         }
-
-        i = j + 1; /* url[j] is a slash - move along one */
     }
 
-    /* reassemble in-place */
-    pos = 0;
-    for (i=0; i<num_chunks; i++) {
-        assert(pos <= urllen);
-        url[pos++] = '/';
-
-        assert(pos + chunks[i].len <= urllen);
-        assert(url + pos <= chunks[i].start);
-
-        if (url+pos < chunks[i].start)
-            memmove(url+pos, chunks[i].start, chunks[i].len);
-        pos += chunks[i].len;
+    /* Copy to dst, while collapsing multi-slashes and handling dot-dirs. */
+    dst = src;
+    while (*src && *src != '?') {
+        if (*src != '/')
+            *dst++ = *src++;
+        else if (*++src == '/')
+            ;
+        else if (*src != '.')
+            *dst++ = '/';
+        else if (ends(src[1]))
+            /* Ignore single-dot component. */
+            ++src;
+        else if (src[1] == '.' && ends(src[2])) {
+            /* Double-dot component. */
+            src += 2;
+            if (dst == url)
+                return NULL; /* Illegal URL */
+            else
+                /* Backtrack to previous slash. */
+                while (*--dst != '/' && dst > url);
+        }
+        else
+            *dst++ = '/';
     }
-    free(chunks);
 
-    if ((num_chunks == 0) || ends_in_slash)
-        url[pos++] = '/';
-    assert(pos <= urllen);
-    url[pos] = '\0';
+    if (dst == url)
+        ++dst;
+    *dst = '\0';
     return url;
+    #undef ends
 }
 
 static void add_forward_mapping(const char * const host,