httpx 0.20.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (250) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -48
  3. data/README.md +54 -45
  4. data/doc/release_notes/0_10_0.md +2 -2
  5. data/doc/release_notes/0_11_0.md +3 -5
  6. data/doc/release_notes/0_12_0.md +5 -5
  7. data/doc/release_notes/0_13_0.md +5 -5
  8. data/doc/release_notes/0_14_0.md +2 -2
  9. data/doc/release_notes/0_16_0.md +3 -3
  10. data/doc/release_notes/0_17_0.md +1 -1
  11. data/doc/release_notes/0_18_0.md +4 -4
  12. data/doc/release_notes/0_18_2.md +1 -1
  13. data/doc/release_notes/0_19_0.md +1 -1
  14. data/doc/release_notes/0_19_8.md +1 -1
  15. data/doc/release_notes/0_20_0.md +2 -2
  16. data/doc/release_notes/0_20_1.md +5 -0
  17. data/doc/release_notes/0_20_2.md +7 -0
  18. data/doc/release_notes/0_20_3.md +6 -0
  19. data/doc/release_notes/0_20_4.md +17 -0
  20. data/doc/release_notes/0_20_5.md +3 -0
  21. data/doc/release_notes/0_21_0.md +96 -0
  22. data/doc/release_notes/0_21_1.md +12 -0
  23. data/doc/release_notes/0_22_0.md +13 -0
  24. data/doc/release_notes/0_22_1.md +11 -0
  25. data/doc/release_notes/0_22_2.md +5 -0
  26. data/doc/release_notes/0_22_3.md +55 -0
  27. data/doc/release_notes/0_22_4.md +6 -0
  28. data/doc/release_notes/0_22_5.md +6 -0
  29. data/doc/release_notes/0_23_0.md +42 -0
  30. data/doc/release_notes/0_23_1.md +5 -0
  31. data/doc/release_notes/0_23_2.md +5 -0
  32. data/doc/release_notes/0_23_3.md +6 -0
  33. data/doc/release_notes/0_23_4.md +5 -0
  34. data/doc/release_notes/0_24_0.md +48 -0
  35. data/doc/release_notes/0_24_1.md +12 -0
  36. data/doc/release_notes/0_24_2.md +12 -0
  37. data/doc/release_notes/0_24_3.md +12 -0
  38. data/doc/release_notes/0_24_4.md +18 -0
  39. data/doc/release_notes/0_24_5.md +6 -0
  40. data/doc/release_notes/0_24_6.md +5 -0
  41. data/doc/release_notes/0_24_7.md +10 -0
  42. data/doc/release_notes/1_0_0.md +60 -0
  43. data/doc/release_notes/1_0_1.md +5 -0
  44. data/doc/release_notes/1_0_2.md +7 -0
  45. data/doc/release_notes/1_1_0.md +32 -0
  46. data/doc/release_notes/1_1_1.md +17 -0
  47. data/doc/release_notes/1_1_2.md +12 -0
  48. data/doc/release_notes/1_1_3.md +18 -0
  49. data/doc/release_notes/1_1_4.md +6 -0
  50. data/doc/release_notes/1_1_5.md +12 -0
  51. data/doc/release_notes/1_2_0.md +49 -0
  52. data/doc/release_notes/1_2_1.md +6 -0
  53. data/doc/release_notes/1_2_2.md +10 -0
  54. data/doc/release_notes/1_2_3.md +16 -0
  55. data/doc/release_notes/1_2_4.md +8 -0
  56. data/doc/release_notes/1_2_5.md +7 -0
  57. data/doc/release_notes/1_2_6.md +13 -0
  58. data/doc/release_notes/1_3_0.md +18 -0
  59. data/doc/release_notes/1_3_1.md +17 -0
  60. data/lib/httpx/adapters/datadog.rb +215 -122
  61. data/lib/httpx/adapters/faraday.rb +145 -107
  62. data/lib/httpx/adapters/sentry.rb +26 -7
  63. data/lib/httpx/adapters/webmock.rb +34 -18
  64. data/lib/httpx/altsvc.rb +63 -26
  65. data/lib/httpx/base64.rb +27 -0
  66. data/lib/httpx/buffer.rb +12 -0
  67. data/lib/httpx/callbacks.rb +5 -3
  68. data/lib/httpx/chainable.rb +54 -39
  69. data/lib/httpx/connection/http1.rb +75 -44
  70. data/lib/httpx/connection/http2.rb +31 -38
  71. data/lib/httpx/connection.rb +287 -117
  72. data/lib/httpx/domain_name.rb +10 -13
  73. data/lib/httpx/errors.rb +52 -2
  74. data/lib/httpx/extensions.rb +24 -131
  75. data/lib/httpx/io/ssl.rb +83 -77
  76. data/lib/httpx/io/tcp.rb +48 -71
  77. data/lib/httpx/io/udp.rb +18 -52
  78. data/lib/httpx/io/unix.rb +10 -15
  79. data/lib/httpx/io.rb +3 -9
  80. data/lib/httpx/loggable.rb +4 -19
  81. data/lib/httpx/options.rb +176 -118
  82. data/lib/httpx/parser/http1.rb +4 -0
  83. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  84. data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
  85. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  86. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  87. data/lib/httpx/plugins/auth.rb +25 -0
  88. data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
  89. data/lib/httpx/plugins/aws_sigv4.rb +12 -9
  90. data/lib/httpx/plugins/basic_auth.rb +29 -0
  91. data/lib/httpx/plugins/brotli.rb +50 -0
  92. data/lib/httpx/plugins/callbacks.rb +91 -0
  93. data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
  94. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
  95. data/lib/httpx/plugins/circuit_breaker.rb +148 -0
  96. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  97. data/lib/httpx/plugins/cookies.rb +30 -17
  98. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
  99. data/lib/httpx/plugins/expect.rb +21 -14
  100. data/lib/httpx/plugins/follow_redirects.rb +140 -41
  101. data/lib/httpx/plugins/grpc/call.rb +2 -3
  102. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  103. data/lib/httpx/plugins/grpc/message.rb +7 -37
  104. data/lib/httpx/plugins/grpc.rb +36 -29
  105. data/lib/httpx/plugins/h2c.rb +26 -19
  106. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  107. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  108. data/lib/httpx/plugins/oauth.rb +175 -0
  109. data/lib/httpx/plugins/persistent.rb +1 -1
  110. data/lib/httpx/plugins/proxy/http.rb +23 -13
  111. data/lib/httpx/plugins/proxy/socks4.rb +9 -7
  112. data/lib/httpx/plugins/proxy/socks5.rb +11 -9
  113. data/lib/httpx/plugins/proxy.rb +80 -61
  114. data/lib/httpx/plugins/push_promise.rb +1 -1
  115. data/lib/httpx/plugins/rate_limiter.rb +5 -1
  116. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  117. data/lib/httpx/plugins/response_cache/store.rb +62 -25
  118. data/lib/httpx/plugins/response_cache.rb +105 -12
  119. data/lib/httpx/plugins/retries.rb +87 -17
  120. data/lib/httpx/plugins/ssrf_filter.rb +145 -0
  121. data/lib/httpx/plugins/stream.rb +27 -23
  122. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  123. data/lib/httpx/plugins/upgrade.rb +8 -10
  124. data/lib/httpx/plugins/webdav.rb +80 -0
  125. data/lib/httpx/pool/synch_pool.rb +93 -0
  126. data/lib/httpx/pool.rb +102 -27
  127. data/lib/httpx/punycode.rb +9 -291
  128. data/lib/httpx/request/body.rb +154 -0
  129. data/lib/httpx/request.rb +130 -146
  130. data/lib/httpx/resolver/https.rb +62 -27
  131. data/lib/httpx/resolver/multi.rb +9 -13
  132. data/lib/httpx/resolver/native.rb +192 -76
  133. data/lib/httpx/resolver/resolver.rb +34 -9
  134. data/lib/httpx/resolver/system.rb +16 -11
  135. data/lib/httpx/resolver.rb +38 -16
  136. data/lib/httpx/response/body.rb +242 -0
  137. data/lib/httpx/response/buffer.rb +96 -0
  138. data/lib/httpx/response.rb +159 -217
  139. data/lib/httpx/selector.rb +9 -4
  140. data/lib/httpx/session.rb +137 -89
  141. data/lib/httpx/session_extensions.rb +4 -1
  142. data/lib/httpx/timers.rb +34 -8
  143. data/lib/httpx/transcoder/body.rb +0 -2
  144. data/lib/httpx/transcoder/chunker.rb +0 -1
  145. data/lib/httpx/transcoder/deflate.rb +37 -0
  146. data/lib/httpx/transcoder/form.rb +52 -33
  147. data/lib/httpx/transcoder/gzip.rb +74 -0
  148. data/lib/httpx/transcoder/json.rb +21 -8
  149. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  150. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
  151. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  152. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  153. data/lib/httpx/transcoder/multipart.rb +17 -0
  154. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  155. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  156. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  157. data/lib/httpx/transcoder/xml.rb +52 -0
  158. data/lib/httpx/transcoder.rb +5 -6
  159. data/lib/httpx/utils.rb +36 -16
  160. data/lib/httpx/version.rb +1 -1
  161. data/lib/httpx.rb +12 -14
  162. data/sig/altsvc.rbs +33 -0
  163. data/sig/buffer.rbs +2 -1
  164. data/sig/callbacks.rbs +3 -3
  165. data/sig/chainable.rbs +11 -9
  166. data/sig/connection/http1.rbs +8 -7
  167. data/sig/connection/http2.rbs +19 -19
  168. data/sig/connection.rbs +64 -24
  169. data/sig/errors.rbs +22 -3
  170. data/sig/httpx.rbs +5 -4
  171. data/sig/io/ssl.rbs +27 -0
  172. data/sig/io/tcp.rbs +60 -0
  173. data/sig/io/udp.rbs +20 -0
  174. data/sig/io/unix.rbs +27 -0
  175. data/sig/io.rbs +6 -0
  176. data/sig/options.rbs +32 -22
  177. data/sig/parser/http1.rbs +1 -1
  178. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  179. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  180. data/sig/plugins/auth.rbs +13 -0
  181. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  182. data/sig/plugins/brotli.rbs +22 -0
  183. data/sig/plugins/callbacks.rbs +38 -0
  184. data/sig/plugins/circuit_breaker.rbs +71 -0
  185. data/sig/plugins/compression.rbs +7 -5
  186. data/sig/plugins/cookies/jar.rbs +2 -2
  187. data/sig/plugins/cookies.rbs +2 -0
  188. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  189. data/sig/plugins/follow_redirects.rbs +18 -4
  190. data/sig/plugins/grpc/call.rbs +19 -0
  191. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  192. data/sig/plugins/grpc/message.rbs +17 -0
  193. data/sig/plugins/grpc.rbs +7 -32
  194. data/sig/plugins/h2c.rbs +1 -1
  195. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  196. data/sig/plugins/oauth.rbs +54 -0
  197. data/sig/plugins/proxy/http.rbs +3 -0
  198. data/sig/plugins/proxy/socks4.rbs +9 -6
  199. data/sig/plugins/proxy/socks5.rbs +10 -6
  200. data/sig/plugins/proxy/ssh.rbs +1 -1
  201. data/sig/plugins/proxy.rbs +13 -5
  202. data/sig/plugins/push_promise.rbs +3 -3
  203. data/sig/plugins/rate_limiter.rbs +1 -1
  204. data/sig/plugins/response_cache.rbs +36 -7
  205. data/sig/plugins/retries.rbs +30 -8
  206. data/sig/plugins/stream.rbs +24 -17
  207. data/sig/plugins/upgrade.rbs +5 -3
  208. data/sig/pool.rbs +10 -7
  209. data/sig/request/body.rbs +38 -0
  210. data/sig/request.rbs +15 -24
  211. data/sig/resolver/https.rbs +8 -3
  212. data/sig/resolver/native.rbs +17 -4
  213. data/sig/resolver/resolver.rbs +8 -6
  214. data/sig/resolver/system.rbs +2 -0
  215. data/sig/resolver.rbs +9 -5
  216. data/sig/response/body.rbs +53 -0
  217. data/sig/response/buffer.rbs +24 -0
  218. data/sig/response.rbs +24 -39
  219. data/sig/selector.rbs +1 -1
  220. data/sig/session.rbs +29 -18
  221. data/sig/timers.rbs +18 -8
  222. data/sig/transcoder/body.rbs +4 -3
  223. data/sig/transcoder/deflate.rbs +11 -0
  224. data/sig/transcoder/form.rbs +5 -3
  225. data/sig/transcoder/gzip.rbs +24 -0
  226. data/sig/transcoder/json.rbs +8 -3
  227. data/sig/{plugins → transcoder}/multipart.rbs +15 -19
  228. data/sig/transcoder/utils/body_reader.rbs +15 -0
  229. data/sig/transcoder/utils/deflater.rbs +29 -0
  230. data/sig/transcoder/utils/inflater.rbs +12 -0
  231. data/sig/transcoder/xml.rbs +22 -0
  232. data/sig/transcoder.rbs +24 -9
  233. data/sig/utils.rbs +8 -2
  234. metadata +163 -41
  235. data/lib/httpx/plugins/authentication.rb +0 -20
  236. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  237. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  238. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  239. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  240. data/lib/httpx/plugins/compression.rb +0 -164
  241. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  242. data/lib/httpx/plugins/multipart.rb +0 -84
  243. data/lib/httpx/registry.rb +0 -85
  244. data/sig/plugins/authentication.rbs +0 -11
  245. data/sig/plugins/compression/brotli.rbs +0 -21
  246. data/sig/plugins/compression/deflate.rbs +0 -17
  247. data/sig/plugins/compression/gzip.rbs +0 -29
  248. data/sig/registry.rbs +0 -12
  249. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  250. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "uri"
