public_suffix 2.0.5 → 4.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +12 -0
  3. data/.github/dependabot.yml +8 -0
  4. data/.github/workflows/release.yml +16 -0
  5. data/.github/workflows/tests.yml +28 -0
  6. data/.gitignore +5 -8
  7. data/.rubocop.yml +19 -1
  8. data/{.rubocop_defaults.yml → .rubocop_opinionated.yml} +62 -34
  9. data/CHANGELOG.md +156 -54
  10. data/Gemfile +9 -5
  11. data/LICENSE.txt +1 -1
  12. data/README.md +44 -15
  13. data/Rakefile +9 -4
  14. data/SECURITY.md +104 -0
  15. data/bin/console +15 -0
  16. data/data/list.txt +3163 -973
  17. data/lib/public_suffix/domain.rb +4 -4
  18. data/lib/public_suffix/errors.rb +3 -1
  19. data/lib/public_suffix/list.rb +78 -117
  20. data/lib/public_suffix/rule.rb +54 -62
  21. data/lib/public_suffix/version.rb +8 -3
  22. data/lib/public_suffix.rb +38 -32
  23. data/public_suffix.gemspec +9 -5
  24. data/test/.empty +2 -0
  25. data/test/acceptance_test.rb +43 -31
  26. data/test/benchmarks/bm_find.rb +66 -0
  27. data/test/benchmarks/bm_find_all.rb +102 -0
  28. data/test/benchmarks/bm_names.rb +91 -0
  29. data/test/benchmarks/bm_select.rb +26 -0
  30. data/test/benchmarks/bm_select_incremental.rb +25 -0
  31. data/test/benchmarks/bm_valid.rb +101 -0
  32. data/test/profilers/domain_profiler.rb +12 -0
  33. data/test/profilers/find_profiler.rb +12 -0
  34. data/test/profilers/find_profiler_jp.rb +12 -0
  35. data/test/{initialization_profiler.rb → profilers/initialization_profiler.rb} +1 -1
  36. data/test/profilers/list_profsize.rb +11 -0
  37. data/test/profilers/object_binsize.rb +57 -0
  38. data/test/psl_test.rb +7 -4
  39. data/test/test_helper.rb +3 -14
  40. data/test/unit/domain_test.rb +17 -15
  41. data/test/unit/errors_test.rb +2 -0
  42. data/test/unit/list_test.rb +54 -72
  43. data/test/unit/public_suffix_test.rb +24 -22
  44. data/test/unit/rule_test.rb +77 -79
  45. metadata +32 -70
  46. data/.ruby-gemset +0 -1
  47. data/.travis.yml +0 -23
  48. data/test/benchmark_helper.rb +0 -4
  49. data/test/execution_profiler.rb +0 -14
  50. data/test/performance_benchmark.rb +0 -38
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # = Public Suffix
2
4
  #
3
5
  # Domain name parser based on the Public Suffix List.
4
6
  #
5
- # Copyright (c) 2009-2017 Simone Carletti <weppos@weppos.net>
7
+ # Copyright (c) 2009-2022 Simone Carletti <weppos@weppos.net>
6
8
 
7
9
  module PublicSuffix
8
10
 
@@ -43,7 +45,7 @@ module PublicSuffix
43
45
  # Initializes with a +tld+, +sld+ and +trd+.
44
46
  # @param [String] tld The TLD (extension)
45
47
  # @param [String] sld The SLD (domain)
46
- # @param [String] tld The TRD (subdomain)
48
+ # @param [String] trd The TRD (subdomain)
47
49
  #
48
50
  # @yield [self] Yields on self.
49
51
  # @yieldparam [PublicSuffix::Domain] self The newly creates instance
@@ -173,8 +175,6 @@ module PublicSuffix
173
175
  # This method doesn't actually validate the domain.
174
176
  # It only checks whether the instance contains
175
177
  # a value for the {#tld} and {#sld} attributes.
176
- # If you also want to validate the domain,
177
- # use {#valid_domain?} instead.
178
178
  #
179
179
  # @example
180
180
  #
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # = Public Suffix
2
4
  #
3
5
  # Domain name parser based on the Public Suffix List.
4
6
  #
5
- # Copyright (c) 2009-2017 Simone Carletti <weppos@weppos.net>
7
+ # Copyright (c) 2009-2022 Simone Carletti <weppos@weppos.net>
6
8
 
7
9
  module PublicSuffix
8
10
 
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # = Public Suffix
2
4
  #
