rest-client 1.6.7 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.mailmap +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop +2 -0
  6. data/.rubocop-disables.yml +386 -0
  7. data/.rubocop.yml +8 -0
  8. data/.travis.yml +62 -0
  9. data/AUTHORS +106 -0
  10. data/Gemfile +11 -0
  11. data/LICENSE +21 -0
  12. data/README.md +901 -0
  13. data/Rakefile +109 -35
  14. data/bin/restclient +11 -12
  15. data/history.md +244 -1
  16. data/lib/restclient.rb +27 -18
  17. data/lib/restclient/abstract_response.rb +197 -51
  18. data/lib/restclient/exceptions.rb +110 -59
  19. data/lib/restclient/params_array.rb +72 -0
  20. data/lib/restclient/payload.rb +74 -75
  21. data/lib/restclient/platform.rb +49 -0
  22. data/lib/restclient/raw_response.rb +21 -6
  23. data/lib/restclient/request.rb +747 -183
  24. data/lib/restclient/resource.rb +22 -13
  25. data/lib/restclient/response.rb +75 -9
  26. data/lib/restclient/utils.rb +274 -0
  27. data/lib/restclient/version.rb +8 -0
  28. data/lib/restclient/windows.rb +8 -0
  29. data/lib/restclient/windows/root_certs.rb +105 -0
  30. data/rest-client.gemspec +32 -0
  31. data/rest-client.windows.gemspec +19 -0
  32. data/spec/ISS.jpg +0 -0
  33. data/spec/helpers.rb +54 -0
  34. data/spec/integration/_lib.rb +1 -0
  35. data/spec/integration/capath_digicert/3513523f.0 +22 -0
  36. data/spec/integration/capath_digicert/399e7759.0 +22 -0
  37. data/spec/integration/capath_digicert/README +8 -0
  38. data/spec/integration/capath_digicert/digicert.crt +22 -0
  39. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  40. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  41. data/spec/integration/capath_verisign/README +8 -0
  42. data/spec/integration/capath_verisign/verisign.crt +14 -0
  43. data/spec/integration/certs/digicert.crt +22 -0
  44. data/spec/integration/httpbin_spec.rb +128 -0
  45. data/spec/integration/integration_spec.rb +118 -0
  46. data/spec/integration/request_spec.rb +109 -7
  47. data/spec/spec_helper.rb +29 -0
  48. data/spec/unit/_lib.rb +1 -0
  49. data/spec/unit/abstract_response_spec.rb +145 -0
  50. data/spec/unit/exceptions_spec.rb +108 -0
  51. data/spec/unit/params_array_spec.rb +36 -0
  52. data/spec/unit/payload_spec.rb +295 -0
  53. data/spec/unit/raw_response_spec.rb +22 -0
  54. data/spec/unit/request2_spec.rb +54 -0
  55. data/spec/unit/request_spec.rb +1238 -0
  56. data/spec/unit/resource_spec.rb +134 -0
  57. data/spec/unit/response_spec.rb +252 -0
  58. data/spec/unit/restclient_spec.rb +80 -0
  59. data/spec/unit/utils_spec.rb +147 -0
  60. data/spec/unit/windows/root_certs_spec.rb +22 -0
  61. metadata +265 -117
  62. data/README.rdoc +0 -285
  63. data/VERSION +0 -1
  64. data/lib/restclient/net_http_ext.rb +0 -55
  65. data/spec/abstract_response_spec.rb +0 -85
  66. data/spec/base.rb +0 -16
  67. data/spec/exceptions_spec.rb +0 -98
  68. data/spec/integration/certs/equifax.crt +0 -19
  69. data/spec/integration_spec.rb +0 -38
  70. data/spec/master_shake.jpg +0 -0
  71. data/spec/payload_spec.rb +0 -234
  72. data/spec/raw_response_spec.rb +0 -17
  73. data/spec/request2_spec.rb +0 -40
  74. data/spec/request_spec.rb +0 -529
  75. data/spec/resource_spec.rb +0 -134
  76. data/spec/response_spec.rb +0 -169
  77. 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
  #