5
+ require "stringio"
6
+ require "zlib"
7
+
8
+ module HTTPX
9
+ module Transcoder
10
+ module GZIP
11
+ class Deflater < Transcoder::Deflater
12
+ def initialize(body)
13
+ @compressed_chunk = "".b
14
+ super
15
+ end
16
+
17
+ def deflate(chunk)
18
+ @deflater ||= Zlib::GzipWriter.new(self)
19
+
20
+ if chunk.nil?
21
+ unless @deflater.closed?
22
+ @deflater.flush
23
+ @deflater.close
24
+ compressed_chunk
25
+ end
26
+ else
27
+ @deflater.write(chunk)
28
+ compressed_chunk
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def write(chunk)
35
+ @compressed_chunk << chunk
36
+ end
37
+
38
+ def compressed_chunk
39
+ @compressed_chunk.dup
40
+ ensure
41
+ @compressed_chunk.clear
42
+ end
43
+ end
44
+
45
+ class Inflater
46
+ def initialize(bytesize)
47
+ @inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
48
+ @bytesize = bytesize
49
+ end
50
+
51
+ def call(chunk)
52
+ buffer = @inflater.inflate(chunk)
53
+ @bytesize -= chunk.bytesize
54
+ if @bytesize <= 0
55
+ buffer << @inflater.finish
56
+ @inflater.close
57
+ end
58
+ buffer
59
+ end
60
+ end
61
+
62
+ module_function
63
+
64
+ def encode(body)
65
+ Deflater.new(body)
66
+ end
67
+
68
+ def decode(response, bytesize: nil)
69
+ bytesize ||= response.headers.key?("content-length") ? response.headers["content-length"].to_i : Float::INFINITY
70
+ Inflater.new(bytesize)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,16 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "forwardable"
4
- require "json"
5
4
 
