rack 1.6.12 → 2.0.3

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 (142) hide show
  1. checksums.yaml +5 -5
  2. data/COPYING +1 -1
  3. data/HISTORY.md +138 -8
  4. data/README.rdoc +17 -25
  5. data/Rakefile +6 -14
  6. data/SPEC +10 -11
  7. data/contrib/rack_logo.svg +164 -111
  8. data/example/protectedlobster.rb +1 -1
  9. data/example/protectedlobster.ru +1 -1
  10. data/lib/rack.rb +70 -21
  11. data/lib/rack/auth/abstract/request.rb +5 -1
  12. data/lib/rack/auth/digest/params.rb +2 -3
  13. data/lib/rack/auth/digest/request.rb +1 -1
  14. data/lib/rack/body_proxy.rb +14 -9
  15. data/lib/rack/builder.rb +3 -3
  16. data/lib/rack/chunked.rb +5 -5
  17. data/lib/rack/{commonlogger.rb → common_logger.rb} +3 -3
  18. data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
  19. data/lib/rack/content_length.rb +2 -2
  20. data/lib/rack/deflater.rb +4 -39
  21. data/lib/rack/directory.rb +66 -54
  22. data/lib/rack/etag.rb +4 -3
  23. data/lib/rack/events.rb +154 -0
  24. data/lib/rack/file.rb +64 -40
  25. data/lib/rack/handler.rb +3 -25
  26. data/lib/rack/handler/cgi.rb +15 -16
  27. data/lib/rack/handler/fastcgi.rb +13 -14
  28. data/lib/rack/handler/lsws.rb +11 -11
  29. data/lib/rack/handler/scgi.rb +15 -15
  30. data/lib/rack/handler/thin.rb +3 -0
  31. data/lib/rack/handler/webrick.rb +24 -26
  32. data/lib/rack/head.rb +15 -17
  33. data/lib/rack/lint.rb +40 -40
  34. data/lib/rack/lobster.rb +1 -1
  35. data/lib/rack/lock.rb +6 -10
  36. data/lib/rack/logger.rb +2 -2
  37. data/lib/rack/media_type.rb +38 -0
  38. data/lib/rack/{methodoverride.rb → method_override.rb} +6 -10
  39. data/lib/rack/mime.rb +18 -5
  40. data/lib/rack/mock.rb +36 -54
  41. data/lib/rack/multipart.rb +35 -6
  42. data/lib/rack/multipart/generator.rb +5 -5
  43. data/lib/rack/multipart/parser.rb +272 -158
  44. data/lib/rack/multipart/uploaded_file.rb +1 -2
  45. data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
  46. data/lib/rack/query_parser.rb +192 -0
  47. data/lib/rack/recursive.rb +8 -8
  48. data/lib/rack/request.rb +383 -307
  49. data/lib/rack/response.rb +130 -57
  50. data/lib/rack/rewindable_input.rb +1 -12
  51. data/lib/rack/runtime.rb +10 -18
  52. data/lib/rack/sendfile.rb +5 -7
  53. data/lib/rack/server.rb +30 -23
  54. data/lib/rack/session/abstract/id.rb +108 -138
  55. data/lib/rack/session/cookie.rb +26 -28
  56. data/lib/rack/session/memcache.rb +8 -14
  57. data/lib/rack/session/pool.rb +14 -21
  58. data/lib/rack/show_exceptions.rb +386 -0
  59. data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
  60. data/lib/rack/static.rb +30 -5
  61. data/lib/rack/tempfile_reaper.rb +2 -2
  62. data/lib/rack/urlmap.rb +15 -14
  63. data/lib/rack/utils.rb +136 -211
  64. data/rack.gemspec +7 -5
  65. data/test/builder/an_underscore_app.rb +5 -0
  66. data/test/builder/options.ru +1 -1
  67. data/test/cgi/test.fcgi +1 -0
  68. data/test/cgi/test.gz +0 -0
  69. data/test/helper.rb +34 -0
  70. data/test/multipart/filename_with_encoded_words +7 -0
  71. data/test/multipart/filename_with_single_quote +7 -0
  72. data/test/multipart/quoted +15 -0
  73. data/test/multipart/rack-logo.png +0 -0
  74. data/test/multipart/unity3d_wwwform +11 -0
  75. data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
  76. data/test/spec_auth_basic.rb +27 -19
  77. data/test/spec_auth_digest.rb +47 -46
  78. data/test/spec_body_proxy.rb +27 -27
  79. data/test/spec_builder.rb +51 -41
  80. data/test/spec_cascade.rb +24 -22
  81. data/test/spec_cgi.rb +49 -67
  82. data/test/spec_chunked.rb +37 -35
  83. data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
  84. data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
  85. data/test/spec_config.rb +3 -2
  86. data/test/spec_content_length.rb +18 -17
  87. data/test/spec_content_type.rb +13 -12
  88. data/test/spec_deflater.rb +85 -49
  89. data/test/spec_directory.rb +87 -27
  90. data/test/spec_etag.rb +32 -31
  91. data/test/spec_events.rb +133 -0
  92. data/test/spec_fastcgi.rb +50 -72
  93. data/test/spec_file.rb +120 -77
  94. data/test/spec_handler.rb +19 -34
  95. data/test/spec_head.rb +15 -14
  96. data/test/spec_lint.rb +164 -199
  97. data/test/spec_lobster.rb +24 -23
  98. data/test/spec_lock.rb +69 -39
  99. data/test/spec_logger.rb +4 -3
  100. data/test/spec_media_type.rb +42 -0
  101. data/test/{spec_methodoverride.rb → spec_method_override.rb} +22 -37
  102. data/test/spec_mime.rb +19 -19
  103. data/test/spec_mock.rb +206 -144
  104. data/test/spec_multipart.rb +322 -200
  105. data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
  106. data/test/spec_recursive.rb +17 -14
  107. data/test/spec_request.rb +768 -607
  108. data/test/spec_response.rb +215 -112
  109. data/test/spec_rewindable_input.rb +50 -40
  110. data/test/spec_runtime.rb +11 -10
  111. data/test/spec_sendfile.rb +30 -35
  112. data/test/spec_server.rb +78 -52
  113. data/test/spec_session_abstract_id.rb +11 -33
  114. data/test/spec_session_abstract_session_hash.rb +45 -0
  115. data/test/spec_session_cookie.rb +99 -67
  116. data/test/spec_session_memcache.rb +63 -101
  117. data/test/spec_session_pool.rb +48 -84
  118. data/test/spec_show_exceptions.rb +80 -0
  119. data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
  120. data/test/spec_static.rb +71 -32
  121. data/test/spec_tempfile_reaper.rb +11 -10
  122. data/test/spec_thin.rb +55 -50
  123. data/test/spec_urlmap.rb +79 -78
  124. data/test/spec_utils.rb +441 -346
  125. data/test/spec_version.rb +2 -8
  126. data/test/spec_webrick.rb +93 -68
  127. data/test/static/foo.html +1 -0
  128. data/test/testrequest.rb +1 -1
  129. data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
  130. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
  131. metadata +102 -66
  132. data/KNOWN-ISSUES +0 -44
  133. data/lib/rack/backports/uri/common_18.rb +0 -56
  134. data/lib/rack/backports/uri/common_192.rb +0 -52
  135. data/lib/rack/backports/uri/common_193.rb +0 -29
  136. data/lib/rack/handler/evented_mongrel.rb +0 -8
  137. data/lib/rack/handler/mongrel.rb +0 -106
  138. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  139. data/lib/rack/showexceptions.rb +0 -387
  140. data/lib/rack/utils/okjson.rb +0 -600
  141. data/test/spec_mongrel.rb +0 -182
  142. data/test/spec_showexceptions.rb +0 -98
