rack 1.4.7 → 1.5.0.beta.1

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

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (60) hide show
  1. data/README.rdoc +2 -30
  2. data/Rakefile +1 -0
  3. data/SPEC +68 -4
  4. data/example/protectedlobster.rb +1 -1
  5. data/lib/rack.rb +2 -14
  6. data/lib/rack/auth/abstract/request.rb +1 -5
  7. data/lib/rack/builder.rb +8 -4
  8. data/lib/rack/cascade.rb +2 -2
  9. data/lib/rack/config.rb +5 -0
  10. data/lib/rack/deflater.rb +2 -1
  11. data/lib/rack/file.rb +25 -28
  12. data/lib/rack/handler.rb +18 -5
  13. data/lib/rack/handler/mongrel.rb +1 -1
  14. data/lib/rack/handler/scgi.rb +1 -1
  15. data/lib/rack/handler/thin.rb +6 -3
  16. data/lib/rack/handler/webrick.rb +1 -0
  17. data/lib/rack/head.rb +2 -0
  18. data/lib/rack/lint.rb +132 -7
  19. data/lib/rack/lobster.rb +3 -3
  20. data/lib/rack/lock.rb +2 -0
  21. data/lib/rack/methodoverride.rb +0 -2
  22. data/lib/rack/mime.rb +29 -0
  23. data/lib/rack/multipart/parser.rb +0 -9
  24. data/lib/rack/request.rb +66 -25
  25. data/lib/rack/response.rb +1 -2
  26. data/lib/rack/sendfile.rb +18 -4
  27. data/lib/rack/server.rb +20 -12
  28. data/lib/rack/session/abstract/id.rb +60 -59
  29. data/lib/rack/session/cookie.rb +11 -16
  30. data/lib/rack/utils.rb +97 -85
  31. data/rack.gemspec +1 -6
  32. data/test/spec_builder.rb +7 -0
  33. data/test/spec_cgi.rb +1 -1
  34. data/test/spec_chunked.rb +3 -5
  35. data/test/spec_content_length.rb +3 -6
  36. data/test/spec_deflater.rb +26 -9
  37. data/test/spec_fastcgi.rb +1 -1
  38. data/test/spec_file.rb +24 -11
  39. data/test/spec_head.rb +3 -8
  40. data/test/spec_lint.rb +6 -6
  41. data/test/spec_lock.rb +4 -7
  42. data/test/spec_methodoverride.rb +4 -1
  43. data/test/spec_mime.rb +51 -0
  44. data/test/spec_mongrel.rb +1 -1
  45. data/test/spec_multipart.rb +15 -49
  46. data/test/spec_nulllogger.rb +3 -6
  47. data/test/spec_request.rb +112 -18
  48. data/test/spec_response.rb +8 -8
  49. data/test/spec_sendfile.rb +52 -13
  50. data/test/spec_server.rb +6 -0
  51. data/test/spec_session_abstract_id.rb +11 -1
  52. data/test/spec_session_cookie.rb +140 -153
  53. data/test/spec_thin.rb +6 -1
  54. data/test/spec_utils.rb +23 -17
  55. data/test/spec_webrick.rb +1 -1
  56. metadata +37 -83
  57. checksums.yaml +0 -7
  58. data/test/cgi/lighttpd.errors +0 -1
  59. data/test/multipart/three_files_three_fields +0 -31
  60. data/test/spec_auth.rb +0 -57
@@ -21,8 +21,7 @@ module Rack
21
21
 
22
22
  def initialize(body=[], status=200, header={})
23
23
  @status = status.to_i
24
- @header = Utils::HeaderHash.new("Content-Type" => "text/html").
25
- merge(header)
24
+ @header = Utils::HeaderHash.new.merge(header)
26
25
 
27
26
  @chunked = "chunked" == @header['Transfer-Encoding']
28
27
  @writer = lambda { |x| @body << x }
@@ -89,13 +89,23 @@ module Rack
89
89
  # RequestHeader Set X-Sendfile-Type X-Sendfile
90
90
  # ProxyPassReverse / http://localhost:8001/
91
91
  # XSendFile on