6
5
  module HTTPX::Transcoder
7
6
  module JSON
8
- JSON_REGEX = %r{\bapplication/(?:vnd\.api\+)?json\b}i.freeze
9
-
10
- using HTTPX::RegexpExtensions unless Regexp.method_defined?(:match?)
11
-
12
7
  module_function
13
8
 
9
+ JSON_REGEX = %r{\bapplication/(?:vnd\.api\+|hal\+)?json\b}i.freeze
10
+
14
11
  class Encoder
15
12
  extend Forwardable
16
13
 
@@ -19,7 +16,7 @@ module HTTPX::Transcoder
19
16
  def_delegator :@raw, :bytesize
20
17
 
21
18
  def initialize(json)
22
- @raw = ::JSON.dump(json)
19
+ @raw = JSON.json_dump(json)
23
20
  @charset = @raw.encoding.name.downcase
24
21
  end
25
22
 
@@ -37,8 +34,24 @@ module HTTPX::Transcoder
37
34
 
38
35
  raise HTTPX::Error, "invalid json mime type (#{content_type})" unless JSON_REGEX.match?(content_type)
39
36
 
40
- ::JSON.method(:parse)
37
+ method(:json_load)
38
+ end
39
+
40
+ # rubocop:disable Style/SingleLineMethods
41
+ if defined?(MultiJson)
42
+ def json_load(*args); MultiJson.load(*args); end
43
+ def json_dump(*args); MultiJson.dump(*args); end
44
+ elsif defined?(Oj)
45
+ def json_load(response, *args); Oj.load(response.to_s, *args); end
46
+ def json_dump(*args); Oj.dump(*args); end
47
+ elsif defined?(Yajl)
48
+ def json_load(response, *args); Yajl::Parser.new(*args).parse(response.to_s); end
49
+ def json_dump(*args); Yajl::Encoder.encode(*args); end
50
+ else
51
+ require "json"
52
+ def json_load(*args); ::JSON.parse(*args); end
53
+ def json_dump(*args); ::JSON.dump(*args); end
41
54
  end
