// 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 <string.h>

#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "content/child/resource_dispatcher.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 "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"
#include "webkit/child/resource_loader_bridge.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 webkit_glue::ResourceLoaderBridge,
                   public base::SupportsWeakPtr<TestBridge> {
 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 webkit_glue::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<TestBridge> 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<WebURLLoaderImpl> 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());
    peer()->OnReceivedRedirect(GURL(kTestURL), GURL(kTestURL),
                               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