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

#import "ios/net/protocol_handler_util.h"

#import <string>

#import "base/base64.h"
#import "base/logging.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "ios/net/crn_http_url_response.h"
#import "net/base/apple/url_conversions.h"
#import "net/base/net_errors.h"
#import "net/http/http_request_headers.h"
#import "net/http/http_response_headers.h"
#import "net/http/http_version.h"
#import "net/url_request/referrer_policy.h"
#import "net/url_request/url_request.h"
#import "url/buildflags.h"
#import "url/gurl.h"

#if !BUILDFLAG(USE_PLATFORM_ICU_ALTERNATIVES)
#import "base/i18n/encoding_detection.h"      // nogncheck
#import "base/i18n/icu_string_conversions.h"  // nogncheck
#endif  // !BUILDFLAG(USE_PLATFORM_ICU_ALTERNATIVES)

namespace {

// "Content-Type" HTTP header.
NSString* const kContentType = @"Content-Type";

}  // namespace

namespace net {

NSString* const kNSErrorDomain = @"org.chromium.net.ErrorDomain";

NSURLResponse* GetNSURLResponseForRequest(URLRequest* request) {
  NSURL* url = NSURLWithGURL(request->url());
  DCHECK(url);

  // Iterate over all the headers and copy them.
  bool has_content_type_header = false;
  NSMutableDictionary* header_fields = [NSMutableDictionary dictionary];
  HttpResponseHeaders* headers = request->response_headers();
  if (headers != nullptr) {
    size_t iter = 0;
    std::string name, value;
    while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
      NSString* key = base::SysUTF8ToNSString(name);
      if (!key) {
        DLOG(ERROR) << "Header name is not in UTF8: " << name;
        // Skip the invalid header.
        continue;
      }
      // Do not copy "Cache-Control" headers as we provide our own controls.
      if ([key caseInsensitiveCompare:@"cache-control"] == NSOrderedSame) {
        continue;
      }
      if ([key caseInsensitiveCompare:kContentType] == NSOrderedSame) {
        key = kContentType;
        has_content_type_header = true;
      }

      // Handle bad encoding.
      NSString* v = base::SysUTF8ToNSString(value);
      if (!v) {
        DLOG(ERROR) << "Header \"" << name << "\" is not in UTF8: " << value;
#if BUILDFLAG(USE_PLATFORM_ICU_ALTERNATIVES)
        DCHECK(FALSE) << "ICU support is required, but not included.";
        continue;
#else
        // Infer the encoding, or skip the header if it's not possible.
        std::string encoding;
        if (!base::DetectEncoding(value, &encoding)) {
          continue;
        }
        std::string value_utf8;
        if (!base::ConvertToUtf8AndNormalize(value, encoding, &value_utf8)) {
          continue;
        }
        v = base::SysUTF8ToNSString(value_utf8);
        DCHECK(v);
#endif  // !BUILDFLAG(USE_PLATFORM_ICU_ALTERNATIVES)
      }

      // Duplicate keys are appended using a comma separator (RFC 2616).
      NSMutableString* existing = [header_fields objectForKey:key];
      if (existing) {
        [existing appendFormat:@",%@", v];
      } else {
        [header_fields setObject:[NSMutableString stringWithString:v]
                          forKey:key];
      }
    }
  }

  // WebUI does not define a "Content-Type" header. Use the MIME type instead.
  if (!has_content_type_header) {
    std::string mime_type = "";
    request->GetMimeType(&mime_type);
    NSString* type = base::SysUTF8ToNSString(mime_type);
    if ([type length]) {
      [header_fields setObject:type forKey:kContentType];
    }
  }
  NSString* content_type = [header_fields objectForKey:kContentType];
  if (content_type) {
    NSRange range = [content_type rangeOfString:@","];
    // If there are several "Content-Type" headers, keep only the first one.
    if (range.location != NSNotFound) {
      [header_fields setObject:[content_type substringToIndex:range.location]
                        forKey:kContentType];
    }
  }

  // Use a "no-store" cache control to ensure that the response is not cached
  // by the system. See b/7045043.
  [header_fields setObject:@"no-store" forKey:@"Cache-Control"];

  // Parse the HTTP version.
  NSString* version_string = @"HTTP/1.1";
  if (headers) {
    const HttpVersion& http_version = headers->GetHttpVersion();
    version_string =
        [NSString stringWithFormat:@"HTTP/%hu.%hu", http_version.major_value(),
                                   http_version.minor_value()];
  }

  return [[CRNHTTPURLResponse alloc] initWithURL:url
                                      statusCode:request->GetResponseCode()
                                     HTTPVersion:version_string
                                    headerFields:header_fields];
}

void CopyHttpHeaders(NSURLRequest* in_request, URLRequest* out_request) {
  DCHECK(out_request->extra_request_headers().IsEmpty());
  NSDictionary* headers = [in_request allHTTPHeaderFields];
  HttpRequestHeaders net_headers;
  NSString* key;
  for (key in headers) {
    if ([key isEqualToString:@"Referer"]) {
      // The referrer must be set through the set_referrer method rather than as
      // a header.
      out_request->SetReferrer(
          base::SysNSStringToUTF8([headers objectForKey:key]));
      // If the referrer is explicitly set, we don't want the network stack to
      // strip it.
      out_request->set_referrer_policy(net::ReferrerPolicy::NEVER_CLEAR);
      continue;
    }
    // Copy over all headers that were set on NSURLRequest
    NSString* value = [headers objectForKey:key];
    net_headers.SetHeader(base::SysNSStringToUTF8(key),
                          base::SysNSStringToUTF8(value));
  }
  // Set default values for some missing headers.
  // The "Accept" header is defined by Webkit on the desktop version.
  net_headers.SetHeaderIfMissing("Accept", "*/*");
  // The custom NSURLProtocol example from Apple adds a default "Content-Type"
  // header for non-empty POST requests. This suggests that this header can be
  // missing, and Chrome network stack does not add it by itself.
  if (out_request->has_upload() && out_request->method() == "POST") {
    DLOG_IF(WARNING, !net_headers.HasHeader(HttpRequestHeaders::kContentType))
        << "Missing \"Content-Type\" header in POST request.";
    net_headers.SetHeaderIfMissing(HttpRequestHeaders::kContentType,
                                   "application/x-www-form-urlencoded");
  }
  out_request->SetExtraRequestHeaders(net_headers);
}

}  // namespace net
