rest-client 1.6.14 → 2.0.2
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.
- checksums.yaml +5 -5
- data/.gitignore +6 -6
- data/.rspec +2 -1
- data/.rubocop-disables.yml +384 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +46 -1
- data/AUTHORS +28 -5
- data/Gemfile +5 -1
- data/LICENSE +21 -0
- data/README.md +784 -0
- data/Rakefile +95 -12
- data/bin/restclient +11 -12
- data/history.md +180 -16
- data/lib/restclient.rb +25 -11
- data/lib/restclient/abstract_response.rb +171 -51
- data/lib/restclient/exceptions.rb +102 -56
- data/lib/restclient/params_array.rb +72 -0
- data/lib/restclient/payload.rb +43 -74
- data/lib/restclient/platform.rb +22 -2
- data/lib/restclient/raw_response.rb +7 -3
- data/lib/restclient/request.rb +672 -179
- data/lib/restclient/resource.rb +6 -7
- data/lib/restclient/response.rb +64 -10
- data/lib/restclient/utils.rb +235 -0
- data/lib/restclient/version.rb +2 -1
- data/lib/restclient/windows.rb +8 -0
- data/lib/restclient/windows/root_certs.rb +105 -0
- data/rest-client.gemspec +16 -11
- data/rest-client.windows.gemspec +19 -0
- data/spec/helpers.rb +22 -0
- data/spec/integration/_lib.rb +1 -0
- data/spec/integration/capath_verisign/415660c1.0 +14 -0
- data/spec/integration/capath_verisign/7651b327.0 +14 -0
- data/spec/integration/capath_verisign/README +8 -0
- data/spec/integration/capath_verisign/verisign.crt +14 -0
- data/spec/integration/httpbin_spec.rb +87 -0
- data/spec/integration/integration_spec.rb +125 -0
- data/spec/integration/request_spec.rb +72 -20
- data/spec/spec_helper.rb +29 -0
- data/spec/unit/_lib.rb +1 -0
- data/spec/unit/abstract_response_spec.rb +145 -0
- data/spec/unit/exceptions_spec.rb +108 -0
- data/spec/{master_shake.jpg → unit/master_shake.jpg} +0 -0
- data/spec/unit/params_array_spec.rb +36 -0
- data/spec/{payload_spec.rb → unit/payload_spec.rb} +73 -54
- data/spec/{raw_response_spec.rb → unit/raw_response_spec.rb} +5 -4
- data/spec/unit/request2_spec.rb +54 -0
- data/spec/unit/request_spec.rb +1250 -0
- data/spec/unit/resource_spec.rb +134 -0
- data/spec/unit/response_spec.rb +241 -0
- data/spec/unit/restclient_spec.rb +79 -0
- data/spec/unit/utils_spec.rb +147 -0
- data/spec/unit/windows/root_certs_spec.rb +22 -0
- metadata +143 -53
- data/README.rdoc +0 -300
- data/lib/restclient/net_http_ext.rb +0 -55
- data/spec/abstract_response_spec.rb +0 -85
- data/spec/base.rb +0 -13
- data/spec/exceptions_spec.rb +0 -98
- data/spec/integration_spec.rb +0 -38
- data/spec/request2_spec.rb +0 -35
- data/spec/request_spec.rb +0 -528
- data/spec/resource_spec.rb +0 -136
- data/spec/response_spec.rb +0 -169
- data/spec/restclient_spec.rb +0 -73
data/lib/restclient/resource.rb
CHANGED
@@ -14,7 +14,7 @@ module RestClient
|
|
14
14
|
#
|
15
15
|
# With a timeout (seconds):
|
16
16
|
#
|
17
|
-
# RestClient::Resource.new('http://slow', :
|
17
|
+
# RestClient::Resource.new('http://slow', :read_timeout => 10)
|
18
18
|
#
|
19
19
|
# With an open timeout (seconds):
|
20
20
|
#
|
@@ -113,8 +113,8 @@ module RestClient
|
|
113
113
|
options[:headers] || {}
|
114
114
|
end
|
115
115
|
|
116
|
-
def
|
117
|
-
options[:
|
116
|
+
def read_timeout
|
117
|
+
options[:read_timeout]
|
118
118
|
end
|
119
119
|
|
120
120
|
def open_timeout
|
@@ -149,10 +149,9 @@ module RestClient
|
|
149
149
|
#
|
150
150
|
def [](suburl, &new_block)
|
151
151
|
case
|
152
|
-
|
153
|
-
|
154
|
-
else
|
155
|
-
self.class.new(concat_urls(url, suburl), options)
|
152
|
+
when block_given? then self.class.new(concat_urls(url, suburl), options, &new_block)
|
153
|
+
when block then self.class.new(concat_urls(url, suburl), options, &block)
|
154
|
+
else self.class.new(concat_urls(url, suburl), options)
|
156
155
|
end
|
157
156
|
end
|
158
157
|
|
data/lib/restclient/response.rb
CHANGED
@@ -2,25 +2,79 @@ module RestClient
|
|
2
2
|
|
3
3
|
# A Response from RestClient, you can access the response body, the code or the headers.
|
4
4
|
#
|
5
|
-
|
5
|
+
class Response < String
|
6
6
|
|
7
7
|
include AbstractResponse
|
8
8
|
|
9
|
-
|
9
|
+
# Return the HTTP response body.
|
10
|
+
#
|
11
|
+
# Future versions of RestClient will deprecate treating response objects
|
12
|
+
# directly as strings, so it will be necessary to call `.body`.
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
#
|
16
|
+
def body
|
17
|
+
# Benchmarking suggests that "#{self}" is fastest, and that caching the
|
18
|
+
# body string in an instance variable doesn't make it enough faster to be
|
19
|
+
# worth the extra memory storage.
|
20
|
+
String.new(self)
|
21
|
+
end
|
10
22
|
|
11
|
-
|
23
|
+
# Convert the HTTP response body to a pure String object.
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
def to_s
|
27
|
+
body
|
28
|
+
end
|
12
29
|
|
13
|
-
|
14
|
-
|
30
|
+
# Convert the HTTP response body to a pure String object.
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
def to_str
|
34
|
+
body
|
15
35
|
end
|
16
36
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
37
|
+
def inspect
|
38
|
+
"<RestClient::Response #{code.inspect} #{body_truncated(10).inspect}>"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.create(body, net_http_res, request)
|
42
|
+
result = self.new(body || '')
|
43
|
+
|
44
|
+
result.response_set_vars(net_http_res, request)
|
45
|
+
fix_encoding(result)
|
46
|
+
|
22
47
|
result
|
23
48
|
end
|
24
49
|
|
50
|
+
def self.fix_encoding(response)
|
51
|
+
charset = RestClient::Utils.get_encoding_from_headers(response.headers)
|
52
|
+
encoding = nil
|
53
|
+
|
54
|
+
begin
|
55
|
+
encoding = Encoding.find(charset) if charset
|
56
|
+
rescue ArgumentError
|
57
|
+
if RestClient.log
|
58
|
+
RestClient.log << "No such encoding: #{charset.inspect}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
return unless encoding
|
63
|
+
|
64
|
+
response.force_encoding(encoding)
|
65
|
+
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def body_truncated(length)
|
72
|
+
b = body
|
73
|
+
if b.length > length
|
74
|
+
b[0..length] + '...'
|
75
|
+
else
|
76
|
+
b
|
77
|
+
end
|
78
|
+
end
|
25
79
|
end
|
26
80
|
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module RestClient
|
2
|
+
# Various utility methods
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
# Return encoding from an HTTP header hash.
|
6
|
+
#
|
7
|
+
# We use the RFC 7231 specification and do not impose a default encoding on
|
8
|
+
# text. This differs from the older RFC 2616 behavior, which specifies
|
9
|
+
# using ISO-8859-1 for text/* content types without a charset.
|
10
|
+
#
|
11
|
+
# Strings will use the default encoding when this method returns nil. This
|
12
|
+
# default is likely to be UTF-8 for Ruby >= 2.0
|
13
|
+
#
|
14
|
+
# @param headers [Hash<Symbol,String>]
|
15
|
+
#
|
16
|
+
# @return [String, nil] encoding Return the string encoding or nil if no
|
17
|
+
# header is found.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# >> get_encoding_from_headers({:content_type => 'text/plain; charset=UTF-8'})
|
21
|
+
# => "UTF-8"
|
22
|
+
#
|
23
|
+
def self.get_encoding_from_headers(headers)
|
24
|
+
type_header = headers[:content_type]
|
25
|
+
return nil unless type_header
|
26
|
+
|
27
|
+
_content_type, params = cgi_parse_header(type_header)
|
28
|
+
|
29
|
+
if params.include?('charset')
|
30
|
+
return params.fetch('charset').gsub(/(\A["']*)|(["']*\z)/, '')
|
31
|
+
end
|
32
|
+
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Parse semi-colon separated, potentially quoted header string iteratively.
|
37
|
+
#
|
38
|
+
# @private
|
39
|
+
#
|
40
|
+
def self._cgi_parseparam(s)
|
41
|
+
return enum_for(__method__, s) unless block_given?
|
42
|
+
|
43
|
+
while s[0] == ';'
|
44
|
+
s = s[1..-1]
|
45
|
+
ends = s.index(';')
|
46
|
+
while ends && ends > 0 \
|
47
|
+
&& (s[0...ends].count('"') -
|
48
|
+
s[0...ends].scan('\"').count) % 2 != 0
|
49
|
+
ends = s.index(';', ends + 1)
|
50
|
+
end
|
51
|
+
if ends.nil?
|
52
|
+
ends = s.length
|
53
|
+
end
|
54
|
+
f = s[0...ends]
|
55
|
+
yield f.strip
|
56
|
+
s = s[ends..-1]
|
57
|
+
end
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# Parse a Content-Type like header.
|
62
|
+
#
|
63
|
+
# Return the main content-type and a hash of options.
|
64
|
+
#
|
65
|
+
# This method was ported directly from Python's cgi.parse_header(). It
|
66
|
+
# probably doesn't read or perform particularly well in ruby.
|
67
|
+
# https://github.com/python/cpython/blob/3.4/Lib/cgi.py#L301-L331
|
68
|
+
#
|
69
|
+
#
|
70
|
+
# @param [String] line
|
71
|
+
# @return [Array(String, Hash)]
|
72
|
+
#
|
73
|
+
def self.cgi_parse_header(line)
|
74
|
+
parts = _cgi_parseparam(';' + line)
|
75
|
+
key = parts.next
|
76
|
+
pdict = {}
|
77
|
+
|
78
|
+
begin
|
79
|
+
while (p = parts.next)
|
80
|
+
i = p.index('=')
|
81
|
+
if i
|
82
|
+
name = p[0...i].strip.downcase
|
83
|
+
value = p[i+1..-1].strip
|
84
|
+
if value.length >= 2 && value[0] == '"' && value[-1] == '"'
|
85
|
+
value = value[1...-1]
|
86
|
+
value = value.gsub('\\\\', '\\').gsub('\\"', '"')
|
87
|
+
end
|
88
|
+
pdict[name] = value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
rescue StopIteration
|
92
|
+
end
|
93
|
+
|
94
|
+
[key, pdict]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Serialize a ruby object into HTTP query string parameters.
|
98
|
+
#
|
99
|
+
# There is no standard for doing this, so we choose our own slightly
|
100
|
+
# idiosyncratic format. The output closely matches the format understood by
|
101
|
+
# Rails, Rack, and PHP.
|
102
|
+
#
|
103
|
+
# If you don't want handling of complex objects and only want to handle
|
104
|
+
# simple flat hashes, you may want to use `URI.encode_www_form` instead,
|
105
|
+
# which implements HTML5-compliant URL encoded form data.
|
106
|
+
#
|
107
|
+
# @param [Hash,ParamsArray] object The object to serialize
|
108
|
+
#
|
109
|
+
# @return [String] A string appropriate for use as an HTTP query string
|
110
|
+
#
|
111
|
+
# @see {flatten_params}
|
112
|
+
#
|
113
|
+
# @see URI.encode_www_form
|
114
|
+
#
|
115
|
+
# @see See also Object#to_query in ActiveSupport
|
116
|
+
# @see http://php.net/manual/en/function.http-build-query.php
|
117
|
+
# http_build_query in PHP
|
118
|
+
# @see See also Rack::Utils.build_nested_query in Rack
|
119
|
+
#
|
120
|
+
# Notable differences from the ActiveSupport implementation:
|
121
|
+
#
|
122
|
+
# - Empty hash and empty array are treated the same as nil instead of being
|
123
|
+
# omitted entirely from the output. Rather than disappearing, they will
|
124
|
+
# appear to be nil instead.
|
125
|
+
#
|
126
|
+
# It's most common to pass a Hash as the object to serialize, but you can
|
127
|
+
# also use a ParamsArray if you want to be able to pass the same key with
|
128
|
+
# multiple values and not use the rack/rails array convention.
|
129
|
+
#
|
130
|
+
# @since 2.0.0
|
131
|
+
#
|
132
|
+
# @example Simple hashes
|
133
|
+
# >> encode_query_string({foo: 123, bar: 456})
|
134
|
+
# => 'foo=123&bar=456'
|
135
|
+
#
|
136
|
+
# @example Simple arrays
|
137
|
+
# >> encode_query_string({foo: [1,2,3]})
|
138
|
+
# => 'foo[]=1&foo[]=2&foo[]=3'
|
139
|
+
#
|
140
|
+
# @example Nested hashes
|
141
|
+
# >> encode_query_string({outer: {foo: 123, bar: 456}})
|
142
|
+
# => 'outer[foo]=123&outer[bar]=456'
|
143
|
+
#
|
144
|
+
# @example Deeply nesting
|
145
|
+
# >> encode_query_string({coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]})
|
146
|
+
# => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3'
|
147
|
+
#
|
148
|
+
# @example Null and empty values
|
149
|
+
# >> encode_query_string({string: '', empty: nil, list: [], hash: {}})
|
150
|
+
# => 'string=&empty&list&hash'
|
151
|
+
#
|
152
|
+
# @example Nested nulls
|
153
|
+
# >> encode_query_string({foo: {string: '', empty: nil}})
|
154
|
+
# => 'foo[string]=&foo[empty]'
|
155
|
+
#
|
156
|
+
# @example Multiple fields with the same name using ParamsArray
|
157
|
+
# >> encode_query_string(RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]]))
|
158
|
+
# => 'foo=1&foo=2&foo=3'
|
159
|
+
#
|
160
|
+
# @example Nested ParamsArray
|
161
|
+
# >> encode_query_string({foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])})
|
162
|
+
# => 'foo[a]=1&foo[a]=2'
|
163
|
+
#
|
164
|
+
# >> encode_query_string(RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]]))
|
165
|
+
# => 'foo[a]=1&foo[a]=2'
|
166
|
+
#
|
167
|
+
def self.encode_query_string(object)
|
168
|
+
flatten_params(object, true).map {|k, v| v.nil? ? k : "#{k}=#{v}" }.join('&')
|
169
|
+
end
|
170
|
+
|
171
|
+
# Transform deeply nested param containers into a flat array of [key,
|
172
|
+
# value] pairs.
|
173
|
+
#
|
174
|
+
# @example
|
175
|
+
# >> flatten_params({key1: {key2: 123}})
|
176
|
+
# => [["key1[key2]", 123]]
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# >> flatten_params({key1: {key2: 123, arr: [1,2,3]}})
|
180
|
+
# => [["key1[key2]", 123], ["key1[arr][]", 1], ["key1[arr][]", 2], ["key1[arr][]", 3]]
|
181
|
+
#
|
182
|
+
# @param object [Hash, ParamsArray] The container to flatten
|
183
|
+
# @param uri_escape [Boolean] Whether to URI escape keys and values
|
184
|
+
# @param parent_key [String] Should not be passed (used for recursion)
|
185
|
+
#
|
186
|
+
def self.flatten_params(object, uri_escape=false, parent_key=nil)
|
187
|
+
unless object.is_a?(Hash) || object.is_a?(ParamsArray) ||
|
188
|
+
(parent_key && object.is_a?(Array))
|
189
|
+
raise ArgumentError.new('expected Hash or ParamsArray, got: ' + object.inspect)
|
190
|
+
end
|
191
|
+
|
192
|
+
# transform empty collections into nil, where possible
|
193
|
+
if object.empty? && parent_key
|
194
|
+
return [[parent_key, nil]]
|
195
|
+
end
|
196
|
+
|
197
|
+
# This is essentially .map(), but we need to do += for nested containers
|
198
|
+
object.reduce([]) { |result, item|
|
199
|
+
if object.is_a?(Array)
|
200
|
+
# item is already the value
|
201
|
+
k = nil
|
202
|
+
v = item
|
203
|
+
else
|
204
|
+
# item is a key, value pair
|
205
|
+
k, v = item
|
206
|
+
k = escape(k.to_s) if uri_escape
|
207
|
+
end
|
208
|
+
|
209
|
+
processed_key = parent_key ? "#{parent_key}[#{k}]" : k
|
210
|
+
|
211
|
+
case v
|
212
|
+
when Array, Hash, ParamsArray
|
213
|
+
result.concat flatten_params(v, uri_escape, processed_key)
|
214
|
+
else
|
215
|
+
v = escape(v.to_s) if uri_escape && v
|
216
|
+
result << [processed_key, v]
|
217
|
+
end
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
# Encode string for safe transport by URI or form encoding. This uses a CGI
|
222
|
+
# style escape, which transforms ` ` into `+` and various special
|
223
|
+
# characters into percent encoded forms.
|
224
|
+
#
|
225
|
+
# This calls URI.encode_www_form_component for the implementation. The only
|
226
|
+
# difference between this and CGI.escape is that it does not escape `*`.
|
227
|
+
# http://stackoverflow.com/questions/25085992/
|
228
|
+
#
|
229
|
+
# @see URI.encode_www_form_component
|
230
|
+
#
|
231
|
+
def self.escape(string)
|
232
|
+
URI.encode_www_form_component(string)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
data/lib/restclient/version.rb
CHANGED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
# Adapted from Puppet, Copyright (c) Puppet Labs Inc,
|
5
|
+
# licensed under the Apache License, Version 2.0.
|
6
|
+
#
|
7
|
+
# https://github.com/puppetlabs/puppet/blob/bbe30e0a/lib/puppet/util/windows/root_certs.rb
|
8
|
+
|
9
|
+
# Represents a collection of trusted root certificates.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
class RestClient::Windows::RootCerts
|
13
|
+
include Enumerable
|
14
|
+
extend FFI::Library
|
15
|
+
|
16
|
+
typedef :ulong, :dword
|
17
|
+
typedef :uintptr_t, :handle
|
18
|
+
|
19
|
+
def initialize(roots)
|
20
|
+
@roots = roots
|
21
|
+
end
|
22
|
+
|
23
|
+
# Enumerates each root certificate.
|
24
|
+
# @yieldparam cert [OpenSSL::X509::Certificate] each root certificate
|
25
|
+
# @api public
|
26
|
+
def each
|
27
|
+
@roots.each {|cert| yield cert}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a new instance.
|
31
|
+
# @return [RestClient::Windows::RootCerts] object constructed from current root certificates
|
32
|
+
def self.instance
|
33
|
+
new(self.load_certs)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns an array of root certificates.
|
37
|
+
#
|
38
|
+
# @return [Array<[OpenSSL::X509::Certificate]>] an array of root certificates
|
39
|
+
# @api private
|
40
|
+
def self.load_certs
|
41
|
+
certs = []
|
42
|
+
|
43
|
+
# This is based on a patch submitted to openssl:
|
44
|
+
# http://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html
|
45
|
+
ptr = FFI::Pointer::NULL
|
46
|
+
store = CertOpenSystemStoreA(nil, "ROOT")
|
47
|
+
begin
|
48
|
+
while (ptr = CertEnumCertificatesInStore(store, ptr)) and not ptr.null?
|
49
|
+
context = CERT_CONTEXT.new(ptr)
|
50
|
+
cert_buf = context[:pbCertEncoded].read_bytes(context[:cbCertEncoded])
|
51
|
+
begin
|
52
|
+
certs << OpenSSL::X509::Certificate.new(cert_buf)
|
53
|
+
rescue => detail
|
54
|
+
warn("Failed to import root certificate: #{detail.inspect}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
ensure
|
58
|
+
CertCloseStore(store, 0)
|
59
|
+
end
|
60
|
+
|
61
|
+
certs
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# typedef ULONG_PTR HCRYPTPROV_LEGACY;
|
67
|
+
# typedef void *HCERTSTORE;
|
68
|
+
|
69
|
+
class CERT_CONTEXT < FFI::Struct
|
70
|
+
layout(
|
71
|
+
:dwCertEncodingType, :dword,
|
72
|
+
:pbCertEncoded, :pointer,
|
73
|
+
:cbCertEncoded, :dword,
|
74
|
+
:pCertInfo, :pointer,
|
75
|
+
:hCertStore, :handle
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
# HCERTSTORE
|
80
|
+
# WINAPI
|
81
|
+
# CertOpenSystemStoreA(
|
82
|
+
# __in_opt HCRYPTPROV_LEGACY hProv,
|
83
|
+
# __in LPCSTR szSubsystemProtocol
|
84
|
+
# );
|
85
|
+
ffi_lib :crypt32
|
86
|
+
attach_function :CertOpenSystemStoreA, [:pointer, :string], :handle
|
87
|
+
|
88
|
+
# PCCERT_CONTEXT
|
89
|
+
# WINAPI
|
90
|
+
# CertEnumCertificatesInStore(
|
91
|
+
# __in HCERTSTORE hCertStore,
|
92
|
+
# __in_opt PCCERT_CONTEXT pPrevCertContext
|
93
|
+
# );
|
94
|
+
ffi_lib :crypt32
|
95
|
+
attach_function :CertEnumCertificatesInStore, [:handle, :pointer], :pointer
|
96
|
+
|
97
|
+
# BOOL
|
98
|
+
# WINAPI
|
99
|
+
# CertCloseStore(
|
100
|
+
# __in_opt HCERTSTORE hCertStore,
|
101
|
+
# __in DWORD dwFlags
|
102
|
+
# );
|
103
|
+
ffi_lib :crypt32
|
104
|
+
attach_function :CertCloseStore, [:handle, :dword], :bool
|
105
|
+
end
|