@@ -19,6 +19,102 @@ open class FilterUtilities: NSObject {
19
19
20
20
// MARK: Initializers
21
21
22
+ open class func getHostnameRuleObj( _ hostname: String ) -> [ String : AnyObject ] ? {
23
+ let rules = ( defaults? . object ( forKey: " rules " ) as AnyObject )
24
+ var adjustedHostname = hostname;
25
+
26
+ // TODO: special handling for special sites like Facebook, Google, etc that are hard to block for end-users
27
+
28
+ // These are all subdomains that are usually just the main site, but on a subdomain for some practical reason
29
+ // We'll assume that if the user blocked the root domain, they meant to block these, and if
30
+ // they block these they meant to block them all
31
+ let AUTOINCLUDED_SUBDOMAINS = [ " www " ,
32
+ " ww1 " ,
33
+ " ww2 " ,
34
+ " www1 " ,
35
+ " www2 " ,
36
+ " secure " ,
37
+ " web " ,
38
+ " app " ,
39
+ " m " ]
40
+
41
+ // first, strip any of the AUTOINCLUDED_SUBDOMAINS, to get to the root domain (or at least one level down)
42
+ for subdomain in AUTOINCLUDED_SUBDOMAINS {
43
+ if ( adjustedHostname. hasPrefix ( " \( subdomain) . " ) ) {
44
+ // remove the prefix
45
+ let newStartIndex = adjustedHostname. index ( adjustedHostname. startIndex, offsetBy: subdomain. characters. count + 1 )
46
+ adjustedHostname = adjustedHostname. substring ( from: newStartIndex)
47
+
48
+ // break so we don't keep going down to sub-subdomains (e.g. www.secure.app.web.example.com)
49
+ break
50
+ }
51
+ }
52
+
53
+ // now see if we have a rule for the stripped domain...
54
+ var ruleObj = rules. object ( forKey: adjustedHostname) as? [ String : AnyObject ]
55
+ if ( ruleObj != nil ) {
56
+ return ruleObj
57
+ }
58
+
59
+ // OR if not, for the stripped domain prefixed by any autoinclude subdomain
60
+ for subdomain in AUTOINCLUDED_SUBDOMAINS {
61
+ ruleObj = rules. object ( forKey: " \( subdomain) . \( adjustedHostname) " ) as? [ String : AnyObject ]
62
+
63
+ // if we found a rule, we're done!
64
+ if ( ruleObj != nil ) { break }
65
+ }
66
+ if ( ruleObj != nil ) {
67
+ return ruleObj
68
+ }
69
+
70
+ // If we still didn't find a rule, finally try checking any regex rules
71
+ // (anything starting/ending with slash is treated as a regex
72
+ // TODO: add sugar to make *.example.com be treated as a regex
73
+ NSLog ( " looking for regexes in all the wrong places for \( adjustedHostname) " )
74
+ for rule in ( rules as! [ String : [ String : AnyObject ] ] ) {
75
+ if ( rule. key. hasPrefix ( " / " ) && rule. key. hasSuffix ( " / " ) ) {
76
+ // power user! just use it as a regex and test it against the hostname
77
+
78
+ // we have to remove the / prefix/suffix first
79
+ let regexStart = rule. key. index ( rule. key. startIndex, offsetBy: 1 )
80
+ let regexEnd = rule. key. index ( rule. key. endIndex, offsetBy: - 1 )
81
+ let regex = rule. key. substring ( with: regexStart..< regexEnd)
82
+
83
+ NSLog ( " Treating as a regex: \( regex) " )
84
+ if ( hostname. range ( of: regex, options: . regularExpression) != nil ) {
85
+ NSLog ( " --> MATCHED: \( rule. key) on \( hostname) " )
86
+ ruleObj = rule. value;
87
+ }
88
+ } else if ( rule. key. hasPrefix ( " *. " ) ) {
89
+ // wildcard subdomain block
90
+
91
+ // first remove the *. prefix
92
+ var regexRule = rule. key. substring ( from: rule. key. index ( rule. key. startIndex, offsetBy: 2 ) )
93
+ NSLog ( " unprefixed: \( regexRule) " )
94
+
95
+ // next remove all other regex special characters
96
+ // (they usually shouldn't be in hostnames anyway
97
+ // TODO: escape them and leave them in the regex
98
+ regexRule = regexRule. replacingOccurrences ( of: " \\ \\ \\ ^ \\ $ \\ . \\ | \\ ? \\ * \\ + \\ ( \\ ) \\ [ \\ { " ,
99
+ with: " \\ $0 " ,
100
+ options: . regularExpression)
101
+ NSLog ( " special chars removed: \( regexRule) " )
102
+
103
+ // and re-add the wildcard subdomain part properly
104
+ regexRule = " .* \\ . \( regexRule) " ;
105
+
106
+ // finally, run it against hostname (make sure to make it work on the root domain also)
107
+ NSLog ( " Treating as a wildcard rule, regex: \( regexRule) " )
108
+ if ( hostname. range ( of: regexRule, options: . regularExpression) != nil ) {
109
+ NSLog ( " --> MATCHED: \( regexRule) on \( hostname) " )
110
+ ruleObj = rule. value;
111
+ }
112
+ }
113
+ }
114
+
115
+ return ruleObj
116
+ }
117
+
22
118
/// Get rule parameters for a flow from the SimpleTunnel user defaults.
23
119
open class func getRule( _ flow: NEFilterFlow ) -> ( SCBlockRuleFilterAction , String , [ String : AnyObject ] ) {
24
120
let hostname = FilterUtilities . getFlowHostname ( flow)
@@ -37,7 +133,7 @@ open class FilterUtilities: NSObject {
37
133
38
134
guard !hostname. isEmpty else { return ( . allow, hostname, [ : ] ) }
39
135
40
- guard let hostNameRule = ( defaults ? . object ( forKey : " rules " ) as AnyObject ) . object ( forKey : hostname) as? [ String : AnyObject ] else {
136
+ guard let hostNameRule = getHostnameRuleObj ( hostname) else {
41
137
NSLog ( " \( hostname) is set for NO RULES " )
42
138
return ( . allow, hostname, [ : ] )
43
139
}
0 commit comments