|
|
@@ -301,6 +301,12 @@ TEST(SocketStream, wait_writable_INET) {
|
|
|
std::thread svr{[&] {
|
|
|
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
ASSERT_LE(0, s);
|
|
|
+ // PORT + 1 is shared with the SSL redirect tests and with
|
|
|
+ // VulnerabilityTest.CRLFInjectionInHeaders, all of which set this. Without
|
|
|
+ // it, a TIME_WAIT one of them left behind makes bind() fail here, and
|
|
|
+ // because that happens on a worker thread the ASSERT does not stop the
|
|
|
+ // test: it runs on and fails at the disconnected_svr_sock check instead.
|
|
|
+ default_socket_options(s);
|
|
|
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
|
|
ASSERT_EQ(0, listen(s, 1));
|
|
|
ASSERT_LE(0, disconnected_svr_sock = accept(s, nullptr, nullptr));
|
|
|
@@ -1302,7 +1308,8 @@ TEST(ParamsOrderTest, ServerSeesTheOrderTheClientSent) {
|
|
|
res.set_content("ok", "text/plain");
|
|
|
});
|
|
|
|
|
|
- thread t = thread([&] { svr.listen(HOST, PORT); });
|
|
|
+ auto port = svr.bind_to_any_port(HOST);
|
|
|
+ thread t = thread([&] { svr.listen_after_bind(); });
|
|
|
auto se = detail::scope_exit([&] {
|
|
|
svr.stop();
|
|
|
t.join();
|
|
|
@@ -1310,12 +1317,149 @@ TEST(ParamsOrderTest, ServerSeesTheOrderTheClientSent) {
|
|
|
});
|
|
|
svr.wait_until_ready();
|
|
|
|
|
|
- Client cli(HOST, PORT);
|
|
|
+ Client cli(HOST, port);
|
|
|
auto res = cli.Get("/order?zulu=1&alpha=2&tag=x&mike=3&tag=y");
|
|
|
ASSERT_TRUE(res);
|
|
|
EXPECT_EQ("zulu=1 alpha=2 tag=x mike=3 tag=y ", order);
|
|
|
}
|
|
|
|
|
|
+TEST(MultipartOrderTest, PartsKeepTheOrderSent) {
|
|
|
+ Server svr;
|
|
|
+ std::string field_order, file_order;
|
|
|
+ svr.Post("/order", [&](const Request &req, Response &res) {
|
|
|
+ for (const auto &field : req.form.fields) {
|
|
|
+ field_order += field.first + "=" + field.second.content + " ";
|
|
|
+ }
|
|
|
+ for (const auto &file : req.form.files) {
|
|
|
+ file_order += file.first + "=" + file.second.filename + " ";
|
|
|
+ }
|
|
|
+ res.set_content("ok", "text/plain");
|
|
|
+ });
|
|
|
+
|
|
|
+ auto port = svr.bind_to_any_port(HOST);
|
|
|
+ thread t = thread([&] { svr.listen_after_bind(); });
|
|
|
+ auto se = detail::scope_exit([&] {
|
|
|
+ svr.stop();
|
|
|
+ t.join();
|
|
|
+ ASSERT_FALSE(svr.is_running());
|
|
|
+ });
|
|
|
+ svr.wait_until_ready();
|
|
|
+
|
|
|
+ UploadFormDataItems items = {
|
|
|
+ {"zulu", "1", "", ""},
|
|
|
+ {"alpha", "2", "", ""},
|
|
|
+ {"mike", "3", "", ""},
|
|
|
+ {"zebra", "z", "z.txt", "text/plain"},
|
|
|
+ {"apple", "a", "a.txt", "text/plain"},
|
|
|
+ };
|
|
|
+
|
|
|
+ Client cli(HOST, port);
|
|
|
+ auto res = cli.Post("/order", items);
|
|
|
+ ASSERT_TRUE(res);
|
|
|
+ EXPECT_EQ("zulu=1 alpha=2 mike=3 ", field_order);
|
|
|
+ EXPECT_EQ("zebra=z.txt apple=a.txt ", file_order);
|
|
|
+}
|
|
|
+
|
|
|
+TEST(MultipartOrderTest, RepeatedNamesKeepTheirOrder) {
|
|
|
+ Server svr;
|
|
|
+ std::vector<std::string> values;
|
|
|
+ std::string first, second, out_of_range, order;
|
|
|
+ svr.Post("/repeated", [&](const Request &req, Response &res) {
|
|
|
+ values = req.form.get_fields("tag");
|
|
|
+ first = req.form.get_field("tag", 0);
|
|
|
+ second = req.form.get_field("tag", 1);
|
|
|
+ out_of_range = req.form.get_field("tag", 2);
|
|
|
+ for (const auto &field : req.form.fields) {
|
|
|
+ order += field.first + "=" + field.second.content + " ";
|
|
|
+ }
|
|
|
+ res.set_content("ok", "text/plain");
|
|
|
+ });
|
|
|
+
|
|
|
+ auto port = svr.bind_to_any_port(HOST);
|
|
|
+ thread t = thread([&] { svr.listen_after_bind(); });
|
|
|
+ auto se = detail::scope_exit([&] {
|
|
|
+ svr.stop();
|
|
|
+ t.join();
|
|
|
+ ASSERT_FALSE(svr.is_running());
|
|
|
+ });
|
|
|
+ svr.wait_until_ready();
|
|
|
+
|
|
|
+ // "other" sits between the two "tag" parts, so the entries sharing a name are
|
|
|
+ // not adjacent in the container.
|
|
|
+ UploadFormDataItems items = {
|
|
|
+ {"tag", "first", "", ""},
|
|
|
+ {"other", "x", "", ""},
|
|
|
+ {"tag", "second", "", ""},
|
|
|
+ };
|
|
|
+
|
|
|
+ Client cli(HOST, port);
|
|
|
+ auto res = cli.Post("/repeated", items);
|
|
|
+ ASSERT_TRUE(res);
|
|
|
+ ASSERT_EQ(2U, values.size());
|
|
|
+ EXPECT_EQ("first", values[0]);
|
|
|
+ EXPECT_EQ("second", values[1]);
|
|
|
+ EXPECT_EQ("first", first);
|
|
|
+ EXPECT_EQ("second", second);
|
|
|
+
|
|
|
+ // std::multimap already kept entries sharing a name in insertion order, so
|
|
|
+ // everything above passes without this change too. The whole traversal is
|
|
|
+ // what tells the two apart: sorting by name would hoist "other" to the front
|
|
|
+ // and the two "tag" parts would end up adjacent.
|
|
|
+ EXPECT_EQ("tag=first other=x tag=second ", order);
|
|
|
+
|
|
|
+ // Advancing past the last entry of a name used to run off the container;
|
|
|
+ // the container's saturating increment makes it return the default instead.
|
|
|
+ EXPECT_EQ("", out_of_range);
|
|
|
+}
|
|
|
+
|
|
|
+TEST(MultipartOrderTest, ContentSurvivesContainerGrowth) {
|
|
|
+ // Server::read_content() keeps a FormFields::iterator alive across the
|
|
|
+ // content callbacks that fill the part it points at. The container is
|
|
|
+ // vector-backed, so a later emplace can reallocate and invalidate an older
|
|
|
+ // iterator; 64 parts grow the vector through seven reallocations, which
|
|
|
+ // checks that every part's content still lands in its own entry.
|
|
|
+ const size_t part_count = 64;
|
|
|
+
|
|
|
+ // One formula for both the parts sent and the values expected back, so the
|
|
|
+ // two cannot drift apart.
|
|
|
+ auto name_of = [](size_t i) { return "f" + std::to_string(i); };
|
|
|
+ auto content_of = [](size_t i) {
|
|
|
+ return std::string(64, static_cast<char>('a' + (i % 26)));
|
|
|
+ };
|
|
|
+
|
|
|
+ Server svr;
|
|
|
+ std::vector<std::pair<std::string, std::string>> received;
|
|
|
+ svr.Post("/many", [&](const Request &req, Response &res) {
|
|
|
+ for (const auto &field : req.form.fields) {
|
|
|
+ received.emplace_back(field.first, field.second.content);
|
|
|
+ }
|
|
|
+ res.set_content("ok", "text/plain");
|
|
|
+ });
|
|
|
+
|
|
|
+ auto port = svr.bind_to_any_port(HOST);
|
|
|
+ thread t = thread([&] { svr.listen_after_bind(); });
|
|
|
+ auto se = detail::scope_exit([&] {
|
|
|
+ svr.stop();
|
|
|
+ t.join();
|
|
|
+ ASSERT_FALSE(svr.is_running());
|
|
|
+ });
|
|
|
+ svr.wait_until_ready();
|
|
|
+
|
|
|
+ UploadFormDataItems items;
|
|
|
+ for (size_t i = 0; i < part_count; i++) {
|
|
|
+ items.push_back({name_of(i), content_of(i), "", ""});
|
|
|
+ }
|
|
|
+
|
|
|
+ Client cli(HOST, port);
|
|
|
+ auto res = cli.Post("/many", items);
|
|
|
+ ASSERT_TRUE(res);
|
|
|
+ ASSERT_EQ(part_count, received.size());
|
|
|
+ for (size_t i = 0; i < part_count; i++) {
|
|
|
+ EXPECT_EQ(name_of(i), received[i].first) << "part " << i;
|
|
|
+ EXPECT_EQ(content_of(i), received[i].second) << "part " << i;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
TEST(ParseMultipartBoundaryTest, DefaultValue) {
|
|
|
string content_type = "multipart/form-data; boundary=something";
|
|
|
string boundary;
|
|
|
@@ -8401,11 +8545,11 @@ TEST(ZstdDecompressor, Decompress) {
|
|
|
|
|
|
// Sends a raw request to a server listening at HOST:PORT.
|
|
|
static bool send_request(time_t read_timeout_sec, const std::string &req,
|
|
|
- std::string *resp = nullptr) {
|
|
|
+ std::string *resp = nullptr, int port = PORT) {
|
|
|
auto error = Error::Success;
|
|
|
|
|
|
auto client_sock = detail::create_client_socket(
|
|
|
- HOST, "", PORT, AF_UNSPEC, false, false, nullptr,
|
|
|
+ HOST, "", port, AF_UNSPEC, false, false, nullptr,
|
|
|
/*connection_timeout_sec=*/5, 0,
|
|
|
/*read_timeout_sec=*/5, 0,
|
|
|
/*write_timeout_sec=*/5, 0, std::string(), error);
|
|
|
@@ -8472,7 +8616,8 @@ TEST(HeadersOrderTest, ReceivedFieldsKeepTheirOrder) {
|
|
|
res.set_content("ok", "text/plain");
|
|
|
});
|
|
|
|
|
|
- thread t = thread([&] { svr.listen(HOST, PORT); });
|
|
|
+ auto port = svr.bind_to_any_port(HOST);
|
|
|
+ thread t = thread([&] { svr.listen_after_bind(); });
|
|
|
auto se = detail::scope_exit([&] {
|
|
|
svr.stop();
|
|
|
t.join();
|
|
|
@@ -8490,7 +8635,7 @@ TEST(HeadersOrderTest, ReceivedFieldsKeepTheirOrder) {
|
|
|
"\r\n";
|
|
|
|
|
|
std::string res;
|
|
|
- ASSERT_TRUE(send_request(5, req, &res));
|
|
|
+ ASSERT_TRUE(send_request(5, req, &res, port));
|
|
|
EXPECT_EQ("HTTP/1.1 200 OK", res.substr(0, 15));
|
|
|
EXPECT_EQ("X-First=1 X-Dup=a X-Second=2 X-Dup=b Connection=close ", received);
|
|
|
}
|
|
|
@@ -8504,7 +8649,8 @@ TEST(HeadersOrderTest, SentFieldsKeepTheirOrder) {
|
|
|
res.set_content("ok", "text/plain");
|
|
|
});
|
|
|
|
|
|
- thread t = thread([&] { svr.listen(HOST, PORT); });
|
|
|
+ auto port = svr.bind_to_any_port(HOST);
|
|
|
+ thread t = thread([&] { svr.listen_after_bind(); });
|
|
|
auto se = detail::scope_exit([&] {
|
|
|
svr.stop();
|
|
|
t.join();
|
|
|
@@ -8513,7 +8659,7 @@ TEST(HeadersOrderTest, SentFieldsKeepTheirOrder) {
|
|
|
|
|
|
svr.wait_until_ready();
|
|
|
|
|
|
- Client cli(HOST, PORT);
|
|
|
+ Client cli(HOST, port);
|
|
|
auto res = cli.Get("/cookies");
|
|
|
ASSERT_TRUE(res);
|
|
|
EXPECT_EQ(2U, res->get_header_value_count("Set-Cookie"));
|