httpx 0.8.0 → 0.10.1

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.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +48 -0
  3. data/README.md +9 -5
  4. data/doc/release_notes/0_0_1.md +7 -0
  5. data/doc/release_notes/0_0_2.md +9 -0
  6. data/doc/release_notes/0_0_3.md +9 -0
  7. data/doc/release_notes/0_0_4.md +7 -0
  8. data/doc/release_notes/0_0_5.md +5 -0
  9. data/doc/release_notes/0_10_0.md +66 -0
  10. data/doc/release_notes/0_10_1.md +39 -0
  11. data/doc/release_notes/0_1_0.md +9 -0
  12. data/doc/release_notes/0_2_0.md +5 -0
  13. data/doc/release_notes/0_2_1.md +16 -0
  14. data/doc/release_notes/0_3_0.md +12 -0
  15. data/doc/release_notes/0_3_1.md +6 -0
  16. data/doc/release_notes/0_4_0.md +51 -0
  17. data/doc/release_notes/0_4_1.md +3 -0
  18. data/doc/release_notes/0_5_0.md +15 -0
  19. data/doc/release_notes/0_5_1.md +14 -0
  20. data/doc/release_notes/0_6_0.md +5 -0
  21. data/doc/release_notes/0_6_1.md +6 -0
  22. data/doc/release_notes/0_6_2.md +6 -0
  23. data/doc/release_notes/0_6_3.md +13 -0
  24. data/doc/release_notes/0_6_4.md +21 -0
  25. data/doc/release_notes/0_6_5.md +22 -0
  26. data/doc/release_notes/0_6_6.md +19 -0
  27. data/doc/release_notes/0_6_7.md +5 -0
  28. data/doc/release_notes/0_7_0.md +46 -0
  29. data/doc/release_notes/0_8_0.md +27 -0
  30. data/doc/release_notes/0_8_1.md +8 -0
  31. data/doc/release_notes/0_8_2.md +7 -0
  32. data/doc/release_notes/0_9_0.md +38 -0
  33. data/lib/httpx.rb +2 -0
  34. data/lib/httpx/adapters/faraday.rb +1 -1
  35. data/lib/httpx/chainable.rb +11 -11
  36. data/lib/httpx/connection.rb +23 -31
  37. data/lib/httpx/connection/http1.rb +30 -4
  38. data/lib/httpx/connection/http2.rb +29 -10
  39. data/lib/httpx/domain_name.rb +440 -0
  40. data/lib/httpx/errors.rb +2 -1
  41. data/lib/httpx/extensions.rb +22 -2
  42. data/lib/httpx/headers.rb +2 -2
  43. data/lib/httpx/io/ssl.rb +0 -1
  44. data/lib/httpx/io/tcp.rb +6 -5
  45. data/lib/httpx/io/udp.rb +4 -1
  46. data/lib/httpx/options.rb +5 -1
  47. data/lib/httpx/parser/http1.rb +14 -17
  48. data/lib/httpx/plugins/compression.rb +46 -65
  49. data/lib/httpx/plugins/compression/brotli.rb +10 -14
  50. data/lib/httpx/plugins/compression/deflate.rb +7 -6
  51. data/lib/httpx/plugins/compression/gzip.rb +23 -5
  52. data/lib/httpx/plugins/cookies.rb +21 -60
  53. data/lib/httpx/plugins/cookies/cookie.rb +173 -0
  54. data/lib/httpx/plugins/cookies/jar.rb +74 -0
  55. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
  56. data/lib/httpx/plugins/expect.rb +12 -1
  57. data/lib/httpx/plugins/follow_redirects.rb +20 -2
  58. data/lib/httpx/plugins/h2c.rb +1 -1
  59. data/lib/httpx/plugins/multipart.rb +12 -6
  60. data/lib/httpx/plugins/persistent.rb +6 -1
  61. data/lib/httpx/plugins/proxy.rb +16 -2
  62. data/lib/httpx/plugins/proxy/socks4.rb +14 -14
  63. data/lib/httpx/plugins/rate_limiter.rb +51 -0
  64. data/lib/httpx/plugins/retries.rb +3 -2
  65. data/lib/httpx/plugins/stream.rb +109 -13
  66. data/lib/httpx/pool.rb +14 -17
  67. data/lib/httpx/request.rb +8 -20
  68. data/lib/httpx/resolver.rb +7 -10
  69. data/lib/httpx/resolver/https.rb +22 -24
  70. data/lib/httpx/resolver/native.rb +19 -16
  71. data/lib/httpx/resolver/resolver_mixin.rb +4 -2
  72. data/lib/httpx/resolver/system.rb +2 -2
  73. data/lib/httpx/response.rb +16 -25
  74. data/lib/httpx/selector.rb +11 -18
  75. data/lib/httpx/session.rb +40 -26
  76. data/lib/httpx/transcoder.rb +18 -0
  77. data/lib/httpx/transcoder/chunker.rb +0 -2
  78. data/lib/httpx/transcoder/form.rb +9 -7
  79. data/lib/httpx/transcoder/json.rb +0 -4
  80. data/lib/httpx/utils.rb +45 -0
  81. data/lib/httpx/version.rb +1 -1
  82. data/sig/buffer.rbs +24 -0
  83. data/sig/callbacks.rbs +14 -0
  84. data/sig/chainable.rbs +37 -0
  85. data/sig/connection.rbs +85 -0
  86. data/sig/connection/http1.rbs +66 -0
  87. data/sig/connection/http2.rbs +78 -0
  88. data/sig/domain_name.rbs +17 -0
  89. data/sig/errors.rbs +3 -0
  90. data/sig/headers.rbs +42 -0
  91. data/sig/httpx.rbs +15 -0
  92. data/sig/loggable.rbs +11 -0
  93. data/sig/missing.rbs +12 -0
  94. data/sig/options.rbs +118 -0
  95. data/sig/parser/http1.rbs +50 -0
  96. data/sig/plugins/authentication.rbs +11 -0
  97. data/sig/plugins/basic_authentication.rbs +13 -0
  98. data/sig/plugins/compression.rbs +55 -0
  99. data/sig/plugins/compression/brotli.rbs +21 -0
  100. data/sig/plugins/compression/deflate.rbs +17 -0
  101. data/sig/plugins/compression/gzip.rbs +29 -0
  102. data/sig/plugins/cookies.rbs +26 -0
  103. data/sig/plugins/cookies/cookie.rbs +50 -0
  104. data/sig/plugins/cookies/jar.rbs +27 -0
  105. data/sig/plugins/digest_authentication.rbs +33 -0
  106. data/sig/plugins/expect.rbs +19 -0
  107. data/sig/plugins/follow_redirects.rbs +37 -0
  108. data/sig/plugins/h2c.rbs +26 -0
  109. data/sig/plugins/multipart.rbs +21 -0
  110. data/sig/plugins/persistent.rbs +17 -0
  111. data/sig/plugins/proxy.rbs +47 -0
  112. data/sig/plugins/proxy/http.rbs +14 -0
  113. data/sig/plugins/proxy/socks4.rbs +33 -0
  114. data/sig/plugins/proxy/socks5.rbs +36 -0
  115. data/sig/plugins/proxy/ssh.rbs +18 -0
  116. data/sig/plugins/push_promise.rbs +22 -0
  117. data/sig/plugins/rate_limiter.rbs +11 -0
  118. data/sig/plugins/retries.rbs +48 -0
  119. data/sig/plugins/stream.rbs +39 -0
  120. data/sig/pool.rbs +36 -0
  121. data/sig/registry.rbs +9 -0
  122. data/sig/request.rbs +61 -0
  123. data/sig/resolver.rbs +26 -0
  124. data/sig/resolver/https.rbs +49 -0
  125. data/sig/resolver/native.rbs +60 -0
  126. data/sig/resolver/resolver_mixin.rbs +27 -0
  127. data/sig/resolver/system.rbs +17 -0
  128. data/sig/response.rbs +87 -0
  129. data/sig/selector.rbs +20 -0
  130. data/sig/session.rbs +49 -0
  131. data/sig/timeout.rbs +29 -0
  132. data/sig/transcoder.rbs +18 -0
  133. data/sig/transcoder/body.rbs +18 -0
  134. data/sig/transcoder/chunker.rbs +32 -0
  135. data/sig/transcoder/form.rbs +16 -0
  136. data/sig/transcoder/json.rbs +14 -0
  137. metadata +128 -22
  138. data/lib/httpx/resolver/options.rb +0 -25
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
3
4
  require "io/wait"
