iOS web browser with a focus on security and privacy
at remove_ckhttpconnection 328 lines 13 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 = @"Host Settings"; 43 self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addHost:)]; 44 self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Done" 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:@"Host settings" message:@"Enter the host/domain to define settings for" preferredStyle:UIAlertControllerStyleAlert]; 123 [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 124 textField.placeholder = @"example.com"; 125 126 if (firstMatch != nil) 127 textField.text = 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 _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:@"Host/domain"]; 173 [row setValue:HOST_SETTINGS_HOST_DEFAULT_LABEL]; 174 [section setFooterTitle:@"These settings will be used as defaults for all hosts unless overridden"]; 175 [section addFormRow:row]; 176 } 177 else { 178 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_HOST rowType:XLFormRowDescriptorTypeText title:@"Host/domain"]; 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:@"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)"]; 183 [section addFormRow:row]; 184 } 185 } 186 187 /* privacy section */ 188 { 189 XLFormSectionDescriptor *section = [XLFormSectionDescriptor formSection]; 190 [section setTitle:@"Privacy"]; 191 [form addFormSection:section]; 192 193 /* whitelist cookies */ 194 { 195 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_WHITELIST_COOKIES rowType:XLFormRowDescriptorTypeSelectorActionSheet title:@"Allow persistent cookies"]; 196 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_WHITELIST_COOKIES host:host row:row withDefault:(![host isDefault])]; 197 198 [section setFooterTitle:[NSString stringWithFormat:@"Allow %@ to permanently store cookies and local storage databases", ([host isDefault] ? @"hosts" : @"this host")]]; 199 [section addFormRow:row]; 200 } 201 } 202 203 /* security section */ 204 { 205 XLFormSectionDescriptor *section = [XLFormSectionDescriptor formSection]; 206 [section setTitle:@"Security"]; 207 [form addFormSection:section]; 208 209 /* tls version */ 210 { 211 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_TLS rowType:XLFormRowDescriptorTypeSelectorActionSheet title:@"TLS version"]; 212 213 NSMutableArray *opts = [[NSMutableArray alloc] init]; 214 if (![host isDefault]) 215 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_DEFAULT displayText:@"(Use Default)"]]; 216 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_TLS_12 displayText:@"TLS 1.2 Only"]]; 217 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_TLS_AUTO displayText:@"TLS 1.2, 1.1, or 1.0"]]; 218 [row setSelectorOptions:opts]; 219 220 NSString *val = [host setting:HOST_SETTINGS_KEY_TLS]; 221 if (val == nil) 222 val = HOST_SETTINGS_DEFAULT; 223 224 for (XLFormOptionsObject *opt in opts) 225 if ([[opt valueData] isEqualToString:val]) 226 [row setValue:opt]; 227 228 [section setFooterTitle:[NSString stringWithFormat:@"Minimum version of TLS required by %@ to negotiate HTTPS connections", ([host isDefault] ? @"hosts" : @"this host")]]; 229 [section addFormRow:row]; 230 } 231 232 /* content policy */ 233 { 234 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_CSP rowType:XLFormRowDescriptorTypeSelectorActionSheet title:@"Content policy"]; 235 236 NSMutableArray *opts = [[NSMutableArray alloc] init]; 237 if (![host isDefault]) 238 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_DEFAULT displayText:@"(Use Default)"]]; 239 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_CSP_OPEN displayText:@"Open (normal browsing mode)"]]; 240 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_CSP_BLOCK_CONNECT displayText:@"No XHR/WebSockets/Video connections"]]; 241 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_CSP_STRICT displayText:@"Strict (no JavaScript, video, etc.)"]]; 242 [row setSelectorOptions:opts]; 243 244 NSString *val = [host setting:HOST_SETTINGS_KEY_CSP]; 245 if (val == nil) 246 val = HOST_SETTINGS_DEFAULT; 247 248 for (XLFormOptionsObject *opt in opts) 249 if ([[opt valueData] isEqualToString:val]) 250 [row setValue:opt]; 251 252 section = [XLFormSectionDescriptor formSection]; 253 [section setTitle:@""]; 254 [section setFooterTitle:[NSString stringWithFormat:@"Restrictions on resources loaded from web pages%@", ([host isDefault] ? @"" : @" at this host")]]; 255 [form addFormSection:section]; 256 [section addFormRow:row]; 257 } 258 259 /* whitelist cookies */ 260 { 261 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_ALLOW_MIXED_MODE rowType:XLFormRowDescriptorTypeSelectorActionSheet title:@"Allow mixed-mode resources"]; 262 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_ALLOW_MIXED_MODE host:host row:row withDefault:(![host isDefault])]; 263 264 section = [XLFormSectionDescriptor formSection]; 265 [section setTitle:@""]; 266 [section setFooterTitle:[NSString stringWithFormat:@"Allow %@ to load page resources from non-HTTPS hosts (useful for RSS readers and other aggregators)", ([host isDefault] ? @"HTTPS hosts" : @"this HTTPS host")]]; 267 [form addFormSection:section]; 268 [section addFormRow:row]; 269 } 270 271 /* block external lan requests */ 272 { 273 XLFormRowDescriptor *row = [XLFormRowDescriptor formRowDescriptorWithTag:HOST_SETTINGS_KEY_BLOCK_LOCAL_NETS rowType:XLFormRowDescriptorTypeSelectorActionSheet title:@"Block external LAN requests"]; 274 [self setYesNoSelectorOptionsForSetting:HOST_SETTINGS_KEY_BLOCK_LOCAL_NETS host:host row:row withDefault:(![host isDefault])]; 275 276 section = [XLFormSectionDescriptor formSection]; 277 [section setTitle:@""]; 278 [section setFooterTitle:[NSString stringWithFormat:@"Resources loaded from %@ will be blocked from loading page elements or making requests to LAN hosts (192.168.0.0/16, 172.16.0.0/12, etc.)", ([host isDefault] ? @"external hosts" : @"this host")]]; 279 [form addFormSection:section]; 280 [section addFormRow:row]; 281 } 282 } 283 284 HostSettingsXLFormViewController *formController = [[HostSettingsXLFormViewController alloc] initWithForm:form]; 285 [formController setDisappearCallback:^(HostSettingsXLFormViewController *form) { 286 if (![host isDefault]) 287 [host setHostname:[[form formValues] objectForKey:HOST_SETTINGS_KEY_HOST]]; 288 289 NSArray *keys = @[ 290 HOST_SETTINGS_KEY_TLS, 291 HOST_SETTINGS_KEY_ALLOW_MIXED_MODE, 292 HOST_SETTINGS_KEY_BLOCK_LOCAL_NETS, 293 HOST_SETTINGS_KEY_WHITELIST_COOKIES, 294 ]; 295 296 for (NSString *key in keys) { 297 XLFormOptionsObject *opt = [[form formValues] objectForKey:key]; 298 if (opt) 299 [host setSetting:key toValue:(NSString *)[opt valueData]]; 300 } 301 302 [host save]; 303 [HostSettings persist]; 304 }]; 305 306 [[self navigationController] pushViewController:formController animated:YES]; 307 self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Hosts" style:UIBarButtonItemStylePlain target:nil action:nil]; 308} 309 310- (void)setYesNoSelectorOptionsForSetting:(NSString *)key host:(HostSettings *)host row:(XLFormRowDescriptor *)row withDefault:(BOOL)withDefault 311{ 312 NSMutableArray *opts = [[NSMutableArray alloc] init]; 313 if (withDefault) 314 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_DEFAULT displayText:@"(Use Default)"]]; 315 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_VALUE_YES displayText:@"Yes"]]; 316 [opts addObject:[XLFormOptionsObject formOptionsObjectWithValue:HOST_SETTINGS_VALUE_NO displayText:@"No"]]; 317 [row setSelectorOptions:opts]; 318 319 NSString *val = [host setting:key]; 320 if (val == nil) 321 val = HOST_SETTINGS_DEFAULT; 322 323 for (XLFormOptionsObject *opt in opts) 324 if ([[opt valueData] isEqualToString:val]) 325 [row setValue:opt]; 326} 327 328@end