92
+ #
93
+ # === Mapping parameter
94
+ #
95
+ # The third parameter allows for an overriding extension of the
96
+ # X-Accel-Mapping header. Mappings should be provided in tuples of internal to
97
+ # external. The internal values may contain regular expression syntax, they
98
+ # will be matched with case indifference.
92
99
 
93
100
  class Sendfile
94
101
  F = ::File
95
102
 
96
- def initialize(app, variation=nil)
103
+ def initialize(app, variation=nil, mappings=[])
97
104
  @app = app
98
105
  @variation = variation
106
+ @mappings = mappings.map do |internal, external|
107
+ [/^#{internal}/i, external]
108
+ end
99
109
  end
100
110
 
101
111
  def call(env)
@@ -107,6 +117,7 @@ module Rack
107
117
  if url = map_accel_path(env, path)
108
118
  headers['Content-Length'] = '0'
109
119
  headers[type] = url
120
+ body.close if body.respond_to?(:close)
110
121
  body = []
111
122
  else
112
123
  env['rack.errors'].puts "X-Accel-Mapping header missing"
@@ -115,6 +126,7 @@ module Rack
115
126
  path = F.expand_path(body.to_path)
116
127
  headers['Content-Length'] = '0'
117
128
  headers[type] = path
129
+ body.close if body.respond_to?(:close)
118
130
  body = []
119
131
  when '', nil
120
132
  else
@@ -131,10 +143,12 @@ module Rack
131
143
  env['HTTP_X_SENDFILE_TYPE']
132
144
  end
133
145
 
134
- def map_accel_path(env, file)
135
- if mapping = env['HTTP_X_ACCEL_MAPPING']
146
+ def map_accel_path(env, path)
147
+ if mapping = @mappings.find { |internal,_| internal =~ path }
148
+ path.sub(*mapping)
149
+ elsif mapping = env['HTTP_X_ACCEL_MAPPING']
136
150
  internal, external = mapping.split('=', 2).map{ |p| p.strip }
137
- file.sub(/^#{internal}/i, external)
151
+ path.sub(/^#{internal}/i, external)
138
152
  end
139
153
  end
140
154
  end
@@ -17,6 +17,10 @@ module Rack
17
17
  lineno += 1
18
18
  }
19
19
 
20
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
21
+ options[:builder] = line
22
+ }
23
+
20
24
  opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
21
25
  options[:debug] = true
22
26
  }
@@ -36,7 +40,7 @@ module Rack
36
40
 
37
41
  opts.separator ""
38
42
  opts.separator "Rack options:"
39
- opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
43
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
40
44
  options[:server] = s
41
45
  }
42
46
 
@@ -192,15 +196,7 @@ module Rack
192
196
  end
193
197
 
194
198
  def app
195
- @app ||= begin
196
- if !::File.exist? options[:config]
197
- abort "configuration #{options[:config]} not found"
198
- end
199
-
200
- app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
201
- self.options.merge! options
202
- app
203
- end
199
+ @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
204
200
  end
205
201
 
206
202
  def self.logging_middleware
@@ -273,6 +269,20 @@ module Rack
273
269
  end
274
270
 
275
271
  private
272
+ def build_app_and_options_from_config
273
+ if !::File.exist? options[:config]
274
+ abort "configuration #{options[:config]} not found"
275
+ end
276
+
277
+ app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
278
+ self.options.merge! options
279
+ app
280
+ end
281
+
282
+ def build_app_from_string
283
+ Rack::Builder.new_from_string(self.options[:builder])
284
+ end
285
+
276
286
  def parse_options(args)
277
287
  options = default_options
278
288
 
@@ -337,8 +347,6 @@ module Rack
337
347
  return :exited unless ::File.exist?(options[:pid])
338
348
 
339
349
  pid = ::File.read(options[:pid]).to_i
340
- return :dead if pid == 0
341
-
342
350
  Process.kill(0, pid)
343
351
  :running
344
352
  rescue Errno::ESRCH
@@ -18,85 +18,85 @@ module Rack
18
18
  ENV_SESSION_KEY = 'rack.session'.freeze
19
19
  ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
20
20
 
