iOS web browser with a focus on security and privacy
at master 172 lines 4.7 kB view raw
1/* 2 * Endless 3 * Copyright (c) 2014-2015 joshua stein <jcs@jcs.org> 4 * 5 * See LICENSE file for redistribution terms. 6 */ 7 8#import "URLBlocker.h" 9 10@implementation URLBlocker 11 12static NSDictionary *_targets; 13static NSMutableDictionary *_disabledTargets; 14static NSCache *ruleCache; 15 16#define RULE_CACHE_SIZE 20 17 18+ (NSString *)disabledTargetsPath 19{ 20 NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 21 return [path stringByAppendingPathComponent:@"url_blocker_disabled.plist"]; 22} 23 24+ (NSDictionary *)targets 25{ 26 NSError *error; 27 28 if (_targets == nil) { 29 NSString *path = [[NSBundle mainBundle] pathForResource:@"urlblocker" ofType:@"json"]; 30 if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { 31 NSLog(@"[URLBlocker] no target plist at %@", path); 32 abort(); 33 } 34 35 NSInputStream *input = [[NSInputStream alloc] initWithFileAtPath:path]; 36 if (input != nil) { 37 [input open]; 38 39 NSDictionary *blockers = [NSJSONSerialization JSONObjectWithStream:input options:kNilOptions error:&error]; 40 if (error != nil) 41 NSLog(@"[URLBlocker] couldn't read %@: %@", path, error); 42 [input close]; 43 44 /* convert from { "desc" => [ "host1", "host2" ] } to { "host1" => "desc", "host2" => "desc" } */ 45 NSMutableDictionary *ttargets = [[NSMutableDictionary alloc] init]; 46 for (NSString *key in [blockers allKeys]) { 47 NSArray *doms = [blockers objectForKey:key]; 48 for (NSString *dom in doms) 49 [ttargets setObject:key forKey:dom]; 50 } 51 52 _targets = [NSDictionary dictionaryWithDictionary:ttargets]; 53 } 54 55 if (!_targets || ![_targets count]) { 56 NSLog(@"[URLBlocker] couldn't read %@", path); 57 _targets = @{}; 58 return _targets; 59 } 60 61#ifdef TRACE_URL_BLOCKER 62 NSLog(@"[URLBlocker] locked and loaded with %lu target domains", [_targets count]); 63#endif 64 } 65 66 return _targets; 67} 68 69+ (NSMutableDictionary *)disabledTargets 70{ 71 if (_disabledTargets == nil) { 72 NSString *path = [[self class] disabledTargetsPath]; 73 if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { 74 _disabledTargets = [NSMutableDictionary dictionaryWithContentsOfFile:path]; 75 76#ifdef TRACE_HTTPS_EVERYWHERE 77 NSLog(@"[URLBlocker] loaded %lu disabled targets", [_disabledTargets count]); 78#endif 79 } 80 else { 81 _disabledTargets = [[NSMutableDictionary alloc] init]; 82 } 83 } 84 85 return _disabledTargets; 86} 87 88+ (void)saveDisabledTargets 89{ 90 [_disabledTargets writeToFile:[[self class] disabledTargetsPath] atomically:YES]; 91} 92 93+ (void)cacheBlockedURL:(NSURL *)url withRule:(NSString *)rule 94{ 95 if (!ruleCache) { 96 ruleCache = [[NSCache alloc] init]; 97 [ruleCache setCountLimit:RULE_CACHE_SIZE]; 98 } 99 100 [ruleCache setObject:rule forKey:url]; 101} 102 103+ (NSString *)blockRuleForURL:(NSURL *)url 104{ 105 NSString *blocker; 106 107 if (!(ruleCache && (blocker = [ruleCache objectForKey:url]))) { 108 NSString *host = [[url host] lowercaseString]; 109 110 if ([[[self class] targets] objectForKey:host]) 111 blocker = host; 112 else { 113 /* now for x.y.z.example.com, try *.y.z.example.com, *.z.example.com, *.example.com, etc. */ 114 /* TODO: should we skip the last component for obviously non-matching things like "*.com", "*.net"? */ 115 NSArray *hostp = [host componentsSeparatedByString:@"."]; 116 for (int i = 1; i < [hostp count]; i++) { 117 NSString *wc = [[hostp subarrayWithRange:NSMakeRange(i, [hostp count] - i)] componentsJoinedByString:@"."]; 118 119 if ([[[self class] targets] objectForKey:wc]) { 120 blocker = wc; 121 break; 122 } 123 } 124 } 125 } 126 127 if (blocker && [[URLBlocker disabledTargets] objectForKey:blocker] != nil) 128 return nil; 129 130 if (blocker) 131 [[self class] cacheBlockedURL:url withRule:blocker]; 132 133 return blocker; 134} 135 136+ (BOOL)shouldBlockURL:(NSURL *)url 137{ 138 return ([self blockRuleForURL:url] != nil); 139} 140 141+ (NSString *)blockingTargetForURL:(NSURL *)url fromMainDocumentURL:(NSURL *)mainUrl 142{ 143 NSString *blocker = [self blockRuleForURL:url]; 144 if (blocker != nil && mainUrl != nil) { 145 /* if this same rule would have blocked our main URL, allow it since the user is probably viewing this site and this isn't a sneaky tracker */ 146 if ([blocker isEqualToString:[self blockRuleForURL:mainUrl]]) { 147 return nil; 148 } 149 150#ifdef TRACE_URL_BLOCKER 151 NSLog(@"[URLBlocker] blocking %@ (via %@) (%@)", url, mainUrl, blocker); 152#endif 153 154 return blocker; 155 } 156 157 return nil; 158} 159 160+ (void)enableTargetByHost:(NSString *)target 161{ 162 [[[self class] disabledTargets] removeObjectForKey:target]; 163 [[self class] saveDisabledTargets]; 164} 165 166+ (void)disableTargetByHost:(NSString *)target withReason:(NSString *)reason 167{ 168 [[[self class] disabledTargets] setObject:reason forKey:target]; 169 [[self class] saveDisabledTargets]; 170} 171 172@end