rest-client 1.8.0 → 2.1.0

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 (55) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.mailmap +10 -0
  4. data/.rspec +2 -1
  5. data/.rubocop +2 -0
  6. data/.rubocop-disables.yml +386 -0
  7. data/.rubocop.yml +8 -0
  8. data/.travis.yml +56 -8
  9. data/AUTHORS +26 -1
  10. data/README.md +901 -0
  11. data/Rakefile +27 -3
  12. data/bin/restclient +3 -5
  13. data/history.md +181 -0
  14. data/lib/restclient/abstract_response.rb +172 -55
  15. data/lib/restclient/exceptions.rb +96 -55
  16. data/lib/restclient/params_array.rb +72 -0
  17. data/lib/restclient/payload.rb +70 -74
  18. data/lib/restclient/platform.rb +19 -0
  19. data/lib/restclient/raw_response.rb +21 -7
  20. data/lib/restclient/request.rb +540 -281
  21. data/lib/restclient/resource.rb +19 -9
  22. data/lib/restclient/response.rb +75 -6
  23. data/lib/restclient/utils.rb +274 -0
  24. data/lib/restclient/version.rb +2 -1
  25. data/lib/restclient.rb +21 -3
  26. data/rest-client.gemspec +12 -10
  27. data/spec/ISS.jpg +0 -0
  28. data/spec/helpers.rb +54 -0
  29. data/spec/integration/_lib.rb +1 -0
  30. data/spec/integration/capath_digicert/3513523f.0 +22 -0
  31. data/spec/integration/capath_digicert/399e7759.0 +22 -0
  32. data/spec/integration/capath_digicert/digicert.crt +20 -17
  33. data/spec/integration/certs/digicert.crt +20 -17
  34. data/spec/integration/httpbin_spec.rb +128 -0
  35. data/spec/integration/integration_spec.rb +97 -14
  36. data/spec/integration/request_spec.rb +25 -2
  37. data/spec/spec_helper.rb +28 -1
  38. data/spec/unit/_lib.rb +1 -0
  39. data/spec/unit/abstract_response_spec.rb +95 -38
  40. data/spec/unit/exceptions_spec.rb +41 -28
  41. data/spec/unit/params_array_spec.rb +36 -0
  42. data/spec/unit/payload_spec.rb +118 -68
  43. data/spec/unit/raw_response_spec.rb +10 -6
  44. data/spec/unit/request2_spec.rb +34 -12
  45. data/spec/unit/request_spec.rb +745 -424
  46. data/spec/unit/resource_spec.rb +31 -27
  47. data/spec/unit/response_spec.rb +134 -57
  48. data/spec/unit/restclient_spec.rb +16 -15
  49. data/spec/unit/utils_spec.rb +147 -0
  50. data/spec/unit/windows/root_certs_spec.rb +3 -3
  51. metadata +79 -29
  52. data/README.rdoc +0 -324
  53. data/spec/integration/capath_digicert/244b5494.0 +0 -19
  54. data/spec/integration/capath_digicert/81b9768f.0 +0 -19
  55. data/spec/unit/master_shake.jpg +0 -0
data/Rakefile CHANGED
@@ -34,6 +34,21 @@ RSpec::Core::RakeTask.new('rcov') do |t|
34
34
  t.rcov_opts = ['--exclude', 'examples']
35
35
  end
36
36
 
37
+ desc 'Regenerate authors file'
38
+ task :authors do
39
+ Dir.chdir(File.dirname(__FILE__)) do
40
+ File.open('AUTHORS', 'w') do |f|
41
+ f.write <<-EOM
42
+ The Ruby REST Client would not be what it is today without the help of
43
+ the following kind souls:
44
+
45
+ EOM
46
+ end
47
+
48
+ sh 'git shortlog -s | cut -f 2 >> AUTHORS'
49
+ end
50
+ end
51
+
37
52
  task :default do
38
53
  sh 'rake -T'
39
54
  end