55
+ # rubocop:enable Style/SingleLineMethods
42
56
  end
43
- register "json", JSON
44
57
  end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require "delegate"
5
+
6
+ module HTTPX
7
+ module Transcoder
8
+ module Multipart
9
+ class FilePart < SimpleDelegator
10
+ attr_reader :original_filename, :content_type
11
+
12
+ def initialize(filename, content_type)
13
+ @original_filename = filename
14
+ @content_type = content_type
15
+ @file = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
16
+ super(@file)
17
+ end
18
+ end
19
+
20
+ class Decoder
21
+ include HTTPX::Utils
22
+
23
+ CRLF = "\r\n"
24
+ BOUNDARY_RE = /;\s*boundary=([^;]+)/i.freeze
25
+ MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{CRLF}/ni.freeze
26
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni.freeze
27
+ MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{CRLF}]*)/ni.freeze
28
+ WINDOW_SIZE = 2 << 14
29
+
30
+ def initialize(response)
31
+ @boundary = begin
32
+ m = response.headers["content-type"].to_s[BOUNDARY_RE, 1]
33
+ raise Error, "no boundary declared in content-type header" unless m
34
+
35
+ m.strip
36
+ end
37
+ @buffer = "".b
38
+ @parts = {}
39
+ @intermediate_boundary = "--#{@boundary}"
40
+ @state = :idle
41
+ end
42
+
43
+ def call(response, *)
44
+ response.body.each do |chunk|
45
+ @buffer << chunk
46
+
47
+ parse
48
+ end
49
+
50
+ raise Error, "invalid or unsupported multipart format" unless @buffer.empty?
51
+
52
+ @parts
53
+ end
54
+
55
+ private
56
+
57
+ def parse
58
+ case @state
59
+ when :idle
60
+ raise Error, "payload does not start with boundary" unless @buffer.start_with?("#{@intermediate_boundary}#{CRLF}")
61
+
62
+ @buffer = @buffer.byteslice(@intermediate_boundary.bytesize + 2..-1)
63
+
64
+ @state = :part_header
65
+ when :part_header
66
+ idx = @buffer.index("#{CRLF}#{CRLF}")
67
+
68
+ # raise Error, "couldn't parse part headers" unless idx
69
+ return unless idx
70
+
71
+ head = @buffer.byteslice(0..idx + 4 - 1)
72
+
73
+ @buffer = @buffer.byteslice(head.bytesize..-1)
74
+
75
+ content_type = head[MULTIPART_CONTENT_TYPE, 1]
76
+ if (name = head[MULTIPART_CONTENT_DISPOSITION, 1])
77
+ name = /\A"(.*)"\Z/ =~ name ? Regexp.last_match(1) : name.dup
78
+ name.gsub!(/\\(.)/, "\\1")
79
+ name
80
+ else
81
+ name = head[MULTIPART_CONTENT_ID, 1]
82
+ end
83
+
84
+ filename = HTTPX::Utils.get_filename(head)
85
+
86
+ name = filename || +"#{content_type || "text/plain"}[]" if name.nil? || name.empty?
87
+
88
+ @current = name
89
+
90
+ @parts[name] = if filename
91
+ FilePart.new(filename, content_type)
92
+ else
93
+ "".b
94
+ end
95
+
96
+ @state = :part_body
97
+ when :part_body
98
+ part = @parts[@current]
99
+
100
+ body_separator = if part.is_a?(FilePart)
101
+ "#{CRLF}#{CRLF}"
102
+ else
103
+ CRLF
104
+ end
105
+ idx = @buffer.index(body_separator)
106
+
107
+ if idx
108
+ payload = @buffer.byteslice(0..idx - 1)
109
+ @buffer = @buffer.byteslice(idx + body_separator.bytesize..-1)
110
+ part << payload
111
+ part.rewind if part.respond_to?(:rewind)
112
+ @state = :parse_boundary
113
+ else
114
+ part << @buffer
115
+ @buffer.clear
116
+ end
117
+ when :parse_boundary
118
+ raise Error, "payload does not start with boundary" unless @buffer.start_with?(@intermediate_boundary)
119
+
120
+ @buffer = @buffer.byteslice(@intermediate_boundary.bytesize..-1)
121
+
122
+ if @buffer == "--"
123
+ @buffer.clear
124
+ @state = :done
125
+ return
126
+ elsif @buffer.start_with?(CRLF)
127
+ @buffer = @buffer.byteslice(2..-1)
128
+ @state = :part_header
129
+ else
130
+ return
131
+ end
132
+ when :done
133
+ raise Error, "parsing should have been over by now"
134
+ end until @buffer.empty?
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module HTTPX::Plugins
4
- module Multipart
3
+ module HTTPX
4
+ module Transcoder::Multipart
5
5
  class Encoder
