rack 1.6.13 → 2.0.0.alpha

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 (138) hide show
  1. checksums.yaml +5 -5
  2. data/HISTORY.md +139 -18
  3. data/README.rdoc +17 -25
  4. data/Rakefile +6 -14
  5. data/SPEC +8 -9
  6. data/contrib/rack_logo.svg +164 -111
  7. data/lib/rack.rb +70 -21
  8. data/lib/rack/auth/digest/request.rb +1 -1
  9. data/lib/rack/body_proxy.rb +14 -9
  10. data/lib/rack/builder.rb +3 -3
  11. data/lib/rack/chunked.rb +5 -5
  12. data/lib/rack/{commonlogger.rb → common_logger.rb} +2 -2
  13. data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
  14. data/lib/rack/content_length.rb +2 -2
  15. data/lib/rack/deflater.rb +4 -4
  16. data/lib/rack/directory.rb +49 -55
  17. data/lib/rack/etag.rb +2 -1
  18. data/lib/rack/events.rb +154 -0
  19. data/lib/rack/file.rb +55 -40
  20. data/lib/rack/handler.rb +2 -24
  21. data/lib/rack/handler/cgi.rb +15 -16
  22. data/lib/rack/handler/fastcgi.rb +13 -14
  23. data/lib/rack/handler/lsws.rb +11 -11
  24. data/lib/rack/handler/scgi.rb +15 -15
  25. data/lib/rack/handler/thin.rb +3 -0
  26. data/lib/rack/handler/webrick.rb +22 -24
  27. data/lib/rack/head.rb +15 -17
  28. data/lib/rack/lint.rb +38 -38
  29. data/lib/rack/lobster.rb +1 -1
  30. data/lib/rack/lock.rb +6 -10
  31. data/lib/rack/logger.rb +2 -2
  32. data/lib/rack/media_type.rb +38 -0
  33. data/lib/rack/{methodoverride.rb → method_override.rb} +4 -11
  34. data/lib/rack/mime.rb +18 -5
  35. data/lib/rack/mock.rb +35 -52
  36. data/lib/rack/multipart.rb +35 -6
  37. data/lib/rack/multipart/generator.rb +4 -4
  38. data/lib/rack/multipart/parser.rb +273 -158
  39. data/lib/rack/multipart/uploaded_file.rb +1 -2
  40. data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
  41. data/lib/rack/query_parser.rb +174 -0
  42. data/lib/rack/recursive.rb +8 -8
  43. data/lib/rack/reloader.rb +1 -2
  44. data/lib/rack/request.rb +370 -304
  45. data/lib/rack/response.rb +129 -56
  46. data/lib/rack/rewindable_input.rb +1 -12
  47. data/lib/rack/runtime.rb +10 -18
  48. data/lib/rack/sendfile.rb +5 -7
  49. data/lib/rack/server.rb +31 -25
  50. data/lib/rack/session/abstract/id.rb +93 -135
  51. data/lib/rack/session/cookie.rb +26 -28
  52. data/lib/rack/session/memcache.rb +8 -14
  53. data/lib/rack/session/pool.rb +14 -21
  54. data/lib/rack/show_exceptions.rb +386 -0
  55. data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
  56. data/lib/rack/static.rb +30 -5
  57. data/lib/rack/tempfile_reaper.rb +2 -2
  58. data/lib/rack/urlmap.rb +13 -14
  59. data/lib/rack/utils.rb +128 -221
  60. data/rack.gemspec +9 -5
  61. data/test/builder/an_underscore_app.rb +5 -0
  62. data/test/builder/options.ru +1 -1
  63. data/test/cgi/test.fcgi +1 -0
  64. data/test/cgi/test.gz +0 -0
  65. data/test/helper.rb +31 -0
  66. data/test/multipart/filename_with_encoded_words +7 -0
  67. data/test/multipart/{filename_with_null_byte → filename_with_single_quote} +1 -1
  68. data/test/multipart/quoted +15 -0
  69. data/test/multipart/rack-logo.png +0 -0
  70. data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
  71. data/test/spec_auth_basic.rb +20 -19
  72. data/test/spec_auth_digest.rb +47 -46
  73. data/test/spec_body_proxy.rb +27 -27
  74. data/test/spec_builder.rb +51 -41
  75. data/test/spec_cascade.rb +24 -22
  76. data/test/spec_cgi.rb +49 -67
  77. data/test/spec_chunked.rb +36 -34
  78. data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
  79. data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
  80. data/test/spec_config.rb +3 -2
  81. data/test/spec_content_length.rb +18 -17
  82. data/test/spec_content_type.rb +13 -12
  83. data/test/spec_deflater.rb +66 -40
  84. data/test/spec_directory.rb +72 -27
  85. data/test/spec_etag.rb +32 -31
  86. data/test/spec_events.rb +133 -0
  87. data/test/spec_fastcgi.rb +50 -72
  88. data/test/spec_file.rb +96 -77
  89. data/test/spec_handler.rb +19 -34
  90. data/test/spec_head.rb +15 -14
  91. data/test/spec_lint.rb +162 -197
  92. data/test/spec_lobster.rb +24 -23
  93. data/test/spec_lock.rb +69 -39
  94. data/test/spec_logger.rb +4 -3
  95. data/test/spec_media_type.rb +42 -0
  96. data/test/spec_method_override.rb +83 -0
  97. data/test/spec_mime.rb +19 -19
  98. data/test/spec_mock.rb +196 -151
  99. data/test/spec_multipart.rb +310 -202
  100. data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
  101. data/test/spec_recursive.rb +17 -14
  102. data/test/spec_request.rb +763 -607
  103. data/test/spec_response.rb +209 -156
  104. data/test/spec_rewindable_input.rb +50 -40
  105. data/test/spec_runtime.rb +11 -10
  106. data/test/spec_sendfile.rb +30 -35
  107. data/test/spec_server.rb +78 -52
  108. data/test/spec_session_abstract_id.rb +11 -33
  109. data/test/spec_session_cookie.rb +97 -65
  110. data/test/spec_session_memcache.rb +63 -101
  111. data/test/spec_session_pool.rb +48 -84
  112. data/test/spec_show_exceptions.rb +80 -0
  113. data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
  114. data/test/spec_static.rb +71 -32
  115. data/test/spec_tempfile_reaper.rb +11 -10
  116. data/test/spec_thin.rb +55 -50
  117. data/test/spec_urlmap.rb +79 -78
  118. data/test/spec_utils.rb +417 -345
  119. data/test/spec_version.rb +2 -8
  120. data/test/spec_webrick.rb +77 -67
  121. data/test/static/foo.html +1 -0
  122. data/test/testrequest.rb +1 -1
  123. data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
  124. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
  125. metadata +116 -71
  126. data/KNOWN-ISSUES +0 -44
  127. data/lib/rack/backports/uri/common_18.rb +0 -56
  128. data/lib/rack/backports/uri/common_192.rb +0 -52
  129. data/lib/rack/backports/uri/common_193.rb +0 -29
  130. data/lib/rack/handler/evented_mongrel.rb +0 -8
  131. data/lib/rack/handler/mongrel.rb +0 -106
  132. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  133. data/lib/rack/showexceptions.rb +0 -387
  134. data/lib/rack/utils/okjson.rb +0 -600
  135. data/test/spec_methodoverride.rb +0 -111
  136. data/test/spec_mongrel.rb +0 -182
  137. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  138. 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.insert(0, [: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,17 @@ 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
49
 
50
50
  @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)))