@@ -54,8 +69,8 @@ namespace :all do
54
69
  task :build => ['ruby:build'] + \
55
70
  WindowsPlatforms.map {|p| "windows:#{p}:build"}
56
71
 
57
- desc "Create tag v#{RestClient::VERSION} and for all platforms build and push " \
58
- "rest-client #{RestClient::VERSION} to Rubygems"
72
+ desc "Create tag v#{RestClient::VERSION} and for all platforms build and " \
73
+ "push rest-client #{RestClient::VERSION} to Rubygems"
59
74
  task :release => ['build', 'ruby:release'] + \
60
75
  WindowsPlatforms.map {|p| "windows:#{p}:push"}
61
76
 
@@ -111,6 +126,15 @@ Rake::RDocTask.new do |t|
111
126
  t.title = "rest-client, fetch RESTful resources effortlessly"
112
127
  t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
113
128
  t.options << '--charset' << 'utf-8'
114
- t.rdoc_files.include('README.rdoc')
129
+ t.rdoc_files.include('README.md')
115
130
  t.rdoc_files.include('lib/*.rb')
116
131
  end
132
+
133
+ ############################
134
+
135
+ require 'rubocop/rake_task'
136
+
137
+ RuboCop::RakeTask.new(:rubocop) do |t|
138
+ t.options = ['--display-cop-names']
139
+ end
140
+ alias_task(:lint, :rubocop)
data/bin/restclient CHANGED
@@ -56,11 +56,9 @@ if @verb
56
56
  end
57
57
 
58
58
  POSSIBLE_VERBS.each do |m|
59
- eval <<-end_eval
60
- def #{m}(path, *args, &b)
61
- r[path].#{m}(*args, &b)
62
- end
63
- end_eval
59
+ define_method(m.to_sym) do |path, *args, &b|
60
+ r[path].public_send(m.to_sym, *args, &b)
61
+ end
64
62
  end
65
63
 
66
64
  def method_missing(s, * args, & b)
