rest-client 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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +2 -0
  4. data/.rubocop-disables.yml +384 -0
  5. data/.rubocop.yml +3 -0
  6. data/.travis.yml +48 -0
  7. data/AUTHORS +98 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE +21 -0
  10. data/README.md +784 -0
  11. data/Rakefile +132 -0
  12. data/bin/restclient +92 -0
  13. data/history.md +324 -0
  14. data/lib/rest-client.rb +2 -0
  15. data/lib/rest_client.rb +2 -0
  16. data/lib/restclient.rb +184 -0
  17. data/lib/restclient/abstract_response.rb +226 -0
  18. data/lib/restclient/exceptions.rb +244 -0
  19. data/lib/restclient/params_array.rb +72 -0
  20. data/lib/restclient/payload.rb +209 -0
  21. data/lib/restclient/platform.rb +49 -0
  22. data/lib/restclient/raw_response.rb +38 -0
  23. data/lib/restclient/request.rb +853 -0
  24. data/lib/restclient/resource.rb +168 -0
  25. data/lib/restclient/response.rb +80 -0
  26. data/lib/restclient/utils.rb +235 -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 +31 -0
  31. data/rest-client.windows.gemspec +19 -0
  32. data/spec/helpers.rb +22 -0
  33. data/spec/integration/_lib.rb +1 -0
  34. data/spec/integration/capath_digicert/244b5494.0 +19 -0
  35. data/spec/integration/capath_digicert/81b9768f.0 +19 -0
  36. data/spec/integration/capath_digicert/README +8 -0
  37. data/spec/integration/capath_digicert/digicert.crt +19 -0
  38. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  39. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  40. data/spec/integration/capath_verisign/README +8 -0
  41. data/spec/integration/capath_verisign/verisign.crt +14 -0
  42. data/spec/integration/certs/digicert.crt +19 -0
  43. data/spec/integration/certs/verisign.crt +14 -0
  44. data/spec/integration/httpbin_spec.rb +87 -0
  45. data/spec/integration/integration_spec.rb +125 -0
  46. data/spec/integration/request_spec.rb +127 -0
  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/master_shake.jpg +0 -0
  52. data/spec/unit/params_array_spec.rb +36 -0
  53. data/spec/unit/payload_spec.rb +263 -0
  54. data/spec/unit/raw_response_spec.rb +18 -0
  55. data/spec/unit/request2_spec.rb +54 -0
  56. data/spec/unit/request_spec.rb +1250 -0
  57. data/spec/unit/resource_spec.rb +134 -0
  58. data/spec/unit/response_spec.rb +241 -0
  59. data/spec/unit/restclient_spec.rb +79 -0
  60. data/spec/unit/utils_spec.rb +147 -0
  61. data/spec/unit/windows/root_certs_spec.rb +22 -0
  62. metadata +282 -0
