Escape URLs according to RFC3986.
authorEmil Mikulic <emikulic@gmail.com>
Tue, 19 May 2015 12:02:12 +0000 (22:02 +1000)
committerEmil Mikulic <emikulic@gmail.com>
Tue, 19 May 2015 12:04:39 +0000 (22:04 +1000)
Previously, we weren't escaping parentheses when generating directory listings.

Pointed out by: Wijatmoko U. Prayitno

darkhttpd.c
devel/test.py

index f932b3f..eb47c54 100644 (file)
@@ -1675,25 +1675,24 @@ static void cleanup_sorted_dirlist(struct dlent **list, const ssize_t size) {
     }
 }
 
-/* Should this character be urlencoded? (according to RFC1738)
- * Contributed by nf.
+/* Is this an unreserved character according to
+ * https://tools.ietf.org/html/rfc3986#section-2.3
  */
-static int needs_urlencoding(const unsigned char c) {
-    unsigned int i;
-    static const char bad[] = "<>\"%{}|^~[]`\\;:/?@#=&";
-
-    /* Non-US-ASCII characters */
-    if ((c <= 0x1F) || (c >= 0x7F))
-        return 1;
-
-    for (i = 0; i < sizeof(bad) - 1; i++)
-        if (c == bad[i])
+static int is_unreserved(const unsigned char c) {
+    if (c >= 'a' && c <= 'z') return 1;
+    if (c >= 'A' && c <= 'Z') return 1;
+    if (c >= '0' && c <= '9') return 1;
+    switch (c) {
+        case '-':
+        case '.':
+        case '_':
+        case '~':
             return 1;
-
+    }
     return 0;
 }
 
-/* Encode string to be an RFC1738-compliant URL part.
+/* Encode string to be an RFC3986-compliant URL part.
  * Contributed by nf.
  */
 static void urlencode(const char *src, char *dest) {
@@ -1701,7 +1700,7 @@ static void urlencode(const char *src, char *dest) {
     int i, j;
 
     for (i = j = 0; src[i] != '\0'; i++) {
-        if (needs_urlencoding((unsigned char)src[i])) {
+        if (!is_unreserved((unsigned char)src[i])) {
             dest[j++] = '%';
             dest[j++] = hex[(src[i] >> 4) & 0xF];
             dest[j++] = hex[ src[i]       & 0xF];
index e11e789..00e47d6 100755 (executable)
@@ -151,7 +151,7 @@ class TestHelper(unittest.TestCase):
 
 class TestDirList(TestHelper):
     def setUp(self):
-        self.fn = WWWROOT+"/escape#this"
+        self.fn = WWWROOT+"/escape(this)name"
         open(self.fn, "w").write("x"*12345)
 
     def tearDown(self):
@@ -161,7 +161,7 @@ class TestDirList(TestHelper):
         resp = self.get("/")
         status, hdrs, body = parse(resp)
         self.assertEquals(ord("#"), 0x23)
-        self.assertContains(body, "escape%23this", "12345")
+        self.assertContains(body, "escape%28this%29name", "12345")
 
 class TestCases(TestHelper):
     pass # these get autogenerated in setUpModule()