Skip to content

Commit 60acc19

Browse files
authored
🔀 Merge pull request #2 from Komposten/release-1.2.0
Release 1.2.0
2 parents c4103c5 + f5c05a9 commit 60acc19

13 files changed

+617
-77
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## 1.2.0
2+
- Added a hash map-based `ruleMap` to `SuffixRules` to speed up the performance when matching rules.
3+
- Added `subdomain` and `icannSubdomain` to `PublicSuffix`.
4+
- Added `isSubdomainOf`, `hasKnownSuffix` and `hasValidDomain` to `PublicSuffix`.
5+
- Added `DomainUtils` with the following static functions:
6+
- `isSubdomainOf`
7+
- `isSubdomain`
8+
- `isKnownSuffix`
9+
- `hasValidDomain`
10+
- Added `PublicSuffix.fromString` as a convenience method for `PublicSuffix(Uri.parse(string))`.
11+
- Updated docs and parameter names to say URL instead of URI, which is more correct (`PublicSuffix.sourceUri` is unchanged to avoid breaking changes).
12+
113
## 1.1.0
214
- Added primary library `public_suffix.dart`, which can be used without `dart:io` and `dart:html` (but still requires to be initialised with a suffix list).
315

lib/public_suffix.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010

1111
export 'src/public_suffix_base.dart';
1212
export 'src/suffix_rules.dart';
13+
export 'src/utilities.dart';

lib/src/browser/suffix_rules_helper.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class SuffixRulesHelper {
5454
object = e.target;
5555
}
5656

