iOS web browser with a focus on security and privacy
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