iOS web browser with a focus on security and privacy
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 "HTTPSEverywhere.h"
9#import "HTTPSEverywhereRule.h"
10
11@implementation HTTPSEverywhereRule
12
13/* typical ruleset imported from XML rule:
14
15 ruleset = {
16 exclusion = {
17 pattern = "^http://(help|meme)\\.duckduckgo\\.com/";
18 };
19 name = DuckDuckGo;
20 rule = (
21 {
22 from = "^http://duckduckgo\\.com/";
23 to = "https://duckduckgo.com/";
24 },
25 {
26 from = "^http://([^/:@\\.]+)\\.duckduckgo\\.com/";
27 to = "https://$1.duckduckgo.com/";
28 },
29 );
30 securecookie = {
31 host = "^duck\\.co$";
32 name = ".*";
33 };
34 target = (
35 {
36 host = "duckduckgo.com";
37 },
38 {
39 host = "*.duckduckgo.com";
40 },
41 );
42 };
43*/
44
45- (id)initWithDictionary:(NSDictionary *)dict
46{
47 NSError *error;
48 NSObject *t;
49
50 NSDictionary *ruleset = [dict objectForKey:@"ruleset"];
51 if (ruleset == nil) {
52 NSLog(@"[HTTPSEverywhere] ruleset dict not found in %@", dict);
53 return nil;
54 }
55
56 self = [super init];
57 if (!self)
58 return nil;
59
60 self.name = (NSString *)[ruleset objectForKey:@"name"];
61
62 NSString *doff = [ruleset objectForKey:@"default_off"];
63 if (doff != nil && ![doff isEqualToString:@""]) {
64 self.on_by_default = NO;
65 self.notes = doff;
66 } else {
67 self.on_by_default = YES;
68 }
69
70 self.platform = (NSString *)[ruleset objectForKey:@"platform"];
71 /* TODO: do something useful with platform to disable rules */
72
73 /* exclusions */
74 if ((t = [ruleset objectForKey:@"exclusion"]) != nil) {
75 if (![t isKindOfClass:[NSArray class]])
76 t = [[NSArray alloc] initWithObjects:t, nil];
77
78 NSMutableArray *excs = [[NSMutableArray alloc] initWithCapacity:2];
79
80 for (NSDictionary *excd in (NSArray *)t) {
81 NSString *pattern = [excd valueForKey:@"pattern"];
82
83 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
84 if (error != nil) {
85 NSLog(@"[HTTPSEverywhere] error compiling regex %@: %@", pattern, error);
86 continue;
87 }
88
89 [excs addObject:regex];
90 }
91
92 self.exclusions = excs;
93 }
94
95 /* actual url mappings, dictionary of input url regex -> good url */
96 if ((t = [ruleset objectForKey:@"rule"]) != nil) {
97 if (![t isKindOfClass:[NSArray class]])
98 t = [[NSArray alloc] initWithObjects:t, nil];
99
100 NSMutableDictionary *rulesd = [[NSMutableDictionary alloc] initWithCapacity:2];
101
102 for (NSDictionary *ruled in (NSArray *)t) {
103 NSString *from = [ruled valueForKey:@"from"];
104 NSString *to = [ruled valueForKey:@"to"];
105
106 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:from options:NSRegularExpressionCaseInsensitive error:&error];
107 if (error != nil) {
108 NSLog(@"[HTTPSEverywhere] error compiling regex %@: %@", from, error);
109 continue;
110 }
111
112 [rulesd setObject:to forKey:regex];
113 }
114
115 self.rules = rulesd;
116 }
117
118 /* securecookies, dictionary of host regex -> cookie name regex */
119 if ((t = [ruleset objectForKey:@"securecookie"]) != nil) {
120 if (![t isKindOfClass:[NSArray class]])
121 t = [[NSArray alloc] initWithObjects:t, nil];
122
123 NSMutableDictionary *scooksd = [[NSMutableDictionary alloc] initWithCapacity:2];
124
125 for (NSDictionary *scookd in (NSArray *)t) {
126 NSString *host = [scookd valueForKey:@"host"];
127 NSString *cname = [scookd valueForKey:@"name"];
128
129 NSRegularExpression *hostreg = [NSRegularExpression regularExpressionWithPattern:host options:NSRegularExpressionCaseInsensitive error:&error];
130 if (error != nil) {
131 NSLog(@"[HTTPSEverywhere] error compiling regex %@: %@", host, error);
132 continue;
133 }
134
135 NSRegularExpression *namereg = [NSRegularExpression regularExpressionWithPattern:cname options:NSRegularExpressionCaseInsensitive error:&error];
136 if (error != nil) {
137 NSLog(@"[HTTPSEverywhere] error compiling regex %@: %@", cname, error);
138 continue;
139 }
140
141 [scooksd setObject:namereg forKey:hostreg];
142 }
143
144 self.secureCookies = scooksd;
145 }
146
147 return self;
148}
149
150/* return nil if URL was not modified by this rule */
151- (NSURL *)apply:(NSURL *)url
152{
153 NSString *absURL = [url absoluteString];
154 NSArray *matches;
155
156 for (NSRegularExpression *reg in (NSArray *)self.exclusions) {
157 if ((matches = [reg matchesInString:absURL options:0 range:NSMakeRange(0, [absURL length])]) != nil && [matches count] > 0) {
158#ifdef TRACE_HTTPS_EVERYWHERE
159 NSLog(@"[HTTPSEverywhere] [%@] exclusion %@ matched %@", self.name, [reg pattern], absURL);
160#endif
161 return nil;
162 }
163 }
164
165 for (NSRegularExpression *reg in (NSDictionary *)self.rules) {
166 if ((matches = [reg matchesInString:absURL options:0 range:NSMakeRange(0, [absURL length])]) != nil && [matches count] > 0) {
167 NSString *dest = [[self rules] objectForKey:reg];
168 dest = [reg stringByReplacingMatchesInString:absURL options:0 range:NSMakeRange(0, [absURL length]) withTemplate:dest];
169
170#ifdef TRACE_HTTPS_EVERYWHERE
171 NSLog(@"[HTTPSEverywhere] [%@] rewrote %@ to %@", self.name, absURL, dest);
172#endif
173
174 /* JS implementation says first matching wins */
175 return [NSURL URLWithString:dest];
176 }
177 }
178
179 return nil;
180}
181
182@end