@@ -51,7 +51,8 @@ module RestClient
51
51
  Request.execute(options.merge(
52
52
  :method => :get,
53
53
  :url => url,
54
- :headers => headers), &(block || @block))
54
+ :headers => headers,
55
+ :log => log), &(block || @block))
55
56
  end
56
57
 
57
58
  def head(additional_headers={}, &block)
@@ -59,7 +60,8 @@ module RestClient
59
60
  Request.execute(options.merge(
60
61
  :method => :head,
61
62
  :url => url,
62
- :headers => headers), &(block || @block))
63
+ :headers => headers,
64
+ :log => log), &(block || @block))
63
65
  end
64
66
 
65
67
  def post(payload, additional_headers={}, &block)
@@ -68,7 +70,8 @@ module RestClient
68
70
  :method => :post,
69
71
  :url => url,
70
72
  :payload => payload,
71
- :headers => headers), &(block || @block))
73
+ :headers => headers,
74
+ :log => log), &(block || @block))
72
75
  end
73
76
 
74
77
  def put(payload, additional_headers={}, &block)
@@ -77,7 +80,8 @@ module RestClient
77
80
  :method => :put,
78
81
  :url => url,
79
82
  :payload => payload,
80
- :headers => headers), &(block || @block))
83
+ :headers => headers,
84
+ :log => log), &(block || @block))
81
85
  end
82
86
 
83
87
  def patch(payload, additional_headers={}, &block)
@@ -86,7 +90,8 @@ module RestClient
86
90
  :method => :patch,
87
91
  :url => url,
88
92
  :payload => payload,
89
- :headers => headers), &(block || @block))
93
+ :headers => headers,
94
+ :log => log), &(block || @block))
90
95
  end
91
96
 
92
97
  def delete(additional_headers={}, &block)
@@ -94,7 +99,8 @@ module RestClient
94
99
  Request.execute(options.merge(
95
100
  :method => :delete,
96
101
  :url => url,
97
- :headers => headers), &(block || @block))
102
+ :headers => headers,
103
+ :log => log), &(block || @block))
98
104
  end
99
105
 
100
106
  def to_s
@@ -113,14 +119,18 @@ module RestClient
113
119
  options[:headers] || {}
114
120
  end
115
121
 
116
- def timeout
117
- options[:timeout]
122
+ def read_timeout
123
+ options[:read_timeout]
118
124
  end
119
125
 
120
126
  def open_timeout
121
127
  options[:open_timeout]
122
128
  end
123
129
 
130
+ def log
131
+ options[:log] || RestClient.log
132
+ end
133
+
124
134
  # Construct a subresource, preserving authentication.
125
135
  #
126
136
  # Example:
@@ -149,10 +159,9 @@ module RestClient
149
159
  #
150
160
  def [](suburl, &new_block)
151
161
  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)
162
+ when block_given? then self.class.new(concat_urls(url, suburl), options, &new_block)
163
+ when block then self.class.new(concat_urls(url, suburl), options, &block)
164
+ else self.class.new(concat_urls(url, suburl), options)
156
165
  end
157
166
  end
158
167
 
@@ -2,23 +2,89 @@ 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, :body, :net_http_res
10
-
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
+ #
11
16
  def body
12
- self
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
22
+
23
+ # Convert the HTTP response body to a pure String object.
24
+ #
25
+ # @return [String]
26
+ def to_s
27
+ body
28
+ end
29
+
30
+ # Convert the HTTP response body to a pure String object.
31
+ #
32
+ # @return [String]
33
+ def to_str
34
+ body
13
35
  end
14
36
 
