Skip to content

Commit 429d8f7

Browse files
committed
Add autoinclude hostnames, + regex/wildcard blocking kiiiiinda work (still very buggy)
1 parent c438db7 commit 429d8f7

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

SelfControlIOS/FilterUtilities.swift

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,102 @@ open class FilterUtilities: NSObject {
1919

2020
// MARK: Initializers
2121

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+
22118
/// Get rule parameters for a flow from the SimpleTunnel user defaults.
23119
open class func getRule(_ flow: NEFilterFlow) -> (SCBlockRuleFilterAction, String, [String: AnyObject]) {
24120
let hostname = FilterUtilities.getFlowHostname(flow)
@@ -37,7 +133,7 @@ open class FilterUtilities: NSObject {
37133

38134
guard !hostname.isEmpty else { return (.allow, hostname, [:]) }
39135

40-
guard let hostNameRule = (defaults?.object(forKey: "rules") as AnyObject).object(forKey: hostname) as? [String: AnyObject] else {
136+
guard let hostNameRule = getHostnameRuleObj(hostname) else {
41137
NSLog("\(hostname) is set for NO RULES")
42138
return (.allow, hostname, [:])
43139
}

0 commit comments

Comments
 (0)