Chromium Code Reviews| OLD | NEW |
|---|---|
| (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" | |
| 6 | |
| 7 #include <stdlib.h> | |
| 8 #include <ctime> | |
| 9 #include <map> | |
| 10 #include <sstream> | |
| 11 #include <string> | |
|
mmenke
2015/10/21 20:21:37
map, string should be in the header, per comment i
svaldez
2015/10/21 21:22:29
Done.
| |
| 12 | |
| 13 #include "base/base64.h" | |
| 14 #include "base/files/file_path.h" | |
|
mmenke
2015/10/21 20:21:37
Already in header
svaldez
2015/10/21 21:22:29
Done.
| |
| 15 #include "base/files/file_util.h" | |
| 16 #include "base/format_macros.h" | |
| 17 #include "base/strings/string_util.h" | |
| 18 #include "base/strings/stringprintf.h" | |
| 19 #include "base/threading/thread_restrictions.h" | |
| 20 #include "net/base/escape.h" | |
| 21 #include "net/base/url_util.h" | |
| 22 #include "net/http/http_byte_range.h" | |
| 23 #include "net/http/http_util.h" | |
| 24 #include "net/test/embedded_test_server/embedded_test_server.h" | |
| 25 #include "net/test/embedded_test_server/http_request.h" | |
| 26 #include "net/test/embedded_test_server/http_response.h" | |
| 27 | |
| 28 namespace net { | |
| 29 namespace test_server { | |
| 30 namespace { | |
| 31 | |
| 32 const UnescapeRule::Type kUnescapeAll = | |
| 33 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS | | |
| 34 UnescapeRule::SPOOFING_AND_CONTROL_CHARS | | |
| 35 UnescapeRule::REPLACE_PLUS_WITH_SPACE; | |
| 36 | |
| 37 std::string GetContentType(const base::FilePath& path) { | |
| 38 if (path.MatchesExtension(FILE_PATH_LITERAL(".crx"))) | |
| 39 return "application/x-chrome-extension"; | |
| 40 if (path.MatchesExtension(FILE_PATH_LITERAL(".exe"))) | |
| 41 return "application/octet-stream"; | |
| 42 if (path.MatchesExtension(FILE_PATH_LITERAL(".gif"))) | |
| 43 return "image/gif"; | |
| 44 if (path.MatchesExtension(FILE_PATH_LITERAL(".jpeg")) || | |
| 45 path.MatchesExtension(FILE_PATH_LITERAL(".jpg"))) { | |
| 46 return "image/jpeg"; | |
| 47 } | |
| 48 if (path.MatchesExtension(FILE_PATH_LITERAL(".js"))) | |
| 49 return "application/javascript"; | |
| 50 if (path.MatchesExtension(FILE_PATH_LITERAL(".json"))) | |
| 51 return "application/json"; | |
| 52 if (path.MatchesExtension(FILE_PATH_LITERAL(".pdf"))) | |
| 53 return "application/pdf"; | |
| 54 if (path.MatchesExtension(FILE_PATH_LITERAL(".txt"))) | |
| 55 return "text/plain"; | |
| 56 if (path.MatchesExtension(FILE_PATH_LITERAL(".wav"))) | |
| 57 return "audio/wav"; | |
| 58 if (path.MatchesExtension(FILE_PATH_LITERAL(".xml"))) | |
| 59 return "text/xml"; | |
| 60 if (path.MatchesExtension(FILE_PATH_LITERAL(".html")) || | |
| 61 path.MatchesExtension(FILE_PATH_LITERAL(".htm"))) { | |
| 62 return "text/html"; | |
| 63 } | |
| 64 return ""; | |
| 65 } | |
| 66 | |
| 67 } // namespace | |
| 68 | |
| 69 CustomHttpResponse::CustomHttpResponse(const std::string& headers, | |
| 70 const std::string& contents) | |
| 71 : headers_(headers), contents_(contents) {} | |
| 72 | |
| 73 void CustomHttpResponse::SendResponse(const SendBytesCallback& send, | |
| 74 const SendCompleteCallback& done) { | |
| 75 std::string response; | |
| 76 if (!headers_.empty() || !contents_.empty()) | |
| 77 response = headers_ + "\r\n" + contents_; | |
|
mmenke
2015/10/21 20:21:37
This seems kinda weird, if headers_ is empty but c
svaldez
2015/10/21 21:22:29
Done.
| |
| 78 send.Run(response, done); | |
| 79 } | |
| 80 | |
| 81 bool ShouldHandle(const HttpRequest& request, const std::string& path_prefix) { | |
| 82 GURL url = request.ToURL(); | |
| 83 return url.path() == path_prefix || | |
| 84 base::StartsWith(url.path(), path_prefix + "/", | |
| 85 base::CompareCase::SENSITIVE); | |
| 86 } | |
| 87 | |
| 88 scoped_ptr<HttpResponse> HandlePrefixedRequest( | |
| 89 const std::string& prefix, | |
| 90 const EmbeddedTestServer::HandleRequestCallback& handler, | |
| 91 const HttpRequest& request) { | |
| 92 if (ShouldHandle(request, prefix)) | |
| 93 return handler.Run(request); | |
| 94 return nullptr; | |
| 95 } | |
| 96 | |
| 97 RequestQuery ParseQuery(const GURL& url) { | |
| 98 RequestQuery queries; | |
| 99 for (QueryIterator it(url); !it.IsAtEnd(); it.Advance()) { | |
| 100 queries[net::UnescapeURLComponent(it.GetKey(), kUnescapeAll)].push_back( | |
| 101 it.GetUnescapedValue()); | |
| 102 } | |
| 103 return queries; | |
| 104 } | |
| 105 | |
| 106 void GetFilePathWithReplacements(const std::string& original_file_path, | |
| 107 const base::StringPairs& text_to_replace, | |
| 108 std::string* replacement_path) { | |
| 109 std::string new_file_path = original_file_path; | |
| 110 bool first_query_parameter = true; | |
| 111 for (auto replacement : text_to_replace) { | |
|
mmenke
2015/10/21 20:21:37
const auto& is preferred, I believe.
svaldez
2015/10/21 21:22:29
Done.
| |
| 112 const std::string& old_text = replacement.first; | |
| 113 const std::string& new_text = replacement.second; | |
| 114 std::string base64_old; | |
| 115 std::string base64_new; | |
| 116 base::Base64Encode(old_text, &base64_old); | |
| 117 base::Base64Encode(new_text, &base64_new); | |
| 118 if (first_query_parameter) { | |
|
mmenke
2015/10/21 20:21:37
nit: Maybe get rid of this variable and use new_f
svaldez
2015/10/21 21:22:29
Done.
| |
| 119 new_file_path += "?"; | |
| 120 first_query_parameter = false; | |
| 121 } else { | |
| 122 new_file_path += "&"; | |
| 123 } | |
| 124 new_file_path += "replace_text="; | |
| 125 new_file_path += base64_old; | |
| 126 new_file_path += ":"; | |
| 127 new_file_path += base64_new; | |
| 128 } | |
| 129 | |
| 130 *replacement_path = new_file_path; | |
| 131 } | |
| 132 | |
| 133 // Handles |request| by serving a file from under |server_root|. | |
| 134 scoped_ptr<HttpResponse> HandleFileRequest(const base::FilePath& server_root, | |
| 135 const HttpRequest& request) { | |
| 136 // This is a test-only server. Ignore I/O thread restrictions. | |
| 137 // TODO(svaldez): Figure out why restriction is necessary. | |
|
mmenke
2015/10/21 20:21:37
Think "why restriction is necessary" -> "why threa
svaldez
2015/10/21 21:22:29
Done.
| |
| 138 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
| 139 | |
| 140 std::string relative_path(request.relative_url); | |
|
mmenke
2015/10/21 20:21:37
Not needed - can you overwrite it unconditionally
svaldez
2015/10/21 21:22:29
Done.
| |
| 141 | |
| 142 // A proxy request will have an absolute path. Simulate the proxy by stripping | |
| 143 // the scheme, host, and port. | |
| 144 GURL request_url = request.ToURL(); | |
| 145 relative_path = request_url.path(); | |
| 146 | |
| 147 std::string post_prefix("/post/"); | |
| 148 if (base::StartsWith(relative_path, post_prefix, | |
| 149 base::CompareCase::SENSITIVE)) { | |
| 150 relative_path = relative_path.substr(post_prefix.size() - 1); | |
| 151 if (request.method != METHOD_POST) | |
| 152 return nullptr; | |
|
mmenke
2015/10/21 20:21:37
Suggest moving this above setting relative_path.
svaldez
2015/10/21 21:22:29
Done.
| |
| 153 } | |
| 154 | |
| 155 RequestQuery query = ParseQuery(request_url); | |
| 156 | |
| 157 scoped_ptr<BasicHttpResponse> failed_response(new BasicHttpResponse); | |
| 158 failed_response->set_code(HTTP_NOT_FOUND); | |
| 159 | |
| 160 if (query.find("expected_body") != query.end()) { | |
| 161 if (request.content.find(query["expected_body"].front()) == | |
| 162 std::string::npos) { | |
| 163 return failed_response.Pass(); | |
| 164 } | |
| 165 } | |
| 166 | |
| 167 if (query.find("expected_headers") != query.end()) { | |
| 168 for (auto header : query["expected_headers"]) { | |
|
mmenke
2015/10/21 20:21:37
const auto&
svaldez
2015/10/21 21:22:29
Done.
| |
| 169 if (header.find(":") == std::string::npos) | |
| 170 return failed_response.Pass(); | |
| 171 std::string key = header.substr(0, header.find(":")); | |
| 172 std::string value = header.substr(header.find(":") + 1); | |
|
mmenke
2015/10/21 20:21:37
Hrm...Thought I commented on this. If header.find
svaldez
2015/10/21 21:22:29
But it won't be since we return a failure a few li
| |
| 173 if (request.headers.find(key) == request.headers.end() || | |
| 174 request.headers.at(key) != value) { | |
| 175 return failed_response.Pass(); | |
| 176 } | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 // Trim the first byte ('/'). | |
|
mmenke
2015/10/21 20:21:37
DCHECK that relative_path starts with "/"?
svaldez
2015/10/21 21:22:29
Done.
| |
| 181 std::string request_path = relative_path.substr(1); | |
| 182 base::FilePath file_path(server_root.AppendASCII(request_path)); | |
| 183 std::string file_contents; | |
| 184 if (!base::ReadFileToString(file_path, &file_contents)) { | |
| 185 file_path = file_path.AppendASCII("index.html"); | |
| 186 if (!base::ReadFileToString(file_path, &file_contents)) | |
| 187 return nullptr; | |
| 188 } | |
| 189 | |
| 190 if (request.method == METHOD_HEAD) | |
| 191 file_contents = ""; | |
| 192 | |
| 193 if (query.find("replace_text") != query.end()) { | |
| 194 for (auto replacement : query["replace_text"]) { | |
|
mmenke
2015/10/21 20:21:37
const auto&
svaldez
2015/10/21 21:22:29
Done.
| |
| 195 std::string find; | |
| 196 std::string with; | |
| 197 base::Base64Decode(replacement.substr(0, replacement.find(":")), &find); | |
| 198 base::Base64Decode(replacement.substr(replacement.find(":") + 1), &with); | |
| 199 base::ReplaceSubstringsAfterOffset(&file_contents, 0, find, with); | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 base::FilePath headers_path( | |
| 204 file_path.AddExtension(FILE_PATH_LITERAL("mock-http-headers"))); | |
| 205 | |
| 206 if (base::PathExists(headers_path)) { | |
| 207 std::string headers_contents; | |
| 208 | |
| 209 if (!base::ReadFileToString(headers_path, &headers_contents)) | |
| 210 return nullptr; | |
| 211 | |
| 212 scoped_ptr<CustomHttpResponse> http_response( | |
| 213 new CustomHttpResponse(headers_contents, file_contents)); | |
| 214 return http_response.Pass(); | |
|
mmenke
2015/10/21 20:21:37
optional nit: Could just do:
return make_scoped_
svaldez
2015/10/21 21:22:29
Done.
| |
| 215 } | |
| 216 | |
| 217 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse); | |
| 218 http_response->set_code(HTTP_OK); | |
| 219 | |
| 220 if (request.headers.find("Range") != request.headers.end()) { | |
| 221 std::vector<HttpByteRange> ranges; | |
| 222 HttpUtil::ParseRangeHeader(request.headers.at("Range"), &ranges); | |
| 223 if (ranges.size() == 1) { | |
|
mmenke
2015/10/21 20:21:37
Can we fail if size() is not 1, or if ParseRangeHe
svaldez
2015/10/21 21:22:29
I think we should be ignoring Range we don't suppo
mmenke
2015/10/21 21:36:38
Good point - you should then include the result of
| |
| 224 ranges[0].ComputeBounds(file_contents.size()); | |
| 225 size_t start = ranges[0].first_byte_position(); | |
| 226 size_t end = ranges[0].last_byte_position(); | |
| 227 | |
| 228 http_response->set_code(HTTP_PARTIAL_CONTENT); | |
| 229 http_response->AddCustomHeader( | |
| 230 "Content-Range", | |
| 231 base::StringPrintf("bytes %" PRIuS "-%" PRIuS "/%" PRIuS, start, end, | |
| 232 file_contents.size())); | |
| 233 | |
| 234 file_contents = file_contents.substr(start, end - start + 1); | |
| 235 } | |
| 236 } | |
| 237 | |
| 238 http_response->set_content_type(GetContentType(file_path)); | |
| 239 http_response->AddCustomHeader("Accept-Ranges", "bytes"); | |
| 240 http_response->AddCustomHeader("ETag", "'" + file_path.MaybeAsASCII() + "'"); | |
| 241 http_response->set_content(file_contents); | |
| 242 return http_response.Pass(); | |
| 243 } | |
| 244 | |
| 245 } // namespace test_server | |
| 246 } // namespace net | |
| OLD | NEW |