From b67896a2d95300e8ffb937f7b67a2c58d355a8c8 Mon Sep 17 00:00:00 2001 From: hippi345 <13539685+hippi345@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:15:19 +0000 Subject: [PATCH 1/4] Fix S3CrtClient to use origin-form request-target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HttpRequest::ToCrtHttpRequest() was constructing an absolute-form request-target (e.g., "PUT http://host/path HTTP/1.1") for every request. This violates RFC 9112 §3.2.1, which requires origin-form (path + query only) when a client connects directly to an origin server. aws-c-http already handles proxy rewriting via aws_http_rewrite_uri_for_proxy_request, converting to absolute-form only when necessary. By passing origin-form here, we prevent security tools like CrowdSec's http-open-proxy scenario from flagging legitimate S3-compatible storage requests as proxy abuse. Fixes #3956 --- ...fix-cursorfix-s3crt-absolute-url-cd0a.json | 6 ++ .../source/http/HttpRequest.cpp | 16 ++--- .../http/HttpClientTest.cpp | 72 +++++++++++++++++++ 3 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 .changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json diff --git a/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json b/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json new file mode 100644 index 000000000000..7864e199b43e --- /dev/null +++ b/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "aws-cpp-sdk-core", + "contributor": "hippi345", + "description": "Fix S3CrtClient to send origin-form request-target (path + query only) instead of absolute-form, preventing false-positive open-proxy detection" +} diff --git a/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp b/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp index 55ca99c64661..512d94bce2e9 100644 --- a/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp +++ b/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp @@ -70,15 +70,6 @@ namespace Aws // CRT sigv4 don't any encoding if double encoding is off, need to encode the path before passing to CRT. const URI& uri = m_uri; Aws::StringStream ss; - Aws::StringStream port; - if (uri.GetScheme() == Scheme::HTTP && uri.GetPort() != HTTP_DEFAULT_PORT) - { - port << ":" << uri.GetPort(); - } - else if (uri.GetScheme() == Scheme::HTTPS && uri.GetPort() != HTTPS_DEFAULT_PORT) - { - port << ":" << uri.GetPort(); - } // Note: GetURLEncodedPathRFC3986 does legacy mode by default, which // leaves some reserved delimeter characters unencoded (things like @@ -88,8 +79,11 @@ namespace Aws // practice however CRT will never support legacy mode and will // always need modern encoding, so be explicit here about which mode // we use. - ss << SchemeMapper::ToString(uri.GetScheme()) << SEPARATOR << uri.GetAuthority() << port.str() - << ((uri.GetPath() == "/") ? "" : uri.GetURLEncodedPath()) + + // Set path to origin-form (path + query only) per RFC 9112 §3.2.1. + // aws-c-http converts to absolute-form only when sending through a + // forward proxy via aws_http_rewrite_uri_for_proxy_request. + ss << (uri.GetPath().empty() ? "/" : uri.GetURLEncodedPath()) << uri.GetQueryString(); request->SetPath(Aws::Crt::ByteCursorFromCString(ss.str().c_str())); diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp index 822df5e5859a..28ffd0a552b6 100644 --- a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -385,4 +386,75 @@ TEST_F(CURLHttpClientTest, TestHttpRequestWorksFine) #endif // ENABLE_CURL_CLIENT #endif // ENABLE_HTTP_CLIENT_TESTING #endif // NO_HTTP_CLIENT + +// Test that ToCrtHttpRequest produces origin-form request-target (path + query only) +TEST_F(HttpClientTest, TestToCrtHttpRequestOriginForm) +{ + // Test with HTTP scheme and non-default port + { + Aws::Http::URI uri("http://example.com:9000/bucket/key?uploadId=abc&partNumber=1"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_PUT); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + // The path should be origin-form: /path?query + // NOT absolute-form: http://example.com:9000/path?query + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + EXPECT_EQ("/bucket/key?uploadId=abc&partNumber=1", path); + EXPECT_FALSE(path.find("http://") == 0); + EXPECT_FALSE(path.find("https://") == 0); + } + + // Test with HTTPS scheme and default port + { + Aws::Http::URI uri("https://s3.amazonaws.com/my-bucket/my-key"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_GET); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + EXPECT_EQ("/my-bucket/my-key", path); + EXPECT_FALSE(path.find("http://") == 0); + EXPECT_FALSE(path.find("https://") == 0); + } + + // Test with query string + { + Aws::Http::URI uri("https://storage.example.com:443/path/to/file?query=value&foo=bar"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_POST); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + EXPECT_EQ("/path/to/file?query=value&foo=bar", path); + EXPECT_FALSE(path.find("http://") == 0); + EXPECT_FALSE(path.find("https://") == 0); + } + + // Test with root path + { + Aws::Http::URI uri("http://localhost:8080/"); + Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_HEAD); + + auto crtRequest = request.ToCrtHttpRequest(); + ASSERT_NE(nullptr, crtRequest); + + auto pathOpt = crtRequest->GetPath(); + ASSERT_TRUE(pathOpt.has_value()); + Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); + // Root path should be "/" + EXPECT_EQ("/", path); + EXPECT_FALSE(path.find("http://") == 0); + } +} + #endif // DISABLE_DNS_REQUIRED_TESTS From 0ea540cdafc80621b9466fbf50b51e58f1ff2b96 Mon Sep 17 00:00:00 2001 From: hippi345 <13539685+hippi345@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:37:50 +0000 Subject: [PATCH 2/4] Review fixes: remove changelog, move test outside DNS guard - Remove .changelog file (upstream has no such directory) - Move ToCrtHttpRequestOriginForm test outside DISABLE_DNS_REQUIRED_TESTS guard as it requires no network access --- .../bugfix-cursorfix-s3crt-absolute-url-cd0a.json | 6 ------ .../http/HttpClientTest.cpp | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) delete mode 100644 .changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json diff --git a/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json b/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json deleted file mode 100644 index 7864e199b43e..000000000000 --- a/.changelog/bugfix-cursorfix-s3crt-absolute-url-cd0a.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "bugfix", - "category": "aws-cpp-sdk-core", - "contributor": "hippi345", - "description": "Fix S3CrtClient to send origin-form request-target (path + query only) instead of absolute-form, preventing false-positive open-proxy detection" -} diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp index 28ffd0a552b6..e9b558a23fb8 100644 --- a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp @@ -387,8 +387,19 @@ TEST_F(CURLHttpClientTest, TestHttpRequestWorksFine) #endif // ENABLE_HTTP_CLIENT_TESTING #endif // NO_HTTP_CLIENT +#endif // DISABLE_DNS_REQUIRED_TESTS + +// Test ToCrtHttpRequest conversion (no network required) +#include +#include +#include + +class HttpRequestConversionTest : public Aws::Testing::AwsCppSdkGTestSuite +{ +}; + // Test that ToCrtHttpRequest produces origin-form request-target (path + query only) -TEST_F(HttpClientTest, TestToCrtHttpRequestOriginForm) +TEST_F(HttpRequestConversionTest, ToCrtHttpRequestOriginForm) { // Test with HTTP scheme and non-default port { @@ -456,5 +467,3 @@ TEST_F(HttpClientTest, TestToCrtHttpRequestOriginForm) EXPECT_FALSE(path.find("http://") == 0); } } - -#endif // DISABLE_DNS_REQUIRED_TESTS From dbdf53656d2d1140eb265eb4bbd702c87621723d Mon Sep 17 00:00:00 2001 From: hippi345 <13539685+hippi345@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:19:40 +0000 Subject: [PATCH 3/4] Address review on origin-form request paths Use GetURLEncodedPath() plus the query string. That path always starts with '/', so the empty-path fallback was unnecessary. Move the ToCrtHttpRequest cases into http/HttpRequestTest.cpp and give each one its own test, including root-plus-query, a trailing slash, reserved characters, and a preserved leading slash. --- .../source/http/HttpRequest.cpp | 12 +- .../http/HttpClientTest.cpp | 79 -------------- .../http/HttpRequestTest.cpp | 103 ++++++++++++++++++ 3 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp diff --git a/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp b/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp index 512d94bce2e9..23ddce0aa242 100644 --- a/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp +++ b/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp @@ -79,12 +79,12 @@ namespace Aws // practice however CRT will never support legacy mode and will // always need modern encoding, so be explicit here about which mode // we use. - - // Set path to origin-form (path + query only) per RFC 9112 §3.2.1. - // aws-c-http converts to absolute-form only when sending through a - // forward proxy via aws_http_rewrite_uri_for_proxy_request. - ss << (uri.GetPath().empty() ? "/" : uri.GetURLEncodedPath()) - << uri.GetQueryString(); + // + // Origin-form request-target (path + query) per RFC 9112 §3.2.1. + // GetURLEncodedPath() always begins with '/', including the root + // path. aws-c-http rewrites this to absolute-form only when the + // request goes through a forward proxy. + ss << uri.GetURLEncodedPath() << uri.GetQueryString(); request->SetPath(Aws::Crt::ByteCursorFromCString(ss.str().c_str())); const char *method = HttpMethodMapper::GetNameForHttpMethod(m_method); diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp index e9b558a23fb8..1b47fd334a2a 100644 --- a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp @@ -388,82 +388,3 @@ TEST_F(CURLHttpClientTest, TestHttpRequestWorksFine) #endif // NO_HTTP_CLIENT #endif // DISABLE_DNS_REQUIRED_TESTS - -// Test ToCrtHttpRequest conversion (no network required) -#include -#include -#include - -class HttpRequestConversionTest : public Aws::Testing::AwsCppSdkGTestSuite -{ -}; - -// Test that ToCrtHttpRequest produces origin-form request-target (path + query only) -TEST_F(HttpRequestConversionTest, ToCrtHttpRequestOriginForm) -{ - // Test with HTTP scheme and non-default port - { - Aws::Http::URI uri("http://example.com:9000/bucket/key?uploadId=abc&partNumber=1"); - Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_PUT); - - auto crtRequest = request.ToCrtHttpRequest(); - ASSERT_NE(nullptr, crtRequest); - - // The path should be origin-form: /path?query - // NOT absolute-form: http://example.com:9000/path?query - auto pathOpt = crtRequest->GetPath(); - ASSERT_TRUE(pathOpt.has_value()); - Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); - EXPECT_EQ("/bucket/key?uploadId=abc&partNumber=1", path); - EXPECT_FALSE(path.find("http://") == 0); - EXPECT_FALSE(path.find("https://") == 0); - } - - // Test with HTTPS scheme and default port - { - Aws::Http::URI uri("https://s3.amazonaws.com/my-bucket/my-key"); - Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_GET); - - auto crtRequest = request.ToCrtHttpRequest(); - ASSERT_NE(nullptr, crtRequest); - - auto pathOpt = crtRequest->GetPath(); - ASSERT_TRUE(pathOpt.has_value()); - Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); - EXPECT_EQ("/my-bucket/my-key", path); - EXPECT_FALSE(path.find("http://") == 0); - EXPECT_FALSE(path.find("https://") == 0); - } - - // Test with query string - { - Aws::Http::URI uri("https://storage.example.com:443/path/to/file?query=value&foo=bar"); - Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_POST); - - auto crtRequest = request.ToCrtHttpRequest(); - ASSERT_NE(nullptr, crtRequest); - - auto pathOpt = crtRequest->GetPath(); - ASSERT_TRUE(pathOpt.has_value()); - Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); - EXPECT_EQ("/path/to/file?query=value&foo=bar", path); - EXPECT_FALSE(path.find("http://") == 0); - EXPECT_FALSE(path.find("https://") == 0); - } - - // Test with root path - { - Aws::Http::URI uri("http://localhost:8080/"); - Aws::Http::Standard::StandardHttpRequest request(uri, Aws::Http::HttpMethod::HTTP_HEAD); - - auto crtRequest = request.ToCrtHttpRequest(); - ASSERT_NE(nullptr, crtRequest); - - auto pathOpt = crtRequest->GetPath(); - ASSERT_TRUE(pathOpt.has_value()); - Aws::String path(reinterpret_cast(pathOpt->ptr), pathOpt->len); - // Root path should be "/" - EXPECT_EQ("/", path); - EXPECT_FALSE(path.find("http://") == 0); - } -} diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp new file mode 100644 index 000000000000..f37b5b9b8c8f --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp @@ -0,0 +1,103 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include + +using namespace Aws::Http; +using namespace Aws::Http::Standard; + +namespace +{ + // Resets the process-global path-separator flag when the test leaves, including on assertion failure. + class PreservePathSeparatorsGuard + { + public: + explicit PreservePathSeparatorsGuard(bool preserve) + { + SetPreservePathSeparators(preserve); + } + + ~PreservePathSeparatorsGuard() + { + SetPreservePathSeparators(false); + } + + PreservePathSeparatorsGuard(const PreservePathSeparatorsGuard&) = delete; + PreservePathSeparatorsGuard& operator=(const PreservePathSeparatorsGuard&) = delete; + }; + + Aws::String CrtRequestPath(const char* uri, HttpMethod method) + { + StandardHttpRequest request(URI(uri), method); + auto crtRequest = request.ToCrtHttpRequest(); + if (!crtRequest) + { + ADD_FAILURE() << "ToCrtHttpRequest returned null for " << uri; + return {}; + } + + auto path = crtRequest->GetPath(); + if (!path.has_value()) + { + ADD_FAILURE() << "CRT request has no path for " << uri; + return {}; + } + + return Aws::String(reinterpret_cast(path->ptr), path->len); + } +} + +class HttpRequestConversionTest : public Aws::Testing::AwsCppSdkGTestSuite +{ +}; + +TEST_F(HttpRequestConversionTest, OriginFormNonDefaultPort) +{ + EXPECT_EQ("/bucket/key?uploadId=abc&partNumber=1", + CrtRequestPath("http://example.com:9000/bucket/key?uploadId=abc&partNumber=1", HttpMethod::HTTP_PUT)); +} + +TEST_F(HttpRequestConversionTest, OriginFormHttpsPath) +{ + EXPECT_EQ("/my-bucket/my-key", + CrtRequestPath("https://s3.amazonaws.com/my-bucket/my-key", HttpMethod::HTTP_GET)); +} + +TEST_F(HttpRequestConversionTest, OriginFormQueryString) +{ + EXPECT_EQ("/path/to/file?query=value&foo=bar", + CrtRequestPath("https://storage.example.com:443/path/to/file?query=value&foo=bar", HttpMethod::HTTP_POST)); +} + +TEST_F(HttpRequestConversionTest, OriginFormRootPath) +{ + EXPECT_EQ("/", CrtRequestPath("http://localhost:8080/", HttpMethod::HTTP_HEAD)); +} + +// Old absolute-form code dropped a root path and emitted "https://host?list-type=2". +TEST_F(HttpRequestConversionTest, OriginFormRootPathWithQuery) +{ + EXPECT_EQ("/?list-type=2", CrtRequestPath("https://host/?list-type=2", HttpMethod::HTTP_GET)); +} + +TEST_F(HttpRequestConversionTest, OriginFormTrailingSlash) +{ + EXPECT_EQ("/bucket/prefix/", CrtRequestPath("https://host/bucket/prefix/", HttpMethod::HTTP_GET)); +} + +TEST_F(HttpRequestConversionTest, OriginFormEncodesReservedCharacters) +{ + EXPECT_EQ("/bucket/a%3Ab%20c%2Bd", CrtRequestPath("https://host/bucket/a:b c+d", HttpMethod::HTTP_PUT)); +} + +TEST_F(HttpRequestConversionTest, OriginFormPreservesLeadingSlashInKey) +{ + PreservePathSeparatorsGuard guard(true); + (void)guard; + EXPECT_EQ("//key", CrtRequestPath("https://host//key", HttpMethod::HTTP_GET)); +} From ac9772227bbdf79defd8620b359d2f4527e85561 Mon Sep 17 00:00:00 2001 From: hippi345 <13539685+hippi345@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:21:55 +0000 Subject: [PATCH 4/4] Format the new HttpRequest tests Put the cases in the repo clang-format style and leave HttpClientTest.cpp unchanged from main. --- .../http/HttpClientTest.cpp | 2 - .../http/HttpRequestTest.cpp | 120 +++++++----------- 2 files changed, 48 insertions(+), 74 deletions(-) diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp index 1b47fd334a2a..822df5e5859a 100644 --- a/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/http/HttpClientTest.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -386,5 +385,4 @@ TEST_F(CURLHttpClientTest, TestHttpRequestWorksFine) #endif // ENABLE_CURL_CLIENT #endif // ENABLE_HTTP_CLIENT_TESTING #endif // NO_HTTP_CLIENT - #endif // DISABLE_DNS_REQUIRED_TESTS diff --git a/tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp b/tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp index f37b5b9b8c8f..f2ea9db654ea 100644 --- a/tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/http/HttpRequestTest.cpp @@ -3,101 +3,77 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include #include #include #include +#include using namespace Aws::Http; using namespace Aws::Http::Standard; -namespace -{ - // Resets the process-global path-separator flag when the test leaves, including on assertion failure. - class PreservePathSeparatorsGuard - { - public: - explicit PreservePathSeparatorsGuard(bool preserve) - { - SetPreservePathSeparators(preserve); - } - - ~PreservePathSeparatorsGuard() - { - SetPreservePathSeparators(false); - } - - PreservePathSeparatorsGuard(const PreservePathSeparatorsGuard&) = delete; - PreservePathSeparatorsGuard& operator=(const PreservePathSeparatorsGuard&) = delete; - }; - - Aws::String CrtRequestPath(const char* uri, HttpMethod method) - { - StandardHttpRequest request(URI(uri), method); - auto crtRequest = request.ToCrtHttpRequest(); - if (!crtRequest) - { - ADD_FAILURE() << "ToCrtHttpRequest returned null for " << uri; - return {}; - } - - auto path = crtRequest->GetPath(); - if (!path.has_value()) - { - ADD_FAILURE() << "CRT request has no path for " << uri; - return {}; - } - - return Aws::String(reinterpret_cast(path->ptr), path->len); - } -} +namespace { +// Resets the process-global path-separator flag when the test leaves, including on assertion failure. +class PreservePathSeparatorsGuard { + public: + explicit PreservePathSeparatorsGuard(bool preserve) { SetPreservePathSeparators(preserve); } -class HttpRequestConversionTest : public Aws::Testing::AwsCppSdkGTestSuite -{ + ~PreservePathSeparatorsGuard() { SetPreservePathSeparators(false); } + + PreservePathSeparatorsGuard(const PreservePathSeparatorsGuard&) = delete; + PreservePathSeparatorsGuard& operator=(const PreservePathSeparatorsGuard&) = delete; }; -TEST_F(HttpRequestConversionTest, OriginFormNonDefaultPort) -{ - EXPECT_EQ("/bucket/key?uploadId=abc&partNumber=1", - CrtRequestPath("http://example.com:9000/bucket/key?uploadId=abc&partNumber=1", HttpMethod::HTTP_PUT)); +Aws::String CrtRequestPath(const char* uri, HttpMethod method) { + StandardHttpRequest request(URI(uri), method); + auto crtRequest = request.ToCrtHttpRequest(); + if (!crtRequest) { + ADD_FAILURE() << "ToCrtHttpRequest returned null for " << uri; + return {}; + } + + auto path = crtRequest->GetPath(); + if (!path.has_value()) { + ADD_FAILURE() << "CRT request has no path for " << uri; + return {}; + } + + return Aws::String(reinterpret_cast(path->ptr), path->len); } +} // namespace + +class HttpRequestConversionTest : public Aws::Testing::AwsCppSdkGTestSuite {}; -TEST_F(HttpRequestConversionTest, OriginFormHttpsPath) -{ - EXPECT_EQ("/my-bucket/my-key", - CrtRequestPath("https://s3.amazonaws.com/my-bucket/my-key", HttpMethod::HTTP_GET)); +TEST_F(HttpRequestConversionTest, OriginFormNonDefaultPort) { + EXPECT_EQ("/bucket/key?uploadId=abc&partNumber=1", + CrtRequestPath("http://example.com:9000/bucket/key?uploadId=abc&partNumber=1", HttpMethod::HTTP_PUT)); } -TEST_F(HttpRequestConversionTest, OriginFormQueryString) -{ - EXPECT_EQ("/path/to/file?query=value&foo=bar", - CrtRequestPath("https://storage.example.com:443/path/to/file?query=value&foo=bar", HttpMethod::HTTP_POST)); +TEST_F(HttpRequestConversionTest, OriginFormHttpsPath) { + EXPECT_EQ("/my-bucket/my-key", CrtRequestPath("https://s3.amazonaws.com/my-bucket/my-key", HttpMethod::HTTP_GET)); } -TEST_F(HttpRequestConversionTest, OriginFormRootPath) -{ - EXPECT_EQ("/", CrtRequestPath("http://localhost:8080/", HttpMethod::HTTP_HEAD)); +TEST_F(HttpRequestConversionTest, OriginFormQueryString) { + EXPECT_EQ("/path/to/file?query=value&foo=bar", + CrtRequestPath("https://storage.example.com:443/path/to/file?query=value&foo=bar", HttpMethod::HTTP_POST)); } +TEST_F(HttpRequestConversionTest, OriginFormRootPath) { EXPECT_EQ("/", CrtRequestPath("http://localhost:8080/", HttpMethod::HTTP_HEAD)); } + // Old absolute-form code dropped a root path and emitted "https://host?list-type=2". -TEST_F(HttpRequestConversionTest, OriginFormRootPathWithQuery) -{ - EXPECT_EQ("/?list-type=2", CrtRequestPath("https://host/?list-type=2", HttpMethod::HTTP_GET)); +TEST_F(HttpRequestConversionTest, OriginFormRootPathWithQuery) { + EXPECT_EQ("/?list-type=2", CrtRequestPath("https://host/?list-type=2", HttpMethod::HTTP_GET)); } -TEST_F(HttpRequestConversionTest, OriginFormTrailingSlash) -{ - EXPECT_EQ("/bucket/prefix/", CrtRequestPath("https://host/bucket/prefix/", HttpMethod::HTTP_GET)); +TEST_F(HttpRequestConversionTest, OriginFormTrailingSlash) { + EXPECT_EQ("/bucket/prefix/", CrtRequestPath("https://host/bucket/prefix/", HttpMethod::HTTP_GET)); } -TEST_F(HttpRequestConversionTest, OriginFormEncodesReservedCharacters) -{ - EXPECT_EQ("/bucket/a%3Ab%20c%2Bd", CrtRequestPath("https://host/bucket/a:b c+d", HttpMethod::HTTP_PUT)); +TEST_F(HttpRequestConversionTest, OriginFormEncodesReservedCharacters) { + EXPECT_EQ("/bucket/a%3Ab%20c%2Bd", CrtRequestPath("https://host/bucket/a:b c+d", HttpMethod::HTTP_PUT)); } -TEST_F(HttpRequestConversionTest, OriginFormPreservesLeadingSlashInKey) -{ - PreservePathSeparatorsGuard guard(true); - (void)guard; - EXPECT_EQ("//key", CrtRequestPath("https://host//key", HttpMethod::HTTP_GET)); +TEST_F(HttpRequestConversionTest, OriginFormPreservesLeadingSlashInKey) { + PreservePathSeparatorsGuard guard(true); + (void)guard; + EXPECT_EQ("//key", CrtRequestPath("https://host//key", HttpMethod::HTTP_GET)); }