blob: 7630dd125fcc6bdc753731e73af7b178b8984f63 [file] [log] [blame]
// Copyright (c) 2006-2008 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 "net/base/mime_sniffer.h"
#include "chrome/browser/renderer_host/download_throttling_resource_handler.h"
#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
BufferedResourceHandler::BufferedResourceHandler(ResourceHandler* handler,
ResourceDispatcherHost* host,
URLRequest* request)
: real_handler_(handler),
host_(host),
request_(request),
bytes_read_(0),
sniff_content_(false),
should_buffer_(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) {
return real_handler_->OnRequestRedirected(request_id, new_url);
}
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) {
return real_handler_->OnResponseCompleted(request_id, status);
}
// 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,
char** buf, int* buf_size,
int min_size) {
if (buffering_) {
*buf = read_buffer_ + bytes_read_;
*buf_size = read_buffer_size_ - bytes_read_;
DCHECK(*buf_size > 0);
return true;
}
if (finished_)
return false;
bool ret = real_handler_->OnWillRead(request_id, buf, buf_size, min_size);
read_buffer_ = *buf;
read_buffer_size_ = *buf_size;
bytes_read_ = 0;
return ret;
}
bool BufferedResourceHandler::OnReadCompleted(int request_id, int* bytes_read) {
if (sniff_content_ || should_buffer_) {
if (KeepBuffering(*bytes_read))
return true;
LOG(INFO) << "Finished buffering " << request_->url().spec();
sniff_content_ = should_buffer_ = false;
*bytes_read = bytes_read_;
// Done buffering, send the pending ResponseStarted event.
if (!CompleteResponseStarted(request_id, true))
return false;
}
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);
if (content_type_options != "nosniff" &&
net::ShouldSniffMimeType(request_->url(), mime_type)) {
// 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 (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;
LOG(INFO) << "To buffer: " << request_->url().spec();
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("http") ||
url.SchemeIs("https");
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::KeepBuffering(int bytes_read) {
DCHECK(read_buffer_);
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_, 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(bytes_read_ < 512 /*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;
}
if (!finished_ && should_buffer_) {
if (!DidBufferEnough(bytes_read_)) {
buffering_ = true;
return true;
}
}
buffering_ = false;
return false;
}
bool BufferedResourceHandler::CompleteResponseStarted(int request_id,
bool in_complete) {
// 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.
std::string content_disposition;
request_->GetResponseHeaderByName("content-disposition",
&content_disposition);
ResourceDispatcherHost::ExtraRequestInfo* info =
ResourceDispatcherHost::ExtraInfoForRequest(request_);
if (info->allow_download &&
host_->ShouldDownload(response_->response_head.mime_type,
content_disposition)) {
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_->CancelWithError(net::ERR_FILE_NOT_FOUND);
return false;
}
info->is_download = true;
scoped_refptr<DownloadThrottlingResourceHandler> download_handler =
new DownloadThrottlingResourceHandler(host_,
request_,
request_->url().spec(),
info->render_process_host_id,
info->render_view_id,
request_id,
in_complete);
if (bytes_read_) {
// a Read has already occurred and we need to copy the data into the
// EventHandler.
char *buf = NULL;
int buf_len = 0;
download_handler->OnWillRead(request_id, &buf, &buf_len, bytes_read_);
CHECK((buf_len >= bytes_read_) && (bytes_read_ >= 0));
memcpy(buf, read_buffer_, bytes_read_);
}
// Update the renderer with the response headers which will cause it to
// cancel the request.
// TODO(paulg): Send the renderer a response that indicates that the request
// will be handled by an external source (the browser).
real_handler_->OnResponseStarted(info->request_id, response_);
real_handler_ = download_handler;
}
return real_handler_->OnResponseStarted(request_id, response_);
}
bool BufferedResourceHandler::DidBufferEnough(int bytes_read) {
const int kRequiredLength = 256;
return bytes_read >= kRequiredLength;
}