• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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 "quiche/quic/tools/quic_memory_cache_backend.h"
6 
7 #include <utility>
8 
9 #include "absl/strings/match.h"
10 #include "absl/strings/numbers.h"
11 #include "absl/strings/str_cat.h"
12 #include "absl/strings/string_view.h"
13 #include "quiche/quic/core/http/spdy_utils.h"
14 #include "quiche/quic/platform/api/quic_bug_tracker.h"
15 #include "quiche/quic/platform/api/quic_logging.h"
16 #include "quiche/quic/tools/web_transport_test_visitors.h"
17 #include "quiche/common/platform/api/quiche_file_utils.h"
18 #include "quiche/common/quiche_text_utils.h"
19 
20 using spdy::Http2HeaderBlock;
21 using spdy::kV3LowestPriority;
22 
23 namespace quic {
24 
ResourceFile(const std::string & file_name)25 QuicMemoryCacheBackend::ResourceFile::ResourceFile(const std::string& file_name)
26     : file_name_(file_name) {}
27 
28 QuicMemoryCacheBackend::ResourceFile::~ResourceFile() = default;
29 
Read()30 void QuicMemoryCacheBackend::ResourceFile::Read() {
31   absl::optional<std::string> maybe_file_contents =
32       quiche::ReadFileContents(file_name_);
33   if (!maybe_file_contents) {
34     QUIC_LOG(DFATAL) << "Failed to read file for the memory cache backend: "
35                      << file_name_;
36     return;
37   }
38   file_contents_ = *maybe_file_contents;
39 
40   // First read the headers.
41   size_t start = 0;
42   while (start < file_contents_.length()) {
43     size_t pos = file_contents_.find('\n', start);
44     if (pos == std::string::npos) {
45       QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
46       return;
47     }
48     size_t len = pos - start;
49     // Support both dos and unix line endings for convenience.
50     if (file_contents_[pos - 1] == '\r') {
51       len -= 1;
52     }
53     absl::string_view line(file_contents_.data() + start, len);
54     start = pos + 1;
55     // Headers end with an empty line.
56     if (line.empty()) {
57       break;
58     }
59     // Extract the status from the HTTP first line.
60     if (line.substr(0, 4) == "HTTP") {
61       pos = line.find(' ');
62       if (pos == std::string::npos) {
63         QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: "
64                          << file_name_;
65         return;
66       }
67       spdy_headers_[":status"] = line.substr(pos + 1, 3);
68       continue;
69     }
70     // Headers are "key: value".
71     pos = line.find(": ");
72     if (pos == std::string::npos) {
73       QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
74       return;
75     }
76     spdy_headers_.AppendValueOrAddHeader(
77         quiche::QuicheTextUtils::ToLower(line.substr(0, pos)),
78         line.substr(pos + 2));
79   }
80 
81   // The connection header is prohibited in HTTP/2.
82   spdy_headers_.erase("connection");
83 
84   // Override the URL with the X-Original-Url header, if present.
85   auto it = spdy_headers_.find("x-original-url");
86   if (it != spdy_headers_.end()) {
87     x_original_url_ = it->second;
88     HandleXOriginalUrl();
89   }
90 
91   // X-Push-URL header is a relatively quick way to support sever push
92   // in the toy server.  A production server should use link=preload
93   // stuff as described in https://w3c.github.io/preload/.
94   it = spdy_headers_.find("x-push-url");
95   if (it != spdy_headers_.end()) {
96     absl::string_view push_urls = it->second;
97     size_t start = 0;
98     while (start < push_urls.length()) {
99       size_t pos = push_urls.find('\0', start);
100       if (pos == std::string::npos) {
101         push_urls_.push_back(absl::string_view(push_urls.data() + start,
102                                                push_urls.length() - start));
103         break;
104       }
105       push_urls_.push_back(absl::string_view(push_urls.data() + start, pos));
106       start += pos + 1;
107     }
108   }
109 
110   body_ = absl::string_view(file_contents_.data() + start,
111                             file_contents_.size() - start);
112 }
113 
SetHostPathFromBase(absl::string_view base)114 void QuicMemoryCacheBackend::ResourceFile::SetHostPathFromBase(
115     absl::string_view base) {
116   QUICHE_DCHECK(base[0] != '/') << base;
117   size_t path_start = base.find_first_of('/');
118   if (path_start == absl::string_view::npos) {
119     host_ = std::string(base);
120     path_ = "";
121     return;
122   }
123 
124   host_ = std::string(base.substr(0, path_start));
125   size_t query_start = base.find_first_of(',');
126   if (query_start > 0) {
127     path_ = std::string(base.substr(path_start, query_start - 1));
128   } else {
129     path_ = std::string(base.substr(path_start));
130   }
131 }
132 
RemoveScheme(absl::string_view url)133 absl::string_view QuicMemoryCacheBackend::ResourceFile::RemoveScheme(
134     absl::string_view url) {
135   if (absl::StartsWith(url, "https://")) {
136     url.remove_prefix(8);
137   } else if (absl::StartsWith(url, "http://")) {
138     url.remove_prefix(7);
139   }
140   return url;
141 }
142 
HandleXOriginalUrl()143 void QuicMemoryCacheBackend::ResourceFile::HandleXOriginalUrl() {
144   absl::string_view url(x_original_url_);
145   SetHostPathFromBase(RemoveScheme(url));
146 }
147 
GetResponse(absl::string_view host,absl::string_view path) const148 const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse(
149     absl::string_view host, absl::string_view path) const {
150   QuicWriterMutexLock lock(&response_mutex_);
151 
152   auto it = responses_.find(GetKey(host, path));
153   if (it == responses_.end()) {
154     uint64_t ignored = 0;
155     if (generate_bytes_response_) {
156       if (absl::SimpleAtoi(absl::string_view(path.data() + 1, path.size() - 1),
157                            &ignored)) {
158         // The actual parsed length is ignored here and will be recomputed
159         // by the caller.
160         return generate_bytes_response_.get();
161       }
162     }
163     QUIC_DVLOG(1) << "Get response for resource failed: host " << host
164                   << " path " << path;
165     if (default_response_) {
166       return default_response_.get();
167     }
168     return nullptr;
169   }
170   return it->second.get();
171 }
172 
173 using ServerPushInfo = QuicBackendResponse::ServerPushInfo;
174 using SpecialResponseType = QuicBackendResponse::SpecialResponseType;
175 
AddSimpleResponse(absl::string_view host,absl::string_view path,int response_code,absl::string_view body)176 void QuicMemoryCacheBackend::AddSimpleResponse(absl::string_view host,
177                                                absl::string_view path,
178                                                int response_code,
179                                                absl::string_view body) {
180   Http2HeaderBlock response_headers;
181   response_headers[":status"] = absl::StrCat(response_code);
182   response_headers["content-length"] = absl::StrCat(body.length());
183   AddResponse(host, path, std::move(response_headers), body);
184 }
185 
AddSimpleResponseWithServerPushResources(absl::string_view host,absl::string_view path,int response_code,absl::string_view body,std::list<ServerPushInfo> push_resources)186 void QuicMemoryCacheBackend::AddSimpleResponseWithServerPushResources(
187     absl::string_view host, absl::string_view path, int response_code,
188     absl::string_view body, std::list<ServerPushInfo> push_resources) {
189   AddSimpleResponse(host, path, response_code, body);
190   MaybeAddServerPushResources(host, path, push_resources);
191 }
192 
AddDefaultResponse(QuicBackendResponse * response)193 void QuicMemoryCacheBackend::AddDefaultResponse(QuicBackendResponse* response) {
194   QuicWriterMutexLock lock(&response_mutex_);
195   default_response_.reset(response);
196 }
197 
AddResponse(absl::string_view host,absl::string_view path,Http2HeaderBlock response_headers,absl::string_view response_body)198 void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
199                                          absl::string_view path,
200                                          Http2HeaderBlock response_headers,
201                                          absl::string_view response_body) {
202   AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
203                   std::move(response_headers), response_body,
204                   Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
205 }
206 
AddResponse(absl::string_view host,absl::string_view path,Http2HeaderBlock response_headers,absl::string_view response_body,Http2HeaderBlock response_trailers)207 void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
208                                          absl::string_view path,
209                                          Http2HeaderBlock response_headers,
210                                          absl::string_view response_body,
211                                          Http2HeaderBlock response_trailers) {
212   AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
213                   std::move(response_headers), response_body,
214                   std::move(response_trailers),
215                   std::vector<spdy::Http2HeaderBlock>());
216 }
217 
SetResponseDelay(absl::string_view host,absl::string_view path,QuicTime::Delta delay)218 bool QuicMemoryCacheBackend::SetResponseDelay(absl::string_view host,
219                                               absl::string_view path,
220                                               QuicTime::Delta delay) {
221   QuicWriterMutexLock lock(&response_mutex_);
222   auto it = responses_.find(GetKey(host, path));
223   if (it == responses_.end()) return false;
224 
225   it->second->set_delay(delay);
226   return true;
227 }
228 
AddResponseWithEarlyHints(absl::string_view host,absl::string_view path,spdy::Http2HeaderBlock response_headers,absl::string_view response_body,const std::vector<spdy::Http2HeaderBlock> & early_hints)229 void QuicMemoryCacheBackend::AddResponseWithEarlyHints(
230     absl::string_view host, absl::string_view path,
231     spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
232     const std::vector<spdy::Http2HeaderBlock>& early_hints) {
233   AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
234                   std::move(response_headers), response_body,
235                   Http2HeaderBlock(), early_hints);
236 }
237 
AddSpecialResponse(absl::string_view host,absl::string_view path,SpecialResponseType response_type)238 void QuicMemoryCacheBackend::AddSpecialResponse(
239     absl::string_view host, absl::string_view path,
240     SpecialResponseType response_type) {
241   AddResponseImpl(host, path, response_type, Http2HeaderBlock(), "",
242                   Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
243 }
244 
AddSpecialResponse(absl::string_view host,absl::string_view path,spdy::Http2HeaderBlock response_headers,absl::string_view response_body,SpecialResponseType response_type)245 void QuicMemoryCacheBackend::AddSpecialResponse(
246     absl::string_view host, absl::string_view path,
247     spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
248     SpecialResponseType response_type) {
249   AddResponseImpl(host, path, response_type, std::move(response_headers),
250                   response_body, Http2HeaderBlock(),
251                   std::vector<spdy::Http2HeaderBlock>());
252 }
253 
QuicMemoryCacheBackend()254 QuicMemoryCacheBackend::QuicMemoryCacheBackend() : cache_initialized_(false) {}
255 
InitializeBackend(const std::string & cache_directory)256 bool QuicMemoryCacheBackend::InitializeBackend(
257     const std::string& cache_directory) {
258   if (cache_directory.empty()) {
259     QUIC_BUG(quic_bug_10932_1) << "cache_directory must not be empty.";
260     return false;
261   }
262   QUIC_LOG(INFO)
263       << "Attempting to initialize QuicMemoryCacheBackend from directory: "
264       << cache_directory;
265   std::vector<std::string> files;
266   if (!quiche::EnumerateDirectoryRecursively(cache_directory, files)) {
267     QUIC_BUG(QuicMemoryCacheBackend unreadable directory)
268         << "Can't read QuicMemoryCacheBackend directory: " << cache_directory;
269     return false;
270   }
271   std::list<std::unique_ptr<ResourceFile>> resource_files;
272   for (const auto& filename : files) {
273     std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename));
274 
275     // Tease apart filename into host and path.
276     std::string base(resource_file->file_name());
277     // Transform windows path separators to URL path separators.
278     for (size_t i = 0; i < base.length(); ++i) {
279       if (base[i] == '\\') {
280         base[i] = '/';
281       }
282     }
283     base.erase(0, cache_directory.length());
284     if (base[0] == '/') {
285       base.erase(0, 1);
286     }
287 
288     resource_file->SetHostPathFromBase(base);
289     resource_file->Read();
290 
291     AddResponse(resource_file->host(), resource_file->path(),
292                 resource_file->spdy_headers().Clone(), resource_file->body());
293 
294     resource_files.push_back(std::move(resource_file));
295   }
296 
297   for (const auto& resource_file : resource_files) {
298     std::list<ServerPushInfo> push_resources;
299     for (const auto& push_url : resource_file->push_urls()) {
300       QuicUrl url(push_url);
301       const QuicBackendResponse* response = GetResponse(url.host(), url.path());
302       if (!response) {
303         QUIC_BUG(quic_bug_10932_2)
304             << "Push URL '" << push_url << "' not found.";
305         return false;
306       }
307       push_resources.push_back(ServerPushInfo(url, response->headers().Clone(),
308                                               kV3LowestPriority,
309                                               (std::string(response->body()))));
310     }
311     MaybeAddServerPushResources(resource_file->host(), resource_file->path(),
312                                 push_resources);
313   }
314 
315   cache_initialized_ = true;
316   return true;
317 }
318 
GenerateDynamicResponses()319 void QuicMemoryCacheBackend::GenerateDynamicResponses() {
320   QuicWriterMutexLock lock(&response_mutex_);
321   // Add a generate bytes response.
322   spdy::Http2HeaderBlock response_headers;
323   response_headers[":status"] = "200";
324   generate_bytes_response_ = std::make_unique<QuicBackendResponse>();
325   generate_bytes_response_->set_headers(std::move(response_headers));
326   generate_bytes_response_->set_response_type(
327       QuicBackendResponse::GENERATE_BYTES);
328 }
329 
EnableWebTransport()330 void QuicMemoryCacheBackend::EnableWebTransport() {
331   enable_webtransport_ = true;
332 }
333 
IsBackendInitialized() const334 bool QuicMemoryCacheBackend::IsBackendInitialized() const {
335   return cache_initialized_;
336 }
337 
FetchResponseFromBackend(const Http2HeaderBlock & request_headers,const std::string &,QuicSimpleServerBackend::RequestHandler * quic_stream)338 void QuicMemoryCacheBackend::FetchResponseFromBackend(
339     const Http2HeaderBlock& request_headers,
340     const std::string& /*request_body*/,
341     QuicSimpleServerBackend::RequestHandler* quic_stream) {
342   const QuicBackendResponse* quic_response = nullptr;
343   // Find response in cache. If not found, send error response.
344   auto authority = request_headers.find(":authority");
345   auto path = request_headers.find(":path");
346   if (authority != request_headers.end() && path != request_headers.end()) {
347     quic_response = GetResponse(authority->second, path->second);
348   }
349 
350   std::string request_url;
351   if (authority != request_headers.end()) {
352     request_url = std::string(authority->second);
353   }
354   if (path != request_headers.end()) {
355     request_url += std::string(path->second);
356   }
357   QUIC_DVLOG(1)
358       << "Fetching QUIC response from backend in-memory cache for url "
359       << request_url;
360   quic_stream->OnResponseBackendComplete(quic_response);
361 }
362 
363 // The memory cache does not have a per-stream handler
CloseBackendResponseStream(QuicSimpleServerBackend::RequestHandler *)364 void QuicMemoryCacheBackend::CloseBackendResponseStream(
365     QuicSimpleServerBackend::RequestHandler* /*quic_stream*/) {}
366 
GetServerPushResources(std::string request_url)367 std::list<ServerPushInfo> QuicMemoryCacheBackend::GetServerPushResources(
368     std::string request_url) {
369   QuicWriterMutexLock lock(&response_mutex_);
370 
371   std::list<ServerPushInfo> resources;
372   auto resource_range = server_push_resources_.equal_range(request_url);
373   for (auto it = resource_range.first; it != resource_range.second; ++it) {
374     resources.push_back(it->second);
375   }
376   QUIC_DVLOG(1) << "Found " << resources.size() << " push resources for "
377                 << request_url;
378   return resources;
379 }
380 
381 QuicMemoryCacheBackend::WebTransportResponse
ProcessWebTransportRequest(const spdy::Http2HeaderBlock & request_headers,WebTransportSession * session)382 QuicMemoryCacheBackend::ProcessWebTransportRequest(
383     const spdy::Http2HeaderBlock& request_headers,
384     WebTransportSession* session) {
385   if (!SupportsWebTransport()) {
386     return QuicSimpleServerBackend::ProcessWebTransportRequest(request_headers,
387                                                                session);
388   }
389 
390   auto path_it = request_headers.find(":path");
391   if (path_it == request_headers.end()) {
392     WebTransportResponse response;
393     response.response_headers[":status"] = "400";
394     return response;
395   }
396   absl::string_view path = path_it->second;
397   if (path == "/echo") {
398     WebTransportResponse response;
399     response.response_headers[":status"] = "200";
400     response.visitor =
401         std::make_unique<EchoWebTransportSessionVisitor>(session);
402     return response;
403   }
404 
405   WebTransportResponse response;
406   response.response_headers[":status"] = "404";
407   return response;
408 }
409 
~QuicMemoryCacheBackend()410 QuicMemoryCacheBackend::~QuicMemoryCacheBackend() {
411   {
412     QuicWriterMutexLock lock(&response_mutex_);
413     responses_.clear();
414   }
415 }
416 
AddResponseImpl(absl::string_view host,absl::string_view path,SpecialResponseType response_type,Http2HeaderBlock response_headers,absl::string_view response_body,Http2HeaderBlock response_trailers,const std::vector<spdy::Http2HeaderBlock> & early_hints)417 void QuicMemoryCacheBackend::AddResponseImpl(
418     absl::string_view host, absl::string_view path,
419     SpecialResponseType response_type, Http2HeaderBlock response_headers,
420     absl::string_view response_body, Http2HeaderBlock response_trailers,
421     const std::vector<spdy::Http2HeaderBlock>& early_hints) {
422   QuicWriterMutexLock lock(&response_mutex_);
423 
424   QUICHE_DCHECK(!host.empty())
425       << "Host must be populated, e.g. \"www.google.com\"";
426   std::string key = GetKey(host, path);
427   if (responses_.contains(key)) {
428     QUIC_BUG(quic_bug_10932_3)
429         << "Response for '" << key << "' already exists!";
430     return;
431   }
432   auto new_response = std::make_unique<QuicBackendResponse>();
433   new_response->set_response_type(response_type);
434   new_response->set_headers(std::move(response_headers));
435   new_response->set_body(response_body);
436   new_response->set_trailers(std::move(response_trailers));
437   for (auto& headers : early_hints) {
438     new_response->AddEarlyHints(headers);
439   }
440   QUIC_DVLOG(1) << "Add response with key " << key;
441   responses_[key] = std::move(new_response);
442 }
443 
GetKey(absl::string_view host,absl::string_view path) const444 std::string QuicMemoryCacheBackend::GetKey(absl::string_view host,
445                                            absl::string_view path) const {
446   std::string host_string = std::string(host);
447   size_t port = host_string.find(':');
448   if (port != std::string::npos)
449     host_string = std::string(host_string.c_str(), port);
450   return host_string + std::string(path);
451 }
452 
MaybeAddServerPushResources(absl::string_view request_host,absl::string_view request_path,std::list<ServerPushInfo> push_resources)453 void QuicMemoryCacheBackend::MaybeAddServerPushResources(
454     absl::string_view request_host, absl::string_view request_path,
455     std::list<ServerPushInfo> push_resources) {
456   std::string request_url = GetKey(request_host, request_path);
457 
458   for (const auto& push_resource : push_resources) {
459     if (PushResourceExistsInCache(request_url, push_resource)) {
460       continue;
461     }
462 
463     QUIC_DVLOG(1) << "Add request-resource association: request url "
464                   << request_url << " push url "
465                   << push_resource.request_url.ToString()
466                   << " response headers "
467                   << push_resource.headers.DebugString();
468     {
469       QuicWriterMutexLock lock(&response_mutex_);
470       server_push_resources_.insert(std::make_pair(request_url, push_resource));
471     }
472     std::string host = push_resource.request_url.host();
473     if (host.empty()) {
474       host = std::string(request_host);
475     }
476     std::string path = push_resource.request_url.path();
477     bool found_existing_response = false;
478     {
479       QuicWriterMutexLock lock(&response_mutex_);
480       found_existing_response = responses_.contains(GetKey(host, path));
481     }
482     if (!found_existing_response) {
483       // Add a server push response to responses map, if it is not in the map.
484       absl::string_view body = push_resource.body;
485       QUIC_DVLOG(1) << "Add response for push resource: host " << host
486                     << " path " << path;
487       AddResponse(host, path, push_resource.headers.Clone(), body);
488     }
489   }
490 }
491 
PushResourceExistsInCache(std::string original_request_url,ServerPushInfo resource)492 bool QuicMemoryCacheBackend::PushResourceExistsInCache(
493     std::string original_request_url, ServerPushInfo resource) {
494   QuicWriterMutexLock lock(&response_mutex_);
495   auto resource_range =
496       server_push_resources_.equal_range(original_request_url);
497   for (auto it = resource_range.first; it != resource_range.second; ++it) {
498     ServerPushInfo push_resource = it->second;
499     if (push_resource.request_url.ToString() ==
500         resource.request_url.ToString()) {
501       return true;
502     }
503   }
504   return false;
505 }
506 
507 }  // namespace quic
508