iOS web browser with a focus on security and privacy
at master 402 lines 18 kB view raw
1/* 2 * Endless 3 * Copyright (c) 2015-2017 joshua stein <jcs@jcs.org> 4 * 5 * See LICENSE file for redistribution terms. 6 */ 7 8#import "AppDelegate.h" 9#import "HostSettings.h" 10#import "HostSettingsController.h" 11 12#import "XLForm.h" 13 14@interface HostSettingsXLFormViewController : XLFormViewController 15@property (copy, nonatomic) void (^disappearCallback)(HostSettingsXLFormViewController *); 16@end 17 18@implementation HostSettingsXLFormViewController 19 20- (void)viewWillDisappear:(BOOL)animated 21{ 22 if (self.disappearCallback) 23 self.disappearCallback(self); 24 25 [super viewWillDisappear:animated]; 26} 27@end 28 29 30@implementation HostSettingsController { 31 AppDelegate *appDelegate; 32 NSMutableArray *_sortedHosts; 33 NSString *firstMatch; 34} 35 36- (void)viewDidLoad 37{ 38 [super viewDidLoad]; 39 40 appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 41 42 self.title = NSLocalizedString(@"Host Settings", nil); 43 self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addHost:)]; 44 self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Done", nil) style:UIBarButtonItemStyleDone target:self.navigationController action:@selector(dismissModalViewControllerAnimated:)]; 45 46 /* most likely the user is wanting to define the site they are currently on, so feed that as a reasonable default the first time around */ 47 if ([[appDelegate webViewController] curWebViewTab] != nil) { 48 NSURL *t = [[[appDelegate webViewController] curWebViewTab] url]; 49 if (t != nil && [t host] != nil) { 50 NSRegularExpression *r = [NSRegularExpression regularExpressionWithPattern:@"^www\\." options:NSRegularExpressionCaseInsensitive error:nil]; 51 52 firstMatch = [r stringByReplacingMatchesInString:[t host] options:0 range:NSMakeRange(0, [[t host] length]) withTemplate:@""]; 53 } 54 } 55} 56 57- (void)didReceiveMemoryWarning 58{ 59 [super didReceiveMemoryWarning]; 60 // Dispose of any resources that can be recreated. 61} 62 63#pragma mark - Table view data source 64 65- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 66{ 67 return 1; 68} 69 70- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 71{ 72 return [[self sortedHosts] count]; 73} 74 75- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 76{ 77 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"host"]; 78 if (cell == nil) 79 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"host"]; 80 81 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 82 83 HostSettings *hs = [HostSettings forHost:[[self sortedHosts] objectAtIndex:indexPath.row]]; 84 cell.textLabel.text = [hs hostname]; 85 if ([hs isDefault]) 86 cell.textLabel.font = [UIFont boldSystemFontOfSize:cell.textLabel.font.pointSize]; 87 else 88 cell.detailTextLabel.text = nil; 89 90 return cell; 91} 92 93- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 94{ 95 return (indexPath.row != 0); 96} 97 98- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 99{ 100 [tableView deselectRowAtIndexPath:indexPath animated:YES]; 101 [self showDetailsForHost:[[self sortedHosts] objectAtIndex:indexPath.row]]; 102} 103 104- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 105{ 106 if (editingStyle == UITableViewCellEditingStyleDelete) { 107 if ([HostSettings removeSettingsForHost:[[self sortedHosts] objectAtIndex:indexPath.row]]) { 108 [HostSettings persist]; 109 _sortedHosts = nil; 110 [[self tableView] reloadData]; 111 } 112 } 113} 114 115- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath 116{ 117 return NO; 118} 119 120- (void)addHost:sender 121{ 122 UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Host settings", nil) message:NSLocalizedString(@"Enter the host/domain to define settings for", nil) preferredStyle:UIAlertControllerStyleAlert]; 123 [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 124 textField.placeholder = @"example.com"; 125 126 if (self->firstMatch != nil) 127 textField.text = self->firstMatch; 128 }]; 129 130 UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK action") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 131 UITextField *host = alertController.textFields.firstObject; 132 if (host && ![[host text] isEqualToString:@""]) { 133 HostSettings *hs = [[HostSettings alloc] initForHost:[host text] withDict:nil]; 134 [hs save]; 135 [HostSettings persist]; 136 self->_sortedHosts = nil; 137 138 [self.tableView reloadData]; 139 [self showDetailsForHost:[host text]]; 140 } 141 }]; 142 143 UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel action") style:UIAlertActionStyleCancel handler:nil]; 144 [alertController addAction:cancelAction]; 145 [alertController addAction:okAction]; 146 147 [self presentViewController:alertController animated:YES completion:nil]; 148 149 firstMatch = nil; 150} 151 152- (NSMutableArray *)sortedHosts 153{ 154 if (_sortedHosts == nil) 155 _sortedHosts = [[NSMutableArray alloc] initWithArray:[HostSettings sortedHosts]]; 156 157 return _sortedHosts; 158} 159 160- (void)showDetailsForHost:(NSString *)thost 161{ 162 HostSettings *host = [HostSettings forHost:thost]; 163 164 XLFormDescriptor *form = [XLFormDescriptor formDescriptorWithTitle:[host hostname]]; 165 166 /* hostname */ 167 { 168 XLFormSectionDescriptor *section = [XLFormSectionDescriptor formSection]; 169 [form addFormSection:section]; 170 171 if ([host isDefault]) { 172 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_HOST rowType:XLFormRowDescriptorTypeInfo title:NSLocalizedString(@"Host/domain", nil)]; 173 [row setValue:HOST_SETTINGS_HOST_DEFAULT_LABEL]; 174 [section setFooterTitle:NSLocalizedString(@"These settings will be used as defaults for all hosts unless overridden", nil)]; 175 [section addFormRow:row]; 176 } 177 else { 178 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_HOST rowType:XLFormRowDescriptorTypeText title:NSLocalizedString(@"Host/domain", nil)]; 179 [row setValue:[host hostname]]; 180 [row.cellConfigAtConfigure setObject:@"example.com" forKey:@"textField.placeholder"]; 181 [row.cellConfigAtConfigure setObject:@(NSTextAlignmentRight) forKey:@"textField.textAlignment"]; 182 [section setFooterTitle:NSLocalizedString(@"These settings will apply to this host and all hosts under it (e.g., \"example.com\" will apply to example.com and www.example.com)", nil)]; 183 [section addFormRow:row]; 184 } 185 } 186 187 /* privacy section */ 188 { 189 XLFormSectionDescriptor *section = [XLFormSectionDescriptor formSection]; 190 [section setTitle:NSLocalizedString(@"Privacy", nil)]; 191 [form addFormSection:section]; 192 193 /* whitelist cookies */ 194 { 195 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_WHITELIST_COOKIES rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"Allow persistent cookies", nil)]; 196 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_WHITELIST_COOKIES host:host row:row withDefault:(![host isDefault])]; 197 198 [section setFooterTitle:([host isDefault] 199 ? NSLocalizedString(@"Allow hosts to permanently store cookies and local storage databases", nil) 200 : NSLocalizedString(@"Allow this host to permanently store cookies and local storage databases", nil)) 201 ]; 202 [section addFormRow:row]; 203 [form addFormSection:section]; 204 } 205 206 /* allow webRTC */ 207 { 208 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_ALLOW_WEBRTC rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"Allow WebRTC", nil)]; 209 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_ALLOW_WEBRTC host:host row:row withDefault:(![host isDefault])]; 210 211 section = [XLFormSectionDescriptor formSection]; 212 [section setTitle:@""]; 213 [section setFooterTitle:([host isDefault] 214 ? NSLocalizedString(@"Allow hosts to access WebRTC functions", nil) 215 : NSLocalizedString(@"Allow this host to access WebRTC functions", nil)) 216 ]; 217 [section addFormRow:row]; 218 [form addFormSection:section]; 219 } 220 221 /* universal link protection */ 222 { 223 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_UNIVERSAL_LINK_PROTECTION rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"Universal Link Protection", nil)]; 224 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_UNIVERSAL_LINK_PROTECTION host:host row:row withDefault:(![host isDefault])]; 225 226 section = [XLFormSectionDescriptor formSection]; 227 [section setTitle:@""]; 228 [section setFooterTitle:([host isDefault] 229 ? NSLocalizedString(@"Handle tapping on links in a non-standard way to avoid possibly opening external applications", nil) 230 : NSLocalizedString(@"Handle tapping on links on pages from this host in a non-standard way to avoid possibly opening external applications", nil)) 231 ]; 232 [section addFormRow:row]; 233 [form addFormSection:section]; 234 } 235 236 } 237 238 /* security section */ 239 { 240 XLFormSectionDescriptor *section = [XLFormSectionDescriptor formSection]; 241 [section setTitle:NSLocalizedString(@"Security", nil)]; 242 [form addFormSection:section]; 243 244 /* ignore TLS errors */ 245 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"allow_tls_error_ignore"]) 246 { 247 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_IGNORE_TLS_ERRORS rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"Ignore TLS errors", nil)]; 248 249 XLFormOptionsObject *yes = [XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_VALUE_YES displayText:NSLocalizedString(@"Yes", nil)]; 250 XLFormOptionsObject *no = [XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_VALUE_NO displayText:NSLocalizedString(@"No", nil)]; 251 252 // This value is always "NO", except, when the user set the global setting 253 // "allow_tls_error_ignore" to YES *and* they surfed to a site with an error 254 // *and* the selected "ignore" on the following error alert. 255 [row setSelectorOptions:@[no]]; 256 257 NSString *val = [host setting:HOST_SETTINGS_KEY_IGNORE_TLS_ERRORS]; 258 [row setValue:[val isEqualToString:HOST_SETTINGS_VALUE_YES] ? yes : no]; 259 260 [section addFormRow:row]; 261 } 262 263 /* tls version */ 264 { 265 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_TLS rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"TLS version", nil)]; 266 267 NSMutableArray *opts = [[NSMutableArray alloc] init]; 268 if (![host isDefault]) 269 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_DEFAULT displayText:NSLocalizedString(@"(Use Default)", nil)]]; 270 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_TLS_12 displayText:NSLocalizedString(@"TLS 1.2 Only", nil)]]; 271 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_TLS_AUTO displayText:NSLocalizedString(@"TLS 1.2, 1.1, or 1.0", nil)]]; 272 [row setSelectorOptions:opts]; 273 274 NSString *val = [host setting:HOST_SETTINGS_KEY_TLS]; 275 if (val == nil) 276 val = HOST_SETTINGS_DEFAULT; 277 278 for (XLFormOptionsObject *opt in opts) 279 if ([[opt valueData] isEqualToString:val]) 280 [row setValue:opt]; 281 282 [section setFooterTitle:([host isDefault] 283 ? NSLocalizedString(@"Minimum version of TLS required by hosts to negotiate HTTPS connections", nil) 284 : NSLocalizedString(@"Minimum version of TLS required by this host to negotiate HTTPS connections", nil))]; 285 [section addFormRow:row]; 286 } 287 288 /* content policy */ 289 { 290 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_CSP rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"Content policy", nil)]; 291 292 NSMutableArray *opts = [[NSMutableArray alloc] init]; 293 if (![host isDefault]) 294 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_DEFAULT displayText:NSLocalizedString(@"(Use Default)", nil)]]; 295 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_CSP_OPEN displayText:NSLocalizedString(@"Open (normal browsing mode)", nil)]]; 296 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_CSP_BLOCK_CONNECT displayText:NSLocalizedString(@"No XHR/WebSocket/Video connections", nil)]]; 297 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_CSP_STRICT displayText:NSLocalizedString(@"Strict (no JavaScript, video, etc.)", nil)]]; 298 [row setSelectorOptions:opts]; 299 300 NSString *val = [host setting:HOST_SETTINGS_KEY_CSP]; 301 if (val == nil) 302 val = HOST_SETTINGS_DEFAULT; 303 304 for (XLFormOptionsObject *opt in opts) 305 if ([[opt valueData] isEqualToString:val]) 306 [row setValue:opt]; 307 308 section = [XLFormSectionDescriptor formSection]; 309 [section setTitle:@""]; 310 [section setFooterTitle:([host isDefault] 311 ? NSLocalizedString(@"Restrictions on resources loaded from web pages", nil) 312 : NSLocalizedString(@"Restrictions on resources loaded from web pages at this host", nil))]; 313 [form addFormSection:section]; 314 [section addFormRow:row]; 315 } 316 317 /* whitelist cookies */ 318 { 319 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_ALLOW_MIXED_MODE rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"Allow mixed-mode resources", nil)]; 320 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_ALLOW_MIXED_MODE host:host row:row withDefault:(![host isDefault])]; 321 322 section = [XLFormSectionDescriptor formSection]; 323 [section setTitle:@""]; 324 [section setFooterTitle:([host isDefault] 325 ? NSLocalizedString(@"Allow HTTPS hosts to load page resources from non-HTTPS hosts (useful for RSS readers and other aggregators)", nil) 326 : NSLocalizedString(@"Allow this HTTPS host to load page resources from non-HTTPS hosts (useful for RSS readers and other aggregators)", nil))]; 327 [form addFormSection:section]; 328 [section addFormRow:row]; 329 } 330 331 /* block external lan requests */ 332 { 333 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_BLOCK_LOCAL_NETS rowType:XLFormRowDescriptorTypeSelectorActionSheet title:NSLocalizedString(@"Block external LAN requests", nil)]; 334 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_BLOCK_LOCAL_NETS host:host row:row withDefault:(![host isDefault])]; 335 336 section = [XLFormSectionDescriptor formSection]; 337 [section setTitle:@""]; 338 [section setFooterTitle:([host isDefault] 339 ? NSLocalizedString(@"Resources loaded from external hosts will be blocked from loading page elements or making requests to LAN hosts (192.168.0.0/16, 172.16.0.0/12, etc.)", nil) 340 : NSLocalizedString(@"Resources loaded from this host will be blocked from loading page elements or making requests to LAN hosts (192.168.0.0/16, 172.16.0.0/12, etc.)", nil))]; 341 [form addFormSection:section]; 342 [section addFormRow:row]; 343 } 344 } 345 346 /* misc section */ 347 { 348 XLFormSectionDescriptor *section = [XLFormSectionDescriptor formSection]; 349 [section setTitle:NSLocalizedString(@"Other", nil)]; 350 [form addFormSection:section]; 351 352 /* user agent */ 353 { 354 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_USER_AGENT rowType:XLFormRowDescriptorTypeText title:NSLocalizedString(@"User Agent", nil)]; 355 [row setValue:[host setting:HOST_SETTINGS_KEY_USER_AGENT]]; 356 [section setFooterTitle:[NSString stringWithFormat:NSLocalizedString(@"Custom user-agent string, or blank to use the default", nil)]]; 357 [section addFormRow:row]; 358 } 359 } 360 361 HostSettingsXLFormViewController *formController = [[HostSettingsXLFormViewController alloc] initWithForm:form]; 362 [formController setDisappearCallback:^(HostSettingsXLFormViewController *form) { 363 if (![host isDefault]) 364 [host setHostname:[[form formValues] objectForKey:HOST_SETTINGS_KEY_HOST]]; 365 366 for (NSString *key in [[HostSettings defaults] allKeys]) { 367 XLFormOptionsObject *opt = [[form formValues] objectForKey:key]; 368 if (opt) 369 [host setSetting:key toValue:(NSString *)[opt valueData]]; 370 } 371 372 [host save]; 373 [HostSettings persist]; 374 375 dispatch_async(dispatch_get_main_queue(), ^{ 376 [[NSNotificationCenter defaultCenter] postNotificationName:HOST_SETTINGS_CHANGED object:nil]; 377 }); 378 }]; 379 380 [[self navigationController] pushViewController:formController animated:YES]; 381 self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Hosts", nil) style:UIBarButtonItemStylePlain target:nil action:nil]; 382} 383 384- (void)setYesNoSelectorOptionsForSetting:(NSString *)key host:(HostSettings *)host row:(XLFormRowDescriptor *)row withDefault:(BOOL)withDefault 385{ 386 NSMutableArray *opts = [[NSMutableArray alloc] init]; 387 if (withDefault) 388 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_DEFAULT displayText:NSLocalizedString(@"(Use Default)", nil)]]; 389 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_VALUE_YES displayText:NSLocalizedString(@"Yes", no)]]; 390 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_VALUE_NO displayText:NSLocalizedString(@"No", no)]]; 391 [row setSelectorOptions:opts]; 392 393 NSString *val = [host setting:key]; 394 if (val == nil) 395 val = HOST_SETTINGS_DEFAULT; 396 397 for (XLFormOptionsObject *opt in opts) 398 if ([[opt valueData] isEqualToString:val]) 399 [row setValue:opt]; 400} 401 402@end