wrest 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/wrest.rb CHANGED
@@ -38,15 +38,46 @@ module Wrest
38
38
  end
39
39
 
40
40
  # Switch Wrest to using Net::HTTP.
41
- def self.use_native
41
+ def self.use_native!
42
42
  silence_warnings{ Wrest.const_set('Http', Wrest::Native) }
43
43
  end
44
44
 
45
45
  # Switch Wrest to using libcurl.
46
- def self.use_curl
46
+ def self.use_curl!
47
47
  require "#{Wrest::Root}/wrest/curl"
48
48
  silence_warnings{ Wrest.const_set('Http', Wrest::Curl) }
49
49
  end
50
+
51
+ # Loads the Memcached caching back-end and the Dalli gem
52
+ def self.enable_memcached_caching!
53
+ require "#{Wrest::Root}/wrest/components/cache_store/memcached"
54
+ end
55
+
56
+ # Assign the default cache store to be used. Default is none.
57
+ def self.default_cachestore=(cachestore)
58
+ @default_cachestore = cachestore
59
+ end
60
+
61
+ # Returns the default cache store, if any is set.
62
+ def self.default_cachestore
63
+ @default_cachestore
64
+ end
65
+
66
+ # Configures Wrest to cache all requests. This will use the Memcached backend.
67
+ def self.always_cache_using_memcached!
68
+ self.enable_memcached_caching!
69
+ self.default_cachestore=Wrest::Components::CacheStore::Memcached.new
70
+ end
71
+
72
+ # Configures Wrest to cache all requests. This will use a Ruby Hash.
73
+ # WARNING: This should NEVER be used in a real environment. The Hash will keep on growing since Wrest does not limit the size of a cache store.
74
+ #
75
+ # Use the Memcached caching back-end for production since the Memcached process uses an LRU based cache removal policy
76
+ # that keeps the number of entries stored within bounds.
77
+ def self.always_cache_using_hash!
78
+ Wrest.logger.warn "Using an in-memory Hash as a cache store. This is dangerous if used in a production environment."
79
+ self.default_cachestore=Hash.new
80
+ end
50
81
  end
51
82
 
52
83
  Wrest.logger = ActiveSupport::BufferedLogger.new(STDOUT)
