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