| // 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 |