// Copyright 2014 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 "content/child/web_url_loader_impl.h" #include #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "base/time/time.h" #include "content/child/resource_dispatcher.h" #include "content/child/resource_loader_bridge.h" #include "content/public/child/request_peer.h" #include "content/public/common/resource_response_info.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" #include "net/url_request/redirect_info.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/platform/WebString.h" #include "third_party/WebKit/public/platform/WebURLError.h" #include "third_party/WebKit/public/platform/WebURLLoaderClient.h" #include "third_party/WebKit/public/platform/WebURLRequest.h" #include "url/gurl.h" namespace content { namespace { const char kTestURL[] = "http://foo"; const char kTestData[] = "blah!"; const char kFtpDirMimeType[] = "text/vnd.chromium.ftp-dir"; // Simple FTP directory listing. Tests are not concerned with correct parsing, // but rather correct cleanup when deleted while parsing. Important details of // this list are that it contains more than one entry that are not "." or "..". const char kFtpDirListing[] = "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 goat\n" "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 hat"; const char kMultipartResponseMimeType[] = "multipart/x-mixed-replace"; const char kMultipartResponseHeaders[] = "HTTP/1.0 200 Peachy\r\n" "Content-Type: multipart/x-mixed-replace; boundary=boundary\r\n\r\n"; // Simple multipart response. Imporant details for the tests are that it // contains multiple chunks, and that it doesn't end with a boundary, so will // send data in OnResponseComplete. Also, it will resolve to kTestData. const char kMultipartResponse[] = "--boundary\n" "Content-type: text/html\n\n" "bl" "--boundary\n" "Content-type: text/html\n\n" "ah!"; class TestBridge : public ResourceLoaderBridge, public base::SupportsWeakPtr { public: TestBridge() : peer_(NULL), canceled_(false) {} virtual ~TestBridge() {} // ResourceLoaderBridge implementation: virtual void SetRequestBody(ResourceRequestBody* request_body) OVERRIDE {} virtual bool Start(RequestPeer* peer) OVERRIDE { EXPECT_FALSE(peer_); peer_ = peer; return true; } virtual void Cancel() OVERRIDE { EXPECT_FALSE(canceled_); canceled_ = true; } virtual void SetDefersLoading(bool value) OVERRIDE {} virtual void DidChangePriority(net::RequestPriority new_priority, int intra_priority_value) OVERRIDE {} virtual bool AttachThreadedDataReceiver( blink::WebThreadedDataReceiver* threaded_data_receiver) OVERRIDE { NOTREACHED(); return false; } virtual void SyncLoad(SyncLoadResponse* response) OVERRIDE {} RequestPeer* peer() { return peer_; } bool canceled() { return canceled_; } private: RequestPeer* peer_; bool canceled_; DISALLOW_COPY_AND_ASSIGN(TestBridge); }; class TestResourceDispatcher : public ResourceDispatcher { public: TestResourceDispatcher() : ResourceDispatcher(NULL) {} virtual ~TestResourceDispatcher() {} // ResourceDispatcher implementation: virtual ResourceLoaderBridge* CreateBridge( const RequestInfo& request_info) OVERRIDE { EXPECT_FALSE(bridge_.get()); TestBridge* bridge = new TestBridge(); bridge_ = bridge->AsWeakPtr(); return bridge; } TestBridge* bridge() { return bridge_.get(); } private: base::WeakPtr bridge_; DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcher); }; class TestWebURLLoaderClient : public blink::WebURLLoaderClient { public: TestWebURLLoaderClient(ResourceDispatcher* dispatcher) : loader_(new WebURLLoaderImpl(dispatcher)), expect_multipart_response_(false), delete_on_receive_redirect_(false), delete_on_receive_response_(false), delete_on_receive_data_(false), delete_on_finish_(false), delete_on_fail_(false), did_receive_redirect_(false), did_receive_response_(false), did_finish_(false) { } virtual ~TestWebURLLoaderClient() {} // blink::WebURLLoaderClient implementation: virtual void willSendRequest( blink::WebURLLoader* loader, blink::WebURLRequest& newRequest, const blink::WebURLResponse& redirectResponse) OVERRIDE { EXPECT_TRUE(loader_); EXPECT_EQ(loader_.get(), loader); // No test currently simulates mutiple redirects. EXPECT_FALSE(did_receive_redirect_); did_receive_redirect_ = true; if (delete_on_receive_redirect_) loader_.reset(); } virtual void didSendData(blink::WebURLLoader* loader, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) OVERRIDE { EXPECT_TRUE(loader_); EXPECT_EQ(loader_.get(), loader); } virtual void didReceiveResponse( blink::WebURLLoader* loader, const blink::WebURLResponse& response) OVERRIDE { EXPECT_TRUE(loader_); EXPECT_EQ(loader_.get(), loader); // Only multipart requests may receive multiple response headers. EXPECT_TRUE(expect_multipart_response_ || !did_receive_response_); did_receive_response_ = true; if (delete_on_receive_response_) loader_.reset(); } virtual void didDownloadData(blink::WebURLLoader* loader, int dataLength, int encodedDataLength) OVERRIDE { EXPECT_TRUE(loader_); EXPECT_EQ(loader_.get(), loader); } virtual void didReceiveData(blink::WebURLLoader* loader, const char* data, int dataLength, int encodedDataLength) OVERRIDE { EXPECT_TRUE(loader_); EXPECT_EQ(loader_.get(), loader); // The response should have started, but must not have finished, or failed. EXPECT_TRUE(did_receive_response_); EXPECT_FALSE(did_finish_); EXPECT_EQ(net::OK, error_.reason); EXPECT_EQ("", error_.domain.utf8()); received_data_.append(data, dataLength); if (delete_on_receive_data_) loader_.reset(); } virtual void didReceiveCachedMetadata(blink::WebURLLoader* loader, const char* data, int dataLength) OVERRIDE { EXPECT_EQ(loader_.get(), loader); } virtual void didFinishLoading(blink::WebURLLoader* loader, double finishTime, int64_t totalEncodedDataLength) OVERRIDE { EXPECT_TRUE(loader_); EXPECT_EQ(loader_.get(), loader); EXPECT_TRUE(did_receive_response_); EXPECT_FALSE(did_finish_); did_finish_ = true; if (delete_on_finish_) loader_.reset(); } virtual void didFail(blink::WebURLLoader* loader, const blink::WebURLError& error) OVERRIDE { EXPECT_TRUE(loader_); EXPECT_EQ(loader_.get(), loader); EXPECT_FALSE(did_finish_); error_ = error; if (delete_on_fail_) loader_.reset(); } WebURLLoaderImpl* loader() { return loader_.get(); } void DeleteLoader() { loader_.reset(); } void set_expect_multipart_response() { expect_multipart_response_ = true; } void set_delete_on_receive_redirect() { delete_on_receive_redirect_ = true; } void set_delete_on_receive_response() { delete_on_receive_response_ = true; } void set_delete_on_receive_data() { delete_on_receive_data_ = true; } void set_delete_on_finish() { delete_on_finish_ = true; } void set_delete_on_fail() { delete_on_fail_ = true; } bool did_receive_redirect() const { return did_receive_redirect_; } bool did_receive_response() const { return did_receive_response_; } const std::string& received_data() const { return received_data_; } bool did_finish() const { return did_finish_; } const blink::WebURLError& error() const { return error_; } private: scoped_ptr loader_; bool expect_multipart_response_; bool delete_on_receive_redirect_; bool delete_on_receive_response_; bool delete_on_receive_data_; bool delete_on_finish_; bool delete_on_fail_; bool did_receive_redirect_; bool did_receive_response_; std::string received_data_; bool did_finish_; blink::WebURLError error_; DISALLOW_COPY_AND_ASSIGN(TestWebURLLoaderClient); }; class WebURLLoaderImplTest : public testing::Test { public: explicit WebURLLoaderImplTest() : client_(&dispatcher_) {} virtual ~WebURLLoaderImplTest() {} void DoStartAsyncRequest() { blink::WebURLRequest request; request.initialize(); request.setURL(GURL(kTestURL)); client()->loader()->loadAsynchronously(request, client()); ASSERT_TRUE(bridge()); ASSERT_TRUE(peer()); } void DoReceiveRedirect() { EXPECT_FALSE(client()->did_receive_redirect()); net::RedirectInfo redirect_info; redirect_info.status_code = 302; redirect_info.new_method = "GET"; redirect_info.new_url = GURL(kTestURL); redirect_info.new_first_party_for_cookies = GURL(kTestURL); peer()->OnReceivedRedirect(redirect_info, content::ResourceResponseInfo()); EXPECT_TRUE(client()->did_receive_redirect()); } void DoReceiveResponse() { EXPECT_FALSE(client()->did_receive_response()); peer()->OnReceivedResponse(content::ResourceResponseInfo()); EXPECT_TRUE(client()->did_receive_response()); } // Assumes it is called only once for a request. void DoReceiveData() { EXPECT_EQ("", client()->received_data()); peer()->OnReceivedData(kTestData, strlen(kTestData), strlen(kTestData)); EXPECT_EQ(kTestData, client()->received_data()); } void DoCompleteRequest() { EXPECT_FALSE(client()->did_finish()); peer()->OnCompletedRequest(net::OK, false, false, "", base::TimeTicks(), strlen(kTestData)); EXPECT_TRUE(client()->did_finish()); // There should be no error. EXPECT_EQ(net::OK, client()->error().reason); EXPECT_EQ("", client()->error().domain.utf8()); } void DoFailRequest() { EXPECT_FALSE(client()->did_finish()); peer()->OnCompletedRequest(net::ERR_FAILED, false, false, "", base::TimeTicks(), strlen(kTestData)); EXPECT_FALSE(client()->did_finish()); EXPECT_EQ(net::ERR_FAILED, client()->error().reason); EXPECT_EQ(net::kErrorDomain, client()->error().domain.utf8()); } void DoReceiveResponseFtp() { EXPECT_FALSE(client()->did_receive_response()); content::ResourceResponseInfo response_info; response_info.mime_type = kFtpDirMimeType; peer()->OnReceivedResponse(response_info); EXPECT_TRUE(client()->did_receive_response()); } void DoReceiveDataFtp() { peer()->OnReceivedData(kFtpDirListing, strlen(kFtpDirListing), strlen(kFtpDirListing)); // The FTP delegate should modify the data the client sees. EXPECT_NE(kFtpDirListing, client()->received_data()); } void DoReceiveResponseMultipart() { EXPECT_FALSE(client()->did_receive_response()); content::ResourceResponseInfo response_info; response_info.headers = new net::HttpResponseHeaders( net::HttpUtil::AssembleRawHeaders(kMultipartResponseHeaders, strlen(kMultipartResponseHeaders))); response_info.mime_type = kMultipartResponseMimeType; peer()->OnReceivedResponse(response_info); EXPECT_TRUE(client()->did_receive_response()); } void DoReceiveDataMultipart() { peer()->OnReceivedData(kMultipartResponse, strlen(kMultipartResponse), strlen(kMultipartResponse)); // Multipart delegate should modify the data the client sees. EXPECT_NE(kMultipartResponse, client()->received_data()); } TestWebURLLoaderClient* client() { return &client_; } TestBridge* bridge() { return dispatcher_.bridge(); } RequestPeer* peer() { return bridge()->peer(); } base::MessageLoop* message_loop() { return &message_loop_; } private: TestResourceDispatcher dispatcher_; TestWebURLLoaderClient client_; base::MessageLoop message_loop_; }; TEST_F(WebURLLoaderImplTest, Success) { DoStartAsyncRequest(); DoReceiveResponse(); DoReceiveData(); DoCompleteRequest(); EXPECT_FALSE(bridge()->canceled()); EXPECT_EQ(kTestData, client()->received_data()); } TEST_F(WebURLLoaderImplTest, Redirect) { DoStartAsyncRequest(); DoReceiveRedirect(); DoReceiveResponse(); DoReceiveData(); DoCompleteRequest(); EXPECT_FALSE(bridge()->canceled()); EXPECT_EQ(kTestData, client()->received_data()); } TEST_F(WebURLLoaderImplTest, Failure) { DoStartAsyncRequest(); DoReceiveResponse(); DoReceiveData(); DoFailRequest(); EXPECT_FALSE(bridge()->canceled()); } // The client may delete the WebURLLoader during any callback from the loader. // These tests make sure that doesn't result in a crash. TEST_F(WebURLLoaderImplTest, DeleteOnReceiveRedirect) { client()->set_delete_on_receive_redirect(); DoStartAsyncRequest(); DoReceiveRedirect(); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, DeleteOnReceiveResponse) { client()->set_delete_on_receive_response(); DoStartAsyncRequest(); DoReceiveResponse(); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, DeleteOnReceiveData) { client()->set_delete_on_receive_data(); DoStartAsyncRequest(); DoReceiveResponse(); DoReceiveData(); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, DeleteOnFinish) { client()->set_delete_on_finish(); DoStartAsyncRequest(); DoReceiveResponse(); DoReceiveData(); DoCompleteRequest(); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, DeleteOnFail) { client()->set_delete_on_fail(); DoStartAsyncRequest(); DoReceiveResponse(); DoReceiveData(); DoFailRequest(); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, DeleteBeforeResponseDataURL) { blink::WebURLRequest request; request.initialize(); request.setURL(GURL("data:text/html;charset=utf-8,blah!")); client()->loader()->loadAsynchronously(request, client()); client()->DeleteLoader(); message_loop()->RunUntilIdle(); EXPECT_FALSE(client()->did_receive_response()); EXPECT_FALSE(bridge()); } // Data URL tests. TEST_F(WebURLLoaderImplTest, DataURL) { blink::WebURLRequest request; request.initialize(); request.setURL(GURL("data:text/html;charset=utf-8,blah!")); client()->loader()->loadAsynchronously(request, client()); message_loop()->RunUntilIdle(); EXPECT_EQ("blah!", client()->received_data()); EXPECT_TRUE(client()->did_finish()); EXPECT_EQ(net::OK, client()->error().reason); EXPECT_EQ("", client()->error().domain.utf8()); } TEST_F(WebURLLoaderImplTest, DataURLDeleteOnReceiveResponse) { blink::WebURLRequest request; request.initialize(); request.setURL(GURL("data:text/html;charset=utf-8,blah!")); client()->set_delete_on_receive_response(); client()->loader()->loadAsynchronously(request, client()); message_loop()->RunUntilIdle(); EXPECT_TRUE(client()->did_receive_response()); EXPECT_EQ("", client()->received_data()); EXPECT_FALSE(client()->did_finish()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, DataURLDeleteOnReceiveData) { blink::WebURLRequest request; request.initialize(); request.setURL(GURL("data:text/html;charset=utf-8,blah!")); client()->set_delete_on_receive_data(); client()->loader()->loadAsynchronously(request, client()); message_loop()->RunUntilIdle(); EXPECT_TRUE(client()->did_receive_response()); EXPECT_EQ("blah!", client()->received_data()); EXPECT_FALSE(client()->did_finish()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, DataURLDeleteOnFinisha) { blink::WebURLRequest request; request.initialize(); request.setURL(GURL("data:text/html;charset=utf-8,blah!")); client()->set_delete_on_finish(); client()->loader()->loadAsynchronously(request, client()); message_loop()->RunUntilIdle(); EXPECT_TRUE(client()->did_receive_response()); EXPECT_EQ("blah!", client()->received_data()); EXPECT_TRUE(client()->did_finish()); EXPECT_FALSE(bridge()); } // FTP integration tests. These are focused more on safe deletion than correct // parsing of FTP responses. TEST_F(WebURLLoaderImplTest, Ftp) { DoStartAsyncRequest(); DoReceiveResponseFtp(); DoReceiveDataFtp(); DoCompleteRequest(); EXPECT_FALSE(bridge()->canceled()); } TEST_F(WebURLLoaderImplTest, FtpDeleteOnReceiveResponse) { client()->set_delete_on_receive_response(); DoStartAsyncRequest(); DoReceiveResponseFtp(); // No data should have been received. EXPECT_EQ("", client()->received_data()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, FtpDeleteOnReceiveFirstData) { client()->set_delete_on_receive_data(); DoStartAsyncRequest(); // Some data is sent in ReceiveResponse for FTP requests, so the bridge should // be deleted here. DoReceiveResponseFtp(); EXPECT_NE("", client()->received_data()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, FtpDeleteOnReceiveMoreData) { DoStartAsyncRequest(); DoReceiveResponseFtp(); DoReceiveDataFtp(); // Directory listings are only parsed once the request completes, so this will // cancel in DoReceiveDataFtp, before the request finishes. client()->set_delete_on_receive_data(); peer()->OnCompletedRequest(net::OK, false, false, "", base::TimeTicks(), strlen(kTestData)); EXPECT_FALSE(client()->did_finish()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, FtpDeleteOnFinish) { client()->set_delete_on_finish(); DoStartAsyncRequest(); DoReceiveResponseFtp(); DoReceiveDataFtp(); DoCompleteRequest(); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, FtpDeleteOnFail) { client()->set_delete_on_fail(); DoStartAsyncRequest(); DoReceiveResponseFtp(); DoReceiveDataFtp(); DoFailRequest(); EXPECT_FALSE(bridge()); } // Multipart integration tests. These are focused more on safe deletion than // correct parsing of Multipart responses. TEST_F(WebURLLoaderImplTest, Multipart) { client()->set_expect_multipart_response(); DoStartAsyncRequest(); DoReceiveResponseMultipart(); DoReceiveDataMultipart(); DoCompleteRequest(); EXPECT_EQ(kTestData, client()->received_data()); EXPECT_FALSE(bridge()->canceled()); } TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveFirstResponse) { client()->set_expect_multipart_response(); client()->set_delete_on_receive_response(); DoStartAsyncRequest(); DoReceiveResponseMultipart(); EXPECT_EQ("", client()->received_data()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveSecondResponse) { client()->set_expect_multipart_response(); DoStartAsyncRequest(); DoReceiveResponseMultipart(); client()->set_delete_on_receive_response(); DoReceiveDataMultipart(); EXPECT_EQ("", client()->received_data()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveFirstData) { client()->set_expect_multipart_response(); client()->set_delete_on_receive_data(); DoStartAsyncRequest(); DoReceiveResponseMultipart(); DoReceiveDataMultipart(); EXPECT_EQ("bl", client()->received_data()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveMoreData) { client()->set_expect_multipart_response(); DoStartAsyncRequest(); DoReceiveResponseMultipart(); DoReceiveDataMultipart(); // For multipart responses, the delegate may send some data when notified // of a request completing. client()->set_delete_on_receive_data(); peer()->OnCompletedRequest(net::OK, false, false, "", base::TimeTicks(), strlen(kTestData)); EXPECT_FALSE(client()->did_finish()); EXPECT_EQ(kTestData, client()->received_data()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, MultipartDeleteFinish) { client()->set_expect_multipart_response(); client()->set_delete_on_finish(); DoStartAsyncRequest(); DoReceiveResponseMultipart(); DoReceiveDataMultipart(); DoCompleteRequest(); EXPECT_EQ(kTestData, client()->received_data()); EXPECT_FALSE(bridge()); } TEST_F(WebURLLoaderImplTest, MultipartDeleteFail) { client()->set_expect_multipart_response(); client()->set_delete_on_fail(); DoStartAsyncRequest(); DoReceiveResponseMultipart(); DoReceiveDataMultipart(); DoFailRequest(); EXPECT_FALSE(bridge()); } } // namespace } // namespace content