httpx 0.20.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +0 -48
- data/README.md +54 -45
- data/doc/release_notes/0_10_0.md +2 -2
- data/doc/release_notes/0_11_0.md +3 -5
- data/doc/release_notes/0_12_0.md +5 -5
- data/doc/release_notes/0_13_0.md +5 -5
- data/doc/release_notes/0_14_0.md +2 -2
- data/doc/release_notes/0_16_0.md +3 -3
- data/doc/release_notes/0_17_0.md +1 -1
- data/doc/release_notes/0_18_0.md +4 -4
- data/doc/release_notes/0_18_2.md +1 -1
- data/doc/release_notes/0_19_0.md +1 -1
- data/doc/release_notes/0_19_8.md +1 -1
- data/doc/release_notes/0_20_0.md +2 -2
- data/doc/release_notes/0_20_1.md +5 -0
- data/doc/release_notes/0_20_2.md +7 -0
- data/doc/release_notes/0_20_3.md +6 -0
- data/doc/release_notes/0_20_4.md +17 -0
- data/doc/release_notes/0_20_5.md +3 -0
- data/doc/release_notes/0_21_0.md +96 -0
- data/doc/release_notes/0_21_1.md +12 -0
- data/doc/release_notes/0_22_0.md +13 -0
- data/doc/release_notes/0_22_1.md +11 -0
- data/doc/release_notes/0_22_2.md +5 -0
- data/doc/release_notes/0_22_3.md +55 -0
- data/doc/release_notes/0_22_4.md +6 -0
- data/doc/release_notes/0_22_5.md +6 -0
- data/doc/release_notes/0_23_0.md +42 -0
- data/doc/release_notes/0_23_1.md +5 -0
- data/doc/release_notes/0_23_2.md +5 -0
- data/doc/release_notes/0_23_3.md +6 -0
- data/doc/release_notes/0_23_4.md +5 -0
- data/doc/release_notes/0_24_0.md +48 -0
- data/doc/release_notes/0_24_1.md +12 -0
- data/doc/release_notes/0_24_2.md +12 -0
- data/doc/release_notes/0_24_3.md +12 -0
- data/doc/release_notes/0_24_4.md +18 -0
- data/doc/release_notes/0_24_5.md +6 -0
- data/doc/release_notes/0_24_6.md +5 -0
- data/doc/release_notes/0_24_7.md +10 -0
- data/doc/release_notes/1_0_0.md +60 -0
- data/doc/release_notes/1_0_1.md +5 -0
- data/doc/release_notes/1_0_2.md +7 -0
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/doc/release_notes/1_1_2.md +12 -0
- data/doc/release_notes/1_1_3.md +18 -0
- data/doc/release_notes/1_1_4.md +6 -0
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/doc/release_notes/1_2_2.md +10 -0
- data/doc/release_notes/1_2_3.md +16 -0
- data/doc/release_notes/1_2_4.md +8 -0
- data/doc/release_notes/1_2_5.md +7 -0
- data/doc/release_notes/1_2_6.md +13 -0
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +215 -122
- data/lib/httpx/adapters/faraday.rb +145 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +34 -18
- data/lib/httpx/altsvc.rb +63 -26
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +12 -0
- data/lib/httpx/callbacks.rb +5 -3
- data/lib/httpx/chainable.rb +54 -39
- data/lib/httpx/connection/http1.rb +75 -44
- data/lib/httpx/connection/http2.rb +31 -38
- data/lib/httpx/connection.rb +287 -117
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +52 -2
- data/lib/httpx/extensions.rb +24 -131
- data/lib/httpx/io/ssl.rb +83 -77
- data/lib/httpx/io/tcp.rb +48 -71
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +10 -15
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +176 -118
- data/lib/httpx/parser/http1.rb +4 -0
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
- data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
- data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
- data/lib/httpx/plugins/aws_sigv4.rb +12 -9
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
- data/lib/httpx/plugins/circuit_breaker.rb +148 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +30 -17
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
- data/lib/httpx/plugins/expect.rb +21 -14
- data/lib/httpx/plugins/follow_redirects.rb +140 -41
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -37
- data/lib/httpx/plugins/grpc.rb +36 -29
- data/lib/httpx/plugins/h2c.rb +26 -19
- data/lib/httpx/plugins/internal_telemetry.rb +16 -0
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
- data/lib/httpx/plugins/oauth.rb +175 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -13
- data/lib/httpx/plugins/proxy/socks4.rb +9 -7
- data/lib/httpx/plugins/proxy/socks5.rb +11 -9
- data/lib/httpx/plugins/proxy.rb +80 -61
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +5 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +62 -25
- data/lib/httpx/plugins/response_cache.rb +105 -12
- data/lib/httpx/plugins/retries.rb +87 -17
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +27 -23
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +80 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +102 -27
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +154 -0
- data/lib/httpx/request.rb +130 -146
- data/lib/httpx/resolver/https.rb +62 -27
- data/lib/httpx/resolver/multi.rb +9 -13
- data/lib/httpx/resolver/native.rb +192 -76
- data/lib/httpx/resolver/resolver.rb +34 -9
- data/lib/httpx/resolver/system.rb +16 -11
- data/lib/httpx/resolver.rb +38 -16
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +96 -0
- data/lib/httpx/response.rb +159 -217
- data/lib/httpx/selector.rb +9 -4
- data/lib/httpx/session.rb +137 -89
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +34 -8
- data/lib/httpx/transcoder/body.rb +0 -2
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -33
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +21 -8
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
- data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
- data/lib/httpx/transcoder/multipart.rb +17 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +72 -0
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/transcoder/xml.rb +52 -0
- data/lib/httpx/transcoder.rb +5 -6
- data/lib/httpx/utils.rb +36 -16
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +12 -14
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +2 -1
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +11 -9
- data/sig/connection/http1.rbs +8 -7
- data/sig/connection/http2.rbs +19 -19
- data/sig/connection.rbs +64 -24
- data/sig/errors.rbs +22 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +27 -0
- data/sig/io.rbs +6 -0
- data/sig/options.rbs +32 -22
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
- data/sig/plugins/brotli.rbs +22 -0
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/circuit_breaker.rbs +71 -0
- data/sig/plugins/compression.rbs +7 -5
- data/sig/plugins/cookies/jar.rbs +2 -2
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +18 -4
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +7 -32
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +54 -0
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy/socks4.rbs +9 -6
- data/sig/plugins/proxy/socks5.rbs +10 -6
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +13 -5
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache.rbs +36 -7
- data/sig/plugins/retries.rbs +30 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +10 -7
- data/sig/request/body.rbs +38 -0
- data/sig/request.rbs +15 -24
- data/sig/resolver/https.rbs +8 -3
- data/sig/resolver/native.rbs +17 -4
- data/sig/resolver/resolver.rbs +8 -6
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +9 -5
- data/sig/response/body.rbs +53 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +24 -39
- data/sig/selector.rbs +1 -1
- data/sig/session.rbs +29 -18
- data/sig/timers.rbs +18 -8
- data/sig/transcoder/body.rbs +4 -3
- data/sig/transcoder/deflate.rbs +11 -0
- data/sig/transcoder/form.rbs +5 -3
- data/sig/transcoder/gzip.rbs +24 -0
- data/sig/transcoder/json.rbs +8 -3
- data/sig/{plugins → transcoder}/multipart.rbs +15 -19
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder/utils/inflater.rbs +12 -0
- data/sig/transcoder/xml.rbs +22 -0
- data/sig/transcoder.rbs +24 -9
- data/sig/utils.rbs +8 -2
- metadata +163 -41
- data/lib/httpx/plugins/authentication.rb +0 -20
- data/lib/httpx/plugins/basic_authentication.rb +0 -30
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -49
- data/lib/httpx/plugins/compression/gzip.rb +0 -88
- data/lib/httpx/plugins/compression.rb +0 -164
- data/lib/httpx/plugins/multipart/decoder.rb +0 -187
- data/lib/httpx/plugins/multipart.rb +0 -84
- data/lib/httpx/registry.rb +0 -85
- data/sig/plugins/authentication.rbs +0 -11
- data/sig/plugins/compression/brotli.rbs +0 -21
- data/sig/plugins/compression/deflate.rbs +0 -17
- data/sig/plugins/compression/gzip.rbs +0 -29
- data/sig/registry.rbs +0 -12
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /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 =
|
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
|
-
|
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
|
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
|
-
|
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
|
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.
|
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
|
data/lib/httpx/transcoder.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
44
|
+
return unless filename
|
35
45
|
|
36
|
-
|
46
|
+
filename = URI::DEFAULT_PARSER.unescape(filename) if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
37
47
|
|
38
|
-
|
39
|
-
return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
|
48
|
+
filename.scrub!
|
40
49
|
|
41
|
-
|
50
|
+
filename = filename.gsub(/\\(.)/, '\1') unless /\\[^\\"]/.match?(filename)
|
42
51
|
|
43
|
-
|
52
|
+
filename.force_encoding ::Encoding.find(encoding) if encoding
|
44
53
|
|
45
|
-
|
54
|
+
filename
|
55
|
+
end
|
46
56
|
|
47
|
-
|
57
|
+
URIParser = URI::RFC2396_Parser.new
|
48
58
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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