From dc9482d7d0fade4c7483f21a028a896ec8624481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 7 Feb 2021 16:18:52 +0100 Subject: [PATCH 1/2] Automatically decode urlencoded (percent-encoded) attributes in URLs --- examples/index.php | 13 ++++++++++--- src/App.php | 2 +- tests/acceptance.sh | 19 ++++++++++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/examples/index.php b/examples/index.php index 424ad4f..d7eb1fd 100644 --- a/examples/index.php +++ b/examples/index.php @@ -18,14 +18,21 @@ }); $app->get('/users/{name}', function (Psr\Http\Message\ServerRequestInterface $request) { + $escape = function (string $str): string { + // replace invalid UTF-8 and control bytes with Unicode replacement character (�) + return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE | ENT_DISALLOWED, 'utf-8')); + }; + return new React\Http\Message\Response( 200, - [], - "Hello " . $request->getAttribute('name') . "!\n" + [ + 'Content-Type' => 'text/plain' + ], + "Hello " . $escape($request->getAttribute('name')) . "!\n" ); }); -$app->get('/uri', function (ServerRequestInterface $request) { +$app->get('/uri[/{path:.*}]', function (ServerRequestInterface $request) { return new React\Http\Message\Response( 200, [ diff --git a/src/App.php b/src/App.php index aefbc3c..71aadb8 100644 --- a/src/App.php +++ b/src/App.php @@ -321,7 +321,7 @@ private function handleRequest(ServerRequestInterface $request, Dispatcher $disp $vars = $routeInfo[2]; foreach ($vars as $key => $value) { - $request = $request->withAttribute($key, $value); + $request = $request->withAttribute($key, rawurldecode($value)); } return $handler($request); diff --git a/tests/acceptance.sh b/tests/acceptance.sh index ba68b14..ad4781e 100755 --- a/tests/acceptance.sh +++ b/tests/acceptance.sh @@ -16,6 +16,15 @@ out=$(curl -v $base// 2>&1); match "HTTP/.* 404" out=$(curl -v $base/ 2>&1 -X POST); match "HTTP/.* 405" out=$(curl -v $base/uri 2>&1); match "HTTP/.* 200" && match "$base/uri" +out=$(curl -v $base/uri/ 2>&1); match "HTTP/.* 200" && match "$base/uri/" +out=$(curl -v $base/uri/foo 2>&1); match "HTTP/.* 200" && match "$base/uri/foo" +out=$(curl -v $base/uri/foo/bar 2>&1); match "HTTP/.* 200" && match "$base/uri/foo/bar" +out=$(curl -v $base/uri/foo//bar 2>&1); match "HTTP/.* 200" && match "$base/uri/foo//bar" +out=$(curl -v $base/uri/Wham! 2>&1); match "HTTP/.* 200" && match "$base/uri/Wham!" +out=$(curl -v $base/uri/Wham%21 2>&1); match "HTTP/.* 200" && match "$base/uri/Wham%21" +echo -n S # out=$(curl -v $base/uri/AC%2FDC 2>&1); match "HTTP/.* 200" && match "$base/uri/AC%2FDC" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) +echo -n S # out=$(curl -v $base/uri/bin%00ary 2>&1); match "HTTP/.* 200" && match "$base/uri/bin%00ary" # skip nginx (400) and Apache (404) +out=$(curl -v $base/uri/AC/DC 2>&1); match "HTTP/.* 200" && match "$base/uri/AC/DC" out=$(curl -v $base/uri? 2>&1); match "HTTP/.* 200" && match "$base/uri" # trailing "?" not reported for empty query string out=$(curl -v $base/uri?query 2>&1); match "HTTP/.* 200" && match "$base/uri?query" out=$(curl -v $base/uri?q=a 2>&1); match "HTTP/.* 200" && match "$base/uri?q=a" @@ -43,7 +52,15 @@ out=$(curl -v $base/query?q=a%00b 2>&1); match "HTTP/.* 200" && m out=$(curl -v $base/query?q=a\&q=b 2>&1); match "HTTP/.* 200" && match "{\"q\":\"b\"}" out=$(curl -v $base/query?q%5B%5D=a\&q%5B%5D=b 2>&1); match "HTTP/.* 200" && match "{\"q\":[[]\"a\",\"b\"[]]}" -out=$(curl -v $base/users/foo 2>&1); match "HTTP/.* 200" && match "Hello foo!" +out=$(curl -v $base/users/foo 2>&1); match "HTTP/.* 200" && match "Hello foo!" +out=$(curl -v $base/users/w%C3%B6rld 2>&1); match "HTTP/.* 200" && match "Hello wörld!" +out=$(curl -v $base/users/w%F6rld 2>&1); match "HTTP/.* 200" && match "Hello w�rld!" # demo expects UTF-8 instead of ISO-8859-1 +out=$(curl -v $base/users/a+b 2>&1); match "HTTP/.* 200" && match "Hello a+b!" +out=$(curl -v $base/users/Wham! 2>&1); match "HTTP/.* 200" && match "Hello Wham!!" +out=$(curl -v $base/users/Wham%21 2>&1); match "HTTP/.* 200" && match "Hello Wham!!" +echo -n S # out=$(curl -v $base/users/AC%2FDC 2>&1); match "HTTP/.* 200" && match "Hello AC/DC!" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) +echo -n S # out=$(curl -v $base/users/bi%00n 2>&1); match "HTTP/.* 200" && match "Hello bi�n!" # skip nginx (400) and Apache (404) + out=$(curl -v $base/users 2>&1); match "HTTP/.* 404" out=$(curl -v $base/users/ 2>&1); match "HTTP/.* 404" out=$(curl -v $base/users/a/b 2>&1); match "HTTP/.* 404" From 2cb0b678c89e7f8b9bc41784bbe142ccb29afa0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 7 Feb 2021 16:53:59 +0100 Subject: [PATCH 2/2] Skip testing unsupported special characters on nginx and Apache --- tests/acceptance.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/acceptance.sh b/tests/acceptance.sh index ad4781e..d658139 100755 --- a/tests/acceptance.sh +++ b/tests/acceptance.sh @@ -9,6 +9,10 @@ match() { (echo ""; echo "Error in test $n: Unable to \"grep $@\" this output:"; echo "$out"; exit 1) || exit 1 } +skipif() { + echo "$out" | grep "$@" >/dev/null && echo -n S && return 1 || return 0 +} + out=$(curl -v $base/ 2>&1); match "HTTP/.* 200" out=$(curl -v $base/test 2>&1); match -i "Location: /" out=$(curl -v $base/invalid 2>&1); match "HTTP/.* 404" @@ -22,8 +26,8 @@ out=$(curl -v $base/uri/foo/bar 2>&1); match "HTTP/.* 200" && m out=$(curl -v $base/uri/foo//bar 2>&1); match "HTTP/.* 200" && match "$base/uri/foo//bar" out=$(curl -v $base/uri/Wham! 2>&1); match "HTTP/.* 200" && match "$base/uri/Wham!" out=$(curl -v $base/uri/Wham%21 2>&1); match "HTTP/.* 200" && match "$base/uri/Wham%21" -echo -n S # out=$(curl -v $base/uri/AC%2FDC 2>&1); match "HTTP/.* 200" && match "$base/uri/AC%2FDC" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) -echo -n S # out=$(curl -v $base/uri/bin%00ary 2>&1); match "HTTP/.* 200" && match "$base/uri/bin%00ary" # skip nginx (400) and Apache (404) +out=$(curl -v $base/uri/AC%2FDC 2>&1); skipif "HTTP/.* 404" && match "HTTP/.* 200" && match "$base/uri/AC%2FDC" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) +out=$(curl -v $base/uri/bin%00ary 2>&1); skipif "HTTP/.* 40[04]" && match "HTTP/.* 200" && match "$base/uri/bin%00ary" # skip nginx (400) and Apache (404) out=$(curl -v $base/uri/AC/DC 2>&1); match "HTTP/.* 200" && match "$base/uri/AC/DC" out=$(curl -v $base/uri? 2>&1); match "HTTP/.* 200" && match "$base/uri" # trailing "?" not reported for empty query string out=$(curl -v $base/uri?query 2>&1); match "HTTP/.* 200" && match "$base/uri?query" @@ -58,8 +62,8 @@ out=$(curl -v $base/users/w%F6rld 2>&1); match "HTTP/.* 200" && m out=$(curl -v $base/users/a+b 2>&1); match "HTTP/.* 200" && match "Hello a+b!" out=$(curl -v $base/users/Wham! 2>&1); match "HTTP/.* 200" && match "Hello Wham!!" out=$(curl -v $base/users/Wham%21 2>&1); match "HTTP/.* 200" && match "Hello Wham!!" -echo -n S # out=$(curl -v $base/users/AC%2FDC 2>&1); match "HTTP/.* 200" && match "Hello AC/DC!" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) -echo -n S # out=$(curl -v $base/users/bi%00n 2>&1); match "HTTP/.* 200" && match "Hello bi�n!" # skip nginx (400) and Apache (404) +out=$(curl -v $base/users/AC%2FDC 2>&1); skipif "HTTP/.* 404" && match "HTTP/.* 200" && match "Hello AC/DC!" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) +out=$(curl -v $base/users/bi%00n 2>&1); skipif "HTTP/.* 40[04]" && match "HTTP/.* 200" && match "Hello bi�n!" # skip nginx (400) and Apache (404) out=$(curl -v $base/users 2>&1); match "HTTP/.* 404" out=$(curl -v $base/users/ 2>&1); match "HTTP/.* 404"