twingly-url 7.0.1 → 7.1.1

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: 4d077940af63f0646300c3438d45f3ca66370825c5ca81df32c6d1597e156071
4
- data.tar.gz: 308a66eb3c281139a9f747a3538c3e53291f491eafa3bbdd3dbe30d1decb42d3
3
+ metadata.gz: 546297fb93a4c8c81ddeae25c27ed9cd174ac4f13df0aae03287ab94f831be6b
4
+ data.tar.gz: 10e53871ab61298b9d1bc219e12b593b0696b4be419d00155317d1d1f3b8e229
5
5
  SHA512:
6
- metadata.gz: 0b50ee3c1e3dd3beb12b85c813e36952945fca3de296257d7e0428a1de78d866932d334f13c405f784659324a69de0b2de293c07520f94f0e9d3c9a0665a3955
7
- data.tar.gz: 278af99f73d82edb9f05eed9f795fe0e0395bf45626f9fde2e09b605ce467afc90c13780020fc94b6e54f2c64daf995f061a20eddbc1fcc84d6ac1cf0a1fcd48
6
+ metadata.gz: 81a743b883a0c5f2f25628566002f646074fcd68f1473ccbd327dd82766bc8e645676cf9a3bfeeed23a7676ff4a1f4522d7b6407d93a55ad73ac3c6ddd1f9b4a
7
+ data.tar.gz: bbb6e7694dbad697653c15205ac2049c8f3ce6f90d338fe7f4634937478da54c63a1504975db4273af1fd36199896c13e232c2d9c01f908b9186f4d11f2003fe
data/README.md CHANGED
@@ -12,6 +12,11 @@ Twingly URL tools.
12
12
  * `Twingly::URL::Hasher.autopingdb_hash(url)` - SHA256 64-bit signed, native endian digest
13
13
  * `twingly/url/utilities` - Utilities to work with URLs
14
14
  * `Twingly::URL::Utilities.extract_valid_urls` - Returns Array of valid `Twingly::URL`
15
+ * `twingly/url/extended` - Extended normalization and hashing, stripping blacklisted query and matrix parameters as well as the fragment.
16
+ * `Twingly::URL::Extended.parse` - Like `Twingly::URL.parse`, with the extended normalization
17
+ * `Twingly::URL::Extended.normalize_and_calculate_urlhash(url, addressable_normalize: false)` - Returns a `HashResult` struct with `url` (original URL minus blacklisted parameters), `callable_url` (original URL canonicalized with `Addressable::URI#normalize`, minus blacklisted parameters), `normalized_url` (scheme-less), `urlhash` (`documentdb_hash` of the normalized URL, as a `String`) and `legacy_urlhash` (same digest with the scheme kept)
18
+ * `addressable_normalize: true` applies `Addressable::URI#normalize` (percent-encoding, case, default port and dot segments) before the extended normalization, so `normalized_url` and both hashes are calculated from the canonical form
19
+ * `Twingly::URL::Extended#addressable_normalized` - Returns a new instance canonicalized with `Addressable::URI#normalize`, `#normalized(addressable_normalize: true)` is the extended normalization applied to that instance
15
20
 
16
21
  ## Getting Started