@@ -22,7 +22,7 @@ module Rack
22
22
  empty = headers[CONTENT_LENGTH].to_i <= 0
23
23
 
24
24
  # client or server error, or explicit message
25
- if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
25
+ if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL]
26
26
  # This double assignment is to prevent an "unused variable" warning on
27
27
  # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
28
28
  req = req = Rack::Request.new(env)
@@ -31,10 +31,10 @@ module Rack
31
31
 
32
32
  # This double assignment is to prevent an "unused variable" warning on
33
33
  # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
34
- detail = detail = env["rack.showstatus.detail"] || message
34
+ detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message
35
35
 
36
36
  body = @template.result(binding)
37
- size = Rack::Utils.bytesize(body)
37
+ size = body.bytesize
38
38
  [status, headers.merge(CONTENT_TYPE => "text/html", CONTENT_LENGTH => size.to_s), [body]]
39
39
  else
40
40
  [status, headers, body]
@@ -1,3 +1,6 @@
1
+ require "rack/file"
2
+ require "rack/utils"
3
+
1
4
  module Rack
2
5
 
3
6
  # The Rack::Static middleware intercepts requests for static files
@@ -84,18 +87,23 @@ module Rack
84
87
  @app = app
85
88
  @urls = options[:urls] || ["/favicon.ico"]
