httpx 0.14.4 → 0.15.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/release_notes/0_13_2.md +1 -1
- data/doc/release_notes/0_14_5.md +11 -0
- data/doc/release_notes/0_15_0.md +53 -0
- data/doc/release_notes/0_15_1.md +8 -0
- data/doc/release_notes/0_15_2.md +9 -0
- data/doc/release_notes/0_15_3.md +5 -0
- data/lib/httpx.rb +1 -0
- data/lib/httpx/connection.rb +14 -3
- data/lib/httpx/connection/http1.rb +22 -5
- data/lib/httpx/connection/http2.rb +14 -0
- data/lib/httpx/domain_name.rb +0 -290
- data/lib/httpx/errors.rb +2 -0
- data/lib/httpx/extensions.rb +1 -1
- data/lib/httpx/loggable.rb +1 -1
- data/lib/httpx/options.rb +5 -1
- data/lib/httpx/plugins/cookies/cookie.rb +1 -2
- data/lib/httpx/plugins/cookies/jar.rb +4 -0
- data/lib/httpx/plugins/digest_authentication.rb +19 -21
- data/lib/httpx/plugins/grpc.rb +1 -1
- data/lib/httpx/plugins/grpc/call.rb +1 -2
- data/lib/httpx/plugins/ntlm_authentication.rb +66 -0
- data/lib/httpx/plugins/proxy/socks4.rb +4 -0
- data/lib/httpx/plugins/proxy/socks5.rb +4 -0
- data/lib/httpx/pmatch_extensions.rb +33 -0
- data/lib/httpx/punycode.rb +304 -0
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/response.rb +2 -0
- data/lib/httpx/selector.rb +41 -37
- data/lib/httpx/utils.rb +6 -4
- data/lib/httpx/version.rb +1 -1
- data/sig/chainable.rbs +5 -0
- data/sig/connection/http1.rbs +2 -0
- data/sig/connection/http2.rbs +2 -0
- data/sig/options.rbs +1 -1
- data/sig/plugins/aws_sdk_authentication.rbs +2 -0
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/digest_authentication.rbs +1 -1
- data/sig/plugins/follow_redirects.rbs +1 -1
- data/sig/plugins/grpc.rbs +93 -0
- data/sig/plugins/multipart.rbs +1 -1
- data/sig/plugins/ntlm_authentication.rbs +27 -0
- data/sig/plugins/proxy/socks4.rbs +1 -0
- data/sig/plugins/proxy/socks5.rbs +1 -0
- data/sig/selector.rbs +1 -0
- data/sig/utils.rbs +7 -0
- metadata +19 -3
- data/lib/httpx/idna.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e35dd08edd7da2735fbcab65f0cab8c7c41314946e64b02780b6afd17faa5622
|
4
|
+
data.tar.gz: 1b34789ecb769043120c5c524657f2e747c91385c32064eed732a25106d2fa81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b313d4468410befb74da77551484b116389fa785c643c935756f949d16077773d6a4ffe1b99ed20c6263e94385a2a4a40f82e3ec029db830d21b8c4ea03b847b
|
7
|
+
data.tar.gz: 69955f7aa53181fc0a5b08dd9a5c909f5f526b45856dbc26991a65719a2916c1372264a89461d1aea91192a020bbffd9abac5d644194b9e4fc6f9c29d7837ef2
|
data/doc/release_notes/0_13_2.md
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
# 0.14.5
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
* After a connection had been initiated, sending multiple concurrent requests (ex: `open_httpx.request(req1, req2, req3)`) could freeze; this happened when the first request would fill the write buffer (like a file upload request), and the subsequent requests would never be buffered afterwards; this was fixed by making pending requests flushing a part of a connection's consumption loop.
|
6
|
+
* Fixing v0.14.1's fixed bug again; The HTTP/1 "Connection: close" header was not being set in the last possible request on a connection, due to ann off-by-one error on connection bookkeeping;
|
7
|
+
* HTTP/1 connections didn't respect a server-set max nunmber of requests after a reconnect; Fixed by making this accounting part of the reset process;
|
8
|
+
|
9
|
+
## Chore
|
10
|
+
|
11
|
+
* Added regression test suite, which reproduce reported bugs before the fix (backported all 0.14.x releases here)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# 0.15.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### HTTP response pattern-matching (ruby 3 only)
|
6
|
+
|
7
|
+
You can now apply pattern matching in responses:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
case response = HTTPX.get("https://google.com")
|
11
|
+
in { status: 200..399, headers: [*, ["x-special-token", token], *], body: }
|
12
|
+
puts "success: #{body.to_s}"
|
13
|
+
in { status: 400..499, body: }
|
14
|
+
puts "client error: #{body.to_s}"
|
15
|
+
in { status: 500.., body: }
|
16
|
+
puts "server error: #{body.to_s}"
|
17
|
+
in { error: error }
|
18
|
+
puts "error: #{error.message}"
|
19
|
+
else
|
20
|
+
raise "unexpected: #{response}"
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
### NTLM Authentication
|
25
|
+
|
26
|
+
A new plugin, `:ntml_authentication`, is now available. Like the name suggests, it allows authenticating requests via [NTLM](https://docs.microsoft.com/en-us/windows-server/security/kerberos/ntlm-overview).
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
ntlm_http = HTTPX.plugin(:ntlm_authentication)
|
30
|
+
|
31
|
+
ntlm.ntlm_authentication("user", "password").get("http://protected-area-requiring-ntlm.net")
|
32
|
+
# or for a specific domain
|
33
|
+
ntlm.ntlm_authentication("user", "password", "Domain\\User").get("http://protected-area-requiring-ntlm.net")
|
34
|
+
```
|
35
|
+
|
36
|
+
## Improvemennts
|
37
|
+
|
38
|
+
A new timeout option, `settings_timeout`, is supported for the HTTP/2 handshake; after the TCP and TLS handshakes are complete, and initiating the HTTP/2 handshake, the client terminates the connection with SETTINGS_TIMEOUT error code, if it doesn't receive the server settings for the amount of seconds set in `settings_timeout` (by default, 10 seconds).
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
# if you want to change
|
42
|
+
HTTPX.with(timeout: {settings_timeout: 5})....
|
43
|
+
|
44
|
+
```
|
45
|
+
|
46
|
+
IDNA 2008 support is now possibly, by integrating [idnx](https://github.com/HoneyryderChuck/idnx) into your dependencies:
|
47
|
+
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# in Gemfile
|
51
|
+
gem "httpx"
|
52
|
+
gem "idnx"
|
53
|
+
```
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# 0.15.1
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
Fixed HTTP/1 connection accounting on requests:
|
6
|
+
|
7
|
+
* when persistent, Connection: close will be set based on the request position on the batch against the allowed requests on the open connection.
|
8
|
+
* when not persistent, Connnection: close will be set on the last request of the batch, being the batch a subset based on allowed requests, or the whole of it.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# 0.15.2
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
* Fixed cookie management for same-keys: before the fix, cookies with same-key, same-domain and same-path were all being sent in subsequent requests, which violates RFC 6265 - 5.4 . As of now, only the last valid cookie for a given key/domain/path will be kept, evicting the others.
|
6
|
+
|
7
|
+
## Chore
|
8
|
+
|
9
|
+
* debug logs were inserting ASCII code string wrappers, even when no color was set. It nonw only sends the plain string.
|
data/lib/httpx.rb
CHANGED
data/lib/httpx/connection.rb
CHANGED
@@ -51,7 +51,7 @@ module HTTPX
|
|
51
51
|
def initialize(type, uri, options)
|
52
52
|
@type = type
|
53
53
|
@origins = [uri.origin]
|
54
|
-
@origin = Utils.
|
54
|
+
@origin = Utils.to_uri(uri.origin)
|
55
55
|
@options = Options.new(options)
|
56
56
|
@window_size = @options.window_size
|
57
57
|
@read_buffer = Buffer.new(BUFFER_SIZE)
|
@@ -254,6 +254,8 @@ module HTTPX
|
|
254
254
|
end
|
255
255
|
|
256
256
|
def consume
|
257
|
+
return unless @io
|
258
|
+
|
257
259
|
catch(:called) do
|
258
260
|
epiped = false
|
259
261
|
loop do
|
@@ -311,7 +313,7 @@ module HTTPX
|
|
311
313
|
|
312
314
|
# exit #consume altogether if all outstanding requests have been dealt with
|
313
315
|
return if @pending.size.zero? && @inflight.zero?
|
314
|
-
end unless (interests == :w || @state == :closing) && !epiped
|
316
|
+
end unless (interests.nil? || interests == :w || @state == :closing) && !epiped
|
315
317
|
|
316
318
|
#
|
317
319
|
# tight write loop.
|
@@ -360,6 +362,8 @@ module HTTPX
|
|
360
362
|
write_drained = false
|
361
363
|
end unless interests == :r
|
362
364
|
|
365
|
+
send_pending if @state == :open
|
366
|
+
|
363
367
|
# return if socket is drained
|
364
368
|
next unless (interests != :r || read_drained) &&
|
365
369
|
(interests != :w || write_drained)
|
@@ -422,6 +426,9 @@ module HTTPX
|
|
422
426
|
emit(:close)
|
423
427
|
end
|
424
428
|
end
|
429
|
+
parser.on(:close_handshake) do
|
430
|
+
consume
|
431
|
+
end
|
425
432
|
parser.on(:reset) do
|
426
433
|
if parser.empty?
|
427
434
|
reset
|
@@ -434,6 +441,9 @@ module HTTPX
|
|
434
441
|
transition(:open)
|
435
442
|
end
|
436
443
|
end
|
444
|
+
parser.on(:current_timeout) do
|
445
|
+
@current_timeout = @timeout = parser.timeout
|
446
|
+
end
|
437
447
|
parser.on(:timeout) do |tout|
|
438
448
|
@timeout = tout
|
439
449
|
end
|
@@ -464,10 +474,11 @@ module HTTPX
|
|
464
474
|
|
465
475
|
send_pending
|
466
476
|
|
467
|
-
@timeout = @current_timeout =
|
477
|
+
@timeout = @current_timeout = parser.timeout
|
468
478
|
emit(:open)
|
469
479
|
when :closing
|
470
480
|
return unless @state == :open
|
481
|
+
|
471
482
|
when :closed
|
472
483
|
return unless @state == :closing
|
473
484
|
return unless @write_buffer.empty?
|
@@ -24,6 +24,10 @@ module HTTPX
|
|
24
24
|
@handshake_completed = false
|
25
25
|
end
|
26
26
|
|
27
|
+
def timeout
|
28
|
+
@options.timeout[:operation_timeout]
|
29
|
+
end
|
30
|
+
|
27
31
|
def interests
|
28
32
|
# this means we're processing incoming response already
|
29
33
|
return :r if @request
|
@@ -40,6 +44,7 @@ module HTTPX
|
|
40
44
|
def reset
|
41
45
|
@max_requests = @options.max_requests || MAX_REQUESTS
|
42
46
|
@parser.reset!
|
47
|
+
@handshake_completed = false
|
43
48
|
end
|
44
49
|
|
45
50
|
def close
|
@@ -258,12 +263,24 @@ module HTTPX
|
|
258
263
|
request.chunk!
|
259
264
|
end
|
260
265
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
266
|
+
connection = if request.options.persistent
|
267
|
+
# when in a persistent connection, the request can't be at
|
268
|
+
# the edge of a renegotiation
|
269
|
+
if @requests.index(request) + 1 < @max_requests
|
270
|
+
"keep-alive"
|
271
|
+
else
|
272
|
+
"close"
|
273
|
+
end
|
265
274
|
else
|
266
|
-
"close"
|
275
|
+
# when it's not a persistent connection, it sets "Connection: close" always
|
276
|
+
# on the last request of the possible batch (either allowed max requests,
|
277
|
+
# or if smaller, the size of the batch itself)
|
278
|
+
requests_limit = [@max_requests, @requests.size].min
|
279
|
+
if request != @requests[requests_limit - 1]
|
280
|
+
"keep-alive"
|
281
|
+
else
|
282
|
+
"close"
|
283
|
+
end
|
267
284
|
end
|
268
285
|
|
269
286
|
{
|
@@ -34,6 +34,12 @@ module HTTPX
|
|
34
34
|
init_connection
|
35
35
|
end
|
36
36
|
|
37
|
+
def timeout
|
38
|
+
return @options.timeout[:operation_timeout] if @handshake_completed
|
39
|
+
|
40
|
+
@options.timeout[:settings_timeout]
|
41
|
+
end
|
42
|
+
|
37
43
|
def interests
|
38
44
|
# waiting for WINDOW_UPDATE frames
|
39
45
|
return :r if @buffer.full?
|
@@ -117,6 +123,13 @@ module HTTPX
|
|
117
123
|
end
|
118
124
|
|
119
125
|
def handle_error(ex)
|
126
|
+
if ex.instance_of?(TimeoutError) && !@handshake_completed && @connection.state != :closed
|
127
|
+
@connection.goaway(:settings_timeout, "closing due to settings timeout")
|
128
|
+
emit(:close_handshake)
|
129
|
+
settings_ex = SettingsTimeoutError.new(ex.timeout, ex.message)
|
130
|
+
settings_ex.set_backtrace(ex.backtrace)
|
131
|
+
ex = settings_ex
|
132
|
+
end
|
120
133
|
@streams.each_key do |request|
|
121
134
|
emit(:error, request, ex)
|
122
135
|
end
|
@@ -312,6 +325,7 @@ module HTTPX
|
|
312
325
|
|
313
326
|
def on_settings(*)
|
314
327
|
@handshake_completed = true
|
328
|
+
emit(:current_timeout)
|
315
329
|
|
316
330
|
if @max_requests.zero?
|
317
331
|
@max_requests = @connection.remote_settings[:settings_max_concurrent_streams]
|
data/lib/httpx/domain_name.rb
CHANGED
@@ -144,295 +144,5 @@ module HTTPX
|
|
144
144
|
1
|
145
145
|
end
|
146
146
|
end
|
147
|
-
|
148
|
-
# :nocov:
|
149
|
-
# rubocop:disable all
|
150
|
-
# -*- coding: utf-8 -*-
|
151
|
-
#--
|
152
|
-
# punycode.rb - PunyCode encoder for the Domain Name library
|
153
|
-
#
|
154
|
-
# Copyright (C) 2011-2017 Akinori MUSHA, All rights reserved.
|
155
|
-
#
|
156
|
-
# Ported from puny.c, a part of VeriSign XCode (encode/decode) IDN
|
157
|
-
# Library.
|
158
|
-
#
|
159
|
-
# Copyright (C) 2000-2002 Verisign Inc., All rights reserved.
|
160
|
-
#
|
161
|
-
# Redistribution and use in source and binary forms, with or
|
162
|
-
# without modification, are permitted provided that the following
|
163
|
-
# conditions are met:
|
164
|
-
#
|
165
|
-
# 1) Redistributions of source code must retain the above copyright
|
166
|
-
# notice, this list of conditions and the following disclaimer.
|
167
|
-
#
|
168
|
-
# 2) Redistributions in binary form must reproduce the above copyright
|
169
|
-
# notice, this list of conditions and the following disclaimer in
|
170
|
-
# the documentation and/or other materials provided with the
|
171
|
-
# distribution.
|
172
|
-
#
|
173
|
-
# 3) Neither the name of the VeriSign Inc. nor the names of its
|
174
|
-
# contributors may be used to endorse or promote products derived
|
175
|
-
# from this software without specific prior written permission.
|
176
|
-
#
|
177
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
178
|
-
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
179
|
-
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
180
|
-
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
181
|
-
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
182
|
-
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
183
|
-
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
184
|
-
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
185
|
-
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
186
|
-
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
187
|
-
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
188
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
189
|
-
#
|
190
|
-
# This software is licensed under the BSD open source license. For more
|
191
|
-
# information visit www.opensource.org.
|
192
|
-
#
|
193
|
-
# Authors:
|
194
|
-
# John Colosi (VeriSign)
|
195
|
-
# Srikanth Veeramachaneni (VeriSign)
|
196
|
-
# Nagesh Chigurupati (Verisign)
|
197
|
-
# Praveen Srinivasan(Verisign)
|
198
|
-
#++
|
199
|
-
module Punycode
|
200
|
-
BASE = 36
|
201
|
-
TMIN = 1
|
202
|
-
TMAX = 26
|
203
|
-
SKEW = 38
|
204
|
-
DAMP = 700
|
205
|
-
INITIAL_BIAS = 72
|
206
|
-
INITIAL_N = 0x80
|
207
|
-
DELIMITER = "-"
|
208
|
-
|
209
|
-
MAXINT = (1 << 32) - 1
|
210
|
-
|
211
|
-
LOBASE = BASE - TMIN
|
212
|
-
CUTOFF = LOBASE * TMAX / 2
|
213
|
-
|
214
|
-
RE_NONBASIC = /[^\x00-\x7f]/.freeze
|
215
|
-
|
216
|
-
# Returns the numeric value of a basic code point (for use in
|
217
|
-
# representing integers) in the range 0 to base-1, or nil if cp
|
218
|
-
# is does not represent a value.
|
219
|
-
DECODE_DIGIT = {}.tap do |map|
|
220
|
-
# ASCII A..Z map to 0..25
|
221
|
-
# ASCII a..z map to 0..25
|
222
|
-
(0..25).each { |i| map[65 + i] = map[97 + i] = i }
|
223
|
-
# ASCII 0..9 map to 26..35
|
224
|
-
(26..35).each { |i| map[22 + i] = i }
|
225
|
-
end
|
226
|
-
|
227
|
-
# Returns the basic code point whose value (when used for
|
228
|
-
# representing integers) is d, which must be in the range 0 to
|
229
|
-
# BASE-1. The lowercase form is used unless flag is true, in
|
230
|
-
# which case the uppercase form is used. The behavior is
|
231
|
-
# undefined if flag is nonzero and digit d has no uppercase
|
232
|
-
# form.
|
233
|
-
ENCODE_DIGIT = proc { |d, flag|
|
234
|
-
(d + 22 + (d < 26 ? 75 : 0) - (flag ? (1 << 5) : 0)).chr
|
235
|
-
# 0..25 map to ASCII a..z or A..Z
|
236
|
-
# 26..35 map to ASCII 0..9
|
237
|
-
}
|
238
|
-
|
239
|
-
DOT = "."
|
240
|
-
PREFIX = "xn--"
|
241
|
-
|
242
|
-
# Most errors we raise are basically kind of ArgumentError.
|
243
|
-
class ArgumentError < ::ArgumentError; end
|
244
|
-
class BufferOverflowError < ArgumentError; end
|
245
|
-
|
246
|
-
class << self
|
247
|
-
# Encode a +string+ in Punycode
|
248
|
-
def encode(string)
|
249
|
-
input = string.unpack("U*")
|
250
|
-
output = +""
|
251
|
-
|
252
|
-
# Initialize the state
|
253
|
-
n = INITIAL_N
|
254
|
-
delta = 0
|
255
|
-
bias = INITIAL_BIAS
|
256
|
-
|
257
|
-
# Handle the basic code points
|
258
|
-
input.each { |cp| output << cp.chr if cp < 0x80 }
|
259
|
-
|
260
|
-
h = b = output.length
|
261
|
-
|
262
|
-
# h is the number of code points that have been handled, b is the
|
263
|
-
# number of basic code points, and out is the number of characters
|
264
|
-
# that have been output.
|
265
|
-
|
266
|
-
output << DELIMITER if b > 0
|
267
|
-
|
268
|
-
# Main encoding loop
|
269
|
-
|
270
|
-
while h < input.length
|
271
|
-
# All non-basic code points < n have been handled already. Find
|
272
|
-
# the next larger one
|
273
|
-
|
274
|
-
m = MAXINT
|
275
|
-
input.each do |cp|
|
276
|
-
m = cp if (n...m) === cp
|
277
|
-
end
|
278
|
-
|
279
|
-
# Increase delta enough to advance the decoder's <n,i> state to
|
280
|
-
# <m,0>, but guard against overflow
|
281
|
-
|
282
|
-
delta += (m - n) * (h + 1)
|
283
|
-
raise BufferOverflowError if delta > MAXINT
|
284
|
-
|
285
|
-
n = m
|
286
|
-
|
287
|
-
input.each do |cp|
|
288
|
-
# AMC-ACE-Z can use this simplified version instead
|
289
|
-
if cp < n
|
290
|
-
delta += 1
|
291
|
-
raise BufferOverflowError if delta > MAXINT
|
292
|
-
elsif cp == n
|
293
|
-
# Represent delta as a generalized variable-length integer
|
294
|
-
q = delta
|
295
|
-
k = BASE
|
296
|
-
loop do
|
297
|
-
t = k <= bias ? TMIN : k - bias >= TMAX ? TMAX : k - bias
|
298
|
-
break if q < t
|
299
|
-
|
300
|
-
q, r = (q - t).divmod(BASE - t)
|
301
|
-
output << ENCODE_DIGIT[t + r, false]
|
302
|
-
k += BASE
|
303
|
-
end
|
304
|
-
|
305
|
-
output << ENCODE_DIGIT[q, false]
|
306
|
-
|
307
|
-
# Adapt the bias
|
308
|
-
delta = h == b ? delta / DAMP : delta >> 1
|
309
|
-
delta += delta / (h + 1)
|
310
|
-
bias = 0
|
311
|
-
while delta > CUTOFF
|
312
|
-
delta /= LOBASE
|
313
|
-
bias += BASE
|
314
|
-
end
|
315
|
-
bias += (LOBASE + 1) * delta / (delta + SKEW)
|
316
|
-
|
317
|
-
delta = 0
|
318
|
-
h += 1
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
|
-
delta += 1
|
323
|
-
n += 1
|
324
|
-
end
|
325
|
-
|
326
|
-
output
|
327
|
-
end
|
328
|
-
|
329
|
-
# Encode a hostname using IDN/Punycode algorithms
|
330
|
-
def encode_hostname(hostname)
|
331
|
-
hostname.match(RE_NONBASIC) || (return hostname)
|
332
|
-
|
333
|
-
hostname.split(DOT).map do |name|
|
334
|
-
if name.match(RE_NONBASIC)
|
335
|
-
PREFIX + encode(name)
|
336
|
-
else
|
337
|
-
name
|
338
|
-
end
|
339
|
-
end.join(DOT)
|
340
|
-
end
|
341
|
-
|
342
|
-
# Decode a +string+ encoded in Punycode
|
343
|
-
def decode(string)
|
344
|
-
# Initialize the state
|
345
|
-
n = INITIAL_N
|
346
|
-
i = 0
|
347
|
-
bias = INITIAL_BIAS
|
348
|
-
|
349
|
-
if j = string.rindex(DELIMITER)
|
350
|
-
b = string[0...j]
|
351
|
-
|
352
|
-
b.match(RE_NONBASIC) &&
|
353
|
-
raise(ArgumentError, "Illegal character is found in basic part: #{string.inspect}")
|
354
|
-
|
355
|
-
# Handle the basic code points
|
356
|
-
|
357
|
-
output = b.unpack("U*")
|
358
|
-
u = string[(j + 1)..-1]
|
359
|
-
else
|
360
|
-
output = []
|
361
|
-
u = string
|
362
|
-
end
|
363
|
-
|
364
|
-
# Main decoding loop: Start just after the last delimiter if any
|
365
|
-
# basic code points were copied; start at the beginning
|
366
|
-
# otherwise.
|
367
|
-
|
368
|
-
input = u.unpack("C*")
|
369
|
-
input_length = input.length
|
370
|
-
h = 0
|
371
|
-
out = output.length
|
372
|
-
|
373
|
-
while h < input_length
|
374
|
-
# Decode a generalized variable-length integer into delta,
|
375
|
-
# which gets added to i. The overflow checking is easier
|
376
|
-
# if we increase i as we go, then subtract off its starting
|
377
|
-
# value at the end to obtain delta.
|
378
|
-
|
379
|
-
oldi = i
|
380
|
-
w = 1
|
381
|
-
k = BASE
|
382
|
-
|
383
|
-
loop do
|
384
|
-
(digit = DECODE_DIGIT[input[h]]) ||
|
385
|
-
raise(ArgumentError, "Illegal character is found in non-basic part: #{string.inspect}")
|
386
|
-
h += 1
|
387
|
-
i += digit * w
|
388
|
-
raise BufferOverflowError if i > MAXINT
|
389
|
-
|
390
|
-
t = k <= bias ? TMIN : k - bias >= TMAX ? TMAX : k - bias
|
391
|
-
break if digit < t
|
392
|
-
|
393
|
-
w *= BASE - t
|
394
|
-
raise BufferOverflowError if w > MAXINT
|
395
|
-
|
396
|
-
k += BASE
|
397
|
-
(h < input_length) || raise(ArgumentError, "Malformed input given: #{string.inspect}")
|
398
|
-
end
|
399
|
-
|
400
|
-
# Adapt the bias
|
401
|
-
delta = oldi == 0 ? i / DAMP : (i - oldi) >> 1
|
402
|
-
delta += delta / (out + 1)
|
403
|
-
bias = 0
|
404
|
-
while delta > CUTOFF
|
405
|
-
delta /= LOBASE
|
406
|
-
bias += BASE
|
407
|
-
end
|
408
|
-
bias += (LOBASE + 1) * delta / (delta + SKEW)
|
409
|
-
|
410
|
-
# i was supposed to wrap around from out+1 to 0, incrementing
|
411
|
-
# n each time, so we'll fix that now:
|
412
|
-
|
413
|
-
q, i = i.divmod(out + 1)
|
414
|
-
n += q
|
415
|
-
raise BufferOverflowError if n > MAXINT
|
416
|
-
|
417
|
-
# Insert n at position i of the output:
|
418
|
-
|
419
|
-
output[i, 0] = n
|
420
|
-
|
421
|
-
out += 1
|
422
|
-
i += 1
|
423
|
-
end
|
424
|
-
output.pack("U*")
|
425
|
-
end
|
426
|
-
|
427
|
-
# Decode a hostname using IDN/Punycode algorithms
|
428
|
-
def decode_hostname(hostname)
|
429
|
-
hostname.gsub(/(\A|#{Regexp.quote(DOT)})#{Regexp.quote(PREFIX)}([^#{Regexp.quote(DOT)}]*)/o) do
|
430
|
-
Regexp.last_match(1) << decode(Regexp.last_match(2))
|
431
|
-
end
|
432
|
-
end
|
433
|
-
end
|
434
|
-
# rubocop:enable all
|
435
|
-
# :nocov:
|
436
|
-
end
|
437
147
|
end
|
438
148
|
end
|