iOS web browser with a focus on security and privacy
at master 256 lines 7.1 kB view raw
1/* 2 * Endless 3 * Copyright (c) 2015 joshua stein <jcs@jcs.org> 4 * 5 * See LICENSE file for redistribution terms. 6 * 7 * To add a new setting: 8 * 1. Define its key in HostSettings.h 9 * 2. Add it to [HostSettings defaults] along with its default value 10 * 3. Add it to [HostSettings showDetailsForHost:] in the appropriate section, or make a new one 11 */ 12 13#import "AppDelegate.h" 14#import "HostSettings.h" 15 16@implementation HostSettings 17 18static NSMutableDictionary *_hosts; 19 20+ (NSDictionary *)defaults 21{ 22 return @{ 23 HOST_SETTINGS_KEY_IGNORE_TLS_ERRORS: HOST_SETTINGS_VALUE_NO, 24 HOST_SETTINGS_KEY_TLS: HOST_SETTINGS_TLS_AUTO, 25 HOST_SETTINGS_KEY_CSP: HOST_SETTINGS_CSP_OPEN, 26 HOST_SETTINGS_KEY_BLOCK_LOCAL_NETS: HOST_SETTINGS_VALUE_YES, 27 HOST_SETTINGS_KEY_ALLOW_MIXED_MODE: HOST_SETTINGS_VALUE_NO, 28 HOST_SETTINGS_KEY_WHITELIST_COOKIES: HOST_SETTINGS_VALUE_NO, 29 HOST_SETTINGS_KEY_USER_AGENT: @"", 30 HOST_SETTINGS_KEY_ALLOW_WEBRTC: HOST_SETTINGS_VALUE_NO, 31 HOST_SETTINGS_KEY_UNIVERSAL_LINK_PROTECTION: HOST_SETTINGS_VALUE_YES, 32 }; 33} 34 35+ (void)migrateFromBuild:(long)lastBuild toBuild:(long)thisBuild 36{ 37 if (lastBuild <= 1401) { 38 /* 39 * t.co does redirections using a text/html page with a 0-delay <meta refresh> tag, 40 * but when the UA is something non-safari, it will send a proper 301 redirect, preserving 41 * the back button navigation. 42 */ 43 HostSettings *hs = [HostSettings forHost:@"t.co"]; 44 if (!hs) 45 hs = [[HostSettings alloc] initForHost:@"t.co" withDict:nil]; 46 [hs setSetting:HOST_SETTINGS_KEY_USER_AGENT toValue:@"curl (to force a 301 redirect)"]; 47 [hs save]; 48 } 49} 50 51+ (NSString *)hostSettingsPath 52{ 53 NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 54 return [path stringByAppendingPathComponent:@"host_settings.plist"]; 55} 56 57+ (NSMutableDictionary *)hosts 58{ 59 if (!_hosts) { 60 NSFileManager *fileManager = [NSFileManager defaultManager]; 61 if ([fileManager fileExistsAtPath:[self hostSettingsPath]]) { 62 NSDictionary *td = [NSMutableDictionary dictionaryWithContentsOfFile:[self hostSettingsPath]]; 63 64 _hosts = [[NSMutableDictionary alloc] initWithCapacity:[td count]]; 65 66 for (NSString *k in [td allKeys]) 67 [_hosts setObject:[[HostSettings alloc] initForHost:k withDict:[td objectForKey:k]] forKey:k]; 68 } 69 else 70 _hosts = [[NSMutableDictionary alloc] initWithCapacity:20]; 71 72 /* ensure default host exists */ 73 if (![_hosts objectForKey:HOST_SETTINGS_DEFAULT]) { 74 HostSettings *hs = [[HostSettings alloc] initForHost:HOST_SETTINGS_DEFAULT withDict:nil]; 75 [hs save]; 76 [HostSettings persist]; 77 } 78 } 79 80 return _hosts; 81} 82 83+ (void)persist 84{ 85 if ([(AppDelegate *)[[UIApplication sharedApplication] delegate] areTesting]) 86 return; 87 88 NSMutableDictionary *td = [[NSMutableDictionary alloc] initWithCapacity:[[self hosts] count]]; 89 for (NSString *k in [[self hosts] allKeys]) 90 [td setObject:[[[self hosts] objectForKey:k] dict] forKey:k]; 91 92 [td writeToFile:[self hostSettingsPath] atomically:YES]; 93} 94 95+ (HostSettings *)forHost:(NSString *)host 96{ 97 return [[self hosts] objectForKey:host]; 98} 99 100+ (HostSettings *)settingsOrDefaultsForHost:(NSString *)host 101{ 102 HostSettings *hs = [self forHost:host]; 103 if (!hs) { 104 /* for a host of x.y.z.example.com, try y.z.example.com, z.example.com, example.com, etc. */ 105 NSArray *hostp = [host componentsSeparatedByString:@"."]; 106 for (int i = 1; i < [hostp count]; i++) { 107 NSString *wc = [[hostp subarrayWithRange:NSMakeRange(i, [hostp count] - i)] componentsJoinedByString:@"."]; 108 109 if ((hs = [HostSettings forHost:wc])) { 110#ifdef TRACE_HOST_SETTINGS 111 NSLog(@"[HostSettings] found entry for component %@ in %@", wc, host); 112#endif 113 break; 114 } 115 } 116 } 117 118 if (!hs) 119 hs = [self defaultHostSettings]; 120 121 return hs; 122} 123 124+ (BOOL)removeSettingsForHost:(NSString *)host 125{ 126 HostSettings *h = [self forHost:host]; 127 if (h && ![h isDefault]) { 128 [[self hosts] removeObjectForKey:host]; 129 return YES; 130 } 131 132 return NO; 133} 134 135+ (HostSettings *)defaultHostSettings 136{ 137 return [self forHost:HOST_SETTINGS_DEFAULT]; 138} 139 140#if DEBUG 141/* just for testing */ 142+ (void)overrideHosts:(NSMutableDictionary *)hosts 143{ 144 _hosts = hosts; 145} 146#endif 147 148+ (NSArray *)sortedHosts 149{ 150 NSMutableArray *sorted = [[NSMutableArray alloc] initWithArray:[[self hosts] allKeys]]; 151 [sorted removeObject:HOST_SETTINGS_DEFAULT]; 152 [sorted sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; 153 [sorted insertObject:HOST_SETTINGS_DEFAULT atIndex:0]; 154 155 return [[NSArray alloc] initWithArray:sorted]; 156} 157 158- (HostSettings *)initForHost:(NSString *)host withDict:(NSDictionary *)dict 159{ 160 self = [super init]; 161 162 host = [host lowercaseString]; 163 164 if (dict) 165 _dict = [[NSMutableDictionary alloc] initWithDictionary:dict]; 166 else 167 _dict = [[NSMutableDictionary alloc] initWithCapacity:10]; 168 169 [_dict setObject:host forKey:HOST_SETTINGS_KEY_HOST]; 170 171 return self; 172} 173 174- (void)save 175{ 176 [[HostSettings hosts] setObject:self forKey:[[self dict] objectForKey:HOST_SETTINGS_KEY_HOST]]; 177} 178 179- (BOOL)isDefault 180{ 181 return ([[[self dict] objectForKey:HOST_SETTINGS_KEY_HOST] isEqualToString:HOST_SETTINGS_DEFAULT]); 182} 183 184- (NSString *)setting:(NSString *)setting 185{ 186 NSString *val = [[self dict] objectForKey:setting]; 187 if (val != NULL && ![val isKindOfClass:[NSString class]]) { 188 NSLog(@"[HostSettings] setting %@ for %@ was %@, not NSString", setting, [self hostname], [val class]); 189 val = nil; 190 } 191 192 if (val != nil && ![val isEqualToString:@""] && ![val isEqualToString:HOST_SETTINGS_DEFAULT]) 193 return val; 194 195 if (val == nil && [self isDefault]) 196 /* default host entries must have a value for every setting */ 197 return [[HostSettings defaults] objectForKey:setting]; 198 199 return nil; 200} 201 202- (NSString *)settingOrDefault:(NSString *)setting 203{ 204 NSString *val = [self setting:setting]; 205 if (val == nil || [val isEqualToString:@""]) 206 /* try default host settings */ 207 val = [[HostSettings defaultHostSettings] setting:setting]; 208 209 return val; 210} 211 212- (BOOL)boolSettingOrDefault:(NSString *)setting 213{ 214 NSString *val = [self settingOrDefault:setting]; 215 if (val != nil && [val isEqualToString:HOST_SETTINGS_VALUE_YES]) 216 return YES; 217 else 218 return NO; 219} 220 221- (void)setSetting:(NSString *)setting toValue:(NSString *)value 222{ 223 if (value == nil || [value isEqualToString:HOST_SETTINGS_DEFAULT]) { 224 [[self dict] removeObjectForKey:setting]; 225 return; 226 } 227 228 if ([setting isEqualToString:HOST_SETTINGS_KEY_TLS]) { 229 if (!([value isEqualToString:HOST_SETTINGS_TLS_12] || [value isEqualToString:HOST_SETTINGS_TLS_AUTO])) 230 return; 231 } 232 233 [[self dict] setObject:value forKey:setting]; 234} 235 236- (NSString *)hostname 237{ 238 if ([self isDefault]) 239 return HOST_SETTINGS_HOST_DEFAULT_LABEL; 240 else 241 return [[self dict] objectForKey:HOST_SETTINGS_KEY_HOST]; 242} 243 244- (void)setHostname:(NSString *)hostname 245{ 246 if ([self isDefault] || !hostname || [hostname isEqualToString:@""]) 247 return; 248 249 hostname = [hostname lowercaseString]; 250 251 [[HostSettings hosts] removeObjectForKey:[[self dict] objectForKey:HOST_SETTINGS_KEY_HOST]]; 252 [[self dict] setObject:hostname forKey:HOST_SETTINGS_KEY_HOST]; 253 [[HostSettings hosts] setObject:self forKey:hostname]; 254} 255 256@end