4
5
  require "http/2/next"
5
6
 
@@ -25,6 +26,7 @@ module HTTPX
25
26
  @pending = []
26
27
  @streams = {}
27
28
  @drains = {}
29
+ @pings = []
28
30
  @buffer = buffer
29
31
  @handshake_completed = false
30
32
  init_connection
@@ -36,7 +38,9 @@ module HTTPX
36
38
 
37
39
  return :w if @connection.state == :closed
38
40
 
39
- return :r unless (@connection.state == :connected && @handshake_completed)
41
+ unless (@connection.state == :connected && @handshake_completed)
42
+ return @buffer.empty? ? :r : :rw
43
+ end
40
44
 
41
45
  return :w unless @pending.empty?
42
46
 
@@ -47,10 +51,6 @@ module HTTPX
47
51
  :rw
48
52
  end
49
53
 
50
- def reset
51
- init_connection
52
- end
53
-
54
54
  def close
55
55
  @connection.goaway unless @connection.state == :closed
56
56
  emit(:close)
@@ -107,6 +107,13 @@ module HTTPX
107
107
  end
108
108
  end
109
109
 
110
+ def ping
111
+ ping = SecureRandom.gen_random(8)
112
+ @connection.ping(ping)
113
+ ensure
114
+ @pings << ping
115
+ end
116
+
110
117
  private
