wrest 1.0.2-universal-java-1.6 → 1.1.0-universal-java-1.6

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.
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