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 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 SpecialResponseType = QuicBackendResponse::SpecialResponseType;
174
AddSimpleResponse(absl::string_view host,absl::string_view path,int response_code,absl::string_view body)175 void QuicMemoryCacheBackend::AddSimpleResponse(absl::string_view host,
176 absl::string_view path,
177 int response_code,
178 absl::string_view body) {
179 Http2HeaderBlock response_headers;
180 response_headers[":status"] = absl::StrCat(response_code);
181 response_headers["content-length"] = absl::StrCat(body.length());
182 AddResponse(host, path, std::move(response_headers), body);
183 }
184
AddDefaultResponse(QuicBackendResponse * response)185 void QuicMemoryCacheBackend::AddDefaultResponse(QuicBackendResponse* response) {
186 QuicWriterMutexLock lock(&response_mutex_);
187 default_response_.reset(response);
188 }
189
AddResponse(absl::string_view host,absl::string_view path,Http2HeaderBlock response_headers,absl::string_view response_body)190 void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
191 absl::string_view path,
192 Http2HeaderBlock response_headers,
193 absl::string_view response_body) {
194 AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
195 std::move(response_headers), response_body,
196 Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
197 }
198
AddResponse(absl::string_view host,absl::string_view path,Http2HeaderBlock response_headers,absl::string_view response_body,Http2HeaderBlock response_trailers)199 void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
200 absl::string_view path,
201 Http2HeaderBlock response_headers,
202 absl::string_view response_body,
203 Http2HeaderBlock response_trailers) {
204 AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
205 std::move(response_headers), response_body,
206 std::move(response_trailers),
207 std::vector<spdy::Http2HeaderBlock>());
208 }
209
SetResponseDelay(absl::string_view host,absl::string_view path,QuicTime::Delta delay)210 bool QuicMemoryCacheBackend::SetResponseDelay(absl::string_view host,
211 absl::string_view path,
212 QuicTime::Delta delay) {
213 QuicWriterMutexLock lock(&response_mutex_);
214 auto it = responses_.find(GetKey(host, path));
215 if (it == responses_.end()) return false;
216
217 it->second->set_delay(delay);
218 return true;
219 }
220
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)221 void QuicMemoryCacheBackend::AddResponseWithEarlyHints(
222 absl::string_view host, absl::string_view path,
223 spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
224 const std::vector<spdy::Http2HeaderBlock>& early_hints) {
225 AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
226 std::move(response_headers), response_body,
227 Http2HeaderBlock(), early_hints);
228 }
229
AddSpecialResponse(absl::string_view host,absl::string_view path,SpecialResponseType response_type)230 void QuicMemoryCacheBackend::AddSpecialResponse(
231 absl::string_view host, absl::string_view path,
232 SpecialResponseType response_type) {
233 AddResponseImpl(host, path, response_type, Http2HeaderBlock(), "",
234 Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
235 }
236
AddSpecialResponse(absl::string_view host,absl::string_view path,spdy::Http2HeaderBlock response_headers,absl::string_view response_body,SpecialResponseType response_type)237 void QuicMemoryCacheBackend::AddSpecialResponse(
238 absl::string_view host, absl::string_view path,
239 spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
240 SpecialResponseType response_type) {
241 AddResponseImpl(host, path, response_type, std::move(response_headers),
242 response_body, Http2HeaderBlock(),
243 std::vector<spdy::Http2HeaderBlock>());
244 }
245
QuicMemoryCacheBackend()246 QuicMemoryCacheBackend::QuicMemoryCacheBackend() : cache_initialized_(false) {}
247
InitializeBackend(const std::string & cache_directory)248 bool QuicMemoryCacheBackend::InitializeBackend(
249 const std::string& cache_directory) {
250 if (cache_directory.empty()) {
251 QUIC_BUG(quic_bug_10932_1) << "cache_directory must not be empty.";
252 return false;
253 }
254 QUIC_LOG(INFO)
255 << "Attempting to initialize QuicMemoryCacheBackend from directory: "
256 << cache_directory;
257 std::vector<std::string> files;
258 if (!quiche::EnumerateDirectoryRecursively(cache_directory, files)) {
259 QUIC_BUG(QuicMemoryCacheBackend unreadable directory)
260 << "Can't read QuicMemoryCacheBackend directory: " << cache_directory;
261 return false;
262 }
263 for (const auto& filename : files) {
264 std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename));
265
266 // Tease apart filename into host and path.
267 std::string base(resource_file->file_name());
268 // Transform windows path separators to URL path separators.
269 for (size_t i = 0; i < base.length(); ++i) {
270 if (base[i] == '\\') {
271 base[i] = '/';
272 }
273 }
274 base.erase(0, cache_directory.length());
275 if (base[0] == '/') {
276 base.erase(0, 1);
277 }
278
279 resource_file->SetHostPathFromBase(base);
280 resource_file->Read();
281
282 AddResponse(resource_file->host(), resource_file->path(),
283 resource_file->spdy_headers().Clone(), resource_file->body());
284 }
285
286 cache_initialized_ = true;
287 return true;
288 }
289
GenerateDynamicResponses()290 void QuicMemoryCacheBackend::GenerateDynamicResponses() {
291 QuicWriterMutexLock lock(&response_mutex_);
292 // Add a generate bytes response.
293 spdy::Http2HeaderBlock response_headers;
294 response_headers[":status"] = "200";
295 generate_bytes_response_ = std::make_unique<QuicBackendResponse>();
296 generate_bytes_response_->set_headers(std::move(response_headers));
297 generate_bytes_response_->set_response_type(
298 QuicBackendResponse::GENERATE_BYTES);
299 }
300
EnableWebTransport()301 void QuicMemoryCacheBackend::EnableWebTransport() {
302 enable_webtransport_ = true;
303 }
304
IsBackendInitialized() const305 bool QuicMemoryCacheBackend::IsBackendInitialized() const {
306 return cache_initialized_;
307 }
308
FetchResponseFromBackend(const Http2HeaderBlock & request_headers,const std::string &,QuicSimpleServerBackend::RequestHandler * quic_stream)309 void QuicMemoryCacheBackend::FetchResponseFromBackend(
310 const Http2HeaderBlock& request_headers,
311 const std::string& /*request_body*/,
312 QuicSimpleServerBackend::RequestHandler* quic_stream) {
313 const QuicBackendResponse* quic_response = nullptr;
314 // Find response in cache. If not found, send error response.
315 auto authority = request_headers.find(":authority");
316 auto path = request_headers.find(":path");
317 if (authority != request_headers.end() && path != request_headers.end()) {
318 quic_response = GetResponse(authority->second, path->second);
319 }
320
321 std::string request_url;
322 if (authority != request_headers.end()) {
323 request_url = std::string(authority->second);
324 }
325 if (path != request_headers.end()) {
326 request_url += std::string(path->second);
327 }
328 QUIC_DVLOG(1)
329 << "Fetching QUIC response from backend in-memory cache for url "
330 << request_url;
331 quic_stream->OnResponseBackendComplete(quic_response);
332 }
333
334 // The memory cache does not have a per-stream handler
CloseBackendResponseStream(QuicSimpleServerBackend::RequestHandler *)335 void QuicMemoryCacheBackend::CloseBackendResponseStream(
336 QuicSimpleServerBackend::RequestHandler* /*quic_stream*/) {}
337
338 QuicMemoryCacheBackend::WebTransportResponse
ProcessWebTransportRequest(const spdy::Http2HeaderBlock & request_headers,WebTransportSession * session)339 QuicMemoryCacheBackend::ProcessWebTransportRequest(
340 const spdy::Http2HeaderBlock& request_headers,
341 WebTransportSession* session) {
342 if (!SupportsWebTransport()) {
343 return QuicSimpleServerBackend::ProcessWebTransportRequest(request_headers,
344 session);
345 }
346
347 auto path_it = request_headers.find(":path");
348 if (path_it == request_headers.end()) {
349 WebTransportResponse response;
350 response.response_headers[":status"] = "400";
351 return response;
352 }
353 absl::string_view path = path_it->second;
354 if (path == "/echo") {
355 WebTransportResponse response;
356 response.response_headers[":status"] = "200";
357 response.visitor =
358 std::make_unique<EchoWebTransportSessionVisitor>(session);
359 return response;
360 }
361
362 WebTransportResponse response;
363 response.response_headers[":status"] = "404";
364 return response;
365 }
366
~QuicMemoryCacheBackend()367 QuicMemoryCacheBackend::~QuicMemoryCacheBackend() {
368 {
369 QuicWriterMutexLock lock(&response_mutex_);
370 responses_.clear();
371 }
372 }
373
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)374 void QuicMemoryCacheBackend::AddResponseImpl(
375 absl::string_view host, absl::string_view path,
376 SpecialResponseType response_type, Http2HeaderBlock response_headers,
377 absl::string_view response_body, Http2HeaderBlock response_trailers,
378 const std::vector<spdy::Http2HeaderBlock>& early_hints) {
379 QuicWriterMutexLock lock(&response_mutex_);
380
381 QUICHE_DCHECK(!host.empty())
382 << "Host must be populated, e.g. \"www.google.com\"";
383 std::string key = GetKey(host, path);
384 if (responses_.contains(key)) {
385 QUIC_BUG(quic_bug_10932_3)
386 << "Response for '" << key << "' already exists!";
387 return;
388 }
389 auto new_response = std::make_unique<QuicBackendResponse>();
390 new_response->set_response_type(response_type);
391 new_response->set_headers(std::move(response_headers));
392 new_response->set_body(response_body);
393 new_response->set_trailers(std::move(response_trailers));
394 for (auto& headers : early_hints) {
395 new_response->AddEarlyHints(headers);
396 }
397 QUIC_DVLOG(1) << "Add response with key " << key;
398 responses_[key] = std::move(new_response);
399 }
400
GetKey(absl::string_view host,absl::string_view path) const401 std::string QuicMemoryCacheBackend::GetKey(absl::string_view host,
402 absl::string_view path) const {
403 std::string host_string = std::string(host);
404 size_t port = host_string.find(':');
405 if (port != std::string::npos)
406 host_string = std::string(host_string.c_str(), port);
407 return host_string + std::string(path);
408 }
409
410 } // namespace quic
411