86
89
  @index = options[:index]
90
+ @gzip = options[:gzip]
87
91
  root = options[:root] || Dir.pwd
88
92
 
89
93
  # HTTP Headers
90
94
  @header_rules = options[:header_rules] || []
91
95
  # Allow for legacy :cache_control option while prioritizing global header_rules setting
92
- @header_rules.insert(0, [:all, {'Cache-Control' => options[:cache_control]}]) if options[:cache_control]
96
+ @header_rules.unshift([:all, {CACHE_CONTROL => options[:cache_control]}]) if options[:cache_control]
93
97
 
94
98
  @file_server = Rack::File.new(root)
95
99
  end
96
100
 
101
+ def add_index_root?(path)
102
+ @index && path =~ /\/$/
103
+ end
104
+
97
105
  def overwrite_file_path(path)
98
- @urls.kind_of?(Hash) && @urls.key?(path) || @index && path =~ /\/$/
106
+ @urls.kind_of?(Hash) && @urls.key?(path) || add_index_root?(path)
99
107
  end
100
108
 
101
109
  def route_file(path)
@@ -110,9 +118,26 @@ module Rack
110
118
  path = env[PATH_INFO]
111
119
 
112
120
  if can_serve(path)
113
- env["PATH_INFO"] = (path =~ /\/$/ ? path + @index : @urls[path]) if overwrite_file_path(path)
114
- path = env["PATH_INFO"]
115
- response = @file_server.call(env)
121
+ if overwrite_file_path(path)
122
+ env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
123
+ elsif @gzip && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
124
+ path = env[PATH_INFO]
125
+ env[PATH_INFO] += '.gz'
126
+ response = @file_server.call(env)
127
+ env[PATH_INFO] = path
128
+
129
+ if response[0] == 404
130
+ response = nil
131
+ else
132
+ if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
133
+ response[1][CONTENT_TYPE] = mime_type
134
+ end
135
+ response[1]['Content-Encoding'] = 'gzip'
136
+ end
137
+ end
138
+
139
+ path = env[PATH_INFO]
140
+ response ||= @file_server.call(env)
116
141
 
117
142
  headers = response[1]
118
143
  applicable_rules(path).each do |rule, new_headers|
@@ -11,10 +11,10 @@ module Rack
11
11
  end
12
12
 
13
13
  def call(env)
14
- env['rack.tempfiles'] ||= []
14
+ env[RACK_TEMPFILES] ||= []
15
15
  status, headers, body = @app.call(env)
16
16
  body_proxy = BodyProxy.new(body) do
17
- env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
17
+ env[RACK_TEMPFILES].each(&:close!) unless env[RACK_TEMPFILES].nil?
18
18
  end
19
19
  [status, headers, body_proxy]
20
20
  end
@@ -41,17 +41,19 @@ module Rack
41
41
  end
42
42
 
43
43
  def call(env)
