Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(941)

Side by Side Diff: net/test/embedded_test_server/request_helpers.cc

Issue 1376593007: SSL in EmbeddedTestServer (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Cleaning up request handler. Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/test/embedded_test_server/request_helpers.h"
mmenke 2015/10/15 20:16:15 default_request_handlers?
svaldez 2015/10/15 21:04:32 Since the main purpose of this file when used by o
mmenke 2015/10/15 21:13:54 If you want shareable helper methods for consumers
6
7 #include <stdlib.h>
8 #include <ctime>
9 #include <map>
10 #include <sstream>
11 #include <string>
12
13 #include "base/base64.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/format_macros.h"
17 #include "base/md5.h"
18 #include "base/path_service.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/thread_task_runner_handle.h"
23 #include "base/threading/thread_restrictions.h"
24 #include "base/time/time.h"
25 #include "net/base/escape.h"
26 #include "net/base/url_util.h"
27 #include "net/test/embedded_test_server/embedded_test_server.h"
28 #include "net/test/embedded_test_server/http_request.h"
29 #include "net/test/embedded_test_server/http_response.h"
30
31 #define NOT_HANDLED nullptr
mmenke 2015/10/15 19:38:18 This seems kinda weird and not really needed.
svaldez 2015/10/15 21:04:33 Done.
32
33 const net::UnescapeRule::Type kUnescapeAll =
34 net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS |
35 net::UnescapeRule::SPOOFING_AND_CONTROL_CHARS |
36 net::UnescapeRule::REPLACE_PLUS_WITH_SPACE;
mmenke 2015/10/15 19:38:17 Anonymous namespace, and move this down so you don
svaldez 2015/10/15 21:04:33 Done.
37
38 const std::string kDefaultRealm = "testrealm";
39 const std::string kDefaultPassword = "password";
mmenke 2015/10/15 19:38:17 const char[] (Only POD types can be globals, and S
svaldez 2015/10/15 21:04:34 Done.
40
41 namespace net {
42 namespace test_server {
43
44 void CustomHttpResponse::SendResponse(SendBytesCallback send,
45 SendCompleteCallback done) {
46 std::string response;
47 if (headers_.size() != 0 || contents_.size() != 0)
48 response = headers_ + "\r\n" + contents_;
49 send.Run(response, done);
50 }
51
52 bool ShouldHandle(const HttpRequest& request, std::string url) {
mmenke 2015/10/15 19:38:17 May want to build this into EmbeddedTestServer, as
mmenke 2015/10/15 19:38:18 |url| -> |path_prefix|
svaldez 2015/10/15 21:04:33 Done.
svaldez 2015/10/15 21:04:33 Done.
53 if (request.relative_url.compare(0, url.size(), url) != 0)
54 return false;
55 std::string endings("/?;#");
mmenke 2015/10/15 19:38:17 Not a fan of manually parsing URLs like this. I s
svaldez 2015/10/15 21:04:33 Done.
56 return request.relative_url.size() == url.size() ||
57 endings.find(request.relative_url.at(url.size())) != std::string::npos;
58 }
59
60 RequestQuery ParseQuery(const GURL& url) {
mmenke 2015/10/15 19:38:18 All this stuff that isn't exposed in the header sh
svaldez 2015/10/15 21:04:34 Done.
61 RequestQuery queries;
62
63 for (QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
64 queries[net::UnescapeURLComponent(it.GetKey(), kUnescapeAll)].push_back(
65 it.GetUnescapedValue());
66 }
67 return queries;
68 }
69
70 void GetFilePathWithReplacements(const std::string& original_file_path,
mmenke 2015/10/15 19:38:18 This needs documentation.
svaldez 2015/10/15 21:04:33 In the header.
71 const base::StringPairs& text_to_replace,
72 std::string* replacement_path) {
mmenke 2015/10/15 19:38:17 Should just return a string (It's a little less ef
svaldez 2015/10/15 21:04:32 Maintaining compatibility with the STS version of
73 std::string new_file_path = original_file_path;
74 bool first_query_parameter = true;
75 for (base::StringPairs::const_iterator it = text_to_replace.begin();
76 it != text_to_replace.end(); ++it) {
mmenke 2015/10/15 19:38:18 Can you use a range loop here?
svaldez 2015/10/15 21:04:33 Done.
77 const std::string& old_text = it->first;
78 const std::string& new_text = it->second;
79 std::string base64_old;
80 std::string base64_new;
81 base::Base64Encode(old_text, &base64_old);
82 base::Base64Encode(new_text, &base64_new);
83 if (first_query_parameter) {
84 new_file_path += "?";
85 first_query_parameter = false;
86 } else {
87 new_file_path += "&";
88 }
89 new_file_path += "replace_text=";
90 new_file_path += base64_old;
91 new_file_path += ":";
92 new_file_path += base64_new;
93 }
94
95 *replacement_path = new_file_path;
96 }
97
98 GURL ToGURL(const HttpRequest& request) {
99 return GURL("http://localhost" + request.relative_url);
mmenke 2015/10/15 19:38:17 Suggest making this a member of HttpRequest (And u
svaldez 2015/10/15 21:04:32 Done.
100 }
101
102 // Handles |request| by serving a file from under |server_root|.
103 scoped_ptr<HttpResponse> HandleFileRequest(const base::FilePath& server_root,
104 const HttpRequest& request) {
105 // This is a test-only server. Ignore I/O thread restrictions.
106 base::ThreadRestrictions::ScopedAllowIO allow_io;
mmenke 2015/10/15 19:38:18 Is this needed? We're running on our private thre
svaldez 2015/10/15 21:04:33 Still required since the restriction is only on th
107
108 std::string relative_url(request.relative_url);
109
110 // A proxy request will have an absolute path. Simulate the proxy by stripping
111 // the scheme, host, and port.
112 GURL request_gurl = ToGURL(request);
113 relative_url = request_gurl.path();
114
115 std::string files_prefix("/files/");
mmenke 2015/10/15 19:38:17 I thought you said you weren't doing this?
svaldez 2015/10/15 21:04:33 Yeah, I was using it to find any remaining old-sty
116 if (relative_url.compare(0, files_prefix.size(), files_prefix) == 0) {
117 LOG(ERROR) << "Using old-style /files/ for: " << relative_url;
118 relative_url = relative_url.substr(files_prefix.size() - 1);
119 }
120
121 std::string post_prefix("/post/");
122 if (relative_url.compare(0, post_prefix.size(), post_prefix) == 0) {
123 relative_url = relative_url.substr(post_prefix.size() - 1);
124 if (request.method != METHOD_POST)
125 return NOT_HANDLED;
126 }
127
128 RequestQuery query = ParseQuery(request_gurl);
129
130 if (query.find("expected_body") != query.end()) {
131 if (request.content.find(query["expected_body"].front()) ==
mmenke 2015/10/15 19:38:18 base::StartsWithASCII? I don't think I've ever se
svaldez 2015/10/15 21:04:33 Is there a version for "containsASCII"?
mmenke 2015/10/15 21:13:54 Oops...That comment was meant to go below the "/po
132 std::string::npos) {
133 scoped_ptr<BasicHttpResponse> not_found_response(new BasicHttpResponse);
134 not_found_response->set_code(HTTP_NOT_FOUND);
135 return not_found_response.Pass();
136 }
137 }
138
139 if (query.find("expected_headers") != query.end()) {
140 std::vector<std::string> headers = query["expected_headers"];
141 for (std::vector<std::string>::iterator it = headers.begin();
142 it != headers.end(); ++it) {
mmenke 2015/10/15 19:38:17 Range loop? (And can then just inline query["expe
svaldez 2015/10/15 21:04:33 Done.
143 std::string key = it->substr(0, it->find(":"));
144 std::string value = it->substr(it->find(":") + 1);
mmenke 2015/10/15 19:38:17 Should probably just explicitly fail when there is
svaldez 2015/10/15 21:04:33 Done.
145 if (request.headers.find(key) == request.headers.end() ||
146 request.headers.at(key) != value) {
147 scoped_ptr<BasicHttpResponse> not_found_response(new BasicHttpResponse);
148 not_found_response->set_code(HTTP_NOT_FOUND);
149 return not_found_response.Pass();
150 }
151 }
152 }
153
154 // Trim the first byte ('/').
155 std::string request_path = relative_url.substr(1);
156 base::FilePath file_path(server_root.AppendASCII(request_path));
157 std::string file_contents;
158 if (!base::ReadFileToString(file_path, &file_contents)) {
159 file_path = file_path.AppendASCII("index.html");
160 if (!base::ReadFileToString(file_path, &file_contents))
161 return NOT_HANDLED;
mmenke 2015/10/15 19:38:17 Should we return an error if there was a files/ or
svaldez 2015/10/15 21:04:33 Could be that there is another File handler that m
mmenke 2015/10/15 21:13:54 But there isn't...files/ was removed anyways, now,
162 }
163
164 if (query.find("replace_text") != query.end()) {
165 std::vector<std::string> replacements = query["replace_text"];
166 for (std::vector<std::string>::iterator it = replacements.begin();
167 it != replacements.end(); ++it) {
mmenke 2015/10/15 19:38:17 Range loop
svaldez 2015/10/15 21:04:33 Done.
168 std::string find, with;
mmenke 2015/10/15 19:38:18 Only declare one variable per line.
svaldez 2015/10/15 21:04:33 Done.
169 base::Base64Decode(it->substr(0, it->find(":")), &find);
170 base::Base64Decode(it->substr(it->find(":") + 1), &with);
171 base::ReplaceSubstringsAfterOffset(&file_contents, 0, find, with);
172 }
173 }
174
175 base::FilePath headers_path(
176 file_path.AddExtension(FILE_PATH_LITERAL("mock-http-headers")));
177
178 if (base::PathExists(headers_path)) {
179 std::string headers_contents;
180
181 if (!base::ReadFileToString(headers_path, &headers_contents))
182 return NOT_HANDLED;
mmenke 2015/10/15 19:38:17 Should we return an error in this case?
svaldez 2015/10/15 21:04:32 The way its currently implemented in STS only retu
183
184 if (request.method == METHOD_HEAD)
185 file_contents = "";
mmenke 2015/10/15 19:38:17 Should we be doing this regardless of the presence
svaldez 2015/10/15 21:04:33 Done.
186
187 scoped_ptr<CustomHttpResponse> http_response(
188 new CustomHttpResponse(headers_contents, file_contents));
189 return http_response.Pass();
190 }
191
192 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
193 http_response->set_code(HTTP_OK);
194
195 if (request.headers.find("Range") != request.headers.end()) {
196 std::string range_header = request.headers.at("Range");
197 if (range_header.compare(0, 6, "bytes=") == 0) {
mmenke 2015/10/15 19:38:18 HttpUtil::ParseRangeHeader?
svaldez 2015/10/15 21:04:33 Done.
198 range_header = range_header.substr(6);
199
200 size_t start = 0;
201 size_t end = file_contents.size();
202 size_t delim = range_header.find("-");
203
204 if (delim == std::string::npos) {
205 start = std::atoi(range_header.c_str());
206 } else if (delim == range_header.size() - 1) {
207 start = std::atoi(range_header.substr(0, delim).c_str());
208 } else if (delim == 0) {
209 start = end - std::atoi(range_header.substr(1).c_str());
210 } else {
211 start = std::atoi(range_header.substr(0, delim).c_str());
212 end = std::atoi(range_header.substr(delim + 1).c_str());
213 }
214
215 http_response->set_code(HTTP_PARTIAL_CONTENT);
216 http_response->AddCustomHeader(
217 "Content-Range",
218 base::StringPrintf("bytes %" PRIuS "-%" PRIuS "/%" PRIuS, start,
219 end - 1, file_contents.size()));
220
221 file_contents = file_contents.substr(start, end - start);
222 }
223 }
224
225 if (file_path.MatchesExtension(FILE_PATH_LITERAL(".crx")))
226 http_response->set_content_type("application/x-chrome-extension");
227 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".exe")))
228 http_response->set_content_type("application/octet-stream");
229 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".gif")))
230 http_response->set_content_type("image/gif");
231 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".jpeg")) ||
232 file_path.MatchesExtension(FILE_PATH_LITERAL(".jpg")))
233 http_response->set_content_type("image/jpeg");
234 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".js")))
235 http_response->set_content_type("application/javascript");
236 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".json")))
237 http_response->set_content_type("application/json");
238 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".pdf")))
239 http_response->set_content_type("application/pdf");
240 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".txt")))
241 http_response->set_content_type("text/plain");
242 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".wav")))
243 http_response->set_content_type("audio/wav");
244 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".xml")))
245 http_response->set_content_type("text/xml");
246 else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".html")) ||
247 file_path.MatchesExtension(FILE_PATH_LITERAL(".htm")))
248 http_response->set_content_type("text/html");
mmenke 2015/10/15 19:38:17 Think all this should be in a separate method.
svaldez 2015/10/15 21:04:33 Done.
249
250 http_response->AddCustomHeader("Accept-Ranges", "bytes");
251 http_response->AddCustomHeader("ETag", "'" + file_path.MaybeAsASCII() + "'");
252 http_response->set_content(file_contents);
253 return http_response.Pass();
254 }
255
256 scoped_ptr<HttpResponse> HandleRedirectConnect(const HttpRequest& request) {
257 if (request.headers.find("Host") == request.headers.end() ||
258 request.headers.at("Host") != "www.redirect.com" ||
259 request.method != METHOD_CONNECT)
260 return NOT_HANDLED;
mmenke 2015/10/15 19:38:18 nit: Use braces
svaldez 2015/10/15 21:04:33 Done.
261
262 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
263 http_response->set_code(HTTP_FOUND);
264 http_response->AddCustomHeader("Location",
265 "http://www.destination.com/foo.js");
266 return http_response.Pass();
267 }
268
269 scoped_ptr<HttpResponse> HandleServerAuthConnect(const HttpRequest& request) {
270 if (request.headers.find("Host") == request.headers.end() ||
271 request.headers.at("Host") != "www.server-auth.com" ||
272 request.method != METHOD_CONNECT)
273 return NOT_HANDLED;
mmenke 2015/10/15 19:38:18 nit: Braces
svaldez 2015/10/15 21:04:33 Done.
274
275 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
276 http_response->set_code(HTTP_UNAUTHORIZED);
277 http_response->AddCustomHeader("WWW-Authenticate",
278 "Basic realm=\"WallyWorld\"");
279 return http_response.Pass();
280 }
281
282 scoped_ptr<HttpResponse> HandleDefaultConnect(const HttpRequest& request) {
283 if (request.method != METHOD_CONNECT)
284 return NOT_HANDLED;
285
286 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
287 http_response->set_code(HTTP_BAD_REQUEST);
288 http_response->set_content(
289 "Your client has issued a malformed or illegal request.");
290 http_response->set_content_type("text/html");
291 return http_response.Pass();
292 }
293
294 scoped_ptr<HttpResponse> HandleCacheControl(std::string url,
mmenke 2015/10/15 19:38:17 All of these should be const std::string &
svaldez 2015/10/15 21:04:33 Acknowledged.
295 std::string value,
mmenke 2015/10/15 19:38:18 Should use a clearer name than "value"
svaldez 2015/10/15 21:04:33 Acknowledged.
296 const HttpRequest& request) {
297 if (!ShouldHandle(request, url))
298 return NOT_HANDLED;
299
300 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
301 http_response->set_code(HTTP_OK);
302 std::stringstream time;
303 time << std::time(0);
mmenke 2015/10/15 19:38:17 Suggest using base::Time
svaldez 2015/10/15 21:04:34 Acknowledged.
304 http_response->set_content("<html><head><title>" + time.str() +
305 "</title></head></html>");
mmenke 2015/10/15 19:38:17 Hrm...I wonder about the utility of all of these -
svaldez 2015/10/15 21:04:33 Acknowledged.
306 http_response->set_content_type("text/html");
307 http_response->AddCustomHeader("Cache-Control", value);
308 return http_response.Pass();
309 }
310
311 scoped_ptr<HttpResponse> HandleCacheExpires(const HttpRequest& request) {
312 if (!ShouldHandle(request, "/cache/expires"))
313 return NOT_HANDLED;
314
315 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
316 http_response->set_code(HTTP_OK);
317 std::stringstream time;
318 time << std::time(0);
319 http_response->set_content("<html><head><title>" + time.str() +
320 "</title></head></html>");
321 http_response->set_content_type("text/html");
322 http_response->AddCustomHeader("Expires", "Thu, 1 Jan 2099 00:00:00 GMT");
323 return http_response.Pass();
324 }
325
326 scoped_ptr<HttpResponse> HandleEchoHeader(std::string url,
327 std::string value,
328 const HttpRequest& request) {
329 if (!ShouldHandle(request, url))
330 return NOT_HANDLED;
331
332 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
333 http_response->set_code(HTTP_OK);
334
335 GURL request_gurl = ToGURL(request);
336 if (request_gurl.has_query()) {
337 std::string header_name = request_gurl.query();
338 http_response->AddCustomHeader("Vary", header_name);
339 if (request.headers.find(header_name) != request.headers.end())
340 http_response->set_content(request.headers.at(header_name));
341 else
342 http_response->set_content("None");
343 }
344
345 http_response->set_content_type("text/plain");
346 http_response->AddCustomHeader("Cache-Control", value);
347 return http_response.Pass();
348 }
349
350 scoped_ptr<HttpResponse> HandleEcho(const HttpRequest& request) {
351 if (!ShouldHandle(request, "/echo"))
mmenke 2015/10/15 19:38:17 Think all of the ones based on path should use a w
svaldez 2015/10/15 21:04:33 Done.
352 return NOT_HANDLED;
353
354 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
355 http_response->set_code(HTTP_OK);
356
357 GURL request_gurl = ToGURL(request);
358 if (request_gurl.has_query()) {
359 RequestQuery query = ParseQuery(request_gurl);
360 if (query.find("status") != query.end())
361 http_response->set_code(static_cast<HttpStatusCode>(
362 std::atoi(query["status"].front().c_str())));
363 }
364
365 http_response->set_content_type("text/html");
366 if (request.method != METHOD_POST && request.method != METHOD_PUT)
367 http_response->set_content("Echo");
368 else
369 http_response->set_content(request.content);
370 return http_response.Pass();
371 }
372
373 scoped_ptr<HttpResponse> HandleEchoTitle(const HttpRequest& request) {
374 if (!ShouldHandle(request, "/echotitle"))
375 return NOT_HANDLED;
376
377 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
378 http_response->set_code(HTTP_OK);
379 http_response->set_content_type("text/html");
380 http_response->set_content("<html><head><title>" + request.content +
381 "</title></head></html>");
382 return http_response.Pass();
383 }
384
385 scoped_ptr<HttpResponse> HandleEchoAll(const HttpRequest& request) {
386 if (!ShouldHandle(request, "/echoall"))
387 return NOT_HANDLED;
388
389 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
390 http_response->set_code(HTTP_OK);
391
392 std::string body;
393 body +=
394 "<html><head><style>"
395 "pre { border: 1px solid black; margin: 5px; padding: 5px }"
396 "</style></head><body>"
397 "<div style=\"float: right\">"
398 "<a href=\"/echo\">back to referring page</a></div>"
399 "<h1>Request Body:</h1><pre>";
400
401 if (request.has_content) {
402 std::vector<std::string> qs = base::SplitString(
403 request.content, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
404 for (size_t i = 0; i < qs.size(); ++i) {
405 body += qs[i] + "\n";
406 }
407 }
408
409 body +=
410 "</pre>"
411 "<h1>Request Headers:</h1><pre>" +
412 request.all_headers +
413 "</pre>"
414 "</body></html>";
415
416 http_response->set_content_type("text/html");
417 http_response->set_content(body);
418 return http_response.Pass();
419 }
420
421 scoped_ptr<HttpResponse> HandleSetCookie(const HttpRequest& request) {
422 if (!ShouldHandle(request, "/set-cookie"))
423 return NOT_HANDLED;
424
425 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
426 http_response->set_code(HTTP_OK);
427 http_response->set_content_type("text/html");
428 std::string content;
429 GURL request_gurl = ToGURL(request);
430 if (request_gurl.has_query()) {
431 std::vector<std::string> cookies = base::SplitString(
432 request_gurl.query(), "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
433 for (size_t i = 0; i < cookies.size(); ++i) {
434 http_response->AddCustomHeader("Set-Cookie", cookies[i]);
435 content += cookies[i];
436 }
437 }
438
439 http_response->set_content(content);
440 return http_response.Pass();
441 }
442
443 scoped_ptr<HttpResponse> HandleSetManyCookies(const HttpRequest& request) {
444 if (!ShouldHandle(request, "/set-many-cookies"))
445 return NOT_HANDLED;
446
447 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
448 http_response->set_code(HTTP_OK);
449 http_response->set_content_type("text/html");
450 std::string content;
451
452 GURL request_gurl = ToGURL(request);
453 size_t num = 0;
454 if (request_gurl.has_query()) {
455 num = std::atoi(request_gurl.query().c_str());
456 }
457
458 for (size_t i = 0; i < num; ++i) {
459 http_response->AddCustomHeader("Set-Cookie", "a=");
460 }
461
462 http_response->set_content(
463 base::StringPrintf("%" PRIuS " cookies were sent", num));
464 return http_response.Pass();
465 }
466
467 scoped_ptr<HttpResponse> HandleExpectAndSetCookie(const HttpRequest& request) {
468 if (!ShouldHandle(request, "/expect-and-set-cookie"))
469 return NOT_HANDLED;
470
471 std::vector<std::string> sentCookies;
472 if (request.headers.find("Cookie") != request.headers.end()) {
473 std::vector<std::string> cookies =
474 base::SplitString(request.headers.at("Cookie"), ";",
475 base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
476 for (size_t i = 0; i < cookies.size(); ++i) {
477 sentCookies.push_back(cookies[i]);
478 }
479 }
480 bool gotExpected = true;
481
482 GURL request_gurl = ToGURL(request);
483 RequestQuery qs = ParseQuery(request_gurl);
484 if (qs.find("expect") != qs.end()) {
485 std::vector<std::string> expected = qs.at("expect");
486 for (size_t i = 0; i < expected.size(); ++i) {
487 bool found = false;
488 for (size_t j = 0; j < sentCookies.size(); ++j) {
489 if (expected[i] == sentCookies[j])
490 found = true;
491 }
492 gotExpected &= found;
493 }
494 }
495
496 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
497 http_response->set_code(HTTP_OK);
498 http_response->set_content_type("text/html");
499 if (gotExpected) {
500 std::vector<std::string> setCookies = qs.at("set");
501 for (size_t i = 0; i < setCookies.size(); ++i) {
502 http_response->AddCustomHeader(
503 "Set-Cookie", net::UnescapeURLComponent(setCookies[i], kUnescapeAll));
504 }
505 }
506
507 std::string content;
508 if (qs.find("data") != qs.end()) {
509 std::vector<std::string> data = qs.at("data");
510 for (size_t i = 0; i < data.size(); ++i) {
511 content += data[i];
512 }
513 }
514
515 http_response->set_content(content);
516 return http_response.Pass();
517 }
518
519 scoped_ptr<HttpResponse> HandleSetHeader(const HttpRequest& request) {
520 if (!ShouldHandle(request, "/set-header"))
521 return NOT_HANDLED;
522
523 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
524 http_response->set_code(HTTP_OK);
525 http_response->set_content_type("text/html");
526
527 std::string content;
528
529 GURL request_gurl = ToGURL(request);
530 if (request_gurl.has_query()) {
531 RequestQuery headers = ParseQuery(request_gurl);
532 for (RequestQuery::iterator it = headers.begin(); it != headers.end();
533 ++it) {
534 std::string header = it->first;
535 std::string key = header.substr(0, header.find(": "));
536 std::string value = header.substr(header.find(": ") + 2);
537 http_response->AddCustomHeader(key, value);
538 content += header;
539 }
540 }
541
542 http_response->set_content(content);
543 return http_response.Pass();
544 }
545
546 scoped_ptr<HttpResponse> HandleNoContent(const HttpRequest& request) {
547 if (!ShouldHandle(request, "/nocontent"))
548 return NOT_HANDLED;
549
550 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
551 http_response->set_code(HTTP_NO_CONTENT);
552 return http_response.Pass();
553 }
554
555 scoped_ptr<HttpResponse> HandleCloseSocket(const HttpRequest& request) {
556 if (!ShouldHandle(request, "/close-socket"))
557 return NOT_HANDLED;
558
559 scoped_ptr<CustomHttpResponse> http_response(new CustomHttpResponse("", ""));
560 return http_response.Pass();
561 }
562
563 scoped_ptr<HttpResponse> HandleAuthBasic(const HttpRequest& request) {
564 if (!ShouldHandle(request, "/auth-basic"))
565 return NOT_HANDLED;
566
567 GURL request_gurl = ToGURL(request);
568 RequestQuery query = ParseQuery(request_gurl);
569
570 std::string expectedPassword = "secret";
571 if (query.find(kDefaultPassword) != query.end())
572 expectedPassword = query.at(kDefaultPassword).front();
573 std::string realm = kDefaultRealm;
574 if (query.find("realm") != query.end())
575 realm = query.at("realm").front();
576
577 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
578 bool authed = false;
579 std::string error;
580 std::string auth;
581 std::string username;
582 std::string userpass;
583 std::string password;
584 std::string b64str;
585 if (request.headers.find("Authorization") == request.headers.end()) {
586 error = "Missing Authorization Header";
587 } else {
588 auth = request.headers.at("Authorization");
589 if (auth.find("Basic ") == std::string::npos) {
590 error = "Invalid Authorization Header";
591 } else {
592 b64str = auth.substr(std::string("Basic ").size());
593 base::Base64Decode(b64str, &userpass);
594 if (userpass.find(":") != std::string::npos) {
595 username = userpass.substr(0, userpass.find(":"));
596 password = userpass.substr(userpass.find(":") + 1);
597 if (password == expectedPassword)
598 authed = true;
599 else
600 error = "Invalid Credentials";
601 } else
602 error = "Invalid Credentials";
603 }
604 }
605
606 if (!authed) {
607 http_response->set_code(HTTP_UNAUTHORIZED);
608 http_response->set_content_type("text/html");
609 http_response->AddCustomHeader("WWW-Authenticate",
610 "Basic realm=\"" + realm + "\"");
611 if (query.find("set-cookie-if-challenged") != query.end())
612 http_response->AddCustomHeader("Set-Cookie", "got_challenged=true");
613 http_response->set_content(base::StringPrintf(
614 "<html><head><title>Denied: %s</title></head>"
615 "<body>auth=%s<p>b64str=%s<p>username: %s<p>userpass: %s<p>"
616 "password: %s<p>You sent:<br>%s<p></body></html>",
617 error.c_str(), auth.c_str(), b64str.c_str(), username.c_str(),
618 userpass.c_str(), password.c_str(), request.all_headers.c_str()));
619 return http_response.Pass();
620 }
621
622 if (request.headers.find("If-None-Match") != request.headers.end() &&
623 request.headers.at("If-None-Match") == "abc") {
624 http_response->set_code(HTTP_NOT_MODIFIED);
625 return http_response.Pass();
626 }
627
628 base::FilePath file_path =
629 base::FilePath().AppendASCII(request.relative_url.substr(1));
630 if (file_path.FinalExtension() == FILE_PATH_LITERAL("gif")) {
631 base::FilePath server_root;
632 PathService::Get(base::DIR_SOURCE_ROOT, &server_root);
633 base::FilePath gif_path =
634 server_root.AppendASCII("chrome/test/data/google/logo.gif");
635 std::string gif_data;
636 base::ReadFileToString(gif_path, &gif_data);
637 http_response->set_content_type("image/gif");
638 http_response->set_content(gif_data);
639 } else {
640 http_response->set_content_type("text/html");
641 http_response->set_content(
642 base::StringPrintf("<html><head><title>%s/%s</title></head>"
643 "<body>auth=%s<p>You sent:<br>%s<p></body></html>",
644 username.c_str(), password.c_str(), auth.c_str(),
645 request.all_headers.c_str()));
646 }
647
648 http_response->set_code(HTTP_OK);
649 http_response->AddCustomHeader("Cache-Control", "max-age=60000");
650 http_response->AddCustomHeader("Etag", "abc");
651 return http_response.Pass();
652 }
653
654 scoped_ptr<HttpResponse> HandleAuthDigest(const HttpRequest& request) {
655 if (!ShouldHandle(request, "/auth-digest"))
656 return NOT_HANDLED;
657
658 std::string nonce = base::MD5String(
659 base::StringPrintf("privatekey%s", request.relative_url.c_str()));
660 std::string opaque = base::MD5String("opaque");
661 std::string password = "secret";
662 std::string realm = kDefaultRealm;
663
664 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
665 bool authed = false;
666 std::string error;
667 std::string auth;
668 std::string digestStr = "Digest";
669 std::string username;
670 if (request.headers.find("Authorization") == request.headers.end()) {
671 error = "no auth";
672 } else if (request.headers.at("Authorization").substr(0, digestStr.size()) !=
673 digestStr) {
674 error = "not digest";
675 } else {
676 auth = request.headers.at("Authorization");
677
678 std::map<std::string, std::string> authPairs;
679 std::vector<std::string> authVector = base::SplitString(
680 auth, ", ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
681 for (std::vector<std::string>::iterator it = authVector.begin();
682 it != authVector.end(); ++it) {
683 std::string key = it->substr(0, it->find("="));
684 std::string value = it->substr(it->find("=") + 1);
685 if (value.substr(0, 1) == "\"" && value.substr(value.size() - 1) == "\"")
686 value = value.substr(1, value.size() - 2);
687 authPairs[key] = value;
688 }
689
690 if (authPairs["nonce"] != nonce) {
691 error = "wrong nonce";
692 } else if (authPairs["opaque"] != opaque) {
693 error = "wrong opaque";
694 } else {
695 username = authPairs["username"];
696
697 std::string hash1 = base::MD5String(
698 base::StringPrintf("%s:%s:%s", authPairs["username"].c_str(),
699 realm.c_str(), password.c_str()));
700 std::string hash2 = base::MD5String(base::StringPrintf(
701 "%s:%s", request.method_string.c_str(), authPairs["uri"].c_str()));
702
703 std::string response;
704 if (authPairs.find("qop") != authPairs.end() &&
705 authPairs.find("nc") != authPairs.end() &&
706 authPairs.find("cnonce") != authPairs.end()) {
707 response = base::MD5String(base::StringPrintf(
708 "%s:%s:%s:%s:%s:%s", hash1.c_str(), nonce.c_str(),
709 authPairs["nc"].c_str(), authPairs["cnonce"].c_str(),
710 authPairs["qop"].c_str(), hash2.c_str()));
711 } else {
712 response = base::MD5String(base::StringPrintf(
713 "%s:%s:%s", hash1.c_str(), nonce.c_str(), hash2.c_str()));
714 }
715
716 if (authPairs["response"] == response)
717 authed = true;
718 else
719 error = "wrong password";
720 }
721 }
722
723 if (!authed) {
724 http_response->set_code(HTTP_UNAUTHORIZED);
725 http_response->set_content_type("text/html");
726 std::string authHeader = base::StringPrintf(
727 "Digest realm=\"%s\", "
728 "domain=\"/\", qop=\"auth\", algorithm=MD5, nonce=\"%s\", "
729 "opaque=\"%s\"",
730 realm.c_str(), nonce.c_str(), opaque.c_str());
731 http_response->AddCustomHeader("WWW-Authenticate", authHeader);
732 http_response->set_content(base::StringPrintf(
733 "<html><head><title>Denied: %s</title></head>"
734 "<body>auth=%s<p>"
735 "You sent:<br>%s<p>We are replying:<br>%s<p></body></html>",
736 error.c_str(), auth.c_str(), request.all_headers.c_str(),
737 authHeader.c_str()));
738 return http_response.Pass();
739 }
740
741 http_response->set_content_type("text/html");
742 http_response->set_content(
743 base::StringPrintf("<html><head><title>%s/%s</title></head>"
744 "<body>auth=%s<p></body></html>",
745 username.c_str(), password.c_str(), auth.c_str()));
746
747 return http_response.Pass();
748 }
749
750 scoped_ptr<HttpResponse> HandleServerRedirect(const HttpRequest& request) {
751 if (!ShouldHandle(request, "/server-redirect"))
752 return NOT_HANDLED;
753
754 GURL request_gurl = ToGURL(request);
755 std::string dest =
756 net::UnescapeURLComponent(request_gurl.query(), kUnescapeAll);
757
758 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
759 http_response->set_code(HTTP_MOVED_PERMANENTLY);
760 http_response->AddCustomHeader("Location", dest);
761 http_response->set_content_type("text/html");
762 http_response->set_content(base::StringPrintf(
763 "<html><head></head><body>Redirecting to %s</body></html>",
764 dest.c_str()));
765 return http_response.Pass();
766 }
767
768 scoped_ptr<HttpResponse> HandleCrossSiteRedirect(EmbeddedTestServer* server,
769 const HttpRequest& request) {
770 if (!ShouldHandle(request, "/cross-site"))
771 return NOT_HANDLED;
772
773 std::string destAll = net::UnescapeURLComponent(
774 request.relative_url.substr(std::string("/cross-site").size() + 1),
775 kUnescapeAll);
776
777 std::string dest = base::StringPrintf(
778 "//%s:%hu/%s", destAll.substr(0, destAll.find("/")).c_str(),
779 server->port(), destAll.substr(destAll.find("/") + 1).c_str());
780
781 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
782 http_response->set_code(HTTP_MOVED_PERMANENTLY);
783 http_response->AddCustomHeader("Location", dest);
784 http_response->set_content_type("text/html");
785 http_response->set_content(base::StringPrintf(
786 "<html><head></head><body>Redirecting to %s</body></html>",
787 dest.c_str()));
788 return http_response.Pass();
789 }
790
791 scoped_ptr<HttpResponse> HandleClientRedirect(const HttpRequest& request) {
792 if (!ShouldHandle(request, "/client-redirect"))
793 return NOT_HANDLED;
794
795 GURL request_gurl = ToGURL(request);
796 std::string dest =
797 net::UnescapeURLComponent(request_gurl.query(), kUnescapeAll);
798
799 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
800 http_response->set_code(HTTP_OK);
801 http_response->set_content_type("text/html");
802 http_response->set_content(base::StringPrintf(
803 "<html><head><meta http-equiv=\"refresh\" content=\"0;url=%s\"></head>"
804 "<body>Redirecting to %s</body></html>",
805 dest.c_str(), dest.c_str()));
806 return http_response.Pass();
807 }
808
809 scoped_ptr<HttpResponse> HandleDefaultResponse(const HttpRequest& request) {
810 if (!ShouldHandle(request, "/defaultresponse"))
811 return NOT_HANDLED;
812
813 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
814 http_response->set_code(HTTP_OK);
815 http_response->set_content_type("text/html");
816 http_response->set_content("Default response given for path: " +
817 request.relative_url);
818 return http_response.Pass();
819 }
820
821 class DelayedHttpResponse : public BasicHttpResponse {
822 public:
823 DelayedHttpResponse(double delay) : delay_(delay) {}
824
825 void SendResponse(SendBytesCallback send,
826 SendCompleteCallback done) override {
827 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
828 FROM_HERE, base::Bind(send, ToResponseString(), done),
829 base::TimeDelta::FromSecondsD(delay_));
830 }
831
832 private:
833 double delay_;
834 };
835
836 scoped_ptr<HttpResponse> HandleSlowServer(const HttpRequest& request) {
837 if (!ShouldHandle(request, "/slow"))
838 return NOT_HANDLED;
839
840 double delay = 1.0f;
841
842 GURL request_gurl = ToGURL(request);
843 if (request_gurl.has_query()) {
844 delay = std::atof(request_gurl.query().c_str());
845 }
846
847 scoped_ptr<BasicHttpResponse> http_response(new DelayedHttpResponse(delay));
848 http_response->set_code(HTTP_OK);
849 http_response->set_content_type("text/plain");
850 http_response->set_content(base::StringPrintf("waited %.1f seconds", delay));
851 return http_response.Pass();
852 }
853
854 void RegisterDefaultHandlers(EmbeddedTestServer* server) {
855 server->RegisterDefaultHandler(base::Bind(&HandleRedirectConnect));
856 server->RegisterDefaultHandler(base::Bind(&HandleServerAuthConnect));
857 server->RegisterDefaultHandler(base::Bind(&HandleDefaultConnect));
858
859 server->RegisterDefaultHandler(
860 base::Bind(&HandleCacheControl, "/nocachetime/maxage", "max-age=0"));
mmenke 2015/10/15 19:38:17 Rather than make all of these take a path, suggest
svaldez 2015/10/15 21:04:33 Done.
861 server->RegisterDefaultHandler(
862 base::Bind(&HandleCacheControl, "/nocachetime", "no-cache"));
863 server->RegisterDefaultHandler(
864 base::Bind(&HandleCacheControl, "/cachetime", "max-age=60"));
865 server->RegisterDefaultHandler(base::Bind(&HandleCacheExpires));
866 server->RegisterDefaultHandler(base::Bind(&HandleCacheControl,
867 "/cache/proxy-revalidate",
868 "max-age=60, proxy-revalidate"));
869 server->RegisterDefaultHandler(
870 base::Bind(&HandleCacheControl, "/cache/private", "max-age=3, private"));
871 server->RegisterDefaultHandler(
872 base::Bind(&HandleCacheControl, "/cache/public", "max-age=3, public"));
873 server->RegisterDefaultHandler(base::Bind(&HandleCacheControl,
874 "/cache/s-maxage",
875 "public, s-maxage=60, max-age=0"));
876 server->RegisterDefaultHandler(base::Bind(
877 &HandleCacheControl, "/cache/must-revalidate", "must-revalidate"));
878 server->RegisterDefaultHandler(base::Bind(&HandleCacheControl,
879 "/cache/must-revalidate/max-age",
880 "max-age=60, must-revalidate"));
881 server->RegisterDefaultHandler(
882 base::Bind(&HandleCacheControl, "/cache/no-store", "no-store"));
883 server->RegisterDefaultHandler(base::Bind(
884 &HandleCacheControl, "/cache/no-store/max-age", "max-age=60, no-store"));
885 server->RegisterDefaultHandler(
886 base::Bind(&HandleCacheControl, "/cache/no-transform", "no-transform"));
887
888 server->RegisterDefaultHandler(
889 base::Bind(&HandleEchoHeader, "/echoheader", "no-cache"));
890 server->RegisterDefaultHandler(
891 base::Bind(&HandleEchoHeader, "/echoheadercache", "max-age=60000"));
892 server->RegisterDefaultHandler(base::Bind(&HandleEcho));
893 server->RegisterDefaultHandler(base::Bind(&HandleEchoTitle));
894 server->RegisterDefaultHandler(base::Bind(&HandleEchoAll));
895
896 server->RegisterDefaultHandler(base::Bind(&HandleSetCookie));
897 server->RegisterDefaultHandler(base::Bind(&HandleSetManyCookies));
898 server->RegisterDefaultHandler(base::Bind(&HandleExpectAndSetCookie));
899 server->RegisterDefaultHandler(base::Bind(&HandleSetHeader));
900 server->RegisterDefaultHandler(base::Bind(&HandleNoContent));
901 server->RegisterDefaultHandler(base::Bind(&HandleCloseSocket));
902 server->RegisterDefaultHandler(base::Bind(&HandleAuthBasic));
903 server->RegisterDefaultHandler(base::Bind(&HandleAuthDigest));
904 server->RegisterDefaultHandler(base::Bind(&HandleServerRedirect));
905 server->RegisterDefaultHandler(base::Bind(&HandleCrossSiteRedirect, server));
906 server->RegisterDefaultHandler(base::Bind(&HandleClientRedirect));
907 server->RegisterDefaultHandler(base::Bind(&HandleDefaultResponse));
908 server->RegisterDefaultHandler(base::Bind(&HandleSlowServer));
909
910 // TODO(svaldez): HandleDownload
911 // TODO(svaldez): HandleDownloadFinish
912 // TODO(svaldez): HandleZipFile
913 // TODO(svaldez): HandleRangeReset
914 // TODO(svaldez): HandleSSLManySmallRecords
915 // TODO(svaldez): HandleChunkedServer
916 // TODO(svaldez): HandleGetSSLSessionCache
917 // TODO(svaldez): HandleGetChannelID
918 // TODO(svaldez): HandleGetClientCert
919 // TODO(svaldez): HandleClientCipherList
920 // TODO(svaldez): HandleEchoMultipartPost
921 }
922
923 } // namespace test_server
924 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698