3
5
  # Domain name parser based on the Public Suffix List.
4
6
  #
5
- # Copyright (c) 2009-2017 Simone Carletti <weppos@weppos.net>
7
+ # Copyright (c) 2009-2022 Simone Carletti <weppos@weppos.net>
6
8
 
7
9
  module PublicSuffix
8
10
 
@@ -35,12 +37,9 @@ module PublicSuffix
35
37
  # The {PublicSuffix::List.default} rule list is used
36
38
  # to tokenize and validate a domain.
37
39
  #
38
- # {PublicSuffix::List} implements +Enumerable+ module.
39
- #
40
40
  class List
41
- include Enumerable
42
41
 
43
- DEFAULT_LIST_PATH = File.join(File.dirname(__FILE__), "..", "..", "data", "list.txt")
42
+ DEFAULT_LIST_PATH = File.expand_path("../../data/list.txt", __dir__)
44
43
 
45
44
  # Gets the default rule list.
46
45
  #
@@ -49,39 +48,27 @@ module PublicSuffix
49
48
  #
50
49
  # @return [PublicSuffix::List]
51
50
  def self.default(**options)
52
- @default ||= parse(File.read(DEFAULT_LIST_PATH), options)
51
+ @default ||= parse(File.read(DEFAULT_LIST_PATH), **options)
53
52
  end
54
53
 
55
54
  # Sets the default rule list to +value+.
56
55
  #
57
- # @param [PublicSuffix::List] value
58
- # The new rule list.
59
- #
56
+ # @param value [PublicSuffix::List] the new list
60
57
  # @return [PublicSuffix::List]
61
58
  def self.default=(value)
62
59
  @default = value
63
60
  end
64
61
 
65
- # Sets the default rule list to +nil+.
66
- #
67
- # @return [self]
68
- def self.clear
69
- self.default = nil
70
- self
71
- end
72
-
73
- # rubocop:disable Metrics/MethodLength
74
-
75
62
  # Parse given +input+ treating the content as Public Suffix List.
76
63
  #
77
64
  # See http://publicsuffix.org/format/ for more details about input format.
78
65
  #
79
- # @param string [#each_line] The list to parse.
80
- # @param private_domain [Boolean] whether to ignore the private domains section.
81
- # @return [Array<PublicSuffix::Rule::*>]
66
+ # @param input [#each_line] the list to parse
67
+ # @param private_domains [Boolean] whether to ignore the private domains section
68
+ # @return [PublicSuffix::List]
82
69
  def self.parse(input, private_domains: true)
83
- comment_token = "//".freeze
84
- private_token = "===BEGIN PRIVATE DOMAINS===".freeze
70
+ comment_token = "//"
71
+ private_token = "===BEGIN PRIVATE DOMAINS==="
85
72
  section = nil # 1 == ICANN, 2 == PRIVATE
86
73
 
87
74
  new do |list|
@@ -96,6 +83,7 @@ module PublicSuffix
96
83
  # include private domains or stop scanner
97
84
  when line.include?(private_token)
98
85
  break if !private_domains
86
+
99
87
  section = 2
100
88
 
101
89
  # skip comments
@@ -103,53 +91,21 @@ module PublicSuffix
103
91
  next
104
92
 
105
93
  else
106
- list.add(Rule.factory(line, private: section == 2), reindex: false)
94
+ list.add(Rule.factory(line, private: section == 2))
107
95
 
108
96
  end
109
97
  end
110
98
  end
111
99
  end
112
- # rubocop:enable Metrics/MethodLength
113
-
114
-
115
- # Gets the array of rules.
116
- #
117
- # @return [Array<PublicSuffix::Rule::*>]
118
- attr_reader :rules
119
100
 
120
101
 
121
102
  # Initializes an empty {PublicSuffix::List}.
122
103
  #
123
104
  # @yield [self] Yields on self.
124
105
  # @yieldparam [PublicSuffix::List] self The newly created instance.
125
- #
126
106
  def initialize
127
- @rules = []
107
+ @rules = {}
128
108
  yield(self) if block_given?
