xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/tools/quic_memory_cache_backend.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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   std::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   for (size_t start = 0; start < file_contents_.length();) {
42     size_t pos = file_contents_.find('\n', start);
43     if (pos == std::string::npos) {
44       QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
45       return;
46     }
47     size_t len = pos - start;
48     // Support both dos and unix line endings for convenience.
49     if (file_contents_[pos - 1] == '\r') {
50       len -= 1;
51     }
52     absl::string_view line(file_contents_.data() + start, len);
53     start = pos + 1;
54     // Headers end with an empty line.
55     if (line.empty()) {
56       body_ = absl::string_view(file_contents_.data() + start,
57                                 file_contents_.size() - start);
58       break;
59     }
60     // Extract the status from the HTTP first line.
61     if (line.substr(0, 4) == "HTTP") {
62       pos = line.find(' ');
63       if (pos == std::string::npos) {
64         QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: "
65                          << file_name_;
66         return;
67       }
68       spdy_headers_[":status"] = line.substr(pos + 1, 3);
69       continue;
70     }
71     // Headers are "key: value".
72     pos = line.find(": ");
73     if (pos == std::string::npos) {
74       QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
75       return;
76     }
77     spdy_headers_.AppendValueOrAddHeader(
78         quiche::QuicheTextUtils::ToLower(line.substr(0, pos)),
79         line.substr(pos + 2));
80   }
81 
82   // The connection header is prohibited in HTTP/2.
83   spdy_headers_.erase("connection");
84 
85   // Override the URL with the X-Original-Url header, if present.
86   if (auto it = spdy_headers_.find("x-original-url");
87       it != spdy_headers_.end()) {
88     x_original_url_ = it->second;
89     HandleXOriginalUrl();
90   }
91 }
92 
SetHostPathFromBase(absl::string_view base)93 void QuicMemoryCacheBackend::ResourceFile::SetHostPathFromBase(
94     absl::string_view base) {
95   QUICHE_DCHECK(base[0] != '/') << base;
96   size_t path_start = base.find_first_of('/');
97   if (path_start == absl::string_view::npos) {
98     host_ = std::string(base);
99     path_ = "";
100     return;
101   }
102 
103   host_ = std::string(base.substr(0, path_start));
104   size_t query_start = base.find_first_of(',');
105   if (query_start > 0) {
106     path_ = std::string(base.substr(path_start, query_start - 1));
107   } else {
108     path_ = std::string(base.substr(path_start));
109   }
110 }
111 
RemoveScheme(absl::string_view url)112 absl::string_view QuicMemoryCacheBackend::ResourceFile::RemoveScheme(
113     absl::string_view url) {
114   if (absl::StartsWith(url, "https://")) {
115     url.remove_prefix(8);
116   } else if (absl::StartsWith(url, "http://")) {
117     url.remove_prefix(7);
118   }
119   return url;
120 }
121 
HandleXOriginalUrl()122 void QuicMemoryCacheBackend::ResourceFile::HandleXOriginalUrl() {
123   absl::string_view url(x_original_url_);
124   SetHostPathFromBase(RemoveScheme(url));
125 }
126 
GetResponse(absl::string_view host,absl::string_view path) const127 const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse(
128     absl::string_view host, absl::string_view path) const {
129   QuicWriterMutexLock lock(&response_mutex_);
130 
131   auto it = responses_.find(GetKey(host, path));
132   if (it == responses_.end()) {
133     uint64_t ignored = 0;
134     if (generate_bytes_response_) {
135       if (absl::SimpleAtoi(absl::string_view(path.data() + 1, path.size() - 1),
136                            &ignored)) {
137         // The actual parsed length is ignored here and will be recomputed
138         // by the caller.
139         return generate_bytes_response_.get();
140       }
141     }
142     QUIC_DVLOG(1) << "Get response for resource failed: host " << host
143                   << " path " << path;
144     if (default_response_) {
145       return default_response_.get();
146     }
147     return nullptr;
148   }
149   return it->second.get();
150 }
151 
152 using SpecialResponseType = QuicBackendResponse::SpecialResponseType;
153 
AddSimpleResponse(absl::string_view host,absl::string_view path,int response_code,absl::string_view body)154 void QuicMemoryCacheBackend::AddSimpleResponse(absl::string_view host,
155                                                absl::string_view path,
156                                                int response_code,
157                                                absl::string_view body) {
158   Http2HeaderBlock response_headers;
159   response_headers[":status"] = absl::StrCat(response_code);
160   response_headers["content-length"] = absl::StrCat(body.length());
161   AddResponse(host, path, std::move(response_headers), body);
162 }
163 
AddDefaultResponse(QuicBackendResponse * response)164 void QuicMemoryCacheBackend::AddDefaultResponse(QuicBackendResponse* response) {
165   QuicWriterMutexLock lock(&response_mutex_);
166   default_response_.reset(response);
167 }
168 
AddResponse(absl::string_view host,absl::string_view path,Http2HeaderBlock response_headers,absl::string_view response_body)169 void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
170                                          absl::string_view path,
171                                          Http2HeaderBlock response_headers,
172                                          absl::string_view response_body) {
173   AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
174                   std::move(response_headers), response_body,
175                   Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
176 }
177 
AddResponse(absl::string_view host,absl::string_view path,Http2HeaderBlock response_headers,absl::string_view response_body,Http2HeaderBlock response_trailers)178 void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
179                                          absl::string_view path,
180                                          Http2HeaderBlock response_headers,
181                                          absl::string_view response_body,
182                                          Http2HeaderBlock response_trailers) {
183   AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
184                   std::move(response_headers), response_body,
185                   std::move(response_trailers),
186                   std::vector<spdy::Http2HeaderBlock>());
187 }
188 
SetResponseDelay(absl::string_view host,absl::string_view path,QuicTime::Delta delay)189 bool QuicMemoryCacheBackend::SetResponseDelay(absl::string_view host,
190                                               absl::string_view path,
191                                               QuicTime::Delta delay) {
192   QuicWriterMutexLock lock(&response_mutex_);
193   auto it = responses_.find(GetKey(host, path));
194   if (it == responses_.end()) return false;
195 
196   it->second->set_delay(delay);
197   return true;
198 }
199 
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)200 void QuicMemoryCacheBackend::AddResponseWithEarlyHints(
201     absl::string_view host, absl::string_view path,
202     spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
203     const std::vector<spdy::Http2HeaderBlock>& early_hints) {
204   AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
205                   std::move(response_headers), response_body,
206                   Http2HeaderBlock(), early_hints);
207 }
208 
AddSpecialResponse(absl::string_view host,absl::string_view path,SpecialResponseType response_type)209 void QuicMemoryCacheBackend::AddSpecialResponse(
210     absl::string_view host, absl::string_view path,
211     SpecialResponseType response_type) {
212   AddResponseImpl(host, path, response_type, Http2HeaderBlock(), "",
213                   Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
214 }
215 
AddSpecialResponse(absl::string_view host,absl::string_view path,spdy::Http2HeaderBlock response_headers,absl::string_view response_body,SpecialResponseType response_type)216 void QuicMemoryCacheBackend::AddSpecialResponse(
217     absl::string_view host, absl::string_view path,
218     spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
219     SpecialResponseType response_type) {
220   AddResponseImpl(host, path, response_type, std::move(response_headers),
221                   response_body, Http2HeaderBlock(),
222                   std::vector<spdy::Http2HeaderBlock>());
223 }
224 
QuicMemoryCacheBackend()225 QuicMemoryCacheBackend::QuicMemoryCacheBackend() : cache_initialized_(false) {}
226 
InitializeBackend(const std::string & cache_directory)227 bool QuicMemoryCacheBackend::InitializeBackend(
228     const std::string& cache_directory) {
229   if (cache_directory.empty()) {
230     QUIC_BUG(quic_bug_10932_1) << "cache_directory must not be empty.";
231     return false;
232   }
233   QUIC_LOG(INFO)
234       << "Attempting to initialize QuicMemoryCacheBackend from directory: "
235       << cache_directory;
236   std::vector<std::string> files;
237   if (!quiche::EnumerateDirectoryRecursively(cache_directory, files)) {
238     QUIC_BUG(QuicMemoryCacheBackend unreadable directory)
239         << "Can't read QuicMemoryCacheBackend directory: " << cache_directory;
240     return false;
241   }
242   for (const auto& filename : files) {
243     std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename));
244 
245     // Tease apart filename into host and path.
246     std::string base(resource_file->file_name());
247     // Transform windows path separators to URL path separators.
248     for (size_t i = 0; i < base.length(); ++i) {
249       if (base[i] == '\\') {
250         base[i] = '/';
251       }
252     }
253     base.erase(0, cache_directory.length());
254     if (base[0] == '/') {
255       base.erase(0, 1);
256     }
257 
258     resource_file->SetHostPathFromBase(base);
259     resource_file->Read();
260 
261     AddResponse(resource_file->host(), resource_file->path(),
262                 resource_file->spdy_headers().Clone(), resource_file->body());
263   }
264 
265   cache_initialized_ = true;
266   return true;
267 }
268 
GenerateDynamicResponses()269 void QuicMemoryCacheBackend::GenerateDynamicResponses() {
270   QuicWriterMutexLock lock(&response_mutex_);
271   // Add a generate bytes response.
272   spdy::Http2HeaderBlock response_headers;
273   response_headers[":status"] = "200";
274   generate_bytes_response_ = std::make_unique<QuicBackendResponse>();
275   generate_bytes_response_->set_headers(std::move(response_headers));
276   generate_bytes_response_->set_response_type(
277       QuicBackendResponse::GENERATE_BYTES);
278 }
279 
EnableWebTransport()280 void QuicMemoryCacheBackend::EnableWebTransport() {
281   enable_webtransport_ = true;
282 }
283 
IsBackendInitialized() const284 bool QuicMemoryCacheBackend::IsBackendInitialized() const {
285   return cache_initialized_;
286 }
287 
FetchResponseFromBackend(const Http2HeaderBlock & request_headers,const std::string &,QuicSimpleServerBackend::RequestHandler * quic_stream)288 void QuicMemoryCacheBackend::FetchResponseFromBackend(
289     const Http2HeaderBlock& request_headers,
290     const std::string& /*request_body*/,
291     QuicSimpleServerBackend::RequestHandler* quic_stream) {
292   const QuicBackendResponse* quic_response = nullptr;
293   // Find response in cache. If not found, send error response.
294   auto authority = request_headers.find(":authority");
295   auto path = request_headers.find(":path");
296   if (authority != request_headers.end() && path != request_headers.end()) {
297     quic_response = GetResponse(authority->second, path->second);
298   }
299 
300   std::string request_url;
301   if (authority != request_headers.end()) {
302     request_url = std::string(authority->second);
303   }
304   if (path != request_headers.end()) {
305     request_url += std::string(path->second);
306   }
307   QUIC_DVLOG(1)
308       << "Fetching QUIC response from backend in-memory cache for url "
309       << request_url;
310   quic_stream->OnResponseBackendComplete(quic_response);
311 }
312 
313 // The memory cache does not have a per-stream handler
CloseBackendResponseStream(QuicSimpleServerBackend::RequestHandler *)314 void QuicMemoryCacheBackend::CloseBackendResponseStream(
315     QuicSimpleServerBackend::RequestHandler* /*quic_stream*/) {}
316 
317 QuicMemoryCacheBackend::WebTransportResponse
ProcessWebTransportRequest(const spdy::Http2HeaderBlock & request_headers,WebTransportSession * session)318 QuicMemoryCacheBackend::ProcessWebTransportRequest(
319     const spdy::Http2HeaderBlock& request_headers,
320     WebTransportSession* session) {
321   if (!SupportsWebTransport()) {
322     return QuicSimpleServerBackend::ProcessWebTransportRequest(request_headers,
323                                                                session);
324   }
325 
326   auto path_it = request_headers.find(":path");
327   if (path_it == request_headers.end()) {
328     WebTransportResponse response;
329     response.response_headers[":status"] = "400";
330     return response;
331   }
332   absl::string_view path = path_it->second;
333   if (path == "/echo") {
334     WebTransportResponse response;
335     response.response_headers[":status"] = "200";
336     response.visitor =
337         std::make_unique<EchoWebTransportSessionVisitor>(session);
338     return response;
339   }
340 
341   WebTransportResponse response;
342   response.response_headers[":status"] = "404";
343   return response;
344 }
345 
~QuicMemoryCacheBackend()346 QuicMemoryCacheBackend::~QuicMemoryCacheBackend() {
347   {
348     QuicWriterMutexLock lock(&response_mutex_);
349     responses_.clear();
350   }
351 }
352 
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)353 void QuicMemoryCacheBackend::AddResponseImpl(
354     absl::string_view host, absl::string_view path,
355     SpecialResponseType response_type, Http2HeaderBlock response_headers,
356     absl::string_view response_body, Http2HeaderBlock response_trailers,
357     const std::vector<spdy::Http2HeaderBlock>& early_hints) {
358   QuicWriterMutexLock lock(&response_mutex_);
359 
360   QUICHE_DCHECK(!host.empty())
361       << "Host must be populated, e.g. \"www.google.com\"";
362   std::string key = GetKey(host, path);
363   if (responses_.contains(key)) {
364     QUIC_BUG(quic_bug_10932_3)
365         << "Response for '" << key << "' already exists!";
366     return;
367   }
368   auto new_response = std::make_unique<QuicBackendResponse>();
369   new_response->set_response_type(response_type);
370   new_response->set_headers(std::move(response_headers));
371   new_response->set_body(response_body);
372   new_response->set_trailers(std::move(response_trailers));
373   for (auto& headers : early_hints) {
374     new_response->AddEarlyHints(headers);
375   }
376   QUIC_DVLOG(1) << "Add response with key " << key;
377   responses_[key] = std::move(new_response);
378 }
379 
GetKey(absl::string_view host,absl::string_view path) const380 std::string QuicMemoryCacheBackend::GetKey(absl::string_view host,
381                                            absl::string_view path) const {
382   std::string host_string = std::string(host);
383   size_t port = host_string.find(':');
384   if (port != std::string::npos)
385     host_string = std::string(host_string.c_str(), port);
386   return host_string + std::string(path);
387 }
388 
389 }  // namespace quic
390