6
6
  attr_reader :bytesize
7
7
 
@@ -19,7 +19,7 @@ module HTTPX::Plugins
19
19
  end
20
20
 
21
21
  def read(length = nil, outbuf = nil)
22
- data = outbuf.clear.force_encoding(Encoding::BINARY) if outbuf
22
+ data = String(outbuf).clear.force_encoding(Encoding::BINARY) if outbuf
23
23
  data ||= "".b
24
24
 
25
25
  read_chunks(data, length)
@@ -43,7 +43,7 @@ module HTTPX::Plugins
43
43
  def to_parts(form)
44
44
  @bytesize = 0
45
45
  params = form.each_with_object([]) do |(key, val), aux|
46
- Multipart.normalize_keys(key, val) do |k, v|
46
+ Transcoder.normalize_keys(key, val, MULTIPART_VALUE_COND) do |k, v|
47
47
  next if v.nil?
48
48
 
49
49
  value, content_type, filename = Part.call(v)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- module Plugins::Multipart
4
+ module Transcoder::Multipart
5
5
  module MimeTypeDetector
6
6
  module_function
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- module Plugins::Multipart
4
+ module Transcoder::Multipart
5
5
  module Part
6
6
  module_function
7
7
 
@@ -21,7 +21,8 @@ module HTTPX
21
21
 