21
- # Thin wrapper around Hash that allows us to lazily load session id into session_options.
21
+ # SessionHash is responsible to lazily load the session from store.
22
+
23
+ class SessionHash
24
+ include Enumerable
25
+ attr_writer :id
22
26
 
23
- class OptionsHash < Hash #:nodoc:
24
- def initialize(by, env, default_options)
25
- @by = by
27
+ def initialize(store, env)
28
+ @store = store
26
29
  @env = env
27
- @session_id_loaded = false
28
- merge!(default_options)
30
+ @loaded = false
29
31
  end
30
32
 
31
- def [](key)
32
- load_session_id! if key == :id && session_id_not_loaded?
33
- super
33
+ def id
34
+ return @id if @loaded or instance_variable_defined?(:@id)
35
+ @id = @store.send(:extract_session_id, @env)
34
36
  end
35
37
 
36
- private
37
-
38
- def session_id_not_loaded?
39
- !(@session_id_loaded || key?(:id))
38
+ def options
39
+ @env[ENV_SESSION_OPTIONS_KEY]
40
40
  end
41
41
 
42
- def load_session_id!
43
- self[:id] = @by.send(:extract_session_id, @env)
44
- @session_id_loaded = true
45
- end
46
- end
47
-
48
- # SessionHash is responsible to lazily load the session from store.
49
-
50
- class SessionHash < Hash
51
- def initialize(by, env)
52
- super()
53
- @by = by
54
- @env = env
55
- @loaded = false
42
+ def each(&block)
43
+ load_for_read!
44
+ @data.each(&block)
56
45
  end
57
46
 
58
47
  def [](key)
59
48
  load_for_read!
60
- super(key.to_s)
49
+ @data[key.to_s]
61
50
  end
51
+ alias :fetch :[]
62
52
 
63
53
  def has_key?(key)
64
54
  load_for_read!
65
- super(key.to_s)
55
+ @data.has_key?(key.to_s)
66
56
  end
67
57
  alias :key? :has_key?
68
58
  alias :include? :has_key?
69
59
 
70
60
  def []=(key, value)
71
61
  load_for_write!
72
- super(key.to_s, value)
62
+ @data[key.to_s] = value
73
63
  end
64
+ alias :store :[]=
74
65
 
75
66
  def clear
76
67
  load_for_write!
77
- super
68
+ @data.clear
69
+ end
70
+
71
+ def destroy
72
+ clear
73
+ @id = @store.send(:destroy_session, @env, id, options)
78
74
  end
79
75
 
80
76
  def to_hash
81
77
  load_for_read!
82
- h = {}.replace(self)
83
- h.delete_if { |k,v| v.nil? }
84
- h
78
+ @data.dup
85
79
  end
86
80
 
87
81
  def update(hash)
88
82
  load_for_write!
89
- super(stringify_keys(hash))
83
+ @data.update(stringify_keys(hash))
84
+ end
85
+ alias :merge! :update
86
+
87
+ def replace(hash)
88
+ load_for_write!
89
+ @data.replace(stringify_keys(hash))
90
90
  end
91
91
 
92
92
  def delete(key)
93
93
  load_for_write!
94
- super(key.to_s)
94
+ @data.delete(key.to_s)
95
95
  end
96
96
 
97
97
  def inspect
98
98
  if loaded?
99
- super
99
+ @data.inspect
100
100
  else
101
101
  "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
102
102
  end
@@ -104,7 +104,8 @@ module Rack
104
104
 
105
105
  def exists?
106
106
  return @exists if instance_variable_defined?(:@exists)
107
- @exists = @by.send(:session_exists?, @env)
107
+ @data = {}
108
+ @exists = @store.send(:session_exists?, @env)
108
109
  end
109
110
 
110
111
  def loaded?
@@ -113,12 +114,7 @@ module Rack
113
114
 
114
115
  def empty?
115
116
  load_for_read!
116
- super
117
- end
118
-
119
- def merge!(hash)
120
- load_for_write!
121
- super
117
+ @data.empty?
122
118
  end
123
119
 
124
120
  private
@@ -132,9 +128,8 @@ module Rack
132
128
  end
133
129
 
134
130
  def load!