15
- def Response.create body, net_http_res, args
16
- result = body || ''
17
- result.extend Response
18
- result.net_http_res = net_http_res
19
- result.args = args
37
+ def inspect
38
+ "<RestClient::Response #{code.inspect} #{body_truncated(10).inspect}>"
39
+ end
40
+
41
+ # Initialize a Response object. Because RestClient::Response is
42
+ # (unfortunately) a subclass of String for historical reasons,
43
+ # Response.create is the preferred initializer.
44
+ #
45
+ # @param [String, nil] body The response body from the Net::HTTPResponse
46
+ # @param [Net::HTTPResponse] net_http_res
47
+ # @param [RestClient::Request] request
48
+ # @param [Time] start_time
49
+ def self.create(body, net_http_res, request, start_time=nil)
50
+ result = self.new(body || '')
51
+
52
+ result.response_set_vars(net_http_res, request, start_time)
53
+ fix_encoding(result)
54
+
20
55
  result
21
56
  end
22
57
 
58
+ # Set the String encoding according to the 'Content-Type: charset' header,
59
+ # if possible.
60
+ def self.fix_encoding(response)
61
+ charset = RestClient::Utils.get_encoding_from_headers(response.headers)
62
+ encoding = nil
63
+
64
+ begin
65
+ encoding = Encoding.find(charset) if charset
66
+ rescue ArgumentError
67
+ if response.log
68
+ response.log << "No such encoding: #{charset.inspect}"
69
+ end
70
+ end
71
+
72
+ return unless encoding
73
+
74
+ response.force_encoding(encoding)
75
+
76
+ response
77
+ end
78
+
79
+ private
80
+
81
+ def body_truncated(length)
82
+ b = body
83
+ if b.length > length
84
+ b[0..length] + '...'
85
+ else
86
+ b
87
+ end
88
+ end
23
89
  end
24
90
  end