44
- path = env[PATH_INFO]
45
- script_name = env['SCRIPT_NAME']
46
- hHost = env['HTTP_HOST']
47
- sName = env['SERVER_NAME']
48
- sPort = env['SERVER_PORT']
44
+ path = env[PATH_INFO]
45
+ script_name = env[SCRIPT_NAME]
46
+ http_host = env[HTTP_HOST]
47
+ server_name = env[SERVER_NAME]
48
+ server_port = env[SERVER_PORT]
49
+
50
+ is_same_server = casecmp?(http_host, server_name) ||
51
+ casecmp?(http_host, "#{server_name}:#{server_port}")
49
52
 
50
53
  @mapping.each do |host, location, match, app|
51
- unless casecmp?(hHost, host) \
52
- || casecmp?(sName, host) \
53
- || (!host && (casecmp?(hHost, sName) ||
54
- casecmp?(hHost, sName+':'+sPort)))
54
+ unless casecmp?(http_host, host) \
55
+ || casecmp?(server_name, host) \
56
+ || (!host && is_same_server)
55
57
  next
56
58
  end
57
59
 
@@ -60,8 +62,8 @@ module Rack
60
62
  rest = m[1]
61
63
  next unless !rest || rest.empty? || rest[0] == ?/
62
64
 
63
- env['SCRIPT_NAME'] = (script_name + location)
64
- env['PATH_INFO'] = rest
65
+ env[SCRIPT_NAME] = (script_name + location)
66
+ env[PATH_INFO] = rest
65
67
 
66
68
  return app.call(env)
67
69
  end
