// 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/multipart_response_delegate.h"

#include <vector>

#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
#include "third_party/WebKit/public/platform/WebURLResponse.h"

using blink::WebString;
using blink::WebURL;
using blink::WebURLError;
using blink::WebURLLoader;
using blink::WebURLLoaderClient;
using blink::WebURLRequest;
using blink::WebURLResponse;
using std::string;

namespace content {

class MultipartResponseDelegateTester {
 public:
  MultipartResponseDelegateTester(MultipartResponseDelegate* delegate)
      : delegate_(delegate) {
  }

  int PushOverLine(const std::string& data, size_t pos) {
    return delegate_->PushOverLine(data, pos);
  }

  bool ParseHeaders() { return delegate_->ParseHeaders(); }
  size_t FindBoundary() { return delegate_->FindBoundary(); }
  std::string& boundary() { return delegate_->boundary_; }
  std::string& data() { return delegate_->data_; }

 private:
  MultipartResponseDelegate* delegate_;
};

namespace {

class MultipartResponseTest : public testing::Test {
};

class MockWebURLLoaderClient : public WebURLLoaderClient {
 public:
  MockWebURLLoaderClient() { Reset(); }

  virtual void willSendRequest(
      WebURLLoader*, WebURLRequest&, const WebURLResponse&) {}
  virtual void didSendData(
      WebURLLoader*, unsigned long long, unsigned long long) {}

  virtual void didReceiveResponse(WebURLLoader* loader,
                                  const WebURLResponse& response) {
    ++received_response_;
    response_ = response;
    data_.clear();
  }
  virtual void didReceiveData(
      blink::WebURLLoader* loader,
      const char* data,
      int data_length,
      int encoded_data_length) {
    ++received_data_;
    data_.append(data, data_length);
    total_encoded_data_length_ += encoded_data_length;
  }
  virtual void didFinishLoading(
      WebURLLoader*, double finishTime, int64_t total_encoded_data_length) {}
  virtual void didFail(WebURLLoader*, const WebURLError&) {}

  void Reset() {
    received_response_ = received_data_ = total_encoded_data_length_ = 0;
    data_.clear();
    response_.reset();
  }

  string GetResponseHeader(const char* name) const {
    return string(response_.httpHeaderField(WebString::fromUTF8(name)).utf8());
  }