129
- reindex!
130
- end
131
-
132
-
133
- # Creates a naive index for +@rules+. Just a hash that will tell
134
- # us where the elements of +@rules+ are relative to its first
135
- # {PublicSuffix::Rule::Base#labels} element.
136
- #
137
- # For instance if @rules[5] and @rules[4] are the only elements of the list
138
- # where Rule#labels.first is 'us' @indexes['us'] #=> [5,4], that way in
139
- # select we can avoid mapping every single rule against the candidate domain.
140
- def reindex!
141
- @indexes = {}
142
- @rules.each_with_index do |rule, index|
143
- tld = Domain.name_to_labels(rule.value).last
144
- @indexes[tld] ||= []
145
- @indexes[tld] << index
146
- end
147
- end
148
-
149
- # Gets the naive index, a hash that with the keys being the first label of
150
- # every rule pointing to an array of integers (indexes of the rules in @rules).
151
- def indexes
152
- @indexes.dup
153
109
  end
154
110
 
155
111
 
@@ -159,42 +115,36 @@ module PublicSuffix
159
115
  # {PublicSuffix::List} and each +PublicSuffix::Rule::*+
160
116
  # in list <tt>one</tt> is available in list <tt>two</tt>, in the same order.
161
117
  #
162
- # @param [PublicSuffix::List] other
163
- # The List to compare.
164
- #
118
+ # @param other [PublicSuffix::List] the List to compare
165
119
  # @return [Boolean]
166
120
  def ==(other)
167
121
  return false unless other.is_a?(List)
168
- equal?(other) || rules == other.rules
122
+
123
+ equal?(other) || @rules == other.rules
169
124
  end
170
125
  alias eql? ==
171
126
 
172
127
  # Iterates each rule in the list.
173
- def each(*args, &block)
174
- @rules.each(*args, &block)
128
+ def each(&block)
129
+ Enumerator.new do |y|
130
+ @rules.each do |key, node|
131
+ y << entry_to_rule(node, key)
132
+ end
133
+ end.each(&block)
175
134
  end
176
135
 
177
136
 
178
137
  # Adds the given object to the list and optionally refreshes the rule index.
179
138
  #
180
- # @param [PublicSuffix::Rule::*] rule
181
- # The rule to add to the list.
182
- # @param [Boolean] reindex
183
- # Set to true to recreate the rule index
184
- # after the rule has been added to the list.
185
- #
139
+ # @param rule [PublicSuffix::Rule::*] the rule to add to the list
186
140
  # @return [self]
187
- #
188
- # @see #reindex!
189
- #
190
- def add(rule, reindex: true)
191
- @rules << rule
192
- reindex! if reindex
141
+ def add(rule)
142
+ @rules[rule.value] = rule_to_entry(rule)
193
143
  self
194
144
  end
195
145
  alias << add
196
146
 
197
- # Gets the number of elements in the list.
147
+ # Gets the number of rules in the list.
198
148
  #
199
149
  # @return [Integer]
200
150
  def size
@@ -208,70 +158,65 @@ module PublicSuffix
208
158
  @rules.empty?
209
159
  end
210
160
 
211
- # Removes all elements.
161
+ # Removes all rules.
212
162
  #
213
163
  # @return [self]
214
164
  def clear
215
165
  @rules.clear
216
- reindex!
217
166
  self
218
167
  end
219
168
 
220
- # Finds and returns the most appropriate rule for the domain name.
221
- #
222
- # From the Public Suffix List documentation:
223
- #
224
- # - If a hostname matches more than one rule in the file,
225
- # the longest matching rule (the one with the most levels) will be used.
226
- # - An exclamation mark (!) at the start of a rule marks an exception to a previous wildcard rule.
227
- # An exception rule takes priority over any other matching rule.
228
- #
229
- # ## Algorithm description
169
+ # Finds and returns the rule corresponding to the longest public suffix for the hostname.
230
170
  #
231
- # 1. Match domain against all rules and take note of the matching ones.
232
- # 2. If no rules match, the prevailing rule is "*".
233
- # 3. If more than one rule matches, the prevailing rule is the one which is an exception rule.
234
- # 4. If there is no matching exception rule, the prevailing rule is the one with the most labels.
235
- # 5. If the prevailing rule is a exception rule, modify it by removing the leftmost label.
236
- # 6. The public suffix is the set of labels from the domain
237
- # which directly match the labels of the prevailing rule (joined by dots).
238
- # 7. The registered domain is the public suffix plus one additional label.
239
- #
240
- # @param name [String, #to_s] The domain name.
241
- # @param [PublicSuffix::Rule::*] default The default rule to return in case no rule matches.
171
+ # @param name [#to_s] the hostname
172
+ # @param default [PublicSuffix::Rule::*] the default rule to return in case no rule matches
242
173
  # @return [PublicSuffix::Rule::*]
