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