url_canonicalize 1.0.0 → 2.0.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 +4 -4
- data/CHANGELOG.md +148 -113
- data/README.md +234 -9
- data/SECURITY.md +25 -0
- data/lib/url_canonicalize/bounded_body.rb +23 -0
- data/lib/url_canonicalize/client.rb +21 -0
- data/lib/{monkey_patches/addressable/uri.rb → url_canonicalize/core_ext/addressable.rb} +4 -2
- data/lib/{monkey_patches → url_canonicalize/core_ext}/string.rb +2 -2
- data/lib/{monkey_patches → url_canonicalize/core_ext}/uri.rb +4 -2
- data/lib/url_canonicalize/core_ext.rb +6 -0
- data/lib/url_canonicalize/destination.rb +120 -0
- data/lib/url_canonicalize/exception.rb +3 -0
- data/lib/url_canonicalize/http.rb +86 -63
- data/lib/url_canonicalize/link_header.rb +34 -0
- data/lib/url_canonicalize/media_type.rb +23 -0
- data/lib/url_canonicalize/options.rb +112 -0
- data/lib/url_canonicalize/request.rb +86 -77
- data/lib/url_canonicalize/response.rb +7 -4
- data/lib/url_canonicalize/result.rb +28 -0
- data/lib/url_canonicalize/transport.rb +47 -0
- data/lib/url_canonicalize/uri.rb +26 -13
- data/lib/url_canonicalize/version.rb +1 -1
- data/lib/url_canonicalize.rb +13 -9
- metadata +22 -25
- data/.codeclimate.yml +0 -25
- data/.github/workflows/codeql-analysis.yml +0 -70
- data/.gitignore +0 -53
- data/.hound.yml +0 -4
- data/.rspec +0 -5
- data/.rubocop.yml +0 -61
- data/.ruby-gemset +0 -1
- data/.travis.yml +0 -8
- data/Gemfile +0 -22
- data/Gemfile.lock +0 -110
- data/Guardfile +0 -18
- data/Rakefile +0 -3
- data/circle.yml +0 -29
- data/renovate.json +0 -6
- data/url_canonicalize.gemspec +0 -29
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
module URLCanonicalize
|
|
4
4
|
# Make an HTTP request
|
|
5
5
|
class Request
|
|
6
|
+
NETWORK_EXCEPTIONS = [
|
|
7
|
+
EOFError,
|
|
8
|
+
Errno::ECONNREFUSED,
|
|
9
|
+
Errno::ECONNRESET,
|
|
10
|
+
Errno::EHOSTUNREACH,
|
|
11
|
+
Errno::EINVAL,
|
|
12
|
+
Errno::ENETUNREACH,
|
|
13
|
+
Errno::ETIMEDOUT,
|
|
14
|
+
Net::OpenTimeout,
|
|
15
|
+
Net::ReadTimeout,
|
|
16
|
+
OpenSSL::SSL::SSLError,
|
|
17
|
+
SocketError,
|
|
18
|
+
Timeout::Error,
|
|
19
|
+
Zlib::BufError,
|
|
20
|
+
Zlib::DataError
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
# Unsuccessful responses to a HEAD request that trigger a retry with GET
|
|
24
|
+
HEAD_FALLBACK_RESPONSES = [
|
|
25
|
+
Net::HTTPForbidden,
|
|
26
|
+
Net::HTTPMethodNotAllowed,
|
|
27
|
+
Net::HTTPNotImplemented
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
6
30
|
def fetch
|
|
7
31
|
handle_response
|
|
8
32
|
end
|
|
@@ -11,15 +35,13 @@ module URLCanonicalize
|
|
|
11
35
|
@location ||= relative_to_absolute(response['location'])
|
|
12
36
|
end
|
|
13
37
|
|
|
38
|
+
# Point this request at a new URI, discarding all state from the previous
|
|
39
|
+
# response so nothing leaks between hops
|
|
14
40
|
def with_uri(uri)
|
|
15
41
|
@uri = uri
|
|
16
42
|
|
|
17
43
|
@url = nil
|
|
18
|
-
|
|
19
|
-
@request = nil
|
|
20
|
-
@response = nil
|
|
21
|
-
@location = nil
|
|
22
|
-
@html = nil
|
|
44
|
+
self.http_method = @default_http_method
|
|
23
45
|
|
|
24
46
|
self
|
|
25
47
|
end
|
|
@@ -31,6 +53,7 @@ module URLCanonicalize
|
|
|
31
53
|
def initialize(http, http_method = :head)
|
|
32
54
|
@http = http
|
|
33
55
|
@http_method = http_method
|
|
56
|
+
@default_http_method = http_method
|
|
34
57
|
end
|
|
35
58
|
|
|
36
59
|
def response
|
|
@@ -55,41 +78,51 @@ module URLCanonicalize
|
|
|
55
78
|
when Net::HTTPRedirection
|
|
56
79
|
handle_redirection
|
|
57
80
|
else
|
|
58
|
-
|
|
81
|
+
handle_unsuccessful
|
|
59
82
|
end
|
|
60
83
|
rescue *NETWORK_EXCEPTIONS => e
|
|
61
|
-
handle_failure(e.class, e.message)
|
|
84
|
+
handle_failure(e.class, e.message, e)
|
|
62
85
|
end
|
|
63
86
|
|
|
64
87
|
def handle_success
|
|
65
|
-
@canonical_url =
|
|
88
|
+
@canonical_url = normalized(relative_to_absolute(URLCanonicalize::LinkHeader.canonical(response)))
|
|
66
89
|
|
|
67
90
|
return enhanced_response if canonical_url || http_method == :get
|
|
68
91
|
|
|
92
|
+
fallback_to_get
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Some servers reject HEAD requests outright, so any HEAD request rejected
|
|
96
|
+
# with one of these statuses is retried as a GET before giving up
|
|
97
|
+
def handle_unsuccessful
|
|
98
|
+
return fallback_to_get if http_method == :head && HEAD_FALLBACK_RESPONSES.any? { |klass| response.is_a?(klass) }
|
|
99
|
+
|
|
100
|
+
handle_failure
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def fallback_to_get
|
|
69
104
|
self.http_method = :get
|
|
70
105
|
fetch
|
|
71
106
|
end
|
|
72
107
|
|
|
108
|
+
# Follow any redirection that carries a usable Location header, whether the
|
|
109
|
+
# redirect is temporary or permanent. A redirection without one cannot be
|
|
110
|
+
# followed, so it is reported as a failure.
|
|
73
111
|
def handle_redirection
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if location
|
|
79
|
-
URLCanonicalize::Response::Redirect.new(location)
|
|
80
|
-
else
|
|
81
|
-
URLCanonicalize::Response::Failure.new(::URI::InvalidURIError, response['location'])
|
|
82
|
-
end
|
|
112
|
+
if location
|
|
113
|
+
URLCanonicalize::Response::Redirect.new(location)
|
|
114
|
+
else
|
|
115
|
+
URLCanonicalize::Response::Failure.new(::URI::InvalidURIError, response['location'])
|
|
83
116
|
end
|
|
84
117
|
end
|
|
85
118
|
|
|
86
|
-
def handle_failure(klass = response.class, message = response.message)
|
|
87
|
-
URLCanonicalize::Response::Failure.new(klass, message)
|
|
119
|
+
def handle_failure(klass = response.class, message = response.message, error = nil)
|
|
120
|
+
URLCanonicalize::Response::Failure.new(klass, message, error)
|
|
88
121
|
end
|
|
89
122
|
|
|
90
123
|
def enhanced_response
|
|
91
124
|
if canonical_url
|
|
92
|
-
|
|
125
|
+
logger&.debug { "canonical_url: #{canonical_url}" }
|
|
93
126
|
response_plus = URLCanonicalize::Response::Success.new(canonical_url, response, html)
|
|
94
127
|
URLCanonicalize::Response::CanonicalFound.new(canonical_url, response_plus)
|
|
95
128
|
else
|
|
@@ -98,19 +131,32 @@ module URLCanonicalize
|
|
|
98
131
|
end
|
|
99
132
|
|
|
100
133
|
def html
|
|
101
|
-
@html ||= Nokogiri::HTML
|
|
134
|
+
@html ||= Nokogiri::HTML(response.body) if URLCanonicalize::MediaType.html?(response)
|
|
102
135
|
end
|
|
103
136
|
|
|
104
137
|
def canonical_url
|
|
105
|
-
@canonical_url ||= relative_to_absolute(
|
|
138
|
+
@canonical_url ||= normalized(relative_to_absolute(canonical_url_element&.[]('href'), document_base_url))
|
|
106
139
|
end
|
|
107
140
|
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
# Declared canonical URLs are returned to callers, so they get the same
|
|
142
|
+
# syntactic normalization as every requested URL
|
|
143
|
+
def normalized(url)
|
|
144
|
+
URLCanonicalize::URI.normalize(url) if url
|
|
110
145
|
end
|
|
111
146
|
|
|
147
|
+
# The first HTML link element whose rel tokens include "canonical", however
|
|
148
|
+
# the tokens are cased, provided it has a usable href
|
|
112
149
|
def canonical_url_element
|
|
113
|
-
@canonical_url_element ||= html
|
|
150
|
+
@canonical_url_element ||= html&.css('head link[rel]')&.find do |element|
|
|
151
|
+
element['rel'].split.any? { |token| token.casecmp?('canonical') } && !element['href'].to_s.strip.empty?
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# HTML canonical links resolve against the document base URL, not the
|
|
156
|
+
# request URL, when the document declares one
|
|
157
|
+
def document_base_url
|
|
158
|
+
base_href = html&.at_css('head base[href]')&.[]('href')
|
|
159
|
+
relative_to_absolute(base_href) || url
|
|
114
160
|
end
|
|
115
161
|
|
|
116
162
|
def uri
|
|
@@ -121,10 +167,6 @@ module URLCanonicalize
|
|
|
121
167
|
@url ||= uri.to_s
|
|
122
168
|
end
|
|
123
169
|
|
|
124
|
-
def host
|
|
125
|
-
@host ||= uri.host
|
|
126
|
-
end
|
|
127
|
-
|
|
128
170
|
def request_for_method
|
|
129
171
|
r = base_request
|
|
130
172
|
headers.each { |header_key, header_value| r[header_key] = header_value }
|
|
@@ -132,8 +174,6 @@ module URLCanonicalize
|
|
|
132
174
|
end
|
|
133
175
|
|
|
134
176
|
def base_request
|
|
135
|
-
check_http_method
|
|
136
|
-
|
|
137
177
|
case http_method
|
|
138
178
|
when :head
|
|
139
179
|
Net::HTTP::Head.new uri
|
|
@@ -145,12 +185,7 @@ module URLCanonicalize
|
|
|
145
185
|
end
|
|
146
186
|
|
|
147
187
|
def headers
|
|
148
|
-
|
|
149
|
-
'Accept-Language' => 'en-US,en;q=0.8',
|
|
150
|
-
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; WOW64) ' \
|
|
151
|
-
'AppleWebKit/537.36 (KHTML, like Gecko) ' \
|
|
152
|
-
'Chrome/51.0.2704.103 Safari/537.36'
|
|
153
|
-
}
|
|
188
|
+
http.options[:headers]
|
|
154
189
|
end
|
|
155
190
|
|
|
156
191
|
def http_method=(value)
|
|
@@ -159,54 +194,28 @@ module URLCanonicalize
|
|
|
159
194
|
@response = nil
|
|
160
195
|
@location = nil
|
|
161
196
|
@html = nil
|
|
197
|
+
@canonical_url = nil
|
|
198
|
+
@canonical_url_element = nil
|
|
162
199
|
end
|
|
163
200
|
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
def
|
|
168
|
-
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def relative_to_absolute(partial_url)
|
|
172
|
-
return unless partial_url
|
|
201
|
+
# Resolve absolute, protocol-relative, root-relative, path-relative and
|
|
202
|
+
# query-only references against the base URL per RFC 3986. Only http(s)
|
|
203
|
+
# results are usable
|
|
204
|
+
def relative_to_absolute(reference, base = url)
|
|
205
|
+
return unless reference
|
|
173
206
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
partial_url # It's already absolute
|
|
178
|
-
else
|
|
179
|
-
::URI.join((uri || url), partial_url).to_s
|
|
180
|
-
end
|
|
181
|
-
rescue ::URI::InvalidURIError
|
|
207
|
+
absolute = ::URI.join(base, reference.strip)
|
|
208
|
+
absolute.to_s if absolute.is_a?(::URI::HTTP)
|
|
209
|
+
rescue ::URI::InvalidURIError, ArgumentError, Encoding::CompatibilityError
|
|
182
210
|
nil
|
|
183
211
|
end
|
|
184
212
|
|
|
185
213
|
def log_response
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
puts "#{http_method.upcase} #{url} #{response.code} #{response.message}"
|
|
189
|
-
|
|
190
|
-
return unless ENV['DEBUG'].casecmp('headers')
|
|
191
|
-
|
|
192
|
-
response.each { |k, v| puts " #{k}:\t#{v}" }
|
|
214
|
+
logger&.debug { "#{http_method.upcase} #{url} #{response.code} #{response.message}" }
|
|
193
215
|
end
|
|
194
216
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
Errno::ECONNRESET,
|
|
199
|
-
Errno::EHOSTUNREACH,
|
|
200
|
-
Errno::EINVAL,
|
|
201
|
-
Errno::ENETUNREACH,
|
|
202
|
-
Errno::ETIMEDOUT,
|
|
203
|
-
Net::OpenTimeout,
|
|
204
|
-
Net::ReadTimeout,
|
|
205
|
-
OpenSSL::SSL::SSLError,
|
|
206
|
-
SocketError,
|
|
207
|
-
Timeout::Error,
|
|
208
|
-
Zlib::BufError,
|
|
209
|
-
Zlib::DataError
|
|
210
|
-
].freeze
|
|
217
|
+
def logger
|
|
218
|
+
http.options[:logger]
|
|
219
|
+
end
|
|
211
220
|
end
|
|
212
221
|
end
|
|
@@ -13,7 +13,8 @@ module URLCanonicalize
|
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
# A redirect to another URL
|
|
17
|
+
class Redirect < Generic; end
|
|
17
18
|
|
|
18
19
|
# Add HTML to a successful response
|
|
19
20
|
class Success < Generic
|
|
@@ -44,15 +45,17 @@ module URLCanonicalize
|
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
# It barfed
|
|
48
|
+
# It barfed. When the failure came from a rescued exception, `error` holds
|
|
49
|
+
# the original exception so callers can see the real cause
|
|
48
50
|
class Failure
|
|
49
|
-
attr_reader :failure_class, :message
|
|
51
|
+
attr_reader :failure_class, :message, :error
|
|
50
52
|
|
|
51
53
|
private
|
|
52
54
|
|
|
53
|
-
def initialize(failure_class, message)
|
|
55
|
+
def initialize(failure_class, message, error = nil)
|
|
54
56
|
@failure_class = failure_class
|
|
55
57
|
@message = message
|
|
58
|
+
@error = error
|
|
56
59
|
end
|
|
57
60
|
end
|
|
58
61
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module URLCanonicalize
|
|
4
|
+
# The outcome of one canonicalization: the canonical URL, the response
|
|
5
|
+
# that confirmed it, and the chain of hops that led there. Immutable
|
|
6
|
+
class Result
|
|
7
|
+
# One step in the request chain: the URL that was requested and how it
|
|
8
|
+
# was discovered (:initial, :redirect or :canonical_link)
|
|
9
|
+
Hop = Struct.new(:url, :via, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
attr_reader :url, :response, :html, :chain, :source
|
|
12
|
+
|
|
13
|
+
def xml
|
|
14
|
+
Nokogiri::XML(response.body)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def initialize(url:, response:, html:, chain:, source:)
|
|
20
|
+
@url = url
|
|
21
|
+
@response = response
|
|
22
|
+
@html = html
|
|
23
|
+
@chain = chain.map(&:freeze).freeze
|
|
24
|
+
@source = source
|
|
25
|
+
freeze
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module URLCanonicalize
|
|
4
|
+
# Builds the connection for a destination, resolving the host through the
|
|
5
|
+
# fetch security policy. Injectable via the :transport option: anything
|
|
6
|
+
# responding to call(uri, options) and returning a Net::HTTP-compatible
|
|
7
|
+
# connection can replace it
|
|
8
|
+
class Transport
|
|
9
|
+
# Resolved lazily so the destination policy can be stubbed in tests
|
|
10
|
+
DEFAULT_RESOLVER = lambda do |uri, options|
|
|
11
|
+
URLCanonicalize::Destination.resolve(uri, options)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(resolver: DEFAULT_RESOLVER)
|
|
15
|
+
@resolver = resolver
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(uri, options)
|
|
20
|
+
http = Net::HTTP.new(uri.host, uri.port, nil)
|
|
21
|
+
|
|
22
|
+
http.ipaddr = @resolver.call(uri, options)
|
|
23
|
+
configure_timeouts(http, options)
|
|
24
|
+
configure_tls(http, uri)
|
|
25
|
+
|
|
26
|
+
http
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def configure_timeouts(http, options)
|
|
32
|
+
http.open_timeout = options[:open_timeout]
|
|
33
|
+
http.read_timeout = options[:read_timeout]
|
|
34
|
+
http.write_timeout = options[:write_timeout]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def configure_tls(http, uri)
|
|
38
|
+
if uri.scheme == 'https'
|
|
39
|
+
http.use_ssl = true # Can generate exception
|
|
40
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
41
|
+
http.verify_hostname = true
|
|
42
|
+
else
|
|
43
|
+
http.use_ssl = false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/url_canonicalize/uri.rb
CHANGED
|
@@ -1,36 +1,49 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module URLCanonicalize
|
|
4
|
-
#
|
|
4
|
+
# Parses and normalizes URLs with local exception handling
|
|
5
5
|
class URI
|
|
6
6
|
class << self
|
|
7
|
+
VALID_CLASSES = [::URI::HTTP, ::URI::HTTPS].freeze
|
|
8
|
+
|
|
9
|
+
# A scheme is recognized only as a leading scheme: token; a colon
|
|
10
|
+
# anywhere else no longer suppresses the default scheme
|
|
11
|
+
SCHEME = /\A[A-Za-z][A-Za-z0-9+.-]*:/
|
|
12
|
+
|
|
7
13
|
def parse(url)
|
|
8
|
-
uri = ::URI.parse
|
|
9
|
-
|
|
14
|
+
uri = ::URI.parse normalize(url)
|
|
15
|
+
validate! uri
|
|
16
|
+
uri
|
|
10
17
|
rescue ::URI::InvalidURIError => e
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
raise URLCanonicalize::Exception::URI, "#{e.class}: #{e.message}" # the original error becomes the cause
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Syntactic normalization per RFC 3986, via Addressable: lowercase
|
|
22
|
+
# scheme and host, internationalized hosts to punycode, uppercase
|
|
23
|
+
# percent-encodings with unreserved characters decoded, dot segments
|
|
24
|
+
# resolved, default ports removed and the empty path replaced by "/".
|
|
25
|
+
# Fragments are client-side state, so no canonical URL carries one
|
|
26
|
+
def normalize(url)
|
|
27
|
+
addressable = Addressable::URI.parse(decorate(url.to_s.strip)).normalize
|
|
28
|
+
addressable.fragment = nil
|
|
29
|
+
addressable.to_s
|
|
30
|
+
rescue Addressable::URI::InvalidURIError, ArgumentError, Encoding::CompatibilityError => e
|
|
31
|
+
raise URLCanonicalize::Exception::URI, "#{e.class}: #{e.message}" # the original error becomes the cause
|
|
14
32
|
end
|
|
15
33
|
|
|
16
34
|
private
|
|
17
35
|
|
|
18
|
-
def
|
|
36
|
+
def validate!(uri)
|
|
19
37
|
raise URLCanonicalize::Exception::URI, "#{uri} must be http or https" unless VALID_CLASSES.include?(uri.class)
|
|
20
38
|
raise URLCanonicalize::Exception::URI, "Missing host name in #{uri}" unless uri.host
|
|
21
39
|
raise URLCanonicalize::Exception::URI, "Empty host name in #{uri}" if uri.host.empty?
|
|
22
|
-
|
|
23
|
-
true
|
|
24
40
|
end
|
|
25
41
|
|
|
26
42
|
def decorate(url)
|
|
27
|
-
return url if url.
|
|
43
|
+
return url if url.match?(SCHEME)
|
|
28
44
|
|
|
29
45
|
"http://#{url}" # Add protocol if we just receive a host name
|
|
30
46
|
end
|
|
31
|
-
|
|
32
|
-
VALID_CLASSES = [::URI::HTTP, ::URI::HTTPS].freeze
|
|
33
|
-
COLON = ':'
|
|
34
47
|
end
|
|
35
48
|
end
|
|
36
49
|
end
|
data/lib/url_canonicalize.rb
CHANGED
|
@@ -4,30 +4,34 @@ require 'uri'
|
|
|
4
4
|
require 'addressable/uri'
|
|
5
5
|
require 'net/http'
|
|
6
6
|
require 'nokogiri'
|
|
7
|
+
require 'timeout'
|
|
7
8
|
|
|
8
9
|
autoload :OpenSSL, 'openssl'
|
|
9
10
|
|
|
10
11
|
# Core methods
|
|
11
12
|
module URLCanonicalize
|
|
13
|
+
autoload :BoundedBody, 'url_canonicalize/bounded_body'
|
|
14
|
+
autoload :Client, 'url_canonicalize/client'
|
|
15
|
+
autoload :Destination, 'url_canonicalize/destination'
|
|
12
16
|
autoload :Exception, 'url_canonicalize/exception'
|
|
13
17
|
autoload :HTTP, 'url_canonicalize/http'
|
|
18
|
+
autoload :LinkHeader, 'url_canonicalize/link_header'
|
|
19
|
+
autoload :MediaType, 'url_canonicalize/media_type'
|
|
20
|
+
autoload :Options, 'url_canonicalize/options'
|
|
14
21
|
autoload :Request, 'url_canonicalize/request'
|
|
15
22
|
autoload :Response, 'url_canonicalize/response'
|
|
23
|
+
autoload :Result, 'url_canonicalize/result'
|
|
24
|
+
autoload :Transport, 'url_canonicalize/transport'
|
|
16
25
|
autoload :URI, 'url_canonicalize/uri'
|
|
17
26
|
autoload :VERSION, 'url_canonicalize/version'
|
|
18
27
|
|
|
19
28
|
class << self
|
|
20
|
-
def canonicalize(url)
|
|
21
|
-
|
|
29
|
+
def canonicalize(url, **)
|
|
30
|
+
Client.new(**).canonicalize(url)
|
|
22
31
|
end
|
|
23
32
|
|
|
24
|
-
def fetch(url)
|
|
25
|
-
|
|
33
|
+
def fetch(url, **)
|
|
34
|
+
Client.new(**).fetch(url)
|
|
26
35
|
end
|
|
27
36
|
end
|
|
28
37
|
end
|
|
29
|
-
|
|
30
|
-
require 'monkey_patches/uri'
|
|
31
|
-
require 'monkey_patches/string'
|
|
32
|
-
require 'monkey_patches/addressable/uri'
|
|
33
|
-
require 'English' # Needed for $LAST_MATCH_INFO
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: url_canonicalize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dominic Sayers
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: addressable
|
|
@@ -46,40 +45,39 @@ executables: []
|
|
|
46
45
|
extensions: []
|
|
47
46
|
extra_rdoc_files: []
|
|
48
47
|
files:
|
|
49
|
-
- ".codeclimate.yml"
|
|
50
|
-
- ".github/workflows/codeql-analysis.yml"
|
|
51
|
-
- ".gitignore"
|
|
52
|
-
- ".hound.yml"
|
|
53
|
-
- ".rspec"
|
|
54
|
-
- ".rubocop.yml"
|
|
55
|
-
- ".ruby-gemset"
|
|
56
|
-
- ".travis.yml"
|
|
57
48
|
- CHANGELOG.md
|
|
58
|
-
- Gemfile
|
|
59
|
-
- Gemfile.lock
|
|
60
|
-
- Guardfile
|
|
61
49
|
- LICENSE
|
|
62
50
|
- README.md
|
|
63
|
-
-
|
|
64
|
-
- circle.yml
|
|
65
|
-
- lib/monkey_patches/addressable/uri.rb
|
|
66
|
-
- lib/monkey_patches/string.rb
|
|
67
|
-
- lib/monkey_patches/uri.rb
|
|
51
|
+
- SECURITY.md
|
|
68
52
|
- lib/url_canonicalize.rb
|
|
53
|
+
- lib/url_canonicalize/bounded_body.rb
|
|
54
|
+
- lib/url_canonicalize/client.rb
|
|
55
|
+
- lib/url_canonicalize/core_ext.rb
|
|
56
|
+
- lib/url_canonicalize/core_ext/addressable.rb
|
|
57
|
+
- lib/url_canonicalize/core_ext/string.rb
|
|
58
|
+
- lib/url_canonicalize/core_ext/uri.rb
|
|
59
|
+
- lib/url_canonicalize/destination.rb
|
|
69
60
|
- lib/url_canonicalize/exception.rb
|
|
70
61
|
- lib/url_canonicalize/http.rb
|
|
62
|
+
- lib/url_canonicalize/link_header.rb
|
|
63
|
+
- lib/url_canonicalize/media_type.rb
|
|
64
|
+
- lib/url_canonicalize/options.rb
|
|
71
65
|
- lib/url_canonicalize/request.rb
|
|
72
66
|
- lib/url_canonicalize/response.rb
|
|
67
|
+
- lib/url_canonicalize/result.rb
|
|
68
|
+
- lib/url_canonicalize/transport.rb
|
|
73
69
|
- lib/url_canonicalize/uri.rb
|
|
74
70
|
- lib/url_canonicalize/version.rb
|
|
75
|
-
- renovate.json
|
|
76
|
-
- url_canonicalize.gemspec
|
|
77
71
|
homepage: https://github.com/dominicsayers/url_canonicalize
|
|
78
72
|
licenses:
|
|
79
73
|
- MIT
|
|
80
74
|
metadata:
|
|
75
|
+
bug_tracker_uri: https://github.com/dominicsayers/url_canonicalize/issues
|
|
76
|
+
changelog_uri: https://github.com/dominicsayers/url_canonicalize/blob/main/CHANGELOG.md
|
|
77
|
+
documentation_uri: https://www.rubydoc.info/gems/url_canonicalize
|
|
78
|
+
homepage_uri: https://github.com/dominicsayers/url_canonicalize
|
|
79
|
+
source_code_uri: https://github.com/dominicsayers/url_canonicalize
|
|
81
80
|
rubygems_mfa_required: 'true'
|
|
82
|
-
post_install_message:
|
|
83
81
|
rdoc_options: []
|
|
84
82
|
require_paths:
|
|
85
83
|
- lib
|
|
@@ -87,15 +85,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
87
85
|
requirements:
|
|
88
86
|
- - ">="
|
|
89
87
|
- !ruby/object:Gem::Version
|
|
90
|
-
version: 3.
|
|
88
|
+
version: 3.3.0
|
|
91
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
90
|
requirements:
|
|
93
91
|
- - ">="
|
|
94
92
|
- !ruby/object:Gem::Version
|
|
95
93
|
version: '0'
|
|
96
94
|
requirements: []
|
|
97
|
-
rubygems_version:
|
|
98
|
-
signing_key:
|
|
95
|
+
rubygems_version: 4.0.16
|
|
99
96
|
specification_version: 4
|
|
100
97
|
summary: Finds the canonical version of a URL
|
|
101
98
|
test_files: []
|
data/.codeclimate.yml
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
engines:
|
|
3
|
-
duplication:
|
|
4
|
-
enabled: true
|
|
5
|
-
config:
|
|
6
|
-
languages:
|
|
7
|
-
- ruby
|
|
8
|
-
- javascript
|
|
9
|
-
- python
|
|
10
|
-
- php
|
|
11
|
-
fixme:
|
|
12
|
-
enabled: true
|
|
13
|
-
rubocop:
|
|
14
|
-
enabled: true
|
|
15
|
-
ratings:
|
|
16
|
-
paths:
|
|
17
|
-
- "**.inc"
|
|
18
|
-
- "**.js"
|
|
19
|
-
- "**.jsx"
|
|
20
|
-
- "**.module"
|
|
21
|
-
- "**.php"
|
|
22
|
-
- "**.py"
|
|
23
|
-
- "**.rb"
|
|
24
|
-
exclude_paths:
|
|
25
|
-
- spec/
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# For most projects, this workflow file will not need changing; you simply need
|
|
2
|
-
# to commit it to your repository.
|
|
3
|
-
#
|
|
4
|
-
# You may wish to alter this file to override the set of languages analyzed,
|
|
5
|
-
# or to provide custom queries or build logic.
|
|
6
|
-
#
|
|
7
|
-
# ******** NOTE ********
|
|
8
|
-
# We have attempted to detect the languages in your repository. Please check
|
|
9
|
-
# the `language` matrix defined below to confirm you have the correct set of
|
|
10
|
-
# supported CodeQL languages.
|
|
11
|
-
#
|
|
12
|
-
name: "CodeQL"
|
|
13
|
-
|
|
14
|
-
on:
|
|
15
|
-
push:
|
|
16
|
-
branches: [ main ]
|
|
17
|
-
pull_request:
|
|
18
|
-
# The branches below must be a subset of the branches above
|
|
19
|
-
branches: [ main ]
|
|
20
|
-
schedule:
|
|
21
|
-
- cron: '28 12 * * 5'
|
|
22
|
-
|
|
23
|
-
jobs:
|
|
24
|
-
analyze:
|
|
25
|
-
name: Analyze
|
|
26
|
-
runs-on: ubuntu-latest
|
|
27
|
-
permissions:
|
|
28
|
-
actions: read
|
|
29
|
-
contents: read
|
|
30
|
-
security-events: write
|
|
31
|
-
|
|
32
|
-
strategy:
|
|
33
|
-
fail-fast: false
|
|
34
|
-
matrix:
|
|
35
|
-
language: [ 'ruby' ]
|
|
36
|
-
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
37
|
-
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
|
38
|
-
|
|
39
|
-
steps:
|
|
40
|
-
- name: Checkout repository
|
|
41
|
-
uses: actions/checkout@v4
|
|
42
|
-
|
|
43
|
-
# Initializes the CodeQL tools for scanning.
|
|
44
|
-
- name: Initialize CodeQL
|
|
45
|
-
uses: github/codeql-action/init@v3
|
|
46
|
-
with:
|
|
47
|
-
languages: ${{ matrix.language }}
|
|
48
|
-
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
49
|
-
# By default, queries listed here will override any specified in a config file.
|
|
50
|
-
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
51
|
-
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
52
|
-
|
|
53
|
-
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
54
|
-
# If this step fails, then you should remove it and run the build manually (see below)
|
|
55
|
-
- name: Autobuild
|
|
56
|
-
uses: github/codeql-action/autobuild@v3
|
|
57
|
-
|
|
58
|
-
# ℹ️ Command-line programs to run using the OS shell.
|
|
59
|
-
# 📚 https://git.io/JvXDl
|
|
60
|
-
|
|
61
|
-
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
62
|
-
# and modify them (or add more) to build your code if your project
|
|
63
|
-
# uses a compiled language
|
|
64
|
-
|
|
65
|
-
#- run: |
|
|
66
|
-
# make bootstrap
|
|
67
|
-
# make release
|
|
68
|
-
|
|
69
|
-
- name: Perform CodeQL Analysis
|
|
70
|
-
uses: github/codeql-action/analyze@v3
|