// Copyright 2023 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/web/content/navigation/content_navigation_context.h"

#import "base/notreached.h"
#import "content/public/browser/navigation_handle.h"
#import "net/base/net_errors.h"

namespace web {

NSInteger GetIOSErrorForNetError(int net_error_code) {
  switch (net_error_code) {
    case net::ERR_ACCESS_DENIED:
      // TODO(crbug.com/40257932): no analog for
      // kCFURLErrorBackgroundSessionInUseByAnotherProcess.
      return kCFURLErrorNoPermissionsToReadFile;
    case net::ERR_CONNECTION_ABORTED:
      return kCFURLErrorBackgroundSessionWasDisconnected;
    case net::ERR_FAILED:
      return kCFURLErrorUserAuthenticationRequired;
    case net::ERR_ABORTED:
      // TODO(crbug.com/40257932) no analog for
      // kCFURLErrorUserCancelledAuthentication.
      return kCFURLErrorCancelled;
    case net::ERR_INVALID_URL:
      return kCFURLErrorBadURL;
    case net::ERR_CONNECTION_TIMED_OUT:
      return kCFURLErrorTimedOut;
    case net::ERR_UNKNOWN_URL_SCHEME:
      return kCFURLErrorUnsupportedURL;
    case net::ERR_NAME_NOT_RESOLVED:
      // TODO(crbug.com/40257932) no analog for
      // kCFURLErrorRedirectToNonExistentLocation.
      return kCFURLErrorCannotFindHost;
    // Note, we do no not handle mapping any iOS error code back to
    // ERR_SOCKET_NOT_CONNECTED so we will piggy-back on this code here.
    case net::ERR_SOCKET_NOT_CONNECTED:
    case net::ERR_CONNECTION_FAILED:
      // TODO(crbug.com/40257932) no analog for kCFURLErrorCallIsActive.
      return kCFURLErrorCannotConnectToHost;
    case net::ERR_CONNECTION_CLOSED:
      return kCFURLErrorNetworkConnectionLost;
    case net::ERR_NAME_RESOLUTION_FAILED:
      return kCFURLErrorDNSLookupFailed;
    case net::ERR_TOO_MANY_REDIRECTS:
      return kCFURLErrorHTTPTooManyRedirects;
    case net::ERR_INSUFFICIENT_RESOURCES:
      return kCFURLErrorResourceUnavailable;
    case net::ERR_INTERNET_DISCONNECTED:
      // TODO(crbug.com/40257932): no analog for
      // kCFURLErrorInternationalRoamingOff.
      // TODO(crbug.com/40257932): no analog for kCFURLErrorDataNotAllowed.
      return kCFURLErrorNotConnectedToInternet;
    case net::ERR_INVALID_RESPONSE:
      // TODO(crbug.com/40257932) no analog for kCFURLErrorCannotParseResponse.
      return kCFURLErrorBadServerResponse;
    case net::ERR_EMPTY_RESPONSE:
      return kCFURLErrorZeroByteResource;
    case net::ERR_CONTENT_DECODING_FAILED:
      // TODO(crbug.com/40257932): no analog for kCFURLErrorCannotDecodeRawData.
      return kCFURLErrorCannotDecodeContentData;
    case net::ERR_CONTENT_LENGTH_MISMATCH:
      return kCFURLErrorRequestBodyStreamExhausted;
    case net::ERR_FILE_NOT_FOUND:
      return kCFURLErrorFileDoesNotExist;
    case net::ERR_INVALID_HANDLE:
      return kCFURLErrorFileIsDirectory;
    case net::ERR_FILE_TOO_BIG:
      return kCFURLErrorDataLengthExceedsMaximum;
    case net::ERR_SSL_PROTOCOL_ERROR:
      return kCFURLErrorSecureConnectionFailed;
    case net::ERR_CERT_DATE_INVALID:
      // TODO(crbug.com/40257932): no analog for
      // kCFURLErrorServerCertificateNotYetValid.
      return kCFURLErrorServerCertificateHasBadDate;
    case net::ERR_CERT_AUTHORITY_INVALID:
    case net::ERR_CERT_INVALID:
      // TODO(crbug.com/40257932): no analog for
      // kCFURLErrorServerCertificateHasUnknownRoot.
      return kCFURLErrorServerCertificateUntrusted;
    case net::ERR_BAD_SSL_CLIENT_AUTH_CERT:
      return kCFURLErrorClientCertificateRejected;
    case net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED:
      return kCFURLErrorClientCertificateRequired;
    default:
      // There are 200+ net error codes but only ~40 iOS error codes.
      // Following Android WebView's pattern (AwWebResourceError.java),
      // we return a generic error for unmapped codes rather than maintaining
      // an exhaustive mapping.
      return kCFURLErrorUnknown;
  }
}

NavigationContext* ContentNavigationContext::GetOrCreate(
    content::NavigationHandle* handle,
    WebState* web_state) {
  auto* context = ContentNavigationContext::GetForNavigationHandle(*handle);
  if (!context) {
    ContentNavigationContext::CreateForNavigationHandle(*handle, web_state);
    context = ContentNavigationContext::GetForNavigationHandle(*handle);
  }
  return context;
}

ContentNavigationContext::~ContentNavigationContext() = default;

WebState* ContentNavigationContext::GetWebState() {
  return web_state_;
}

int64_t ContentNavigationContext::GetNavigationId() const {
  return handle_->GetNavigationId();
}

const GURL& ContentNavigationContext::GetUrl() const {
  return handle_->GetURL();
}

bool ContentNavigationContext::HasUserGesture() const {
  return handle_->HasUserGesture();
}

ui::PageTransition ContentNavigationContext::GetPageTransition() const {
  return handle_->GetPageTransition();
}

bool ContentNavigationContext::IsSameDocument() const {
  return handle_->IsSameDocument();
}

bool ContentNavigationContext::HasCommitted() const {
  return handle_->HasCommitted();
}

bool ContentNavigationContext::IsDownload() const {
  return handle_->IsDownload();
}

bool ContentNavigationContext::IsPost() const {
  return handle_->IsPost();
}

NSError* ContentNavigationContext::GetError() const {
  net::Error net_error_code = handle_->GetNetErrorCode();
  if (net_error_code == net::OK) {
    error_ = nil;
  } else {
    auto ios_error_code = GetIOSErrorForNetError(net_error_code);
    error_ = [[NSError alloc]
        initWithDomain:NSURLErrorDomain
                  code:ios_error_code
              userInfo:@{
                @"url" : [NSString stringWithUTF8String:GetUrl().spec().c_str()]
              }];
  }
  return error_;
}

net::HttpResponseHeaders* ContentNavigationContext::GetResponseHeaders() const {
  return const_cast<net::HttpResponseHeaders*>(handle_->GetResponseHeaders());
}

bool ContentNavigationContext::IsRendererInitiated() const {
  return handle_->IsRendererInitiated();
}

HttpsUpgradeType ContentNavigationContext::GetFailedHttpsUpgradeType() const {
  // TODO(crbug.com/40257932): Determine an analog.
  return HttpsUpgradeType::kNone;
}

ContentNavigationContext::ContentNavigationContext(
    content::NavigationHandle& handle,
    WebState* web_state)
    : handle_(handle), web_state_(web_state) {}

NAVIGATION_HANDLE_USER_DATA_KEY_IMPL(ContentNavigationContext);

}  // namespace web
