blob: a08ac4087510998ccd8bf090a6f008f3b042d581 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/navigation/crw_error_page_helper.h"
#include "base/check.h"
#include "base/strings/sys_string_conversions.h"
#include "net/base/escape.h"
#include "net/base/url_util.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const char kOriginalUrlKey[] = "url";
// Returns the bundle from which the html files should be loaded.
NSBundle* BundleForHTMLFiles() {
return [NSBundle bundleForClass:CRWErrorPageHelper.class];
}
// Escapes HTML characters in |text|.
NSString* EscapeHTMLCharacters(NSString* text) {
return base::SysUTF8ToNSString(
net::EscapeForHTML(base::SysNSStringToUTF8(text)));
}
// Resturns the path for the error page to be loaded.
NSString* LoadedErrorPageFilePath() {
NSString* path = [BundleForHTMLFiles() pathForResource:@"error_page_loaded"
ofType:@"html"];
DCHECK(path) << "Loaded error page should exist";
return path;
}
// Returns the path for the error page to be injected.
NSString* InjectedErrorPageFilePath() {
NSString* path = [BundleForHTMLFiles() pathForResource:@"error_page_injected"
ofType:@"html"];
DCHECK(path) << "Injected error page should exist";
return path;
}
} // namespace
@interface CRWErrorPageHelper ()
@property(nonatomic, strong) NSError* error;
// The error page HTML to be injected into existing page.
@property(nonatomic, strong) NSString* automaticReloadJavaScript;
@property(nonatomic, strong, readonly) NSString* failedNavigationURLString;
@end
@implementation CRWErrorPageHelper
@synthesize failedNavigationURL = _failedNavigationURL;
@synthesize errorPageFileURL = _errorPageFileURL;
- (instancetype)initWithError:(NSError*)error {
if (self = [super init]) {
_error = [error copy];
}
return self;
}
#pragma mark - Properties
- (NSURL*)failedNavigationURL {
if (!_failedNavigationURL) {
_failedNavigationURL = [NSURL URLWithString:self.failedNavigationURLString];
}
return _failedNavigationURL;
}
- (NSString*)failedNavigationURLString {
return self.error.userInfo[NSURLErrorFailingURLStringErrorKey];
}
- (NSURL*)errorPageFileURL {
if (!_errorPageFileURL) {
NSURLQueryItem* itemURL = [NSURLQueryItem
queryItemWithName:base::SysUTF8ToNSString(kOriginalUrlKey)
value:EscapeHTMLCharacters(self.failedNavigationURLString)];
NSURLQueryItem* itemDontLoad = [NSURLQueryItem queryItemWithName:@"dontLoad"
value:@"true"];
NSURLComponents* URL = [[NSURLComponents alloc] initWithString:@"file:///"];
URL.path = LoadedErrorPageFilePath();
URL.queryItems = @[ itemURL, itemDontLoad ];
DCHECK(URL.URL) << "file URL should be valid";
_errorPageFileURL = URL.URL;
}
return _errorPageFileURL;
}
- (NSString*)automaticReloadJavaScript {
if (!_automaticReloadJavaScript) {
NSString* path = InjectedErrorPageFilePath();
NSString* HTMLTemplate =
[NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:nil];
NSString* failedNavigationURLString =
EscapeHTMLCharacters(self.failedNavigationURLString);
_automaticReloadJavaScript =
[NSString stringWithFormat:HTMLTemplate, failedNavigationURLString];
}
return _automaticReloadJavaScript;
}
#pragma mark - Public
+ (GURL)failedNavigationURLFromErrorPageFileURL:(const GURL&)URL {
if (!URL.is_valid())
return GURL();
if (URL.SchemeIsFile() &&
URL.path() == base::SysNSStringToUTF8(LoadedErrorPageFilePath())) {
std::string value;
if (net::GetValueForKeyInQuery(URL, kOriginalUrlKey, &value)) {
return GURL(value);
}
}
return GURL();
}
+ (BOOL)isErrorPageFileURL:(const GURL&)URL {
return [self failedNavigationURLFromErrorPageFileURL:URL].is_valid();
}
- (NSString*)scriptForInjectingHTML:(NSString*)HTML
addAutomaticReload:(BOOL)addAutomaticReload {
NSString* HTMLToInject = HTML;
if (addAutomaticReload) {
HTMLToInject =
[HTMLToInject stringByAppendingString:self.automaticReloadJavaScript];
}
// Serialize as JSON to be able to inject HTML characters.
NSString* JSON = [[NSString alloc]
initWithData:[NSJSONSerialization dataWithJSONObject:@[ HTMLToInject ]
options:0
error:nil]
encoding:NSUTF8StringEncoding];
NSString* escapedHTML =
[JSON substringWithRange:NSMakeRange(1, JSON.length - 2)];
return
[NSString stringWithFormat:
@"document.open(); document.write(%@); document.close();",
escapedHTML];
}
- (BOOL)isErrorPageFileURLForFailedNavigationURL:(NSURL*)URL {
// Check that |URL| is a file URL of error page.
if (!URL.fileURL || ![URL.path isEqualToString:self.errorPageFileURL.path]) {
return NO;
}
// Check that |URL| has the same failed URL as |self|.
NSURLComponents* URLComponents = [NSURLComponents componentsWithURL:URL
resolvingAgainstBaseURL:NO];
NSURL* failedNavigationURL = nil;
for (NSURLQueryItem* item in URLComponents.queryItems) {
if ([item.name isEqualToString:base::SysUTF8ToNSString(kOriginalUrlKey)]) {
failedNavigationURL = [NSURL URLWithString:item.value];
break;
}
}
return [failedNavigationURL isEqual:self.failedNavigationURL];
}
@end