243
174
  def find(name, default: default_rule, **options)
244
175
  rule = select(name, **options).inject do |l, r|
245
- return r if r.class == Rule::Exception
176
+ return r if r.instance_of?(Rule::Exception)
177
+
246
178
  l.length > r.length ? l : r
247
179
  end
248
180
  rule || default
249
181
  end
250
182
 
251
- # Selects all the rules matching given domain.
252
- #
253
- # Internally, the lookup heavily rely on the `@indexes`. The input is split into labels,
254
- # and we retriever from the index only the rules that end with the input label. After that,
255
- # a sequential scan is performed. In most cases, where the number of rules for the same label
256
- # is limited, this algorithm is efficient enough.
183
+ # Selects all the rules matching given hostame.
257
184
  #
258
- # If `ignore_private` is set to true, the algorithm will skip the rules that are flagged as private domain.
259
- # Note that the rules will still be part of the loop. If you frequently need to access lists
260
- # ignoring the private domains, you should create a list that doesn't include these domains setting the
185
+ # If `ignore_private` is set to true, the algorithm will skip the rules that are flagged as
186
+ # private domain. Note that the rules will still be part of the loop.
187
+ # If you frequently need to access lists ignoring the private domains,
188
+ # you should create a list that doesn't include these domains setting the
261
189
  # `private_domains: false` option when calling {.parse}.
262
190
  #
263
- # @param [String, #to_s] name The domain name.
264
- # @param [Boolean] ignore_private
191
+ # Note that this method is currently private, as you should not rely on it. Instead,
192
+ # the public interface is {#find}. The current internal algorithm allows to return all
193
+ # matching rules, but different data structures may not be able to do it, and instead would
194
+ # return only the match. For this reason, you should rely on {#find}.
195
+ #
196
+ # @param name [#to_s] the hostname
197
+ # @param ignore_private [Boolean]
265
198
  # @return [Array<PublicSuffix::Rule::*>]
266
199
  def select(name, ignore_private: false)
267
200
  name = name.to_s
268
- indices = (@indexes[Domain.name_to_labels(name).last] || [])
269
201
 
270
- finder = @rules.values_at(*indices).lazy
271
- finder = finder.select { |rule| rule.match?(name) }
272
- finder = finder.select { |rule| !rule.private } if ignore_private
273
- finder.to_a
202
+ parts = name.split(DOT).reverse!
203
+ index = 0
204
+ query = parts[index]
205
+ rules = []
206
+
207
+ loop do
208
+ match = @rules[query]
209
+ rules << entry_to_rule(match, query) if !match.nil? && (ignore_private == false || match.private == false)
210
+
211
+ index += 1
212
+ break if index >= parts.size
213
+
214
+ query = parts[index] + DOT + query
215
+ end
216
+
217
+ rules
274
218
  end
219
+ private :select
275
220
 
276
221
  # Gets the default rule.
277
222
  #
@@ -282,5 +227,21 @@ module PublicSuffix
282
227
  PublicSuffix::Rule.default
283
228
  end
284
229
 
230
+
231
+ protected
232
+
233
+ attr_reader :rules
234
+
235
+
236
+ private
237
+
238
+ def entry_to_rule(entry, value)
239
+ entry.type.new(value: value, length: entry.length, private: entry.private)
240
+ end
241
+
242
+ def rule_to_entry(rule)
243
+ Rule::Entry.new(rule.class, rule.length, rule.private)
244
+ end
245
+
285
246
  end
286
247
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # = Public Suffix
2
4
  #
3
5
  # Domain name parser based on the Public Suffix List.
4
6
  #
5
- # Copyright (c) 2009-2017 Simone Carletti <weppos@weppos.net>
7
+ # Copyright (c) 2009-2022 Simone Carletti <weppos@weppos.net>
6
8
 
7
9
  module PublicSuffix
8
10
 
@@ -19,6 +21,9 @@ module PublicSuffix
19
21
  #
20
22
  module Rule
21
23
 
24
+ # @api internal
25
+ Entry = Struct.new(:type, :length, :private) # rubocop:disable Lint/StructNewOverride
26
+
22
27
  # = Abstract rule class
23
28
  #
24
29
  # This represent the base class for a Rule definition
@@ -99,25 +104,36 @@ module PublicSuffix
99
104
  # @return [String] the rule definition
100
105
  attr_reader :value
101
106
 
