Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(117)

Side by Side Diff: content/browser/service_worker/link_header_support.cc

Issue 1811163002: Share link header parsing code between blink and content. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@base-optional
Patch Set: rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/service_worker/link_header_support.h" 5 #include "content/browser/service_worker/link_header_support.h"
6 6
7 #include "base/command_line.h" 7 #include "base/command_line.h"
8 #include "base/strings/string_split.h" 8 #include "base/strings/string_split.h"
9 #include "base/strings/string_util.h" 9 #include "base/strings/string_util.h"
10 #include "components/link_header_util/link_header_util.h"
10 #include "content/browser/loader/resource_message_filter.h" 11 #include "content/browser/loader/resource_message_filter.h"
11 #include "content/browser/loader/resource_request_info_impl.h" 12 #include "content/browser/loader/resource_request_info_impl.h"
12 #include "content/browser/service_worker/service_worker_context_wrapper.h" 13 #include "content/browser/service_worker/service_worker_context_wrapper.h"
13 #include "content/common/service_worker/service_worker_utils.h" 14 #include "content/common/service_worker/service_worker_utils.h"
14 #include "content/public/browser/browser_thread.h" 15 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/content_browser_client.h" 16 #include "content/public/browser/content_browser_client.h"
16 #include "content/public/common/content_client.h" 17 #include "content/public/common/content_client.h"
17 #include "content/public/common/content_switches.h" 18 #include "content/public/common/content_switches.h"
18 #include "content/public/common/origin_util.h" 19 #include "content/public/common/origin_util.h"
19 #include "net/http/http_util.h" 20 #include "net/http/http_util.h"
20 #include "net/url_request/url_request.h" 21 #include "net/url_request/url_request.h"
21 22
22 namespace content { 23 namespace content {
23 24
24 namespace { 25 namespace {
25 26
26 // A variation of base::StringTokenizer and net::HttpUtil::ValuesIterator.
27 // Takes the parsing of StringTokenizer and adds support for quoted strings that
28 // are quoted by matching <> (and does not support escaping in those strings).
29 // Also has the behavior of ValuesIterator where it strips whitespace from all
30 // values and only outputs non-empty values.
31 // Only supports ',' as separator and supports '' "" and <> as quote chars.
32 // TODO(mek): Figure out if there is a way to share this with the parsing code
33 // in blink::LinkHeader.
34 class ValueTokenizer {
35 public:
36 ValueTokenizer(std::string::const_iterator begin,
37 std::string::const_iterator end)
38 : token_begin_(begin), token_end_(begin), end_(end) {}
39
40 std::string::const_iterator token_begin() const { return token_begin_; }
41 std::string::const_iterator token_end() const { return token_end_; }
42
43 bool GetNext() {
44 while (GetNextInternal()) {
45 net::HttpUtil::TrimLWS(&token_begin_, &token_end_);
46
47 // Only return non-empty values.
48 if (token_begin_ != token_end_)
49 return true;
50 }
51 return false;
52 }
53
54 private:
55 // Updates token_begin_ and token_end_ to point to the (possibly empty) next
56 // token. Returns false if end-of-string was reached first.
57 bool GetNextInternal() {
58 // First time this is called token_end_ points to the first character in the
59 // input. Every other time token_end_ points to the delimiter at the end of
60 // the last returned token (which could be the end of the string).
61
62 // End of string, return false.
63 if (token_end_ == end_)
64 return false;
65
66 // Skip past the delimiter.
67 if (*token_end_ == ',')
68 ++token_end_;
69
70 // Make token_begin_ point to the beginning of the next token, and search
71 // for the end of the token in token_end_.
72 token_begin_ = token_end_;
73
74 // Set to true if we're currently inside a quoted string.
75 bool in_quote = false;
76 // Set to true if we're currently inside a quoted string, and have just
77 // encountered an escape character. In this case a closing quote will be
78 // ignored.
79 bool in_escape = false;
80 // If currently in a quoted string, this is the character that (when not
81 // escaped) indicates the end of the string.
82 char quote_close_char = '\0';
83 // If currently in a quoted string, this is set to true if it is possible to
84 // escape the closing quote using '\'.
85 bool quote_allows_escape = false;
86
87 while (token_end_ != end_) {
88 char c = *token_end_;
89 if (in_quote) {
90 if (in_escape) {
91 in_escape = false;
92 } else if (quote_allows_escape && c == '\\') {
93 in_escape = true;
94 } else if (c == quote_close_char) {
95 in_quote = false;
96 }
97 } else {
98 if (c == ',')
99 break;
100 if (c == '\'' || c == '"' || c == '<') {
101 in_quote = true;
102 quote_close_char = (c == '<' ? '>' : c);
103 quote_allows_escape = (c != '<');
104 }
105 }
106 ++token_end_;
107 }
108 return true;
109 }
110
111 std::string::const_iterator token_begin_;
112 std::string::const_iterator token_end_;
113 std::string::const_iterator end_;
114 };
115
116 // Parses one link in a link header into its url and parameters.
117 // A link is of the form "<some-url>; param1=value1; param2=value2".
118 // Returns false if parsing the link failed, returns true on success. This
119 // method is more lenient than the RFC. It doesn't fail on things like invalid
120 // characters in the URL, and also doesn't verify that certain parameters should
121 // or shouldn't be quoted strings.
122 // If a parameter occurs more than once in the link, only the first value is
123 // returned in params as this is the required behavior for all attributes chrome
124 // currently cares about in link headers.
125 bool ParseLink(std::string::const_iterator begin,
126 std::string::const_iterator end,
127 std::string* url,
128 std::unordered_map<std::string, std::string>* params) {
129 // Can't parse an empty string.
130 if (begin == end)
131 return false;
132
133 // Extract the URL part (everything between '<' and first '>' character).
134 if (*begin != '<')
135 return false;
136 ++begin;
137 std::string::const_iterator url_begin = begin;
138 std::string::const_iterator url_end = std::find(begin, end, '>');
139 // Fail if we did not find a '>'.
140 if (url_end == end)
141 return false;
142 begin = url_end;
143 net::HttpUtil::TrimLWS(&url_begin, &url_end);
144 *url = std::string(url_begin, url_end);
145
146 // Skip the '>' at the end of the URL, trim any remaining whitespace, and make
147 // sure it is followed by a ';' to indicate the start of parameters.
148 ++begin;
149 net::HttpUtil::TrimLWS(&begin, &end);
150 if (begin != end && *begin != ';')
151 return false;
152
153 // Parse all the parameters.
154 net::HttpUtil::NameValuePairsIterator params_iterator(
155 begin, end, ';', net::HttpUtil::NameValuePairsIterator::VALUES_OPTIONAL);
156 while (params_iterator.GetNext()) {
157 if (!net::HttpUtil::IsToken(params_iterator.name_begin(),
158 params_iterator.name_end()))
159 return false;
160 std::string name = base::ToLowerASCII(base::StringPiece(
161 params_iterator.name_begin(), params_iterator.name_end()));
162 params->insert(std::make_pair(name, params_iterator.value()));
163 }
164 return params_iterator.valid();
165 }
166
167 void RegisterServiceWorkerFinished(int64_t trace_id, bool result) { 27 void RegisterServiceWorkerFinished(int64_t trace_id, bool result) {
168 TRACE_EVENT_ASYNC_END1("ServiceWorker", 28 TRACE_EVENT_ASYNC_END1("ServiceWorker",
169 "LinkHeaderResourceThrottle::HandleServiceWorkerLink", 29 "LinkHeaderResourceThrottle::HandleServiceWorkerLink",
170 trace_id, "Success", result); 30 trace_id, "Success", result);
171 } 31 }
172 32
173 void HandleServiceWorkerLink( 33 void HandleServiceWorkerLink(
174 const net::URLRequest* request, 34 const net::URLRequest* request,
175 const std::string& url, 35 const std::string& url,
176 const std::unordered_map<std::string, std::string>& params, 36 const std::unordered_map<std::string, base::Optional<std::string>>& params,
177 ServiceWorkerContextWrapper* service_worker_context_for_testing) { 37 ServiceWorkerContextWrapper* service_worker_context_for_testing) {
178 DCHECK_CURRENTLY_ON(BrowserThread::IO); 38 DCHECK_CURRENTLY_ON(BrowserThread::IO);
179 39
180 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( 40 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
181 switches::kEnableExperimentalWebPlatformFeatures)) { 41 switches::kEnableExperimentalWebPlatformFeatures)) {
182 // TODO(mek): Integrate with experimental framework. 42 // TODO(mek): Integrate with experimental framework.
183 return; 43 return;
184 } 44 }
185 45
186 if (ContainsKey(params, "anchor")) 46 if (ContainsKey(params, "anchor"))
(...skipping 21 matching lines...) Expand all
208 // be able to be intercepted by a serviceworker isn't very useful, so this 68 // be able to be intercepted by a serviceworker isn't very useful, so this
209 // should share logic with ServiceWorkerRequestHandler and 69 // should share logic with ServiceWorkerRequestHandler and
210 // ForeignFetchRequestHandler to limit the requests for which serviceworker 70 // ForeignFetchRequestHandler to limit the requests for which serviceworker
211 // links are processed. 71 // links are processed.
212 72
213 GURL context_url = request->url(); 73 GURL context_url = request->url();
214 GURL script_url = context_url.Resolve(url); 74 GURL script_url = context_url.Resolve(url);
215 auto scope_param = params.find("scope"); 75 auto scope_param = params.find("scope");
216 GURL scope_url = scope_param == params.end() 76 GURL scope_url = scope_param == params.end()
217 ? script_url.Resolve("./") 77 ? script_url.Resolve("./")
218 : context_url.Resolve(scope_param->second); 78 : context_url.Resolve(scope_param->second.value_or(""));
219 79
220 if (!context_url.is_valid() || !script_url.is_valid() || 80 if (!context_url.is_valid() || !script_url.is_valid() ||
221 !scope_url.is_valid()) 81 !scope_url.is_valid())
222 return; 82 return;
223 if (!ServiceWorkerUtils::CanRegisterServiceWorker(context_url, scope_url, 83 if (!ServiceWorkerUtils::CanRegisterServiceWorker(context_url, scope_url,
224 script_url)) 84 script_url))
225 return; 85 return;
226 std::string error; 86 std::string error;
227 if (ServiceWorkerUtils::ContainsDisallowedCharacter(scope_url, script_url, 87 if (ServiceWorkerUtils::ContainsDisallowedCharacter(scope_url, script_url,
228 &error)) 88 &error))
(...skipping 19 matching lines...) Expand all
248 } 108 }
249 109
250 void ProcessLinkHeaderValueForRequest( 110 void ProcessLinkHeaderValueForRequest(
251 const net::URLRequest* request, 111 const net::URLRequest* request,
252 std::string::const_iterator value_begin, 112 std::string::const_iterator value_begin,
253 std::string::const_iterator value_end, 113 std::string::const_iterator value_end,
254 ServiceWorkerContextWrapper* service_worker_context_for_testing) { 114 ServiceWorkerContextWrapper* service_worker_context_for_testing) {
255 DCHECK_CURRENTLY_ON(BrowserThread::IO); 115 DCHECK_CURRENTLY_ON(BrowserThread::IO);
256 116
257 std::string url; 117 std::string url;
258 std::unordered_map<std::string, std::string> params; 118 std::unordered_map<std::string, base::Optional<std::string>> params;
259 if (!ParseLink(value_begin, value_end, &url, &params)) 119 if (!link_header_util::ParseLinkHeaderValue(value_begin, value_end, &url,
120 &params))
260 return; 121 return;
261 122
262 for (const auto& rel : 123 for (const auto& rel : base::SplitStringPiece(params["rel"].value_or(""),
263 base::SplitStringPiece(params["rel"], HTTP_LWS, base::TRIM_WHITESPACE, 124 HTTP_LWS, base::TRIM_WHITESPACE,
264 base::SPLIT_WANT_NONEMPTY)) { 125 base::SPLIT_WANT_NONEMPTY)) {
265 if (base::EqualsCaseInsensitiveASCII(rel, "serviceworker")) 126 if (base::EqualsCaseInsensitiveASCII(rel, "serviceworker"))
266 HandleServiceWorkerLink(request, url, params, 127 HandleServiceWorkerLink(request, url, params,
267 service_worker_context_for_testing); 128 service_worker_context_for_testing);
268 } 129 }
269 } 130 }
270 131
271 } // namespace 132 } // namespace
272 133
273 void ProcessRequestForLinkHeaders(const net::URLRequest* request) { 134 void ProcessRequestForLinkHeaders(const net::URLRequest* request) {
274 std::string link_header; 135 std::string link_header;
275 request->GetResponseHeaderByName("link", &link_header); 136 request->GetResponseHeaderByName("link", &link_header);
276 if (link_header.empty()) 137 if (link_header.empty())
277 return; 138 return;
278 139
279 ProcessLinkHeaderForRequest(request, link_header); 140 ProcessLinkHeaderForRequest(request, link_header);
280 } 141 }
281 142
282 void ProcessLinkHeaderForRequest( 143 void ProcessLinkHeaderForRequest(
283 const net::URLRequest* request, 144 const net::URLRequest* request,
284 const std::string& link_header, 145 const std::string& link_header,
285 ServiceWorkerContextWrapper* service_worker_context_for_testing) { 146 ServiceWorkerContextWrapper* service_worker_context_for_testing) {
286 ValueTokenizer tokenizer(link_header.begin(), link_header.end()); 147 for (const auto& value : link_header_util::SplitLinkHeader(link_header)) {
287 while (tokenizer.GetNext()) { 148 ProcessLinkHeaderValueForRequest(request, value.first, value.second,
288 ProcessLinkHeaderValueForRequest(request, tokenizer.token_begin(),
289 tokenizer.token_end(),
290 service_worker_context_for_testing); 149 service_worker_context_for_testing);
291 } 150 }
292 } 151 }
293 152
294 void SplitLinkHeaderForTesting(const std::string& header,
295 std::vector<std::string>* values) {
296 values->clear();
297 ValueTokenizer tokenizer(header.begin(), header.end());
298 while (tokenizer.GetNext()) {
299 values->push_back(
300 std::string(tokenizer.token_begin(), tokenizer.token_end()));
301 }
302 }
303
304 bool ParseLinkHeaderValueForTesting(
305 const std::string& link,
306 std::string* url,
307 std::unordered_map<std::string, std::string>* params) {
308 return ParseLink(link.begin(), link.end(), url, params);
309 }
310
311 } // namespace content 153 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698