17
22
 
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "twingly/url"
4
+ require "twingly/url/hasher"
5
+
6
+ module Twingly
7
+ class URL
8
+ # The purpose of the twingly-url gems is to replicate the normalization happening in our legacy .NET code.
9
+ # As both twingly-url and the legacy .NET system are used in production, we can't change these things in the
10
+ # gem just yet, which is why this extension was created here.
11
+ class Extended < Twingly::URL
12
+ # Use the same expression as we use in Zambezi: https://github.com/twingly/zambezi/blob/d997871aea199256ddc56762c975b6e961d5a2d1/lib/post_document.rb#L12
13
+ # This way the urlhashes here will be the same as the one we have in Elasticsearch
14
+ PROTOCOL_EXPRESSION = /\Ahttps?:/i
15
+
16
+ HashResult = Struct.new(:url, :callable_url, :normalized_url, :urlhash, :legacy_urlhash)
17
+
18
+ # These parameters are just used for tracking, and should therefore not be included in the normalized URL
19
+ # See https://en.wikipedia.org/wiki/UTM_parameters
20
+ BLACKLISTED_QUERY_PARAMETERS = %w[
21
+ utm_source
22
+ utm_medium
23
+ utm_campaign
24
+ utm_term
25
+ utm_content
26
+ b_source
27
+ b_medium
28
+ b_campaign
29
+ session
30
+ PHPSESSID
31
+ cb
32
+ ].freeze
33
+
34
+ BLACKLISTED_MATRIX_PARAMETERS = %w[
35
+ jsessionid
36
+ jsessionid_jboss
37
+ JSESSIONID_B2BCH
38
+ JSESSIONID_kookbnagWEB
39
+ ].freeze
40
+
41
+ # Precompiled regex matching any blacklisted matrix parameter
42
+ # Matches: ;paramname=value where value ends at ; / ? or #
43
+ MATRIX_PARAM_REGEX = %r{;(?:#{BLACKLISTED_MATRIX_PARAMETERS.map { |p| Regexp.escape(p) }.join('|')})=[^;/?#]*}i
44
+
45
+ # Taken from twingly-url, with the addition of normalizing the query and fragment components
46
+ # See https://github.com/twingly/twingly-url/blob/e20f5fce077d93e89ef8520961be453c90cfec8c/lib/twingly/url.rb#L185-L193
47
+ def normalized(addressable_normalize: false) # rubocop:disable Metrics/AbcSize
48
+ return addressable_normalized.normalized if addressable_normalize
49
+
50
+ normalized_url = addressable_uri.dup
51
+
52
+ normalized_url.scheme = normalized_scheme
53
+ normalized_url.host = normalized_host
54
+ normalized_url.path = normalized_path
55
+ normalized_url.query_values = normalized_query
56
+ normalized_url.fragment = normalized_fragment
57
+
58
+ # This is a bit ugly, remove when Twingly::URL is updated to
59
+ # to handle this.
60
+ public_suffix_domain = get_public_suffix_domain(normalized_url.host)
61
+ self.class.send(:new, normalized_url, public_suffix_domain)
62
+ end
63
+
64
+ def addressable_normalized
65
+ addressable_normalized_url = normalize_addressable_url
66
+ public_suffix_domain = get_public_suffix_domain(addressable_normalized_url.host)
67
+ self.class.send(:new, addressable_normalized_url, public_suffix_domain)
68
+ end
69
+
70
+ def original_url_without_blacklisted_parameters
71
+ url = addressable_uri.dup
72
+ url.path = without_blacklisted_matrix_parameters(addressable_uri.path)
73
+ url.query_values = without_blacklisted_query_parameters(addressable_uri.query_values)
74
+ url.to_s
75
+ end
76
+
77
+ def query_values
78
+ addressable_uri.query_values
79
+ end
80
+
81
+ def query
82
+ addressable_uri.query
83
+ end
84
+
85
+ private
86
+
87
+ # copied the way public_suffix_domain is calculated
88
+ # from Twingly::URL to be able calculate it without calling the parse method.
89
+ def get_public_suffix_domain(host)
90
+ public_suffix_domain = PublicSuffix.parse(host, list: CUSTOM_PSL, default_rule: nil)
91
+ raise Twingly::URL::Error::ParseError if public_suffix_domain.nil?
92
+ raise Twingly::URL::Error::ParseError if public_suffix_domain.sld.nil?
93
+
94
+ public_suffix_domain
95
+ end
96
+
97
+ def normalized_path
98
+ addressable_uri.path = without_blacklisted_matrix_parameters(addressable_uri.path)
99
+
100
+ super
101
+ end
102
+
103
+ def normalized_query
104
+ without_blacklisted_query_parameters(addressable_uri.query_values)
105
+ end
106
+
107
+ def normalize_addressable_url
108
+ addressable_uri.normalize
109
+ end
110
+
111
+ def without_blacklisted_query_parameters(query_values)
112
+ return if query_values.nil?
113
+
114
+ values_without_blacklisted_params = query_values.except(*BLACKLISTED_QUERY_PARAMETERS)
115
+
116
+ return if values_without_blacklisted_params.empty?
117
+
118
+ values_without_blacklisted_params
119
+ end
120
+
121
+ def without_blacklisted_matrix_parameters(path)
122
+ # No need to run gsub if there are no matrix parameters
123
+ return path unless path.include?(";")
124
+
125
+ path.gsub(MATRIX_PARAM_REGEX, "")
126
+ end
127
+
128
+ def normalized_fragment
129
+ nil
130
+ end
131
+
132
+ def self.normalize_and_calculate_urlhash(url, addressable_normalize: false)
133
+ return empty_result if url.to_s.strip.empty?
134
+
135
+ twingly_url = if url.is_a?(Extended)
136
+ url
137
+ else
138
+ Extended.parse(url)
139
+ end
140
+
141
+ return empty_result unless twingly_url.valid?
142
+
143
+ original_url = twingly_url.original_url_without_blacklisted_parameters
144
+ addressable_normalized = twingly_url.addressable_normalized
145
+ callable_url = addressable_normalized.original_url_without_blacklisted_parameters
146
+
147
+ twingly_url = addressable_normalized if addressable_normalize
148
+
149
+ normalized_url = twingly_url.normalized.to_s
150
+ normalized_url_without_scheme = remove_scheme(normalized_url)
151
+ urlhash = calculate_urlhash(normalized_url_without_scheme)
152
+ legacy_urlhash = calculate_urlhash(normalized_url)
153
+
154
+ HashResult.new(original_url, callable_url, normalized_url_without_scheme, urlhash, legacy_urlhash)
155
+ end
156
+
157
+ def self.empty_result
158
+ HashResult.new(nil, nil, nil, nil, nil)
159
+ end
160
+
161
+ def self.remove_scheme(url)
162
+ url.to_s.sub(PROTOCOL_EXPRESSION, "")
163
+ end
164
+
165
+ def self.calculate_urlhash(url)
166
+ Twingly::URL::Hasher.documentdb_hash(url).to_s
167
+ end
168
+
169
+ private_class_method :empty_result
170
+ private_class_method :calculate_urlhash
171
+ end
172
+ end
173
+ end
@@ -12,7 +12,7 @@ module Twingly
12
12
  ""
13
13
  end
14
14
 
15
- def normalized
15
+ def normalized(**)
16
16
  self
17
17
  end
18
18
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Twingly
4
4
  class URL
5
- VERSION = "7.0.1"
5
+ VERSION = "7.1.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twingly-url
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.1
4
+ version: 7.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Twingly AB
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-01 00:00:00.000000000 Z
11
+ date: 2026-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 3.0.1
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '6.0'
36
+ version: '8'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,21 +43,21 @@ dependencies:
43
43
  version: 3.0.1
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '6.0'
46
+ version: '8'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rake
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '12'
53
+ version: '0'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '12'
60
+ version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rspec
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -97,6 +97,7 @@ files:
97
97
  - lib/twingly/public_suffix_list.rb
98
98
  - lib/twingly/url.rb
99
99
  - lib/twingly/url/error.rb
100
+ - lib/twingly/url/extended.rb
100
101
  - lib/twingly/url/hasher.rb
101
102
  - lib/twingly/url/null_url.rb
102
103
  - lib/twingly/url/utilities.rb
@@ -113,14 +114,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
114
  requirements:
114
115
  - - ">="
115
116
  - !ruby/object:Gem::Version
116
- version: '2.6'
117
+ version: '3.0'
117
118
  required_rubygems_version: !ruby/object:Gem::Requirement
118
119
  requirements:
119
120
  - - ">="
120
121
  - !ruby/object:Gem::Version
121
122
  version: '0'
122
123
  requirements: []
123
- rubygems_version: 3.3.7
124
+ rubygems_version: 3.5.22
124
125
  signing_key:
125
126
  specification_version: 4
126
127
  summary: Ruby library for URL handling