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

#include "third_party/blink/public/platform/web_url_request_util.h"

#include <stddef.h>
#include <stdint.h>

#include <limits>

#include "base/atomic_sequence_num.h"
#include "base/check.h"
#include "base/notreached.h"
#include "base/rand_util.h"
#include "base/time/time.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/data_pipe_getter.mojom-blink.h"
#include "third_party/blink/public/mojom/blob/blob.mojom-blink.h"
#include "third_party/blink/public/mojom/blob/blob_registry.mojom-blink.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom-blink.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-blink.h"
#include "third_party/blink/public/platform/cross_variant_mojo_util.h"
#include "third_party/blink/public/platform/file_path_conversion.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_data.h"
#include "third_party/blink/public/platform/web_http_body.h"
#include "third_party/blink/public/platform/web_http_header_visitor.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/renderer/platform/loader/mixed_content.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"

namespace blink {

namespace {

class HeaderFlattener : public WebHTTPHeaderVisitor {
 public:
  HeaderFlattener() = default;
  ~HeaderFlattener() override = default;

  void VisitHeader(const WebString& name, const WebString& value) override {
    const String wtf_name = name;
    const String wtf_value = value;

    // Skip over referrer headers found in the header map because we already
    // pulled it out as a separate parameter.
    if (EqualIgnoringAsciiCase(wtf_name, "referer")) {
      return;
    }

    if (!buffer_.empty())
      buffer_.Append("\r\n");
    buffer_.Append(wtf_name);
    buffer_.Append(": ");
    buffer_.Append(wtf_value);
  }

  WebString GetBuffer() { return buffer_.ToString(); }