107
+ # @return [String] the length of the rule
108
+ attr_reader :length
109
+
102
110
  # @return [Boolean] true if the rule is a private domain
103
111
  attr_reader :private
104
112
 
105
113
 
106
- # Initializes a new rule with name and value.
107
- # If value is +nil+, name also becomes the value for this rule.
114
+ # Initializes a new rule from the content.
115
+ #
116
+ # @param content [String] the content of the rule
117
+ # @param private [Boolean]
118
+ def self.build(content, private: false)
119
+ new(value: content, private: private)
120
+ end
121
+
122
+ # Initializes a new rule.
108
123
  #
109
- # @param value [String] the value of the rule
110
- def initialize(value, private: false)
124
+ # @param value [String]
125
+ # @param private [Boolean]
126
+ def initialize(value:, length: nil, private: false)
111
127
  @value = value.to_s
128
+ @length = length || @value.count(DOT) + 1
112
129
  @private = private
113
130
  end
114
131
 
115
132
  # Checks whether this rule is equal to <tt>other</tt>.
116
133
  #
117
- # @param [PublicSuffix::Rule::*] other The rule to compare
118
- # @return [Boolean]
119
- # Returns true if this rule and other are instances of the same class
120
- # and has the same value, false otherwise.
134
+ # @param other [PublicSuffix::Rule::*] The rule to compare
135
+ # @return [Boolean] true if this rule and other are instances of the same class
136
+ # and has the same value, false otherwise.
121
137
  def ==(other)
122
138
  equal?(other) || (self.class == other.class && value == other.value)
123
139
  end
@@ -137,12 +153,12 @@ module PublicSuffix
137
153
  # @see https://publicsuffix.org/list/
138
154
  #
139
155
  # @example
140
- # Rule.factory("com").match?("example.com")
156
+ # PublicSuffix::Rule.factory("com").match?("example.com")
141
157
  # # => true
142
- # Rule.factory("com").match?("example.net")
158
+ # PublicSuffix::Rule.factory("com").match?("example.net")
143
159
  # # => false
144
160
  #
145
- # @param name [String, #to_s] The domain name to check.
161
+ # @param name [String] the domain name to check
146
162
  # @return [Boolean]
147
163
  def match?(name)
148
164
  # Note: it works because of the assumption there are no
@@ -150,7 +166,7 @@ module PublicSuffix
150
166
  # we need to properly walk the input and skip parts according
151
167
  # to wildcard component.
152
168
  diff = name.chomp(value)
153
- diff.empty? || diff[-1] == "."
169
+ diff.empty? || diff.end_with?(DOT)
154
170
  end
155
171
 
156
172
  # @abstract
@@ -159,12 +175,7 @@ module PublicSuffix
159
175
  end
160
176
 
161
177
  # @abstract
162
- def length
163
- raise NotImplementedError
164
- end
165
-
166
- # @abstract
167
- # @param [String, #to_s] name The domain name to decompose
178
+ # @param domain [#to_s] The domain name to decompose
168
179
  # @return [Array<String, nil>]
169
180
  def decompose(*)
170
181
  raise NotImplementedError
@@ -184,7 +195,7 @@ module PublicSuffix
184
195
 
185
196
  # Decomposes the domain name according to rule properties.
186
197
  #
187
- # @param [String, #to_s] name The domain name to decompose
198
+ # @param domain [#to_s] The domain name to decompose
188
199
  # @return [Array<String>] The array with [trd + sld, tld].
189
200
  def decompose(domain)
190
201
  suffix = parts.join('\.')
@@ -200,27 +211,27 @@ module PublicSuffix
200
211
  @value.split(DOT)
201
212
  end
202
213
 
203
- # Gets the length of this rule for comparison,
204
- # represented by the number of dot-separated parts in the rule.
205
- #
206
- # @return [Integer] The length of the rule.
207
- def length
208
- @length ||= parts.length
209
- end
210
-
211
214
  end
212
215
 
213
216
  # Wildcard represents a wildcard rule (e.g. *.co.uk).
214
217
  class Wildcard < Base
215
218
 
216
- # Initializes a new rule from +definition+.
219
+ # Initializes a new rule from the content.
217
220
  #
218
- # The wildcard "*" is removed from the value, as it's common
219
- # for each wildcard rule.
221
+ # @param content [String] the content of the rule
222
+ # @param private [Boolean]
223
+ def self.build(content, private: false)
224
+ new(value: content.to_s[2..-1], private: private)
225
+ end
226
+
227
+ # Initializes a new rule.
220
228
  #
