1 #include "async_resp.hpp" 2 #include "http/http2_connection.hpp" 3 #include "http/http_request.hpp" 4 #include "http/http_response.hpp" 5 #include "nghttp2_adapters.hpp" 6 7 #include <nghttp2/nghttp2.h> 8 #include <unistd.h> 9 10 #include <boost/asio/buffer.hpp> 11 #include <boost/asio/impl/write.hpp> 12 #include <boost/asio/io_context.hpp> 13 #include <boost/asio/steady_timer.hpp> 14 #include <boost/beast/_experimental/test/stream.hpp> 15 #include <boost/beast/http/field.hpp> 16 17 #include <bit> 18 #include <cstddef> 19 #include <cstdint> 20 #include <functional> 21 #include <memory> 22 #include <string> 23 #include <string_view> 24 #include <utility> 25 #include <vector> 26 27 #include "gmock/gmock.h" 28 #include "gtest/gtest.h" 29 namespace crow 30 { 31 32 namespace 33 { 34 35 using ::testing::Pair; 36 using ::testing::UnorderedElementsAre; 37 38 struct FakeHandler 39 { 40 bool called = false; 41 void handle(Request& req, 42 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 43 { 44 called = true; 45 EXPECT_EQ(req.url().buffer(), "/redfish/v1/"); 46 EXPECT_EQ(req.methodString(), "GET"); 47 EXPECT_EQ(req.getHeaderValue(boost::beast::http::field::user_agent), 48 "curl/8.5.0"); 49 EXPECT_EQ(req.getHeaderValue(boost::beast::http::field::accept), "*/*"); 50 EXPECT_EQ(req.getHeaderValue(":authority"), "localhost:18080"); 51 asyncResp->res.write("StringOutput"); 52 } 53 }; 54 55 std::string getDateStr() 56 { 57 return "TestTime"; 58 } 59 60 void unpackHeaders(std::string_view dataField, 61 std::vector<std::pair<std::string, std::string>>& headers) 62 { 63 nghttp2_hd_inflater_ex inflater; 64 65 while (!dataField.empty()) 66 { 67 nghttp2_nv nv; 68 int inflateFlags = 0; 69 const uint8_t* data = std::bit_cast<const uint8_t*>(dataField.data()); 70 ssize_t parsed = inflater.hd2(&nv, &inflateFlags, data, 71 dataField.size(), 1); 72 73 ASSERT_GT(parsed, 0); 74 dataField.remove_prefix(static_cast<size_t>(parsed)); 75 if ((inflateFlags & NGHTTP2_HD_INFLATE_EMIT) > 0) 76 { 77 const char* namePtr = std::bit_cast<const char*>(nv.name); 78 std::string key(namePtr, nv.namelen); 79 const char* valPtr = std::bit_cast<const char*>(nv.value); 80 std::string value(valPtr, nv.valuelen); 81 headers.emplace_back(key, value); 82 } 83 if ((inflateFlags & NGHTTP2_HD_INFLATE_FINAL) > 0) 84 { 85 EXPECT_EQ(inflater.endHeaders(), 0); 86 break; 87 } 88 } 89 EXPECT_TRUE(dataField.empty()); 90 } 91 92 TEST(http_connection, RequestPropogates) 93 { 94 using namespace std::literals; 95 boost::asio::io_context io; 96 boost::beast::test::stream stream(io); 97 boost::beast::test::stream out(io); 98 stream.connect(out); 99 // This is a binary pre-encrypted stream captured from curl for a request to 100 // curl https://localhost:18080/redfish/v1/ 101 std::string_view toSend = 102 // Hello 103 "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" 104 // 18 byte settings frame 105 "\x00\x00\x12\x04\x00\x00\x00\x00\x00" 106 // Settings 107 "\x00\x03\x00\x00\x00\x64\x00\x04\x00\xa0\x00\x00\x00\x02\x00\x00\x00\x00" 108 // Window update frame 109 "\x00\x00\x04\x08\x00\x00\x00\x00\x00" 110 // Window update 111 "\x3e\x7f\x00\x01" 112 // Header frame END_STREAM, END_HEADERS set 113 "\x00\x00\x29\x01\x05\x00\x00\x00" 114 // Header payload 115 "\x01\x82\x87\x41\x8b\xa0\xe4\x1d\x13\x9d\x09\xb8\x17\x80\xf0\x3f" 116 "\x04\x89\x62\xc2\xc9\x29\x91\x3b\x1d\xc2\xc7\x7a\x88\x25\xb6\x50" 117 "\xc3\xcb\xb6\xb8\x3f\x53\x03\x2a\x2f\x2a"sv; 118 119 boost::asio::write(out, boost::asio::buffer(toSend)); 120 121 FakeHandler handler; 122 boost::asio::steady_timer timer(io); 123 std::function<std::string()> date(getDateStr); 124 auto conn = std::make_shared< 125 HTTP2Connection<boost::beast::test::stream, FakeHandler>>( 126 std::move(stream), &handler, date); 127 conn->start(); 128 129 std::string_view expectedPrefix = 130 // Settings frame size 13 131 "\x00\x00\x0c\x04\x00\x00\x00\x00\x00" 132 // 4 max concurrent streams 133 "\x00\x03\x00\x00\x00\x04" 134 // Enable push = false 135 "\x00\x02\x00\x00\x00\x00" 136 // Settings ACK from server to client 137 "\x00\x00\x00\x04\x01\x00\x00\x00\x00" 138 139 // Start Headers frame stream 1, size 0x034b 140 "\x00\x03\x4b\x01\x04\x00\x00\x00\x01"sv; 141 142 std::string_view expectedPostfix = 143 // Data Frame, Length 12, Stream 1, End Stream flag set 144 "\x00\x00\x0c\x00\x01\x00\x00\x00\x01" 145 // The body expected 146 "StringOutput"sv; 147 148 std::string_view outStr; 149 constexpr size_t headerSize = 0x34b; 150 151 // Run until we receive the expected amount of data 152 while (outStr.size() < 153 expectedPrefix.size() + headerSize + expectedPostfix.size()) 154 { 155 io.run_one(); 156 outStr = out.str(); 157 } 158 EXPECT_TRUE(handler.called); 159 160 // check the stream output against expected 161 EXPECT_EQ(outStr.substr(0, expectedPrefix.size()), expectedPrefix); 162 outStr.remove_prefix(expectedPrefix.size()); 163 std::vector<std::pair<std::string, std::string>> headers; 164 unpackHeaders(outStr.substr(0, headerSize), headers); 165 outStr.remove_prefix(headerSize); 166 167 EXPECT_THAT( 168 headers, 169 UnorderedElementsAre( 170 Pair(":status", "200"), Pair("content-length", "12"), 171 Pair("strict-transport-security", 172 "max-age=31536000; includeSubdomains"), 173 Pair("x-frame-options", "DENY"), Pair("pragma", "no-cache"), 174 Pair("cache-control", "no-store, max-age=0"), 175 Pair("x-content-type-options", "nosniff"), 176 Pair("referrer-policy", "no-referrer"), 177 Pair( 178 "permissions-policy", 179 "accelerometer=(),ambient-light-sensor=(),autoplay=(),battery=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),layout-animations=(self),legacy-image-formats=(self),magnetometer=(),microphone=(),midi=(),oversized-images=(self),payment=(),picture-in-picture=(),publickey-credentials-get=(),speaker-selection=(),sync-xhr=(self),unoptimized-images=(self),unsized-media=(self),usb=(),screen-wak-lock=(),web-share=(),xr-spatial-tracking=()"), 180 Pair("x-permitted-cross-domain-policies", "none"), 181 Pair("cross-origin-embedder-policy", "require-corp"), 182 Pair("cross-origin-opener-policy", "same-origin"), 183 Pair("cross-origin-resource-policy", "same-origin"), 184 Pair( 185 "content-security-policy", 186 "default-src 'none'; img-src 'self' data:; font-src 'self'; style-src 'self'; script-src 'self'; connect-src 'self' wss:; form-action 'none'; frame-ancestors 'none'; object-src 'none'; base-uri 'none'"), 187 Pair("date", "TestTime"))); 188 189 EXPECT_EQ(outStr, expectedPostfix); 190 } 191 192 } // namespace 193 } // namespace crow 194