iOS web browser with a focus on security and privacy
1/*
2 * Endless
3 * Copyright (c) 2014-2017 joshua stein <jcs@jcs.org>
4 *
5 * See LICENSE file for redistribution terms.
6 */
7
8#import "AppDelegate.h"
9#import "Bookmark.h"
10#import "HTTPSEverywhere.h"
11#import "URLInterceptor.h"
12
13#import "UIResponder+FirstResponder.h"
14
15@implementation AppDelegate
16{
17 NSMutableArray *_keyCommands;
18 NSMutableArray *_allKeyBindings;
19 NSArray *_allCommandsAndKeyBindings;
20}
21
22- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
23{
24 @try {
25 NSURL *resourceURL = [[NSBundle mainBundle] URLForResource:@"fabric.apikey" withExtension:nil];
26 if (resourceURL) {
27 NSString *fabricAPIKey = [[NSString stringWithContentsOfURL:resourceURL usedEncoding:nil error:nil] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
28 CrashlyticsKit.delegate = self;
29 [Crashlytics startWithAPIKey:fabricAPIKey];
30 } else {
31 NSLog(@"[AppDelegate] no fabric.apikey found, not enabling fabric");
32 }
33 }
34 @catch (NSException *e) {
35 NSLog(@"[AppDelegate] failed setting up fabric: %@", e);
36 }
37
38#ifdef USE_DUMMY_URLINTERCEPTOR
39 [NSURLProtocol registerClass:[DummyURLInterceptor class]];
40#else
41 [NSURLProtocol registerClass:[URLInterceptor class]];
42#endif
43
44 self.hstsCache = [HSTSCache retrieve];
45 self.cookieJar = [[CookieJar alloc] init];
46 [Bookmark retrieveList];
47
48 [self initializeDefaults];
49
50 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
51 self.window.backgroundColor = [UIColor groupTableViewBackgroundColor];
52 self.window.rootViewController = [[WebViewController alloc] init];
53 self.window.rootViewController.restorationIdentifier = @"WebViewController";
54
55 return YES;
56}
57
58- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
59{
60 [self.window makeKeyAndVisible];
61
62 return YES;
63}
64
65- (void)applicationWillResignActive:(UIApplication *)application
66{
67 [application ignoreSnapshotOnNextApplicationLaunch];
68 [[self webViewController] viewIsNoLongerVisible];
69}
70
71- (void)applicationDidEnterBackground:(UIApplication *)application
72{
73 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
74
75 if (![self areTesting]) {
76 [HostSettings persist];
77 [[self hstsCache] persist];
78 }
79
80 if ([userDefaults boolForKey:@"clear_on_background"]) {
81 [[self webViewController] removeAllTabs];
82 [[self cookieJar] clearAllNonWhitelistedData];
83 }
84 else
85 [[self cookieJar] clearAllOldNonWhitelistedData];
86
87 [application ignoreSnapshotOnNextApplicationLaunch];
88}
89
90- (void)applicationDidBecomeActive:(UIApplication *)application
91{
92 [[self webViewController] viewIsVisible];
93}
94
95- (void)applicationWillTerminate:(UIApplication *)application
96{
97 /* this definitely ends our sessions */
98 [[self cookieJar] clearAllNonWhitelistedData];
99
100 [application ignoreSnapshotOnNextApplicationLaunch];
101}
102
103- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
104{
105#ifdef TRACE
106 NSLog(@"[AppDelegate] request to open url \"%@\"", url);
107#endif
108 if ([[[url scheme] lowercaseString] isEqualToString:@"endlesshttp"])
109 url = [NSURL URLWithString:[[url absoluteString] stringByReplacingCharactersInRange:NSMakeRange(0, [@"endlesshttp" length]) withString:@"http"]];
110 else if ([[[url scheme] lowercaseString] isEqualToString:@"endlesshttps"])
111 url = [NSURL URLWithString:[[url absoluteString] stringByReplacingCharactersInRange:NSMakeRange(0, [@"endlesshttps" length]) withString:@"https"]];
112
113 [[self webViewController] dismissViewControllerAnimated:YES completion:nil];
114 [[self webViewController] addNewTabForURL:url];
115
116 return YES;
117}
118
119- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
120{
121 if ([self areTesting])
122 return NO;
123
124 /* if we tried last time and failed, the state might be corrupt */
125 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
126 if ([userDefaults objectForKey:STATE_RESTORE_TRY_KEY] != nil) {
127 NSLog(@"[AppDelegate] previous startup failed, not restoring application state");
128 [userDefaults removeObjectForKey:STATE_RESTORE_TRY_KEY];
129 return NO;
130 }
131 else
132 [userDefaults setBool:YES forKey:STATE_RESTORE_TRY_KEY];
133
134 [userDefaults synchronize];
135
136 return YES;
137}
138
139- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
140{
141 if ([self areTesting])
142 return NO;
143
144 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
145 if ([userDefaults boolForKey:@"clear_on_background"])
146 return NO;
147
148 return YES;
149}
150
151- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL))completionHandler
152{
153 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
154
155#ifdef TRACE
156 NSLog(@"crashlytics report found, %@sending to crashlytics: %@", ([userDefaults boolForKey:@"crash_reporting"] ? @"" : @"NOT "), report);
157#endif
158
159 completionHandler([userDefaults boolForKey:@"crash_reporting"]);
160}
161
162- (NSArray<UIKeyCommand *> *)keyCommands
163{
164 if (!_keyCommands) {
165 _keyCommands = [[NSMutableArray alloc] init];
166
167 [_keyCommands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:UIKeyModifierCommand action:@selector(handleKeyboardShortcut:) discoverabilityTitle:@"Go Back"]];
168 [_keyCommands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:UIKeyModifierCommand action:@selector(handleKeyboardShortcut:) discoverabilityTitle:@"Go Forward"]];
169
170 [_keyCommands addObject:[UIKeyCommand keyCommandWithInput:@"b" modifierFlags:UIKeyModifierCommand action:@selector(handleKeyboardShortcut:) discoverabilityTitle:@"Show Bookmarks"]];
171
172 [_keyCommands addObject:[UIKeyCommand keyCommandWithInput:@"l" modifierFlags:UIKeyModifierCommand action:@selector(handleKeyboardShortcut:) discoverabilityTitle:@"Focus URL Field"]];
173
174 [_keyCommands addObject:[UIKeyCommand keyCommandWithInput:@"t" modifierFlags:UIKeyModifierCommand action:@selector(handleKeyboardShortcut:) discoverabilityTitle:@"Create New Tab"]];
175 [_keyCommands addObject:[UIKeyCommand keyCommandWithInput:@"w" modifierFlags:UIKeyModifierCommand action:@selector(handleKeyboardShortcut:) discoverabilityTitle:@"Close Tab"]];
176
177 for (int i = 1; i <= 10; i++)
178 [_keyCommands addObject:[UIKeyCommand keyCommandWithInput:[NSString stringWithFormat:@"%d", (i == 10 ? 0 : i)] modifierFlags:UIKeyModifierCommand action:@selector(handleKeyboardShortcut:) discoverabilityTitle:[NSString stringWithFormat:@"Switch to Tab %d", i]]];
179 }
180
181 if (!_allKeyBindings) {
182 _allKeyBindings = [[NSMutableArray alloc] init];
183 const long modPermutations[] = {
184 UIKeyModifierAlphaShift,
185 UIKeyModifierShift,
186 UIKeyModifierControl,
187 UIKeyModifierAlternate,
188 UIKeyModifierCommand,
189 UIKeyModifierCommand | UIKeyModifierAlternate,
190 UIKeyModifierCommand | UIKeyModifierControl,
191 UIKeyModifierControl | UIKeyModifierAlternate,
192 UIKeyModifierControl | UIKeyModifierCommand,
193 UIKeyModifierControl | UIKeyModifierAlternate | UIKeyModifierCommand,
194 kNilOptions,
195 };
196
197 NSString *chars = @"`1234567890-=\b\tqwertyuiop[]\\asdfghjkl;'\rzxcvbnm,./ ";
198 for (int j = 0; j < sizeof(modPermutations); j++) {
199 for (int i = 0; i < [chars length]; i++) {
200 NSString *c = [chars substringWithRange:NSMakeRange(i, 1)];
201
202 [_allKeyBindings addObject:[UIKeyCommand keyCommandWithInput:c modifierFlags:modPermutations[j] action:@selector(handleKeyboardShortcut:)]];
203 }
204
205 [_allKeyBindings addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:modPermutations[j] action:@selector(handleKeyboardShortcut:)]];
206 [_allKeyBindings addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:modPermutations[j] action:@selector(handleKeyboardShortcut:)]];
207 [_allKeyBindings addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:modPermutations[j] action:@selector(handleKeyboardShortcut:)]];
208 [_allKeyBindings addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:modPermutations[j] action:@selector(handleKeyboardShortcut:)]];
209 [_allKeyBindings addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:modPermutations[j] action:@selector(handleKeyboardShortcut:)]];
210 }
211
212 _allCommandsAndKeyBindings = [_keyCommands arrayByAddingObjectsFromArray:_allKeyBindings];
213 }
214
215 /* if settings are up or something else, ignore shortcuts */
216 if (![[self topViewController] isKindOfClass:[WebViewController class]])
217 return nil;
218
219 id cur = [UIResponder currentFirstResponder];
220 if (cur == nil || [NSStringFromClass([cur class]) isEqualToString:@"UIWebView"])
221 return _allCommandsAndKeyBindings;
222 else {
223#ifdef TRACE_KEYBOARD_INPUT
224 NSLog(@"[AppDelegate] current first responder is a %@, only passing shortcuts", NSStringFromClass([cur class]));
225#endif
226 return _keyCommands;
227 }
228}
229
230- (void)handleKeyboardShortcut:(UIKeyCommand *)keyCommand
231{
232 if ([keyCommand modifierFlags] == UIKeyModifierCommand) {
233 if ([[keyCommand input] isEqualToString:@"b"]) {
234 [[self webViewController] showBookmarksForEditing:NO];
235 return;
236 }
237
238 if ([[keyCommand input] isEqualToString:@"l"]) {
239 [[self webViewController] focusUrlField];
240 return;
241 }
242
243 if ([[keyCommand input] isEqualToString:@"t"]) {
244 [[self webViewController] addNewTabForURL:nil forRestoration:NO withCompletionBlock:^(BOOL finished) {
245 [[self webViewController] focusUrlField];
246 }];
247 return;
248 }
249
250 if ([[keyCommand input] isEqualToString:@"w"]) {
251 [[self webViewController] removeTab:[[[self webViewController] curWebViewTab] tabIndex]];
252 return;
253 }
254
255 if ([[keyCommand input] isEqualToString:UIKeyInputLeftArrow]) {
256 [[[self webViewController] curWebViewTab] goBack];
257 return;
258 }
259
260 if ([[keyCommand input] isEqualToString:UIKeyInputRightArrow]) {
261 [[[self webViewController] curWebViewTab] goForward];
262 return;
263 }
264
265 for (int i = 0; i <= 9; i++) {
266 if ([[keyCommand input] isEqualToString:[NSString stringWithFormat:@"%d", i]]) {
267 [[self webViewController] switchToTab:[NSNumber numberWithInt:(i == 0 ? 9 : i - 1)]];
268 return;
269 }
270 }
271 }
272
273 if ([self webViewController] && [[self webViewController] curWebViewTab])
274 [[[self webViewController] curWebViewTab] handleKeyCommand:keyCommand];
275}
276
277- (UIViewController *)topViewController
278{
279 return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
280}
281
282- (UIViewController *)topViewController:(UIViewController *)rootViewController
283{
284 if (rootViewController.presentedViewController == nil)
285 return rootViewController;
286
287 if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]]) {
288 UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
289 UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
290 return [self topViewController:lastViewController];
291 }
292
293 UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
294 return [self topViewController:presentedViewController];
295}
296
297- (void)initializeDefaults
298{
299 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
300
301 NSString *plistPath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"InAppSettings.bundle"] stringByAppendingPathComponent:@"Root.inApp.plist"];
302 NSDictionary *settingsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath];
303
304 for (NSDictionary *pref in [settingsDictionary objectForKey:@"PreferenceSpecifiers"]) {
305 NSString *key = [pref objectForKey:@"Key"];
306 if (key == nil)
307 continue;
308
309 if ([userDefaults objectForKey:key] == NULL) {
310 NSObject *val = [pref objectForKey:@"DefaultValue"];
311 if (val == nil)
312 continue;
313
314 [userDefaults setObject:val forKey:key];
315#ifdef TRACE
316 NSLog(@"[AppDelegate] initialized default preference for %@ to %@", key, val);
317#endif
318 }
319 }
320
321 if (![userDefaults synchronize]) {
322 NSLog(@"[AppDelegate] failed saving preferences");
323 abort();
324 }
325
326 _searchEngines = [NSMutableDictionary dictionaryWithContentsOfFile:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"SearchEngines.plist"]];
327}
328
329- (BOOL)areTesting
330{
331 if (NSClassFromString(@"XCTestProbe") != nil) {
332 NSLog(@"we are testing");
333 return YES;
334 }
335 else {
336 NSDictionary *environment = [[NSProcessInfo processInfo] environment];
337 if (environment[@"ARE_UI_TESTING"]) {
338 NSLog(@"we are UI testing");
339 return YES;
340 }
341 }
342
343 return NO;
344}
345
346@end