135
- id, session = @by.send(:load_session, @env)
136
- @env[ENV_SESSION_OPTIONS_KEY][:id] = id
137
- replace(stringify_keys(session))
131
+ @id, session = @store.send(:load_session, @env)
132
+ @data = stringify_keys(session)
138
133
  @loaded = true
139
134
  end
140
135
 
@@ -225,7 +220,7 @@ module Rack
225
220
 
226
221
  def generate_sid(secure = @sid_secure)
227
222
  if secure
228
- SecureRandom.hex(@sid_length)
223
+ secure.hex(@sid_length)
229
224
  else
230
225
  "%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
231
226
  end
@@ -238,8 +233,8 @@ module Rack
238
233
 
239
234
  def prepare_session(env)
240
235
  session_was = env[ENV_SESSION_KEY]
241
- env[ENV_SESSION_KEY] = SessionHash.new(self, env)
242
- env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
236
+ env[ENV_SESSION_KEY] = session_class.new(self, env)
237
+ env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
243
238
  env[ENV_SESSION_KEY].merge! session_was if session_was
244
239
  end
245
240
 
@@ -261,10 +256,10 @@ module Rack
261
256
  sid
262
257
  end
263
258
 
264
- # Returns the current session id from the OptionsHash.
259
+ # Returns the current session id from the SessionHash.
265
260
 
266
261
  def current_session_id(env)
267
- env[ENV_SESSION_OPTIONS_KEY][:id]
262
+ env[ENV_SESSION_KEY].id
268
263
  end
269
264
 
270
265
  # Check if the session exists or not.
@@ -287,7 +282,7 @@ module Rack
287
282
  end
288
283
 
289
284
  def loaded_session?(session)
290
- !session.is_a?(SessionHash) || session.loaded?
285
+ !session.is_a?(session_class) || session.loaded?
291
286
  end
292
287
 
293
288
  def forced_session_update?(session, options)
@@ -310,21 +305,21 @@ module Rack
310
305
  # response with the session's id.
311
306
 
312
307
  def commit_session(env, status, headers, body)
313
- session = env['rack.session']
314
- options = env['rack.session.options']
308
+ session = env[ENV_SESSION_KEY]
309
+ options = session.options
315
310
 
316
311
  if options[:drop] || options[:renew]
317
- session_id = destroy_session(env, options[:id] || generate_sid, options)
312
+ session_id = destroy_session(env, session.id || generate_sid, options)
318
313
  return [status, headers, body] unless session_id
319
314
  end
320
315
 
321
316
  return [status, headers, body] unless commit_session?(env, session, options)
322
317
 
323
318
  session.send(:load!) unless loaded_session?(session)
324
- session = session.to_hash
325
- session_id ||= options[:id] || generate_sid
319
+ session_id ||= session.id
320
+ session_data = session.to_hash.delete_if { |k,v| v.nil? }
326
321
 
327
- if not data = set_session(env, session_id, session, options)
322
+ if not data = set_session(env, session_id, session_data, options)
328
323
  env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
329
324
  elsif options[:defer] and not options[:renew]
330
325
  env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
@@ -348,6 +343,12 @@ module Rack
348
343
  end
349
344
  end
350
345
 
346
+ # Allow subclasses to prepare_session for different Session classes
347
+
348
+ def session_class
349
+ SessionHash
350
+ end
351
+
351
352
  # All thread safety and session retrival proceedures should occur here.
352
353
  # Should return [session_id, session].
353
354
  # If nil is provided as the session id, generation of a new valid id
@@ -61,6 +61,7 @@ module Rack
61
61
  end
62
62
 
63
63
  def decode(str)
64
+ return unless str
64
65
  ::Marshal.load(super(str)) rescue nil
65
66
  end
66
67
  end
@@ -97,7 +98,7 @@ module Rack
97
98
 
98
99
  private
99
100
 
100
- def load_session(env)
101
+ def get_session(env, sid)
101
102
  data = unpacked_cookie_data(env)
102
103
  data = persistent_session_id!(data)
103
104
  [data["session_id"], data]
@@ -114,14 +115,7 @@ module Rack
114
115
 
115
116
  if @secrets.size > 0 && session_data
116
117
  session_data, digest = session_data.split("--")
