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(const std::shared_ptr<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                   "*/*");
51         EXPECT_EQ(req->getHeaderValue(":authority"), "localhost:18080");
52         asyncResp->res.write("StringOutput");
53     }
54 };
55 
56 std::string getDateStr()
57 {
58     return "TestTime";
59 }
60 
61 void unpackHeaders(std::string_view dataField,
62                    std::vector<std::pair<std::string, std::string>>& headers)
63 {
64     nghttp2_hd_inflater_ex inflater;
65 
66     while (!dataField.empty())
67     {
68         nghttp2_nv nv;
69         int inflateFlags = 0;
70         const uint8_t* data = std::bit_cast<const uint8_t*>(dataField.data());
71         ssize_t parsed =
72             inflater.hd2(&nv, &inflateFlags, data, dataField.size(), 1);
73 
74         ASSERT_GT(parsed, 0);
75         dataField.remove_prefix(static_cast<size_t>(parsed));
76         if ((inflateFlags & NGHTTP2_HD_INFLATE_EMIT) > 0)
77         {
78             const char* namePtr = std::bit_cast<const char*>(nv.name);
79             std::string key(namePtr, nv.namelen);
80             const char* valPtr = std::bit_cast<const char*>(nv.value);
81             std::string value(valPtr, nv.valuelen);
82             headers.emplace_back(key, value);
83         }
84         if ((inflateFlags & NGHTTP2_HD_INFLATE_FINAL) > 0)
85         {
86             EXPECT_EQ(inflater.endHeaders(), 0);
87             break;
88         }
89     }
90     EXPECT_TRUE(dataField.empty());
91 }
92 
93 TEST(http_connection, RequestPropogates)
94 {
95     using namespace std::literals;
96     boost::asio::io_context io;
97     boost::beast::test::stream stream(io);
98     boost::beast::test::stream out(io);
99     stream.connect(out);
100     // This is a binary pre-encrypted stream captured from curl for a request to
101     // curl https://localhost:18080/redfish/v1/
102     std::string_view toSend =
103         // Hello
104         "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
105         // 18 byte settings frame
106         "\x00\x00\x12\x04\x00\x00\x00\x00\x00"
107         // Settings
108         "\x00\x03\x00\x00\x00\x64\x00\x04\x00\xa0\x00\x00\x00\x02\x00\x00\x00\x00"
109         // Window update frame
110         "\x00\x00\x04\x08\x00\x00\x00\x00\x00"
111         // Window update
112         "\x3e\x7f\x00\x01"
113         // Header frame END_STREAM, END_HEADERS set
114         "\x00\x00\x29\x01\x05\x00\x00\x00"
115         // Header payload
116         "\x01\x82\x87\x41\x8b\xa0\xe4\x1d\x13\x9d\x09\xb8\x17\x80\xf0\x3f"
117         "\x04\x89\x62\xc2\xc9\x29\x91\x3b\x1d\xc2\xc7\x7a\x88\x25\xb6\x50"
118         "\xc3\xcb\xb6\xb8\x3f\x53\x03\x2a\x2f\x2a"sv;
119 
120     boost::asio::write(out, boost::asio::buffer(toSend));
121 
122     FakeHandler handler;
123     boost::asio::steady_timer timer(io);
124     std::function<std::string()> date(getDateStr);
125     auto conn = std::make_shared<
126         HTTP2Connection<boost::beast::test::stream, FakeHandler>>(
127         std::move(stream), &handler, date);
128     conn->start();
129 
130     std::string_view expectedPrefix =
131         // Settings frame size 13
132         "\x00\x00\x0c\x04\x00\x00\x00\x00\x00"
133         // 4 max concurrent streams
134         "\x00\x03\x00\x00\x00\x04"
135         // Enable push = false
136         "\x00\x02\x00\x00\x00\x00"
137         // Settings ACK from server to client
138         "\x00\x00\x00\x04\x01\x00\x00\x00\x00"
139 
140         // Start Headers frame stream 1, size 0x005f
141         "\x00\x00\x5f\x01\x04\x00\x00\x00\x01"sv;
142 
143     std::string_view expectedPostfix =
144         // Data Frame, Length 12, Stream 1, End Stream flag set
145         "\x00\x00\x0c\x00\x01\x00\x00\x00\x01"
146         // The body expected
147         "StringOutput"sv;
148 
149     std::string_view outStr;
150     constexpr size_t headerSize = 0x05f;
151 
152     // Run until we receive the expected amount of data
153     while (outStr.size() <
154            expectedPrefix.size() + headerSize + expectedPostfix.size())
155     {
156         io.run_one();
157         outStr = out.str();
158     }
159     EXPECT_TRUE(handler.called);
160 
161     // check the stream output against expected
162     EXPECT_EQ(outStr.substr(0, expectedPrefix.size()), expectedPrefix);
163     outStr.remove_prefix(expectedPrefix.size());
164     std::vector<std::pair<std::string, std::string>> headers;
165     unpackHeaders(outStr.substr(0, headerSize), headers);
166     outStr.remove_prefix(headerSize);
167 
168     EXPECT_THAT(headers,
169                 UnorderedElementsAre(
170                     Pair(":status", "200"), Pair("content-length", "12"),
171                     Pair("strict-transport-security",
172                          "max-age=31536000; includeSubdomains"),
173                     Pair("cache-control", "no-store, max-age=0"),
174                     Pair("x-content-type-options", "nosniff"),
175                     Pair("pragma", "no-cache"), Pair("date", "TestTime")));
176 
177     EXPECT_EQ(outStr, expectedPostfix);
178 }
179 
180 } // namespace
181 } // namespace crow
182