22
22
  value = value.open(File::RDONLY) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
23
23
 
24
- if value.is_a?(File)
24
+ if value.respond_to?(:path) && value.respond_to?(:read)
25
+ # either a File, a Tempfile, or something else which has to quack like a file
25
26
  filename ||= File.basename(value.path)
26
27
  content_type ||= MimeTypeDetector.call(value, filename) || "application/octet-stream"
27
28
  [value, content_type, filename]
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "multipart/encoder"
4
+ require_relative "multipart/decoder"
5
+ require_relative "multipart/part"
6
+ require_relative "multipart/mime_type_detector"
7
+
8
+ module HTTPX::Transcoder
9
+ module Multipart
10
+ MULTIPART_VALUE_COND = lambda do |value|
11
+ value.respond_to?(:read) ||
12
+ (value.respond_to?(:to_hash) &&
13
+ value.key?(:body) &&
14
+ (value.key?(:filename) || value.key?(:content_type)))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module HTTPX
6
+ module Transcoder
7
+ class BodyReader
8
+ def initialize(body)
9
+ @body = if body.respond_to?(:read)
10
+ body.rewind if body.respond_to?(:rewind)
11
+ body
12
+ elsif body.respond_to?(:each)
13
+ body.enum_for(:each)
14
+ else
15
+ StringIO.new(body.to_s)
16
+ end
17
+ end
18
+
19
+ def bytesize
20
+ return @body.bytesize if @body.respond_to?(:bytesize)
21
+
22
+ Float::INFINITY
23
+ end
24
+
25
+ def read(length = nil, outbuf = nil)
26
+ return @body.read(length, outbuf) if @body.respond_to?(:read)
27
+
28
+ begin
29
+ chunk = @body.next
30
+ if outbuf
31
+ outbuf.clear.force_encoding(Encoding::BINARY)
32
+ outbuf << chunk
33
+ else
34
+ outbuf = chunk
35
+ end
36
+ outbuf unless length && outbuf.empty?
37
+ rescue StopIteration
38
+ end
39
+ end
40
+
41
+ def close
42
+ @body.close if @body.respond_to?(:close)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require_relative "body_reader"
5
+
6
+ module HTTPX
7
+ module Transcoder
8
+ class Deflater
9
+ extend Forwardable
10
+
11
+ attr_reader :content_type
12
+
13
+ def initialize(body)
14
+ @content_type = body.content_type
15
+ @body = BodyReader.new(body)
16
+ @closed = false
17
+ end
18
+
19
+ def bytesize
20
+ buffer_deflate!
21
+
22
+ @buffer.size
23
+ end
24
+
25
+ def read(length = nil, outbuf = nil)
26
+ return @buffer.read(length, outbuf) if @buffer
27
+
28
+ return if @closed
29
+
30
+ chunk = @body.read(length)
31
+
32
+ compressed_chunk = deflate(chunk)
33
+
34
+ return unless compressed_chunk
35
+
36
+ if outbuf
37
+ outbuf.clear.force_encoding(Encoding::BINARY)
38
+ outbuf << compressed_chunk
39
+ else
40
+ compressed_chunk
41
+ end
42
+ end
43
+
44
+ def close
45
+ return if @closed
46
+
47
+ @buffer.close if @buffer
48
+
49
+ @body.close
50
+
51
+ @closed = true
52
+ end
53
+
54
+ private
55
+
56
+ # rubocop:disable Naming/MemoizedInstanceVariableName
57
+ def buffer_deflate!
58
+ return @buffer if defined?(@buffer)
59
+
60
+ buffer = Response::Buffer.new(
61
+ threshold_size: Options::MAX_BODY_THRESHOLD_SIZE
62
+ )
63
+ ::IO.copy_stream(self, buffer)
64
+
65
+ buffer.rewind
66
+
67
+ @buffer = buffer
68
+ end
69
+ # rubocop:enable Naming/MemoizedInstanceVariableName
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ module HTTPX
2
+ module Transcoder
3
+ class Inflater
4
+ def initialize(bytesize)
5
+ @bytesize = bytesize
6
+ end
7
+
8
+ def call(chunk)
9
+ buffer = @inflater.inflate(chunk)
10
+ @bytesize -= chunk.bytesize
11
+ if @bytesize <= 0
12
+ buffer << @inflater.finish
13
+ @inflater.close
14
+ end
15
+ buffer
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "forwardable"
5
+ require "uri"
6
+
7
+ module HTTPX::Transcoder
8
+ module Xml
9
+ module_function
10
+
11
+ MIME_TYPES = %r{\b(application|text)/(.+\+)?xml\b}.freeze
12
+
13
+ class Encoder
14
+ def initialize(xml)
15
+ @raw = xml
16
+ end
17
+
18
+ def content_type
19
+ charset = @raw.respond_to?(:encoding) ? @raw.encoding.to_s.downcase : "utf-8"
20
+ "application/xml; charset=#{charset}"
21
+ end
22
+
23
+ def bytesize
24
+ @raw.to_s.bytesize
25
+ end
26
+
27
+ def to_s
28
+ @raw.to_s
29
+ end
30
+ end
31
+
32
+ def encode(xml)
33
+ Encoder.new(xml)
34
+ end
35
+
36
+ begin
37
+ require "nokogiri"
38
+
39
+ def decode(response)
40
+ content_type = response.content_type.mime_type
41
+
42
+ raise HTTPX::Error, "invalid form mime type (#{content_type})" unless MIME_TYPES.match?(content_type)
43
+
44
+ Nokogiri::XML.method(:parse)
45
+ end
46
+ rescue LoadError
47
+ def decode(_response)
48
+ raise HTTPX::Error, "\"nokogiri\" is required in order to decode XML"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,14 +2,10 @@
2
2
 