51
+ unless casecmp?(http_host, host) \
52
+ || casecmp?(server_name, host) \
53
+ || (!host && (casecmp?(http_host, server_name) ||
54
+ casecmp?(http_host, "#{server_name}:#{server_port}")))
55
55
  next
56
56
  end
57
57
 
@@ -60,8 +60,8 @@ module Rack
60
60
  rest = m[1]
61
61
  next unless !rest || rest.empty? || rest[0] == ?/
62
62
 
63
- env['SCRIPT_NAME'] = (script_name + location)
64
- env['PATH_INFO'] = rest
63
+ env[SCRIPT_NAME] = (script_name + location)
64
+ env[PATH_INFO] = rest
65
65
 
66
66
  return app.call(env)
67
67
  end
@@ -69,8 +69,8 @@ module Rack
69
69
  [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
70
70
 
71
71
  ensure
72
- env['PATH_INFO'] = path
73
- env['SCRIPT_NAME'] = script_name
72
+ env[PATH_INFO] = path
73
+ env[SCRIPT_NAME] = script_name
74
74
  end
75
75
 
76
76
  private
@@ -87,4 +87,3 @@ module Rack
87
87
  end
88
88
  end
89
89
  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)
95
-
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
62
+ self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
106
63
 
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
  #
@@ -311,45 +248,41 @@ module Rack
311
248
  rfc2822(value[:expires].clone.gmtime) if value[:expires]
312
249
  secure = "; secure" if value[:secure]
313
250
  httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
314
- same_site =
315
- case value[:same_site]
316
- when false, nil
317
- nil
318
- when :lax, 'Lax', :Lax
319
- '; SameSite=Lax'.freeze
320
- when true, :strict, 'Strict', :Strict
321
- '; SameSite=Strict'.freeze
322
- else
323
- raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
324
- end
251
+ first_party = "; First-Party" if value[:first_party]
325
252
  value = value[:value]
326
253
  end
327
254
  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
255
 
332
- case header["Set-Cookie"]
256
+ cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
257
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{first_party}"
258
+
259
+ case header
333
260
  when nil, ''
334
- header["Set-Cookie"] = cookie
261
+ cookie
335
262
  when String
336
- header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
263
+ [header, cookie].join("\n")
337
264
  when Array
338
- header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
265
+ (header + [cookie]).join("\n")
266
+ else
267
+ raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
339
268
  end
269
+ end
270
+ module_function :add_cookie_to_header
340
271
 
272
+ def set_cookie_header!(header, key, value)
273
+ header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
341
274
  nil
342
275
  end
343
276
  module_function :set_cookie_header!