111
118
 
112
119
  def send_pending
@@ -141,6 +148,7 @@ module HTTPX
141
148
  @connection.on(:promise, &method(:on_promise))
142
149
  @connection.on(:altsvc) { |frame| on_altsvc(frame[:origin], frame) }
143
150
  @connection.on(:settings_ack, &method(:on_settings))
151
+ @connection.on(:ack, &method(:on_pong))
144
152
  @connection.on(:goaway, &method(:on_close))
145
153
  #
146
154
  # Some servers initiate HTTP/2 negotiation right away, some don't.
@@ -151,6 +159,9 @@ module HTTPX
151
159
  @connection.send_connection_preface
152
160
  end
153
161
 
162
+ alias_method :reset, :init_connection
163
+ public :reset
164
+
154
165
  def handle_stream(stream, request)
155
166
  stream.on(:close, &method(:on_stream_close).curry[stream, request])
156
167
  stream.on(:half_close) do
@@ -258,16 +269,16 @@ module HTTPX
258
269
  end
259
270
 
260
271
  def on_close(_last_frame, error, _payload)
272
+ is_connection_closed = @connection.state == :closed
261
273
  if error && error != :no_error
274
+ @buffer.clear if is_connection_closed
262
275
  ex = Error.new(0, error)
263
276
  ex.set_backtrace(caller)
264
- @streams.each_key do |request|
265
- emit(:error, request, ex)
266
- end
277
+ handle_error(ex)
267
278
  end
268
- return unless @connection.state == :closed && @streams.size.zero?
279
+ return unless is_connection_closed && @streams.size.zero?
269
280
 
270
- emit(:close)
281
+ emit(:close, is_connection_closed)
271
282
  end
272
283
 
273
284
  def on_frame_sent(frame)
