| #include "quiche/binary_http/binary_http_message.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| |
| using ::testing::ContainerEq; |
| using ::testing::FieldsAre; |
| using ::testing::StrEq; |
| |
| namespace quiche { |
| namespace { |
| |
| std::string WordToBytes(uint32_t word) { |
| return std::string({static_cast<char>(word >> 24), |
| static_cast<char>(word >> 16), |
| static_cast<char>(word >> 8), static_cast<char>(word)}); |
| } |
| |
| template <class T> |
| void TestPrintTo(const T& resp) { |
| std::ostringstream os; |
| PrintTo(resp, &os); |
| EXPECT_EQ(os.str(), resp.DebugString()); |
| } |
| } // namespace |
| // Test examples from |
| // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html |
| |
| TEST(BinaryHttpRequest, EncodeGetNoBody) { |
| /* |
| GET /hello.txt HTTP/1.1 |
| User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 |
| Host: www.example.com |
| Accept-Language: en, mi |
| */ |
| BinaryHttpRequest request({"GET", "https", "www.example.com", "/hello.txt"}); |
| request |
| .AddHeaderField({"User-Agent", |
| "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"}) |
| ->AddHeaderField({"Host", "www.example.com"}) |
| ->AddHeaderField({"Accept-Language", "en, mi"}); |
| /* |
| 00000000: 00034745 54056874 74707300 0a2f6865 ..GET.https../he |
| 00000010: 6c6c6f2e 74787440 6c0a7573 65722d61 llo.txt@l.user-a |
| 00000020: 67656e74 34637572 6c2f372e 31362e33 gent4curl/7.16.3 |
| 00000030: 206c6962 6375726c 2f372e31 362e3320 libcurl/7.16.3 |
| 00000040: 4f70656e 53534c2f 302e392e 376c207a OpenSSL/0.9.7l z |
| 00000050: 6c69622f 312e322e 3304686f 73740f77 lib/1.2.3.host.w |
| 00000060: 77772e65 78616d70 6c652e63 6f6d0f61 ww.example.com.a |
| 00000070: 63636570 742d6c61 6e677561 67650665 ccept-language.e |
| 00000080: 6e2c206d 6900 n, mi.. |
| */ |
| const uint32_t expected_words[] = { |
| 0x00034745, 0x54056874, 0x74707300, 0x0a2f6865, 0x6c6c6f2e, 0x74787440, |
| 0x6c0a7573, 0x65722d61, 0x67656e74, 0x34637572, 0x6c2f372e, 0x31362e33, |
| 0x206c6962, 0x6375726c, 0x2f372e31, 0x362e3320, 0x4f70656e, 0x53534c2f, |
| 0x302e392e, 0x376c207a, 0x6c69622f, 0x312e322e, 0x3304686f, 0x73740f77, |
| 0x77772e65, 0x78616d70, 0x6c652e63, 0x6f6d0f61, 0x63636570, 0x742d6c61, |
| 0x6e677561, 0x67650665, 0x6e2c206d, 0x69000000}; |
| std::string expected; |
| for (const auto& word : expected_words) { |
| expected += WordToBytes(word); |
| } |
| // Remove padding. |
| expected.resize(expected.size() - 2); |
| |
| const auto result = request.Serialize(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(*result, expected); |
| EXPECT_THAT( |
| request.DebugString(), |
| StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{Field{user-agent=curl/" |
| "7.16.3 " |
| "libcurl/7.16.3 OpenSSL/0.9.7l " |
| "zlib/1.2.3};Field{host=www.example.com};Field{accept-language=en, " |
| "mi}}Body{}}}")); |
| TestPrintTo(request); |
| } |
| |
| TEST(BinaryHttpRequest, DecodeGetNoBody) { |
| const uint32_t words[] = { |
| 0x00034745, 0x54056874, 0x74707300, 0x0a2f6865, 0x6c6c6f2e, 0x74787440, |
| 0x6c0a7573, 0x65722d61, 0x67656e74, 0x34637572, 0x6c2f372e, 0x31362e33, |
| 0x206c6962, 0x6375726c, 0x2f372e31, 0x362e3320, 0x4f70656e, 0x53534c2f, |
| 0x302e392e, 0x376c207a, 0x6c69622f, 0x312e322e, 0x3304686f, 0x73740f77, |
| 0x77772e65, 0x78616d70, 0x6c652e63, 0x6f6d0f61, 0x63636570, 0x742d6c61, |
| 0x6e677561, 0x67650665, 0x6e2c206d, 0x69000000}; |
| std::string data; |
| for (const auto& word : words) { |
| data += WordToBytes(word); |
| } |
| |
| // Remove all padding |
| data.resize(data.size() - 3); |
| |
| const auto request_so = BinaryHttpRequest::Create(data); |
| ASSERT_TRUE(request_so.ok()); |
| const BinaryHttpRequest request = *request_so; |
| ASSERT_THAT(request.control_data(), |
| FieldsAre("GET", "https", "", "/hello.txt")); |
| std::vector<BinaryHttpMessage::Field> expected_fields = { |
| {"user-agent", "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"}, |
| {"host", "www.example.com"}, |
| {"accept-language", "en, mi"}}; |
| for (const auto& field : expected_fields) { |
| TestPrintTo(field); |
| } |
| ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields)); |
| ASSERT_EQ(request.body(), ""); |
| EXPECT_THAT( |
| request.DebugString(), |
| StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{Field{user-agent=curl/" |
| "7.16.3 " |
| "libcurl/7.16.3 OpenSSL/0.9.7l " |
| "zlib/1.2.3};Field{host=www.example.com};Field{accept-language=en, " |
| "mi}}Body{}}}")); |
| TestPrintTo(request); |
| } |
| |
| TEST(BinaryHttpRequest, EncodeGetWithAuthority) { |
| /* |
| GET https://www.example.com/hello.txt HTTP/1.1 |
| User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 |
| Accept-Language: en, mi |
| */ |
| BinaryHttpRequest request({"GET", "https", "www.example.com", "/hello.txt"}); |
| request |
| .AddHeaderField({"User-Agent", |
| "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"}) |
| ->AddHeaderField({"Accept-Language", "en, mi"}); |
| /* |
| 00000000: 00034745 54056874 7470730f 7777772e ..GET.https.www. |
| 00000010: 6578616d 706c652e 636f6d0a 2f68656c example.com./hel |
| 00000020: 6c6f2e74 78744057 0a757365 722d6167 lo.txt@W.user-ag |
| 00000030: 656e7434 6375726c 2f372e31 362e3320 ent4curl/7.16.3 |
| 00000040: 6c696263 75726c2f 372e3136 2e33204f libcurl/7.16.3 O |
| 00000050: 70656e53 534c2f30 2e392e37 6c207a6c penSSL/0.9.7l zl |
| 00000060: 69622f31 2e322e33 0f616363 6570742d ib/1.2.3.accept- |
| 00000070: 6c616e67 75616765 06656e2c 206d6900 language.en, mi. |
| */ |
| |
| const uint32_t expected_words[] = { |
| 0x00034745, 0x54056874, 0x7470730f, 0x7777772e, 0x6578616d, 0x706c652e, |
| 0x636f6d0a, 0x2f68656c, 0x6c6f2e74, 0x78744057, 0x0a757365, 0x722d6167, |
| 0x656e7434, 0x6375726c, 0x2f372e31, 0x362e3320, 0x6c696263, 0x75726c2f, |
| 0x372e3136, 0x2e33204f, 0x70656e53, 0x534c2f30, 0x2e392e37, 0x6c207a6c, |
| 0x69622f31, 0x2e322e33, 0x0f616363, 0x6570742d, 0x6c616e67, 0x75616765, |
| 0x06656e2c, 0x206d6900}; |
| std::string expected; |
| for (const auto& word : expected_words) { |
| expected += WordToBytes(word); |
| } |
| const auto result = request.Serialize(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(*result, expected); |
| EXPECT_THAT( |
| request.DebugString(), |
| StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{Field{user-agent=curl/" |
| "7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l " |
| "zlib/1.2.3};Field{accept-language=en, mi}}Body{}}}")); |
| } |
| |
| TEST(BinaryHttpRequest, DecodeGetWithAuthority) { |
| const uint32_t words[] = { |
| 0x00034745, 0x54056874, 0x7470730f, 0x7777772e, 0x6578616d, 0x706c652e, |
| 0x636f6d0a, 0x2f68656c, 0x6c6f2e74, 0x78744057, 0x0a757365, 0x722d6167, |
| 0x656e7434, 0x6375726c, 0x2f372e31, 0x362e3320, 0x6c696263, 0x75726c2f, |
| 0x372e3136, 0x2e33204f, 0x70656e53, 0x534c2f30, 0x2e392e37, 0x6c207a6c, |
| 0x69622f31, 0x2e322e33, 0x0f616363, 0x6570742d, 0x6c616e67, 0x75616765, |
| 0x06656e2c, 0x206d6900, 0x00}; |
| std::string data; |
| for (const auto& word : words) { |
| data += WordToBytes(word); |
| } |
| const auto request_so = BinaryHttpRequest::Create(data); |
| ASSERT_TRUE(request_so.ok()); |
| const BinaryHttpRequest request = *request_so; |
| ASSERT_THAT(request.control_data(), |
| FieldsAre("GET", "https", "www.example.com", "/hello.txt")); |
| std::vector<BinaryHttpMessage::Field> expected_fields = { |
| {"user-agent", "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"}, |
| {"accept-language", "en, mi"}}; |
| ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields)); |
| ASSERT_EQ(request.body(), ""); |
| EXPECT_THAT( |
| request.DebugString(), |
| StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{Field{user-agent=curl/" |
| "7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l " |
| "zlib/1.2.3};Field{accept-language=en, mi}}Body{}}}")); |
| } |
| |
| TEST(BinaryHttpRequest, EncodePostBody) { |
| /* |
| POST /hello.txt HTTP/1.1 |
| User-Agent: not/telling |
| Host: www.example.com |
| Accept-Language: en |
| |
| Some body that I used to post. |
| */ |
| BinaryHttpRequest request({"POST", "https", "www.example.com", "/hello.txt"}); |
| request.AddHeaderField({"User-Agent", "not/telling"}) |
| ->AddHeaderField({"Host", "www.example.com"}) |
| ->AddHeaderField({"Accept-Language", "en"}) |
| ->set_body({"Some body that I used to post.\r\n"}); |
| /* |
| 00000000: 0004504f 53540568 74747073 000a2f68 ..POST.https../h |
| 00000010: 656c6c6f 2e747874 3f0a7573 65722d61 ello.txt?.user-a |
| 00000020: 67656e74 0b6e6f74 2f74656c 6c696e67 gent.not/telling |
| 00000030: 04686f73 740f7777 772e6578 616d706c .host.www.exampl |
| 00000040: 652e636f 6d0f6163 63657074 2d6c616e e.com.accept-lan |
| 00000050: 67756167 6502656e 20536f6d 6520626f guage.en Some bo |
| 00000060: 64792074 68617420 49207573 65642074 dy that I used t |
| 00000070: 6f20706f 73742e0d 0a o post.... |
| */ |
| const uint32_t expected_words[] = { |
| 0x0004504f, 0x53540568, 0x74747073, 0x000a2f68, 0x656c6c6f, 0x2e747874, |
| 0x3f0a7573, 0x65722d61, 0x67656e74, 0x0b6e6f74, 0x2f74656c, 0x6c696e67, |
| 0x04686f73, 0x740f7777, 0x772e6578, 0x616d706c, 0x652e636f, 0x6d0f6163, |
| 0x63657074, 0x2d6c616e, 0x67756167, 0x6502656e, 0x20536f6d, 0x6520626f, |
| 0x64792074, 0x68617420, 0x49207573, 0x65642074, 0x6f20706f, 0x73742e0d, |
| 0x0a000000}; |
| std::string expected; |
| for (const auto& word : expected_words) { |
| expected += WordToBytes(word); |
| } |
| // Remove padding. |
| expected.resize(expected.size() - 3); |
| const auto result = request.Serialize(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(*result, expected); |
| EXPECT_THAT( |
| request.DebugString(), |
| StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{Field{user-agent=not/" |
| "telling};Field{host=www.example.com};Field{accept-language=en}}" |
| "Body{Some " |
| "body that I used to post.\r\n}}}")); |
| } |
| |
| TEST(BinaryHttpRequest, DecodePostBody) { |
| const uint32_t words[] = { |
| 0x0004504f, 0x53540568, 0x74747073, 0x000a2f68, 0x656c6c6f, 0x2e747874, |
| 0x3f0a7573, 0x65722d61, 0x67656e74, 0x0b6e6f74, 0x2f74656c, 0x6c696e67, |
| 0x04686f73, 0x740f7777, 0x772e6578, 0x616d706c, 0x652e636f, 0x6d0f6163, |
| 0x63657074, 0x2d6c616e, 0x67756167, 0x6502656e, 0x20536f6d, 0x6520626f, |
| 0x64792074, 0x68617420, 0x49207573, 0x65642074, 0x6f20706f, 0x73742e0d, |
| 0x0a000000}; |
| std::string data; |
| for (const auto& word : words) { |
| data += WordToBytes(word); |
| } |
| const auto request_so = BinaryHttpRequest::Create(data); |
| ASSERT_TRUE(request_so.ok()); |
| BinaryHttpRequest request = *request_so; |
| ASSERT_THAT(request.control_data(), |
| FieldsAre("POST", "https", "", "/hello.txt")); |
| std::vector<BinaryHttpMessage::Field> expected_fields = { |
| {"user-agent", "not/telling"}, |
| {"host", "www.example.com"}, |
| {"accept-language", "en"}}; |
| ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields)); |
| ASSERT_EQ(request.body(), "Some body that I used to post.\r\n"); |
| EXPECT_THAT( |
| request.DebugString(), |
| StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{Field{user-agent=not/" |
| "telling};Field{host=www.example.com};Field{accept-language=en}}" |
| "Body{Some " |
| "body that I used to post.\r\n}}}")); |
| } |
| |
| TEST(BinaryHttpRequest, Equality) { |
| BinaryHttpRequest request({"POST", "https", "www.example.com", "/hello.txt"}); |
| request.AddHeaderField({"User-Agent", "not/telling"}) |
| ->set_body({"hello, world!\r\n"}); |
| |
| BinaryHttpRequest same({"POST", "https", "www.example.com", "/hello.txt"}); |
| same.AddHeaderField({"User-Agent", "not/telling"}) |
| ->set_body({"hello, world!\r\n"}); |
| EXPECT_EQ(request, same); |
| } |
| |
| TEST(BinaryHttpRequest, Inequality) { |
| BinaryHttpRequest request({"POST", "https", "www.example.com", "/hello.txt"}); |
| request.AddHeaderField({"User-Agent", "not/telling"}) |
| ->set_body({"hello, world!\r\n"}); |
| |
| BinaryHttpRequest different_control( |
| {"PUT", "https", "www.example.com", "/hello.txt"}); |
| different_control.AddHeaderField({"User-Agent", "not/telling"}) |
| ->set_body({"hello, world!\r\n"}); |
| EXPECT_NE(request, different_control); |
| |
| BinaryHttpRequest different_header( |
| {"PUT", "https", "www.example.com", "/hello.txt"}); |
| different_header.AddHeaderField({"User-Agent", "told/you"}) |
| ->set_body({"hello, world!\r\n"}); |
| EXPECT_NE(request, different_header); |
| |
| BinaryHttpRequest no_header( |
| {"PUT", "https", "www.example.com", "/hello.txt"}); |
| no_header.set_body({"hello, world!\r\n"}); |
| EXPECT_NE(request, no_header); |
| |
| BinaryHttpRequest different_body( |
| {"POST", "https", "www.example.com", "/hello.txt"}); |
| different_body.AddHeaderField({"User-Agent", "not/telling"}) |
| ->set_body({"goodbye, world!\r\n"}); |
| EXPECT_NE(request, different_body); |
| |
| BinaryHttpRequest no_body({"POST", "https", "www.example.com", "/hello.txt"}); |
| no_body.AddHeaderField({"User-Agent", "not/telling"}); |
| EXPECT_NE(request, no_body); |
| } |
| |
| TEST(BinaryHttpResponse, EncodeNoBody) { |
| /* |
| HTTP/1.1 404 Not Found |
| Server: Apache |
| */ |
| BinaryHttpResponse response(404); |
| response.AddHeaderField({"Server", "Apache"}); |
| /* |
| 0141940e 06736572 76657206 41706163 .A...server.Apac |
| 686500 he.. |
| */ |
| const uint32_t expected_words[] = {0x0141940e, 0x06736572, 0x76657206, |
| 0x41706163, 0x68650000}; |
| std::string expected; |
| for (const auto& word : expected_words) { |
| expected += WordToBytes(word); |
| } |
| // Remove padding. |
| expected.resize(expected.size() - 1); |
| const auto result = response.Serialize(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(*result, expected); |
| EXPECT_THAT( |
| response.DebugString(), |
| StrEq("BinaryHttpResponse(404){BinaryHttpMessage{Headers{Field{server=" |
| "Apache}}Body{}}}")); |
| } |
| |
| TEST(BinaryHttpResponse, DecodeNoBody) { |
| /* |
| HTTP/1.1 404 Not Found |
| Server: Apache |
| */ |
| const uint32_t words[] = {0x0141940e, 0x06736572, 0x76657206, 0x41706163, |
| 0x68650000}; |
| std::string data; |
| for (const auto& word : words) { |
| data += WordToBytes(word); |
| } |
| const auto response_so = BinaryHttpResponse::Create(data); |
| ASSERT_TRUE(response_so.ok()); |
| const BinaryHttpResponse response = *response_so; |
| ASSERT_EQ(response.status_code(), 404); |
| std::vector<BinaryHttpMessage::Field> expected_fields = { |
| {"server", "Apache"}}; |
| ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields)); |
| ASSERT_EQ(response.body(), ""); |
| ASSERT_TRUE(response.informational_responses().empty()); |
| EXPECT_THAT( |
| response.DebugString(), |
| StrEq("BinaryHttpResponse(404){BinaryHttpMessage{Headers{Field{server=" |
| "Apache}}Body{}}}")); |
| } |
| |
| TEST(BinaryHttpResponse, EncodeBody) { |
| /* |
| HTTP/1.1 200 OK |
| Server: Apache |
| |
| Hello, world! |
| */ |
| BinaryHttpResponse response(200); |
| response.AddHeaderField({"Server", "Apache"}); |
| response.set_body("Hello, world!\r\n"); |
| /* |
| 0140c80e 06736572 76657206 41706163 .@...server.Apac |
| 68650f48 656c6c6f 2c20776f 726c6421 he.Hello, world! |
| 0d0a .... |
| */ |
| const uint32_t expected_words[] = {0x0140c80e, 0x06736572, 0x76657206, |
| 0x41706163, 0x68650f48, 0x656c6c6f, |
| 0x2c20776f, 0x726c6421, 0x0d0a0000}; |
| std::string expected; |
| for (const auto& word : expected_words) { |
| expected += WordToBytes(word); |
| } |
| // Remove padding. |
| expected.resize(expected.size() - 2); |
| |
| const auto result = response.Serialize(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(*result, expected); |
| EXPECT_THAT( |
| response.DebugString(), |
| StrEq("BinaryHttpResponse(200){BinaryHttpMessage{Headers{Field{server=" |
| "Apache}}Body{Hello, world!\r\n}}}")); |
| } |
| |
| TEST(BinaryHttpResponse, DecodeBody) { |
| /* |
| HTTP/1.1 200 OK |
| |
| Hello, world! |
| */ |
| const uint32_t words[] = {0x0140c80e, 0x06736572, 0x76657206, |
| 0x41706163, 0x68650f48, 0x656c6c6f, |
| 0x2c20776f, 0x726c6421, 0x0d0a0000}; |
| std::string data; |
| for (const auto& word : words) { |
| data += WordToBytes(word); |
| } |
| const auto response_so = BinaryHttpResponse::Create(data); |
| ASSERT_TRUE(response_so.ok()); |
| const BinaryHttpResponse response = *response_so; |
| ASSERT_EQ(response.status_code(), 200); |
| std::vector<BinaryHttpMessage::Field> expected_fields = { |
| {"server", "Apache"}}; |
| ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields)); |
| ASSERT_EQ(response.body(), "Hello, world!\r\n"); |
| ASSERT_TRUE(response.informational_responses().empty()); |
| EXPECT_THAT( |
| response.DebugString(), |
| StrEq("BinaryHttpResponse(200){BinaryHttpMessage{Headers{Field{server=" |
| "Apache}}Body{Hello, world!\r\n}}}")); |
| } |
| |
| TEST(BHttpResponse, AddBadInformationalResponseCode) { |
| BinaryHttpResponse response(200); |
| ASSERT_FALSE(response.AddInformationalResponse(50, {}).ok()); |
| ASSERT_FALSE(response.AddInformationalResponse(300, {}).ok()); |
| } |
| |
| TEST(BinaryHttpResponse, EncodeMultiInformationalWithBody) { |
| /* |
| HTTP/1.1 102 Processing |
| Running: "sleep 15" |
| |
| HTTP/1.1 103 Early Hints |
| Link: </style.css>; rel=preload; as=style |
| Link: </script.js>; rel=preload; as=script |
| |
| HTTP/1.1 200 OK |
| Date: Mon, 27 Jul 2009 12:28:53 GMT |
| Server: Apache |
| Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT |
| ETag: "34aa387-d-1568eb00" |
| Accept-Ranges: bytes |
| Content-Length: 51 |
| Vary: Accept-Encoding |
| Content-Type: text/plain |
| |
| Hello World! My content includes a trailing CRLF. |
| */ |
| BinaryHttpResponse response(200); |
| response.AddHeaderField({"Date", "Mon, 27 Jul 2009 12:28:53 GMT"}) |
| ->AddHeaderField({"Server", "Apache"}) |
| ->AddHeaderField({"Last-Modified", "Wed, 22 Jul 2009 19:15:56 GMT"}) |
| ->AddHeaderField({"ETag", "\"34aa387-d-1568eb00\""}) |
| ->AddHeaderField({"Accept-Ranges", "bytes"}) |
| ->AddHeaderField({"Content-Length", "51"}) |
| ->AddHeaderField({"Vary", "Accept-Encoding"}) |
| ->AddHeaderField({"Content-Type", "text/plain"}); |
| response.set_body("Hello World! My content includes a trailing CRLF.\r\n"); |
| ASSERT_TRUE( |
| response.AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| ASSERT_TRUE(response |
| .AddInformationalResponse( |
| 103, {{"Link", "</style.css>; rel=preload; as=style"}, |
| {"Link", "</script.js>; rel=preload; as=script"}}) |
| .ok()); |
| |
| /* |
| 01406613 0772756e 6e696e67 0a22736c .@f..running."sl |
| 65657020 31352240 67405304 6c696e6b eep 15"@g@S.link |
| 233c2f73 74796c65 2e637373 3e3b2072 #</style.css>; r |
| 656c3d70 72656c6f 61643b20 61733d73 el=preload; as=s |
| 74796c65 046c696e 6b243c2f 73637269 tyle.link$</scri |
| 70742e6a 733e3b20 72656c3d 7072656c pt.js>; rel=prel |
| 6f61643b 2061733d 73637269 707440c8 oad; as=script@. |
| 40ca0464 6174651d 4d6f6e2c 20323720 @..date.Mon, 27 |
| 4a756c20 32303039 2031323a 32383a35 Jul 2009 12:28:5 |
| 3320474d 54067365 72766572 06417061 3 GMT.server.Apa |
| 6368650d 6c617374 2d6d6f64 69666965 che.last-modifie |
| 641d5765 642c2032 32204a75 6c203230 d.Wed, 22 Jul 20 |
| 30392031 393a3135 3a353620 474d5404 09 19:15:56 GMT. |
| 65746167 14223334 61613338 372d642d etag."34aa387-d- |
| 31353638 65623030 220d6163 63657074 1568eb00".accept |
| 2d72616e 67657305 62797465 730e636f -ranges.bytes.co |
| 6e74656e 742d6c65 6e677468 02353104 ntent-length.51. |
| 76617279 0f416363 6570742d 456e636f vary.Accept-Enco |
| 64696e67 0c636f6e 74656e74 2d747970 ding.content-typ |
| 650a7465 78742f70 6c61696e 3348656c e.text/plain3Hel |
| 6c6f2057 6f726c64 21204d79 20636f6e lo World! My con |
| 74656e74 20696e63 6c756465 73206120 tent includes a |
| 74726169 6c696e67 2043524c 462e0d0a trailing CRLF... |
| */ |
| const uint32_t expected_words[] = { |
| 0x01406613, 0x0772756e, 0x6e696e67, 0x0a22736c, 0x65657020, 0x31352240, |
| 0x67405304, 0x6c696e6b, 0x233c2f73, 0x74796c65, 0x2e637373, 0x3e3b2072, |
| 0x656c3d70, 0x72656c6f, 0x61643b20, 0x61733d73, 0x74796c65, 0x046c696e, |
| 0x6b243c2f, 0x73637269, 0x70742e6a, 0x733e3b20, 0x72656c3d, 0x7072656c, |
| 0x6f61643b, 0x2061733d, 0x73637269, 0x707440c8, 0x40ca0464, 0x6174651d, |
| 0x4d6f6e2c, 0x20323720, 0x4a756c20, 0x32303039, 0x2031323a, 0x32383a35, |
| 0x3320474d, 0x54067365, 0x72766572, 0x06417061, 0x6368650d, 0x6c617374, |
| 0x2d6d6f64, 0x69666965, 0x641d5765, 0x642c2032, 0x32204a75, 0x6c203230, |
| 0x30392031, 0x393a3135, 0x3a353620, 0x474d5404, 0x65746167, 0x14223334, |
| 0x61613338, 0x372d642d, 0x31353638, 0x65623030, 0x220d6163, 0x63657074, |
| 0x2d72616e, 0x67657305, 0x62797465, 0x730e636f, 0x6e74656e, 0x742d6c65, |
| 0x6e677468, 0x02353104, 0x76617279, 0x0f416363, 0x6570742d, 0x456e636f, |
| 0x64696e67, 0x0c636f6e, 0x74656e74, 0x2d747970, 0x650a7465, 0x78742f70, |
| 0x6c61696e, 0x3348656c, 0x6c6f2057, 0x6f726c64, 0x21204d79, 0x20636f6e, |
| 0x74656e74, 0x20696e63, 0x6c756465, 0x73206120, 0x74726169, 0x6c696e67, |
| 0x2043524c, 0x462e0d0a}; |
| std::string expected; |
| for (const auto& word : expected_words) { |
| expected += WordToBytes(word); |
| } |
| const auto result = response.Serialize(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_EQ(*result, expected); |
| EXPECT_THAT( |
| response.DebugString(), |
| StrEq( |
| "BinaryHttpResponse(200){BinaryHttpMessage{Headers{Field{date=Mon, " |
| "27 Jul 2009 12:28:53 " |
| "GMT};Field{server=Apache};Field{last-modified=Wed, 22 Jul 2009 " |
| "19:15:56 " |
| "GMT};Field{etag=\"34aa387-d-1568eb00\"};Field{accept-ranges=bytes};" |
| "Field{" |
| "content-length=51};Field{vary=Accept-Encoding};Field{content-type=" |
| "text/plain}}Body{Hello World! My content includes a trailing " |
| "CRLF.\r\n}}InformationalResponse{Field{running=\"sleep " |
| "15\"}};InformationalResponse{Field{link=</style.css>; rel=preload; " |
| "as=style};Field{link=</script.js>; rel=preload; as=script}}}")); |
| TestPrintTo(response); |
| } |
| |
| TEST(BinaryHttpResponse, DecodeMultiInformationalWithBody) { |
| /* |
| HTTP/1.1 102 Processing |
| Running: "sleep 15" |
| |
| HTTP/1.1 103 Early Hints |
| Link: </style.css>; rel=preload; as=style |
| Link: </script.js>; rel=preload; as=script |
| |
| HTTP/1.1 200 OK |
| Date: Mon, 27 Jul 2009 12:28:53 GMT |
| Server: Apache |
| Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT |
| ETag: "34aa387-d-1568eb00" |
| Accept-Ranges: bytes |
| Content-Length: 51 |
| Vary: Accept-Encoding |
| Content-Type: text/plain |
| |
| Hello World! My content includes a trailing CRLF. |
| */ |
| const uint32_t words[] = { |
| 0x01406613, 0x0772756e, 0x6e696e67, 0x0a22736c, 0x65657020, 0x31352240, |
| 0x67405304, 0x6c696e6b, 0x233c2f73, 0x74796c65, 0x2e637373, 0x3e3b2072, |
| 0x656c3d70, 0x72656c6f, 0x61643b20, 0x61733d73, 0x74796c65, 0x046c696e, |
| 0x6b243c2f, 0x73637269, 0x70742e6a, 0x733e3b20, 0x72656c3d, 0x7072656c, |
| 0x6f61643b, 0x2061733d, 0x73637269, 0x707440c8, 0x40ca0464, 0x6174651d, |
| 0x4d6f6e2c, 0x20323720, 0x4a756c20, 0x32303039, 0x2031323a, 0x32383a35, |
| 0x3320474d, 0x54067365, 0x72766572, 0x06417061, 0x6368650d, 0x6c617374, |
| 0x2d6d6f64, 0x69666965, 0x641d5765, 0x642c2032, 0x32204a75, 0x6c203230, |
| 0x30392031, 0x393a3135, 0x3a353620, 0x474d5404, 0x65746167, 0x14223334, |
| 0x61613338, 0x372d642d, 0x31353638, 0x65623030, 0x220d6163, 0x63657074, |
| 0x2d72616e, 0x67657305, 0x62797465, 0x730e636f, 0x6e74656e, 0x742d6c65, |
| 0x6e677468, 0x02353104, 0x76617279, 0x0f416363, 0x6570742d, 0x456e636f, |
| 0x64696e67, 0x0c636f6e, 0x74656e74, 0x2d747970, 0x650a7465, 0x78742f70, |
| 0x6c61696e, 0x3348656c, 0x6c6f2057, 0x6f726c64, 0x21204d79, 0x20636f6e, |
| 0x74656e74, 0x20696e63, 0x6c756465, 0x73206120, 0x74726169, 0x6c696e67, |
| 0x2043524c, 0x462e0d0a, 0x00000000}; |
| std::string data; |
| for (const auto& word : words) { |
| data += WordToBytes(word); |
| } |
| const auto response_so = BinaryHttpResponse::Create(data); |
| ASSERT_TRUE(response_so.ok()); |
| const BinaryHttpResponse response = *response_so; |
| std::vector<BinaryHttpMessage::Field> expected_fields = { |
| {"date", "Mon, 27 Jul 2009 12:28:53 GMT"}, |
| {"server", "Apache"}, |
| {"last-modified", "Wed, 22 Jul 2009 19:15:56 GMT"}, |
| {"etag", "\"34aa387-d-1568eb00\""}, |
| {"accept-ranges", "bytes"}, |
| {"content-length", "51"}, |
| {"vary", "Accept-Encoding"}, |
| {"content-type", "text/plain"}}; |
| |
| ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields)); |
| ASSERT_EQ(response.body(), |
| "Hello World! My content includes a trailing CRLF.\r\n"); |
| std::vector<BinaryHttpMessage::Field> header102 = { |
| {"running", "\"sleep 15\""}}; |
| std::vector<BinaryHttpMessage::Field> header103 = { |
| {"link", "</style.css>; rel=preload; as=style"}, |
| {"link", "</script.js>; rel=preload; as=script"}}; |
| std::vector<BinaryHttpResponse::InformationalResponse> expected_control = { |
| {102, header102}, {103, header103}}; |
| ASSERT_THAT(response.informational_responses(), |
| ContainerEq(expected_control)); |
| EXPECT_THAT( |
| response.DebugString(), |
| StrEq( |
| "BinaryHttpResponse(200){BinaryHttpMessage{Headers{Field{date=Mon, " |
| "27 Jul 2009 12:28:53 " |
| "GMT};Field{server=Apache};Field{last-modified=Wed, 22 Jul 2009 " |
| "19:15:56 " |
| "GMT};Field{etag=\"34aa387-d-1568eb00\"};Field{accept-ranges=bytes};" |
| "Field{" |
| "content-length=51};Field{vary=Accept-Encoding};Field{content-type=" |
| "text/plain}}Body{Hello World! My content includes a trailing " |
| "CRLF.\r\n}}InformationalResponse{Field{running=\"sleep " |
| "15\"}};InformationalResponse{Field{link=</style.css>; rel=preload; " |
| "as=style};Field{link=</script.js>; rel=preload; as=script}}}")); |
| TestPrintTo(response); |
| } |
| |
| TEST(BinaryHttpMessage, SwapBody) { |
| BinaryHttpRequest request({}); |
| request.set_body("hello, world!"); |
| std::string other = "goodbye, world!"; |
| request.swap_body(other); |
| EXPECT_EQ(request.body(), "goodbye, world!"); |
| EXPECT_EQ(other, "hello, world!"); |
| } |
| |
| TEST(BinaryHttpResponse, Equality) { |
| BinaryHttpResponse response(200); |
| response.AddHeaderField({"Server", "Apache"})->set_body("Hello, world!\r\n"); |
| ASSERT_TRUE( |
| response.AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| |
| BinaryHttpResponse same(200); |
| same.AddHeaderField({"Server", "Apache"})->set_body("Hello, world!\r\n"); |
| ASSERT_TRUE( |
| same.AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}).ok()); |
| ASSERT_EQ(response, same); |
| } |
| |
| TEST(BinaryHttpResponse, Inequality) { |
| BinaryHttpResponse response(200); |
| response.AddHeaderField({"Server", "Apache"})->set_body("Hello, world!\r\n"); |
| ASSERT_TRUE( |
| response.AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| |
| BinaryHttpResponse different_status(201); |
| different_status.AddHeaderField({"Server", "Apache"}) |
| ->set_body("Hello, world!\r\n"); |
| EXPECT_TRUE(different_status |
| .AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| EXPECT_NE(response, different_status); |
| |
| BinaryHttpResponse different_header(200); |
| different_header.AddHeaderField({"Server", "python3"}) |
| ->set_body("Hello, world!\r\n"); |
| EXPECT_TRUE(different_header |
| .AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| EXPECT_NE(response, different_header); |
| |
| BinaryHttpResponse no_header(200); |
| no_header.set_body("Hello, world!\r\n"); |
| EXPECT_TRUE( |
| no_header.AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| EXPECT_NE(response, no_header); |
| |
| BinaryHttpResponse different_body(200); |
| different_body.AddHeaderField({"Server", "Apache"}) |
| ->set_body("Goodbye, world!\r\n"); |
| EXPECT_TRUE(different_body |
| .AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| EXPECT_NE(response, different_body); |
| |
| BinaryHttpResponse no_body(200); |
| no_body.AddHeaderField({"Server", "Apache"}); |
| EXPECT_TRUE( |
| no_body.AddInformationalResponse(102, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| EXPECT_NE(response, no_body); |
| |
| BinaryHttpResponse different_informational(200); |
| different_informational.AddHeaderField({"Server", "Apache"}) |
| ->set_body("Hello, world!\r\n"); |
| EXPECT_TRUE(different_informational |
| .AddInformationalResponse(198, {{"Running", "\"sleep 15\""}}) |
| .ok()); |
| EXPECT_NE(response, different_informational); |
| |
| BinaryHttpResponse no_informational(200); |
| no_informational.AddHeaderField({"Server", "Apache"}) |
| ->set_body("Hello, world!\r\n"); |
| EXPECT_NE(response, no_informational); |
| } |
| |
| MATCHER_P(HasEqPayload, value, "Payloads of messages are equivalent.") { |
| return arg.IsPayloadEqual(value); |
| } |
| |
| template <typename T> |
| void TestPadding(T& message) { |
| const auto data_so = message.Serialize(); |
| ASSERT_TRUE(data_so.ok()); |
| auto data = *data_so; |
| ASSERT_EQ(data.size(), message.EncodedSize()); |
| |
| message.set_num_padding_bytes(10); |
| const auto padded_data_so = message.Serialize(); |
| ASSERT_TRUE(padded_data_so.ok()); |
| const auto padded_data = *padded_data_so; |
| ASSERT_EQ(padded_data.size(), message.EncodedSize()); |
| |
| // Check padding size output. |
| ASSERT_EQ(data.size() + 10, padded_data.size()); |
| // Check for valid null byte padding output |
| data.resize(data.size() + 10); |
| ASSERT_EQ(data, padded_data); |
| |
| // Deserialize padded and not padded, and verify they are the same. |
| const auto deserialized_padded_message_so = T::Create(data); |
| ASSERT_TRUE(deserialized_padded_message_so.ok()); |
| const auto deserialized_padded_message = *deserialized_padded_message_so; |
| ASSERT_EQ(deserialized_padded_message, message); |
| ASSERT_EQ(deserialized_padded_message.num_padding_bytes(), size_t(10)); |
| |
| // Invalid padding |
| data[data.size() - 1] = 'a'; |
| const auto bad_so = T::Create(data); |
| ASSERT_FALSE(bad_so.ok()); |
| |
| // Check that padding does not impact equality. |
| data.resize(data.size() - 10); |
| const auto deserialized_message_so = T::Create(data); |
| ASSERT_TRUE(deserialized_message_so.ok()); |
| const auto deserialized_message = *deserialized_message_so; |
| ASSERT_EQ(deserialized_message.num_padding_bytes(), size_t(0)); |
| // Confirm that the message payloads are equal, but not fully equivalent due |
| // to padding. |
| ASSERT_THAT(deserialized_message, HasEqPayload(deserialized_padded_message)); |
| ASSERT_NE(deserialized_message, deserialized_padded_message); |
| } |
| |
| TEST(BinaryHttpRequest, Padding) { |
| /* |
| GET /hello.txt HTTP/1.1 |
| User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 |
| Host: www.example.com |
| Accept-Language: en, mi |
| */ |
| BinaryHttpRequest request({"GET", "https", "", "/hello.txt"}); |
| request |
| .AddHeaderField({"User-Agent", |
| "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"}) |
| ->AddHeaderField({"Host", "www.example.com"}) |
| ->AddHeaderField({"Accept-Language", "en, mi"}); |
| TestPadding(request); |
| } |
| |
| TEST(BinaryHttpResponse, Padding) { |
| /* |
| HTTP/1.1 200 OK |
| Server: Apache |
| |
| Hello, world! |
| */ |
| BinaryHttpResponse response(200); |
| response.AddHeaderField({"Server", "Apache"}); |
| response.set_body("Hello, world!\r\n"); |
| TestPadding(response); |
| } |
| |
| } // namespace quiche |