344
277
 
345
- def delete_cookie_header!(header, key, value = {})
346
- case header["Set-Cookie"]
278
+ def make_delete_cookie_header(header, key, value)
279
+ case header
347
280
  when nil, ''
348
281
  cookies = []
349
282
  when String
350
- cookies = header["Set-Cookie"].split("\n")
283
+ cookies = header.split("\n")
351
284
  when Array
352
- cookies = header["Set-Cookie"]
285
+ cookies = header
353
286
  end
354
287
 
355
288
  cookies.reject! { |cookie|
@@ -362,29 +295,28 @@ module Rack
362
295
  end
363
296
  }
364
297
 
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))
298
+ cookies.join("\n")
299
+ end
300
+ module_function :make_delete_cookie_header
371
301
 
302
+ def delete_cookie_header!(header, key, value = {})
303
+ header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
372
304
  nil
373
305
  end
374
306
  module_function :delete_cookie_header!
375
307
 
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
308
+ # Adds a cookie that will *remove* a cookie from the client. Hence the
309
+ # strange method name.
310
+ def add_remove_cookie_to_header(header, key, value = {})
311
+ new_header = make_delete_cookie_header(header, key, value)
312
+
313
+ add_cookie_to_header(new_header, key,
314
+ {:value => '', :path => nil, :domain => nil,
315
+ :max_age => '0',
316
+ :expires => Time.at(0) }.merge(value))
317
+
386
318
  end
387
- module_function :bytesize
319
+ module_function :add_remove_cookie_to_header
388
320
 
389
321
  def rfc2822(time)
390
322
  time.rfc2822
@@ -411,8 +343,13 @@ module Rack
411
343
  # Returns nil if the header is missing or syntactically invalid.
412
344
  # Returns an empty array if none of the ranges are satisfiable.
413
345
  def byte_ranges(env, size)
346
+ warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
347
+ get_byte_ranges env['HTTP_RANGE'], size
348
+ end
349
+ module_function :byte_ranges
350
+
351
+ def get_byte_ranges(http_range, size)
414
352
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
415
- http_range = env['HTTP_RANGE']
416
353
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
417
354
  ranges = []
418
355
  $1.split(/,\s*/).each do |range_spec|
@@ -438,7 +375,7 @@ module Rack
438
375
  end
439
376
  ranges
440
377
  end
441
- module_function :byte_ranges
378
+ module_function :get_byte_ranges
442
379
 
443
380
  # Constant time string comparison.
444
381
  #
@@ -447,7 +384,7 @@ module Rack
447
384
  # on variable length plaintext strings because it could leak length info
448
385
  # via timing attacks.
449
386
  def secure_compare(a, b)
450
- return false unless bytesize(a) == bytesize(b)
387
+ return false unless a.bytesize == b.bytesize
451
388
 
452
389
  l = a.unpack("C*")
453
390
 
@@ -496,6 +433,12 @@ module Rack
496
433
  hash.each { |k, v| self[k] = v }
497
434
  end
498
435
 
436
+ # on dup/clone, we need to duplicate @names hash
437
+ def initialize_copy(other)
438
+ super
439
+ @names = other.names.dup
440
+ end
441
+
499
442
  def each
500
443
  super do |k, v|
501
444
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -513,21 +456,20 @@ module Rack
513
456
  end
514
457
 
515
458
  def []=(k, v)
516
- canonical = k.downcase
459
+ canonical = k.downcase.freeze
517
460
  delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
518
- @names[k] = @names[canonical] = k
461
+ @names[canonical] = k
519
462
  super k, v
520
463
  end
521
464
 
522
465
  def delete(k)
523
466
  canonical = k.downcase
524
467
  result = super @names.delete(canonical)
525
- @names.delete_if { |name,| name.downcase == canonical }
526
468
  result
527
469
  end
528
470
 
529
471
  def include?(k)
530
- @names.include?(k) || @names.include?(k.downcase)
472
+ super || @names.include?(k.downcase)
531
473
  end
532
474
 
533
475
  alias_method :has_key?, :include?
@@ -549,45 +491,11 @@ module Rack
549
491
  other.each { |k, v| self[k] = v }
550
492
  self
551
493
  end
552
- end
553
494
 
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
-
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
495
+ protected
496
+ def names
497
+ @names
588
498
  end
589
- hash
590
- end
591
499
  end
592
500
 
593
501
  # Every standard HTTP code mapped to the appropriate message.
@@ -635,6 +543,7 @@ module Rack
635
543
  415 => 'Unsupported Media Type',
636
544
  416 => 'Range Not Satisfiable',
637
545
  417 => 'Expectation Failed',
546
+ 421 => 'Misdirected Request',
638
547
  422 => 'Unprocessable Entity',
639
548
  423 => 'Locked',
640
549
  424 => 'Failed Dependency',
@@ -671,8 +580,6 @@ module Rack
671
580
  end
672
581
  module_function :status_code
673
582
 
674
- Multipart = Rack::Multipart
675
-
676
583
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
677
584
 
678
585
  def clean_path_info(path_info)