| 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 <stdint.h> |
| 18 #include <stdlib.h> |
| 19 #include <string.h> |
| 20 |
| 21 #include <vector> |
| 22 |
| 23 #include "base/files/file_path.h" |
| 24 #include "base/format_macros.h" |
| 25 #include "base/logging.h" |
| 26 #include "base/memory/scoped_ptr.h" |
| 27 #include "base/strings/stringprintf.h" |
| 28 #include "base/strings/utf_string_conversions.h" |
| 29 #include "build/build_config.h" |
| 30 #include "gtest/gtest.h" |
| 31 #include "test/multiprocess_exec.h" |
| 32 #include "test/paths.h" |
| 33 #include "util/file/file_io.h" |
| 34 #include "util/stdlib/move.h" |
| 35 #include "util/misc/random_string.h" |
| 36 #include "util/net/http_body.h" |
| 37 #include "util/net/http_headers.h" |
| 38 #include "util/net/http_multipart_builder.h" |
| 39 |
| 40 namespace crashpad { |
| 41 namespace test { |
| 42 namespace { |
| 43 |
| 44 class HTTPTransportTestFixture : public MultiprocessExec { |
| 45 public: |
| 46 using RequestValidator = |
| 47 void(*)(HTTPTransportTestFixture*, const std::string&); |
| 48 |
| 49 HTTPTransportTestFixture(const HTTPHeaders& headers, |
| 50 scoped_ptr<HTTPBodyStream> body_stream, |
| 51 uint16_t http_response_code, |
| 52 RequestValidator request_validator) |
| 53 : MultiprocessExec(), |
| 54 headers_(headers), |
| 55 body_stream_(crashpad::move(body_stream)), |
| 56 response_code_(http_response_code), |
| 57 request_validator_(request_validator) { |
| 58 base::FilePath server_path = Paths::TestDataRoot().Append( |
| 59 FILE_PATH_LITERAL("util/net/http_transport_test_server.py")); |
| 60 #if defined(OS_POSIX) |
| 61 SetChildCommand(server_path.value(), nullptr); |
| 62 #elif defined(OS_WIN) |
| 63 // Explicitly invoke a shell and python so that python can be found in the |
| 64 // path, and run the test script. |
| 65 std::vector<std::string> args; |
| 66 args.push_back("/c"); |
| 67 args.push_back("python"); |
| 68 args.push_back(base::UTF16ToUTF8(server_path.value())); |
| 69 SetChildCommand(getenv("COMSPEC"), &args); |
| 70 #endif // OS_POSIX |
| 71 } |
| 72 |
| 73 const HTTPHeaders& headers() { return headers_; } |
| 74 |
| 75 private: |
| 76 void MultiprocessParent() override { |
| 77 // Use Logging*File() instead of Checked*File() so that the test can fail |
| 78 // gracefully with a gtest assertion if the child does not execute properly. |
| 79 |
| 80 // The child will write the HTTP server port number as a packed unsigned |
| 81 // short to stdout. |
| 82 uint16_t port; |
| 83 ASSERT_TRUE(LoggingReadFile(ReadPipeHandle(), &port, sizeof(port))); |
| 84 |
| 85 // Then the parent will tell the web server what response code to send |
| 86 // for the HTTP request. |
| 87 ASSERT_TRUE(LoggingWriteFile( |
| 88 WritePipeHandle(), &response_code_, sizeof(response_code_))); |
| 89 |
| 90 // The parent will also tell the web server what response body to send back. |
| 91 // The web server will only send the response body if the response code is |
| 92 // 200. |
| 93 const std::string random_string = RandomString(); |
| 94 |
| 95 ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), |
| 96 random_string.c_str(), |
| 97 random_string.size())); |
| 98 |
| 99 // Now execute the HTTP request. |
| 100 scoped_ptr<HTTPTransport> transport(HTTPTransport::Create()); |
| 101 transport->SetMethod("POST"); |
| 102 transport->SetURL(base::StringPrintf("http://127.0.0.1:%d/upload", port)); |
| 103 for (const auto& pair : headers_) { |
| 104 transport->SetHeader(pair.first, pair.second); |
| 105 } |
| 106 transport->SetBodyStream(crashpad::move(body_stream_)); |
| 107 |
| 108 std::string response_body; |
| 109 bool success = transport->ExecuteSynchronously(&response_body); |
| 110 if (response_code_ == 200) { |
| 111 EXPECT_TRUE(success); |
| 112 std::string expect_response_body = random_string + "\r\n"; |
| 113 EXPECT_EQ(expect_response_body, response_body); |
| 114 } else { |
| 115 EXPECT_FALSE(success); |
| 116 EXPECT_TRUE(response_body.empty()); |
| 117 } |
| 118 |
| 119 // Read until the child's stdout closes. |
| 120 std::string request; |
| 121 char buf[32]; |
| 122 FileOperationResult bytes_read; |
| 123 while ((bytes_read = ReadFile(ReadPipeHandle(), buf, sizeof(buf))) != 0) { |
| 124 ASSERT_GE(bytes_read, 0); |
| 125 request.append(buf, bytes_read); |
| 126 } |
| 127 |
| 128 if (request_validator_) |
| 129 request_validator_(this, request); |
| 130 } |
| 131 |
| 132 HTTPHeaders headers_; |
| 133 scoped_ptr<HTTPBodyStream> body_stream_; |
| 134 uint16_t response_code_; |
| 135 RequestValidator request_validator_; |
| 136 }; |
| 137 |
| 138 const char kMultipartFormData[] = "multipart/form-data"; |
| 139 |
| 140 void GetHeaderField(const std::string& request, |
| 141 const std::string& header, |
| 142 std::string* value) { |
| 143 size_t index = request.find(header); |
| 144 ASSERT_NE(std::string::npos, index); |
| 145 // Since the header is never the first line of the request, it should always |
| 146 // be preceded by a CRLF. |
| 147 EXPECT_EQ('\n', request[index - 1]); |
| 148 EXPECT_EQ('\r', request[index - 2]); |
| 149 |
| 150 index += header.length(); |
| 151 EXPECT_EQ(':', request[index++]); |
| 152 // Per RFC 7230 §3.2, there can be one or more spaces or horizontal tabs. |
| 153 // For testing purposes, just assume one space. |
| 154 EXPECT_EQ(' ', request[index++]); |
| 155 |
| 156 size_t header_end = request.find('\r', index); |
| 157 ASSERT_NE(std::string::npos, header_end); |
| 158 |
| 159 *value = request.substr(index, header_end - index); |
| 160 } |
| 161 |
| 162 void GetMultipartBoundary(const std::string& request, |
| 163 std::string* multipart_boundary) { |
| 164 std::string content_type; |
| 165 GetHeaderField(request, kContentType, &content_type); |
| 166 |
| 167 ASSERT_GE(content_type.length(), strlen(kMultipartFormData)); |
| 168 size_t index = strlen(kMultipartFormData); |
| 169 EXPECT_EQ(kMultipartFormData, content_type.substr(0, index)); |
| 170 |
| 171 EXPECT_EQ(';', content_type[index++]); |
| 172 |
| 173 size_t boundary_begin = content_type.find('=', index); |
| 174 ASSERT_NE(std::string::npos, boundary_begin); |
| 175 EXPECT_EQ('=', content_type[boundary_begin++]); |
| 176 if (multipart_boundary) { |
| 177 *multipart_boundary = content_type.substr(boundary_begin); |
| 178 } |
| 179 } |
| 180 |
| 181 const char kBoundaryEq[] = "boundary="; |
| 182 |
| 183 void ValidFormData(HTTPTransportTestFixture* fixture, |
| 184 const std::string& request) { |
| 185 std::string actual_boundary; |
| 186 GetMultipartBoundary(request, &actual_boundary); |
| 187 |
| 188 const auto& content_type = fixture->headers().find(kContentType); |
| 189 ASSERT_NE(fixture->headers().end(), content_type); |
| 190 |
| 191 size_t boundary = content_type->second.find(kBoundaryEq); |
| 192 ASSERT_NE(std::string::npos, boundary); |
| 193 std::string expected_boundary = |
| 194 content_type->second.substr(boundary + strlen(kBoundaryEq)); |
| 195 EXPECT_EQ(expected_boundary, actual_boundary); |
| 196 |
| 197 size_t body_start = request.find("\r\n\r\n"); |
| 198 ASSERT_NE(std::string::npos, body_start); |
| 199 body_start += 4; |
| 200 |
| 201 std::string expected = "--" + expected_boundary + "\r\n"; |
| 202 expected += "Content-Disposition: form-data; name=\"key1\"\r\n\r\n"; |
| 203 expected += "test\r\n"; |
| 204 ASSERT_LT(body_start + expected.length(), request.length()); |
| 205 EXPECT_EQ(expected, request.substr(body_start, expected.length())); |
| 206 |
| 207 body_start += expected.length(); |
| 208 |
| 209 expected = "--" + expected_boundary + "\r\n"; |
| 210 expected += "Content-Disposition: form-data; name=\"key2\"\r\n\r\n"; |
| 211 expected += "--abcdefg123\r\n"; |
| 212 expected += "--" + expected_boundary + "--\r\n"; |
| 213 ASSERT_EQ(body_start + expected.length(), request.length()); |
| 214 EXPECT_EQ(expected, request.substr(body_start)); |
| 215 } |
| 216 |
| 217 TEST(HTTPTransport, ValidFormData) { |
| 218 HTTPMultipartBuilder builder; |
| 219 builder.SetFormData("key1", "test"); |
| 220 builder.SetFormData("key2", "--abcdefg123"); |
| 221 |
| 222 HTTPHeaders headers; |
| 223 EXPECT_TRUE(headers.insert(builder.GetContentType()).second); |
| 224 |
| 225 HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200, |
| 226 &ValidFormData); |
| 227 test.Run(); |
| 228 } |
| 229 |
| 230 const char kTextPlain[] = "text/plain"; |
| 231 |
| 232 void ErrorResponse(HTTPTransportTestFixture* fixture, |
| 233 const std::string& request) { |
| 234 std::string content_type; |
| 235 GetHeaderField(request, kContentType, &content_type); |
| 236 EXPECT_EQ(kTextPlain, content_type); |
| 237 } |
| 238 |
| 239 TEST(HTTPTransport, ErrorResponse) { |
| 240 HTTPMultipartBuilder builder; |
| 241 HTTPHeaders headers; |
| 242 headers[kContentType] = kTextPlain; |
| 243 HTTPTransportTestFixture test(headers, builder.GetBodyStream(), |
| 244 404, &ErrorResponse); |
| 245 test.Run(); |
| 246 } |
| 247 |
| 248 const char kTextBody[] = "hello world"; |
| 249 |
| 250 void UnchunkedPlainText(HTTPTransportTestFixture* fixture, |
| 251 const std::string& request) { |
| 252 std::string header_value; |
| 253 GetHeaderField(request, kContentType, &header_value); |
| 254 EXPECT_EQ(kTextPlain, header_value); |
| 255 |
| 256 GetHeaderField(request, kContentLength, &header_value); |
| 257 const auto& content_length = fixture->headers().find(kContentLength); |
| 258 ASSERT_NE(fixture->headers().end(), content_length); |
| 259 EXPECT_EQ(content_length->second, header_value); |
| 260 |
| 261 size_t body_start = request.rfind("\r\n"); |
| 262 ASSERT_NE(std::string::npos, body_start); |
| 263 |
| 264 EXPECT_EQ(kTextBody, request.substr(body_start + 2)); |
| 265 } |
| 266 |
| 267 TEST(HTTPTransport, UnchunkedPlainText) { |
| 268 scoped_ptr<HTTPBodyStream> body_stream(new StringHTTPBodyStream(kTextBody)); |
| 269 |
| 270 HTTPHeaders headers; |
| 271 headers[kContentType] = kTextPlain; |
| 272 headers[kContentLength] = base::StringPrintf("%" PRIuS, strlen(kTextBody)); |
| 273 |
| 274 HTTPTransportTestFixture test(headers, crashpad::move(body_stream), 200, |
| 275 &UnchunkedPlainText); |
| 276 test.Run(); |
| 277 } |
| 278 |
| 279 void RunUpload33k(bool has_content_length) { |
| 280 // On OS X, NSMutableURLRequest winds up calling into a CFReadStream’s Read() |
| 281 // callback with a 32kB buffer. Make sure that it’s able to get everything |
| 282 // when enough is available to fill this buffer, requiring more than one |
| 283 // Read(). |
| 284 |
| 285 std::string request_string(33 * 1024, 'a'); |
| 286 scoped_ptr<HTTPBodyStream> body_stream( |
| 287 new StringHTTPBodyStream(request_string)); |
| 288 |
| 289 HTTPHeaders headers; |
| 290 headers[kContentType] = "application/octet-stream"; |
| 291 if (has_content_length) { |
| 292 headers[kContentLength] = |
| 293 base::StringPrintf("%" PRIuS, request_string.size()); |
| 294 } |
| 295 HTTPTransportTestFixture test(headers, crashpad::move(body_stream), 200, |
| 296 [](HTTPTransportTestFixture* fixture, const std::string& request) { |
| 297 size_t body_start = request.rfind("\r\n"); |
| 298 EXPECT_EQ(33 * 1024u + 2, request.size() - body_start); |
| 299 }); |
| 300 test.Run(); |
| 301 } |
| 302 |
| 303 TEST(HTTPTransport, Upload33k) { |
| 304 RunUpload33k(true); |
| 305 } |
| 306 |
| 307 TEST(HTTPTransport, Upload33k_LengthUnknown) { |
| 308 // The same as Upload33k, but without declaring Content-Length ahead of time. |
| 309 RunUpload33k(false); |
| 310 } |
| 311 |
| 312 } // namespace |
| 313 } // namespace test |
| 314 } // namespace crashpad |
| OLD | NEW |