chromium/ios/testing/embedded_test_server_handlers.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ios/testing/embedded_test_server_handlers.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "url/gurl.h"

namespace testing {

const char kTestFormPage[] = "ios.testing.HandleForm";
const char kTestFormFieldValue[] = "test-value";
const char kTestDownloadMimeType[] = "application/vnd.test";

namespace {
// Extracts and escapes url spec from the query.
std::string ExtractUlrSpecFromQuery(
    const net::test_server::HttpRequest& request) {
  GURL request_url = request.GetURL();
  std::string spec =
      base::UnescapeBinaryURLComponent(request_url.query_piece());

  // Escape the URL spec.
  GURL url(spec);
  return url.is_valid() ? base::EscapeForHTML(url.spec()) : spec;
}

// A HttpResponse that responds with |length| zeroes and kTestDownloadMimeType
// MIME Type.
class DownloadResponse : public net::test_server::BasicHttpResponse {
 public:
  DownloadResponse(int length) : length_(length) {}

  DownloadResponse(const DownloadResponse&) = delete;
  DownloadResponse& operator=(const DownloadResponse&) = delete;

  void SendResponse(
      base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override {
    base::StringPairs headers = {
        {"content-type", kTestDownloadMimeType},
        {"content-length", base::StringPrintf("%d", length_)}};

    delegate->SendResponseHeaders(net::HTTP_OK, "OK", headers);
    Send(delegate, length_);
  }

 private:
  // Sends "0" |count| times using 1KB blocks. Using blocks with smaller size is
  // performance inefficient and can cause unnecessary delays especially when
  // multiple tests run in parallel on a single machine.
  static void Send(
      base::WeakPtr<net::test_server::HttpResponseDelegate> delegate,
      int count) {
    if (!count) {
      if (delegate)
        delegate->FinishResponse();
      return;
    }
    int block_size = std::min(count, 1000);
    std::string content_block(block_size, 0);
    auto next_send =
        base::BindOnce(&DownloadResponse::Send, delegate, count - block_size);

    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&net::test_server::HttpResponseDelegate::SendContents,
                       delegate, content_block, std::move(next_send)),
        base::Milliseconds(100));
  }

  int length_ = 0;
};

// A slow response that would take several hours to finish. This is useful for
// testing scenarios where a load is interrupted after it starts but before it
// finishes.
class SlowResponse : public net::test_server::BasicHttpResponse {
 public:
  SlowResponse() = default;

  SlowResponse(const SlowResponse&) = delete;
  SlowResponse& operator=(const SlowResponse&) = delete;

  void SendResponse(
      base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override {
    delegate_ = delegate;
    delegate_->SendResponseHeaders(
        net::HTTP_OK, "OK",
        {{"Content-Length",
          base::NumberToString(kilobytes_left_to_send_ * 1024)},
         {"Content-Type", "text/plain"}});
    SendKilobyte();
  }

 private:
  void SendKilobyte() {
    if (kilobytes_left_to_send_ == 0) {
      delegate_->FinishResponse();
      return;
    }

    DCHECK_GT(kilobytes_left_to_send_, 0);
    kilobytes_left_to_send_--;

    delegate_->SendContents(
        std::string(1024, 'a'),
        base::BindOnce(&SlowResponse::SendKilobyteAfterDelay,
                       weak_factory_.GetWeakPtr()));
  }

  void SendKilobyteAfterDelay() {
    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&SlowResponse::SendKilobyte, weak_factory_.GetWeakPtr()),
        delay_between_kilobytes_);
  }

  base::WeakPtrFactory<SlowResponse> weak_factory_{this};
  base::WeakPtr<net::test_server::HttpResponseDelegate> delegate_ = nullptr;
  int kilobytes_left_to_send_ = 100000;
  base::TimeDelta delay_between_kilobytes_ = base::Seconds(1);
};

}  // namespace

std::unique_ptr<net::test_server::HttpResponse> HandleIFrame(
    const net::test_server::HttpRequest& request) {
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_content_type("text/html");
  http_response->set_content(
      base::StringPrintf("<html><head></head><body><iframe "
                         "src='%s'></iframe>Main frame text</body></html>",
                         ExtractUlrSpecFromQuery(request).c_str()));
  return std::move(http_response);
}

// Returns a page with |html|.
std::unique_ptr<net::test_server::HttpResponse> HandlePageWithHtml(
    const std::string& html,
    const net::test_server::HttpRequest& request) {
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_content_type("text/html");
  http_response->set_content(html);
  return std::move(http_response);
}

std::unique_ptr<net::test_server::HttpResponse> HandlePageWithContents(
    const net::test_server::HttpRequest& request) {
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_content_type("text/html");
  http_response->set_content(request.GetURL().query());
  return std::move(http_response);
}

std::unique_ptr<net::test_server::HttpResponse> HandleEchoQueryOrCloseSocket(
    const bool& responds_with_content,
    const net::test_server::HttpRequest& request) {
  if (!responds_with_content) {
    return std::make_unique<net::test_server::RawHttpResponse>(
        /*headers=*/"", /*contents=*/"");
  }
  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
  response->set_content_type("text/html");
  response->set_content(request.GetURL().query());
  response->AddCustomHeader("Cache-Control", "no-store");
  return std::move(response);
}

std::unique_ptr<net::test_server::HttpResponse> HandleForm(
    const net::test_server::HttpRequest& request) {
  std::string form_action = ExtractUlrSpecFromQuery(request);
  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
  response->set_content_type("text/html");
  response->set_content(base::StringPrintf(
      "<form method='post' id='form' action='%s'>"
      "  <input type='text' name='test-name' value='%s'>"
      "</form>"
      "%s",
      form_action.c_str(), kTestFormFieldValue, kTestFormPage));

  return std::move(response);
}

std::unique_ptr<net::test_server::HttpResponse> HandleDownload(
    const net::test_server::HttpRequest& request) {
  int length = 0;
  if (!base::StringToInt(request.GetURL().query(), &length)) {
    length = 1;
  }

  return std::make_unique<DownloadResponse>(length);
}

std::unique_ptr<net::test_server::HttpResponse> HandleSlow(
    const net::test_server::HttpRequest& request) {
  return std::make_unique<SlowResponse>();
}

}  // namespace testing