httpx 0.14.3 → 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) 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_4.md +5 -0
  4. data/doc/release_notes/0_14_5.md +11 -0
  5. data/doc/release_notes/0_15_0.md +53 -0
  6. data/doc/release_notes/0_15_1.md +8 -0
  7. data/doc/release_notes/0_15_2.md +9 -0
  8. data/lib/httpx.rb +1 -0
  9. data/lib/httpx/connection.rb +14 -3
  10. data/lib/httpx/connection/http1.rb +22 -3
  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/utils.rbs +7 -0
  46. metadata +19 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 716a91d8937fe675798bd8ad275cf5944616069149f2ace7b8c04c42c4cfeb19
4
- data.tar.gz: cb76bc4343b18a30e1ffb093885e899b7b0cc9c70f40e5b461f2de4d4cce89f6
3
+ metadata.gz: 63059d2ae3e913d077850d4a95fc5aa4a9f0691aa92834ddf6898d38b8ec0913
4
+ data.tar.gz: a9f689d038c7ee258ce2a2dd68d56fd21104be96b8f12c20a240cd024eea6576
5
5
  SHA512:
6
- metadata.gz: 789cb7389ada8841232580200950c0066e30071ae120323fbb2b4646ae69cf43422c8e181aa02d2820d315c0a4a839a4635d584f4a57218e30f1c66f07cbcb80
7
- data.tar.gz: 3815634b93c035a445557b4ae8718b985bbc8ee4776921cba4ee50fdb58dd31769e3f25aaa051f59df8aa96a0ab69a281e5a3ce7b013d762aa258f807afc74b7
6
+ metadata.gz: 940075fbe22ed29a3a0b5a6a85f69ef25bab2dac876eed6779b840b761dd8c7cff09a43f861fd2e39a42b82652d01bcf4aa0384afc1d4d253b9eb82d4f2adb21
7
+ data.tar.gz: 8efa63c8c6c7c5c8869e507e64932167082f27c85d2c3bd026a9bc29d8c052d8e26612e35c06f046b2c9b5c8b6dd102d20a29d7ef67a7babc4c0ad0f990f471b
@@ -1,4 +1,4 @@
1
- # 0.13.1
1
+ # 0.13.2
2
2
 
3
3
  ## Improvements
4
4
 
@@ -0,0 +1,5 @@
1
+ # 0.14.4
2
+
3
+ ## Bugfixes
4
+
5
+ * The HTTP/1 handler was miscalculating the last request for a given connection, and potentially freezing it.
@@ -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
@@ -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,10 +263,24 @@ module HTTPX
258
263
  request.chunk!
259
264
  end
260
265
 
261
- connection = if request.options.persistent || request != @requests[-1]
262
- "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
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]
@@ -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