rest-client 1.7.0.rc1-x86-mswin32

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rest-client might be problematic. Click here for more details.

Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +14 -0
  5. data/AUTHORS +81 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +21 -0
  8. data/README.rdoc +325 -0
  9. data/Rakefile +117 -0
  10. data/bin/restclient +93 -0
  11. data/history.md +166 -0
  12. data/lib/rest-client.rb +2 -0
  13. data/lib/rest_client.rb +2 -0
  14. data/lib/restclient.rb +164 -0
  15. data/lib/restclient/abstract_response.rb +106 -0
  16. data/lib/restclient/exceptions.rb +203 -0
  17. data/lib/restclient/payload.rb +240 -0
  18. data/lib/restclient/platform.rb +30 -0
  19. data/lib/restclient/raw_response.rb +34 -0
  20. data/lib/restclient/request.rb +582 -0
  21. data/lib/restclient/resource.rb +169 -0
  22. data/lib/restclient/response.rb +24 -0
  23. data/lib/restclient/version.rb +7 -0
  24. data/lib/restclient/windows.rb +8 -0
  25. data/lib/restclient/windows/root_certs.rb +105 -0
  26. data/rest-client.gemspec +30 -0
  27. data/rest-client.windows.gemspec +19 -0
  28. data/spec/integration/capath_digicert/244b5494.0 +19 -0
  29. data/spec/integration/capath_digicert/81b9768f.0 +19 -0
  30. data/spec/integration/capath_digicert/README +8 -0
  31. data/spec/integration/capath_digicert/digicert.crt +19 -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/certs/digicert.crt +19 -0
  37. data/spec/integration/certs/verisign.crt +14 -0
  38. data/spec/integration/integration_spec.rb +35 -0
  39. data/spec/integration/request_spec.rb +104 -0
  40. data/spec/spec_helper.rb +12 -0
  41. data/spec/unit/abstract_response_spec.rb +85 -0
  42. data/spec/unit/exceptions_spec.rb +95 -0
  43. data/spec/unit/master_shake.jpg +0 -0
  44. data/spec/unit/payload_spec.rb +245 -0
  45. data/spec/unit/raw_response_spec.rb +17 -0
  46. data/spec/unit/request2_spec.rb +32 -0
  47. data/spec/unit/request_spec.rb +905 -0
  48. data/spec/unit/resource_spec.rb +133 -0
  49. data/spec/unit/response_spec.rb +166 -0
  50. data/spec/unit/restclient_spec.rb +79 -0
  51. data/spec/unit/windows/root_certs_spec.rb +22 -0
  52. metadata +241 -0