data/history.md CHANGED
@@ -1,3 +1,168 @@
1
+ # 2.1.0
2
+
3
+ - Add a dependency on http-accept for parsing Content-Type charset headers.
4
+ This works around a bad memory leak introduced in MRI Ruby 2.4.0 and fixed in
5
+ Ruby 2.4.2. (#615)
6
+ - Use mime/types/columnar from mime-types 2.6.1+, which is leaner in memory
7
+ usage than the older storage model of mime-types. (#393)
8
+ - Add `:log` option to individual requests. This allows users to set a log on a
9
+ per-request / per-resource basis instead of the kludgy global log. (#538)
10
+ - Log request duration by tracking request start and end times. Make
11
+ `log_response` a method on the Response object, and ensure the `size` method
12
+ works on RawResponse objects. (#126)
13
+ - `# => 200 OK | text/html 1270 bytes, 0.08s`
14
+ - Also add a new `:stream_log_percent` parameter, which is applicable only
15
+ when `:raw_response => true` is set. This causes progress logs to be
16
+ emitted only on every N% (default 10%) of the total download size rather
17
+ than on every chunk.
18
+ - Drop custom handling of compression and use built-in Net::HTTP support for
19
+ supported Content-Encodings like gzip and deflate. Don't set any explicit
20
+ `Accept-Encoding` header, rely instead on Net::HTTP defaults. (#597)
21
+ - Note: this changes behavior for compressed responses when using
22
+ `:raw_response => true`. Previously the raw response would not have been
23
+ uncompressed by rest-client, but now Net::HTTP will uncompress it.
24
+ - The previous fix to avoid having Netrc username/password override an
25
+ Authorization header was case-sensitive and incomplete. Fix this by
26
+ respecting existing Authorization headers, regardless of letter case. (#550)
27
+ - Handle ParamsArray payloads. Previously, rest-client would silently drop a
28
+ ParamsArray passed as the payload. Instead, automatically use
29
+ Payload::Multipart if the ParamsArray contains a file handle, or use
30
+ Payload::UrlEncoded if it doesn't. (#508)
31
+ - Gracefully handle Payload objects (Payload::Base or subclasses) that are
32
+ passed as a payload argument. Previously, `Payload.generate` would wrap a
33
+ Payload object in Payload::Streamed, creating a pointlessly nested payload.
34
+ Also add a `closed?` method to Payload objects, and don't error in
35
+ `short_inspect` if `size` returns nil. (#603)
36
+ - Test with an image in the public domain to avoid licensing complexity. (#607)
37
+
38
+ # 2.0.2
39
+
40
+ - Suppress the header override warning introduced in 2.0.1 if the value is the
41
+ same. There's no conflict if the value is unchanged. (#578)
42
+
43
+ # 2.0.1
44
+
45
+ - Warn if auto-generated headers from the payload, such as Content-Type,
46
+ override headers set by the user. This is usually not what the user wants to
47
+ happen, and can be surprising. (#554)
48
+ - Drop the old check for weak default TLS ciphers, and use the built-in Ruby
49
+ defaults. Ruby versions from Oct. 2014 onward use sane defaults, so this is
50
+ no longer needed. (#573)
51
+
52
+ # 2.0.0
53
+
54
+ This release is largely API compatible, but makes several breaking changes.
55
+
56
+ - Drop support for Ruby 1.9
57
+ - Allow mime-types as new as 3.x (requires ruby 2.0)
58
+ - Respect Content-Type charset header provided by server. Previously,
59
+ rest-client would not override the string encoding chosen by Net::HTTP. Now
60
+ responses that specify a charset will yield a body string in that encoding.
61
+ For example, `Content-Type: text/plain; charset=EUC-JP` will return a String
62
+ encoded with `Encoding::EUC_JP`. (#361)
63
+ - Change exceptions raised on request timeout. Instead of
64
+ `RestClient::RequestTimeout` (which is still used for HTTP 408), network
65
+ timeouts will now raise either `RestClient::Exceptions::ReadTimeout` or
66
+ `RestClient::Exceptions::OpenTimeout`, both of which inherit from
67
+ `RestClient::Exceptions::Timeout`. For backwards compatibility, this still
68
+ inherits from `RestClient::RequestTimeout` so existing uses will still work.
69
+ This may change in a future major release. These new timeout classes also
70
+ make the original wrapped exception available as `#original_exception`.
71
+ - Unify request exceptions under `RestClient::RequestFailed`, which still
72
+ inherits from `ExceptionWithResponse`. Previously, HTTP 304, 401, and 404
73
+ inherited directly from `ExceptionWithResponse` rather than from
74
+ `RequestFailed`. Now _all_ HTTP status code exceptions inherit from both.
75
+ - Rename the `:timeout` request option to `:read_timeout`. When `:timeout` is
76
+ passed, now set both `:read_timeout` and `:open_timeout`.
77
+ - Change default HTTP Accept header to `*/*`
78
+ - Use a more descriptive User-Agent header by default
79
+ - Drop RC4-MD5 from default cipher list
80
+ - Only prepend http:// to URIs without a scheme
81
+ - Fix some support for using IPv6 addresses in URLs (still affected by Ruby
82
+ 2.0+ bug https://bugs.ruby-lang.org/issues/9129, with the fix expected to be
83
+ backported to 2.0 and 2.1)
84
+ - `Response` objects are now a subclass of `String` rather than a `String` that
85
+ mixes in the response functionality. Most of the methods remain unchanged,
86
+ but this makes it much easier to understand what is happening when you look
87
+ at a RestClient response object. There are a few additional changes:
88
+ - Response objects now implement `.inspect` to make this distinction clearer.
89
+ - `Response#to_i` will now behave like `String#to_i` instead of returning the
90
+ HTTP response code, which was very surprising behavior.
91
+ - `Response#body` and `#to_s` will now return a true `String` object rather
92
+ than self. Previously there was no easy way to get the true `String`
93
+ response instead of the Frankenstein response string object with
94
+ AbstractResponse mixed in.
95
+ - Response objects no longer accept an extra request args hash, but instead
96
+ access request args directly from the request object, which reduces
97
+ confusion and duplication.
98
+ - Handle multiple HTTP response headers with the same name (except for
99
+ Set-Cookie, which is special) by joining the values with a comma space,
100
+ compliant with RFC 7230
101
+ - Rewrite cookie support to be much smarter and to use cookie jars consistently
102
+ for requests, responses, and redirection in order to resolve long-standing
103
+ complaints about the previously broken behavior: (#498)
104
+ - The `:cookies` option may now be a Hash of Strings, an Array of
105
+ HTTP::Cookie objects, or a full HTTP::CookieJar.
106
+ - Add `RestClient::Request#cookie_jar` and reimplement `Request#cookies` to
107
+ be a wrapper around the cookie jar.
108
+ - Still support passing the `:cookies` option in the headers hash, but now
109
+ raise ArgumentError if that option is also passed to `Request#initialize`.
110
+ - Warn if both `:cookies` and a `Cookie` header are supplied.
111
+ - Use the `Request#cookie_jar` as the basis for `Response#cookie_jar`,
112
+ creating a copy of the jar and adding any newly received cookies.
113
+ - When following redirection, also use this same strategy so that cookies
114
+ from the original request are carried through in a standards-compliant way
115
+ by the cookie jar.
116
+ - Don't set basic auth header if explicit `Authorization` header is specified
117
+ - Add `:proxy` option to requests, which can be used for thread-safe
118
+ per-request proxy configuration, overriding `RestClient.proxy`
119
+ - Allow overriding `ENV['http_proxy']` to disable proxies by setting
120
+ `RestClient.proxy` to a falsey value. Previously there was no way in Ruby 2.x
121
+ to turn off a proxy specified in the environment without changing `ENV`.
122
+ - Add actual support for streaming request payloads. Previously rest-client
123
+ would call `.to_s` even on RestClient::Payload::Streamed objects. Instead,
124
+ treat any object that responds to `.read` as a streaming payload and pass it
125
+ through to `.body_stream=` on the Net:HTTP object. This massively reduces the
126
+ memory required for large file uploads.
127
+ - Changes to redirection behavior: (#381, #484)
128
+ - Remove `RestClient::MaxRedirectsReached` in favor of the normal
129
+ `ExceptionWithResponse` subclasses. This makes the response accessible on
130
+ the exception object as `.response`, making it possible for callers to tell
131
+ what has actually happened when the redirect limit is reached.
132
+ - When following HTTP redirection, store a list of each previous response on
133
+ the response object as `.history`. This makes it possible to access the
134
+ original response headers and body before the redirection was followed.
135
+ - Follow redirection consistently, regardless of whether the HTTP method was
136
+ passed as a symbol or string. Under the hood rest-client now normalizes the
137
+ HTTP request method to a lowercase string.
138
+ - Add `:before_execution_proc` option to `RestClient::Request`. This makes it
139
+ possible to add procs like `RestClient.add_before_execution_proc` to a single
140
+ request without global state.
141
+ - Run tests on Travis's beta OS X support.
142
+ - Make `Request#transmit` a private method, along with a few others.
143
+ - Refactor URI parsing to happen earlier, in Request initialization.
144
+ - Improve consistency and functionality of complex URL parameter handling:
145
+ - When adding URL params, handle URLs that already contain params.
146
+ - Add new convention for handling URL params containing deeply nested arrays
147
+ and hashes, unify handling of null/empty values, and use the same code for
148
+ GET and POST params. (#437)
149
+ - Add the RestClient::ParamsArray class, a simple array-like container that
150
+ can be used to pass multiple keys with same name or keys where the ordering
151
+ is significant.
152
+ - Add a few more exception classes for obscure HTTP status codes.
153
+ - Multipart: use a much more robust multipart boundary with greater entropy.
154
+ - Make `RestClient::Payload::Base#inspect` stop pretending to be a String.
155
+ - Add `Request#redacted_uri` and `Request#redacted_url` to display the URI
156
+ with any password redacted.
157
+
158
+ # 2.0.0.rc1
159
+
160
+ Changes in the release candidate that did not persist through the final 2.0.0
161
+ release:
162
+ - RestClient::Exceptions::Timeout was originally going to be a direct subclass
163
+ of RestClient::Exception in the release candidate. This exception tree was
164
+ made a subclass of RestClient::RequestTimeout prior to the final release.
165
+
1
166
  # 1.8.0
2
167
 
3
168
  - Security: implement standards compliant cookie handling by adding a
@@ -51,6 +216,22 @@
51
216
  - Disable timeouts with :timeout => nil rather than :timeout => -1
52
217
  - Drop all Net::HTTP monkey patches
53
218
 
219
+ # 1.6.14
220
+
221
+ - This release is unchanged from 1.6.9. It was published in order to supersede
222
+ the malicious 1.6.10-13 versions, even for users who are still pinning to the
223
+ legacy 1.6.x series. All users are encouraged to upgrade to rest-client 2.x.
224
+
225
+ # 1.6.10, 1.6.11, 1.6.12, 1.6.13 (CVE-2019-15224)
226
+
227
+ - These versions were pushed by a malicious actor and included a backdoor permitting
228
+ remote code execution in Rails environments. (#713)
229
+ - They were live for about five days before being yanked.
230
+
231
+ # 1.6.9
232
+
233
+ - Move rdoc to a development dependency
234
+
54
235
  # 1.6.8
55
236
 
56
237
  - The 1.6.x series will be the last to support Ruby 1.8.7
@@ -5,13 +5,36 @@ module RestClient
5
5
 
6
6
  module AbstractResponse
7
7
 
8
- attr_reader :net_http_res, :args, :request
8
+ attr_reader :net_http_res, :request, :start_time, :end_time, :duration
9
+
10
+ def inspect
11
+ raise NotImplementedError.new('must override in subclass')
12
+ end
13
+
14
+ # Logger from the request, potentially nil.
15
+ def log
16
+ request.log
17
+ end
18
+
19
+ def log_response
20
+ return unless log
21
+
22
+ code = net_http_res.code
23
+ res_name = net_http_res.class.to_s.gsub(/\ANet::HTTP/, '')
24
+ content_type = (net_http_res['Content-type'] || '').gsub(/;.*\z/, '')
25
+
26
+ log << "# => #{code} #{res_name} | #{content_type} #{size} bytes, #{sprintf('%.2f', duration)}s\n"
27
+ end
9
28
 
10
29
  # HTTP status code
11
30
  def code
12
31
  @code ||= @net_http_res.code.to_i
13
32
  end
14
33
 
34
+ def history
35
+ @history ||= request.redirection_history || []
36
+ end
37
+
15
38
  # A hash of the headers, beautified with symbols and underscores.
16
39
  # e.g. "Content-type" will become :content_type.
17
40
  def headers
@@ -23,17 +46,39 @@ module RestClient
23
46
  @raw_headers ||= @net_http_res.to_hash
24
47
  end
25
48
 
26
- def response_set_vars(net_http_res, args, request)
49
+ # @param [Net::HTTPResponse] net_http_res
50
+ # @param [RestClient::Request] request
51
+ # @param [Time] start_time
52
+ def response_set_vars(net_http_res, request, start_time)
27
53
  @net_http_res = net_http_res
28
- @args = args
29
54
  @request = request
55
+ @start_time = start_time
56
+ @end_time = Time.now
57
+
58
+ if @start_time
59
+ @duration = @end_time - @start_time
60
+ else
61
+ @duration = nil
62
+ end
63
+
64
+ # prime redirection history
65
+ history
30
66
  end
31
67
 
32
- # Hash of cookies extracted from response headers
68
+ # Hash of cookies extracted from response headers.
69
+ #
70
+ # NB: This will return only cookies whose domain matches this request, and
71
+ # may not even return all of those cookies if there are duplicate names.
72
+ # Use the full cookie_jar for more nuanced access.
73
+ #
74
+ # @see #cookie_jar
75
+ #
76
+ # @return [Hash]
77
+ #
33
78
  def cookies
34
79
  hash = {}
35
80
 
36
- cookie_jar.cookies.each do |cookie|
81
+ cookie_jar.cookies(@request.uri).each do |cookie|
37
82
  hash[cookie.name] = cookie.value
38
83
  end
39
84
 
@@ -45,91 +90,163 @@ module RestClient
45
90
  # @return [HTTP::CookieJar]
46
91
  #
47
92
  def cookie_jar
48
- return @cookie_jar if @cookie_jar
93
+ return @cookie_jar if defined?(@cookie_jar) && @cookie_jar
49
94
 
50
- jar = HTTP::CookieJar.new
95
+ jar = @request.cookie_jar.dup
51
96
  headers.fetch(:set_cookie, []).each do |cookie|
52
- jar.parse(cookie, @request.url)
97
+ jar.parse(cookie, @request.uri)
53
98
  end
54
99
 
55
100
  @cookie_jar = jar
56
101
  end
57
102
 
58
103
  # Return the default behavior corresponding to the response code:
59
- # 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
60
- def return! request = nil, result = nil, & block
61
- if (200..207).include? code
104
+ #
105
+ # For 20x status codes: return the response itself
106
+ #
107
+ # For 30x status codes:
108
+ # 301, 302, 307: redirect GET / HEAD if there is a Location header
109
+ # 303: redirect, changing method to GET, if there is a Location header
110
+ #
111
+ # For all other responses, raise a response exception
112
+ #
113
+ def return!(&block)
114
+ case code
115
+ when 200..207
62
116
  self
63
- elsif [301, 302, 307].include? code
64
- unless [:get, :head].include? args[:method]
65
- raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
117
+ when 301, 302, 307
118
+ case request.method
119
+ when 'get', 'head'
120
+ check_max_redirects
121
+ follow_redirection(&block)
66
122
  else
67
- follow_redirection(request, result, & block)
123
+ raise exception_with_response
68
124
  end
69
- elsif code == 303
70
- args[:method] = :get
71
- args.delete :payload
72
- follow_redirection(request, result, & block)
73
- elsif Exceptions::EXCEPTIONS_MAP[code]
74
- raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
125
+ when 303
126
+ check_max_redirects
127
+ follow_get_redirection(&block)
75
128
  else
76
- raise RequestFailed.new(self, code)
129
+ raise exception_with_response
77
130
  end
78
131
  end
79
132
 
80
133
  def to_i
81
- code
134
+ warn('warning: calling Response#to_i is not recommended')
135
+ super
82
136
  end
83
137
 
84
138
  def description
85
139
  "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
86
140
  end
87
141
 
88
- # Follow a redirection
89
- def follow_redirection request = nil, result = nil, & block
90
- new_args = @args.dup
142
+ # Follow a redirection response by making a new HTTP request to the
143
+ # redirection target.
144
+ def follow_redirection(&block)
145
+ _follow_redirection(request.args.dup, &block)
146
+ end
91
147
 
92
- url = headers[:location]
93
- if url !~ /^http/
94
- url = URI.parse(request.url).merge(url).to_s
95
- end
96
- new_args[:url] = url
97
- if request
98
- if request.max_redirects == 0
99
- raise MaxRedirectsReached
100
- end
101
- new_args[:password] = request.password
102
- new_args[:user] = request.user
103
- new_args[:headers] = request.headers
104
- new_args[:max_redirects] = request.max_redirects - 1
105
-
106
- # TODO: figure out what to do with original :cookie, :cookies values
107
- new_args[:headers]['Cookie'] = HTTP::Cookie.cookie_value(
108
- cookie_jar.cookies(new_args.fetch(:url)))
109
- end
148
+ # Follow a redirection response, but change the HTTP method to GET and drop
149
+ # the payload from the original request.
150
+ def follow_get_redirection(&block)
151
+ new_args = request.args.dup
152
+ new_args[:method] = :get
153
+ new_args.delete(:payload)
110
154
 
111
- Request.execute(new_args, &block)
155
+ _follow_redirection(new_args, &block)
112
156
  end
113
157
 
158
+ # Convert headers hash into canonical form.
159
+ #
160
+ # Header names will be converted to lowercase symbols with underscores
161
+ # instead of hyphens.
162
+ #
163
+ # Headers specified multiple times will be joined by comma and space,
164
+ # except for Set-Cookie, which will always be an array.
165
+ #
166
+ # Per RFC 2616, if a server sends multiple headers with the same key, they
167
+ # MUST be able to be joined into a single header by a comma. However,
168
+ # Set-Cookie (RFC 6265) cannot because commas are valid within cookie
169
+ # definitions. The newer RFC 7230 notes (3.2.2) that Set-Cookie should be
170
+ # handled as a special case.
171
+ #
172
+ # http://tools.ietf.org/html/rfc2616#section-4.2
173
+ # http://tools.ietf.org/html/rfc7230#section-3.2.2
174
+ # http://tools.ietf.org/html/rfc6265
175
+ #
176
+ # @param headers [Hash]
177
+ # @return [Hash]
178
+ #
114
179
  def self.beautify_headers(headers)
115
180
  headers.inject({}) do |out, (key, value)|
116
- out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first
181
+ key_sym = key.tr('-', '_').downcase.to_sym
182
+
183
+ # Handle Set-Cookie specially since it cannot be joined by comma.
184
+ if key.downcase == 'set-cookie'
185
+ out[key_sym] = value
186
+ else
187
+ out[key_sym] = value.join(', ')
188
+ end
189
+
117
190
  out
118
191
  end
119
192
  end
120
193
 
121
194
  private
122
195
 
123
- # Parse a cookie value and return its content in an Hash
124
- def parse_cookie cookie_content
125
- out = {}
126
- CGI::Cookie::parse(cookie_content).each do |key, cookie|
127
- unless ['expires', 'path'].include? key
128
- out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : ''
129
- end
196
+ # Follow a redirection
197
+ #
198
+ # @param new_args [Hash] Start with this hash of arguments for the
199
+ # redirection request. The hash will be mutated, so be sure to dup any
200
+ # existing hash that should not be modified.
201
+ #
202
+ def _follow_redirection(new_args, &block)
203
+
204
+ # parse location header and merge into existing URL
205
+ url = headers[:location]
206
+
207
+ # cannot follow redirection if there is no location header
208
+ unless url
209
+ raise exception_with_response
130
210
  end
131
- out
211
+
212
+ # handle relative redirects
213
+ unless url.start_with?('http')
214
+ url = URI.parse(request.url).merge(url).to_s
215
+ end
216
+ new_args[:url] = url
217
+
218
+ new_args[:password] = request.password
219
+ new_args[:user] = request.user
220
+ new_args[:headers] = request.headers
221
+ new_args[:max_redirects] = request.max_redirects - 1
222
+
223
+ # pass through our new cookie jar
224
+ new_args[:cookies] = cookie_jar
225
+
226
+ # prepare new request
227
+ new_req = Request.new(new_args)
228
+
229
+ # append self to redirection history
230
+ new_req.redirection_history = history + [self]
231
+
232
+ # execute redirected request
233
+ new_req.execute(&block)
132
234
  end
133
- end
134
235
 
236
+ def check_max_redirects
237
+ if request.max_redirects <= 0
238
+ raise exception_with_response
239
+ end
240
+ end
241
+
242
+ def exception_with_response
243
+ begin
244
+ klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
245
+ rescue KeyError
246
+ raise RequestFailed.new(self, code)
247
+ end
248
+
249
+ raise klass.new(self, code)
250
+ end
251
+ end
135
252
  end