blob: 1dbcd3a82976bc09f5ca5e6fd8cdc98c6d1e8f84 [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/renderer_host/buffered_resource_handler.h"
#include <vector>
#include "base/histogram.h"
#include "base/logging.h"
#include "base/string_util.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/renderer_host/download_throttling_resource_handler.h"
#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h"
#include "chrome/browser/renderer_host/x509_user_cert_resource_handler.h"
#include "chrome/common/extensions/user_script.h"
#include "chrome/common/resource_response.h"
#include "chrome/common/url_constants.h"
#include "net/base/io_buffer.h"
#include "net/base/mime_sniffer.h"
#include "net/base/mime_util.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "webkit/glue/plugins/plugin_list.h"
namespace {
void RecordSnifferMetrics(bool sniffing_blocked,
bool we_would_like_to_sniff,
const std::string& mime_type) {
static scoped_refptr<Histogram> nosniff_usage = BooleanHistogram::FactoryGet(
"nosniff.usage", Histogram::kUmaTargetedHistogramFlag);
nosniff_usage->AddBoolean(sniffing_blocked);
if (sniffing_blocked) {
static scoped_refptr<Histogram> nosniff_otherwise =
BooleanHistogram::FactoryGet("nosniff.otherwise",
Histogram::kUmaTargetedHistogramFlag);
nosniff_otherwise->AddBoolean(we_would_like_to_sniff);
static scoped_refptr<Histogram> nosniff_empty_mime_type =
BooleanHistogram::FactoryGet("nosniff.empty_mime_type",
Histogram::kUmaTargetedHistogramFlag);
nosniff_empty_mime_type->AddBoolean(mime_type.empty());
}
}
} // namespace
BufferedResourceHandler::BufferedResourceHandler(ResourceHandler* handler,
ResourceDispatcherHost* host,
URLRequest* request)
: real_handler_(handler),
host_(host),
request_(request),
read_buffer_size_(0),
bytes_read_(0),
sniff_content_(false),
should_buffer_(false),
wait_for_plugins_(false),
buffering_(false),
finished_(false) {
}
bool BufferedResourceHandler::OnUploadProgress(int request_id,
uint64 position,
uint64 size) {
return real_handler_->OnUploadProgress(request_id, position, size);
}
bool BufferedResourceHandler::OnRequestRedirected(int request_id,
const GURL& new_url,
ResourceResponse* response,
bool* defer) {
return real_handler_->OnRequestRedirected(
request_id, new_url, response, defer);
}
bool BufferedResourceHandler::OnResponseStarted(int request_id,
ResourceResponse* response) {
response_ = response;
if (!DelayResponse())
return CompleteResponseStarted(request_id, false);
return true;
}
bool BufferedResourceHandler::OnResponseCompleted(
int request_id,
const URLRequestStatus& status,
const std::string& security_info) {
return real_handler_->OnResponseCompleted(request_id, status, security_info);
}
void BufferedResourceHandler::OnRequestClosed() {
request_ = NULL;
real_handler_->OnRequestClosed();
}
bool BufferedResourceHandler::OnWillStart(int request_id,
const GURL& url,
bool* defer) {
return real_handler_->OnWillStart(request_id, url, defer);
}
// We'll let the original event handler provide a buffer, and reuse it for
// subsequent reads until we're done buffering.
bool BufferedResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf,
int* buf_size, int min_size) {
if (buffering_) {
DCHECK(!my_buffer_.get());
my_buffer_ = new net::IOBuffer(net::kMaxBytesToSniff);
*buf = my_buffer_.get();
*buf_size = net::kMaxBytesToSniff;
return true;
}
if (finished_)
return false;
if (!real_handler_->OnWillRead(request_id, buf, buf_size, min_size)) {
return false;
}
read_buffer_ = *buf;
read_buffer_size_ = *buf_size;
DCHECK_GE(read_buffer_size_, net::kMaxBytesToSniff * 2);
bytes_read_ = 0;
return true;
}
bool BufferedResourceHandler::OnReadCompleted(int request_id, int* bytes_read) {
if (sniff_content_ || should_buffer_) {
if (KeepBuffering(*bytes_read))
return true;
*bytes_read = bytes_read_;
// Done buffering, send the pending ResponseStarted event.
if (!CompleteResponseStarted(request_id, true))
return false;
} else if (wait_for_plugins_) {
return true;
}
// Release the reference that we acquired at OnWillRead.
read_buffer_ = NULL;
return real_handler_->OnReadCompleted(request_id, bytes_read);
}
bool BufferedResourceHandler::DelayResponse() {
std::string mime_type;
request_->GetMimeType(&mime_type);
std::string content_type_options;
request_->GetResponseHeaderByName("x-content-type-options",
&content_type_options);
const bool sniffing_blocked =
LowerCaseEqualsASCII(content_type_options, "nosniff");
const bool not_modified_status =
response_->response_head.headers &&
response_->response_head.headers->response_code() == 304;
const bool we_would_like_to_sniff = not_modified_status ?
false : net::ShouldSniffMimeType(request_->url(), mime_type);
RecordSnifferMetrics(sniffing_blocked, we_would_like_to_sniff, mime_type);
if (!sniffing_blocked && we_would_like_to_sniff) {
// We're going to look at the data before deciding what the content type
// is. That means we need to delay sending the ResponseStarted message
// over the IPC channel.
sniff_content_ = true;
LOG(INFO) << "To buffer: " << request_->url().spec();
return true;
}
if (sniffing_blocked && mime_type.empty() && !not_modified_status) {
// Ugg. The server told us not to sniff the content but didn't give us a
// mime type. What's a browser to do? Turns out, we're supposed to treat
// the response as "text/plain". This is the most secure option.
mime_type.assign("text/plain");
response_->response_head.mime_type.assign(mime_type);
}
if (mime_type == "application/rss+xml" ||
mime_type == "application/atom+xml") {
// Sad face. The server told us that they wanted us to treat the response
// as RSS or Atom. Unfortunately, we don't have a built-in feed previewer
// like other browsers. We can't just render the content as XML because
// web sites let third parties inject arbitrary script into their RSS
// feeds. That leaves us with little choice but to practically ignore the
// response. In the future, when we have an RSS feed previewer, we can
// remove this logic.
mime_type.assign("text/plain");
response_->response_head.mime_type.assign(mime_type);
}
if (ShouldBuffer(request_->url(), mime_type)) {
// This is a temporary fix for the fact that webkit expects to have
// enough data to decode the doctype in order to select the rendering
// mode.
should_buffer_ = true;
return true;
}
if (!not_modified_status && ShouldWaitForPlugins()) {
wait_for_plugins_ = true;
return true;
}
return false;
}
bool BufferedResourceHandler::ShouldBuffer(const GURL& url,
const std::string& mime_type) {
// We are willing to buffer for HTTP and HTTPS.
bool sniffable_scheme = url.is_empty() ||
url.SchemeIs(chrome::kHttpScheme) ||
url.SchemeIs(chrome::kHttpsScheme);
if (!sniffable_scheme)
return false;
// Today, the only reason to buffer the request is to fix the doctype decoding
// performed by webkit: if there is not enough data it will go to quirks mode.
// We only expect the doctype check to apply to html documents.
return mime_type == "text/html";
}
bool BufferedResourceHandler::DidBufferEnough(int bytes_read) {
const int kRequiredLength = 256;
return bytes_read >= kRequiredLength;
}
bool BufferedResourceHandler::KeepBuffering(int bytes_read) {
DCHECK(read_buffer_);
if (my_buffer_) {
// We are using our own buffer to read, update the main buffer.
// TODO(darin): We should handle the case where read_buffer_size_ is small!
// See RedirectToFileResourceHandler::BufIsFull to see how this impairs
// downstream ResourceHandler implementations.
CHECK_LT(bytes_read + bytes_read_, read_buffer_size_);
memcpy(read_buffer_->data() + bytes_read_, my_buffer_->data(), bytes_read);
my_buffer_ = NULL;
}
bytes_read_ += bytes_read;
finished_ = (bytes_read == 0);
if (sniff_content_) {
std::string type_hint, new_type;
request_->GetMimeType(&type_hint);
if (!net::SniffMimeType(read_buffer_->data(), bytes_read_,
request_->url(), type_hint, &new_type)) {
// SniffMimeType() returns false if there is not enough data to determine
// the mime type. However, even if it returns false, it returns a new type
// that is probably better than the current one.
DCHECK_LT(bytes_read_, net::kMaxBytesToSniff);
if (!finished_) {
buffering_ = true;
return true;
}
}
sniff_content_ = false;
response_->response_head.mime_type.assign(new_type);
// We just sniffed the mime type, maybe there is a doctype to process.
if (ShouldBuffer(request_->url(), new_type)) {
should_buffer_ = true;
} else if (ShouldWaitForPlugins()) {
wait_for_plugins_ = true;
}
}
if (should_buffer_) {
if (!finished_ && !DidBufferEnough(bytes_read_)) {
buffering_ = true;
return true;
}
should_buffer_ = false;
if (ShouldWaitForPlugins())
wait_for_plugins_ = true;
}
buffering_ = false;
if (wait_for_plugins_)
return true;
return false;
}
bool BufferedResourceHandler::CompleteResponseStarted(int request_id,
bool in_complete) {
ResourceDispatcherHostRequestInfo* info =
ResourceDispatcherHost::InfoForRequest(request_);
std::string mime_type;
request_->GetMimeType(&mime_type);
// Check if this is an X.509 certificate, if yes, let it be handled
// by X509UserCertResourceHandler.
if (mime_type == "application/x-x509-user-cert") {
// This is entirely similar to how DownloadThrottlingResourceHandler
// works except we are doing it for an X.509 client certificates.
if (response_->response_head.headers && // Can be NULL if FTP.
response_->response_head.headers->response_code() / 100 != 2) {
// The response code indicates that this is an error page, but we are
// expecting an X.509 user certificate. We follow Firefox here and show
// our own error page instead of handling the error page as a
// certificate.
// TODO(abarth): We should abstract the response_code test, but this kind
// of check is scattered throughout our codebase.
request_->SimulateError(net::ERR_FILE_NOT_FOUND);
return false;
}
X509UserCertResourceHandler* x509_cert_handler =
new X509UserCertResourceHandler(host_, request_,
info->child_id(), info->route_id());
UseAlternateResourceHandler(request_id, x509_cert_handler);
}
// Check to see if we should forward the data from this request to the
// download thread.
// TODO(paulg): Only download if the context from the renderer allows it.
if (info->allow_download() && ShouldDownload(NULL)) {
if (response_->response_head.headers && // Can be NULL if FTP.
response_->response_head.headers->response_code() / 100 != 2) {
// The response code indicates that this is an error page, but we don't
// know how to display the content. We follow Firefox here and show our
// own error page instead of triggering a download.
// TODO(abarth): We should abstract the response_code test, but this kind
// of check is scattered throughout our codebase.
request_->SimulateError(net::ERR_FILE_NOT_FOUND);
return false;
}
info->set_is_download(true);
DownloadThrottlingResourceHandler* download_handler =
new DownloadThrottlingResourceHandler(host_,
request_,
request_->url(),
info->child_id(),
info->route_id(),
request_id,
in_complete);
UseAlternateResourceHandler(request_id, download_handler);
}
return real_handler_->OnResponseStarted(request_id, response_);
}
bool BufferedResourceHandler::ShouldWaitForPlugins() {
bool need_plugin_list;
if (!ShouldDownload(&need_plugin_list) || !need_plugin_list)
return false;
// We don't want to keep buffering as our buffer will fill up.
ResourceDispatcherHostRequestInfo* info =
ResourceDispatcherHost::InfoForRequest(request_);
host_->PauseRequest(info->child_id(), info->request_id(), true);
// Schedule plugin loading on the file thread.
ChromeThread::PostTask(
ChromeThread::FILE, FROM_HERE,
NewRunnableMethod(this, &BufferedResourceHandler::LoadPlugins));
return true;
}
// This test mirrors the decision that WebKit makes in
// WebFrameLoaderClient::dispatchDecidePolicyForMIMEType.
bool BufferedResourceHandler::ShouldDownload(bool* need_plugin_list) {
if (need_plugin_list)
*need_plugin_list = false;
std::string type = StringToLowerASCII(response_->response_head.mime_type);
std::string disposition;
request_->GetResponseHeaderByName("content-disposition", &disposition);
disposition = StringToLowerASCII(disposition);
// First, examine content-disposition.
if (!disposition.empty()) {
bool should_download = true;
// Some broken sites just send ...
// Content-Disposition: ; filename="file"
// ... screen those out here.
if (disposition[0] == ';')
should_download = false;
if (disposition.compare(0, 6, "inline") == 0)
should_download = false;
// Some broken sites just send ...
// Content-Disposition: filename="file"
// ... without a disposition token... Screen those out.
if (disposition.compare(0, 8, "filename") == 0)
should_download = false;
// Also in use is Content-Disposition: name="file"
if (disposition.compare(0, 4, "name") == 0)
should_download = false;
// We have a content-disposition of "attachment" or unknown.
// RFC 2183, section 2.8 says that an unknown disposition
// value should be treated as "attachment".
if (should_download)
return true;
}
// Special-case user scripts to get downloaded instead of viewed.
if (UserScript::HasUserScriptFileExtension(request_->url()))
return true;
// MIME type checking.
if (net::IsSupportedMimeType(type))
return false;
if (need_plugin_list) {
if (!NPAPI::PluginList::Singleton()->PluginsLoaded()) {
*need_plugin_list = true;
return true;
}
} else {
DCHECK(NPAPI::PluginList::Singleton()->PluginsLoaded());
}
// Finally, check the plugin list.
WebPluginInfo info;
bool allow_wildcard = false;
return !NPAPI::PluginList::Singleton()->GetPluginInfo(
GURL(), type, allow_wildcard, &info, NULL) || !info.enabled;
}
void BufferedResourceHandler::UseAlternateResourceHandler(
int request_id,
ResourceHandler* handler) {
ResourceDispatcherHostRequestInfo* info =
ResourceDispatcherHost::InfoForRequest(request_);
if (bytes_read_) {
// A Read has already occured and we need to copy the data into the new
// ResourceHandler.
net::IOBuffer* buf = NULL;
int buf_len = 0;
handler->OnWillRead(request_id, &buf, &buf_len, bytes_read_);
CHECK((buf_len >= bytes_read_) && (bytes_read_ >= 0));
memcpy(buf->data(), read_buffer_->data(), bytes_read_);
}
// Inform the original ResourceHandler that this will be handled entirely by
// the new ResourceHandler.
real_handler_->OnResponseStarted(info->request_id(), response_);
URLRequestStatus status(URLRequestStatus::HANDLED_EXTERNALLY, 0);
real_handler_->OnResponseCompleted(info->request_id(), status, std::string());
// Remove the non-owning pointer to the CrossSiteResourceHandler, if any,
// from the extra request info because the CrossSiteResourceHandler (part of
// the original ResourceHandler chain) will be deleted by the next statement.
info->set_cross_site_handler(NULL);
// This is handled entirely within the new ResourceHandler, so just reset the
// original ResourceHandler.
real_handler_ = handler;
}
void BufferedResourceHandler::LoadPlugins() {
std::vector<WebPluginInfo> plugins;
NPAPI::PluginList::Singleton()->GetPlugins(false, &plugins);
ChromeThread::PostTask(
ChromeThread::IO, FROM_HERE,
NewRunnableMethod(this, &BufferedResourceHandler::OnPluginsLoaded));
}
void BufferedResourceHandler::OnPluginsLoaded() {
wait_for_plugins_ = false;
if (!request_)
return;
ResourceDispatcherHostRequestInfo* info =
ResourceDispatcherHost::InfoForRequest(request_);
host_->PauseRequest(info->child_id(), info->request_id(), false);
if (!CompleteResponseStarted(info->request_id(), false))
host_->CancelRequest(info->child_id(), info->request_id(), false);
}