@@ -0,0 +1,274 @@
1
+ require 'http/accept'
2
+
3
+ module RestClient
4
+ # Various utility methods
5
+ module Utils
6
+
7
+ # Return encoding from an HTTP header hash.
8
+ #
9
+ # We use the RFC 7231 specification and do not impose a default encoding on
10
+ # text. This differs from the older RFC 2616 behavior, which specifies
11
+ # using ISO-8859-1 for text/* content types without a charset.
12
+ #
13
+ # Strings will use the default encoding when this method returns nil. This
14
+ # default is likely to be UTF-8 for Ruby >= 2.0
15
+ #
16
+ # @param headers [Hash<Symbol,String>]
17
+ #
18
+ # @return [String, nil] Return the string encoding or nil if no header is
19
+ # found.
20
+ #
21
+ # @example
22
+ # >> get_encoding_from_headers({:content_type => 'text/plain; charset=UTF-8'})
23
+ # => "UTF-8"
24
+ #
25
+ def self.get_encoding_from_headers(headers)
26
+ type_header = headers[:content_type]
27
+ return nil unless type_header
28
+
29
+ # TODO: remove this hack once we drop support for Ruby 2.0
30
+ if RUBY_VERSION.start_with?('2.0')
31
+ _content_type, params = deprecated_cgi_parse_header(type_header)
32
+
33
+ if params.include?('charset')
34
+ return params.fetch('charset').gsub(/(\A["']*)|(["']*\z)/, '')
35
+ end
36
+
37
+ else
38
+
39
+ begin
40
+ _content_type, params = cgi_parse_header(type_header)
41
+ rescue HTTP::Accept::ParseError
42
+ return nil
43
+ else
44
+ params['charset']
45
+ end
46
+ end
47
+ end
48
+
49
+ # Parse a Content-Type like header.
50
+ #
51
+ # Return the main content-type and a hash of params.
52
+ #
53
+ # @param [String] line
54
+ # @return [Array(String, Hash)]
55
+ #
56
+ def self.cgi_parse_header(line)
57
+ types = HTTP::Accept::MediaTypes.parse(line)
58
+
59
+ if types.empty?
60
+ raise HTTP::Accept::ParseError.new("Found no types in header line")
61
+ end
62
+
63
+ [types.first.mime_type, types.first.parameters]
64
+ end
65
+
66
+ # Parse semi-colon separated, potentially quoted header string iteratively.
67
+ #
68
+ # @private
69
+ #
70
+ # @deprecated This method is deprecated and only exists to support Ruby
71
+ # 2.0, which is not supported by HTTP::Accept.
72
+ #
73
+ # @todo remove this method when dropping support for Ruby 2.0
74
+ #
75
+ def self._cgi_parseparam(s)
76
+ return enum_for(__method__, s) unless block_given?
77
+
78
+ while s[0] == ';'
79
+ s = s[1..-1]
80
+ ends = s.index(';')
81
+ while ends && ends > 0 \
82
+ && (s[0...ends].count('"') -
83
+ s[0...ends].scan('\"').count) % 2 != 0
84
+ ends = s.index(';', ends + 1)
85
+ end
86
+ if ends.nil?
87
+ ends = s.length
88
+ end
89
+ f = s[0...ends]
90
+ yield f.strip
91
+ s = s[ends..-1]
92
+ end
93
+ nil
94
+ end
95
+
96
+ # Parse a Content-Type like header.
97
+ #
98
+ # Return the main content-type and a hash of options.
99
+ #
100
+ # This method was ported directly from Python's cgi.parse_header(). It
101
+ # probably doesn't read or perform particularly well in ruby.
102
+ # https://github.com/python/cpython/blob/3.4/Lib/cgi.py#L301-L331
103
+ #
104
+ # @param [String] line
105
+ # @return [Array(String, Hash)]
106
+ #
107
+ # @deprecated This method is deprecated and only exists to support Ruby
108
+ # 2.0, which is not supported by HTTP::Accept.
109
+ #
110
+ # @todo remove this method when dropping support for Ruby 2.0
111
+ #
112
+ def self.deprecated_cgi_parse_header(line)
113
+ parts = _cgi_parseparam(';' + line)
114
+ key = parts.next
115
+ pdict = {}
116
+
117
+ begin
118
+ while (p = parts.next)
119
+ i = p.index('=')
120
+ if i
121
+ name = p[0...i].strip.downcase
122
+ value = p[i+1..-1].strip
123
+ if value.length >= 2 && value[0] == '"' && value[-1] == '"'
124
+ value = value[1...-1]
125
+ value = value.gsub('\\\\', '\\').gsub('\\"', '"')
126
+ end
127
+ pdict[name] = value
128
+ end
129
+ end
130
+ rescue StopIteration
131
+ end
132
+
133
+ [key, pdict]
134
+ end
135
+
136
+ # Serialize a ruby object into HTTP query string parameters.
137
+ #
138
+ # There is no standard for doing this, so we choose our own slightly
139
+ # idiosyncratic format. The output closely matches the format understood by
140
+ # Rails, Rack, and PHP.
141
+ #
142
+ # If you don't want handling of complex objects and only want to handle
143
+ # simple flat hashes, you may want to use `URI.encode_www_form` instead,
144
+ # which implements HTML5-compliant URL encoded form data.
145
+ #
146
+ # @param [Hash,ParamsArray] object The object to serialize
147
+ #
148
+ # @return [String] A string appropriate for use as an HTTP query string
149
+ #
150
+ # @see {flatten_params}
151
+ #
152
+ # @see URI.encode_www_form
153
+ #
154
+ # @see See also Object#to_query in ActiveSupport
155
+ # @see http://php.net/manual/en/function.http-build-query.php
156
+ # http_build_query in PHP
157
+ # @see See also Rack::Utils.build_nested_query in Rack
158
+ #
159
+ # Notable differences from the ActiveSupport implementation:
160
+ #
161
+ # - Empty hash and empty array are treated the same as nil instead of being
162
+ # omitted entirely from the output. Rather than disappearing, they will
163
+ # appear to be nil instead.
164
+ #
165
+ # It's most common to pass a Hash as the object to serialize, but you can
166
+ # also use a ParamsArray if you want to be able to pass the same key with
167
+ # multiple values and not use the rack/rails array convention.
168
+ #
169
+ # @since 2.0.0
170
+ #
171
+ # @example Simple hashes
172
+ # >> encode_query_string({foo: 123, bar: 456})
173
+ # => 'foo=123&bar=456'
174
+ #
175
+ # @example Simple arrays
176
+ # >> encode_query_string({foo: [1,2,3]})
177
+ # => 'foo[]=1&foo[]=2&foo[]=3'
178
+ #
179
+ # @example Nested hashes
180
+ # >> encode_query_string({outer: {foo: 123, bar: 456}})
181
+ # => 'outer[foo]=123&outer[bar]=456'
182
+ #
183
+ # @example Deeply nesting
184
+ # >> encode_query_string({coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]})
185
+ # => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3'
186
+ #
187
+ # @example Null and empty values
188
+ # >> encode_query_string({string: '', empty: nil, list: [], hash: {}})
189
+ # => 'string=&empty&list&hash'
190
+ #
191
+ # @example Nested nulls
192
+ # >> encode_query_string({foo: {string: '', empty: nil}})
193
+ # => 'foo[string]=&foo[empty]'
194
+ #
195
+ # @example Multiple fields with the same name using ParamsArray
196
+ # >> encode_query_string(RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]]))
197
+ # => 'foo=1&foo=2&foo=3'
198
+ #
199
+ # @example Nested ParamsArray
200
+ # >> encode_query_string({foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])})
201
+ # => 'foo[a]=1&foo[a]=2'
202
+ #
203
+ # >> encode_query_string(RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]]))
204
+ # => 'foo[a]=1&foo[a]=2'
205
+ #
206
+ def self.encode_query_string(object)
207
+ flatten_params(object, true).map {|k, v| v.nil? ? k : "#{k}=#{v}" }.join('&')
208
+ end
209
+
210
+ # Transform deeply nested param containers into a flat array of [key,
211
+ # value] pairs.
212
+ #
213
+ # @example
214
+ # >> flatten_params({key1: {key2: 123}})
215
+ # => [["key1[key2]", 123]]
216
+ #
217
+ # @example
218
+ # >> flatten_params({key1: {key2: 123, arr: [1,2,3]}})
219
+ # => [["key1[key2]", 123], ["key1[arr][]", 1], ["key1[arr][]", 2], ["key1[arr][]", 3]]
220
+ #
221
+ # @param object [Hash, ParamsArray] The container to flatten
222
+ # @param uri_escape [Boolean] Whether to URI escape keys and values
223
+ # @param parent_key [String] Should not be passed (used for recursion)
224
+ #
225
+ def self.flatten_params(object, uri_escape=false, parent_key=nil)
226
+ unless object.is_a?(Hash) || object.is_a?(ParamsArray) ||
227
+ (parent_key && object.is_a?(Array))
228
+ raise ArgumentError.new('expected Hash or ParamsArray, got: ' + object.inspect)
229
+ end
230
+
231
+ # transform empty collections into nil, where possible
232
+ if object.empty? && parent_key
233
+ return [[parent_key, nil]]
234
+ end
235
+
236
+ # This is essentially .map(), but we need to do += for nested containers
237
+ object.reduce([]) { |result, item|
238
+ if object.is_a?(Array)
239
+ # item is already the value
240
+ k = nil
241
+ v = item
242
+ else
243
+ # item is a key, value pair
244
+ k, v = item
245
+ k = escape(k.to_s) if uri_escape
246
+ end
247
+
248
+ processed_key = parent_key ? "#{parent_key}[#{k}]" : k
249
+
250
+ case v
251
+ when Array, Hash, ParamsArray
252
+ result.concat flatten_params(v, uri_escape, processed_key)
253
+ else
254
+ v = escape(v.to_s) if uri_escape && v
255
+ result << [processed_key, v]
256
+ end
257
+ }
258
+ end
259
+
260
+ # Encode string for safe transport by URI or form encoding. This uses a CGI
261
+ # style escape, which transforms ` ` into `+` and various special
262
+ # characters into percent encoded forms.
263
+ #
264
+ # This calls URI.encode_www_form_component for the implementation. The only
265
+ # difference between this and CGI.escape is that it does not escape `*`.
266
+ # http://stackoverflow.com/questions/25085992/
267
+ #
268
+ # @see URI.encode_www_form_component
269
+ #
270
+ def self.escape(string)
271
+ URI.encode_www_form_component(string)
272
+ end
273
+ end
274
+ end