117
-
118
- if session_data && digest
119
- ok = @secrets.any? do |secret|
120
- secret && Rack::Utils.secure_compare(digest, generate_hmac(session_data, secret))
121
- end
122
- end
123
-
124
- session_data = nil unless ok
118
+ session_data = nil unless digest_match?(session_data, digest)
125
119
  end
126
120
 
127
121
  coder.decode(session_data) || {}
@@ -134,18 +128,12 @@ module Rack
134
128
  data
135
129
  end
136
130
 
137
- # Overwrite set cookie to bypass content equality and always stream the cookie.
138
-
139
- def set_cookie(env, headers, cookie)
140
- Utils.set_cookie_header!(headers, @key, cookie)
141
- end
142
-
143
131
  def set_session(env, session_id, session, options)
144
132
  session = session.merge("session_id" => session_id)
145
133
  session_data = coder.encode(session)
146
134
 
147
135
  if @secrets.first
148
- session_data = "#{session_data}--#{generate_hmac(session_data, @secrets.first)}"
136
+ session_data << "--#{generate_hmac(session_data, @secrets.first)}"
149
137
  end
150
138
 
151
139
  if session_data.size > (4096 - @key.size)
@@ -161,6 +149,13 @@ module Rack
161
149
  generate_sid unless options[:drop]
162
150
  end
163
151
 
152
+ def digest_match?(data, digest)
153
+ return unless data && digest
154
+ @secrets.any? do |secret|
155
+ digest == generate_hmac(data, secret)
156
+ end
157
+ end
158
+
164
159
  def generate_hmac(data, secret)
165
160
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
166
161
  end
@@ -51,23 +51,12 @@ module Rack
51
51
 
52
52
  class << self
53
53
  attr_accessor :key_space_limit
54
- attr_accessor :param_depth_limit
55
- attr_accessor :multipart_part_limit
56
54
  end
57
55
 
58
56
  # The default number of bytes to allow parameter keys to take up.
59
57
  # This helps prevent a rogue client from flooding a Request.
60
58
  self.key_space_limit = 65536
61
59
 
62
- # Default depth at which the parameter parser will raise an exception for
63
- # being too deep. This helps prevent SystemStackErrors
64
- self.param_depth_limit = 100
65
- #
66
- # The maximum number of parts a request can contain. Accepting to many part
67
- # can lead to the server running out of file handles.
68
- # Set to `0` for no limit.
69
- self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
70
-
71
60
  # Stolen from Mongrel, with some small modifications:
72
61
  # Parses a query string by breaking it up at the '&'
73
62
  # and ';' characters. You can also use this to parse
@@ -111,9 +100,7 @@ module Rack
111
100
  end
112
101
  module_function :parse_nested_query
113
102
 
114
- def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit)
115
- raise RangeError if depth <= 0
116
-
103
+ def normalize_params(params, name, v = nil)
117
104
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
118
105
  k = $1 || ''
119
106
  after = $' || ''
@@ -131,14 +118,14 @@ module Rack
131
118
  params[k] ||= []
132
119
  raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
133
120
  if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
134
- normalize_params(params[k].last, child_key, v, depth - 1)
121
+ normalize_params(params[k].last, child_key, v)
135
122
  else
136
- params[k] << normalize_params(params.class.new, child_key, v, depth - 1)
123
+ params[k] << normalize_params(params.class.new, child_key, v)
137
124
  end
138
125
  else
139
126
  params[k] ||= params.class.new
140
127
  raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
141
- params[k] = normalize_params(params[k], after, v, depth - 1)
128
+ params[k] = normalize_params(params[k], after, v)
142
129
  end
143
130
 
144
131
  return params
@@ -180,6 +167,31 @@ module Rack
180
167
  end
181
168
  module_function :build_nested_query
182
169
 
