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