 private:
  StringBuilder buffer_;
};

int GetInitialRequestID() {
  // Starting with a random number speculatively avoids RDH_INVALID_REQUEST_ID
  // which are assumed to have been caused by restarting RequestID at 0 when
  // restarting a renderer after a crash - this would cause collisions if
  // requests from the previously crashed renderer are still active.  See
  // https://crbug.com/614281#c61 for more details about this hypothesis.
  //
  // To avoid increasing the likelihood of overflowing the range of available
  // RequestIDs, kMax is set to a relatively low value of 2^20 (rather than
  // to something higher like 2^31).
  const int kMin = 0;
  const int kMax = 1 << 20;
  return base::RandIntInclusive(kMin, kMax);
}

}  // namespace

WebString GetWebURLRequestHeadersAsString(const WebURLRequest& request) {
  HeaderFlattener flattener;
  request.VisitHttpHeaderFields(&flattener);
  return flattener.GetBuffer();
}

WebHTTPBody GetWebHTTPBodyForRequestBody(
    const network::ResourceRequestBody& input) {
  WebHTTPBody http_body;
  http_body.Initialize();
  http_body.SetIdentifier(input.identifier());
  http_body.SetContainsPasswordData(input.contains_sensitive_info());
  for (auto& element : *input.elements()) {
    switch (element.type()) {
      case network::DataElement::Tag::kBytes: {
        const auto& bytes = element.As<network::DataElementBytes>().bytes();
        http_body.AppendData(WebData(bytes));
        break;
      }
      case network::DataElement::Tag::kFile: {
        const auto& file = element.As<network::DataElementFile>();
        std::optional<base::Time> modification_time;
        if (!file.expected_modification_time().is_null())
          modification_time = file.expected_modification_time();
        http_body.AppendFileRange(
            FilePathToWebString(file.path()), file.offset(),
            (file.length() != std::numeric_limits<uint64_t>::max())
                ? file.length()
                : -1,
            modification_time);
        break;
      }
      case network::DataElement::Tag::kDataPipe: {
        http_body.AppendDataPipe(
            element.As<network::DataElementDataPipe>().CloneDataPipeGetter());
        break;
      }
      case network::DataElement::Tag::kChunkedDataPipe:
        NOTREACHED();
    }
  }
  return http_body;
}

scoped_refptr<network::ResourceRequestBody> GetRequestBodyForWebURLRequest(
    const WebURLRequest& request) {
  scoped_refptr<network::ResourceRequestBody> request_body;

  if (request.HttpBody().IsNull()) {
    return request_body;
  }

  const std::string& method = request.HttpMethod().Latin1();
  // GET and HEAD requests shouldn't have http bodies.
  DCHECK(method != "GET" && method != "HEAD");

  return GetRequestBodyForWebHTTPBody(request.HttpBody());
}

scoped_refptr<network::ResourceRequestBody> GetRequestBodyForWebHTTPBody(
    const WebHTTPBody& httpBody) {
  scoped_refptr<network::ResourceRequestBody> request_body =
      new network::ResourceRequestBody();
  size_t i = 0;
  WebHTTPBody::Element element;
  while (httpBody.ElementAt(i++, element)) {
    switch (element.type) {
      case HTTPBodyElementType::kTypeData:
        request_body->AppendBytes(element.data.Copy());
        break;
      case HTTPBodyElementType::kTypeFile:
        if (element.file_length == -1) {
          request_body->AppendFileRange(
              WebStringToFilePath(element.file_path), 0,
              std::numeric_limits<uint64_t>::max(),
              element.modification_time.value_or(base::Time()));
        } else {
          request_body->AppendFileRange(
              WebStringToFilePath(element.file_path),
              static_cast<uint64_t>(element.file_start),
              static_cast<uint64_t>(element.file_length),
              element.modification_time.value_or(base::Time()));
        }
        break;
      case HTTPBodyElementType::kTypeBlob: {
        DCHECK(element.optional_blob);
        mojo::Remote<mojom::blink::Blob> blob_remote(
            std::move(element.optional_blob));

        mojo::PendingRemote<network::mojom::blink::DataPipeGetter>
            data_pipe_getter_remote;
        blob_remote->AsDataPipeGetter(
            data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
        request_body->AppendDataPipe(
            ToCrossVariantMojoType(std::move(data_pipe_getter_remote)));
        break;
      }
      case HTTPBodyElementType::kTypeDataPipe: {
        mojo::Remote<network::mojom::blink::DataPipeGetter> data_pipe_getter(
            std::move(element.data_pipe_getter));

        // Set the cloned DataPipeGetter to the output |request_body|, while
        // keeping the original message pipe back in the input |httpBody|. This
        // way the consumer of the |httpBody| can retrieve the data pipe
        // multiple times (e.g. during redirects) until the request is finished.
        mojo::PendingRemote<network::mojom::blink::DataPipeGetter>
            cloned_getter;
        data_pipe_getter->Clone(cloned_getter.InitWithNewPipeAndPassReceiver());
        request_body->AppendDataPipe(
            ToCrossVariantMojoType(std::move(cloned_getter)));
        element.data_pipe_getter = data_pipe_getter.Unbind();
        break;
      }
    }
  }
  request_body->set_identifier(httpBody.Identifier());
  request_body->set_contains_sensitive_info(httpBody.ContainsPasswordData());
  return request_body;
}

mojom::blink::RequestContextType GetRequestContextTypeForWebURLRequest(
    const WebURLRequest& request) {
  return static_cast<mojom::blink::RequestContextType>(
      request.GetRequestContext());
}

network::mojom::blink::RequestDestination GetRequestDestinationForWebURLRequest(
    const WebURLRequest& request) {
  return static_cast<network::mojom::blink::RequestDestination>(
      request.GetRequestDestination());
}

mojom::blink::MixedContentContextType
GetMixedContentContextTypeForWebURLRequest(const WebURLRequest& request) {
  return MixedContent::ContextTypeFromRequestContext(
      request.GetRequestContext(), MixedContent::CheckModeForPlugin::kLax);
}

int GenerateRequestId() {
  static const int kInitialRequestID = GetInitialRequestID();
  static base::AtomicSequenceNumber sequence;
  return kInitialRequestID + sequence.GetNext();
}

}  // namespace blink
