@@ -12,52 +12,69 @@ import 'package:punycode/punycode.dart';
12
12
13
13
import 'suffix_rules.dart' ;
14
14
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 .
16
16
class PublicSuffix {
17
17
final Uri sourceUri;
18
18
19
19
bool _sourcePunycoded = false ;
20
+ bool _hasKnownSuffix;
20
21
String _root;
21
22
String _suffix;
22
23
String _domain;
24
+ String _subdomain;
23
25
String _icannRoot;
24
26
String _icannSuffix;
25
27
String _icannDomain;
28
+ String _icannSubdomain;
26
29
27
30
PublicSuffix _punyDecoded;
28
31
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.
30
33
///
31
34
/// The registrable domain is the public suffix and one preceding label.
32
35
/// For example, `images.google.co.uk` has the registrable domain `google.co.uk` .
33
36
String get domain => _domain;
34
37
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.
36
46
///
37
47
/// The root domain is the label that precedes the public suffix.
38
48
/// For example, `images.google.co.uk` has the root domain `google` .
39
49
String get root => _root;
40
50
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.
42
52
///
43
53
/// The public suffix is the labels at the end of the URL which are not controlled
44
54
/// by the registrant of the domain.
45
55
/// For example, `images.google.co.uk` has the public suffix `co.uk` .
46
56
String get suffix => _suffix;
47
57
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.
49
59
///
50
60
/// The registrable domain is the public suffix and one preceding label.
51
61
/// For example, `images.google.co.uk` has the registrable domain `google.co.uk` .
52
62
String get icannDomain => _icannDomain;
53
63
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.
55
72
///
56
73
/// The root domain is the label that precedes the public suffix.
57
74
/// For example, `images.google.co.uk` has the root domain `google` .
58
75
String get icannRoot => _icannRoot;
59
76
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.
61
78
///
62
79
/// The public suffix is the labels at the end of the URL which are not controlled
63
80
/// by the registrant of the domain.
@@ -67,16 +84,62 @@ class PublicSuffix {
67
84
/// Returns a punycode decoded version of this object.
68
85
PublicSuffix get punyDecoded => _punyDecoded;
69
86
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.
71
88
///
72
89
/// If [true] , then [root] , [suffix] and [domain] will be different from the
73
90
/// `icann` -prefixed getters.
74
91
bool isPrivateSuffix () => icannSuffix != _suffix;
75
92
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) {
78
139
_domain = _buildRegistrableDomain (_root, _suffix);
79
140
_icannDomain = _buildRegistrableDomain (_icannRoot, _icannSuffix);
141
+ _subdomain = _getSubdomain (host, _domain);
142
+ _icannSubdomain = _getSubdomain (host, _icannDomain);
80
143
}
81
144
82
145
/// Creates a new instance based on the specified [sourceUri] .
@@ -91,15 +154,21 @@ class PublicSuffix {
91
154
}
92
155
if (! sourceUri.hasAuthority) {
93
156
throw ArgumentError (
94
- "The URI is missing the authority component: $sourceUri " );
157
+ "The URL is missing the authority component: $sourceUri " );
95
158
}
96
159
97
- _parseUri (sourceUri, SuffixRules .rules );
160
+ _parseUrl (sourceUri, SuffixRules .ruleMap );
98
161
}
99
162
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);
103
172
var prevailingIcannRule = _getPrevailingRule (matchingRules['icann' ]);
104
173
var prevailingAllRule = _getPrevailingRule (matchingRules['all' ]);
105
174
@@ -117,19 +186,22 @@ class PublicSuffix {
117
186
_suffix = allData['suffix' ];
118
187
_root = allData['root' ];
119
188
_domain = allData['registrable' ];
189
+ _subdomain = allData['sub' ];
120
190
_icannSuffix = icannData['suffix' ];
121
191
_icannRoot = icannData['root' ];
122
192
_icannDomain = icannData['registrable' ];
193
+ _icannSubdomain = icannData['sub' ];
194
+ _hasKnownSuffix = (prevailingAllRule.labels != "*" );
123
195
124
196
var puny = allData['puny' ];
125
197
var icannPuny = icannData['puny' ];
126
198
127
- _punyDecoded = PublicSuffix ._(sourceUri, puny['root' ], puny['suffix' ],
199
+ _punyDecoded = PublicSuffix ._(sourceUri, host, puny['root' ], puny['suffix' ],
128
200
icannPuny['root' ], icannPuny['suffix' ]);
129
201
}
130
202
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 ();
133
205
host = Uri .decodeComponent (host);
134
206
135
207
var punycodes = RegExp (r'xn--[a-z0-9-]+' ).allMatches (host);
@@ -149,52 +221,26 @@ class PublicSuffix {
149
221
}
150
222
151
223
Map <String , List <Rule >> _findMatchingRules (
152
- String host, List < Rule > suffixList ) {
224
+ String host, Map < String , Iterable < Rule >> suffixMap ) {
153
225
var icannMatches = < Rule > [];
154
226
var allMatches = < Rule > [];
155
227
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];
159
230
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);
184
235
185
- if (rulePart != '*' && rulePart != hostPart ) {
186
- matches = false ;
187
- break ;
236
+ if (rule.isIcann ) {
237
+ icannMatches. add (rule) ;
238
+ }
188
239
}
189
-
190
- r-- ;
191
- h-- ;
192
240
}
193
- } else {
194
- matches = false ;
195
241
}
196
242
197
- return matches ;
243
+ return { 'icann' : icannMatches, 'all' : allMatches} ;
198
244
}
199
245
200
246
Rule _getPrevailingRule (List <Rule > matchingRules) {
@@ -230,17 +276,20 @@ class PublicSuffix {
230
276
var puny = {'root' : root, 'suffix' : suffix};
231
277
232
278
if (_sourcePunycoded) {
279
+ host = _punyEncode (host);
233
280
suffix = _punyEncode (suffix);
234
281
root = _punyEncode (root);
235
282
}
236
283
237
284
var registrable = _buildRegistrableDomain (root, suffix);
285
+ var sub = _getSubdomain (host, registrable);
238
286
239
287
return {
240
288
'suffix' : suffix,
241
289
'root' : root,
242
290
'puny' : puny,
243
- 'registrable' : registrable
291
+ 'registrable' : registrable,
292
+ 'sub' : sub
244
293
};
245
294
}
246
295
@@ -288,4 +337,18 @@ class PublicSuffix {
288
337
String _buildRegistrableDomain (String root, String suffix) {
289
338
return (root.isNotEmpty ? "$root .$suffix " : null );
290
339
}
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
+ }
291
354
}
0 commit comments