170
+ def q_values(q_value_header)
171
+ q_value_header.to_s.split(/\s*,\s*/).map do |part|
172
+ value, parameters = part.split(/\s*;\s*/, 2)
173
+ quality = 1.0
174
+ if md = /\Aq=([\d.]+)/.match(parameters)
175
+ quality = md[1].to_f
176
+ end
177
+ [value, quality]
178
+ end
179
+ end
180
+ module_function :q_values
181
+
182
+ def best_q_match(q_value_header, available_mimes)
183
+ values = q_values(q_value_header)
184
+
185
+ values.map do |req_mime, quality|
186
+ match = available_mimes.first { |am| Rack::Mime.match?(am, req_mime) }
187
+ next unless match
188
+ [match, quality]
189
+ end.compact.sort_by do |match, quality|
190
+ (match.split('/', 2).count('*') * -10) + quality
191
+ end.last.first
192
+ end
193
+ module_function :best_q_match
194
+
183
195
  ESCAPE_HTML = {
184
196
  "&" => "&amp;",
185
197
  "<" => "&lt;",
@@ -237,6 +249,7 @@ module Rack
237
249
  when Hash
238
250
  domain = "; domain=" + value[:domain] if value[:domain]
239
251
  path = "; path=" + value[:path] if value[:path]
252
+ max_age = "; max-age=" + value[:max_age] if value[:max_age]
240
253
  # According to RFC 2109, we need dashes here.
241
254
  # N.B.: cgi.rb uses spaces...
242
255
  expires = "; expires=" +
@@ -248,7 +261,7 @@ module Rack
248
261
  value = [value] unless Array === value
249
262
  cookie = escape(key) + "=" +
250
263
  value.map { |v| escape v }.join("&") +
251
- "#{domain}#{path}#{expires}#{secure}#{httponly}"
264
+ "#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}"
252
265
 
253
266
  case header["Set-Cookie"]
254
267
  when nil, ''
@@ -287,6 +300,7 @@ module Rack
287
300
 
288
301
  set_cookie_header!(header, key,
289
302
  {:value => '', :path => nil, :domain => nil,
303
+ :max_age => '0',
290
304
  :expires => Time.at(0) }.merge(value))
291
305
 
292
306
  nil
@@ -355,18 +369,6 @@ module Rack
355
369
  end
356
370
  module_function :byte_ranges
357
371
 
358
- # Constant time string comparison.
359
- def secure_compare(a, b)
360
- return false unless bytesize(a) == bytesize(b)
361
-
362
- l = a.unpack("C*")
363
-
364
- r, i = 0, -1
365
- b.each_byte { |v| r |= v ^ l[i+=1] }
366
- r == 0
367
- end
368
- module_function :secure_compare
369
-
370
372
  # Context allows the use of a compatible middleware at different points
371
373
  # in a request handling stack. A compatible middleware must define
372
374
  # #context which should take the arguments env and app. The first of which
@@ -498,62 +500,72 @@ module Rack
498
500
 
499
501
  # Every standard HTTP code mapped to the appropriate message.
500
502
  # Generated with:
501
- # curl -s http://www.iana.org/assignments/http-status-codes | \
502
- # ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
503
- # puts " #{m[1]} => \x27#{m[2].strip}x27,"'
503
+ # irb -ropen-uri -rnokogiri
504
+ # > Nokogiri::XML(open("http://www.iana.org/assignments/http-status-codes/http-status-codes.xml")).css("record").each{|r|
505
+ # puts "#{r.css('value').text} => '#{r.css('description').text}'"}
504
506
  HTTP_STATUS_CODES = {
505
- 100 => 'Continue',
506
- 101 => 'Switching Protocols',
507
- 102 => 'Processing',
508
- 200 => 'OK',
509
- 201 => 'Created',
510
- 202 => 'Accepted',
511
- 203 => 'Non-Authoritative Information',
512
- 204 => 'No Content',
513
- 205 => 'Reset Content',
514
- 206 => 'Partial Content',
515
- 207 => 'Multi-Status',
516
- 226 => 'IM Used',
517
- 300 => 'Multiple Choices',
518
- 301 => 'Moved Permanently',
519
- 302 => 'Found',
520
- 303 => 'See Other',
521
- 304 => 'Not Modified',
522
- 305 => 'Use Proxy',
523
- 306 => 'Reserved',
524
- 307 => 'Temporary Redirect',
525
- 400 => 'Bad Request',
526
- 401 => 'Unauthorized',
527
- 402 => 'Payment Required',
528
- 403 => 'Forbidden',
529
- 404 => 'Not Found',
530
- 405 => 'Method Not Allowed',
531
- 406 => 'Not Acceptable',
532
- 407 => 'Proxy Authentication Required',
533
- 408 => 'Request Timeout',
534
- 409 => 'Conflict',
535
- 410 => 'Gone',
536
- 411 => 'Length Required',
537
- 412 => 'Precondition Failed',
538
- 413 => 'Request Entity Too Large',
539
- 414 => 'Request-URI Too Long',
540
- 415 => 'Unsupported Media Type',
541
- 416 => 'Requested Range Not Satisfiable',
542
- 417 => 'Expectation Failed',
543
- 418 => "I'm a Teapot",
544
- 422 => 'Unprocessable Entity',
545
- 423 => 'Locked',
546
- 424 => 'Failed Dependency',
547
- 426 => 'Upgrade Required',
548
- 500 => 'Internal Server Error',
549
- 501 => 'Not Implemented',
550
- 502 => 'Bad Gateway',
551
- 503 => 'Service Unavailable',
552
- 504 => 'Gateway Timeout',
553
- 505 => 'HTTP Version Not Supported',
554
- 506 => 'Variant Also Negotiates',
555
- 507 => 'Insufficient Storage',
556
- 510 => 'Not Extended',
507
+ 100 => 'Continue',
508
+ 101 => 'Switching Protocols',
509
+ 102 => 'Processing',
510
+ 200 => 'OK',
511
+ 201 => 'Created',
512
+ 202 => 'Accepted',
513
+ 203 => 'Non-Authoritative Information',
514
+ 204 => 'No Content',
515
+ 205 => 'Reset Content',
516
+ 206 => 'Partial Content',
517
+ 207 => 'Multi-Status',
518
+ 208 => 'Already Reported',
519
+ 226 => 'IM Used',
520
+ 300 => 'Multiple Choices',
521
+ 301 => 'Moved Permanently',
522
+ 302 => 'Found',
523
+ 303 => 'See Other',
524
+ 304 => 'Not Modified',
525
+ 305 => 'Use Proxy',
526
+ 306 => 'Reserved',
527
+ 307 => 'Temporary Redirect',
528
+ 308 => 'Permanent Redirect',
529
+ 400 => 'Bad Request',
530
+ 401 => 'Unauthorized',
531
+ 402 => 'Payment Required',
532
+ 403 => 'Forbidden',
533
+ 404 => 'Not Found',
534
+ 405 => 'Method Not Allowed',
535
+ 406 => 'Not Acceptable',
536
+ 407 => 'Proxy Authentication Required',
537
+ 408 => 'Request Timeout',
538
+ 409 => 'Conflict',
539
+ 410 => 'Gone',
540
+ 411 => 'Length Required',
541
+ 412 => 'Precondition Failed',
542
+ 413 => 'Request Entity Too Large',
543
+ 414 => 'Request-URI Too Long',
544
+ 415 => 'Unsupported Media Type',
545
+ 416 => 'Requested Range Not Satisfiable',
546
+ 417 => 'Expectation Failed',
547
+ 422 => 'Unprocessable Entity',
548
+ 423 => 'Locked',
549
+ 424 => 'Failed Dependency',
550
+ 425 => 'Reserved for WebDAV advanced collections expired proposal',
551
+ 426 => 'Upgrade Required',
552
+ 427 => 'Unassigned',
553
+ 428 => 'Precondition Required',
554
+ 429 => 'Too Many Requests',
555
+ 430 => 'Unassigned',
556
+ 431 => 'Request Header Fields Too Large',
557
+ 500 => 'Internal Server Error',
558
+ 501 => 'Not Implemented',
559
+ 502 => 'Bad Gateway',
560
+ 503 => 'Service Unavailable',
561
+ 504 => 'Gateway Timeout',
562
+ 505 => 'HTTP Version Not Supported',
563
+ 506 => 'Variant Also Negotiates (Experimental)',
564
+ 507 => 'Insufficient Storage',
565
+ 508 => 'Loop Detected',
566
+ 509 => 'Unassigned',
567
+ 510 => 'Not Extended',
568
+ 511 => 'Network Authentication Required'
557
569
  }
558
570
 
559
571
  # Responses with HTTP status codes that should not have an entity body