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.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +6 -6
  3. data/.rspec +2 -1
  4. data/.rubocop-disables.yml +384 -0
  5. data/.rubocop.yml +3 -0
  6. data/.travis.yml +46 -1
  7. data/AUTHORS +28 -5
  8. data/Gemfile +5 -1
  9. data/LICENSE +21 -0
  10. data/README.md +784 -0
  11. data/Rakefile +95 -12
  12. data/bin/restclient +11 -12
  13. data/history.md +180 -16
  14. data/lib/restclient.rb +25 -11
  15. data/lib/restclient/abstract_response.rb +171 -51
  16. data/lib/restclient/exceptions.rb +102 -56
  17. data/lib/restclient/params_array.rb +72 -0
  18. data/lib/restclient/payload.rb +43 -74
  19. data/lib/restclient/platform.rb +22 -2
  20. data/lib/restclient/raw_response.rb +7 -3
  21. data/lib/restclient/request.rb +672 -179
  22. data/lib/restclient/resource.rb +6 -7
  23. data/lib/restclient/response.rb +64 -10
  24. data/lib/restclient/utils.rb +235 -0
  25. data/lib/restclient/version.rb +2 -1
  26. data/lib/restclient/windows.rb +8 -0
  27. data/lib/restclient/windows/root_certs.rb +105 -0
  28. data/rest-client.gemspec +16 -11
  29. data/rest-client.windows.gemspec +19 -0
  30. data/spec/helpers.rb +22 -0
  31. data/spec/integration/_lib.rb +1 -0
  32. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  33. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  34. data/spec/integration/capath_verisign/README +8 -0
  35. data/spec/integration/capath_verisign/verisign.crt +14 -0
  36. data/spec/integration/httpbin_spec.rb +87 -0
  37. data/spec/integration/integration_spec.rb +125 -0
  38. data/spec/integration/request_spec.rb +72 -20
  39. data/spec/spec_helper.rb +29 -0
  40. data/spec/unit/_lib.rb +1 -0
  41. data/spec/unit/abstract_response_spec.rb +145 -0
  42. data/spec/unit/exceptions_spec.rb +108 -0
  43. data/spec/{master_shake.jpg → unit/master_shake.jpg} +0 -0
  44. data/spec/unit/params_array_spec.rb +36 -0
  45. data/spec/{payload_spec.rb → unit/payload_spec.rb} +73 -54
  46. data/spec/{raw_response_spec.rb → unit/raw_response_spec.rb} +5 -4
  47. data/spec/unit/request2_spec.rb +54 -0
  48. data/spec/unit/request_spec.rb +1250 -0
  49. data/spec/unit/resource_spec.rb +134 -0
  50. data/spec/unit/response_spec.rb +241 -0
  51. data/spec/unit/restclient_spec.rb +79 -0
  52. data/spec/unit/utils_spec.rb +147 -0
  53. data/spec/unit/windows/root_certs_spec.rb +22 -0
  54. metadata +143 -53
  55. data/README.rdoc +0 -300
  56. data/lib/restclient/net_http_ext.rb +0 -55
  57. data/spec/abstract_response_spec.rb +0 -85
  58. data/spec/base.rb +0 -13
  59. data/spec/exceptions_spec.rb +0 -98
  60. data/spec/integration_spec.rb +0 -38
  61. data/spec/request2_spec.rb +0 -35
  62. data/spec/request_spec.rb +0 -528
  63. data/spec/resource_spec.rb +0 -136
  64. data/spec/response_spec.rb +0 -169
  65. data/spec/restclient_spec.rb +0 -73
@@ -14,7 +14,7 @@ module RestClient
14
14
  #
15
15
  # With a timeout (seconds):
16
16
  #
17
- # RestClient::Resource.new('http://slow', :timeout => 10)
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 timeout
117
- options[:timeout]
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
- 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
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
 
@@ -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
- module Response
5
+ class Response < String
6
6
 
7
7
  include AbstractResponse
8
8
 
9
- attr_accessor :args, :net_http_res
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
- attr_writer :body
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
- def body
14
- self
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 Response.create body, net_http_res, args
18
- result = body || ''
19
- result.extend Response
20
- result.net_http_res = net_http_res
21
- result.args = args
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
@@ -1,5 +1,6 @@
1
1
  module RestClient
2
- VERSION = '1.6.14' unless defined?(self::VERSION)
2
+ VERSION_INFO = [2, 0, 2] unless defined?(self::VERSION_INFO)
3
+ VERSION = VERSION_INFO.map(&:to_s).join('.') unless defined?(self::VERSION)
3
4
 
4
5
  def self.version
5
6
  VERSION
@@ -0,0 +1,8 @@
1
+ module RestClient
2
+ module Windows
3
+ end
4
+ end
5
+
6
+ if RestClient::Platform.windows?
7
+ require_relative './windows/root_certs'
8
+ end
@@ -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