3
3
  module HTTPX
4
4
  module Transcoder
5
- extend Registry
6
-
7
- using RegexpExtensions unless Regexp.method_defined?(:match?)
8
-
9
5
  module_function
10
6
 
11
7
  def normalize_keys(key, value, cond = nil, &block)
12
- if (cond && cond.call(value))
8
+ if cond && cond.call(value)
13
9
  block.call(key.to_s, value)
14
10
  elsif value.respond_to?(:to_ary)
15
11
  if value.empty?
@@ -73,7 +69,7 @@ module HTTPX
73
69
  end
74
70
 
75
71
  def params_hash_has_key?(hash, key)
76
- return false if /\[\]/.match?(key)
72
+ return false if key.include?("[]")
77
73
 
78
74
  key.split(/[\[\]]+/).inject(hash) do |h, part|
79
75
  next h if part == ""
@@ -90,4 +86,7 @@ end
90
86
  require "httpx/transcoder/body"
91
87
  require "httpx/transcoder/form"
92
88
  require "httpx/transcoder/json"
89
+ require "httpx/transcoder/xml"
93
90
  require "httpx/transcoder/chunker"
91
+ require "httpx/transcoder/deflate"
92
+ require "httpx/transcoder/gzip"
data/lib/httpx/utils.rb CHANGED
@@ -4,6 +4,11 @@ module HTTPX
4
4
  module Utils
