gman 8.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05dbe9c50c24586c2c071eccee35d04c016224fc909aea2699e09701bf0ac56b
4
- data.tar.gz: f6aab93cdcec8b9c6330dfaead40263272a2cc48a075ec6549d2378c6f13c12b
3
+ metadata.gz: 29b30e2a1e50e8e0f8f7d5ca50a6c2a8671b1e69a1d8213415bd6d6a70a35af9
4
+ data.tar.gz: 07b8f53008e8699d2f67ef162460540196cd90c3e89e1ed3a4054a185b0a325f
5
5
  SHA512:
6
- metadata.gz: ce3099efeaef338677d10bcfa222c7be2d10405682c9bcd0dbc33d887e6e0050eecf31bbdac2a5b703431e1516534a18f2247cf921d42931e6c12ace08171f57
7
- data.tar.gz: dfe56fb1e9a5c73710fddc489c6715cd8db0835b5725bc9814c497845d98cef52ed45b9af37464fd679fa7cc1493c7e057656e5b4b3ae8497d9734f439775277
6
+ metadata.gz: 25c6626204645cfbc03b7875ddb5a7eb51d18d56b5f70af1f993def0ece5841a36b27e151c2e517eee4060059627f648f1883c45216d50b68ee1f749ea4999de
7
+ data.tar.gz: 5ca3b6cba72c5bf9238abaa014c61f9caaeaa10bcc4a0f147b27f92fb5fa96a2fff835fbd2b479764429d6ce6e0e1bd15937b8f371c8483dbb63724ebbafb032
@@ -4,7 +4,7 @@ class Gman
4
4
  class DomainList
