Browse Source

Keep a %u escape that is not a character

decode_path_component dropped a %u escape of a surrogate, joining what
was on either side of it, so /ad%uD800min became /admin after a filter
had seen something else. Keep such an escape as it is.
yhirose 1 day ago
parent
commit
455166314b
2 changed files with 12 additions and 4 deletions
  1. 8 2
      httplib.h
  2. 4 2
      test/test.cc

+ 8 - 2
httplib.h

@@ -11612,8 +11612,14 @@ inline std::string decode_path_component(const std::string &component) {
           // so to_utf8 writes at most 3 bytes. buff[4] is safe.
           // so to_utf8 writes at most 3 bytes. buff[4] is safe.
           char buff[4];
           char buff[4];
           size_t len = detail::to_utf8(val, buff);
           size_t len = detail::to_utf8(val, buff);
-          if (len > 0) { result.append(buff, len); }
-          i += 5; // 'u0000'
+          if (len > 0) {
+            result.append(buff, len);
+            i += 5; // 'u0000'
+          } else {
+            // A surrogate is no character. Dropping it would join what is on
+            // either side of it, so the escape is kept as it is.
+            result += component[i];
+          }
         } else {
         } else {
           result += component[i];
           result += component[i];
         }
         }

+ 4 - 2
test/test.cc

@@ -459,8 +459,10 @@ TEST(DecodePathTest, UnicodeEncoding) {
   EXPECT_EQ(U8("あ"), decode_path_component("%u3042"));
   EXPECT_EQ(U8("あ"), decode_path_component("%u3042"));
   // %uFFFF = max 4-digit hex (3-byte UTF-8, must not overflow buff[4])
   // %uFFFF = max 4-digit hex (3-byte UTF-8, must not overflow buff[4])
   EXPECT_FALSE(decode_path_component("%uFFFF").empty());
   EXPECT_FALSE(decode_path_component("%uFFFF").empty());
-  // %uD800 = surrogate (invalid, silently dropped)
-  EXPECT_EQ("", decode_path_component("%uD800"));
+  // %uD800 = surrogate (invalid, kept as it is rather than joining its
+  // neighbours)
+  EXPECT_EQ("%uD800", decode_path_component("%uD800"));
+  EXPECT_EQ("/ad%uD800min", decode_path_component("/ad%uD800min"));
 }
 }
 
 
 TEST(DecodeQueryTest, RejectsNonHexEscapes) {
 TEST(DecodeQueryTest, RejectsNonHexEscapes) {