@@ -304,6 +315,14 @@ module HTTPX
304
315
  emit(:origin, origin)
305
316
  end
306
317
 
318
+ def on_pong(ping)
319
+ if !@pings.delete(ping.to_s)
320
+ close(:protocol_error, "ping payload did not match")
321
+ else
322
+ emit(:pong)
323
+ end
324
+ end
325
+
307
326
  def respond_to_missing?(meth, *args)
308
327
  @connection.respond_to?(meth, *args) || super
309
328
  end
@@ -0,0 +1,440 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # domain_name.rb - Domain Name manipulation library for Ruby
5
+ #
6
+ # Copyright (C) 2011-2017 Akinori MUSHA, All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions
10
+ # are met:
11
+ # 1. Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ # 2. Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
+ # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
+ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
+ # SUCH DAMAGE.
28
+
29
+ require "ipaddr"
30
+
31
+ module HTTPX
32
+ # Represents a domain name ready for extracting its registered domain
33
+ # and TLD.
34
+ class DomainName
35
+ include Comparable
36
+
37
+ # The full host name normalized, ASCII-ized and downcased using the
38
+ # Unicode NFC rules and the Punycode algorithm. If initialized with
39
+ # an IP address, the string representation of the IP address
40
+ # suitable for opening a connection to.
41
+ attr_reader :hostname
42
+
43
+ # The Unicode representation of the #hostname property.
44
+ #
45
+ # :attr_reader: hostname_idn
46
+
47
+ # The least "universally original" domain part of this domain name.
48
+ # For example, "example.co.uk" for "www.sub.example.co.uk". This
49
+ # may be nil if the hostname does not have one, like when it is an
50
+ # IP address, an effective TLD or higher itself, or of a
51
+ # non-canonical domain.
52
+ attr_reader :domain
53
+
54
+ DOT = "." # :nodoc:
55
+
56
+ class << self
57
+ def new(domain)
58
+ return domain if domain.is_a?(self)
59
+
60
+ super(domain)
61
+ end
62
+
63
+ # Normalizes a _domain_ using the Punycode algorithm as necessary.
64
+ # The result will be a downcased, ASCII-only string.
65
+ def normalize(domain)
66
+ domain = domain.ascii_only? ? domain : domain.chomp(DOT).unicode_normalize(:nfc)
67
+ Punycode.encode_hostname(domain).downcase
68
+ end
69
+ end
70
+
71
+ # Parses _hostname_ into a DomainName object. An IP address is also
72
+ # accepted. An IPv6 address may be enclosed in square brackets.
73
+ def initialize(hostname)
74
+ hostname = String(hostname)
75
+
76
+ raise ArgumentError, "domain name must not start with a dot: #{hostname}" if hostname.start_with?(DOT)
77
+
78
+ begin
79
+ @ipaddr = IPAddr.new(hostname)
80
+ @hostname = @ipaddr.to_s
81
+ return
82
+ rescue IPAddr::Error
83
+ nil
84
+ end
85
+
86
+ @hostname = DomainName.normalize(hostname)
87
+ tld = if (last_dot = @hostname.rindex(DOT))
88
+ @hostname[(last_dot + 1)..-1]
89
+ else
90
+ @hostname
91
+ end
92
+
93
+ # unknown/local TLD
94
+ @domain = if last_dot
95
+ # fallback - accept cookies down to second level
96
+ # cf. http://www.dkim-reputation.org/regdom-libs/
97
+ if (penultimate_dot = @hostname.rindex(DOT, last_dot - 1))
98
+ @hostname[(penultimate_dot + 1)..-1]
99
+ else
100
+ @hostname
101
+ end
102
+ else
103
+ # no domain part - must be a local hostname
104
+ tld
105
+ end
106
+ end
107
+
108
+ # Checks if the server represented by this domain is qualified to
109
+ # send and receive cookies with a domain attribute value of
110
+ # _domain_. A true value given as the second argument represents
111
+ # cookies without a domain attribute value, in which case only
112
+ # hostname equality is checked.
113
+ def cookie_domain?(domain, host_only = false)
114
+ # RFC 6265 #5.3
115
+ # When the user agent "receives a cookie":
116
+ return self == @domain if host_only
117
+
118
+ domain = DomainName.new(domain)
119
+
120
+ # RFC 6265 #5.1.3
121
+ # Do not perform subdomain matching against IP addresses.
122
+ @hostname == domain.hostname if @ipaddr
123
+
124
+ # RFC 6265 #4.1.1
125
+ # Domain-value must be a subdomain.
126
+ @domain && self <= domain && domain <= @domain ? true : false
127
+ end
128
+
129
+ # def ==(other)
130
+ # other = DomainName.new(other)
131
+ # other.hostname == @hostname
132
+ # end
133
+
134
+ def <=>(other)
135
+ other = DomainName.new(other)
136
+ othername = other.hostname
137
+ if othername == @hostname
138
+ 0
139
+ elsif @hostname.end_with?(othername) && @hostname[-othername.size - 1, 1] == DOT
140
+ # The other is higher
141
+ -1
142
+ elsif othername.end_with?(@hostname) && othername[-@hostname.size - 1, 1] == DOT
143
+ # The other is lower
144
+ 1
145
+ else
146
+ 1
147
+ end
148
+ end
149
+
150
+ # :nocov:
151
+ # rubocop:disable all
152
+ # -*- coding: utf-8 -*-
153
+ #--
154
+ # punycode.rb - PunyCode encoder for the Domain Name library
155
+ #
156
+ # Copyright (C) 2011-2017 Akinori MUSHA, All rights reserved.
157
+ #
158
+ # Ported from puny.c, a part of VeriSign XCode (encode/decode) IDN
159
+ # Library.
160
+ #
161
+ # Copyright (C) 2000-2002 Verisign Inc., All rights reserved.
162
+ #
163
+ # Redistribution and use in source and binary forms, with or
164
+ # without modification, are permitted provided that the following
165
+ # conditions are met:
166
+ #
167
+ # 1) Redistributions of source code must retain the above copyright
168
+ # notice, this list of conditions and the following disclaimer.
169
+ #
170
+ # 2) Redistributions in binary form must reproduce the above copyright
171
+ # notice, this list of conditions and the following disclaimer in
172
+ # the documentation and/or other materials provided with the
173
+ # distribution.
174
+ #
175
+ # 3) Neither the name of the VeriSign Inc. nor the names of its
176
+ # contributors may be used to endorse or promote products derived
177
+ # from this software without specific prior written permission.
178
+ #
179
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
180
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
181
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
182
+ # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
183
+ # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
184
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
185
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
186
+ # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
187
+ # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
188
+ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
189
+ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
190
+ # POSSIBILITY OF SUCH DAMAGE.
191
+ #
192
+ # This software is licensed under the BSD open source license. For more
193
+ # information visit www.opensource.org.
194
+ #
195
+ # Authors:
196
+ # John Colosi (VeriSign)
197
+ # Srikanth Veeramachaneni (VeriSign)
198
+ # Nagesh Chigurupati (Verisign)
199
+ # Praveen Srinivasan(Verisign)
200
+ #++
201
+ module Punycode
202
+ BASE = 36
203
+ TMIN = 1
204
+ TMAX = 26
205
+ SKEW = 38
206
+ DAMP = 700
207
+ INITIAL_BIAS = 72
208
+ INITIAL_N = 0x80
209
+ DELIMITER = "-"
210
+
211
+ MAXINT = (1 << 32) - 1
212
+
213
+ LOBASE = BASE - TMIN
214
+ CUTOFF = LOBASE * TMAX / 2
215
+
216
+ RE_NONBASIC = /[^\x00-\x7f]/.freeze
217
+
218
+ # Returns the numeric value of a basic code point (for use in
219
+ # representing integers) in the range 0 to base-1, or nil if cp
220
+ # is does not represent a value.
221
+ DECODE_DIGIT = {}.tap do |map|
222
+ # ASCII A..Z map to 0..25
223
+ # ASCII a..z map to 0..25
224
+ (0..25).each { |i| map[65 + i] = map[97 + i] = i }
225
+ # ASCII 0..9 map to 26..35
226
+ (26..35).each { |i| map[22 + i] = i }
227
+ end
228
+
229
+ # Returns the basic code point whose value (when used for
230
+ # representing integers) is d, which must be in the range 0 to
231
+ # BASE-1. The lowercase form is used unless flag is true, in
232
+ # which case the uppercase form is used. The behavior is
233
+ # undefined if flag is nonzero and digit d has no uppercase
234
+ # form.
235
+ ENCODE_DIGIT = proc { |d, flag|
236
+ (d + 22 + (d < 26 ? 75 : 0) - (flag ? (1 << 5) : 0)).chr
237
+ # 0..25 map to ASCII a..z or A..Z
238
+ # 26..35 map to ASCII 0..9
239
+ }
240
+
241
+ DOT = "."
242
+ PREFIX = "xn--"
243
+
244
+ # Most errors we raise are basically kind of ArgumentError.
245
+ class ArgumentError < ::ArgumentError; end
246
+ class BufferOverflowError < ArgumentError; end
247
+
248
+ class << self
249
+ # Encode a +string+ in Punycode
250
+ def encode(string)
251
+ input = string.unpack("U*")
252
+ output = +""
253
+
254
+ # Initialize the state
255
+ n = INITIAL_N
256
+ delta = 0
257
+ bias = INITIAL_BIAS
258
+
259
+ # Handle the basic code points
260
+ input.each { |cp| output << cp.chr if cp < 0x80 }
261
+
262
+ h = b = output.length
263
+
264
+ # h is the number of code points that have been handled, b is the
265
+ # number of basic code points, and out is the number of characters
266
+ # that have been output.
267
+
268
+ output << DELIMITER if b > 0
269
+
270
+ # Main encoding loop
271
+
272
+ while h < input.length
273
+ # All non-basic code points < n have been handled already. Find
274
+ # the next larger one
275
+
276
+ m = MAXINT
277
+ input.each do |cp|
278
+ m = cp if (n...m) === cp
279
+ end
280
+
281
+ # Increase delta enough to advance the decoder's <n,i> state to
282
+ # <m,0>, but guard against overflow
283
+
284
+ delta += (m - n) * (h + 1)
285
+ raise BufferOverflowError if delta > MAXINT
286
+
287
+ n = m
288
+
289
+ input.each do |cp|
290
+ # AMC-ACE-Z can use this simplified version instead
291
+ if cp < n
292
+ delta += 1
293
+ raise BufferOverflowError if delta > MAXINT
294
+ elsif cp == n
295
+ # Represent delta as a generalized variable-length integer
296
+ q = delta
297
+ k = BASE
298
+ loop do
299
+ t = k <= bias ? TMIN : k - bias >= TMAX ? TMAX : k - bias
300
+ break if q < t
301
+
302
+ q, r = (q - t).divmod(BASE - t)
303
+ output << ENCODE_DIGIT[t + r, false]
304
+ k += BASE
305
+ end
306
+
307
+ output << ENCODE_DIGIT[q, false]
308
+
309
+ # Adapt the bias
310
+ delta = h == b ? delta / DAMP : delta >> 1
311
+ delta += delta / (h + 1)
312
+ bias = 0
313
+ while delta > CUTOFF
314
+ delta /= LOBASE
315
+ bias += BASE
316
+ end
317
+ bias += (LOBASE + 1) * delta / (delta + SKEW)
318
+
319
+ delta = 0
320
+ h += 1
321
+ end
322
+ end
323
+
324
+ delta += 1
325
+ n += 1
326
+ end
327
+
328
+ output
329
+ end
330
+
331
+ # Encode a hostname using IDN/Punycode algorithms
332
+ def encode_hostname(hostname)
333
+ hostname.match(RE_NONBASIC) || (return hostname)
334
+
335
+ hostname.split(DOT).map do |name|
336
+ if name.match(RE_NONBASIC)
337
+ PREFIX + encode(name)
338
+ else
339
+ name
340
+ end
341
+ end.join(DOT)
342
+ end
343
+
344
+ # Decode a +string+ encoded in Punycode
345
+ def decode(string)
346
+ # Initialize the state
347
+ n = INITIAL_N
348
+ i = 0
349
+ bias = INITIAL_BIAS
350
+
351
+ if j = string.rindex(DELIMITER)
352
+ b = string[0...j]
353
+
354
+ b.match(RE_NONBASIC) &&
355
+ raise(ArgumentError, "Illegal character is found in basic part: #{string.inspect}")
356
+
357
+ # Handle the basic code points
358
+
359
+ output = b.unpack("U*")
360
+ u = string[(j + 1)..-1]
361
+ else
362
+ output = []
363
+ u = string
364
+ end
365
+
366
+ # Main decoding loop: Start just after the last delimiter if any
367
+ # basic code points were copied; start at the beginning
368
+ # otherwise.
369
+
370
+ input = u.unpack("C*")
371
+ input_length = input.length
372
+ h = 0
373
+ out = output.length
374
+
375
+ while h < input_length
376
+ # Decode a generalized variable-length integer into delta,
377
+ # which gets added to i. The overflow checking is easier
378
+ # if we increase i as we go, then subtract off its starting
379
+ # value at the end to obtain delta.
380
+
381
+ oldi = i
382
+ w = 1
383
+ k = BASE
384
+
385
+ loop do
386
+ (digit = DECODE_DIGIT[input[h]]) ||
387
+ raise(ArgumentError, "Illegal character is found in non-basic part: #{string.inspect}")
388
+ h += 1
389
+ i += digit * w
390
+ raise BufferOverflowError if i > MAXINT
391
+
392
+ t = k <= bias ? TMIN : k - bias >= TMAX ? TMAX : k - bias
393
+ break if digit < t
394
+
395
+ w *= BASE - t
396
+ raise BufferOverflowError if w > MAXINT
397
+
398
+ k += BASE
399
+ (h < input_length) || raise(ArgumentError, "Malformed input given: #{string.inspect}")
400
+ end
401
+
402
+ # Adapt the bias
403
+ delta = oldi == 0 ? i / DAMP : (i - oldi) >> 1
404
+ delta += delta / (out + 1)
405
+ bias = 0
406
+ while delta > CUTOFF
407
+ delta /= LOBASE
408
+ bias += BASE
409
+ end
410
+ bias += (LOBASE + 1) * delta / (delta + SKEW)
411
+
412
+ # i was supposed to wrap around from out+1 to 0, incrementing
413
+ # n each time, so we'll fix that now:
414
+
415
+ q, i = i.divmod(out + 1)
416
+ n += q
417
+ raise BufferOverflowError if n > MAXINT
418
+
419
+ # Insert n at position i of the output:
420
+
421
+ output[i, 0] = n
422
+
423
+ out += 1
424
+ i += 1
425
+ end
426
+ output.pack("U*")
427
+ end
428
+
429
+ # Decode a hostname using IDN/Punycode algorithms
430
+ def decode_hostname(hostname)
431
+ hostname.gsub(/(\A|#{Regexp.quote(DOT)})#{Regexp.quote(PREFIX)}([^#{Regexp.quote(DOT)}]*)/o) do
432
+ Regexp.last_match(1) << decode(Regexp.last_match(2))
433
+ end
434
+ end
435
+ end
436
+ # rubocop:enable all
437
+ # :nocov:
438
+ end
439
+ end
440
+ end