httpx 0.14.3 → 0.15.2
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/doc/release_notes/0_13_2.md +1 -1
- data/doc/release_notes/0_14_4.md +5 -0
- 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/lib/httpx.rb +1 -0
- data/lib/httpx/connection.rb +14 -3
- data/lib/httpx/connection/http1.rb +22 -3
- 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/utils.rbs +7 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63059d2ae3e913d077850d4a95fc5aa4a9f0691aa92834ddf6898d38b8ec0913
|
4
|
+
data.tar.gz: a9f689d038c7ee258ce2a2dd68d56fd21104be96b8f12c20a240cd024eea6576
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 940075fbe22ed29a3a0b5a6a85f69ef25bab2dac876eed6779b840b761dd8c7cff09a43f861fd2e39a42b82652d01bcf4aa0384afc1d4d253b9eb82d4f2adb21
|
7
|
+
data.tar.gz: 8efa63c8c6c7c5c8869e507e64932167082f27c85d2c3bd026a9bc29d8c052d8e26612e35c06f046b2c9b5c8b6dd102d20a29d7ef67a7babc4c0ad0f990f471b
|
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,10 +263,24 @@ module HTTPX
|
|
258
263
|
request.chunk!
|
259
264
|
end
|
260
265
|
|
261
|
-
connection = if request.options.persistent
|
262
|
-
|
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
|
263
274
|
else
|
264
|
-
"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
|
265
284
|
end
|
266
285
|
|
267
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
|