@@ -69,8 +71,8 @@ module Rack
69
71
  [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
70
72
 
71
73
  ensure
72
- env['PATH_INFO'] = path
73
- env['SCRIPT_NAME'] = script_name
74
+ env[PATH_INFO] = path
75
+ env[SCRIPT_NAME] = script_name
74
76
  end
75
77
 
76
78
  private
@@ -87,4 +89,3 @@ module Rack
87
89
  end
88
90
  end
89
91
  end
90
-
@@ -1,35 +1,28 @@
1
1
  # -*- encoding: binary -*-
2
+ require 'uri'
2
3
  require 'fileutils'
3
4
  require 'set'
4
5
  require 'tempfile'
5
- require 'rack/multipart'
6
+ require 'rack/query_parser'
6
7
  require 'time'
7
8
 
8
- major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
9
-
10
- if major == 1 && minor < 9
11
- require 'rack/backports/uri/common_18'
12
- elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
13
- require 'rack/backports/uri/common_192'
14
- elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
15
- require 'rack/backports/uri/common_193'
16
- else
17
- require 'uri/common'
18
- end
19
-
20
9
  module Rack
21
10
  # Rack::Utils contains a grab-bag of useful methods for writing web
22
11
  # applications adopted from all kinds of Ruby libraries.
23
12
 
24
13
  module Utils
25
- # ParameterTypeError is the error that is raised when incoming structural
26
- # parameters (parsed by parse_nested_query) contain conflicting types.
27
- class ParameterTypeError < TypeError; end
14
+ ParameterTypeError = QueryParser::ParameterTypeError
15
+ InvalidParameterError = QueryParser::InvalidParameterError
16
+ DEFAULT_SEP = QueryParser::DEFAULT_SEP
17
+ COMMON_SEP = QueryParser::COMMON_SEP
18
+ KeySpaceConstrainedParams = QueryParser::Params
28
19
 
29
- # InvalidParameterError is the error that is raised when incoming structural
30
- # parameters (parsed by parse_nested_query) contain invalid format or byte
31
- # sequence.
32
- class InvalidParameterError < ArgumentError; end
20
+ class << self
21
+ attr_accessor :default_query_parser
22
+ end
23
+ # The default number of bytes to allow parameter keys to take up.
24
+ # This helps prevent a rogue client from flooding a Request.
25
+ self.default_query_parser = QueryParser.make_default(65536, 100)
33
26
 
34
27
  # URI escapes. (CGI style space to +)
35
28
  def escape(s)
@@ -40,137 +33,70 @@ module Rack
40
33
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
41
34
  # true URI escaping.
42
35
  def escape_path(s)
43
- escape(s).gsub('+', '%20')
36
+ ::URI::DEFAULT_PARSER.escape s
44
37
  end
45
38
  module_function :escape_path
46
39
 
40
+ # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
41
+ # unescaping query parameters or form components.
42
+ def unescape_path(s)
43
+ ::URI::DEFAULT_PARSER.unescape s
44
+ end
45
+ module_function :unescape_path
46
+
47
+
47
48
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
48
49
  # target encoding of the string returned, and it defaults to UTF-8
49
- if defined?(::Encoding)
50
- def unescape(s, encoding = Encoding::UTF_8)
51
- URI.decode_www_form_component(s, encoding)
52
- end
53
- else
54
- def unescape(s, encoding = nil)
55
- URI.decode_www_form_component(s, encoding)
56
- end
50
+ def unescape(s, encoding = Encoding::UTF_8)
51
+ URI.decode_www_form_component(s, encoding)
57
52
  end
58
53
  module_function :unescape
59
54
 
60
- DEFAULT_SEP = /[&;] */n
61
-
62
55
  class << self
63
- attr_accessor :key_space_limit
64
- attr_accessor :param_depth_limit
65
56
  attr_accessor :multipart_part_limit
66
57
  end
67
58
 
68
- # The default number of bytes to allow parameter keys to take up.
69
- # This helps prevent a rogue client from flooding a Request.
70
- self.key_space_limit = 65536
71
-
72
- # Default depth at which the parameter parser will raise an exception for
73
- # being too deep. This helps prevent SystemStackErrors
74
- self.param_depth_limit = 100
75
-
76
59
  # The maximum number of parts a request can contain. Accepting too many part
77
60
  # can lead to the server running out of file handles.
78
61
  # Set to `0` for no limit.
79
- # FIXME: RACK_MULTIPART_LIMIT was introduced by mistake and it will be removed in 1.7.0
80
- self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_LIMIT'] || 128).to_i
81
-
82
- # Stolen from Mongrel, with some small modifications:
83
- # Parses a query string by breaking it up at the '&'
84
- # and ';' characters. You can also use this to parse
85
- # cookies by changing the characters used in the second
86
- # parameter (which defaults to '&;').
87
- def parse_query(qs, d = nil, &unescaper)
88
- unescaper ||= method(:unescape)
89
-
90
- params = KeySpaceConstrainedParams.new
91
-
92
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
93
- next if p.empty?
94
- k, v = p.split('=', 2).map(&unescaper)
62
+ self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
95
63
 
96
- if cur = params[k]
97
- if cur.class == Array
98
- params[k] << v
99
- else
100
- params[k] = [cur, v]
101
- end
102
- else
103
- params[k] = v
104
- end
105
- end
106
-
107
- return params.to_params_hash
64
+ def self.param_depth_limit
65
+ default_query_parser.param_depth_limit
108
66
  end
109
- module_function :parse_query
110
-
111
- # parse_nested_query expands a query string into structural types. Supported
112
- # types are Arrays, Hashes and basic value types. It is possible to supply
113
- # query strings with parameters of conflicting types, in this case a
114
- # ParameterTypeError is raised. Users are encouraged to return a 400 in this
115
- # case.
116
- def parse_nested_query(qs, d = nil)
117
- params = KeySpaceConstrainedParams.new
118
67
 
119
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
120
- k, v = p.split('=', 2).map { |s| unescape(s) }
68
+ def self.param_depth_limit=(v)
69
+ self.default_query_parser = self.default_query_parser.new_depth_limit(v)
70
+ end
121
71
 
122
- normalize_params(params, k, v)
123
- end
72
+ def self.key_space_limit
73
+ default_query_parser.key_space_limit
74
+ end
124
75
 
125
- return params.to_params_hash
126
- rescue ArgumentError => e
127
- raise InvalidParameterError, e.message
76
+ def self.key_space_limit=(v)
77
+ self.default_query_parser = self.default_query_parser.new_space_limit(v)
128
78
  end
129
- module_function :parse_nested_query
130
79
 
131
- # normalize_params recursively expands parameters into structural types. If
132
- # the structural types represented by two different parameter names are in
133
- # conflict, a ParameterTypeError is raised.
134
- def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit)
135
- raise RangeError if depth <= 0
136
-
137
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
138
- k = $1 || ''
139
- after = $' || ''
140
-
141
- return if k.empty?
142
-
143
- if after == ""
144
- params[k] = v
145
- elsif after == "["
146
- params[name] = v
147
- elsif after == "[]"
148
- params[k] ||= []
149
- raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
150
- params[k] << v
151
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
152
- child_key = $1
153
- params[k] ||= []
154
- raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
155
- if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
156
- normalize_params(params[k].last, child_key, v, depth - 1)
157
- else
158
- params[k] << normalize_params(params.class.new, child_key, v, depth - 1)
159
- end
160
- else
161
- params[k] ||= params.class.new
162
- raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
163
- params[k] = normalize_params(params[k], after, v, depth - 1)
80
+ if defined?(Process::CLOCK_MONOTONIC)
81
+ def clock_time
82
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
83
+ end
84
+ else
85
+ def clock_time
86
+ Time.now.to_f
164
87
  end