5
5
  COMMENT_REGEX = %r{//[/\s]*(.*)$}i
6
6
 
7
- attr_writer :data, :path, :contents
7
+ attr_writer :path, :contents
8
8
 
9
9
  class << self
10
10
  # The current, government domain list
@@ -41,6 +41,12 @@ class Gman
41
41
  end
42
42
  end
43
43
 
44
+ # Replaces the list data, a hash of group => domains
45
+ def data=(data)
46
+ @data = data
47
+ clear_cache
48
+ end
49
+
44
50
  # Returns the parsed contents of the domain list as a hash
45
51
  # in the form for group => domains
46
52
  def data
@@ -73,7 +79,7 @@ class Gman
73
79
 
74
80
  # Return an array of strings representing all domains on the list
75
81
  def domains
76
- data.values.flatten.compact.sort.uniq
82
+ @domains ||= data.values.flatten.compact.sort.uniq.freeze
77
83
  end
78
84
 
79
85
  # Return the total number of domains in the list
@@ -84,11 +90,12 @@ class Gman
84
90
  # Alphabetize groups and domains within each group
85
91
  # We need to ensure exceptions appear after their coresponding rules
86
92
  def alphabetize
87
- @data = data.sort_by { |k, _v| k.downcase }.to_h
88
- @data.map do |_group, domains|
93
+ self.data = data.sort_by { |k, _v| k.downcase }.to_h
94
+ @data.each_value do |domains|
89
95
  domains.sort! { |a, b| sort_with_exceptions(a, b) }
90
96
  domains.uniq!
91
97
  end
98
+ clear_cache
92
99
  end
93
100
 
94
101
  # Write the domain list to disk
@@ -112,14 +119,60 @@ class Gman
112
119
  end
113
120
  alias to_public_suffix to_s
114
121
 
115
- # Given a domain, find any domain on the list that includes that domain
116
- # E.g., `fcc.gov` would be the parent of `data.fcc.gov`
122
+ # Returns the group a list entry belongs to, e.g., "US Federal" for "fcc.gov"
123
+ # The entry must match the list verbatim. If an entry appears in more than
124
+ # one group, the first group wins.
125
+ def group_for(entry)
126
+ group_index[entry]
127
+ end
128
+
129
+ # Given a domain, find the list entry that covers it, using public suffix
130
+ # rules. E.g., `fcc.gov` would be the parent of `data.fcc.gov`, and
131
+ # `*.foo.gov` the parent of `bar.foo.gov`.
132
+ #
133
+ # If more than one entry matches, the longest wins. A domain under an
134
+ # exception rule (e.g., `!mail.gov.ua`) has no parent. A domain that is
135
+ # itself on the list returns the entry that covers its parent, if any.
136
+ #
137
+ # Returns the list entry as written (e.g., `*.foo.gov`), or nil
117
138
  def parent_domain(domain)
118
- domains.find { |c| domain =~ /\.#{Regexp.escape(c)}$/ }
139
+ rule = parent_list.find(domain, default: nil)
140
+ if rule && !rule.is_a?(PublicSuffix::Rule::Exception) && rule.value == domain
141
+ ancestor = domain.split('.', 2)[1]
142
+ rule = ancestor && parent_list.find(ancestor, default: nil)
143
+ end
144
+ return if rule.nil? || rule.is_a?(PublicSuffix::Rule::Exception)
145
+
146
+ rule.rule
147
+ end
148
+
149
+ # Returns the exception rule (e.g., `!mail.gov.ua`) that excludes the given
150
+ # domain from the list, or nil if none does
151
+ def exception_for(domain)
152
+ rule = parent_list.find(domain, default: nil)
153
+ rule.rule if rule.is_a?(PublicSuffix::Rule::Exception)
119
154
  end
120
155
 
121
156
  private
122
157
 
158
+ # Clear values derived from data. Call after mutating data in place.
159
+ def clear_cache
160
+ @domains = @parent_list = @group_index = nil
161
+ end
162
+
163
+ # PublicSuffix::List of the current data, for parent lookups. Unlike
164
+ # #public_suffix_list, it is rebuilt when the data changes.
165
+ def parent_list
166
+ @parent_list ||= PublicSuffix::List.parse(domains.join("\n"))
167
+ end
168
+
169
+ # Hash of list entry => group, for constant-time group lookups
170
+ def group_index
171
+ @group_index ||= data.each_with_object({}) do |(group, domains), index|
172
+ domains.each { |domain| index[domain] ||= group }
173
+ end
174
+ end
175
+
123
176
  # Parse a public-suffix formatted string into a hash of groups => [domains]
124
177
  def string_to_hash(string)
125
178
  return unless string
@@ -110,12 +110,8 @@ class Gman
110
110
  def list_category
111
111
  return @list_category if defined?(@list_category)
112
112
 
113
- match = Gman.list.public_suffix_list.find(domain.to_s)
114
- return @list_category = nil unless match
115
-
116
- regex = %r{// ([^\n]+)\n?[^/]*\n#{Regexp.escape(match.value)}\n}im
117
- matches = Gman.list.contents.match(regex)
118
- @list_category = matches ? matches[1] : nil
113
+ match = Gman.list.public_suffix_list.find(domain.to_s, default: nil)
114
+ @list_category = match && Gman.list.group_for(match.value)
119
115
  end
120
116
 
121
117
  def matches
data/lib/gman/importer.rb CHANGED
@@ -6,6 +6,7 @@ require 'yaml'
6
6
  require 'open-uri'
7
7
  require 'resolv'
8
8
  require 'logger'
9
+ require 'addressable/uri'
9
10
  require 'swot'
10
11
  require_relative '../gman'
11
12
  require_relative 'domain_list'
@@ -71,7 +72,7 @@ class Gman
71
72
 
72
73
  def normalize_domain(domain)
73
74
  domain = Gman.new(domain).to_s
74
- domain.to_s.downcase.strip.gsub(/^www./, '').gsub(%r{/$}, '')
75
+ domain.to_s.downcase.strip.delete_prefix('www.').delete_suffix('/')
75
76
  end
76
77
 
77
78
  def valid_domain?(domain, options = {})
@@ -152,34 +153,33 @@ class Gman
152
153
 
153
154
  if current.domains.include?(domain)
154
155
  reject(domain, 'duplicate')
155
- else
156
- parent = current.parent_domain(domain)
156
+ elsif (parent = current.parent_domain(domain))
157
157
  reject(domain, "subdomain of #{parent}")
158
+ else
159
+ reject(domain, "excluded by #{current.exception_for(domain)}")
158
160
  end
159
161
  end
160
162
 
163
+ # A domain is a dupe if it's on the list, covered by a list entry, or
164
+ # excluded by an exception rule
161
165
  def dupe?(domain)
162
- current.domains.include?(domain) || current.parent_domain(domain)
166
+ current.domains.include?(domain) || current.parent_domain(domain) || current.exception_for(domain)
163
167
  end
164
168
 
165
169
  def normalize_domains!
166
- domain_list.to_h.each_value do |domains|
167
- domains.map! { |domain| normalize_domain(domain) }
168
- domains.uniq!
170
+ domain_list.data = domain_list.data.transform_values do |domains|
171
+ domains.map { |domain| normalize_domain(domain) }.uniq
169
172
  end
170
173
  end
171
174
 
172
175
  def ensure_validity!(options = {})
173
- domain_list.data.each_value do |domains|
174
- domains.select! { |domain| valid_domain?(domain, options) }
176
+ domain_list.data = domain_list.data.transform_values do |domains|
177
+ domains.select { |domain| valid_domain?(domain, options) }
175
178
  end
176
179
  end
177
180
 
178
181
  def add_to_current
179
- domain_list.data.each do |group, domains|
180
- current.data[group] ||= []
181
- current.data[group].concat domains
182
- end
182
+ current.data = current.data.merge(domain_list.data) { |_group, existing, added| existing + added }
183
183
  current.write
184
184
  end
185
185
 
data/lib/gman/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Gman
4
- VERSION = '8.0.0'
4
+ VERSION = '8.1.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gman
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.0
4
+ version: 8.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Balter