• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 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 #include "net/test/embedded_test_server/request_handler_util.h"
6 
7 #include <stdlib.h>
8 
9 #include <ctime>
10 #include <sstream>
11 #include <utility>
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/strings/escape.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/threading/thread_restrictions.h"
22 #include "build/build_config.h"
23 #include "net/base/url_util.h"
24 #include "net/http/http_byte_range.h"
25 #include "net/http/http_util.h"
26 #include "net/test/embedded_test_server/http_request.h"
27 #include "url/gurl.h"
28 
29 namespace net::test_server {
30 constexpr base::FilePath::CharType kMockHttpHeadersExtension[] =
31     FILE_PATH_LITERAL("mock-http-headers");
32 
GetContentType(const base::FilePath & path)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(".css")))
37     return "text/css";
38   if (path.MatchesExtension(FILE_PATH_LITERAL(".exe")))
39     return "application/octet-stream";
40   if (path.MatchesExtension(FILE_PATH_LITERAL(".gif")))
41     return "image/gif";
42   if (path.MatchesExtension(FILE_PATH_LITERAL(".gzip")) ||
43       path.MatchesExtension(FILE_PATH_LITERAL(".gz"))) {
44     return "application/x-gzip";
45   }
46   if (path.MatchesExtension(FILE_PATH_LITERAL(".jpeg")) ||
47       path.MatchesExtension(FILE_PATH_LITERAL(".jpg"))) {
48     return "image/jpeg";
49   }
50   if (path.MatchesExtension(FILE_PATH_LITERAL(".js")))
51     return "application/javascript";
52   if (path.MatchesExtension(FILE_PATH_LITERAL(".json")))
53     return "application/json";
54   if (path.MatchesExtension(FILE_PATH_LITERAL(".pdf")))
55     return "application/pdf";
56   if (path.MatchesExtension(FILE_PATH_LITERAL(".svg")))
57     return "image/svg+xml";
58   if (path.MatchesExtension(FILE_PATH_LITERAL(".txt")))
59     return "text/plain";
60   if (path.MatchesExtension(FILE_PATH_LITERAL(".wav")))
61     return "audio/wav";
62   if (path.MatchesExtension(FILE_PATH_LITERAL(".webp")))
63     return "image/webp";
64   if (path.MatchesExtension(FILE_PATH_LITERAL(".mp4")))
65     return "video/mp4";
66   if (path.MatchesExtension(FILE_PATH_LITERAL(".webm")))
67     return "video/webm";
68   if (path.MatchesExtension(FILE_PATH_LITERAL(".xml")))
69     return "text/xml";
70   if (path.MatchesExtension(FILE_PATH_LITERAL(".mhtml")))
71     return "multipart/related";
72   if (path.MatchesExtension(FILE_PATH_LITERAL(".mht")))
73     return "message/rfc822";
74   if (path.MatchesExtension(FILE_PATH_LITERAL(".html")) ||
75       path.MatchesExtension(FILE_PATH_LITERAL(".htm"))) {
76     return "text/html";
77   }
78   return "";
79 }
80 
ShouldHandle(const HttpRequest & request,const std::string & path_prefix)81 bool ShouldHandle(const HttpRequest& request, const std::string& path_prefix) {
82   if (request.method == METHOD_CONNECT) {
83     return false;
84   }
85 
86   GURL url = request.GetURL();
87   return url.path() == path_prefix ||
88          base::StartsWith(url.path(), path_prefix + "/",
89                           base::CompareCase::SENSITIVE);
90 }
91 
HandlePrefixedRequest(const std::string & prefix,const EmbeddedTestServer::HandleRequestCallback & handler,const HttpRequest & request)92 std::unique_ptr<HttpResponse> HandlePrefixedRequest(
93     const std::string& prefix,
94     const EmbeddedTestServer::HandleRequestCallback& handler,
95     const HttpRequest& request) {
96   if (ShouldHandle(request, prefix))
97     return handler.Run(request);
98   return nullptr;
99 }
100 
ParseQuery(const GURL & url)101 RequestQuery ParseQuery(const GURL& url) {
102   RequestQuery queries;
103   for (QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
104     std::string unescaped_query = base::UnescapeBinaryURLComponent(
105         it.GetKey(), base::UnescapeRule::REPLACE_PLUS_WITH_SPACE);
106     queries[unescaped_query].push_back(it.GetUnescapedValue());
107   }
108   return queries;
109 }
110 
GetFilePathWithReplacements(const std::string & original_file_path,const base::StringPairs & text_to_replace)111 std::string GetFilePathWithReplacements(
112     const std::string& original_file_path,
113     const base::StringPairs& text_to_replace) {
114   std::string new_file_path = original_file_path;
115   for (const auto& replacement : text_to_replace) {
116     const std::string& old_text = replacement.first;
117     const std::string& new_text = replacement.second;
118     std::string base64_old;
119     std::string base64_new;
120     base::Base64Encode(old_text, &base64_old);
121     base::Base64Encode(new_text, &base64_new);
122     if (new_file_path == original_file_path)
123       new_file_path += "?";
124     else
125       new_file_path += "&";
126     new_file_path += "replace_text=";
127     new_file_path += base64_old;
128     new_file_path += ":";
129     new_file_path += base64_new;
130   }
131 
132   return new_file_path;
133 }
134 
135 // Returns false if there were errors, otherwise true.
UpdateReplacedText(const RequestQuery & query,std::string * data)136 bool UpdateReplacedText(const RequestQuery& query, std::string* data) {
137   auto replace_text = query.find("replace_text");
138   if (replace_text == query.end())
139     return true;
140 
141   for (const auto& replacement : replace_text->second) {
142     if (replacement.find(":") == std::string::npos)
143       return false;
144     std::string find;
145     std::string with;
146     base::Base64Decode(replacement.substr(0, replacement.find(":")), &find);
147     base::Base64Decode(replacement.substr(replacement.find(":") + 1), &with);
148     base::ReplaceSubstringsAfterOffset(data, 0, find, with);
149   }
150 
151   return true;
152 }
153 
154 // Handles |request| by serving a file from under |server_root|.
HandleFileRequest(const base::FilePath & server_root,const HttpRequest & request)155 std::unique_ptr<HttpResponse> HandleFileRequest(
156     const base::FilePath& server_root,
157     const HttpRequest& request) {
158   // This is a test-only server. Ignore I/O thread restrictions.
159   // TODO(svaldez): Figure out why thread is I/O restricted in the first place.
160   base::ScopedAllowBlockingForTesting allow_blocking;
161 
162   if (request.method == METHOD_CONNECT) {
163     return nullptr;
164   }
165 
166   // A proxy request will have an absolute path. Simulate the proxy by stripping
167   // the scheme, host, and port.
168   GURL request_url = request.GetURL();
169   std::string relative_path(request_url.path());
170 
171   std::string post_prefix("/post/");
172   if (base::StartsWith(relative_path, post_prefix,
173                        base::CompareCase::SENSITIVE)) {
174     if (request.method != METHOD_POST)
175       return nullptr;
176     relative_path = relative_path.substr(post_prefix.size() - 1);
177   }
178 
179   RequestQuery query = ParseQuery(request_url);
180 
181   auto failed_response = std::make_unique<BasicHttpResponse>();
182   failed_response->set_code(HTTP_NOT_FOUND);
183 
184   if (query.find("expected_body") != query.end()) {
185     if (request.content.find(query["expected_body"].front()) ==
186         std::string::npos) {
187       return failed_response;
188     }
189   }
190 
191   if (query.find("expected_headers") != query.end()) {
192     for (const auto& header : query["expected_headers"]) {
193       if (header.find(":") == std::string::npos)
194         return failed_response;
195       std::string key = header.substr(0, header.find(":"));
196       std::string value = header.substr(header.find(":") + 1);
197       if (request.headers.find(key) == request.headers.end() ||
198           request.headers.at(key) != value) {
199         return failed_response;
200       }
201     }
202   }
203 
204   // Trim the first byte ('/').
205   DCHECK(base::StartsWith(relative_path, "/", base::CompareCase::SENSITIVE));
206   std::string request_path = relative_path.substr(1);
207   base::FilePath file_path(server_root.AppendASCII(request_path));
208   std::string file_contents;
209   if (!base::ReadFileToString(file_path, &file_contents)) {
210     file_path = file_path.AppendASCII("index.html");
211     if (!base::ReadFileToString(file_path, &file_contents))
212       return nullptr;
213   }
214 
215   if (request.method == METHOD_HEAD)
216     file_contents = "";
217 
218   if (!UpdateReplacedText(query, &file_contents))
219     return failed_response;
220 
221   base::FilePath headers_path(
222       file_path.AddExtension(kMockHttpHeadersExtension));
223 
224   if (base::PathExists(headers_path)) {
225     std::string headers_contents;
226 
227     if (!base::ReadFileToString(headers_path, &headers_contents) ||
228         !UpdateReplacedText(query, &headers_contents)) {
229       return nullptr;
230     }
231 
232     return std::make_unique<RawHttpResponse>(headers_contents, file_contents);
233   }
234 
235   auto http_response = std::make_unique<BasicHttpResponse>();
236   http_response->set_code(HTTP_OK);
237 
238   if (request.headers.find("Range") != request.headers.end()) {
239     std::vector<HttpByteRange> ranges;
240 
241     if (HttpUtil::ParseRangeHeader(request.headers.at("Range"), &ranges) &&
242         ranges.size() == 1) {
243       ranges[0].ComputeBounds(file_contents.size());
244       size_t start = ranges[0].first_byte_position();
245       size_t end = ranges[0].last_byte_position();
246 
247       http_response->set_code(HTTP_PARTIAL_CONTENT);
248       http_response->AddCustomHeader(
249           "Content-Range",
250           base::StringPrintf("bytes %" PRIuS "-%" PRIuS "/%" PRIuS, start, end,
251                              file_contents.size()));
252 
253       file_contents = file_contents.substr(start, end - start + 1);
254     }
255   }
256 
257   http_response->set_content_type(GetContentType(file_path));
258   http_response->AddCustomHeader("Accept-Ranges", "bytes");
259   http_response->AddCustomHeader("ETag", "'" + file_path.MaybeAsASCII() + "'");
260   http_response->set_content(file_contents);
261   return http_response;
262 }
263 
264 }  // namespace net::test_server
265