88
+ end
89
+ module_function :clock_time
165
90
 
166
- return params
91
+ def parse_query(qs, d = nil, &unescaper)
92
+ Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
167
93
  end
168
- module_function :normalize_params
94
+ module_function :parse_query
169
95
 
170
- def params_hash_type?(obj)
171
- obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
96
+ def parse_nested_query(qs, d = nil)
97
+ Rack::Utils.default_query_parser.parse_nested_query(qs, d)
172
98
  end
173
- module_function :params_hash_type?
99
+ module_function :parse_nested_query
174
100
 
175
101
  def build_query(params)
176
102
  params.map { |k, v|
@@ -236,13 +162,8 @@ module Rack
236
162
  '"' => "&quot;",
237
163
  "/" => "&#x2F;"
238
164
  }
239
- if //.respond_to?(:encoding)
240
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
241
- else
242
- # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise
243
- # TODO doesn't apply to jruby, so a better condition above might be preferable?
244
- ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
245
- end
165
+
166
+ ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
246
167
 
247
168
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
248
169
  def escape_html(string)
@@ -278,12 +199,28 @@ module Rack
278
199
  end
279
200
  module_function :select_best_encoding
280
201
 
281
- def set_cookie_header!(header, key, value)
202
+ def parse_cookies(env)
203
+ parse_cookies_header env[HTTP_COOKIE]
204
+ end
205
+ module_function :parse_cookies
206
+
207
+ def parse_cookies_header(header)
208
+ # According to RFC 2109:
209
+ # If multiple cookies satisfy the criteria above, they are ordered in
210
+ # the Cookie header such that those with more specific Path attributes
211
+ # precede those with less specific. Ordering with respect to other
212
+ # attributes (e.g., Domain) is unspecified.
213
+ cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
214
+ cookies.each_with_object({}) { |(k,v), hash| hash[k] = Array === v ? v.first : v }
215
+ end
216
+ module_function :parse_cookies_header
217
+
218
+ def add_cookie_to_header(header, key, value)
282
219
  case value
283
220
  when Hash
284
- domain = "; domain=" + value[:domain] if value[:domain]
285
- path = "; path=" + value[:path] if value[:path]
286
- max_age = "; max-age=" + value[:max_age].to_s if value[:max_age]
221
+ domain = "; domain=#{value[:domain]}" if value[:domain]
222
+ path = "; path=#{value[:path]}" if value[:path]
223
+ max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
287
224
  # There is an RFC mess in the area of date formatting for Cookies. Not
288
225
  # only are there contradicting RFCs and examples within RFC text, but
289
226
  # there are also numerous conflicting names of fields and partially
@@ -291,7 +228,7 @@ module Rack
291
228
  #
292
229
  # These are best described in RFC 2616 3.3.1. This RFC text also
293
230
  # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
294
- # fixed length format with space-date delimeted fields.
231
+ # fixed length format with space-date delimited fields.
295
232
  #
296
233
  # See also RFC 1123 section 5.2.14.
297
234
  #
@@ -325,31 +262,37 @@ module Rack
325
262
  value = value[:value]
326
263
  end
327
264
  value = [value] unless Array === value