5
5
  using URIExtensions
6
6
 
7
+ TOKEN = %r{[^\s()<>,;:\\"/\[\]?=]+}.freeze
8
+ VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/.freeze
9
+ FILENAME_REGEX = /\s*filename=(#{VALUE})/.freeze
10
+ FILENAME_EXTENSION_REGEX = /\s*filename\*=(#{VALUE})/.freeze
11
+
7
12
  module_function
8
13
 
9
14
  def now
@@ -25,31 +30,46 @@ module HTTPX
25
30
  time - Time.now
26
31
  end
27
32
 
28
- if RUBY_VERSION < "2.3"
29
-
30
- def to_uri(uri)
31
- URI(uri)
33
+ def get_filename(header, _prefix_regex = nil)
34
+ filename = nil
35
+ case header
36
+ when FILENAME_REGEX
37
+ filename = Regexp.last_match(1)
38
+ filename = Regexp.last_match(1) if filename =~ /^"(.*)"$/
39
+ when FILENAME_EXTENSION_REGEX
40
+ filename = Regexp.last_match(1)
41
+ encoding, _, filename = filename.split("'", 3)
32
42
  end
33
43
 
34
- else
44
+ return unless filename
35
45
 
36
- URIParser = URI::RFC2396_Parser.new
46
+ filename = URI::DEFAULT_PARSER.unescape(filename) if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
37
47
 
38
- def to_uri(uri)
39
- return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
48
+ filename.scrub!
40
49
 
41
- uri = URI(URIParser.escape(uri))
50
+ filename = filename.gsub(/\\(.)/, '\1') unless /\\[^\\"]/.match?(filename)
42
51
 
43
- non_ascii_hostname = URIParser.unescape(uri.host)
52
+ filename.force_encoding ::Encoding.find(encoding) if encoding
44
53
 
45
- non_ascii_hostname.force_encoding(Encoding::UTF_8)
54
+ filename
55
+ end
46
56
 
47
- idna_hostname = Punycode.encode_hostname(non_ascii_hostname)
57
+ URIParser = URI::RFC2396_Parser.new
48
58
 
49
- uri.host = idna_hostname
50
- uri.non_ascii_hostname = non_ascii_hostname
51
- uri
52
- end
59
+ def to_uri(uri)
60
+ return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
61
+
62
+ uri = URI(URIParser.escape(uri))
63
+
64
+ non_ascii_hostname = URIParser.unescape(uri.host)
65
+
66
+ non_ascii_hostname.force_encoding(Encoding::UTF_8)
67
+
68
+ idna_hostname = Punycode.encode_hostname(non_ascii_hostname)
69
+
70
+ uri.host = idna_hostname
71
+ uri.non_ascii_hostname = non_ascii_hostname
72
+ uri
53
73
  end
54
74
  end
55
75
  end
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.20.0"
4
+ VERSION = "1.3.1"
5
5
  end