@@ -0,0 +1,106 @@
1
+ require 'cgi'
2
+
3
+ module RestClient
4
+
5
+ module AbstractResponse
6
+
7
+ attr_reader :net_http_res, :args
8
+
9
+ # HTTP status code
10
+ def code
11
+ @code ||= @net_http_res.code.to_i
12
+ end
13
+
14
+ # A hash of the headers, beautified with symbols and underscores.
15
+ # e.g. "Content-type" will become :content_type.
16
+ def headers
17
+ @headers ||= AbstractResponse.beautify_headers(@net_http_res.to_hash)
18
+ end
19
+
20
+ # The raw headers.
21
+ def raw_headers
22
+ @raw_headers ||= @net_http_res.to_hash
23
+ end
24
+
25
+ # Hash of cookies extracted from response headers
26
+ def cookies
27
+ @cookies ||= (self.headers[:set_cookie] || {}).inject({}) do |out, cookie_content|
28
+ out.merge parse_cookie(cookie_content)
29
+ end
30
+ end
31
+
32
+ # Return the default behavior corresponding to the response code:
33
+ # the response itself for code in 200..206, redirection for 301, 302 and 307 in get and head cases, redirection for 303 and an exception in other cases
34
+ def return! request = nil, result = nil, & block
35
+ if (200..207).include? code
36
+ self
37
+ elsif [301, 302, 307].include? code
38
+ unless [:get, :head].include? args[:method]
39
+ raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
40
+ else
41
+ follow_redirection(request, result, & block)
42
+ end
43
+ elsif code == 303
44
+ args[:method] = :get
45
+ args.delete :payload
46
+ follow_redirection(request, result, & block)
47
+ elsif Exceptions::EXCEPTIONS_MAP[code]
48
+ raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
49
+ else
50
+ raise RequestFailed.new(self, code)
51
+ end
52
+ end
53
+
54
+ def to_i
55
+ code
56
+ end
57
+
58
+ def description
59
+ "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
60
+ end
61
+
62
+ # Follow a redirection
63
+ def follow_redirection request = nil, result = nil, & block
64
+ url = headers[:location]
65
+ if url !~ /^http/
66
+ url = URI.parse(args[:url]).merge(url).to_s
67
+ end
68
+ args[:url] = url
69
+ if request
70
+ if request.max_redirects == 0
71
+ raise MaxRedirectsReached
72
+ end
73
+ args[:password] = request.password
74
+ args[:user] = request.user
75
+ args[:headers] = request.headers
76
+ args[:max_redirects] = request.max_redirects - 1
77
+ # pass any cookie set in the result
78
+ if result && result['set-cookie']
79
+ args[:headers][:cookies] = (args[:headers][:cookies] || {}).merge(parse_cookie(result['set-cookie']))
80
+ end
81
+ end
82
+ Request.execute args, &block
83
+ end
84
+
85
+ def AbstractResponse.beautify_headers(headers)
86
+ headers.inject({}) do |out, (key, value)|
87
+ out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first
88
+ out
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ # Parse a cookie value and return its content in an Hash
95
+ def parse_cookie cookie_content
96
+ out = {}
97
+ CGI::Cookie::parse(cookie_content).each do |key, cookie|
98
+ unless ['expires', 'path'].include? key
99
+ out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : ''
100
+ end
101
+ end
102
+ out
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,203 @@
1
+ module RestClient
2
+
3
+ STATUSES = {100 => 'Continue',
4
+ 101 => 'Switching Protocols',
5
+ 102 => 'Processing', #WebDAV
6
+
7
+ 200 => 'OK',
8
+ 201 => 'Created',
9
+ 202 => 'Accepted',
10
+ 203 => 'Non-Authoritative Information', # http/1.1
11
+ 204 => 'No Content',
12
+ 205 => 'Reset Content',
13
+ 206 => 'Partial Content',
14
+ 207 => 'Multi-Status', #WebDAV
15
+
16
+ 300 => 'Multiple Choices',
17
+ 301 => 'Moved Permanently',
18
+ 302 => 'Found',
19
+ 303 => 'See Other', # http/1.1
20
+ 304 => 'Not Modified',
21
+ 305 => 'Use Proxy', # http/1.1
22
+ 306 => 'Switch Proxy', # no longer used
23
+ 307 => 'Temporary Redirect', # http/1.1
24
+
25
+ 400 => 'Bad Request',
26
+ 401 => 'Unauthorized',
27
+ 402 => 'Payment Required',
28
+ 403 => 'Forbidden',
29
+ 404 => 'Resource Not Found',
30
+ 405 => 'Method Not Allowed',
31
+ 406 => 'Not Acceptable',
32
+ 407 => 'Proxy Authentication Required',
33
+ 408 => 'Request Timeout',
34
+ 409 => 'Conflict',
35
+ 410 => 'Gone',
36
+ 411 => 'Length Required',
37
+ 412 => 'Precondition Failed',
38
+ 413 => 'Request Entity Too Large',
39
+ 414 => 'Request-URI Too Long',
40
+ 415 => 'Unsupported Media Type',
41
+ 416 => 'Requested Range Not Satisfiable',
42
+ 417 => 'Expectation Failed',
43
+ 418 => 'I\'m A Teapot', #RFC2324
44
+ 421 => 'Too Many Connections From This IP',
45
+ 422 => 'Unprocessable Entity', #WebDAV
46
+ 423 => 'Locked', #WebDAV
47
+ 424 => 'Failed Dependency', #WebDAV
48
+ 425 => 'Unordered Collection', #WebDAV
49
+ 426 => 'Upgrade Required',
50
+ 428 => 'Precondition Required', #RFC6585
51
+ 429 => 'Too Many Requests', #RFC6585
52
+ 431 => 'Request Header Fields Too Large', #RFC6585
53
+ 449 => 'Retry With', #Microsoft
54
+ 450 => 'Blocked By Windows Parental Controls', #Microsoft
55
+
56
+ 500 => 'Internal Server Error',
57
+ 501 => 'Not Implemented',
58
+ 502 => 'Bad Gateway',
59
+ 503 => 'Service Unavailable',
60
+ 504 => 'Gateway Timeout',
61
+ 505 => 'HTTP Version Not Supported',
62
+ 506 => 'Variant Also Negotiates',
63
+ 507 => 'Insufficient Storage', #WebDAV
64
+ 509 => 'Bandwidth Limit Exceeded', #Apache
65
+ 510 => 'Not Extended',
66
+ 511 => 'Network Authentication Required', # RFC6585
67
+ }
68
+
69
+ # Compatibility : make the Response act like a Net::HTTPResponse when needed
70
+ module ResponseForException
71
+ def method_missing symbol, *args
72
+ if net_http_res.respond_to? symbol
73
+ warn "[warning] The response contained in an RestClient::Exception is now a RestClient::Response instead of a Net::HTTPResponse, please update your code"
74
+ net_http_res.send symbol, *args
75
+ else
76
+ super
77
+ end
78
+ end
79
+ end
80
+
81
+ # This is the base RestClient exception class. Rescue it if you want to
82
+ # catch any exception that your request might raise
83
+ # You can get the status code by e.http_code, or see anything about the
84
+ # response via e.response.
85
+ # For example, the entire result body (which is
86
+ # probably an HTML error page) is e.response.
87
+ class Exception < RuntimeError
88
+ attr_accessor :response
89
+ attr_writer :message
90
+
91
+ def initialize response = nil, initial_response_code = nil
92
+ @response = response
93
+ @message = nil
94
+ @initial_response_code = initial_response_code
95
+
96
+ # compatibility: this make the exception behave like a Net::HTTPResponse
97
+ response.extend ResponseForException if response
98
+ end
99
+
100
+ def http_code
101
+ # return integer for compatibility
102
+ if @response
103
+ @response.code.to_i
104
+ else
105
+ @initial_response_code
106
+ end
107
+ end
108
+
109
+ def http_body
110
+ @response.body if @response
111
+ end
112
+
113
+ def inspect
114
+ "#{message}: #{http_body}"
115
+ end
116
+
117
+ def to_s
118
+ inspect
119
+ end
120
+
121
+ def message
122
+ @message || self.class.name
123
+ end
124
+
125
+ end
126
+
127
+ # Compatibility
128
+ class ExceptionWithResponse < Exception
129
+ end
130
+
131
+ # The request failed with an error code not managed by the code
132
+ class RequestFailed < ExceptionWithResponse
133
+
134
+ def message
135
+ "HTTP status code #{http_code}"
136
+ end
137
+
138
+ def to_s
139
+ message
140
+ end
141
+ end
142
+
143
+ # We will a create an exception for each status code, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
144
+ module Exceptions
145
+ # Map http status codes to the corresponding exception class
146
+ EXCEPTIONS_MAP = {}
147
+ end
148
+
149
+ STATUSES.each_pair do |code, message|
150
+
151
+ # Compatibility
152
+ superclass = ([304, 401, 404].include? code) ? ExceptionWithResponse : RequestFailed
153
+ klass = Class.new(superclass) do
154
+ send(:define_method, :message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
155
+ end
156
+ klass_constant = const_set message.delete(' \-\''), klass
157
+ Exceptions::EXCEPTIONS_MAP[code] = klass_constant
158
+ end
159
+
160
+ # A redirect was encountered; caught by execute to retry with the new url.
161
+ class Redirect < Exception
162
+
163
+ def message
164
+ 'Redirect'
165
+ end
166
+
167
+ attr_accessor :url
168
+
169
+ def initialize(url)
170
+ @url = url
171
+ end
172
+ end
173
+
174
+ class MaxRedirectsReached < Exception
175
+ def message
176
+ 'Maximum number of redirect reached'
177
+ end
178
+ end
179
+
180
+ # The server broke the connection prior to the request completing. Usually
181
+ # this means it crashed, or sometimes that your network connection was
182
+ # severed before it could complete.
183
+ class ServerBrokeConnection < Exception
184
+ def initialize(message = 'Server broke connection')
185
+ super nil, nil
186
+ self.message = message
187
+ end
188
+ end
189
+
190
+ class SSLCertificateNotVerified < Exception
191
+ def initialize(message)
192
+ super nil, nil
193
+ self.message = message
194
+ end
195
+ end
196
+ end
197
+
198
+ # backwards compatibility
199
+ class RestClient::Request
200
+ Redirect = RestClient::Redirect
201
+ Unauthorized = RestClient::Unauthorized
202
+ RequestFailed = RestClient::RequestFailed
203
+ end
@@ -0,0 +1,240 @@
1
+ require 'tempfile'
2
+ require 'stringio'
3
+ require 'mime/types'
4
+
5
+ module RestClient
6
+ module Payload
7
+ extend self
8
+
9
+ def generate(params)
10
+ if params.is_a?(String)
11
+ Base.new(params)
12
+ elsif params.is_a?(Hash)
13
+ if params.delete(:multipart) == true || has_file?(params)
14
+ Multipart.new(params)
15
+ else
16
+ UrlEncoded.new(params)
17
+ end
18
+ elsif params.respond_to?(:read)
19
+ Streamed.new(params)
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ def has_file?(params)
26
+ params.any? do |_, v|
27
+ case v
28
+ when Hash
29
+ has_file?(v)
30
+ when Array
31
+ has_file_array?(v)
32
+ else
33
+ v.respond_to?(:path) && v.respond_to?(:read)
34
+ end
35
+ end
36
+ end
37
+
38
+ def has_file_array?(params)
39
+ params.any? do |v|
40
+ case v
41
+ when Hash
42
+ has_file?(v)
43
+ when Array
44
+ has_file_array?(v)
45
+ else
46
+ v.respond_to?(:path) && v.respond_to?(:read)
47
+ end
48
+ end
49
+ end
50
+
51
+ class Base
52
+ def initialize(params)
53
+ build_stream(params)
54
+ end
55
+
56
+ def build_stream(params)
57
+ @stream = StringIO.new(params)
58
+ @stream.seek(0)
59
+ end
60
+
61
+ def read(bytes=nil)
62
+ @stream.read(bytes)
63
+ end
64
+
65
+ alias :to_s :read
66
+
67
+ # Flatten parameters by converting hashes of hashes to flat hashes
68
+ # {keys1 => {keys2 => value}} will be transformed into [keys1[key2], value]
69
+ def flatten_params(params, parent_key = nil)
70
+ result = []
71
+ params.each do |key, value|
72
+ calculated_key = parent_key ? "#{parent_key}[#{handle_key(key)}]" : handle_key(key)
73
+ if value.is_a? Hash
74
+ result += flatten_params(value, calculated_key)
75
+ elsif value.is_a? Array
76
+ result += flatten_params_array(value, calculated_key)
77
+ else
78
+ result << [calculated_key, value]
79
+ end
80
+ end
81
+ result
82
+ end
83
+
84
+ def flatten_params_array value, calculated_key
85
+ result = []
86
+ value.each do |elem|
87
+ if elem.is_a? Hash
88
+ result += flatten_params(elem, calculated_key)
89
+ elsif elem.is_a? Array
90
+ result += flatten_params_array(elem, calculated_key)
91
+ else
92
+ result << ["#{calculated_key}[]", elem]
93
+ end
94
+ end
95
+ result
96
+ end
97
+
98
+ def headers
99
+ {'Content-Length' => size.to_s}
100
+ end
101
+
102
+ def size
103
+ @stream.size
104
+ end
105
+
106
+ alias :length :size
107
+
108
+ def close
109
+ @stream.close unless @stream.closed?
110
+ end
111
+
112
+ def inspect
113
+ result = to_s.inspect
114
+ @stream.seek(0)
115
+ result
116
+ end
117
+
118
+ def short_inspect
119
+ (size > 500 ? "#{size} byte(s) length" : inspect)
120
+ end
121
+
122
+ end
123
+
124
+ class Streamed < Base
125
+ def build_stream(params = nil)
126
+ @stream = params
127
+ end
128
+
129
+ def size
130
+ if @stream.respond_to?(:size)
131
+ @stream.size
132
+ elsif @stream.is_a?(IO)
133
+ @stream.stat.size
134
+ end
135
+ end
136
+
137
+ alias :length :size
138
+ end
139
+
140
+ class UrlEncoded < Base
141
+ def build_stream(params = nil)
142
+ @stream = StringIO.new(flatten_params(params).collect do |entry|
143
+ "#{entry[0]}=#{handle_key(entry[1])}"
144
+ end.join("&"))
145
+ @stream.seek(0)
146
+ end
147
+
148
+ # for UrlEncoded escape the keys
149
+ def handle_key key
150
+ parser.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
151
+ end
152
+
153
+ def headers
154
+ super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
155
+ end
156
+
157
+ private
158
+ def parser
159
+ URI.const_defined?(:Parser) ? URI::Parser.new : URI
160
+ end
161
+ end
162
+
163
+ class Multipart < Base
164
+ EOL = "\r\n"
165
+
166
+ def build_stream(params)
167
+ b = "--#{boundary}"
168
+
169
+ @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
170
+ @stream.binmode
171
+ @stream.write(b + EOL)
172
+
173
+ if params.is_a? Hash
174
+ x = flatten_params(params)
175
+ else
176
+ x = params
177
+ end
178
+
179
+ last_index = x.length - 1
180
+ x.each_with_index do |a, index|
181
+ k, v = * a
182
+ if v.respond_to?(:read) && v.respond_to?(:path)
183
+ create_file_field(@stream, k, v)
184
+ else
185
+ create_regular_field(@stream, k, v)
186
+ end
187
+ @stream.write(EOL + b)
188
+ @stream.write(EOL) unless last_index == index
189
+ end
190
+ @stream.write('--')
191
+ @stream.write(EOL)
192
+ @stream.seek(0)
193
+ end
194
+
195
+ def create_regular_field(s, k, v)
196
+ s.write("Content-Disposition: form-data; name=\"#{k}\"")
197
+ s.write(EOL)
198
+ s.write(EOL)
199
+ s.write(v)
200
+ end
201
+
202
+ def create_file_field(s, k, v)
203
+ begin
204
+ s.write("Content-Disposition: form-data;")
205
+ s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
206
+ s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
207
+ s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
208
+ s.write(EOL)
209
+ while data = v.read(8124)
210
+ s.write(data)
211
+ end
212
+ ensure
213
+ v.close if v.respond_to?(:close)
214
+ end
215
+ end
216
+
217
+ def mime_for(path)
218
+ mime = MIME::Types.type_for path
219
+ mime.empty? ? 'text/plain' : mime[0].content_type
220
+ end
221
+
222
+ def boundary
223
+ @boundary ||= rand(1_000_000).to_s
224
+ end
225
+
226
+ # for Multipart do not escape the keys
227
+ def handle_key key
228
+ key
229
+ end
230
+
231
+ def headers
232
+ super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
233
+ end
234
+
235
+ def close
236
+ @stream.close!
237
+ end
238
+ end
239
+ end
240
+ end