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 <string.h> | |
19 | |
20 #include <vector> | |
21 | |
22 #include "base/logging.h" | |
23 #include "base/memory/scoped_ptr.h" | |
24 #include "base/strings/stringprintf.h" | |
25 #include "build/build_config.h" | |
26 #include "gtest/gtest.h" | |
27 #include "util/file/fd_io.h" | |
28 #include "util/net/http_body.h" | |
29 #include "util/net/http_headers.h" | |
30 #include "util/net/http_multipart_builder.h" | |
31 #include "util/test/multiprocess_exec.h" | |
32 | |
33 namespace crashpad { | |
34 namespace test { | |
35 namespace { | |
36 | |
37 class HTTPTransportTestFixture : public MultiprocessExec { | |
38 public: | |
39 using RequestValidator = | |
40 void(*)(HTTPTransportTestFixture*, const std::string&); | |
41 | |
42 HTTPTransportTestFixture(const HTTPHeaders& headers, | |
43 scoped_ptr<HTTPBodyStream> body_stream, | |
44 uint32_t http_response_code, | |
45 RequestValidator request_validator) | |
46 : MultiprocessExec(), | |
47 headers_(headers), | |
48 body_stream_(body_stream.Pass()), | |
49 response_code_(http_response_code), | |
50 request_validator_(request_validator) { | |
51 // TODO(rsesek): Use a more robust mechanism to locate testdata | |
52 // <https://code.google.com/p/crashpad/issues/detail?id=4>. | |
53 SetChildCommand("util/net/http_transport_test_server.py", nullptr); | |
54 } | |
55 | |
56 const HTTPHeaders& headers() { return headers_; } | |
57 | |
58 private: | |
59 void MultiprocessParent() override { | |
60 // The child will write the HTTP server port number as a packed int to | |
Mark Mentovai
2014/10/31 15:13:36
The comment should reflect the changed type.
| |
61 // stdout. | |
62 uint32_t port; | |
63 CheckedReadFD(ReadPipeFD(), &port, sizeof(port)); | |
64 | |
65 // Then the parent will tell the web server what response code to send | |
66 // for the HTTP request. | |
67 CheckedWriteFD(WritePipeFD(), &response_code_, sizeof(response_code_)); | |
68 | |
69 // Now execute the HTTP request. | |
70 scoped_ptr<HTTPTransport> transport(HTTPTransport::Create()); | |
71 transport->SetMethod("POST"); | |
72 transport->SetURL(base::StringPrintf("http://127.0.0.1:%d/upload", port)); | |
73 for (const auto& pair : headers_) { | |
74 transport->SetHeader(pair.first, pair.second); | |
75 } | |
76 transport->SetBodyStream(body_stream_.Pass()); | |
77 | |
78 EXPECT_EQ(transport->ExecuteSynchronously(), (response_code_ == 200)); | |
79 | |
80 // Read until the child's stdout closes. | |
81 std::string request; | |
82 char buf[32]; | |
83 ssize_t bytes_read; | |
84 while ((bytes_read = ReadFD(ReadPipeFD(), buf, sizeof(buf))) != 0) { | |
85 ASSERT_GE(bytes_read, 0); | |
86 request.append(buf, bytes_read); | |
87 } | |
88 | |
89 if (request_validator_) | |
90 request_validator_(this, request); | |
91 } | |
92 | |
93 HTTPHeaders headers_; | |
94 scoped_ptr<HTTPBodyStream> body_stream_; | |
95 uint32_t response_code_; | |
96 RequestValidator request_validator_; | |
97 }; | |
98 | |
99 const char kMultipartFormData[] = "multipart/form-data"; | |
100 | |
101 void GetHeaderField(const std::string& request, | |
102 const std::string& header, | |
103 std::string* value) { | |
104 size_t index = request.find(header); | |
105 ASSERT_NE(std::string::npos, index); | |
106 // Since the header is never the first line of the request, it should always | |
107 // be preceded by a CRLF. | |
108 EXPECT_EQ('\n', request[index - 1]); | |
109 EXPECT_EQ('\r', request[index - 2]); | |
110 | |
111 index += header.length(); | |
112 EXPECT_EQ(':', request[index++]); | |
113 // Per RFC 7230 ยง3.2, there can be one or more spaces or horizontal tabs. | |
114 // For testing purposes, just assume one space. | |
115 EXPECT_EQ(' ', request[index++]); | |
116 | |
117 size_t header_end = request.find('\r', index); | |
118 ASSERT_NE(std::string::npos, header_end); | |
119 | |
120 *value = request.substr(index, header_end - index); | |
121 } | |
122 | |
123 void GetMultipartBoundary(const std::string& request, | |
124 std::string* multipart_boundary) { | |
125 std::string content_type; | |
126 GetHeaderField(request, kContentType, &content_type); | |
127 | |
128 ASSERT_GE(content_type.length(), strlen(kMultipartFormData)); | |
129 size_t index = strlen(kMultipartFormData); | |
130 EXPECT_EQ(kMultipartFormData, content_type.substr(0, index)); | |
131 | |
132 EXPECT_EQ(';', content_type[index++]); | |
133 | |
134 size_t boundary_begin = content_type.find('=', index); | |
135 ASSERT_NE(std::string::npos, boundary_begin); | |
136 EXPECT_EQ('=', content_type[boundary_begin++]); | |
137 if (multipart_boundary) { | |
138 *multipart_boundary = content_type.substr(boundary_begin); | |
139 } | |
140 } | |
141 | |
142 const char kBoundaryEq[] = "boundary="; | |
143 | |
144 void ValidFormData(HTTPTransportTestFixture* fixture, | |
145 const std::string& request) { | |
146 std::string actual_boundary; | |
147 GetMultipartBoundary(request, &actual_boundary); | |
148 | |
149 const auto& content_type = fixture->headers().find(kContentType); | |
150 ASSERT_NE(fixture->headers().end(), content_type); | |
151 | |
152 size_t boundary = content_type->second.find(kBoundaryEq); | |
153 ASSERT_NE(std::string::npos, boundary); | |
154 std::string expected_boundary = | |
155 content_type->second.substr(boundary + strlen(kBoundaryEq)); | |
156 EXPECT_EQ(expected_boundary, actual_boundary); | |
157 | |
158 size_t body_start = request.find("\r\n\r\n"); | |
159 ASSERT_NE(std::string::npos, body_start); | |
160 body_start += 4; | |
161 | |
162 std::string expected = "--" + expected_boundary + "\r\n"; | |
163 expected += "Content-Disposition: form-data; name=\"key1\"\r\n\r\n"; | |
164 expected += "test\r\n"; | |
165 ASSERT_LT(body_start + expected.length(), request.length()); | |
166 EXPECT_EQ(expected, request.substr(body_start, expected.length())); | |
167 | |
168 body_start += expected.length(); | |
169 | |
170 expected = "--" + expected_boundary + "\r\n"; | |
171 expected += "Content-Disposition: form-data; name=\"key2\"\r\n\r\n"; | |
172 expected += "--abcdefg123\r\n"; | |
173 expected += "--" + expected_boundary + "--\r\n"; | |
174 ASSERT_EQ(body_start + expected.length(), request.length()); | |
175 EXPECT_EQ(expected, request.substr(body_start)); | |
176 } | |
177 | |
178 TEST(HTTPTransport, ValidFormData) { | |
179 HTTPMultipartBuilder builder; | |
180 builder.SetFormData("key1", "test"); | |
181 builder.SetFormData("key2", "--abcdefg123"); | |
182 | |
183 HTTPHeaders headers; | |
184 headers.insert(builder.GetContentType()); | |
185 | |
186 HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200, | |
187 &ValidFormData); | |
188 test.Run(); | |
189 } | |
190 | |
191 const char kTextPlain[] = "text/plain"; | |
192 | |
193 void ErrorResponse(HTTPTransportTestFixture* fixture, | |
194 const std::string& request) { | |
195 std::string content_type; | |
196 GetHeaderField(request, kContentType, &content_type); | |
197 EXPECT_EQ(kTextPlain, content_type); | |
198 } | |
199 | |
200 TEST(HTTPTransport, ErrorResponse) { | |
201 HTTPMultipartBuilder builder; | |
202 HTTPHeaders headers; | |
203 headers[kContentType] = kTextPlain; | |
204 HTTPTransportTestFixture test(headers, builder.GetBodyStream(), | |
205 404, &ErrorResponse); | |
206 test.Run(); | |
207 } | |
208 | |
209 const char kTextBody[] = "hello world"; | |
210 | |
211 void UnchunkedPlainText(HTTPTransportTestFixture* fixture, | |
212 const std::string& request) { | |
213 std::string header_value; | |
214 GetHeaderField(request, kContentType, &header_value); | |
215 EXPECT_EQ(kTextPlain, header_value); | |
216 | |
217 GetHeaderField(request, kContentLength, &header_value); | |
218 const auto& content_length = fixture->headers().find(kContentLength); | |
219 ASSERT_NE(fixture->headers().end(), content_length); | |
220 EXPECT_EQ(content_length->second, header_value); | |
221 | |
222 size_t body_start = request.rfind("\r\n"); | |
223 ASSERT_NE(std::string::npos, body_start); | |
224 | |
225 EXPECT_EQ(kTextBody, request.substr(body_start + 2)); | |
226 } | |
227 | |
228 TEST(HTTPTransport, UnchunkedPlainText) { | |
229 scoped_ptr<HTTPBodyStream> body_stream(new StringHTTPBodyStream(kTextBody)); | |
230 | |
231 HTTPHeaders headers; | |
232 headers[kContentType] = kTextPlain; | |
233 headers[kContentLength] = base::StringPrintf("%zu", strlen(kTextBody)); | |
234 | |
235 HTTPTransportTestFixture test(headers, body_stream.Pass(), 200, | |
236 &UnchunkedPlainText); | |
237 test.Run(); | |
238 } | |
239 | |
240 } // namespace | |
241 } // namespace test | |
242 } // namespace crashpad | |
OLD | NEW |