blob: dcb9722d37c33e997a59f6859961c73881a69f82 [file] [log] [blame]
// Copyright 2017 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.
#ifndef CONTENT_BROWSER_LOADER_CROSS_SITE_DOCUMENT_RESOURCE_HANDLER_H_
#define CONTENT_BROWSER_LOADER_CROSS_SITE_DOCUMENT_RESOURCE_HANDLER_H_
#include <memory>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/loader/layered_resource_handler.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/common/resource_type.h"
#include "services/network/cross_origin_read_blocking.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
namespace net {
class URLRequest;
} // namespace net
namespace content {
// A ResourceHandler that prevents the renderer process from receiving network
// responses that contain cross-site documents (HTML, XML, some plain text) or
// similar data that should be opaque (JSON), with appropriate exceptions to
// preserve compatibility. Other cross-site resources such as scripts, images,
// stylesheets, etc are still allowed.
//
// This handler is not used for navigations, which create a new security context
// based on the origin of the response. It currently only protects documents
// from sites that require dedicated renderer processes, though it could be
// expanded to apply to all sites.
//
// When a response is blocked, the renderer is sent an empty response body (with
// stripped down set of response headers) instead of seeing a failed request. A
// failed request would change page- visible behavior (e.g., for a blocked XHR).
// An empty response can generally be consumed by the renderer without noticing
// the difference.
//
// To allow stripping response headers of a blocked response,
// CrossSiteDocumentResourceHandler holds onto ResourceResponse received in
// OnResponseStarted and replays it only after making the final block-or-allow
// decision (this may require sniffing - processing the first few OnWillRead
// and OnReadCompleted calls). Note that the first OnWillRead is forwarded into
// the downstream handler (to use a single buffer from the downstream handler,
// rather than allocating a separate buffer and copying the data between the
// buffers) and this leads to perturbed order of calls (that is OnWillRead is
// received by the downstream handler *before* OnResponseStarted) - this
// behavior is very similar to what MimeSniffingResourceHandler does.
//
// For more details, see:
// http://chromium.org/developers/design-documents/blocking-cross-site-documents
class CONTENT_EXPORT CrossSiteDocumentResourceHandler
: public LayeredResourceHandler {
public:
CrossSiteDocumentResourceHandler(
std::unique_ptr<ResourceHandler> next_handler,
net::URLRequest* request,
network::mojom::RequestMode request_mode);
~CrossSiteDocumentResourceHandler() override;
// LayeredResourceHandler overrides:
void OnRequestRedirected(
const net::RedirectInfo& redirect_info,
network::ResourceResponse* response,
std::unique_ptr<ResourceController> controller) override;
void OnResponseStarted(
network::ResourceResponse* response,
std::unique_ptr<ResourceController> controller) override;
void OnWillRead(scoped_refptr<net::IOBuffer>* buf,
int* buf_size,
std::unique_ptr<ResourceController> controller) override;
void OnReadCompleted(int bytes_read,
std::unique_ptr<ResourceController> controller) override;
void OnResponseCompleted(
const net::URLRequestStatus& status,
std::unique_ptr<ResourceController> controller) override;
private:
FRIEND_TEST_ALL_PREFIXES(CrossSiteDocumentResourceHandlerTest,
ResponseBlocking);
FRIEND_TEST_ALL_PREFIXES(CrossSiteDocumentResourceHandlerTest,
OnWillReadDefer);
FRIEND_TEST_ALL_PREFIXES(CrossSiteDocumentResourceHandlerTest,
MimeSnifferInterop);
// A ResourceController subclass for running deferred operations.
class Controller;
// Computes whether this response contains a cross-site document that needs to
// be blocked from the renderer process. This is a first approximation based
// on the headers, and may be revised after some of the data is sniffed.
bool ShouldBlockBasedOnHeaders(const network::ResourceResponse& response);
// Once the downstream handler has allocated the buffer for OnWillRead
// (possibly after deferring), this sets up sniffing into a local buffer.
// Called by the OnWillReadController.
void ResumeOnWillRead(scoped_refptr<net::IOBuffer>* buf, int* buf_size);
// Stops local buffering and optionally copies the data from the
// |local_buffer_| into the |next_handler_|'s buffer that was returned by the
// |next_handler_| in response to OnWillRead.
void StopLocalBuffering(bool copy_data_to_next_handler);
// WeakPtrFactory for |next_handler_|.
base::WeakPtrFactory<ResourceHandler> weak_next_handler_;
// Temporary storage for response headers, while we haven't yet made the
// allow-vs-block decisions and haven't yet passed the response headers down
// to the next handler.
scoped_refptr<network::ResourceResponse> pending_response_start_;
// A local buffer for sniffing content and using for throwaway reads.
// This is not shared with the renderer process.
scoped_refptr<net::IOBuffer> local_buffer_;
// The buffer allocated by the next ResourceHandler for reads, which is used
// if sniffing determines that we should proceed with the response.
scoped_refptr<net::IOBuffer> next_handler_buffer_;
// The number of bytes written into |local_buffer_| by previous reads.
int local_buffer_bytes_read_ = 0;
// The size of |next_handler_buffer_|.
int next_handler_buffer_size_ = 0;
// The helper class that encapsulates the logic for deciding whether to block
// the response or not.
std::unique_ptr<network::CrossOriginReadBlocking::ResponseAnalyzer> analyzer_;
// Fetch request mode (e.g. 'no-cors' VS 'cors' VS 'same-origin', etc.).
network::mojom::RequestMode request_mode_;
// Tracks whether OnResponseStarted has been called, to ensure that it happens
// before OnWillRead and OnReadCompleted.
bool has_response_started_ = false;
// Whether this response is a cross-site document that should be blocked,
// pending the outcome of sniffing the content. Set in OnResponseStarted and
// should only be read afterwards.
bool should_block_based_on_headers_ = false;
// Whether this response will be allowed through despite being flagged for
// blocking (via |should_block_based_on_headers_), because sniffing determined
// it was incorrectly labeled and might be needed for compatibility (e.g.,
// in case it is Javascript).
bool allow_based_on_sniffing_ = false;
// Whether the next ResourceHandler has already been told that the read has
// completed, and thus it is safe to cancel or detach on the next read.
bool blocked_read_completed_ = false;
// Whether the request should be allowed because of
// ContentBrowserClient::GetInitatorSchemeBypassingDocumentBlocking
bool is_initiator_scheme_excluded_ = false;
// Whether |is_initiator_scheme_excluded_| actually prevented blocking from
// happening.
bool initiator_scheme_prevented_blocking_ = false;
// Whether the response is being blocked because of the presence of
// Cross-Origin-Resource-Policy header in 'no-cors' mode (in this case the
// response should fail with net::ERR_BLOCKED_BY_RESPONSE error code).
bool blocked_by_cross_origin_resource_policy_ = false;
base::WeakPtrFactory<CrossSiteDocumentResourceHandler> weak_this_;
DISALLOW_COPY_AND_ASSIGN(CrossSiteDocumentResourceHandler);
};
} // namespace content
#endif // CONTENT_BROWSER_LOADER_CROSS_SITE_DOCUMENT_RESOURCE_HANDLER_H_