OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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 "chrome/browser/renderer_host/buffered_resource_handler.h" | |
6 | |
7 #include <vector> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/metrics/histogram.h" | |
11 #include "base/string_util.h" | |
12 #include "chrome/browser/browser_thread.h" | |
13 #include "chrome/browser/renderer_host/download_throttling_resource_handler.h" | |
14 #include "chrome/browser/renderer_host/resource_dispatcher_host.h" | |
15 #include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" | |
16 #include "chrome/browser/renderer_host/x509_user_cert_resource_handler.h" | |
17 #include "chrome/common/extensions/user_script.h" | |
18 #include "chrome/common/resource_response.h" | |
19 #include "chrome/common/url_constants.h" | |
20 #include "net/base/io_buffer.h" | |
21 #include "net/base/mime_sniffer.h" | |
22 #include "net/base/mime_util.h" | |
23 #include "net/base/net_errors.h" | |
24 #include "net/http/http_response_headers.h" | |
25 #include "webkit/plugins/npapi/plugin_list.h" | |
26 | |
27 namespace { | |
28 | |
29 void RecordSnifferMetrics(bool sniffing_blocked, | |
30 bool we_would_like_to_sniff, | |
31 const std::string& mime_type) { | |
32 static scoped_refptr<base::Histogram> nosniff_usage = | |
33 base::BooleanHistogram::FactoryGet( | |
34 "nosniff.usage", base::Histogram::kUmaTargetedHistogramFlag); | |
35 nosniff_usage->AddBoolean(sniffing_blocked); | |
36 | |
37 if (sniffing_blocked) { | |
38 static scoped_refptr<base::Histogram> nosniff_otherwise = | |
39 base::BooleanHistogram::FactoryGet( | |
40 "nosniff.otherwise", base::Histogram::kUmaTargetedHistogramFlag); | |
41 nosniff_otherwise->AddBoolean(we_would_like_to_sniff); | |
42 | |
43 static scoped_refptr<base::Histogram> nosniff_empty_mime_type = | |
44 base::BooleanHistogram::FactoryGet( | |
45 "nosniff.empty_mime_type", | |
46 base::Histogram::kUmaTargetedHistogramFlag); | |
47 nosniff_empty_mime_type->AddBoolean(mime_type.empty()); | |
48 } | |
49 } | |
50 | |
51 } // namespace | |
52 | |
53 BufferedResourceHandler::BufferedResourceHandler(ResourceHandler* handler, | |
54 ResourceDispatcherHost* host, | |
55 net::URLRequest* request) | |
56 : real_handler_(handler), | |
57 host_(host), | |
58 request_(request), | |
59 read_buffer_size_(0), | |
60 bytes_read_(0), | |
61 sniff_content_(false), | |
62 should_buffer_(false), | |
63 wait_for_plugins_(false), | |
64 buffering_(false), | |
65 finished_(false) { | |
66 } | |
67 | |
68 bool BufferedResourceHandler::OnUploadProgress(int request_id, | |
69 uint64 position, | |
70 uint64 size) { | |
71 return real_handler_->OnUploadProgress(request_id, position, size); | |
72 } | |
73 | |
74 bool BufferedResourceHandler::OnRequestRedirected(int request_id, | |
75 const GURL& new_url, | |
76 ResourceResponse* response, | |
77 bool* defer) { | |
78 return real_handler_->OnRequestRedirected( | |
79 request_id, new_url, response, defer); | |
80 } | |
81 | |
82 bool BufferedResourceHandler::OnResponseStarted(int request_id, | |
83 ResourceResponse* response) { | |
84 response_ = response; | |
85 if (!DelayResponse()) | |
86 return CompleteResponseStarted(request_id, false); | |
87 return true; | |
88 } | |
89 | |
90 bool BufferedResourceHandler::OnResponseCompleted( | |
91 int request_id, | |
92 const net::URLRequestStatus& status, | |
93 const std::string& security_info) { | |
94 return real_handler_->OnResponseCompleted(request_id, status, security_info); | |
95 } | |
96 | |
97 void BufferedResourceHandler::OnRequestClosed() { | |
98 request_ = NULL; | |
99 real_handler_->OnRequestClosed(); | |
100 } | |
101 | |
102 bool BufferedResourceHandler::OnWillStart(int request_id, | |
103 const GURL& url, | |
104 bool* defer) { | |
105 return real_handler_->OnWillStart(request_id, url, defer); | |
106 } | |
107 | |
108 // We'll let the original event handler provide a buffer, and reuse it for | |
109 // subsequent reads until we're done buffering. | |
110 bool BufferedResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf, | |
111 int* buf_size, int min_size) { | |
112 if (buffering_) { | |
113 DCHECK(!my_buffer_.get()); | |
114 my_buffer_ = new net::IOBuffer(net::kMaxBytesToSniff); | |
115 *buf = my_buffer_.get(); | |
116 *buf_size = net::kMaxBytesToSniff; | |
117 return true; | |
118 } | |
119 | |
120 if (finished_) | |
121 return false; | |
122 | |
123 if (!real_handler_->OnWillRead(request_id, buf, buf_size, min_size)) { | |
124 return false; | |
125 } | |
126 read_buffer_ = *buf; | |
127 read_buffer_size_ = *buf_size; | |
128 DCHECK_GE(read_buffer_size_, net::kMaxBytesToSniff * 2); | |
129 bytes_read_ = 0; | |
130 return true; | |
131 } | |
132 | |
133 bool BufferedResourceHandler::OnReadCompleted(int request_id, int* bytes_read) { | |
134 if (sniff_content_ || should_buffer_) { | |
135 if (KeepBuffering(*bytes_read)) | |
136 return true; | |
137 | |
138 *bytes_read = bytes_read_; | |
139 | |
140 // Done buffering, send the pending ResponseStarted event. | |
141 if (!CompleteResponseStarted(request_id, true)) | |
142 return false; | |
143 } else if (wait_for_plugins_) { | |
144 return true; | |
145 } | |
146 | |
147 // Release the reference that we acquired at OnWillRead. | |
148 read_buffer_ = NULL; | |
149 return real_handler_->OnReadCompleted(request_id, bytes_read); | |
150 } | |
151 | |
152 BufferedResourceHandler::~BufferedResourceHandler() {} | |
153 | |
154 bool BufferedResourceHandler::DelayResponse() { | |
155 std::string mime_type; | |
156 request_->GetMimeType(&mime_type); | |
157 | |
158 std::string content_type_options; | |
159 request_->GetResponseHeaderByName("x-content-type-options", | |
160 &content_type_options); | |
161 | |
162 const bool sniffing_blocked = | |
163 LowerCaseEqualsASCII(content_type_options, "nosniff"); | |
164 const bool not_modified_status = | |
165 response_->response_head.headers && | |
166 response_->response_head.headers->response_code() == 304; | |
167 const bool we_would_like_to_sniff = not_modified_status ? | |
168 false : net::ShouldSniffMimeType(request_->url(), mime_type); | |
169 | |
170 RecordSnifferMetrics(sniffing_blocked, we_would_like_to_sniff, mime_type); | |
171 | |
172 if (!sniffing_blocked && we_would_like_to_sniff) { | |
173 // We're going to look at the data before deciding what the content type | |
174 // is. That means we need to delay sending the ResponseStarted message | |
175 // over the IPC channel. | |
176 sniff_content_ = true; | |
177 VLOG(1) << "To buffer: " << request_->url().spec(); | |
178 return true; | |
179 } | |
180 | |
181 if (sniffing_blocked && mime_type.empty() && !not_modified_status) { | |
182 // Ugg. The server told us not to sniff the content but didn't give us a | |
183 // mime type. What's a browser to do? Turns out, we're supposed to treat | |
184 // the response as "text/plain". This is the most secure option. | |
185 mime_type.assign("text/plain"); | |
186 response_->response_head.mime_type.assign(mime_type); | |
187 } | |
188 | |
189 if (mime_type == "application/rss+xml" || | |
190 mime_type == "application/atom+xml") { | |
191 // Sad face. The server told us that they wanted us to treat the response | |
192 // as RSS or Atom. Unfortunately, we don't have a built-in feed previewer | |
193 // like other browsers. We can't just render the content as XML because | |
194 // web sites let third parties inject arbitrary script into their RSS | |
195 // feeds. That leaves us with little choice but to practically ignore the | |
196 // response. In the future, when we have an RSS feed previewer, we can | |
197 // remove this logic. | |
198 mime_type.assign("text/plain"); | |
199 response_->response_head.mime_type.assign(mime_type); | |
200 } | |
201 | |
202 if (ShouldBuffer(request_->url(), mime_type)) { | |
203 // This is a temporary fix for the fact that webkit expects to have | |
204 // enough data to decode the doctype in order to select the rendering | |
205 // mode. | |
206 should_buffer_ = true; | |
207 return true; | |
208 } | |
209 | |
210 if (!not_modified_status && ShouldWaitForPlugins()) { | |
211 wait_for_plugins_ = true; | |
212 return true; | |
213 } | |
214 | |
215 return false; | |
216 } | |
217 | |
218 bool BufferedResourceHandler::ShouldBuffer(const GURL& url, | |
219 const std::string& mime_type) { | |
220 // We are willing to buffer for HTTP and HTTPS. | |
221 bool sniffable_scheme = url.is_empty() || | |
222 url.SchemeIs(chrome::kHttpScheme) || | |
223 url.SchemeIs(chrome::kHttpsScheme); | |
224 if (!sniffable_scheme) | |
225 return false; | |
226 | |
227 // Today, the only reason to buffer the request is to fix the doctype decoding | |
228 // performed by webkit: if there is not enough data it will go to quirks mode. | |
229 // We only expect the doctype check to apply to html documents. | |
230 return mime_type == "text/html"; | |
231 } | |
232 | |
233 bool BufferedResourceHandler::DidBufferEnough(int bytes_read) { | |
234 const int kRequiredLength = 256; | |
235 | |
236 return bytes_read >= kRequiredLength; | |
237 } | |
238 | |
239 bool BufferedResourceHandler::KeepBuffering(int bytes_read) { | |
240 DCHECK(read_buffer_); | |
241 if (my_buffer_) { | |
242 // We are using our own buffer to read, update the main buffer. | |
243 // TODO(darin): We should handle the case where read_buffer_size_ is small! | |
244 // See RedirectToFileResourceHandler::BufIsFull to see how this impairs | |
245 // downstream ResourceHandler implementations. | |
246 CHECK_LT(bytes_read + bytes_read_, read_buffer_size_); | |
247 memcpy(read_buffer_->data() + bytes_read_, my_buffer_->data(), bytes_read); | |
248 my_buffer_ = NULL; | |
249 } | |
250 bytes_read_ += bytes_read; | |
251 finished_ = (bytes_read == 0); | |
252 | |
253 if (sniff_content_) { | |
254 std::string type_hint, new_type; | |
255 request_->GetMimeType(&type_hint); | |
256 | |
257 if (!net::SniffMimeType(read_buffer_->data(), bytes_read_, | |
258 request_->url(), type_hint, &new_type)) { | |
259 // SniffMimeType() returns false if there is not enough data to determine | |
260 // the mime type. However, even if it returns false, it returns a new type | |
261 // that is probably better than the current one. | |
262 DCHECK_LT(bytes_read_, net::kMaxBytesToSniff); | |
263 if (!finished_) { | |
264 buffering_ = true; | |
265 return true; | |
266 } | |
267 } | |
268 sniff_content_ = false; | |
269 response_->response_head.mime_type.assign(new_type); | |
270 | |
271 // We just sniffed the mime type, maybe there is a doctype to process. | |
272 if (ShouldBuffer(request_->url(), new_type)) { | |
273 should_buffer_ = true; | |
274 } else if (ShouldWaitForPlugins()) { | |
275 wait_for_plugins_ = true; | |
276 } | |
277 } | |
278 | |
279 if (should_buffer_) { | |
280 if (!finished_ && !DidBufferEnough(bytes_read_)) { | |
281 buffering_ = true; | |
282 return true; | |
283 } | |
284 | |
285 should_buffer_ = false; | |
286 if (ShouldWaitForPlugins()) | |
287 wait_for_plugins_ = true; | |
288 } | |
289 | |
290 buffering_ = false; | |
291 | |
292 if (wait_for_plugins_) | |
293 return true; | |
294 | |
295 return false; | |
296 } | |
297 | |
298 bool BufferedResourceHandler::CompleteResponseStarted(int request_id, | |
299 bool in_complete) { | |
300 ResourceDispatcherHostRequestInfo* info = | |
301 ResourceDispatcherHost::InfoForRequest(request_); | |
302 std::string mime_type; | |
303 request_->GetMimeType(&mime_type); | |
304 | |
305 // Check if this is an X.509 certificate, if yes, let it be handled | |
306 // by X509UserCertResourceHandler. | |
307 if (mime_type == "application/x-x509-user-cert") { | |
308 // This is entirely similar to how DownloadThrottlingResourceHandler | |
309 // works except we are doing it for an X.509 client certificates. | |
310 | |
311 if (response_->response_head.headers && // Can be NULL if FTP. | |
312 response_->response_head.headers->response_code() / 100 != 2) { | |
313 // The response code indicates that this is an error page, but we are | |
314 // expecting an X.509 user certificate. We follow Firefox here and show | |
315 // our own error page instead of handling the error page as a | |
316 // certificate. | |
317 // TODO(abarth): We should abstract the response_code test, but this kind | |
318 // of check is scattered throughout our codebase. | |
319 request_->SimulateError(net::ERR_FILE_NOT_FOUND); | |
320 return false; | |
321 } | |
322 | |
323 X509UserCertResourceHandler* x509_cert_handler = | |
324 new X509UserCertResourceHandler(host_, request_, | |
325 info->child_id(), info->route_id()); | |
326 UseAlternateResourceHandler(request_id, x509_cert_handler); | |
327 } | |
328 | |
329 // Check to see if we should forward the data from this request to the | |
330 // download thread. | |
331 // TODO(paulg): Only download if the context from the renderer allows it. | |
332 if (info->allow_download() && ShouldDownload(NULL)) { | |
333 if (response_->response_head.headers && // Can be NULL if FTP. | |
334 response_->response_head.headers->response_code() / 100 != 2) { | |
335 // The response code indicates that this is an error page, but we don't | |
336 // know how to display the content. We follow Firefox here and show our | |
337 // own error page instead of triggering a download. | |
338 // TODO(abarth): We should abstract the response_code test, but this kind | |
339 // of check is scattered throughout our codebase. | |
340 request_->SimulateError(net::ERR_FILE_NOT_FOUND); | |
341 return false; | |
342 } | |
343 | |
344 info->set_is_download(true); | |
345 | |
346 DownloadThrottlingResourceHandler* download_handler = | |
347 new DownloadThrottlingResourceHandler(host_, | |
348 request_, | |
349 request_->url(), | |
350 info->child_id(), | |
351 info->route_id(), | |
352 request_id, | |
353 in_complete); | |
354 UseAlternateResourceHandler(request_id, download_handler); | |
355 } | |
356 return real_handler_->OnResponseStarted(request_id, response_); | |
357 } | |
358 | |
359 bool BufferedResourceHandler::ShouldWaitForPlugins() { | |
360 bool need_plugin_list; | |
361 if (!ShouldDownload(&need_plugin_list) || !need_plugin_list) | |
362 return false; | |
363 | |
364 // We don't want to keep buffering as our buffer will fill up. | |
365 ResourceDispatcherHostRequestInfo* info = | |
366 ResourceDispatcherHost::InfoForRequest(request_); | |
367 host_->PauseRequest(info->child_id(), info->request_id(), true); | |
368 | |
369 // Schedule plugin loading on the file thread. | |
370 BrowserThread::PostTask( | |
371 BrowserThread::FILE, FROM_HERE, | |
372 NewRunnableMethod(this, &BufferedResourceHandler::LoadPlugins)); | |
373 return true; | |
374 } | |
375 | |
376 // This test mirrors the decision that WebKit makes in | |
377 // WebFrameLoaderClient::dispatchDecidePolicyForMIMEType. | |
378 bool BufferedResourceHandler::ShouldDownload(bool* need_plugin_list) { | |
379 if (need_plugin_list) | |
380 *need_plugin_list = false; | |
381 std::string type = StringToLowerASCII(response_->response_head.mime_type); | |
382 std::string disposition; | |
383 request_->GetResponseHeaderByName("content-disposition", &disposition); | |
384 disposition = StringToLowerASCII(disposition); | |
385 | |
386 // First, examine content-disposition. | |
387 if (!disposition.empty()) { | |
388 bool should_download = true; | |
389 | |
390 // Some broken sites just send ... | |
391 // Content-Disposition: ; filename="file" | |
392 // ... screen those out here. | |
393 if (disposition[0] == ';') | |
394 should_download = false; | |
395 | |
396 if (disposition.compare(0, 6, "inline") == 0) | |
397 should_download = false; | |
398 | |
399 // Some broken sites just send ... | |
400 // Content-Disposition: filename="file" | |
401 // ... without a disposition token... Screen those out. | |
402 if (disposition.compare(0, 8, "filename") == 0) | |
403 should_download = false; | |
404 | |
405 // Also in use is Content-Disposition: name="file" | |
406 if (disposition.compare(0, 4, "name") == 0) | |
407 should_download = false; | |
408 | |
409 // We have a content-disposition of "attachment" or unknown. | |
410 // RFC 2183, section 2.8 says that an unknown disposition | |
411 // value should be treated as "attachment". | |
412 if (should_download) | |
413 return true; | |
414 } | |
415 | |
416 // Special-case user scripts to get downloaded instead of viewed. | |
417 if (UserScript::HasUserScriptFileExtension(request_->url())) | |
418 return true; | |
419 | |
420 // MIME type checking. | |
421 if (net::IsSupportedMimeType(type)) | |
422 return false; | |
423 | |
424 if (need_plugin_list) { | |
425 if (!webkit::npapi::PluginList::Singleton()->PluginsLoaded()) { | |
426 *need_plugin_list = true; | |
427 return true; | |
428 } | |
429 } else { | |
430 DCHECK(webkit::npapi::PluginList::Singleton()->PluginsLoaded()); | |
431 } | |
432 | |
433 // Finally, check the plugin list. | |
434 webkit::npapi::WebPluginInfo info; | |
435 bool allow_wildcard = false; | |
436 return !webkit::npapi::PluginList::Singleton()->GetPluginInfo( | |
437 GURL(), type, allow_wildcard, &info, NULL) || | |
438 !webkit::npapi::IsPluginEnabled(info); | |
439 } | |
440 | |
441 void BufferedResourceHandler::UseAlternateResourceHandler( | |
442 int request_id, | |
443 ResourceHandler* handler) { | |
444 ResourceDispatcherHostRequestInfo* info = | |
445 ResourceDispatcherHost::InfoForRequest(request_); | |
446 if (bytes_read_) { | |
447 // A Read has already occured and we need to copy the data into the new | |
448 // ResourceHandler. | |
449 net::IOBuffer* buf = NULL; | |
450 int buf_len = 0; | |
451 handler->OnWillRead(request_id, &buf, &buf_len, bytes_read_); | |
452 CHECK((buf_len >= bytes_read_) && (bytes_read_ >= 0)); | |
453 memcpy(buf->data(), read_buffer_->data(), bytes_read_); | |
454 } | |
455 | |
456 // Inform the original ResourceHandler that this will be handled entirely by | |
457 // the new ResourceHandler. | |
458 real_handler_->OnResponseStarted(info->request_id(), response_); | |
459 net::URLRequestStatus status(net::URLRequestStatus::HANDLED_EXTERNALLY, 0); | |
460 real_handler_->OnResponseCompleted(info->request_id(), status, std::string()); | |
461 | |
462 // Remove the non-owning pointer to the CrossSiteResourceHandler, if any, | |
463 // from the extra request info because the CrossSiteResourceHandler (part of | |
464 // the original ResourceHandler chain) will be deleted by the next statement. | |
465 info->set_cross_site_handler(NULL); | |
466 | |
467 // This is handled entirely within the new ResourceHandler, so just reset the | |
468 // original ResourceHandler. | |
469 real_handler_ = handler; | |
470 } | |
471 | |
472 void BufferedResourceHandler::LoadPlugins() { | |
473 std::vector<webkit::npapi::WebPluginInfo> plugins; | |
474 webkit::npapi::PluginList::Singleton()->GetPlugins(false, &plugins); | |
475 | |
476 BrowserThread::PostTask( | |
477 BrowserThread::IO, FROM_HERE, | |
478 NewRunnableMethod(this, &BufferedResourceHandler::OnPluginsLoaded)); | |
479 } | |
480 | |
481 void BufferedResourceHandler::OnPluginsLoaded() { | |
482 wait_for_plugins_ = false; | |
483 if (!request_) | |
484 return; | |
485 | |
486 ResourceDispatcherHostRequestInfo* info = | |
487 ResourceDispatcherHost::InfoForRequest(request_); | |
488 host_->PauseRequest(info->child_id(), info->request_id(), false); | |
489 if (!CompleteResponseStarted(info->request_id(), false)) | |
490 host_->CancelRequest(info->child_id(), info->request_id(), false); | |
491 } | |
OLD | NEW |