// Copyright 2020 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/chrome/browser/web/model/invalid_url_tab_helper.h"

#import "base/strings/sys_string_conversions.h"
#import "ios/net/protocol_handler_util.h"
#import "net/base/apple/url_conversions.h"
#import "net/base/data_url.h"
#import "net/base/net_errors.h"
#import "net/http/http_response_headers.h"
#import "ui/base/page_transition_types.h"
#import "url/url_constants.h"

namespace {

// Returns true if URL request is valid and navigation should be allowed.
bool IsUrlRequestValid(NSURLRequest* request) {
  if (request.URL.absoluteString.length > url::kMaxURLChars) {
    return false;
  }

  if ([request.URL.scheme isEqualToString:@"data"]) {
    std::string mime_type;
    std::string charset;
    std::string data;
    scoped_refptr<net::HttpResponseHeaders> headers;
    if (net::DataURL::BuildResponse(net::GURLWithNSURL(request.URL),
                                    base::SysNSStringToUTF8(request.HTTPMethod),
                                    &mime_type, &charset, &data,
                                    &headers) == net::ERR_INVALID_URL) {
      return false;
    }
  }

  return true;
}

}  // namespace

InvalidUrlTabHelper::InvalidUrlTabHelper(web::WebState* web_state)
    : web::WebStatePolicyDecider(web_state) {}
InvalidUrlTabHelper::~InvalidUrlTabHelper() = default;

void InvalidUrlTabHelper::ShouldAllowRequest(
    NSURLRequest* request,
    web::WebStatePolicyDecider::RequestInfo request_info,
    web::WebStatePolicyDecider::PolicyDecisionCallback callback) {
  if (IsUrlRequestValid(request)) {
    return std::move(callback).Run(PolicyDecision::Allow());
  }

  // URL is invalid. Show error for certain browser-initiated navigations (f.e.
  // the user typed the URL, tapped the bookmark or onmibox suggestion) and
  // silently cancel the navigation for other navigations (f.e. the user clicked
  // the link or page made a client side redirect).

  using ui::PageTransitionCoreTypeIs;
  ui::PageTransition transition = request_info.transition_type;
  if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
      PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED) ||
      PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_AUTO_BOOKMARK)) {
    return std::move(callback).Run(PolicyDecision::CancelAndDisplayError(
        [NSError errorWithDomain:net::kNSErrorDomain
                            code:net::ERR_INVALID_URL
                        userInfo:nil]));
  }
  std::move(callback).Run(PolicyDecision::Cancel());
}
