// 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 "components/embedder_support/android/util/web_resource_response.h"

#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/containers/flat_map.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "components/embedder_support/android/util/input_stream.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_job.h"
#include "third_party/jni_zero/default_conversions.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/embedder_support/android/util_jni/WebResourceResponseInfo_jni.h"

using base::android::AppendJavaStringArrayToStringVector;
using base::android::ConvertJavaStringToUTF8;
using base::android::ScopedJavaLocalRef;

namespace embedder_support {

namespace {

// We include null chars to prevent collisions with any valid cookie header
// names and values.
// LINT.IfChange(MultiCookieKeys)
constexpr char kCookieMultiHeaderNameChars[] = "\0Set-Cookie-Multivalue\0";
constexpr char kCookieMultiHeaderValueSeparatorChars[] = "\0";
// LINT.ThenChange(/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewProviderFactoryBoundaryInterface.java:MultiCookieKeys)

// std::string_literals are banned by the style guide, so this code manually
// constructs the string_view instances with explicit length to ensure they
// contain the \0 characters. Subtracting 1 to remove the compiler-inserted null
// terminator.
constexpr std::string_view kCookieMultiHeaderName(
    kCookieMultiHeaderNameChars,
    sizeof(kCookieMultiHeaderNameChars) - 1);
constexpr std::string_view kCookieMultiHeaderValueSeparator(
    kCookieMultiHeaderValueSeparatorChars,
    sizeof(kCookieMultiHeaderValueSeparatorChars) - 1);

}  // namespace

WebResourceResponse::WebResourceResponse(
    const base::android::JavaRef<jobject>& obj)
    : java_object_(obj) {}

WebResourceResponse::~WebResourceResponse() = default;

bool WebResourceResponse::HasInputStream(JNIEnv* env) const {
  return Java_WebResourceResponseInfo_hasInputStream(env, java_object_);
}

std::unique_ptr<InputStream> WebResourceResponse::GetInputStream(JNIEnv* env) {
  return Java_WebResourceResponseInfo_transferStreamToNative(env, java_object_);
}

bool WebResourceResponse::GetMimeType(JNIEnv* env,
                                      std::string* mime_type) const {
  std::optional<std::string> opt_mime_type =
      Java_WebResourceResponseInfo_getMimeType(env, java_object_);

  if (!opt_mime_type) {
    return false;
  }
  *mime_type = *opt_mime_type;
  return true;
}

bool WebResourceResponse::GetCharset(JNIEnv* env, std::string* charset) const {
  std::optional<std::string> opt_charset =
      Java_WebResourceResponseInfo_getCharset(env, java_object_);
  if (!opt_charset) {
    return false;
  }
  *charset = *opt_charset;
  return true;
}

bool WebResourceResponse::GetStatusInfo(JNIEnv* env,
                                        int* status_code,
                                        std::string* reason_phrase) const {
  int status = Java_WebResourceResponseInfo_getStatusCode(env, java_object_);
  std::optional<std::string> opt_reason_phrase =
      Java_WebResourceResponseInfo_getReasonPhrase(env, java_object_);
  if (status < 100 || status >= 600 || !opt_reason_phrase) {
    return false;
  }
  *status_code = status;
  *reason_phrase = *opt_reason_phrase;
  return true;
}

bool WebResourceResponse::GetResponseHeaders(
    JNIEnv* env,
    net::HttpResponseHeaders* headers) const {
  base::flat_map<std::string, std::string> response_headers =
      Java_WebResourceResponseInfo_getResponseHeaders(env, java_object_);

  bool used_multi_cookie_header = false;
  bool did_modify_headers = false;
  for (const auto& [name, value] : response_headers) {
    if (name == kCookieMultiHeaderName) {
      used_multi_cookie_header = true;
      // The Set-Cookie header is special, in that it cannot be easily joined
      // with comma separation. Instead, we allow the client to provide multiple
      // values separated by the null character, which is not a legal value in
      // header values, so we can then split it here.
      //
      // We do this because the API uses a simple string->string map to supply
      // response header values, so we cannot cleanly send more than one value
      // for each key.
      std::vector<std::string_view> cookies = base::SplitStringPiece(
          value, kCookieMultiHeaderValueSeparator, base::KEEP_WHITESPACE,
          base::SPLIT_WANT_NONEMPTY);
      for (const auto& cookie : cookies) {
        headers->AddCookie(cookie);
      }
      did_modify_headers |= !cookies.empty();
    } else {
      headers->AddHeader(name, value);
      did_modify_headers = true;
    }
  }

  base::UmaHistogramBoolean(
      "Android.WebView.ShouldInterceptRequest.DidIncludeMultiCookieHeader",
      used_multi_cookie_header);
  return did_modify_headers;
}

}  // namespace embedder_support

DEFINE_JNI(WebResourceResponseInfo)
