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 +4 -4
- data/README.md +5 -0
- data/lib/twingly/url/extended.rb +173 -0
- data/lib/twingly/url/null_url.rb +1 -1
- data/lib/twingly/version.rb +1 -1
- metadata +11 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 546297fb93a4c8c81ddeae25c27ed9cd174ac4f13df0aae03287ab94f831be6b
|
|
4
|
+
data.tar.gz: 10e53871ab61298b9d1bc219e12b593b0696b4be419d00155317d1d1f3b8e229
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/twingly/url/null_url.rb
CHANGED
data/lib/twingly/version.rb
CHANGED
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.
|
|
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:
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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.
|
|
124
|
+
rubygems_version: 3.5.22
|
|
124
125
|
signing_key:
|
|
125
126
|
specification_version: 4
|
|
126
127
|
summary: Ruby library for URL handling
|