// Copyright 2015 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/util/error_translation_util.h"

#import <CFNetwork/CFNetwork.h>
#import <Foundation/Foundation.h>

#import "base/ios/ios_util.h"
#import "base/ios/ns_error_util.h"
#import "ios/net/protocol_handler_util.h"
#import "ios/web/common/features.h"
#import "ios/web/public/web_client.h"
#import "net/base/apple/url_conversions.h"
#import "net/base/net_errors.h"
#import "url/gurl.h"

namespace web {

bool GetNetErrorFromIOSErrorCode(NSInteger ios_error_code,
                                 int* net_error_code,
                                 NSURL* url) {
  DCHECK(net_error_code);
  bool translation_success = true;
  switch (ios_error_code) {
    case kCFURLErrorBackgroundSessionInUseByAnotherProcess:
      *net_error_code = net::ERR_ACCESS_DENIED;
      break;
    case kCFURLErrorBackgroundSessionWasDisconnected:
      *net_error_code = net::ERR_CONNECTION_ABORTED;
      break;
    case kCFURLErrorUnknown:
      *net_error_code = net::ERR_FAILED;
      break;
    case kCFURLErrorCancelled:
      *net_error_code = net::ERR_ABORTED;
      break;
    case kCFURLErrorBadURL:
      *net_error_code = net::ERR_INVALID_URL;
      break;
    case kCFURLErrorTimedOut:
      *net_error_code = net::ERR_CONNECTION_TIMED_OUT;
      break;
    case kCFURLErrorUnsupportedURL:
      if (GetWebClient()->IsAppSpecificURL(net::GURLWithNSURL(url))) {
        // Scheme is valid, but URL is not supported.
        *net_error_code = net::ERR_INVALID_URL;
      } else {
        // Scheme is not app-specific and not supported by WebState.
        *net_error_code = net::ERR_UNKNOWN_URL_SCHEME;
      }
      break;
    case kCFURLErrorCannotFindHost:
      *net_error_code = net::ERR_NAME_NOT_RESOLVED;
      break;
    case kCFURLErrorCannotConnectToHost:
      *net_error_code = net::ERR_CONNECTION_FAILED;
      break;
    case kCFURLErrorNetworkConnectionLost:
      // This looks like catch-all code for errors like ERR_CONNECTION_CLOSED,
      // ERR_EMPTY_RESPONSE, ERR_NETWORK_CHANGED or ERR_CONNECTION_RESET.
      // ERR_CONNECTION_CLOSED is too specific for this case, but there is no
      // better cross platform analogue.
      *net_error_code = net::ERR_CONNECTION_CLOSED;
      break;
    case kCFURLErrorDNSLookupFailed:
      *net_error_code = net::ERR_NAME_RESOLUTION_FAILED;
      break;
    case kCFURLErrorHTTPTooManyRedirects:
      *net_error_code = net::ERR_TOO_MANY_REDIRECTS;
      break;
    case kCFURLErrorResourceUnavailable:
      *net_error_code = net::ERR_INSUFFICIENT_RESOURCES;
      break;
    case kCFURLErrorNotConnectedToInternet:
      *net_error_code = net::ERR_INTERNET_DISCONNECTED;
      break;
    case kCFURLErrorRedirectToNonExistentLocation:
      *net_error_code = net::ERR_NAME_NOT_RESOLVED;
      break;
    case kCFURLErrorBadServerResponse:
      *net_error_code = net::ERR_INVALID_RESPONSE;
      break;
    case kCFURLErrorUserCancelledAuthentication:
      *net_error_code = net::ERR_ABORTED;
      break;
    case kCFURLErrorUserAuthenticationRequired:
      *net_error_code = net::ERR_FAILED;
      break;
    case kCFURLErrorZeroByteResource:
      *net_error_code = net::ERR_EMPTY_RESPONSE;
      break;
    case kCFURLErrorCannotDecodeRawData:
      *net_error_code = net::ERR_CONTENT_DECODING_FAILED;
      break;
    case kCFURLErrorCannotDecodeContentData:
      *net_error_code = net::ERR_CONTENT_DECODING_FAILED;
      break;
    case kCFURLErrorCannotParseResponse:
      *net_error_code = net::ERR_INVALID_RESPONSE;
      break;
    case kCFURLErrorInternationalRoamingOff:
      *net_error_code = net::ERR_INTERNET_DISCONNECTED;
      break;
    case kCFURLErrorCallIsActive:
      *net_error_code = net::ERR_CONNECTION_FAILED;
      break;
    case kCFURLErrorDataNotAllowed:
      *net_error_code = net::ERR_INTERNET_DISCONNECTED;
      break;
    case kCFURLErrorRequestBodyStreamExhausted:
      *net_error_code = net::ERR_CONTENT_LENGTH_MISMATCH;
      break;
    case kCFURLErrorFileDoesNotExist:
      *net_error_code = net::ERR_FILE_NOT_FOUND;
      break;
    case kCFURLErrorFileIsDirectory:
      *net_error_code = net::ERR_INVALID_HANDLE;
      break;
    case kCFURLErrorNoPermissionsToReadFile:
      *net_error_code = net::ERR_ACCESS_DENIED;
      break;
    case kCFURLErrorDataLengthExceedsMaximum:
      *net_error_code = net::ERR_FILE_TOO_BIG;
      break;
    case kCFURLErrorSecureConnectionFailed:
      *net_error_code = net::ERR_SSL_PROTOCOL_ERROR;
      break;
    case kCFURLErrorServerCertificateHasBadDate:
      *net_error_code = net::ERR_CERT_DATE_INVALID;
      break;
    case kCFURLErrorServerCertificateUntrusted:
      *net_error_code = net::ERR_CERT_AUTHORITY_INVALID;
      break;
    case kCFURLErrorServerCertificateHasUnknownRoot:
      *net_error_code = net::ERR_CERT_AUTHORITY_INVALID;
      break;
    case kCFURLErrorServerCertificateNotYetValid:
      *net_error_code = net::ERR_CERT_DATE_INVALID;
      break;
    case kCFURLErrorClientCertificateRejected:
      *net_error_code = net::ERR_BAD_SSL_CLIENT_AUTH_CERT;
      break;
    case kCFURLErrorClientCertificateRequired:
      *net_error_code = net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
      break;
    default:
      translation_success = false;
      break;
  }
  return translation_success;
}

NSError* NetErrorFromError(NSError* error) {
  DCHECK(error);

  int net_error_code = net::ERR_FAILED;
  if (base::ios::IsRunningOnOrLater(26, 4, 0) &&
      base::FeatureList::IsEnabled(
          web::features::kNetErrorFromErrorChainKillSwitch)) {
    NSURL* url = error.userInfo[NSURLErrorFailingURLErrorKey];

    // Iterate through the error chain to find a translatable error.
    NSError* current_error = error;
    while (current_error) {
      if ([current_error.domain isEqualToString:NSURLErrorDomain] ||
          [current_error.domain isEqualToString:static_cast<NSString*>(
                                                    kCFErrorDomainCFNetwork)]) {
        if (GetNetErrorFromIOSErrorCode(current_error.code, &net_error_code,
                                        url)) {
          break;
        }
      }
      current_error = current_error.userInfo[NSUnderlyingErrorKey];
    }
  } else {
    NSError* underlying_error =
        base::ios::GetFinalUnderlyingErrorFromError(error);
    if ([underlying_error.domain isEqualToString:NSURLErrorDomain] ||
        [underlying_error.domain
            isEqualToString:static_cast<NSString*>(kCFErrorDomainCFNetwork)]) {
      // Attempt to translate NSURL and CFNetwork error codes into their
      // corresponding net error codes.
      NSURL* url = error.userInfo[NSURLErrorFailingURLErrorKey];
      GetNetErrorFromIOSErrorCode(underlying_error.code, &net_error_code, url);
    }
  }

  return NetErrorFromError(error, net_error_code);
}

NSError* NetErrorFromError(NSError* error, int net_error_code) {
  DCHECK(error);
  NSError* net_error =
      [NSError errorWithDomain:net::kNSErrorDomain
                          code:static_cast<NSInteger>(net_error_code)
                      userInfo:nil];
  return base::ios::ErrorWithAppendedUnderlyingError(error, net_error);
}

}  // namespace web
