1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/url_request/url_request_test_job.h"
11
12 #include <algorithm>
13 #include <list>
14 #include <memory>
15
16 #include "base/compiler_specific.h"
17 #include "base/functional/bind.h"
18 #include "base/lazy_instance.h"
19 #include "base/location.h"
20 #include "base/strings/string_util.h"
21 #include "base/task/single_thread_task_runner.h"
22 #include "base/time/time.h"
23 #include "net/base/io_buffer.h"
24 #include "net/base/net_errors.h"
25 #include "net/http/http_response_headers.h"
26 #include "net/http/http_util.h"
27
28 namespace net {
29
30 namespace {
31
32 typedef std::list<URLRequestTestJob*> URLRequestJobList;
33 base::LazyInstance<URLRequestJobList>::Leaky
34 g_pending_jobs = LAZY_INSTANCE_INITIALIZER;
35
36 } // namespace
37
38 // static getters for known URLs
test_url_1()39 GURL URLRequestTestJob::test_url_1() {
40 return GURL("test:url1");
41 }
42
test_url_2()43 GURL URLRequestTestJob::test_url_2() {
44 return GURL("test:url2");
45 }
46
test_url_3()47 GURL URLRequestTestJob::test_url_3() {
48 return GURL("test:url3");
49 }
50
test_url_4()51 GURL URLRequestTestJob::test_url_4() {
52 return GURL("test:url4");
53 }
54
test_url_auto_advance_async_reads_1()55 GURL URLRequestTestJob::test_url_auto_advance_async_reads_1() {
56 return GURL("test:url_auto_advance_async_reads_1");
57 }
58
test_url_error()59 GURL URLRequestTestJob::test_url_error() {
60 return GURL("test:error");
61 }
62
test_url_redirect_to_url_1()63 GURL URLRequestTestJob::test_url_redirect_to_url_1() {
64 return GURL("test:redirect_to_1");
65 }
66
test_url_redirect_to_url_2()67 GURL URLRequestTestJob::test_url_redirect_to_url_2() {
68 return GURL("test:redirect_to_2");
69 }
70
71 // static getters for known URL responses
test_data_1()72 std::string URLRequestTestJob::test_data_1() {
73 return std::string("<html><title>Test One</title></html>");
74 }
test_data_2()75 std::string URLRequestTestJob::test_data_2() {
76 return std::string("<html><title>Test Two Two</title></html>");
77 }
test_data_3()78 std::string URLRequestTestJob::test_data_3() {
79 return std::string("<html><title>Test Three Three Three</title></html>");
80 }
test_data_4()81 std::string URLRequestTestJob::test_data_4() {
82 return std::string("<html><title>Test Four Four Four Four</title></html>");
83 }
84
85 // static getter for simple response headers
test_headers()86 std::string URLRequestTestJob::test_headers() {
87 static const char kHeaders[] =
88 "HTTP/1.1 200 OK\n"
89 "Content-type: text/html\n"
90 "\n";
91 return std::string(kHeaders, std::size(kHeaders));
92 }
93
94 // static getter for redirect response headers
test_redirect_headers()95 std::string URLRequestTestJob::test_redirect_headers() {
96 static const char kHeaders[] =
97 "HTTP/1.1 302 MOVED\n"
98 "Location: somewhere\n"
99 "\n";
100 return std::string(kHeaders, std::size(kHeaders));
101 }
102
103 // static getter for redirect response headers
test_redirect_to_url_1_headers()104 std::string URLRequestTestJob::test_redirect_to_url_1_headers() {
105 std::string headers = "HTTP/1.1 302 MOVED";
106 headers.push_back('\n');
107 headers += "Location: ";
108 headers += test_url_1().spec();
109 headers.push_back('\n');
110 headers.push_back('\n');
111 return headers;
112 }
113
114 // static getter for redirect response headers
test_redirect_to_url_2_headers()115 std::string URLRequestTestJob::test_redirect_to_url_2_headers() {
116 std::string headers = "HTTP/1.1 302 MOVED";
117 headers.push_back('\n');
118 headers += "Location: ";
119 headers += test_url_2().spec();
120 headers.push_back('\n');
121 headers.push_back('\n');
122 return headers;
123 }
124
125 // static getter for error response headers
test_error_headers()126 std::string URLRequestTestJob::test_error_headers() {
127 static const char kHeaders[] =
128 "HTTP/1.1 500 BOO HOO\n"
129 "\n";
130 return std::string(kHeaders, std::size(kHeaders));
131 }
132
URLRequestTestJob(URLRequest * request,bool auto_advance)133 URLRequestTestJob::URLRequestTestJob(URLRequest* request, bool auto_advance)
134 : URLRequestJob(request),
135 auto_advance_(auto_advance),
136 response_headers_length_(0) {}
137
URLRequestTestJob(URLRequest * request,const std::string & response_headers,const std::string & response_data,bool auto_advance)138 URLRequestTestJob::URLRequestTestJob(URLRequest* request,
139 const std::string& response_headers,
140 const std::string& response_data,
141 bool auto_advance)
142 : URLRequestJob(request),
143 auto_advance_(auto_advance),
144 response_data_(response_data),
145 response_headers_(base::MakeRefCounted<net::HttpResponseHeaders>(
146 net::HttpUtil::AssembleRawHeaders(response_headers))),
147 response_headers_length_(response_headers.size()) {}
148
~URLRequestTestJob()149 URLRequestTestJob::~URLRequestTestJob() {
150 std::erase(g_pending_jobs.Get(), this);
151 }
152
GetMimeType(std::string * mime_type) const153 bool URLRequestTestJob::GetMimeType(std::string* mime_type) const {
154 DCHECK(mime_type);
155 if (!response_headers_.get())
156 return false;
157 return response_headers_->GetMimeType(mime_type);
158 }
159
SetPriority(RequestPriority priority)160 void URLRequestTestJob::SetPriority(RequestPriority priority) {
161 priority_ = priority;
162 }
163
Start()164 void URLRequestTestJob::Start() {
165 // Start reading asynchronously so that all error reporting and data
166 // callbacks happen as they would for network requests.
167 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
168 FROM_HERE, base::BindOnce(&URLRequestTestJob::StartAsync,
169 weak_factory_.GetWeakPtr()));
170 }
171
StartAsync()172 void URLRequestTestJob::StartAsync() {
173 if (!response_headers_.get()) {
174 SetResponseHeaders(test_headers());
175 if (request_->url() == test_url_1()) {
176 response_data_ = test_data_1();
177 stage_ = DATA_AVAILABLE; // Simulate a synchronous response for this one.
178 } else if (request_->url() == test_url_2()) {
179 response_data_ = test_data_2();
180 } else if (request_->url() == test_url_3()) {
181 response_data_ = test_data_3();
182 } else if (request_->url() == test_url_4()) {
183 response_data_ = test_data_4();
184 } else if (request_->url() == test_url_auto_advance_async_reads_1()) {
185 response_data_ = test_data_1();
186 stage_ = DATA_AVAILABLE; // Data is available immediately.
187 async_reads_ = true; // All reads complete asynchronously.
188 } else if (request_->url() == test_url_redirect_to_url_1()) {
189 SetResponseHeaders(test_redirect_to_url_1_headers());
190 } else if (request_->url() == test_url_redirect_to_url_2()) {
191 SetResponseHeaders(test_redirect_to_url_2_headers());
192 } else {
193 AdvanceJob();
194
195 // Return an error on unexpected urls.
196 NotifyStartError(ERR_INVALID_URL);
197 return;
198 }
199 }
200
201 AdvanceJob();
202
203 this->NotifyHeadersComplete();
204 }
205
SetResponseHeaders(const std::string & response_headers)206 void URLRequestTestJob::SetResponseHeaders(
207 const std::string& response_headers) {
208 response_headers_ = base::MakeRefCounted<HttpResponseHeaders>(
209 net::HttpUtil::AssembleRawHeaders(response_headers));
210 response_headers_length_ = response_headers.size();
211 }
212
CopyDataForRead(IOBuffer * buf,int buf_size)213 int URLRequestTestJob::CopyDataForRead(IOBuffer* buf, int buf_size) {
214 int bytes_read = 0;
215 if (offset_ < static_cast<int>(response_data_.length())) {
216 bytes_read = buf_size;
217 if (bytes_read + offset_ > static_cast<int>(response_data_.length()))
218 bytes_read = static_cast<int>(response_data_.length()) - offset_;
219
220 memcpy(buf->data(), &response_data_.c_str()[offset_], bytes_read);
221 offset_ += bytes_read;
222 }
223 return bytes_read;
224 }
225
ReadRawData(IOBuffer * buf,int buf_size)226 int URLRequestTestJob::ReadRawData(IOBuffer* buf, int buf_size) {
227 if (stage_ == WAITING || async_reads_) {
228 async_buf_ = buf;
229 async_buf_size_ = buf_size;
230 if (stage_ != WAITING) {
231 stage_ = WAITING;
232 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
233 FROM_HERE, base::BindOnce(&URLRequestTestJob::ProcessNextOperation,
234 weak_factory_.GetWeakPtr()));
235 }
236 return ERR_IO_PENDING;
237 }
238
239 return CopyDataForRead(buf, buf_size);
240 }
241
GetResponseInfo(HttpResponseInfo * info)242 void URLRequestTestJob::GetResponseInfo(HttpResponseInfo* info) {
243 if (response_headers_.get())
244 info->headers = response_headers_;
245 }
246
GetLoadTimingInfo(LoadTimingInfo * load_timing_info) const247 void URLRequestTestJob::GetLoadTimingInfo(
248 LoadTimingInfo* load_timing_info) const {
249 // Preserve the times the URLRequest is responsible for, but overwrite all
250 // the others.
251 base::TimeTicks request_start = load_timing_info->request_start;
252 base::Time request_start_time = load_timing_info->request_start_time;
253 *load_timing_info = load_timing_info_;
254 load_timing_info->request_start = request_start;
255 load_timing_info->request_start_time = request_start_time;
256 }
257
GetTotalReceivedBytes() const258 int64_t URLRequestTestJob::GetTotalReceivedBytes() const {
259 return response_headers_length_ + offset_;
260 }
261
IsRedirectResponse(GURL * location,int * http_status_code,bool * insecure_scheme_was_upgraded)262 bool URLRequestTestJob::IsRedirectResponse(GURL* location,
263 int* http_status_code,
264 bool* insecure_scheme_was_upgraded) {
265 if (!response_headers_.get())
266 return false;
267
268 std::string value;
269 if (!response_headers_->IsRedirect(&value))
270 return false;
271
272 *insecure_scheme_was_upgraded = false;
273 *location = request_->url().Resolve(value);
274 *http_status_code = response_headers_->response_code();
275 return true;
276 }
277
Kill()278 void URLRequestTestJob::Kill() {
279 stage_ = DONE;
280 URLRequestJob::Kill();
281 weak_factory_.InvalidateWeakPtrs();
282 std::erase(g_pending_jobs.Get(), this);
283 }
284
ProcessNextOperation()285 void URLRequestTestJob::ProcessNextOperation() {
286 switch (stage_) {
287 case WAITING:
288 // Must call AdvanceJob() prior to NotifyReadComplete() since that may
289 // delete |this|.
290 AdvanceJob();
291 stage_ = DATA_AVAILABLE;
292 // OK if ReadRawData wasn't called yet.
293 if (async_buf_) {
294 int result = CopyDataForRead(async_buf_.get(), async_buf_size_);
295 if (result < 0) {
296 NOTREACHED() << "Reads should not fail in DATA_AVAILABLE.";
297 }
298 if (NextReadAsync()) {
299 // Make all future reads return io pending until the next
300 // ProcessNextOperation().
301 stage_ = WAITING;
302 }
303 ReadRawDataComplete(result);
304 }
305 break;
306 case DATA_AVAILABLE:
307 AdvanceJob();
308 stage_ = ALL_DATA; // done sending data
309 break;
310 case ALL_DATA:
311 stage_ = DONE;
312 return;
313 case DONE:
314 return;
315 default:
316 NOTREACHED() << "Invalid stage";
317 }
318 }
319
NextReadAsync()320 bool URLRequestTestJob::NextReadAsync() {
321 return false;
322 }
323
AdvanceJob()324 void URLRequestTestJob::AdvanceJob() {
325 if (auto_advance_) {
326 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
327 FROM_HERE, base::BindOnce(&URLRequestTestJob::ProcessNextOperation,
328 weak_factory_.GetWeakPtr()));
329 return;
330 }
331 g_pending_jobs.Get().push_back(this);
332 }
333
334 // static
ProcessOnePendingMessage()335 bool URLRequestTestJob::ProcessOnePendingMessage() {
336 if (g_pending_jobs.Get().empty())
337 return false;
338
339 URLRequestTestJob* next_job(g_pending_jobs.Get().front());
340 g_pending_jobs.Get().pop_front();
341
342 DCHECK(!next_job->auto_advance()); // auto_advance jobs should be in this q
343 next_job->ProcessNextOperation();
344 return true;
345 }
346
347 } // namespace net
348