blob: 81c78a9a6090eae973d38e02de27ce4b091c8786 [file] [log] [blame]
Ed Tanousf0b59af2024-03-20 13:38:04 -07001#include "async_resp.hpp"
Ed Tanous52dd6932024-01-29 08:34:59 -08002#include "http/http2_connection.hpp"
3#include "http/http_request.hpp"
4#include "http/http_response.hpp"
Ed Tanous5b904292024-04-16 11:10:17 -07005#include "nghttp2_adapters.hpp"
Ed Tanous52dd6932024-01-29 08:34:59 -08006
Ed Tanousf0b59af2024-03-20 13:38:04 -07007#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>
Ed Tanous52dd6932024-01-29 08:34:59 -080013#include <boost/asio/steady_timer.hpp>
14#include <boost/beast/_experimental/test/stream.hpp>
Ed Tanousf0b59af2024-03-20 13:38:04 -070015#include <boost/beast/http/field.hpp>
Ed Tanous52dd6932024-01-29 08:34:59 -080016
17#include <bit>
Ed Tanousf0b59af2024-03-20 13:38:04 -070018#include <cstddef>
19#include <cstdint>
Ed Tanous52dd6932024-01-29 08:34:59 -080020#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"
29namespace crow
30{
31
32namespace
33{
34
35using ::testing::Pair;
36using ::testing::UnorderedElementsAre;
37
38struct 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
55std::string getDateStr()
56{
57 return "TestTime";
58}
59
60void unpackHeaders(std::string_view dataField,
61 std::vector<std::pair<std::string, std::string>>& headers)
62{
Ed Tanousa93163a2024-03-25 11:20:18 -070063 nghttp2_hd_inflater_ex inflater;
Ed Tanous52dd6932024-01-29 08:34:59 -080064
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
92TEST(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
Ed Tanous325310d2024-03-15 09:05:04 -0700139 // Start Headers frame stream 1, size 0x034b
140 "\x00\x03\x4b\x01\x04\x00\x00\x00\x01"sv;
Ed Tanous52dd6932024-01-29 08:34:59 -0800141
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;
Ed Tanous325310d2024-03-15 09:05:04 -0700149 constexpr size_t headerSize = 0x34b;
Ed Tanous52dd6932024-01-29 08:34:59 -0800150
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
Ed Tanous325310d2024-03-15 09:05:04 -0700161 EXPECT_EQ(outStr.substr(0, expectedPrefix.size()), expectedPrefix);
Ed Tanous52dd6932024-01-29 08:34:59 -0800162 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(
Ed Tanous325310d2024-03-15 09:05:04 -0700170 Pair(":status", "200"), Pair("content-length", "12"),
Ed Tanous52dd6932024-01-29 08:34:59 -0800171 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