  int received_response_, received_data_, total_encoded_data_length_;
  string data_;
  WebURLResponse response_;
};

// We can't put this in an anonymous function because it's a friend class for
// access to private members.
TEST(MultipartResponseTest, Functions) {
  // PushOverLine tests

  WebURLResponse response;
  response.initialize();
  response.setMIMEType("multipart/x-mixed-replace");
  response.setHTTPHeaderField("Foo", "Bar");
  response.setHTTPHeaderField("Content-type", "text/plain");
  MockWebURLLoaderClient client;
  MultipartResponseDelegate delegate(&client, NULL, response, "bound");
  MultipartResponseDelegateTester delegate_tester(&delegate);

  struct {
    const char* input;
    const int position;
    const int expected;
  } line_tests[] = {
    { "Line", 0, 0 },
    { "Line", 2, 0 },
    { "Line", 10, 0 },
    { "\r\nLine", 0, 2 },
    { "\nLine", 0, 1 },
    { "\n\nLine", 0, 2 },
    { "\rLine", 0, 1 },
    { "Line\r\nLine", 4, 2 },
    { "Line\nLine", 4, 1 },
    { "Line\n\nLine", 4, 2 },
    { "Line\rLine", 4, 1 },
    { "Line\r\rLine", 4, 1 },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(line_tests); ++i) {
    EXPECT_EQ(line_tests[i].expected,
              delegate_tester.PushOverLine(line_tests[i].input,
                                           line_tests[i].position));
  }

  // ParseHeaders tests
  struct {
    const char* data;
    const bool rv;
    const int received_response_calls;
    const char* newdata;
  } header_tests[] = {
    { "This is junk", false, 0, "This is junk" },
    { "Foo: bar\nBaz:\n\nAfter:\n", true, 1, "After:\n" },
    { "Foo: bar\nBaz:\n", false, 0, "Foo: bar\nBaz:\n" },
    { "Foo: bar\r\nBaz:\r\n\r\nAfter:\r\n", true, 1, "After:\r\n" },
    { "Foo: bar\r\nBaz:\r\n", false, 0, "Foo: bar\r\nBaz:\r\n" },
    { "Foo: bar\nBaz:\r\n\r\nAfter:\n\n", true, 1, "After:\n\n" },
    { "Foo: bar\r\nBaz:\n", false, 0, "Foo: bar\r\nBaz:\n" },
    { "\r\n", true, 1, "" },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(header_tests); ++i) {
    client.Reset();
    delegate_tester.data().assign(header_tests[i].data);
    EXPECT_EQ(header_tests[i].rv,
              delegate_tester.ParseHeaders());
    EXPECT_EQ(header_tests[i].received_response_calls,
              client.received_response_);
    EXPECT_EQ(string(header_tests[i].newdata),
              delegate_tester.data());
  }
  // Test that the resource response is filled in correctly when parsing
  // headers.
  client.Reset();
  string test_header("content-type: image/png\ncontent-length: 10\n\n");
  delegate_tester.data().assign(test_header);
  EXPECT_TRUE(delegate_tester.ParseHeaders());
  EXPECT_TRUE(delegate_tester.data().length() == 0);
  EXPECT_EQ(string("image/png"), client.GetResponseHeader("Content-Type"));
  EXPECT_EQ(string("10"), client.GetResponseHeader("content-length"));
  // This header is passed from the original request.
  EXPECT_EQ(string("Bar"), client.GetResponseHeader("foo"));

  // Make sure we parse the right mime-type if a charset is provided.
  client.Reset();
  string test_header2("content-type: text/html; charset=utf-8\n\n");
  delegate_tester.data().assign(test_header2);
  EXPECT_TRUE(delegate_tester.ParseHeaders());
  EXPECT_TRUE(delegate_tester.data().length() == 0);
  EXPECT_EQ(string("text/html; charset=utf-8"),
            client.GetResponseHeader("Content-Type"));
  EXPECT_EQ(string("utf-8"),
            string(client.response_.textEncodingName().utf8()));

  // FindBoundary tests
  struct {
    const char* boundary;
    const char* data;
    const size_t position;
  } boundary_tests[] = {
    { "bound", "bound", 0 },
    { "bound", "--bound", 0 },
    { "bound", "junkbound", 4 },
    { "bound", "junk--bound", 4 },
    { "foo", "bound", string::npos },
    { "bound", "--boundbound", 0 },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(boundary_tests); ++i) {
    delegate_tester.boundary().assign(boundary_tests[i].boundary);
    delegate_tester.data().assign(boundary_tests[i].data);
    EXPECT_EQ(boundary_tests[i].position,
              delegate_tester.FindBoundary());
  }
}

TEST(MultipartResponseTest, MissingBoundaries) {
  WebURLResponse response;
  response.initialize();
  response.setMIMEType("multipart/x-mixed-replace");
  response.setHTTPHeaderField("Foo", "Bar");
  response.setHTTPHeaderField("Content-type", "text/plain");
  MockWebURLLoaderClient client;
  MultipartResponseDelegate delegate(&client, NULL, response, "bound");

  // No start boundary
  string no_start_boundary(
    "Content-type: text/plain\n\n"
    "This is a sample response\n"
    "--bound--"
    "ignore junk after end token --bound\n\nTest2\n");
  delegate.OnReceivedData(no_start_boundary.c_str(),
                          static_cast<int>(no_start_boundary.length()),
                          static_cast<int>(no_start_boundary.length()));
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
  EXPECT_EQ(string("This is a sample response"), client.data_);
  EXPECT_EQ(static_cast<int>(no_start_boundary.length()),
            client.total_encoded_data_length_);

  delegate.OnCompletedRequest();
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);

  // No end boundary
  client.Reset();
  MultipartResponseDelegate delegate2(&client, NULL, response, "bound");
  string no_end_boundary(
    "bound\nContent-type: text/plain\n\n"
    "This is a sample response\n");
  delegate2.OnReceivedData(no_end_boundary.c_str(),
                          static_cast<int>(no_end_boundary.length()),
                          static_cast<int>(no_end_boundary.length()));
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
  EXPECT_EQ("This is a sample response\n", client.data_);
  EXPECT_EQ(static_cast<int>(no_end_boundary.length()),
            client.total_encoded_data_length_);