@@ -56,16 +87,19 @@ RUBY_PLATFORM =~ /java/ ? gem('json-jruby', '>= 1.4.2') : gem('json', '>= 1.4.2'
56
87
  ActiveSupport::JSON.backend = "JSONGem"
57
88
 
58
89
  require "#{Wrest::Root}/wrest/core_ext/string"
90
+ require "#{Wrest::Root}/wrest/hash_with_case_insensitive_access"
59
91
 
60
92
  # Load XmlMini Extensions
61
93
  require "#{Wrest::Root}/wrest/xml_mini"
62
94
 
63
95
  # Load Wrest Core
64
96
  require "#{Wrest::Root}/wrest/version"
97
+ require "#{Wrest::Root}/wrest/cache_proxy"
65
98
  require "#{Wrest::Root}/wrest/http_shared"
66
99
  require "#{Wrest::Root}/wrest/http_codes"
67
100
  require "#{Wrest::Root}/wrest/native"
68
101
 
102
+
69
103
  # Load Wrest Wrappers
70
104
  require "#{Wrest::Root}/wrest/uri"
71
105
  require "#{Wrest::Root}/wrest/uri_template"
@@ -0,0 +1,111 @@
1
+ module Wrest
2
+
3
+ class CacheProxy
4
+ class << self
5
+ def new(get, cache_store)
6
+ if cache_store
7
+ DefaultCacheProxy.new(get, cache_store)
8
+ else
9
+ NullCacheProxy.new(get)
10
+ end
11
+ end
12
+ end
13
+
14
+ class NullCacheProxy
15
+ def initialize(get)
16
+ @get = get
17
+ end
18
+ def get
19
+ @get.invoke_without_cache_check
20
+ end
21
+ end
22
+
23
+ class DefaultCacheProxy
24
+ HOP_BY_HOP_HEADERS = ["connection",
25
+ "keep-alive",
26
+ "proxy-authenticate",
27
+ "proxy-authorization",
28
+ "te",
29
+ "trailers",
30
+ "transfer-encoding",
31
+ "upgrade"]
32
+
33
+ def initialize(get, cache_store)
34
+ @get = get
35
+ @cache_store = cache_store
36
+ end
37
+
38
+ def log_cached_response
39
+ Wrest.logger.debug "<*> (GET #{@get.hash}) #{@get.uri.protocol}://#{@get.uri.host}:#{@get.uri.port}#{@get.http_request.path}"
40
+ end
41
+
42
+ def get
43
+ cached_response = @cache_store[@get.hash]
44
+ return get_fresh_response if cached_response.nil?
45
+
46
+ if cached_response.expired?
47
+ if cached_response.can_be_validated?
48
+ get_validated_response_for(cached_response)
49
+ else
50
+ get_fresh_response
51
+ end
52
+ else
53
+ log_cached_response
54
+ cached_response
55
+ end
56
+ end
57
+
58
+ def update_cache_headers_for(cached_response, new_response)
59
+ # RFC 2616 13.5.3 (Combining Headers)
60
+ cached_response.headers.merge!(new_response.headers.select {|key, value| not (HOP_BY_HOP_HEADERS.include? key.downcase)})
61
+ end
62
+
63
+ def cache(response)
64
+ @cache_store[@get.hash] = response.clone if response && response.cacheable?
65
+ end
66
+
67
+ #:nodoc:
68
+ def get_fresh_response
69
+ @cache_store.delete @get.hash
70
+
71
+ response = @get.invoke_without_cache_check
72
+
73
+ cache(response)
74
+
75
+ response
76
+ end
77
+
78
+ #:nodoc:
79
+ def get_validated_response_for(cached_response)
80
+ new_response = send_validation_request_for(cached_response)
81
+ if new_response.code == "304"
82
+ update_cache_headers_for(cached_response, new_response)
83
+ log_cached_response
84
+ cached_response
85
+ else
86
+ cache(new_response)
87
+ new_response
88
+ end
89
+ end
90
+
91
+ #:nodoc:
92
+ # Send a cache-validation request to the server. This would be the actual Get request with extra cache-validation headers.
93
+ # If a 304 (Not Modified) is received, Wrest would use the cached_response itself. Otherwise the new response is cached and used.
94
+ def send_validation_request_for(cached_response)
95
+ last_modified = cached_response.last_modified
96
+ etag = cached_response.headers["etag"]
97
+
98
+ cache_validation_headers = {}
99
+ cache_validation_headers["if-modified-since"] = last_modified unless last_modified.nil?
100
+ cache_validation_headers["if-none-match"] = etag unless etag.nil?
101
+
102
+ new_headers =@get.headers.clone.merge cache_validation_headers
103
+ new_options =@get.options.clone.tap { |opts| opts.delete :cache_store } # do not run this through the caching mechanism.
104
+
105
+ new_request = Wrest::Native::Get.new(@get.uri, @get.parameters, new_headers, new_options)
106
+
107
+ new_request.invoke
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,34 @@
1
+ begin
2
+ gem 'dalli', '~> 1.0.1'
3
+ rescue Gem::LoadError => e
4
+ Wrest.logger.debug "Dalli ~> 1.0.1 not found. Dalli is necessary to use the memcached caching back-end. To install dalli run `(sudo) gem install dalli`."
5
+ raise e
6
+ end
7
+
8
+ require 'dalli'
9
+
10
+ module Wrest::Components::CacheStore
11
+ class Memcached
12
+
13
+ def initialize(server_urls=nil, options={})
14
+ @memcached = Dalli::Client.new(server_urls, options)
15
+ end
16
+
17
+ def [](key)
18
+ @memcached.get(key)
19
+ end
20
+
21
+ def []=(key, value)
22
+ @memcached.set(key, value)
23
+ end
24
+
25
+ # should be compatible with Hash - return value of the deleted element.
26
+ def delete(key)
27
+ value = self[key]
28
+
29
+ @memcached.delete key
30
+
31
+ return value
32
+ end
33
+ end
34
+ end
data/lib/wrest/curl.rb CHANGED
@@ -8,9 +8,9 @@
8
8
  # See the License for the specific language governing permissions and limitations under the License.
9
9
 
10
10
  begin
11
- gem 'patron', '~> 0.4.9'
11
+ gem 'patron', '~> 0.4.11'
12
12
  rescue Gem::LoadError => e
13
- Wrest.logger.debug "Patron ~> 0.4.9 not found. Patron is necessary to use libcurl. To install Patron run `sudo gem install patron` (patron is not available on JRuby, but you shouldn't need it anyway)."
13
+ Wrest.logger.debug "Patron ~> 0.4.11 not found. Patron is necessary to use libcurl. To install Patron run `sudo gem install patron` (patron is not available on JRuby, but you shouldn't need it anyway)."
14
14
  raise e
15
15
  end
16
16
  require 'patron'
@@ -68,8 +68,8 @@ module Wrest
68
68
  # Data about the request is and logged to Wrest.logger
69
69
  # The log entry contains the following information:
70
70
  #
71
- # --> indicates a request
72
- # <-- indicates a response
71
+ # <- indicates a request
72
+ # -> indicates a response
73
73
  #
74
74
  # The type of request is mentioned in caps, followed by a hash
75
75
  # uniquely uniquely identifying a particular request/response pair.
@@ -0,0 +1,52 @@
1
+ module Wrest
2
+
3
+ # A hash with case-insensitive key access.
4
+ #
5
+ # hash = Wrest::HashWithCaseInsensitiveAccess.new 'Abcd' => 1, 'xyz' => 2
6
+ #
7
+ # hash['abcd'] #=> 1
8
+ # hash['aBCd'] #=> 1
9
+ #
10
+ class HashWithCaseInsensitiveAccess < ::Hash #:nodoc:
11
+
12
+ def initialize(hash={})
13
+ super()
14
+ hash.each do |key, value|
15
+ self[convert_key(key)] = value
16
+ end
17
+ end
18
+ def [](key)
19
+ super(convert_key(key))
20
+ end
21
+
22
+ def []=(key, value)
23
+ super(convert_key(key), value)
24
+ end
25
+
26
+ def delete(key)
27
+ super(convert_key(key))
28
+ end
29
+
30
+ def values_at(*indices)
31
+ indices.collect { |key| self[convert_key(key)] }
32
+ end
33
+
34
+ def merge(other)
35
+ dup.merge!(other)
36
+ end
37
+
38
+ def merge!(other)
39
+ other.each do |key, value|
40
+ self[convert_key(key)] = value
41
+ end
42
+ self
43
+ end
44
+
45
+ protected
46
+
47
+ def convert_key(key)
48
+ key.is_a?(String) ? key.downcase : key
49
+ end
50
+
51
+ end
52
+ end
@@ -8,18 +8,48 @@
8
8
 
9
9
  module Wrest::Native
10
10
  class Get < Request
11
+
12
+ attr_reader :cache_proxy
13
+
11
14
  def initialize(wrest_uri, parameters = {}, headers = {}, options = {})
12
15
  follow_redirects = options[:follow_redirects]
13
16
  options[:follow_redirects] = (follow_redirects == nil ? true : follow_redirects)
14
- options[:cache_store] ||= {}
17
+ @cache_proxy = Wrest::CacheProxy::new(self, options[:cache_store] || Wrest.default_cachestore)
15
18
  super(
16
- wrest_uri,
17
- Net::HTTP::Get,
19
+ wrest_uri,
20
+ Net::HTTP::Get,
18
21
  parameters,
19
22
  nil,
20
23
  headers,
21
24
  options
22
25
  )
23
26
  end
27
+
28
+ # Checks equality between two Wrest::Native::Get objects.
29
+ # Comparing two Wrest::Native::Get objects with identical values for the following properties would return True.
30
+ # uri, parameters, username, password and ssh verify_mode.
31
+ def ==(other)
32
+ return true if self.equal?(other)
33
+ return false unless other.class == self.class
34
+ return true if self.uri == other.uri and
35
+ self.parameters == other.parameters and
36
+ self.username == other.username and
37
+ self.password == other.password and
38
+ self.verify_mode == other.verify_mode
39
+ false
40
+ end
41
+
42
+ # Returns a hash value for this Wrest::Native::Get object.
43
+ # Objects that returns true when compared using the == operator would return the same hash value also.
44
+ def hash
45
+ self.uri.hash + self.parameters.hash + self.username.hash + self.password.hash + self.verify_mode.hash + 20110106
46
+ end
47
+
48
+ #:nodoc:
49
+ def invoke_with_cache_check
50
+ cache_proxy.get
51
+ end
52
+
53
+ alias_method_chain :invoke, :cache_check
24
54
  end
25
55
  end
@@ -30,7 +30,7 @@ module Wrest #:nodoc:
30
30
 
31
31
  raise Wrest::Exceptions::AutoRedirectLimitExceeded if (redirect_request_options[:follow_redirects_count] += 1) > redirect_request_options[:follow_redirects_limit]
32
32
 
33
- Wrest.logger.debug "--| Redirecting to #{target}"
33
+ Wrest.logger.debug "-| Redirecting to #{target}"
34
34
  Wrest::Uri.new(target, redirect_request_options).get
35
35
  end
36
36
  end
@@ -13,7 +13,7 @@ module Wrest::Native
13
13
  # or Wrest::Native::Get etc. instead.
14
14
  class Request
15
15
  attr_reader :http_request, :uri, :body, :headers, :username, :password, :follow_redirects,
16
- :follow_redirects_limit, :follow_redirects_count, :timeout, :connection, :parameters, :cache_store
16
+ :follow_redirects_limit, :follow_redirects_count, :timeout, :connection, :parameters, :cache_store, :verify_mode, :options
17
17
  # Valid tuples for the options are:
18
18
  # :username => String, defaults to nil
19
19
  # :password => String, defaults to nil
@@ -32,12 +32,12 @@ module Wrest::Native
32
32
  # :connection => The HTTP Connection object to use. This is how a keep-alive connection can be
33
33
  # used for multiple requests.
34
34
  # :verify_mode => The verification mode to be used for Net::HTTP https connections. Defaults to OpenSSL::SSL::VERIFY_PEER
35
- # :cache_store => The object which should be used as cache store for cacheable responses (caching is not supported in this version)
35
+ # :cache_store => The object which should be used as cache store for cacheable responses. If not supplied, caching will be disabled.
36
36
  # :detailed_http_logging => nil/$stdout/$stderr or File/Logger/IO object. Defaults to nil (recommended).
37
37
  # :callback => A Hash whose keys are the response codes (or Range of response codes),
38
38
  # and the values are the callback functions to be executed.
39
39
  # eg: { <response code> => lambda { |response| some_operation } }
40
- #
40
+ #
41
41
  # *WARNING* : detailed_http_logging causes a serious security hole. Never use it in production code.
42
42
  #
43
43
  def initialize(wrest_uri, http_request_klass, parameters = {}, body = nil, headers = {}, options = {})
@@ -66,8 +66,8 @@ module Wrest::Native
66
66
  # Data about the request is and logged to Wrest.logger
67
67
  # The log entry contains the following information:
68
68
  #
69
- # --> indicates a request
70
- # <-- indicates a response
69
+ # <- indicates a request
70
+ # -> indicates a response
71
71
  #
72
72
  # The type of request is mentioned in caps, followed by a hash
73
73
  # uniquely identifying a particular request/response pair.
@@ -85,7 +85,7 @@ module Wrest::Native
85
85
  @connection.set_debug_output @detailed_http_logging
86
86
  http_request.basic_auth username, password unless username.nil? || password.nil?
87
87
 
88
- prefix = "#{http_request.method} #{http_request.hash} #{@connection.hash}"
88
+ prefix = "#{http_request.method} #{self.hash} #{@connection.hash}"
89
89
 
90
90
  Wrest.logger.debug "<- (#{prefix}) #{@uri.protocol}://#{@uri.host}:#{@uri.port}#{@http_request.path}"
91
91
  time = Benchmark.realtime { response = Wrest::Native::Response.new( do_request ) }
@@ -27,24 +27,50 @@ module Wrest #:nodoc:
27
27
  include HttpCodes
28
28
 
29
29
  extend Forwardable
30
- def_delegators :@http_response, :code, :message, :body, :Http_version,
31
- :[], :content_length, :content_type, :each_header, :each_name, :each_value, :fetch,
32
- :get_fields, :key?, :type_params
30
+ def_delegators :@http_response, :code, :message, :body, :http_version,
31
+ :content_length, :content_type
33
32
 
34
- # We're overriding :new to act as a factory so
33
+ def_delegators :headers, :[]
34
+
35
+ # TODO : Are these needed in the Response namespace itself? Can be accessed from the headers method.
36
+ def_delegators :@http_response, :each_header, :each_name, :each_value, :fetch,
37
+ :get_fields, :key?, :type_params
38
+
39
+ # We're overriding :new to act as a factory so
35
40
  # we can build the appropriate Response instance based
36
- # on th response code.
41
+ # on the response code.
37
42
  def self.new(http_response)
38
43
  code = http_response.code.to_i
39
44
  instance = ((300..303).include?(code) || (305..399).include?(code) ? Wrest::Native::Redirection : self).allocate
40
45
  instance.send :initialize, http_response
41
46
  instance
42
47
  end
43
-
48
+
44
49
  def initialize(http_response)
45
50
  @http_response = http_response
46
51
  end
47
52
 
53
+ def initialize_copy(source)
54
+ @headers = source.headers.clone
55
+ end
56
+
57
+ # Checks equality between two Wrest::Native::Response objects.
58
+ def ==(other)
59
+ return true if self.equal?(other)
60
+ return false unless other.class == self.class
61
+ return true if self.code == other.code and
62
+ self.headers == other.headers and
63
+ self.http_version == other.http_version and
64
+ self.message == other.message and
65
+ self.body == other.body
66
+ false
67
+ end
68
+
69
+ # Return the hash of a Wrest::Native::Response object.
70
+ def hash
71
+ self.code.hash + self.message.hash + self.headers.hash + self.http_version.hash + self.body.hash
72
+ end
73
+
48
74
 
49
75
  def deserialise(options = {})
50
76
  deserialise_using(Wrest::Components::Translators.lookup(@http_response.content_type),options)
@@ -54,13 +80,22 @@ module Wrest #:nodoc:
54
80
  translator.deserialise(@http_response,options)
55
81
  end
56
82
 
83
+ # Gives a hash of the response headers. The keys of the hash are case-insensitive.
57
84
  def headers
58
- @http_response.to_hash
85
+ return @headers if @headers
86
+
87
+ nethttp_headers_with_string_values=@http_response.to_hash.inject({}) {|new_headers, (old_key, old_value)|
88
+ new_headers[old_key] = old_value.is_a?(Array) ? old_value.join(",") : old_value
89
+ new_headers
90
+ }
91
+
92
+ @headers=Wrest::HashWithCaseInsensitiveAccess.new(nethttp_headers_with_string_values)
93
+
59
94
  end
60
-
95
+
61
96
  # A null object implementation - invoking this method on
62
97
  # a response simply returns the same response unless
63
- # the response is Redirection (code 3xx), in which case a
98
+ # the response is Redirection (code 3xx), in which case a
64
99
  # get is invoked on the url stored in the response headers
65
100
  # under the key 'location' and the new Response is returned.
66
101
  def follow(redirect_request_options = {})
@@ -71,12 +106,29 @@ module Wrest #:nodoc:
71
106
  self[Native::StandardHeaders::Connection].downcase == Native::StandardTokens::Close.downcase
72
107
  end
73
108
 
109
+ # Returns whether this response is cacheable.
74
110
  def cacheable?
75
- code_cacheable? && no_cache_flag_not_set? && no_store_flag_not_set? && expires_header_not_in_past?
111
+ code_cacheable? && no_cache_flag_not_set? && no_store_flag_not_set? &&
112
+ (not max_age.nil? or (expires_not_in_our_past? && expires_not_in_its_past?)) && pragma_nocache_not_set? &&
113
+ vary_tag_not_set?
76
114
  end
77
115
 
116
+ #:nodoc:
78
117
  def code_cacheable?
79
- !code.nil? && !/2\d{2}/.match(code).nil?
118
+ !code.nil? && ([200, 203, 300, 301, 302, 304, 307].include?(code.to_i))
119
+ end
120
+
121
+ #:nodoc:
122
+ def max_age
123
+ return @max_age if @max_age
124
+
125
+ max_age =cache_control_headers.grep(/max-age/)
126
+
127
+ @max_age = unless max_age.empty?
128
+ max_age.first.split('=').last.to_i
129
+ else
130
+ nil
131
+ end
80
132
  end
81
133
 
82
134
  def no_cache_flag_not_set?
@@ -87,40 +139,122 @@ module Wrest #:nodoc:
87
139
  not cache_control_headers.include?('no-store')
88
140
  end
89
141
 
90
- def expires_header_not_in_past?
91
- expires_header = cache_control_headers.find{ |h| h.include? 'Expires' }
92
- if expires_header.nil?
93
- true
142
+ def pragma_nocache_not_set?
143
+ headers['pragma'].nil? || (not headers['pragma'].include? 'no-cache')
144
+ end
145
+
146
+ #:nodoc:
147
+ def vary_tag_not_set?
148
+ headers['vary'].nil?
149
+ end
150
+
151
+ # Returns the Date from the response headers.
152
+ def response_date
153
+ return @response_date if @response_date
154
+ @response_date = parse_datefield(headers, "date")
155
+ end
156
+
157
+
158
+ # Returns the Expires date from the response headers.
159
+ def expires
160
+ return @expires if @expires
161
+ @expires = parse_datefield(headers, "expires")
162
+ end
163
+
164
+ # Returns whether the Expires header of this response is earlier than current time.
165
+ def expires_not_in_our_past?
166
+ if expires.nil?
167
+ false
94
168
  else
95
- expires_on = DateTime.parse(expires_header.split("=")[1])
96
- expires_on > DateTime.now
169
+ expires.to_i > Time.now.to_i
97
170
  end
98
171
  end
99
172
 
173
+ # Is the Expires of this response earlier than its Date header.
174
+ def expires_not_in_its_past?
175
+ # Invalid header value for Date or Expires means the response is not cacheable
176
+ if expires.nil? || response_date.nil?
177
+ false
178
+ else
179
+ expires > response_date
180
+ end
181
+ end
182
+
183
+ # Age of the response calculated according to RFC 2616 13.2.3
184
+ def current_age
185
+ current_time = Time.now.to_i
186
+
187
+ # RFC 2616 13.2.3 Age Calculations. TODO: include response_delay in the calculation as defined in RFC. For this, include original Request with Response.
188
+ date_value = DateTime.parse(headers['date']).to_i rescue current_time
189
+ age_value = headers['age'].to_i || 0
190
+
191
+ apparent_age = current_time - date_value
192
+
193
+ [apparent_age, age_value].max
194
+ end
195
+
196
+ # The values in Cache-Control header as an array.
100
197
  def cache_control_headers
101
- @cache_control_headers unless @cache_control_headers.nil?
102
- if headers['Cache-Control'].nil? then
103
- @cache_control_headers = []
104
- else
105
- cache_headers = headers['Cache-Control'].split(",")
106
- @cache_control_headers = correct_expires_headers(cache_headers)
107
- @cache_control_headers.collect
198
+ @cache_control_headers ||= recalculate_cache_control_headers
199
+ end
200
+
201
+ #:nodoc:
202
+ def recalculate_cache_control_headers
203
+ headers['cache-control'].split(",").collect {|cc| cc.strip } rescue []
204
+ end
205
+
206
+ # How long (in seconds) is this response expected to be fresh
207
+ def freshness_lifetime
208
+ @freshness_lifetime ||= recalculate_freshness_lifetime
209
+ end
210
+
211
+ #:nodoc:
212
+ def recalculate_freshness_lifetime
213
+ return max_age if max_age
214
+
215
+ response_date = DateTime.parse(headers['date']).to_i
216
+ expires_date = DateTime.parse(headers['expires']).to_i
217
+
218
+ return (expires_date - response_date)
219
+ end
220
+
221
+ # Has this response expired? The expiry is calculated from the Max-Age/Expires header.
222
+ def expired?
223
+ freshness=freshness_lifetime
224
+ if freshness <= 0
225
+ return true
108
226
  end
227
+
228
+ freshness <= current_age
109
229
  end
110
230
 
111
- :private
231
+ def last_modified
232
+ headers['last-modified']
233
+ end
234
+
235
+ # Can this response be validated by sending a validation request to the server. The response need to have either
236
+ # Last-Modified or ETag header (or both) for it to be validatable.
237
+ def can_be_validated?
238
+ not (last_modified.nil? and headers['etag'].nil?)
239
+ end
112
240
 
113
- def correct_expires_headers(cache_headers)
114
- # The expires header "Expires = Sun, 06 Nov 1994 08:49:37 GMT" would have split into two ['Expires = Sun',' 06 Nov 1994 08:49:37 GMT']
115
- expires_index = cache_headers.find_index(){ |a| a.include? 'Expires' }
116
- if expires_index
117
- expires_part_1 = cache_headers.delete(cache_headers[expires_index])
118
- # earlier delete shifted the second part on same index
119
- expires_part_2 = cache_headers.delete(cache_headers[expires_index])
120
- cache_headers.push(expires_part_1+','+expires_part_2)
241
+
242
+ #:nodoc:
243
+ # helper function. Used to parse date fields.
244
+ # this function is used and tested by the expires and response_date methods
245
+ def parse_datefield(hash, key)
246
+ if hash[key]
247
+ # Can't trust external input. Do not crash even if invalid dates are passed.
248
+ begin
249
+ DateTime.parse(hash[key].to_s)
250
+ rescue ArgumentError
251
+ nil
252
+ end
253
+ else
254
+ nil
121
255
  end
122
- cache_headers
123
256
  end
257
+
124
258
  end
125
259
  end
126
260
  end