328
- cookie = escape(key) + "=" +
329
- value.map { |v| escape v }.join("&") +
330
- "#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
331
265
 
332
- case header["Set-Cookie"]
266
+ cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
267
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
268
+
269
+ case header
333
270
  when nil, ''
334
- header["Set-Cookie"] = cookie
271
+ cookie
335
272
  when String
336
- header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
273
+ [header, cookie].join("\n")
337
274
  when Array
338
- header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
275
+ (header + [cookie]).join("\n")
276
+ else
277
+ raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
339
278
  end
279
+ end
280
+ module_function :add_cookie_to_header
340
281
 
282
+ def set_cookie_header!(header, key, value)
283
+ header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
341
284
  nil
342
285
  end
343
286
  module_function :set_cookie_header!
344
287
 
345
- def delete_cookie_header!(header, key, value = {})
346
- case header["Set-Cookie"]
288
+ def make_delete_cookie_header(header, key, value)
289
+ case header
347
290
  when nil, ''
348
291
  cookies = []
349
292
  when String
350
- cookies = header["Set-Cookie"].split("\n")
293
+ cookies = header.split("\n")
351
294
  when Array
352
- cookies = header["Set-Cookie"]
295
+ cookies = header
353
296
  end
354
297
 
355
298
  cookies.reject! { |cookie|
@@ -362,29 +305,28 @@ module Rack
362
305
  end
363
306
  }
364
307
 
365
- header["Set-Cookie"] = cookies.join("\n")
366
-
367
- set_cookie_header!(header, key,
368
- {:value => '', :path => nil, :domain => nil,
369
- :max_age => '0',
370
- :expires => Time.at(0) }.merge(value))
308
+ cookies.join("\n")
309
+ end
310
+ module_function :make_delete_cookie_header
371
311
 
312
+ def delete_cookie_header!(header, key, value = {})
313
+ header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
372
314
  nil
373
315
  end
374
316
  module_function :delete_cookie_header!
375
317
 
376
- # Return the bytesize of String; uses String#size under Ruby 1.8 and
377
- # String#bytesize under 1.9.
378
- if ''.respond_to?(:bytesize)
379
- def bytesize(string)
380
- string.bytesize
381
- end
382
- else
383
- def bytesize(string)
384
- string.size
385
- end
318
+ # Adds a cookie that will *remove* a cookie from the client. Hence the
319
+ # strange method name.
320
+ def add_remove_cookie_to_header(header, key, value = {})
321
+ new_header = make_delete_cookie_header(header, key, value)
322
+
323
+ add_cookie_to_header(new_header, key,
324
+ {:value => '', :path => nil, :domain => nil,
325
+ :max_age => '0',
326
+ :expires => Time.at(0) }.merge(value))
327
+
386
328
  end
387
- module_function :bytesize
329
+ module_function :add_remove_cookie_to_header
388
330
 
389
331
  def rfc2822(time)
390
332
  time.rfc2822
@@ -411,8 +353,13 @@ module Rack
411
353
  # Returns nil if the header is missing or syntactically invalid.
412
354
  # Returns an empty array if none of the ranges are satisfiable.
413
355
  def byte_ranges(env, size)
356
+ warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
357
+ get_byte_ranges env['HTTP_RANGE'], size
358
+ end
359
+ module_function :byte_ranges
360
+
361
+ def get_byte_ranges(http_range, size)
414
362
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
415
- http_range = env['HTTP_RANGE']
416
363
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
417
364
  ranges = []
418
365
  $1.split(/,\s*/).each do |range_spec|
@@ -438,7 +385,7 @@ module Rack
438
385
  end
439
386
  ranges
440
387
  end
441
- module_function :byte_ranges
388
+ module_function :get_byte_ranges
442
389
 
443
390
  # Constant time string comparison.
444
391
  #
@@ -447,7 +394,7 @@ module Rack
447
394
  # on variable length plaintext strings because it could leak length info
448
395
  # via timing attacks.
449
396
  def secure_compare(a, b)
450
- return false unless bytesize(a) == bytesize(b)
397
+ return false unless a.bytesize == b.bytesize
451
398
 
452
399
  l = a.unpack("C*")