@@ -0,0 +1,168 @@
1
+ module RestClient
2
+ # A class that can be instantiated for access to a RESTful resource,
3
+ # including authentication.
4
+ #
5
+ # Example:
6
+ #
7
+ # resource = RestClient::Resource.new('http://some/resource')
8
+ # jpg = resource.get(:accept => 'image/jpg')
9
+ #
10
+ # With HTTP basic authentication:
11
+ #
12
+ # resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
13
+ # resource.delete
14
+ #
15
+ # With a timeout (seconds):
16
+ #
17
+ # RestClient::Resource.new('http://slow', :read_timeout => 10)
18
+ #
19
+ # With an open timeout (seconds):
20
+ #
21
+ # RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
22
+ #
23
+ # You can also use resources to share common headers. For headers keys,
24
+ # symbols are converted to strings. Example:
25
+ #
26
+ # resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
27
+ #
28
+ # This header will be transported as X-Client-Version (notice the X prefix,
29
+ # capitalization and hyphens)
30
+ #
31
+ # Use the [] syntax to allocate subresources:
32
+ #
33
+ # site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
34
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
35
+ #
36
+ class Resource
37
+ attr_reader :url, :options, :block
38
+
39
+ def initialize(url, options={}, backwards_compatibility=nil, &block)
40
+ @url = url
41
+ @block = block
42
+ if options.class == Hash
43
+ @options = options
44
+ else # compatibility with previous versions
45
+ @options = { :user => options, :password => backwards_compatibility }
46
+ end
47
+ end
48
+
49
+ def get(additional_headers={}, &block)
50
+ headers = (options[:headers] || {}).merge(additional_headers)
51
+ Request.execute(options.merge(
52
+ :method => :get,
53
+ :url => url,
54
+ :headers => headers), &(block || @block))
55
+ end
56
+
57
+ def head(additional_headers={}, &block)
58
+ headers = (options[:headers] || {}).merge(additional_headers)
59
+ Request.execute(options.merge(
60
+ :method => :head,
61
+ :url => url,
62
+ :headers => headers), &(block || @block))
63
+ end
64
+
65
+ def post(payload, additional_headers={}, &block)
66
+ headers = (options[:headers] || {}).merge(additional_headers)
67
+ Request.execute(options.merge(
68
+ :method => :post,
69
+ :url => url,
70
+ :payload => payload,
71
+ :headers => headers), &(block || @block))
72
+ end
73
+
74
+ def put(payload, additional_headers={}, &block)
75
+ headers = (options[:headers] || {}).merge(additional_headers)
76
+ Request.execute(options.merge(
77
+ :method => :put,
78
+ :url => url,
79
+ :payload => payload,
80
+ :headers => headers), &(block || @block))
81
+ end
82
+
83
+ def patch(payload, additional_headers={}, &block)
84
+ headers = (options[:headers] || {}).merge(additional_headers)
85
+ Request.execute(options.merge(
86
+ :method => :patch,
87
+ :url => url,
88
+ :payload => payload,
89
+ :headers => headers), &(block || @block))
90
+ end
91
+
92
+ def delete(additional_headers={}, &block)
93
+ headers = (options[:headers] || {}).merge(additional_headers)
94
+ Request.execute(options.merge(
95
+ :method => :delete,
96
+ :url => url,
97
+ :headers => headers), &(block || @block))
98
+ end
99
+
100
+ def to_s
101
+ url
102
+ end
103
+
104
+ def user
105
+ options[:user]
106
+ end
107
+
108
+ def password
109
+ options[:password]
110
+ end
111
+
112
+ def headers
113
+ options[:headers] || {}
114
+ end
115
+
116
+ def read_timeout
117
+ options[:read_timeout]
118
+ end
119
+
120
+ def open_timeout
121
+ options[:open_timeout]
122
+ end
123
+
124
+ # Construct a subresource, preserving authentication.
125
+ #
126
+ # Example:
127
+ #
128
+ # site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
129
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
130
+ #
131
+ # This is especially useful if you wish to define your site in one place and
132
+ # call it in multiple locations:
133
+ #
134
+ # def orders
135
+ # RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
136
+ # end
137
+ #
138
+ # orders.get # GET http://example.com/orders
139
+ # orders['1'].get # GET http://example.com/orders/1
140
+ # orders['1/items'].delete # DELETE http://example.com/orders/1/items
141
+ #
142
+ # Nest resources as far as you want:
143
+ #
144
+ # site = RestClient::Resource.new('http://example.com')
145
+ # posts = site['posts']
146
+ # first_post = posts['1']
147
+ # comments = first_post['comments']
148
+ # comments.post 'Hello', :content_type => 'text/plain'
149
+ #
150
+ def [](suburl, &new_block)
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 self.class.new(concat_urls(url, suburl), options)
155
+ end
156
+ end
157
+
158
+ def concat_urls(url, suburl) # :nodoc:
159
+ url = url.to_s
160
+ suburl = suburl.to_s
161
+ if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
162
+ url + suburl
163
+ else
164
+ "#{url}/#{suburl}"
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,80 @@
1
+ module RestClient
2
+
3
+ # A Response from RestClient, you can access the response body, the code or the headers.
4
+ #
5
+ class Response < String
6
+
7
+ include AbstractResponse
8
+
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
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
35
+ end
36
+
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
+
47
+ result
48
+ end
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
79
+ end
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