httpx 0.14.4 → 0.15.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_13_2.md +1 -1
  3. data/doc/release_notes/0_14_5.md +11 -0
  4. data/doc/release_notes/0_15_0.md +53 -0
  5. data/doc/release_notes/0_15_1.md +8 -0
  6. data/doc/release_notes/0_15_2.md +9 -0
  7. data/doc/release_notes/0_15_3.md +5 -0
  8. data/lib/httpx.rb +1 -0
  9. data/lib/httpx/connection.rb +14 -3
  10. data/lib/httpx/connection/http1.rb +22 -5
  11. data/lib/httpx/connection/http2.rb +14 -0
  12. data/lib/httpx/domain_name.rb +0 -290
  13. data/lib/httpx/errors.rb +2 -0
  14. data/lib/httpx/extensions.rb +1 -1
  15. data/lib/httpx/loggable.rb +1 -1
  16. data/lib/httpx/options.rb +5 -1
  17. data/lib/httpx/plugins/cookies/cookie.rb +1 -2
  18. data/lib/httpx/plugins/cookies/jar.rb +4 -0
  19. data/lib/httpx/plugins/digest_authentication.rb +19 -21
  20. data/lib/httpx/plugins/grpc.rb +1 -1
  21. data/lib/httpx/plugins/grpc/call.rb +1 -2
  22. data/lib/httpx/plugins/ntlm_authentication.rb +66 -0
  23. data/lib/httpx/plugins/proxy/socks4.rb +4 -0
  24. data/lib/httpx/plugins/proxy/socks5.rb +4 -0
  25. data/lib/httpx/pmatch_extensions.rb +33 -0
  26. data/lib/httpx/punycode.rb +304 -0
  27. data/lib/httpx/request.rb +1 -1
  28. data/lib/httpx/response.rb +2 -0
  29. data/lib/httpx/selector.rb +41 -37
  30. data/lib/httpx/utils.rb +6 -4
  31. data/lib/httpx/version.rb +1 -1
  32. data/sig/chainable.rbs +5 -0
  33. data/sig/connection/http1.rbs +2 -0
  34. data/sig/connection/http2.rbs +2 -0
  35. data/sig/options.rbs +1 -1
  36. data/sig/plugins/aws_sdk_authentication.rbs +2 -0
  37. data/sig/plugins/basic_authentication.rbs +1 -1
  38. data/sig/plugins/digest_authentication.rbs +1 -1
  39. data/sig/plugins/follow_redirects.rbs +1 -1
  40. data/sig/plugins/grpc.rbs +93 -0
  41. data/sig/plugins/multipart.rbs +1 -1
  42. data/sig/plugins/ntlm_authentication.rbs +27 -0
  43. data/sig/plugins/proxy/socks4.rbs +1 -0
  44. data/sig/plugins/proxy/socks5.rbs +1 -0
  45. data/sig/selector.rbs +1 -0
  46. data/sig/utils.rbs +7 -0
  47. metadata +19 -3
  48. data/lib/httpx/idna.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 495553e317911e0901a61617fac9bacd2ee33cb9c145eb01909fd09a8d1a6f9e
4
- data.tar.gz: fb19c9915540fcc3358f18f37ac18c80812824d4dacffb69956577a7df3c96a5
3
+ metadata.gz: e35dd08edd7da2735fbcab65f0cab8c7c41314946e64b02780b6afd17faa5622
4
+ data.tar.gz: 1b34789ecb769043120c5c524657f2e747c91385c32064eed732a25106d2fa81
5
5
  SHA512:
6
- metadata.gz: fa1c3937960cc63d447d33bc966ea453281537bdd35318becbe7c963aad82def455a657b0a7d72a5b1efe8021541f817c3657b38ca085b3643ec2f1e11a6f7cb
7
- data.tar.gz: b8322a02d53e6922f0ec2a999cc9220a7cac74600b97e7e3e9f9b9ec003ef3c184184d90b2d976f7581422d1cf03355b57fc9c32dcdaf61651545a1d5ac30983
6
+ metadata.gz: b313d4468410befb74da77551484b116389fa785c643c935756f949d16077773d6a4ffe1b99ed20c6263e94385a2a4a40f82e3ec029db830d21b8c4ea03b847b
7
+ data.tar.gz: 69955f7aa53181fc0a5b08dd9a5c909f5f526b45856dbc26991a65719a2916c1372264a89461d1aea91192a020bbffd9abac5d644194b9e4fc6f9c29d7837ef2
@@ -1,4 +1,4 @@
1
- # 0.13.1
1
+ # 0.13.2
2
2
 
3
3
  ## Improvements
4
4
 
@@ -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.
@@ -0,0 +1,5 @@
1
+ # 0.15.3
2
+
3
+ ## Bugfixes
4
+
5
+ * Fixed connection management, where selectables could end up with "arrays of IO objects" as elements, instead of just IO objects. This caused bugs when connecting and performing concurrent requests on multiple hosts (#138).
data/lib/httpx.rb CHANGED
@@ -6,6 +6,7 @@ require "httpx/extensions"
6
6
 
7
7
  require "httpx/errors"
8
8
  require "httpx/utils"
9
+ require "httpx/punycode"
9
10
  require "httpx/domain_name"
10
11
  require "httpx/altsvc"
11
12
  require "httpx/callbacks"
@@ -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.uri(uri.origin)
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 = @options.timeout[:operation_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
- requests_limit = [@max_requests, @requests.size].min
262
-
263
- connection = if request.options.persistent || request != @requests[requests_limit - 1]
264
- "keep-alive"
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]
@@ -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