453
400
 
@@ -496,6 +443,12 @@ module Rack
496
443
  hash.each { |k, v| self[k] = v }
497
444
  end
498
445
 
446
+ # on dup/clone, we need to duplicate @names hash
447
+ def initialize_copy(other)
448
+ super
449
+ @names = other.names.dup
450
+ end
451
+
499
452
  def each
500
453
  super do |k, v|
501
454
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -513,21 +466,20 @@ module Rack
513
466
  end
514
467
 
515
468
  def []=(k, v)
516
- canonical = k.downcase
469
+ canonical = k.downcase.freeze
517
470
  delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
518
- @names[k] = @names[canonical] = k
471
+ @names[canonical] = k
519
472
  super k, v
520
473
  end
521
474
 
522
475
  def delete(k)
523
476
  canonical = k.downcase
524
477
  result = super @names.delete(canonical)
525
- @names.delete_if { |name,| name.downcase == canonical }
526
478
  result
527
479
  end
528
480
 
529
481
  def include?(k)
530
- @names.include?(k) || @names.include?(k.downcase)
482
+ super || @names.include?(k.downcase)
531
483
  end
532
484
 
533
485
  alias_method :has_key?, :include?
@@ -549,45 +501,11 @@ module Rack
549
501
  other.each { |k, v| self[k] = v }
550
502
  self
551
503
  end
552
- end
553
-
554
- class KeySpaceConstrainedParams
555
- def initialize(limit = Utils.key_space_limit)
556
- @limit = limit
557
- @size = 0
558
- @params = {}
559
- end
560
-
561
- def [](key)
562
- @params[key]
563
- end
564
504
 
565
- def []=(key, value)
566
- @size += key.size if key && !@params.key?(key)
567
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
568
- @params[key] = value
569
- end
570
-
571
- def key?(key)
572
- @params.key?(key)
573
- end
574
-
575
- def to_params_hash
576
- hash = @params
577
- hash.keys.each do |key|
578
- value = hash[key]
579
- if value.kind_of?(self.class)
580
- if value.object_id == self.object_id
581
- hash[key] = hash
582
- else
583
- hash[key] = value.to_params_hash
584
- end
585
- elsif value.kind_of?(Array)
586
- value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
587
- end
505
+ protected
506
+ def names
507
+ @names
588
508
  end
589
- hash
590
- end
591
509
  end
592
510
 
593
511
  # Every standard HTTP code mapped to the appropriate message.
@@ -635,6 +553,7 @@ module Rack
635
553
  415 => 'Unsupported Media Type',
636
554
  416 => 'Range Not Satisfiable',
637
555
  417 => 'Expectation Failed',
556
+ 421 => 'Misdirected Request',
638
557
  422 => 'Unprocessable Entity',
639
558
  423 => 'Locked',
640
559
  424 => 'Failed Dependency',
@@ -642,6 +561,7 @@ module Rack
642
561
  428 => 'Precondition Required',
643
562
  429 => 'Too Many Requests',
644
563
  431 => 'Request Header Fields Too Large',
564
+ 451 => 'Unavailable for Legal Reasons',
645
565
  500 => 'Internal Server Error',
646
566
  501 => 'Not Implemented',
647
567
  502 => 'Bad Gateway',
@@ -656,7 +576,7 @@ module Rack
656
576
  }
657
577
 
658
578
  # Responses with HTTP status codes that should not have an entity body
659
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
579
+ STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
660
580
 
661
581
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
662
582
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -671,8 +591,6 @@ module Rack
671
591
  end
672
592
  module_function :status_code
673
593
 
674
- Multipart = Rack::Multipart
675
-
676
594
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
677
595
 
678
596
  def clean_path_info(path_info)
@@ -691,5 +609,12 @@ module Rack
691
609
  end
692
610
  module_function :clean_path_info
693
611
 
612
+ NULL_BYTE = "\0".freeze
613
+
614
+ def valid_path?(path)
615
+ path.valid_encoding? && !path.include?(NULL_BYTE)
616
+ end
617
+ module_function :valid_path?
618
+
694
619
  end
695
620
  end