rest-client 1.7.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  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 +199 -0
  14. data/lib/restclient/abstract_response.rb +196 -50
  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 +20 -1
  19. data/lib/restclient/raw_response.rb +21 -6
  20. data/lib/restclient/request.rb +572 -284
  21. data/lib/restclient/resource.rb +19 -9
  22. data/lib/restclient/response.rb +75 -9
  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 +13 -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 +29 -6
  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 -35
  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 -5
  44. data/spec/unit/request2_spec.rb +34 -12
  45. data/spec/unit/request_spec.rb +751 -418
  46. data/spec/unit/resource_spec.rb +31 -27
  47. data/spec/unit/response_spec.rb +144 -58
  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 +121 -70
  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,186 @@
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
+
166
+ # 1.8.0
167
+
168
+ - Security: implement standards compliant cookie handling by adding a
169
+ dependency on http-cookie. This breaks compatibility, but was necessary to
170
+ address a session fixation / cookie disclosure vulnerability.
171
+ (#369 / CVE-2015-1820)
172
+
173
+ Previously, any Set-Cookie headers found in an HTTP 30x response would be
174
+ sent to the redirection target, regardless of domain. Responses now expose a
175
+ cookie jar and respect standards compliant domain / path flags in Set-Cookie
176
+ headers.
177
+
178
+ # 1.7.3
179
+
180
+ - Security: redact password in URI from logs (#349 / OSVDB-117461)
181
+ - Drop monkey patch on MIME::Types (added `type_for_extension` method, use
182
+ the public interface instead.
183
+
1
184
  # 1.7.2
2
185
 
3
186
  - Ignore duplicate certificates in CA store on Windows
@@ -33,6 +216,22 @@
33
216
  - Disable timeouts with :timeout => nil rather than :timeout => -1
34
217
  - Drop all Net::HTTP monkey patches
35
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
+
36
235
  # 1.6.8
37
236
 
38
237
  - The 1.6.x series will be the last to support Ruby 1.8.7
@@ -1,16 +1,40 @@
1
1
  require 'cgi'
2
+ require 'http-cookie'
2
3
 
3
4
  module RestClient
4
5
 
5
6
  module AbstractResponse
6
7
 
7
- attr_reader :net_http_res, :args
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
8
28
 
9
29
  # HTTP status code
10
30
  def code
11
31
  @code ||= @net_http_res.code.to_i
12
32
  end
13
33
 
34
+ def history
35
+ @history ||= request.redirection_history || []
36
+ end
37
+
14
38
  # A hash of the headers, beautified with symbols and underscores.
15
39
  # e.g. "Content-type" will become :content_type.
16
40
  def headers
@@ -22,85 +46,207 @@ module RestClient
22
46
  @raw_headers ||= @net_http_res.to_hash
23
47
  end
24
48
 
25
- # Hash of cookies extracted from response headers
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)
53
+ @net_http_res = net_http_res
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
66
+ end
67
+
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
+ #
26
78
  def cookies
27
- @cookies ||= (self.headers[:set_cookie] || {}).inject({}) do |out, cookie_content|
28
- out.merge parse_cookie(cookie_content)
79
+ hash = {}
80
+
81
+ cookie_jar.cookies(@request.uri).each do |cookie|
82
+ hash[cookie.name] = cookie.value
83
+ end
84
+
85
+ hash
86
+ end
87
+
88
+ # Cookie jar extracted from response headers.
89
+ #
90
+ # @return [HTTP::CookieJar]
91
+ #
92
+ def cookie_jar
93
+ return @cookie_jar if defined?(@cookie_jar) && @cookie_jar
94
+
95
+ jar = @request.cookie_jar.dup
96
+ headers.fetch(:set_cookie, []).each do |cookie|
97
+ jar.parse(cookie, @request.uri)
29
98
  end
99
+
100
+ @cookie_jar = jar
30
101
  end
31
102
 
32
103
  # 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
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
36
116
  self
37
- elsif [301, 302, 307].include? code
38
- unless [:get, :head].include? args[:method]
39
- 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)
40
122
  else
41
- follow_redirection(request, result, & block)
123
+ raise exception_with_response
42
124
  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)
125
+ when 303
126
+ check_max_redirects
127
+ follow_get_redirection(&block)
49
128
  else
50
- raise RequestFailed.new(self, code)
129
+ raise exception_with_response
51
130
  end
52
131
  end
53
132
 
54
133
  def to_i
55
- code
134
+ warn('warning: calling Response#to_i is not recommended')
135
+ super
56
136
  end
57
137
 
58
138
  def description
59
139
  "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
60
140
  end
61
141
 
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
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
147
+
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)
154
+
155
+ _follow_redirection(new_args, &block)
83
156
  end
84
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
+ #
85
179
  def self.beautify_headers(headers)
86
180
  headers.inject({}) do |out, (key, value)|
87
- 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
+
88
190
  out
89
191
  end
90
192
  end
91
193
 
92
194
  private
93
195
 
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
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
210
+ end
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)
234
+ end
235
+
236
+ def check_max_redirects
237
+ if request.max_redirects <= 0
238
+ raise exception_with_response
101
239
  end
102
- out
103
240
  end
104
- end
105
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
106
252
  end