  delegate2.OnCompletedRequest();
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
  EXPECT_EQ(string("This is a sample response\n"), client.data_);
  EXPECT_EQ(static_cast<int>(no_end_boundary.length()),
            client.total_encoded_data_length_);

  // Neither boundary
  client.Reset();
  MultipartResponseDelegate delegate3(&client, NULL, response, "bound");
  string no_boundaries(
    "Content-type: text/plain\n\n"
    "This is a sample response\n");
  delegate3.OnReceivedData(no_boundaries.c_str(),
                           static_cast<int>(no_boundaries.length()),
                           static_cast<int>(no_boundaries.length()));
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
  EXPECT_EQ("This is a sample response\n", client.data_);
  EXPECT_EQ(static_cast<int>(no_boundaries.length()),
            client.total_encoded_data_length_);

  delegate3.OnCompletedRequest();
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
  EXPECT_EQ(string("This is a sample response\n"), client.data_);
  EXPECT_EQ(static_cast<int>(no_boundaries.length()),
            client.total_encoded_data_length_);
}

TEST(MultipartResponseTest, MalformedBoundary) {
  // Some servers send a boundary that is prefixed by "--".  See bug 5786.

  WebURLResponse response;
  response.initialize();
  response.setMIMEType("multipart/x-mixed-replace");
  response.setHTTPHeaderField("Foo", "Bar");
  response.setHTTPHeaderField("Content-type", "text/plain");
  MockWebURLLoaderClient client;
  MultipartResponseDelegate delegate(&client, NULL, response, "--bound");

  string data(
    "--bound\n"
    "Content-type: text/plain\n\n"
    "This is a sample response\n"
    "--bound--"
    "ignore junk after end token --bound\n\nTest2\n");
  delegate.OnReceivedData(data.c_str(),
                          static_cast<int>(data.length()),
                          static_cast<int>(data.length()));
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
  EXPECT_EQ(string("This is a sample response"), client.data_);
  EXPECT_EQ(static_cast<int>(data.length()), client.total_encoded_data_length_);

  delegate.OnCompletedRequest();
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
}


// Used in for tests that break the data in various places.
struct TestChunk {
  const int start_pos;  // offset in data
  const int end_pos;    // end offset in data
  const int expected_responses;
  const int expected_received_data;
  const char* expected_data;
  const int expected_encoded_data_length;
};

void VariousChunkSizesTest(const TestChunk chunks[], int chunks_size,
                           int responses, int received_data,
                           const char* completed_data,
                           int completed_encoded_data_length) {
  const string data(
    "--bound\n"                    // 0-7
    "Content-type: image/png\n\n"  // 8-32
    "datadatadatadatadata"         // 33-52
    "--bound\n"                    // 53-60
    "Content-type: image/jpg\n\n"  // 61-85
    "foofoofoofoofoo"              // 86-100
    "--bound--");                  // 101-109

  WebURLResponse response;
  response.initialize();
  response.setMIMEType("multipart/x-mixed-replace");
  MockWebURLLoaderClient client;
  MultipartResponseDelegate delegate(&client, NULL, response, "bound");

  for (int i = 0; i < chunks_size; ++i) {
    ASSERT_TRUE(chunks[i].start_pos < chunks[i].end_pos);
    string chunk = data.substr(chunks[i].start_pos,
                               chunks[i].end_pos - chunks[i].start_pos);
    delegate.OnReceivedData(
        chunk.c_str(),
        static_cast<int>(chunk.length()),
        static_cast<int>(chunk.length()));
    EXPECT_EQ(chunks[i].expected_responses, client.received_response_);
    EXPECT_EQ(chunks[i].expected_received_data, client.received_data_);
    EXPECT_EQ(string(chunks[i].expected_data), client.data_);
    EXPECT_EQ(chunks[i].expected_encoded_data_length,
              client.total_encoded_data_length_);
  }
  // Check final state
  delegate.OnCompletedRequest();
  EXPECT_EQ(responses, client.received_response_);
  EXPECT_EQ(received_data, client.received_data_);
  string completed_data_string(completed_data);
  EXPECT_EQ(completed_data_string, client.data_);
  EXPECT_EQ(completed_encoded_data_length, client.total_encoded_data_length_);
}

TEST(MultipartResponseTest, BreakInBoundary) {
  // Break in the first boundary
  const TestChunk bound1[] = {
    { 0, 4, 0, 0, "", 0 },
    { 4, 110, 2, 2, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(bound1, arraysize(bound1),
                        2, 2, "foofoofoofoofoo", 110);

  // Break in first and second
  const TestChunk bound2[] = {
    { 0, 4, 0, 0, "", 0 },
    { 4, 55, 1, 1, "datadatadatadat", 55 },
    { 55, 65, 1, 2, "datadatadatadatadata", 65 },
    { 65, 110, 2, 3, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(bound2, arraysize(bound2),
                        2, 3, "foofoofoofoofoo", 110);

  // Break in second only
  const TestChunk bound3[] = {
    { 0, 55, 1, 1, "datadatadatadat", 55 },
    { 55, 110, 2, 3, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(bound3, arraysize(bound3),
                        2, 3, "foofoofoofoofoo", 110);
}

TEST(MultipartResponseTest, BreakInHeaders) {
  // Break in first header
  const TestChunk header1[] = {
    { 0, 10, 0, 0, "", 0 },
    { 10, 35, 1, 0, "", 0 },
    { 35, 110, 2, 2, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(header1, arraysize(header1),
                        2, 2, "foofoofoofoofoo", 110);

  // Break in both headers
  const TestChunk header2[] = {
    { 0, 10, 0, 0, "", 0 },
    { 10, 65, 1, 1, "datadatadatadatadata", 65 },
    { 65, 110, 2, 2, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(header2, arraysize(header2),
                        2, 2, "foofoofoofoofoo", 110);

  // Break at end of a header
  const TestChunk header3[] = {
    { 0, 33, 1, 0, "", 0 },
    { 33, 65, 1, 1, "datadatadatadatadata", 65 },
    { 65, 110, 2, 2, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(header3, arraysize(header3),
                        2, 2, "foofoofoofoofoo", 110);
}

TEST(MultipartResponseTest, BreakInData) {
  // All data as one chunk
  const TestChunk data1[] = {
    { 0, 110, 2, 2, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(data1, arraysize(data1),
                        2, 2, "foofoofoofoofoo", 110);

  // breaks in data segment
  const TestChunk data2[] = {
    { 0, 35, 1, 0, "", 0 },
    { 35, 65, 1, 1, "datadatadatadatadata", 65 },
    { 65, 90, 2, 1, "", 65 },
    { 90, 110, 2, 2, "foofoofoofoofoo", 110 },
  };
  VariousChunkSizesTest(data2, arraysize(data2),
                        2, 2, "foofoofoofoofoo", 110);

  // Incomplete send
  const TestChunk data3[] = {
    { 0, 35, 1, 0, "", 0 },
    { 35, 90, 2, 1, "", 90 },
  };
  VariousChunkSizesTest(data3, arraysize(data3),
                        2, 2, "foof", 90);
}

TEST(MultipartResponseTest, SmallChunk) {
  WebURLResponse response;
  response.initialize();
  response.setMIMEType("multipart/x-mixed-replace");
  response.setHTTPHeaderField("Content-type", "text/plain");
  MockWebURLLoaderClient client;
  MultipartResponseDelegate delegate(&client, NULL, response, "bound");

  // Test chunks of size 1, 2, and 0.
  string data(
    "--boundContent-type: text/plain\n\n"
    "\n--boundContent-type: text/plain\n\n"
    "\n\n--boundContent-type: text/plain\n\n"
    "--boundContent-type: text/plain\n\n"
    "end--bound--");
  delegate.OnReceivedData(data.c_str(),
                          static_cast<int>(data.length()),
                          static_cast<int>(data.length()));
  EXPECT_EQ(4, client.received_response_);
  EXPECT_EQ(2, client.received_data_);
  EXPECT_EQ(string("end"), client.data_);
  EXPECT_EQ(static_cast<int>(data.length()), client.total_encoded_data_length_);

  delegate.OnCompletedRequest();
  EXPECT_EQ(4, client.received_response_);
  EXPECT_EQ(2, client.received_data_);
}

TEST(MultipartResponseTest, MultipleBoundaries) {
  // Test multiple boundaries back to back
  WebURLResponse response;
  response.initialize();
  response.setMIMEType("multipart/x-mixed-replace");
  MockWebURLLoaderClient client;
  MultipartResponseDelegate delegate(&client, NULL, response, "bound");

  string data("--bound\r\n\r\n--bound\r\n\r\nfoofoo--bound--");
  delegate.OnReceivedData(data.c_str(),
                          static_cast<int>(data.length()),
                          static_cast<int>(data.length()));
  EXPECT_EQ(2, client.received_response_);
  EXPECT_EQ(1, client.received_data_);
  EXPECT_EQ(string("foofoo"), client.data_);
  EXPECT_EQ(static_cast<int>(data.length()), client.total_encoded_data_length_);
}

TEST(MultipartResponseTest, MultipartByteRangeParsingTest) {
  // Test multipart/byteranges based boundary parsing.
  WebURLResponse response1;
  response1.initialize();
  response1.setMIMEType("multipart/x-mixed-replace");
  response1.setHTTPHeaderField("Content-Length", "200");
  response1.setHTTPHeaderField("Content-type",
                               "multipart/byteranges; boundary=--bound--");

  std::string multipart_boundary;
  bool result = MultipartResponseDelegate::ReadMultipartBoundary(
      response1, &multipart_boundary);
  EXPECT_EQ(result, true);
  EXPECT_EQ(string("--bound--"),
            multipart_boundary);

  WebURLResponse response2;
  response2.initialize();
  response2.setMIMEType("image/png");

  response2.setHTTPHeaderField("Content-Length", "300");
  response2.setHTTPHeaderField("Last-Modified",
                               "Mon, 04 Apr 2005 20:36:01 GMT");
  response2.setHTTPHeaderField("Date", "Thu, 11 Sep 2008 18:21:42 GMT");

  multipart_boundary.clear();
  result = MultipartResponseDelegate::ReadMultipartBoundary(
      response2, &multipart_boundary);
  EXPECT_EQ(result, false);

  WebURLResponse response3;
  response3.initialize();
  response3.setMIMEType("multipart/byteranges");

  response3.setHTTPHeaderField("Content-Length", "300");
  response3.setHTTPHeaderField("Last-Modified",
                               "Mon, 04 Apr 2005 20:36:01 GMT");
  response3.setHTTPHeaderField("Date", "Thu, 11 Sep 2008 18:21:42 GMT");
  response3.setHTTPHeaderField("Content-type", "multipart/byteranges");

  multipart_boundary.clear();
  result = MultipartResponseDelegate::ReadMultipartBoundary(
      response3, &multipart_boundary);
  EXPECT_EQ(result, false);
  EXPECT_EQ(multipart_boundary.length(), 0U);

  WebURLResponse response4;
  response4.initialize();
  response4.setMIMEType("multipart/byteranges");
  response4.setHTTPHeaderField("Content-Length", "200");
  response4.setHTTPHeaderField("Content-type",
      "multipart/byteranges; boundary=--bound--; charSet=utf8");

  multipart_boundary.clear();

  result = MultipartResponseDelegate::ReadMultipartBoundary(
      response4, &multipart_boundary);
  EXPECT_EQ(result, true);
  EXPECT_EQ(string("--bound--"), multipart_boundary);

  WebURLResponse response5;
  response5.initialize();
  response5.setMIMEType("multipart/byteranges");
  response5.setHTTPHeaderField("Content-Length", "200");
  response5.setHTTPHeaderField("Content-type",
      "multipart/byteranges; boundary=\"--bound--\"; charSet=utf8");

  multipart_boundary.clear();

  result = MultipartResponseDelegate::ReadMultipartBoundary(
      response5, &multipart_boundary);
  EXPECT_EQ(result, true);
  EXPECT_EQ(string("--bound--"), multipart_boundary);
}

TEST(MultipartResponseTest, MultipartContentRangesTest) {
  WebURLResponse response1;
  response1.initialize();
  response1.setMIMEType("application/pdf");
  response1.setHTTPHeaderField("Content-Length", "200");  // Ignored!
  // Use intentionally >32bit values to check they are handled correctly.
  response1.setHTTPHeaderField("Content-Range",
                               "bytes 5000000000-5000000050/6000000000");

  int64 content_range_lower_bound = 0;
  int64 content_range_upper_bound = 0;
  int64 content_range_instance_size = 0;

  bool result = MultipartResponseDelegate::ReadContentRanges(
      response1, &content_range_lower_bound,
      &content_range_upper_bound,
      &content_range_instance_size);

  EXPECT_EQ(result, true);
  EXPECT_EQ(content_range_lower_bound, 5e9);
  EXPECT_EQ(content_range_upper_bound, 5e9+50);
  EXPECT_EQ(content_range_instance_size, 6e9);

  WebURLResponse response2;
  response2.initialize();
  response2.setMIMEType("application/pdf");
  response2.setHTTPHeaderField("Content-Length", "200");
  response2.setHTTPHeaderField("Content-Range", "bytes 1000/1050");

  content_range_lower_bound = 0;
  content_range_upper_bound = 0;
  content_range_instance_size = 0;

  result = MultipartResponseDelegate::ReadContentRanges(
      response2, &content_range_lower_bound,
      &content_range_upper_bound,
      &content_range_instance_size);

  EXPECT_EQ(result, false);

  WebURLResponse response3;
  response3.initialize();
  response3.setMIMEType("application/pdf");
  response3.setHTTPHeaderField("Content-Length", "200");
  response3.setHTTPHeaderField("Range", "bytes 1000-1050/5000");

  content_range_lower_bound = 0;
  content_range_upper_bound = 0;
  content_range_instance_size = 0;

  result = MultipartResponseDelegate::ReadContentRanges(
      response3, &content_range_lower_bound,
      &content_range_upper_bound,
      &content_range_instance_size);

  EXPECT_EQ(result, true);
  EXPECT_EQ(content_range_lower_bound, 1000);
  EXPECT_EQ(content_range_upper_bound, 1050);

  WebURLResponse response4;
  response4.initialize();
  response4.setMIMEType("application/pdf");
  response4.setHTTPHeaderField("Content-Length", "200");

  content_range_lower_bound = 0;
  content_range_upper_bound = 0;
  content_range_instance_size = 0;

  result = MultipartResponseDelegate::ReadContentRanges(
      response4, &content_range_lower_bound,
      &content_range_upper_bound,
      &content_range_instance_size);

  EXPECT_EQ(result, false);
}

TEST(MultipartResponseTest, MultipartPayloadSet) {
  WebURLResponse response;
  response.initialize();
  response.setMIMEType("multipart/x-mixed-replace");
  MockWebURLLoaderClient client;
  MultipartResponseDelegate delegate(&client, NULL, response, "bound");

  string data(
      "--bound\n"
      "Content-type: text/plain\n\n"
      "response data\n"
      "--bound\n");
  delegate.OnReceivedData(data.c_str(),
                          static_cast<int>(data.length()),
                          static_cast<int>(data.length()));
  EXPECT_EQ(1, client.received_response_);
  EXPECT_EQ(string("response data"), client.data_);
  EXPECT_EQ(static_cast<int>(data.length()), client.total_encoded_data_length_);
  EXPECT_FALSE(client.response_.isMultipartPayload());

  string data2(
      "Content-type: text/plain\n\n"
      "response data2\n"
      "--bound\n");
  delegate.OnReceivedData(data2.c_str(),
                          static_cast<int>(data2.length()),
                          static_cast<int>(data2.length()));
  EXPECT_EQ(2, client.received_response_);
  EXPECT_EQ(string("response data2"), client.data_);
  EXPECT_EQ(static_cast<int>(data.length()) + static_cast<int>(data2.length()),
            client.total_encoded_data_length_);
  EXPECT_TRUE(client.response_.isMultipartPayload());
}

}  // namespace

}  // namespace content