57-
if (e is HttpRequest) {
57+
if (object is HttpRequest) {
5858
throw Exception(
5959
"Request for public suffix list failed: [${object.status}] ${object.statusText}");
6060
} else {

lib/src/io/suffix_rules_helper.dart

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,17 @@ class SuffixRulesHelper {
4646
}
4747

4848
static Future<void> _initFromUrl(Uri uri) async {
49-
try {
50-
var request = await HttpClient().getUrl(uri);
51-
var response = await request.close();
52-
var data = await response.transform(Utf8Decoder()).join();
49+
var request = await HttpClient().getUrl(uri);
50+
var response = await request.close();
5351

54-
SuffixRules.initFromString(data);
55-
} catch (e) {
56-
rethrow;
52+
switch (response.statusCode) {
53+
case 200:
54+
var data = await response.transform(Utf8Decoder()).join();
55+
SuffixRules.initFromString(data);
56+
break;
57+
default:
58+
throw Exception(
59+
"Request for public suffix list failed: [${response.statusCode}]");
5760
}
5861
}
5962
}

lib/src/public_suffix_base.dart

Lines changed: 119 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,52 +12,69 @@ import 'package:punycode/punycode.dart';
1212

1313
import 'suffix_rules.dart';
1414

15-
/// A description of the public suffix, root domain and registrable domain for a URI.
15+
/// A description of the public suffix, root domain and registrable domain for a URL.
1616
class PublicSuffix {
1717
final Uri sourceUri;
1818

1919
bool _sourcePunycoded = false;
20+
bool _hasKnownSuffix;
2021
String _root;
2122
String _suffix;
2223
String _domain;
24+
String _subdomain;
2325
String _icannRoot;
2426
String _icannSuffix;
2527
String _icannDomain;
28+
String _icannSubdomain;
2629

2730
PublicSuffix _punyDecoded;
2831

29-
/// Returns the registrable domain part of the URI, based on both ICANN/IANA and private rules.
32+
/// Returns the registrable domain part of the URL, based on both ICANN/IANA and private rules.
3033
///
3134
/// The registrable domain is the public suffix and one preceding label.
3235
/// For example, `images.google.co.uk` has the registrable domain `google.co.uk`.
3336
String get domain => _domain;
3437

35-
/// Returns the root domain part of the URI, based on both ICANN/IANA and private rules.
38+
/// Returns the subdomain part of the URL, based on both ICANN/IANA and private rules.
39+
///
40+
/// The subdomain is the part of the host that precedes the registrable domain
41+
/// (see [domain]).
42+
/// For example, `images.google.co.uk` has the subdomain `images`.
43+
String get subdomain => _subdomain;
44+
45+
/// Returns the root domain part of the URL, based on both ICANN/IANA and private rules.
3646
///
3747
/// The root domain is the label that precedes the public suffix.
3848
/// For example, `images.google.co.uk` has the root domain `google`.
3949
String get root => _root;
4050

41-
/// Returns the public suffix part of the URI, based on both ICANN/IANA and private rules.
51+
/// Returns the public suffix part of the URL, based on both ICANN/IANA and private rules.
4252
///
4353
/// The public suffix is the labels at the end of the URL which are not controlled
4454
/// by the registrant of the domain.
4555
/// For example, `images.google.co.uk` has the public suffix `co.uk`.
4656
String get suffix => _suffix;
4757

48-
/// Returns the registrable domain part of the URI, based on ICANN/IANA rules.
58+
/// Returns the registrable domain part of the URL, based on ICANN/IANA rules.
4959
///
5060
/// The registrable domain is the public suffix and one preceding label.
5161
/// For example, `images.google.co.uk` has the registrable domain `google.co.uk`.
5262
String get icannDomain => _icannDomain;
5363

54-
/// Returns the root domain part of the URI, based on ICANN/IANA rules.
64+
/// Returns the subdomain part of the URL, based on ICANN/IANA rules.
65+
///
66+
/// The subdomain is the part of the host that precedes the registrable domain
67+
/// (see [domain]).
68+
/// For example, `images.google.co.uk` has the subdomain `images`.
69+
String get icannSubdomain => _icannSubdomain;
70+
71+
/// Returns the root domain part of the URL, based on ICANN/IANA rules.
5572
///
5673
/// The root domain is the label that precedes the public suffix.
5774
/// For example, `images.google.co.uk` has the root domain `google`.
5875
String get icannRoot => _icannRoot;
5976

60-
/// Returns the public suffix part of the URI, based on ICANN/IANA rules.
77+
/// Returns the public suffix part of the URL, based on ICANN/IANA rules.
6178
///
6279
/// The public suffix is the labels at the end of the URL which are not controlled
6380
/// by the registrant of the domain.
@@ -67,16 +84,62 @@ class PublicSuffix {
6784
/// Returns a punycode decoded version of this object.
6885
PublicSuffix get punyDecoded => _punyDecoded;
6986

70-
/// Checks if the URI was matched with a private rule rather than an ICANN/IANA rule.
87+
/// Checks if the URL was matched with a private rule rather than an ICANN/IANA rule.
7188
///
7289
/// If [true], then [root], [suffix] and [domain] will be different from the
7390
/// `icann`-prefixed getters.
7491
bool isPrivateSuffix() => icannSuffix != _suffix;
7592

76-
PublicSuffix._(this.sourceUri, this._root, this._suffix, this._icannRoot,
77-
this._icannSuffix) {
93+
/// Whether the [suffix] is a known suffix or not.
94+
///
95+
/// A known suffix is one which has a rule in the suffix rule list.
96+
bool hasKnownSuffix() => _hasKnownSuffix;
97+
98+
/// Checks if the registrable domain is valid.
99+
///
100+
/// If [icann] is [true] the check will be based on [icannDomain], otherwise
101+
/// [domain] is used.
102+
/// If [acceptDefaultRule] is [false] URLs with suffixes only matching the
103+
/// default rule (`*`) will be seen as invalid.
104+
bool hasValidDomain({bool icann = false, bool acceptDefaultRule = true}) {
105+
var _domain = (icann ? icannDomain : domain);
106+
107+
if (acceptDefaultRule || hasKnownSuffix()) {
108+
return _domain != null;
109+
} else {
110+
return false;
111+
}
112+
}
113+
114+
/// Checks if this object represents a subdomain of another.
115+
///
116+
/// The domain and subdomain properties are compared to determine if
117+
/// this object represents a subdomain of [other]. If [icann] is [true],
118+
/// comparison will be based on only the ICANN/IANA rules.
119+
///
120+
/// For example, `http://images.google.co.uk` is a subdomain of `http://google.co.uk`.
121+
///
122+
/// If [other] has a subdomain and this object represents a subdomain of that,
123+
/// [true] is still returned.
124+
bool isSubdomainOf(PublicSuffix other, {bool icann = false}) {
125+
if (icann) {
126+
return icannDomain == other.icannDomain &&
127+
icannSubdomain != null &&
128+
(other.icannSubdomain == null ||
129+
icannSubdomain.endsWith(other.icannSubdomain));
130+
} else {
131+
return domain == other.domain &&
132+
subdomain != null &&
133+
(other.subdomain == null || subdomain.endsWith(other.subdomain));
134+
}
135+
}
136+
137+
PublicSuffix._(this.sourceUri, String host, this._root, this._suffix,
138+
this._icannRoot, this._icannSuffix) {
78139
_domain = _buildRegistrableDomain(_root, _suffix);
79140
_icannDomain = _buildRegistrableDomain(_icannRoot, _icannSuffix);
141+
_subdomain = _getSubdomain(host, _domain);
142+
_icannSubdomain = _getSubdomain(host, _icannDomain);
80143
}
81144

82145
/// Creates a new instance based on the specified [sourceUri].
@@ -91,15 +154,21 @@ class PublicSuffix {
91154
}
92155
if (!sourceUri.hasAuthority) {
93156
throw ArgumentError(
94-
"The URI is missing the authority component: $sourceUri");
157+
"The URL is missing the authority component: $sourceUri");
95158
}
96159

97-
_parseUri(sourceUri, SuffixRules.rules);
160+
_parseUrl(sourceUri, SuffixRules.ruleMap);
98161
}
99162

100-
void _parseUri(Uri uri, List<Rule> suffixList) {
101-
var host = _decodeHost(uri);
102-
var matchingRules = _findMatchingRules(host, suffixList);
163+
/// Creates a new instance from a URL in a string.
164+
///
165+
/// This is a convenience method that simply converts [url] into a URI object
166+
/// and creates an instance from it.
167+
PublicSuffix.fromString(String url) : this(Uri.parse(url));
168+
169+
void _parseUrl(Uri url, Map<String, Iterable<Rule>> suffixMap) {
170+
var host = _decodeHost(url);
171+
var matchingRules = _findMatchingRules(host, suffixMap);
103172
var prevailingIcannRule = _getPrevailingRule(matchingRules['icann']);
104173
var prevailingAllRule = _getPrevailingRule(matchingRules['all']);
105174

@@ -117,19 +186,22 @@ class PublicSuffix {
117186
_suffix = allData['suffix'];
118187
_root = allData['root'];
119188
_domain = allData['registrable'];
189+
_subdomain = allData['sub'];
120190
_icannSuffix = icannData['suffix'];
121191
_icannRoot = icannData['root'];
122192
_icannDomain = icannData['registrable'];
193+
_icannSubdomain = icannData['sub'];
194+
_hasKnownSuffix = (prevailingAllRule.labels != "*");
123195

124196
var puny = allData['puny'];
125197
var icannPuny = icannData['puny'];
126198

127-
_punyDecoded = PublicSuffix._(sourceUri, puny['root'], puny['suffix'],
199+
_punyDecoded = PublicSuffix._(sourceUri, host, puny['root'], puny['suffix'],
128200
icannPuny['root'], icannPuny['suffix']);
129201
}
130202

131-
String _decodeHost(Uri uri) {
132-
var host = uri.host.replaceAll(RegExp(r'\.+$'), '').toLowerCase();
203+
String _decodeHost(Uri url) {
204+
var host = url.host.replaceAll(RegExp(r'\.+$'), '').toLowerCase();
133205
host = Uri.decodeComponent(host);
134206

135207
var punycodes = RegExp(r'xn--[a-z0-9-]+').allMatches(host);
@@ -149,52 +221,26 @@ class PublicSuffix {
149221
}
150222

151223
Map<String, List<Rule>> _findMatchingRules(
152-
String host, List<Rule> suffixList) {
224+
String host, Map<String, Iterable<Rule>> suffixMap) {
153225
var icannMatches = <Rule>[];
154226
var allMatches = <Rule>[];
155227

156-
for (var rule in suffixList) {
157-
if (_ruleMatches(rule, host)) {
158-
allMatches.add(rule);
228+
var lastLabel = host.substring(host.lastIndexOf(('.')) + 1);
229+
var suffixList = suffixMap[lastLabel];
159230

160-
if (rule.isIcann) {
161-
icannMatches.add(rule);
162-
}
163-
}
164-
}
165-
166-
return {'icann': icannMatches, 'all': allMatches};
167-
}
168-
169-
bool _ruleMatches(Rule rule, String host) {
170-
var hostParts = host.split('.');
171-
var ruleParts = rule.labels.split('.');
172-
173-
hostParts.removeWhere((e) => e.isEmpty);
174-
175-
var matches = true;
176-
177-
if (ruleParts.length <= hostParts.length) {
178-
int r = ruleParts.length - 1;
179-
int h = hostParts.length - 1;
180-
181-
while (r >= 0) {
182-
var rulePart = ruleParts[r];
183-
var hostPart = hostParts[h];
231+
if (suffixList != null) {
232+
for (var rule in suffixList) {
233+
if (rule.matches(host)) {
234+
allMatches.add(rule);
184235

185-
if (rulePart != '*' && rulePart != hostPart) {
186-
matches = false;
187-
break;
236+
if (rule.isIcann) {
237+
icannMatches.add(rule);
238+
}
188239
}
189-
190-
r--;
191-
h--;
192240
}
193-
} else {
194-
matches = false;
195241
}
196242

197-
return matches;
243+
return {'icann': icannMatches, 'all': allMatches};
198244
}
199245

200246
Rule _getPrevailingRule(List<Rule> matchingRules) {
@@ -230,17 +276,20 @@ class PublicSuffix {
230276
var puny = {'root': root, 'suffix': suffix};
231277

232278
if (_sourcePunycoded) {
279+
host = _punyEncode(host);
233280
suffix = _punyEncode(suffix);
234281
root = _punyEncode(root);
235282
}
236283

237284
var registrable = _buildRegistrableDomain(root, suffix);
285+
var sub = _getSubdomain(host, registrable);
238286

239287
return {
240288
'suffix': suffix,
241289
'root': root,
242290
'puny': puny,
243-
'registrable': registrable
291+
'registrable': registrable,
292+
'sub': sub
244293
};
245294
}
246295

@@ -288,4 +337,18 @@ class PublicSuffix {
288337
String _buildRegistrableDomain(String root, String suffix) {
289338
return (root.isNotEmpty ? "$root.$suffix" : null);
290339
}
340+
341+
String _getSubdomain(String host, String registrableDomain) {
342+
var sub;
343+
344+
if (registrableDomain != null) {
345+
var index = host.lastIndexOf(registrableDomain);
346+
347+
if (index > 0) {
348+
sub = host.substring(0, index - 1);
349+
}
350+
}
351+
352+
return sub;
353+
}
291354
}

0 commit comments

Comments
 (0)