Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Crashpad Authors. All rights reserved. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 | |
| 15 #include "util/net/http_transport.h" | |
| 16 | |
| 17 #include <vector> | |
| 18 | |
| 19 #include "base/logging.h" | |
| 20 #include "base/memory/scoped_ptr.h" | |
| 21 #include "base/strings/stringprintf.h" | |
| 22 #include "build/build_config.h" | |
| 23 #include "gtest/gtest.h" | |
| 24 #include "util/file/fd_io.h" | |
| 25 #include "util/net/http_body.h" | |
| 26 #include "util/net/http_headers.h" | |
| 27 #include "util/net/http_multipart_builder.h" | |
| 28 #include "util/test/multiprocess_exec.h" | |
| 29 | |
| 30 #if defined(OS_MACOSX) | |
| 31 #include "util/net/http_transport_mac.h" | |
| 32 #endif | |
| 33 | |
| 34 namespace crashpad { | |
| 35 namespace test { | |
| 36 namespace { | |
| 37 | |
| 38 class HTTPTransportTestFixture : public MultiprocessExec { | |
| 39 public: | |
| 40 using RequestValidator = | |
| 41 void(*)(HTTPTransportTestFixture*, const std::string&); | |
| 42 | |
| 43 HTTPTransportTestFixture(const HTTPHeaders& headers, | |
| 44 scoped_ptr<HTTPBodyStream> body_stream, | |
| 45 int http_response_code, | |
| 46 RequestValidator request_validator) | |
| 47 : MultiprocessExec(), | |
| 48 headers_(headers), | |
| 49 body_stream_(body_stream.Pass()), | |
| 50 response_code_(http_response_code), | |
| 51 request_validator_(request_validator) { | |
| 52 // TODO(rsesek): Use a more robust mechanism to locate testdata | |
| 53 // <https://code.google.com/p/crashpad/issues/detail?id=4>. | |
| 54 SetChildCommand("util/net/http_transport_test_server.py", nullptr); | |
| 55 } | |
| 56 | |
| 57 const HTTPHeaders& headers() { return headers_; } | |
| 58 | |
| 59 private: | |
| 60 void MultiprocessParent() override { | |
| 61 // The child will write the HTTP server port number as a packed int to | |
|
Mark Mentovai
2014/10/30 21:13:55
I’d prefer fixed-size (uint16_t). Fixed-size for r
Robert Sesek
2014/10/31 14:48:15
uint16_t is not enough bits. Dynamic ports go all
Robert Sesek
2014/10/31 15:12:00
Sorry, I was holding Python's struct wrong. uint16
Mark Mentovai
2014/10/31 15:13:36
Robert Sesek wrote:
| |
| 62 // stdout. | |
| 63 int port = -1; | |
|
Mark Mentovai
2014/10/30 21:13:55
No initialization necessary. CheckedReadFD() is gu
Robert Sesek
2014/10/31 14:48:15
Done.
| |
| 64 CheckedReadFD(ReadPipeFD(), &port, sizeof(port)); | |
| 65 ASSERT_NE(-1, port); | |
| 66 | |
| 67 // Then the parent will write the test's HTTP response code. | |
| 68 CheckedWriteFD(WritePipeFD(), &response_code_, sizeof(response_code_)); | |
|
Mark Mentovai
2014/10/30 21:13:55
This wasn’t clear to me when I started reading thr
Robert Sesek
2014/10/31 14:48:15
I thought the above comment covered this, but I ad
| |
| 69 | |
| 70 // Now execute the HTTP request. | |
| 71 scoped_ptr<HTTPTransport> transport(CreateTransport()); | |
| 72 transport->SetMethod("POST"); | |
| 73 transport->SetURL(base::StringPrintf("http://localhost:%d/upload", port)); | |
|
Mark Mentovai
2014/10/30 21:13:55
Since you listen explicitly on 127.0.0.1 in the Py
Robert Sesek
2014/10/31 14:48:15
Done.
| |
| 74 for (const auto& pair : headers_) { | |
| 75 transport->SetHeader(pair.first, pair.second); | |
| 76 } | |
| 77 transport->SetBodyStream(body_stream_.Pass()); | |
| 78 | |
| 79 EXPECT_EQ(transport->ExecuteSynchronously(), (response_code_ == 200)); | |
| 80 | |
| 81 // Read until the child's stdout closes. | |
| 82 std::string request; | |
| 83 char buf[32]; | |
| 84 ssize_t bytes_read; | |
| 85 while ((bytes_read = ReadFD(ReadPipeFD(), buf, sizeof(buf))) != 0) { | |
| 86 ASSERT_GE(bytes_read, 0); | |
| 87 request.append(buf, bytes_read); | |
| 88 } | |
| 89 | |
| 90 if (request_validator_) | |
| 91 request_validator_(this, request); | |
| 92 } | |
| 93 | |
| 94 scoped_ptr<HTTPTransport> CreateTransport() { | |
|
Mark Mentovai
2014/10/30 21:13:55
Instead of doing this here, should we have a stati
Robert Sesek
2014/10/31 14:48:15
I went back on forth for this, but since you broug
| |
| 95 #if defined(OS_MACOSX) | |
| 96 return scoped_ptr<HTTPTransport>(new HTTPTransportMac()); | |
| 97 #else | |
| 98 #error "No HTTP transport available." | |
| 99 #endif | |
| 100 } | |
| 101 | |
| 102 HTTPHeaders headers_; | |
| 103 scoped_ptr<HTTPBodyStream> body_stream_; | |
| 104 int response_code_; | |
| 105 RequestValidator request_validator_; | |
| 106 }; | |
| 107 | |
| 108 const char kMultipartFormData[] = "multipart/form-data"; | |
| 109 | |
| 110 void GetHeaderField(const std::string& request, | |
| 111 const std::string& header, | |
| 112 std::string* value) { | |
| 113 size_t index = request.find(header); | |
| 114 ASSERT_NE(std::string::npos, index); | |
| 115 // Since the header is never the first line of the request, it should always | |
| 116 // be preceded by a CRLF. | |
| 117 EXPECT_EQ('\n', request[index - 1]); | |
| 118 EXPECT_EQ('\r', request[index - 2]); | |
| 119 | |
| 120 index += header.length(); | |
| 121 EXPECT_EQ(':', request[index++]); | |
| 122 // Per RFC 7230 §3.2, there can be one or more spaces or horizontal tabs. | |
| 123 // For testing purposes, just assume one space. | |
| 124 EXPECT_EQ(' ', request[index++]); | |
| 125 | |
| 126 size_t header_end = request.find('\r', index); | |
| 127 ASSERT_NE(std::string::npos, header_end); | |
| 128 | |
| 129 *value = request.substr(index, header_end - index); | |
| 130 } | |
| 131 | |
| 132 void GetMultipartBoundary(const std::string& request, | |
| 133 std::string* multipart_boundary) { | |
| 134 std::string content_type; | |
| 135 GetHeaderField(request, kContentType, &content_type); | |
| 136 | |
| 137 ASSERT_GE(content_type.length(), strlen(kMultipartFormData)); | |
|
Mark Mentovai
2014/10/30 21:13:55
#include <string.h>
Robert Sesek
2014/10/31 14:48:15
Done.
| |
| 138 size_t index = strlen(kMultipartFormData); | |
| 139 EXPECT_EQ(kMultipartFormData, content_type.substr(0, index)); | |
| 140 | |
| 141 EXPECT_EQ(';', content_type[index++]); | |
| 142 | |
| 143 size_t boundary_begin = content_type.find('=', index); | |
| 144 ASSERT_NE(std::string::npos, boundary_begin); | |
| 145 EXPECT_EQ('=', content_type[boundary_begin++]); | |
| 146 if (multipart_boundary) { | |
| 147 *multipart_boundary = content_type.substr(boundary_begin); | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 const char kBoundaryEq[] = "boundary="; | |
| 152 | |
| 153 void ValidFormData(HTTPTransportTestFixture* fixture, | |
| 154 const std::string& request) { | |
| 155 std::string actual_boundary; | |
| 156 GetMultipartBoundary(request, &actual_boundary); | |
| 157 | |
| 158 const auto& content_type = fixture->headers().find(kContentType); | |
| 159 ASSERT_NE(fixture->headers().end(), content_type); | |
| 160 | |
| 161 size_t boundary = content_type->second.find(kBoundaryEq); | |
| 162 ASSERT_NE(std::string::npos, boundary); | |
| 163 std::string expected_boundary = | |
| 164 content_type->second.substr(boundary + strlen(kBoundaryEq)); | |
| 165 EXPECT_EQ(expected_boundary, actual_boundary); | |
| 166 | |
| 167 size_t body_start = request.find("\r\n\r\n"); | |
| 168 ASSERT_NE(std::string::npos, body_start); | |
| 169 body_start += 4; | |
| 170 | |
| 171 std::string expected = "--" + expected_boundary + "\r\n"; | |
| 172 expected += "Content-Disposition: form-data; name=\"key1\"\r\n\r\n"; | |
| 173 expected += "test\r\n"; | |
| 174 ASSERT_LT(body_start + expected.length(), request.length()); | |
| 175 EXPECT_EQ(expected, request.substr(body_start, expected.length())); | |
| 176 | |
| 177 body_start += expected.length(); | |
| 178 | |
| 179 expected = "--" + expected_boundary + "\r\n"; | |
| 180 expected += "Content-Disposition: form-data; name=\"key2\"\r\n\r\n"; | |
| 181 expected += "--abcdefg123\r\n"; | |
| 182 expected += "--" + expected_boundary + "--\r\n"; | |
| 183 ASSERT_EQ(body_start + expected.length(), request.length()); | |
| 184 EXPECT_EQ(expected, request.substr(body_start)); | |
| 185 } | |
| 186 | |
| 187 TEST(HTTPTransport, ValidFormData) { | |
| 188 HTTPMultipartBuilder builder; | |
| 189 builder.SetFormData("key1", "test"); | |
| 190 builder.SetFormData("key2", "--abcdefg123"); | |
| 191 | |
| 192 HTTPHeaders headers; | |
| 193 headers.insert(builder.GetContentType()); | |
| 194 | |
| 195 HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200, | |
| 196 &ValidFormData); | |
| 197 test.Run(); | |
| 198 } | |
| 199 | |
| 200 const char kTextPlain[] = "text/plain"; | |
| 201 | |
| 202 void ErrorResponse(HTTPTransportTestFixture* fixture, | |
| 203 const std::string& request) { | |
| 204 std::string content_type; | |
| 205 GetHeaderField(request, kContentType, &content_type); | |
| 206 EXPECT_EQ(kTextPlain, content_type); | |
| 207 } | |
| 208 | |
| 209 TEST(HTTPTransport, ErrorResponse) { | |
| 210 HTTPMultipartBuilder builder; | |
| 211 HTTPHeaders headers; | |
| 212 headers[kContentType] = kTextPlain; | |
| 213 HTTPTransportTestFixture test(headers, builder.GetBodyStream(), | |
| 214 404, &ErrorResponse); | |
| 215 test.Run(); | |
| 216 } | |
| 217 | |
| 218 const char kTextBody[] = "hello world"; | |
| 219 | |
| 220 void UnchunkedPlainText(HTTPTransportTestFixture* fixture, | |
| 221 const std::string& request) { | |
| 222 std::string header_value; | |
| 223 GetHeaderField(request, kContentType, &header_value); | |
| 224 EXPECT_EQ(kTextPlain, header_value); | |
| 225 | |
| 226 GetHeaderField(request, kContentLength, &header_value); | |
| 227 const auto& content_length = fixture->headers().find(kContentLength); | |
| 228 ASSERT_NE(fixture->headers().end(), content_length); | |
| 229 EXPECT_EQ(content_length->second, header_value); | |
| 230 | |
| 231 size_t body_start = request.rfind("\r\n"); | |
| 232 ASSERT_NE(std::string::npos, body_start); | |
| 233 | |
| 234 EXPECT_EQ(kTextBody, request.substr(body_start + 2)); | |
| 235 } | |
| 236 | |
| 237 TEST(HTTPTransport, UnchunkedPlainText) { | |
| 238 scoped_ptr<HTTPBodyStream> body_stream(new StringHTTPBodyStream(kTextBody)); | |
| 239 | |
| 240 HTTPHeaders headers; | |
| 241 headers[kContentType] = kTextPlain; | |
| 242 headers[kContentLength] = base::StringPrintf("%zu", strlen(kTextBody)); | |
| 243 | |
| 244 HTTPTransportTestFixture test(headers, body_stream.Pass(), 200, | |
| 245 &UnchunkedPlainText); | |
| 246 test.Run(); | |
| 247 } | |
| 248 | |
| 249 } // namespace | |
| 250 } // namespace test | |
| 251 } // namespace crashpad | |
|
Mark Mentovai
2014/10/30 21:13:55
Nothing with a file part? Are you just trusting th
Robert Sesek
2014/10/31 14:48:15
Yes, we should rely on composition of components a
| |
| OLD | NEW |