221
- # @param definition [String] the rule as defined in the PSL
222
- def initialize(definition, private: false)
223
- super(definition.to_s[2..-1], private: private)
229
+ # @param value [String]
230
+ # @param length [Integer]
231
+ # @param private [Boolean]
232
+ def initialize(value:, length: nil, private: false)
233
+ super(value: value, length: length, private: private)
234
+ length or @length += 1 # * counts as 1
224
235
  end
225
236
 
226
237
  # Gets the original rule definition.
@@ -232,7 +243,7 @@ module PublicSuffix
232
243
 
233
244
  # Decomposes the domain name according to rule properties.
234
245
  #
235
- # @param [String, #to_s] name The domain name to decompose
246
+ # @param domain [#to_s] The domain name to decompose
236
247
  # @return [Array<String>] The array with [trd + sld, tld].
237
248
  def decompose(domain)
238
249
  suffix = ([".*?"] + parts).join('\.')
@@ -248,28 +259,17 @@ module PublicSuffix
248
259
  @value.split(DOT)
249
260
  end
250
261
 
251
- # Gets the length of this rule for comparison,
252
- # represented by the number of dot-separated parts in the rule
253
- # plus 1 for the *.
254
- #
255
- # @return [Integer] The length of the rule.
256
- def length
257
- @length ||= parts.length + 1 # * counts as 1
258
- end
259
-
260
262
  end
261
263
 
262
264
  # Exception represents an exception rule (e.g. !parliament.uk).
263
265
  class Exception < Base
264
266
 
265
- # Initializes a new rule from +definition+.
266
- #
267
- # The bang ! is removed from the value, as it's common
268
- # for each wildcard rule.
267
+ # Initializes a new rule from the content.
269
268
  #
270
- # @param definition [String] the rule as defined in the PSL
271
- def initialize(definition, private: false)
272
- super(definition.to_s[1..-1], private: private)
269
+ # @param content [#to_s] the content of the rule
270
+ # @param private [Boolean]
271
+ def self.build(content, private: false)
272
+ new(value: content.to_s[1..-1], private: private)
273
273
  end
274
274
 
275
275
  # Gets the original rule definition.
@@ -281,7 +281,7 @@ module PublicSuffix
281
281
 
282
282
  # Decomposes the domain name according to rule properties.
283
283
  #
284
- # @param [String, #to_s] name The domain name to decompose
284
+ # @param domain [#to_s] The domain name to decompose
285
285
  # @return [Array<String>] The array with [trd + sld, tld].
286
286
  def decompose(domain)
287
287
  suffix = parts.join('\.')
@@ -302,14 +302,6 @@ module PublicSuffix
302
302
  @value.split(DOT)[1..-1]
303
303
  end
304
304
 
305
- # Gets the length of this rule for comparison,
306
- # represented by the number of dot-separated parts in the rule.
307
- #
308
- # @return [Integer] The length of the rule.
309
- def length
310
- @length ||= parts.length
311
- end
312
-
313
305
  end
314
306
 
315
307
 
@@ -329,7 +321,7 @@ module PublicSuffix
329
321
  # PublicSuffix::Rule.factory("!congresodelalengua3.ar")
330
322
  # # => #<PublicSuffix::Rule::Exception>
331
323
  #
332
- # @param [String] content The rule content.
324
+ # @param content [#to_s] the content of the rule
333
325
  # @return [PublicSuffix::Rule::*] A rule instance.
334
326
  def self.factory(content, private: false)
335
327
  case content.to_s[0, 1]
@@ -339,7 +331,7 @@ module PublicSuffix
339
331
  Exception
340
332
  else
341
333
  Normal
342
- end.new(content, private: private)
334
+ end.build(content, private: private)
343
335
  end
344
336
 
345
337
  # The default rule to use if no rule match.
@@ -1,10 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
1
4
  # = Public Suffix
2
5
  #
3
6
  # Domain name parser based on the Public Suffix List.
4
7
  #
5
- # Copyright (c) 2009-2017 Simone Carletti <weppos@weppos.net>
8
+ # Copyright (c) 2009-2022 Simone Carletti <weppos@weppos.net>
6
9
 
7
10
  module PublicSuffix
8
- # The current library version.
9
- VERSION = "2.0.5".freeze
11
+
12
+ # @return [String] The current library version.
13
+ VERSION = "4.0.7"
14
+
10
15
  end