text: /*
   *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
   *
   *  Use of this source code is governed by a BSD-style license
   *  that can be found in the LICENSE file in the root of the source
   *  tree. An additional intellectual property rights grant can be found
   *  in the file PATENTS.  All contributing project authors may
   *  be found in the AUTHORS file in the root of the source tree.
   */
  
  #include <algorithm>
  
  #include "webrtc/base/gunit.h"
  #include "webrtc/base/httpbase.h"
  #include "webrtc/base/testutils.h"
  
  namespace rtc {
  
  const char* const kHttpResponse =
    "HTTP/1.1 200\r\n"
    "Connection: Keep-Alive\r\n"
    "Content-Type: text/plain\r\n"
    "Proxy-Authorization: 42\r\n"
    "Transfer-Encoding: chunked\r\n"
    "\r\n"
    "00000008\r\n"
    "Goodbye!\r\n"
    "0\r\n\r\n";
  
  const char* const kHttpEmptyResponse =
    "HTTP/1.1 200\r\n"
    "Connection: Keep-Alive\r\n"
    "Content-Length: 0\r\n"
    "Proxy-Authorization: 42\r\n"
    "\r\n";
  
  const char* const kHttpResponsePrefix =
    "HTTP/1.1 200\r\n"
    "Connection: Keep-Alive\r\n"
    "Content-Type: text/plain\r\n"
    "Proxy-Authorization: 42\r\n"
    "Transfer-Encoding: chunked\r\n"
    "\r\n"
    "8\r\n"
    "Goodbye!\r\n";
  
  class HttpBaseTest : public testing::Test, public IHttpNotify {
  public:
    enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
    struct Event {
      EventType event;
      bool chunked;
      size_t data_size;
      HttpMode mode;
      HttpError err;
    };
    HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { }
  
    virtual void SetUp() { }
    virtual void TearDown() {
      delete http_stream;
      // Avoid an ASSERT, in case a test doesn't clean up properly
      base.abort(HE_NONE);
    }
  
    virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
      LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
      Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
      events.push_back(e);
      if (obtain_stream) {
        ObtainDocumentStream();
      }
      return HE_NONE;
    }
    virtual void onHttpComplete(HttpMode mode, HttpError err) {
      LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
      Event e = { E_COMPLETE, false, 0, mode, err };
      events.push_back(e);
    }
    virtual void onHttpClosed(HttpError err) {
      LOG_F(LS_VERBOSE) << "err: " << err;
      Event e = { E_CLOSED, false, 0, HM_NONE, err };
      events.push_back(e);
    }
  
    void SetupSource(const char* response);
  
    void VerifyHeaderComplete(size_t event_count, bool empty_doc);
    void VerifyDocumentContents(const char* expected_data,
                                size_t expected_length = SIZE_UNKNOWN);
  
    void ObtainDocumentStream();
    void VerifyDocumentStreamIsOpening();
    void VerifyDocumentStreamOpenEvent();
    void ReadDocumentStreamData(const char* expected_data);
    void VerifyDocumentStreamIsEOS();
  
    void SetupDocument(const char* response);
    void VerifySourceContents(const char* expected_data,
                              size_t expected_length = SIZE_UNKNOWN);
  
    void VerifyTransferComplete(HttpMode mode, HttpError error);
  
    HttpBase base;
    MemoryStream* mem;
    HttpResponseData data;
  
    // The source of http data, and source events
    testing::StreamSource src;
    std::vector<Event> events;
  
    // Document stream, and stream events
    bool obtain_stream;
    StreamInterface* http_stream;
    testing::StreamSink sink;
  };
  
  void HttpBaseTest::SetupSource(const char* http_data) {
    LOG_F(LS_VERBOSE) << "Enter";
  
    src.SetState(SS_OPENING);
    src.QueueString(http_data);
  
    base.notify(this);
    base.attach(&src);
    EXPECT_TRUE(events.empty());
  
    src.SetState(SS_OPEN);
    ASSERT_EQ(1U, events.size());
    EXPECT_EQ(E_COMPLETE, events[0].event);
    EXPECT_EQ(HM_CONNECT, events[0].mode);
    EXPECT_EQ(HE_NONE, events[0].err);
    events.clear();
  
    mem = new MemoryStream;
    data.document.reset(mem);
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
    LOG_F(LS_VERBOSE) << "Enter";
  
    ASSERT_EQ(event_count, events.size());
    EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
  
    std::string header;
    EXPECT_EQ(HVER_1_1, data.version);
    EXPECT_EQ(static_cast<uint32>(HC_OK), data.scode);
    EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
    EXPECT_EQ("42", header);
    EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
    EXPECT_EQ("Keep-Alive", header);
  
    if (empty_doc) {
      EXPECT_FALSE(events[0].chunked);
      EXPECT_EQ(0U, events[0].data_size);
  
      EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
      EXPECT_EQ("0", header);
    } else {
      EXPECT_TRUE(events[0].chunked);
      EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
  
      EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
      EXPECT_EQ("text/plain", header);
      EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
      EXPECT_EQ("chunked", header);
    }
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
                                            size_t expected_length) {
    LOG_F(LS_VERBOSE) << "Enter";
  
    if (SIZE_UNKNOWN == expected_length) {
      expected_length = strlen(expected_data);
    }
    EXPECT_EQ(mem, data.document.get());
  
    size_t length;
    mem->GetSize(&length);
    EXPECT_EQ(expected_length, length);
    EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::ObtainDocumentStream() {
    LOG_F(LS_VERBOSE) << "Enter";
    EXPECT_FALSE(http_stream);
    http_stream = base.GetDocumentStream();
    ASSERT_TRUE(NULL != http_stream);
    sink.Monitor(http_stream);
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::VerifyDocumentStreamIsOpening() {
    LOG_F(LS_VERBOSE) << "Enter";
    ASSERT_TRUE(NULL != http_stream);
    EXPECT_EQ(0, sink.Events(http_stream));
    EXPECT_EQ(SS_OPENING, http_stream->GetState());
  
    size_t read = 0;
    char buffer[5] = { 0 };
    EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
    LOG_F(LS_VERBOSE) << "Enter";
  
    ASSERT_TRUE(NULL != http_stream);
    EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
    EXPECT_EQ(SS_OPEN, http_stream->GetState());
  
    // HTTP headers haven't arrived yet
    EXPECT_EQ(0U, events.size());
    EXPECT_EQ(static_cast<uint32>(HC_INTERNAL_SERVER_ERROR), data.scode);
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
    LOG_F(LS_VERBOSE) << "Enter";
  
    ASSERT_TRUE(NULL != http_stream);
    EXPECT_EQ(SS_OPEN, http_stream->GetState());
  
    // Pump the HTTP I/O using Read, and verify the results.
    size_t verified_length = 0;
    const size_t expected_length = strlen(expected_data);
    while (verified_length < expected_length) {
      size_t read = 0;
      char buffer[5] = { 0 };
      size_t amt_to_read =
          std::min(expected_length - verified_length, sizeof(buffer));
      EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL));
      EXPECT_EQ(amt_to_read, read);
      EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
      verified_length += read;
    }
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::VerifyDocumentStreamIsEOS() {
    LOG_F(LS_VERBOSE) << "Enter";
  
    ASSERT_TRUE(NULL != http_stream);
    size_t read = 0;
    char buffer[5] = { 0 };
    EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
    EXPECT_EQ(SS_CLOSED, http_stream->GetState());
  
    // When EOS is caused by Read, we don't expect SE_CLOSE
    EXPECT_EQ(0, sink.Events(http_stream));
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::SetupDocument(const char* document_data) {
    LOG_F(LS_VERBOSE) << "Enter";
    src.SetState(SS_OPEN);
  
    base.notify(this);
    base.attach(&src);
    EXPECT_TRUE(events.empty());
  
    if (document_data) {
      // Note: we could just call data.set_success("text/plain", mem), but that
      // won't allow us to use the chunked transfer encoding.
      mem = new MemoryStream(document_data);
      data.document.reset(mem);
      data.setHeader(HH_CONTENT_TYPE, "text/plain");
      data.setHeader(HH_TRANSFER_ENCODING, "chunked");
    } else {
      data.setHeader(HH_CONTENT_LENGTH, "0");
    }
    data.scode = HC_OK;
    data.setHeader(HH_PROXY_AUTHORIZATION, "42");
    data.setHeader(HH_CONNECTION, "Keep-Alive");
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::VerifySourceContents(const char* expected_data,
                                          size_t expected_length) {
    LOG_F(LS_VERBOSE) << "Enter";
    if (SIZE_UNKNOWN == expected_length) {
      expected_length = strlen(expected_data);
    }
    std::string contents = src.ReadData();
    EXPECT_EQ(expected_length, contents.length());
    EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
    LOG_F(LS_VERBOSE) << "Enter";
    // Verify that http operation has completed
    ASSERT_TRUE(events.size() > 0);
    size_t last_event = events.size() - 1;
    EXPECT_EQ(E_COMPLETE, events[last_event].event);
    EXPECT_EQ(mode, events[last_event].mode);
    EXPECT_EQ(error, events[last_event].err);
    LOG_F(LS_VERBOSE) << "Exit";
  }
  
  //
  // Tests
  //
  
  TEST_F(HttpBaseTest, SupportsSend) {
    // Queue response document
    SetupDocument("Goodbye!");
  
    // Begin send
    base.send(&data);
  
    // Send completed successfully
    VerifyTransferComplete(HM_SEND, HE_NONE);
    VerifySourceContents(kHttpResponse);
  }
  
  TEST_F(HttpBaseTest, SupportsSendNoDocument) {
    // Queue response document
    SetupDocument(NULL);
  
    // Begin send
    base.send(&data);
  
    // Send completed successfully
    VerifyTransferComplete(HM_SEND, HE_NONE);
    VerifySourceContents(kHttpEmptyResponse);
  }
  
  TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
    // This test is attempting to expose a bug that occurs when a particular
    // base objects is used for receiving, and then used for sending.  In
    // particular, the HttpParser state is different after receiving.  Simulate
    // that here.
    SetupSource(kHttpResponse);
    base.recv(&data);
    VerifyTransferComplete(HM_RECV, HE_NONE);
  
    src.Clear();
    data.clear(true);
    events.clear();
    base.detach();
  
    // Queue response document
    SetupDocument("Goodbye!");
  
    // Prevent entire response from being sent
    const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
    src.SetWriteBlock(kInterruptedLength);
  
    // Begin send
    base.send(&data);
  
    // Document is mostly complete, but no completion signal yet.
    EXPECT_TRUE(events.empty());
    VerifySourceContents(kHttpResponse, kInterruptedLength);
  
    src.SetState(SS_CLOSED);
  
    // Send completed with disconnect error, and no additional data.
    VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
    EXPECT_TRUE(src.ReadData().empty());
  }
  
  TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
    // Queue response document
    SetupSource(kHttpResponse);
  
    // Begin receive
    base.recv(&data);
  
    // Document completed successfully
    VerifyHeaderComplete(2, false);
    VerifyTransferComplete(HM_RECV, HE_NONE);
    VerifyDocumentContents("Goodbye!");
  }
  
  TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
    // Switch to pull mode
    ObtainDocumentStream();
    VerifyDocumentStreamIsOpening();
  
    // Queue response document
    SetupSource(kHttpResponse);
    VerifyDocumentStreamIsOpening();
  
    // Begin receive
    base.recv(&data);
  
    // Pull document data
    VerifyDocumentStreamOpenEvent();
    ReadDocumentStreamData("Goodbye!");
    VerifyDocumentStreamIsEOS();
  
    // Document completed successfully
    VerifyHeaderComplete(2, false);
    VerifyTransferComplete(HM_RECV, HE_NONE);
    VerifyDocumentContents("");
  }
  
  TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
  
    // TODO: Remove extra logging once test failure is understood
    LoggingSeverity old_sev = rtc::LogMessage::GetLogToDebug();
    rtc::LogMessage::LogToDebug(LS_VERBOSE);
  
  
    // Switch to pull mode
    ObtainDocumentStream();
    VerifyDocumentStreamIsOpening();
  
    // Queue response document
    SetupSource(kHttpResponse);
    VerifyDocumentStreamIsOpening();
  
    // Begin receive
    base.recv(&data);
  
    // Pull some of the data
    VerifyDocumentStreamOpenEvent();
    ReadDocumentStreamData("Goodb");
  
    // We've seen the header by now
    VerifyHeaderComplete(1, false);
  
    // Close the pull stream, this will transition back to push I/O.
    http_stream->Close();
    Thread::Current()->ProcessMessages(0);
  
    // Remainder of document completed successfully
    VerifyTransferComplete(HM_RECV, HE_NONE);
    VerifyDocumentContents("ye!");
  
    rtc::LogMessage::LogToDebug(old_sev);
  }
  
  TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
    // Queue response document
    SetupSource(kHttpResponse);
  
    // Switch to pull mode in response to header arrival
    obtain_stream = true;
  
    // Begin receive
    base.recv(&data);
  
    // We've already seen the header, but not data has arrived
    VerifyHeaderComplete(1, false);
    VerifyDocumentContents("");
  
    // Pull the document data
    ReadDocumentStreamData("Goodbye!");
    VerifyDocumentStreamIsEOS();
  
    // Document completed successfully
    VerifyTransferComplete(HM_RECV, HE_NONE);
    VerifyDocumentContents("");
  }
  
  TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
    // Queue empty response document
    SetupSource(kHttpEmptyResponse);
  
    // Switch to pull mode in response to header arrival
    obtain_stream = true;
  
    // Begin receive
    base.recv(&data);
  
    // We've already seen the header, but not data has arrived
    VerifyHeaderComplete(1, true);
    VerifyDocumentContents("");
  
    // The document is still open, until we attempt to read
    ASSERT_TRUE(NULL != http_stream);
    EXPECT_EQ(SS_OPEN, http_stream->GetState());
  
    // Attempt to read data, and discover EOS
    VerifyDocumentStreamIsEOS();
  
    // Document completed successfully
    VerifyTransferComplete(HM_RECV, HE_NONE);
    VerifyDocumentContents("");
  }
  
  TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
    // Switch to pull mode
    ObtainDocumentStream();
    VerifyDocumentStreamIsOpening();
  
    // Queue response document
    SetupSource(kHttpResponsePrefix);
    VerifyDocumentStreamIsOpening();
  
    // Begin receive
    base.recv(&data);
  
    // Pull document data
    VerifyDocumentStreamOpenEvent();
    ReadDocumentStreamData("Goodbye!");
  
    // Simulate unexpected close
    src.SetState(SS_CLOSED);
  
    // Observe error event on document stream
    EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
  
    // Future reads give an error
    int error = 0;
    char buffer[5] = { 0 };
    EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error));
    EXPECT_EQ(HE_DISCONNECTED, error);
  
    // Document completed with error
    VerifyHeaderComplete(2, false);
    VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
    VerifyDocumentContents("");
  }
  
  } // namespace rtc
  