rack 1.6.13 → 2.1.4.3

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.
Files changed (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +105 -141
  5. data/Rakefile +27 -28
  6. data/SPEC +6 -7
  7. data/bin/rackup +1 -0
  8. data/contrib/rack_logo.svg +164 -111
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +4 -2
  11. data/example/protectedlobster.ru +3 -1
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +7 -1
  14. data/lib/rack/auth/basic.rb +4 -1
  15. data/lib/rack/auth/digest/md5.rb +9 -7
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +5 -4
  18. data/lib/rack/auth/digest/request.rb +3 -1
  19. data/lib/rack/body_proxy.rb +11 -9
  20. data/lib/rack/builder.rb +42 -18
  21. data/lib/rack/cascade.rb +6 -5
  22. data/lib/rack/chunked.rb +33 -10
  23. data/lib/rack/{commonlogger.rb → common_logger.rb} +14 -10
  24. data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +5 -3
  27. data/lib/rack/content_type.rb +3 -1
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +33 -53
  30. data/lib/rack/directory.rb +75 -60
  31. data/lib/rack/etag.rb +8 -5
  32. data/lib/rack/events.rb +156 -0
  33. data/lib/rack/file.rb +4 -149
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler/cgi.rb +18 -17
  36. data/lib/rack/handler/fastcgi.rb +17 -16
  37. data/lib/rack/handler/lsws.rb +14 -12
  38. data/lib/rack/handler/scgi.rb +22 -19
  39. data/lib/rack/handler/thin.rb +6 -1
  40. data/lib/rack/handler/webrick.rb +28 -28
  41. data/lib/rack/handler.rb +9 -26
  42. data/lib/rack/head.rb +17 -17
  43. data/lib/rack/lint.rb +55 -52
  44. data/lib/rack/lobster.rb +8 -6
  45. data/lib/rack/lock.rb +17 -10
  46. data/lib/rack/logger.rb +4 -2
  47. data/lib/rack/media_type.rb +43 -0
  48. data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
  49. data/lib/rack/mime.rb +27 -6
  50. data/lib/rack/mock.rb +101 -60
  51. data/lib/rack/multipart/generator.rb +11 -12
  52. data/lib/rack/multipart/parser.rb +292 -161
  53. data/lib/rack/multipart/uploaded_file.rb +3 -2
  54. data/lib/rack/multipart.rb +38 -8
  55. data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
  56. data/lib/rack/query_parser.rb +218 -0
  57. data/lib/rack/recursive.rb +11 -9
  58. data/lib/rack/reloader.rb +10 -4
  59. data/lib/rack/request.rb +447 -305
  60. data/lib/rack/response.rb +196 -83
  61. data/lib/rack/rewindable_input.rb +5 -14
  62. data/lib/rack/runtime.rb +12 -18
  63. data/lib/rack/sendfile.rb +19 -14
  64. data/lib/rack/server.rb +118 -41
  65. data/lib/rack/session/abstract/id.rb +139 -94
  66. data/lib/rack/session/cookie.rb +34 -26
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +12 -10
  69. data/lib/rack/show_exceptions.rb +392 -0
  70. data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
  71. data/lib/rack/static.rb +41 -11
  72. data/lib/rack/tempfile_reaper.rb +4 -2
  73. data/lib/rack/urlmap.rb +25 -15
  74. data/lib/rack/utils.rb +203 -277
  75. data/lib/rack.rb +76 -24
  76. data/rack.gemspec +25 -14
  77. metadata +62 -183
  78. data/HISTORY.md +0 -375
  79. data/KNOWN-ISSUES +0 -44
  80. data/lib/rack/backports/uri/common_18.rb +0 -56
  81. data/lib/rack/backports/uri/common_192.rb +0 -52
  82. data/lib/rack/backports/uri/common_193.rb +0 -29
  83. data/lib/rack/handler/evented_mongrel.rb +0 -8
  84. data/lib/rack/handler/mongrel.rb +0 -106
  85. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  86. data/lib/rack/showexceptions.rb +0 -387
  87. data/lib/rack/utils/okjson.rb +0 -600
  88. data/test/builder/anything.rb +0 -5
  89. data/test/builder/comment.ru +0 -4
  90. data/test/builder/end.ru +0 -5
  91. data/test/builder/line.ru +0 -1
  92. data/test/builder/options.ru +0 -2
  93. data/test/cgi/assets/folder/test.js +0 -1
  94. data/test/cgi/assets/fonts/font.eot +0 -1
  95. data/test/cgi/assets/images/image.png +0 -1
  96. data/test/cgi/assets/index.html +0 -1
  97. data/test/cgi/assets/javascripts/app.js +0 -1
  98. data/test/cgi/assets/stylesheets/app.css +0 -1
  99. data/test/cgi/lighttpd.conf +0 -26
  100. data/test/cgi/rackup_stub.rb +0 -6
  101. data/test/cgi/sample_rackup.ru +0 -5
  102. data/test/cgi/test +0 -9
  103. data/test/cgi/test+directory/test+file +0 -1
  104. data/test/cgi/test.fcgi +0 -8
  105. data/test/cgi/test.ru +0 -5
  106. data/test/gemloader.rb +0 -10
  107. data/test/multipart/bad_robots +0 -259
  108. data/test/multipart/binary +0 -0
  109. data/test/multipart/content_type_and_no_filename +0 -6
  110. data/test/multipart/empty +0 -10
  111. data/test/multipart/fail_16384_nofile +0 -814
  112. data/test/multipart/file1.txt +0 -1
  113. data/test/multipart/filename_and_modification_param +0 -7
  114. data/test/multipart/filename_and_no_name +0 -6
  115. data/test/multipart/filename_with_escaped_quotes +0 -6
  116. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  117. data/test/multipart/filename_with_null_byte +0 -7
  118. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  119. data/test/multipart/filename_with_unescaped_percentages +0 -6
  120. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  121. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  122. data/test/multipart/filename_with_unescaped_quotes +0 -6
  123. data/test/multipart/ie +0 -6
  124. data/test/multipart/invalid_character +0 -6
  125. data/test/multipart/mixed_files +0 -21
  126. data/test/multipart/nested +0 -10
  127. data/test/multipart/none +0 -9
  128. data/test/multipart/semicolon +0 -6
  129. data/test/multipart/text +0 -15
  130. data/test/multipart/three_files_three_fields +0 -31
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -81
  135. data/test/spec_auth_digest.rb +0 -259
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -223
  138. data/test/spec_cascade.rb +0 -61
  139. data/test/spec_cgi.rb +0 -102
  140. data/test/spec_chunked.rb +0 -101
  141. data/test/spec_commonlogger.rb +0 -93
  142. data/test/spec_conditionalget.rb +0 -102
  143. data/test/spec_config.rb +0 -22
  144. data/test/spec_content_length.rb +0 -85
  145. data/test/spec_content_type.rb +0 -45
  146. data/test/spec_deflater.rb +0 -339
  147. data/test/spec_directory.rb +0 -88
  148. data/test/spec_etag.rb +0 -107
  149. data/test/spec_fastcgi.rb +0 -107
  150. data/test/spec_file.rb +0 -221
  151. data/test/spec_handler.rb +0 -72
  152. data/test/spec_head.rb +0 -45
  153. data/test/spec_lint.rb +0 -550
  154. data/test/spec_lobster.rb +0 -58
  155. data/test/spec_lock.rb +0 -164
  156. data/test/spec_logger.rb +0 -23
  157. data/test/spec_methodoverride.rb +0 -111
  158. data/test/spec_mime.rb +0 -51
  159. data/test/spec_mock.rb +0 -297
  160. data/test/spec_mongrel.rb +0 -182
  161. data/test/spec_multipart.rb +0 -600
  162. data/test/spec_nulllogger.rb +0 -20
  163. data/test/spec_recursive.rb +0 -72
  164. data/test/spec_request.rb +0 -1232
  165. data/test/spec_response.rb +0 -407
  166. data/test/spec_rewindable_input.rb +0 -118
  167. data/test/spec_runtime.rb +0 -49
  168. data/test/spec_sendfile.rb +0 -130
  169. data/test/spec_server.rb +0 -167
  170. data/test/spec_session_abstract_id.rb +0 -53
  171. data/test/spec_session_cookie.rb +0 -410
  172. data/test/spec_session_memcache.rb +0 -358
  173. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  174. data/test/spec_session_pool.rb +0 -246
  175. data/test/spec_showexceptions.rb +0 -98
  176. data/test/spec_showstatus.rb +0 -103
  177. data/test/spec_static.rb +0 -145
  178. data/test/spec_tempfile_reaper.rb +0 -63
  179. data/test/spec_thin.rb +0 -91
  180. data/test/spec_urlmap.rb +0 -236
  181. data/test/spec_utils.rb +0 -647
  182. data/test/spec_version.rb +0 -17
  183. data/test/spec_webrick.rb +0 -184
  184. data/test/static/another/index.html +0 -1
  185. data/test/static/index.html +0 -1
  186. data/test/testrequest.rb +0 -78
  187. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  188. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/request.rb CHANGED
@@ -1,4 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
4
+ require 'rack/media_type'
5
+
6
+ require_relative 'core_ext/regexp'
2
7
 
3
8
  module Rack
4
9
  # Rack::Request provides a convenient interface to a Rack
@@ -10,371 +15,462 @@ module Rack
10
15
  # req.params["data"]
11
16
 
12
17
  class Request
13
- # The environment of the request.
14
- attr_reader :env
15
-
16
- SCHEME_WHITELIST = %w(https http).freeze
18
+ using ::Rack::RegexpExtensions
17
19
 
18
- def initialize(env)
19
- @env = env
20
+ class << self
21
+ attr_accessor :ip_filter
20
22
  end
21
23
 
22
- def body; @env["rack.input"] end
23
- def script_name; @env[SCRIPT_NAME].to_s end
24
- def path_info; @env[PATH_INFO].to_s end
25
- def request_method; @env["REQUEST_METHOD"] end
26
- def query_string; @env[QUERY_STRING].to_s end
27
- def content_length; @env['CONTENT_LENGTH'] end
24
+ self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
25
+ ALLOWED_SCHEMES = %w(https http).freeze
26
+ SCHEME_WHITELIST = ALLOWED_SCHEMES
27
+ if Object.respond_to?(:deprecate_constant)
28
+ deprecate_constant :SCHEME_WHITELIST
29
+ end
28
30
 
29
- def content_type
30
- content_type = @env['CONTENT_TYPE']
31
- content_type.nil? || content_type.empty? ? nil : content_type
31
+ def initialize(env)
32
+ @params = nil
33
+ super(env)
32
34
  end
33
35
 
34
- def session; @env['rack.session'] ||= {} end
35
- def session_options; @env['rack.session.options'] ||= {} end
36
- def logger; @env['rack.logger'] end
37
-
38
- # The media type (type/subtype) portion of the CONTENT_TYPE header
39
- # without any media type parameters. e.g., when CONTENT_TYPE is
40
- # "text/plain;charset=utf-8", the media-type is "text/plain".
41
- #
42
- # For more information on the use of media types in HTTP, see:
43
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
44
- def media_type
45
- content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
36
+ def params
37
+ @params ||= super
46
38
  end
47
39
 
48
- # The media type parameters provided in CONTENT_TYPE as a Hash, or
49
- # an empty Hash if no CONTENT_TYPE or media-type parameters were
50
- # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
51
- # this method responds with the following Hash:
52
- # { 'charset' => 'utf-8' }
53
- def media_type_params
54
- return {} if content_type.nil?
55
- Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
56
- collect { |s| s.split('=', 2) }.
57
- map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
40
+ def update_param(k, v)
41
+ super
42
+ @params = nil
58
43
  end
59
44
 
60
- # The character set of the request body if a "charset" media type
61
- # parameter was given, or nil if no "charset" was specified. Note
62
- # that, per RFC2616, text/* media types that specify no explicit
63
- # charset are to be considered ISO-8859-1.
64
- def content_charset
65
- media_type_params['charset']
45
+ def delete_param(k)
46
+ v = super
47
+ @params = nil
48
+ v
66
49
  end
67
50
 
68
- def scheme
69
- if @env['HTTPS'] == 'on'
70
- 'https'
71
- elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
72
- 'https'
73
- elsif forwarded_scheme
74
- forwarded_scheme
75
- else
76
- @env["rack.url_scheme"]
51
+ module Env
52
+ # The environment of the request.
53
+ attr_reader :env
54
+
55
+ def initialize(env)
56
+ @env = env
57
+ super()
77
58
  end
78
- end
79
59
 
80
- def ssl?
81
- scheme == 'https'
82
- end
60
+ # Predicate method to test to see if `name` has been set as request
61
+ # specific data
62
+ def has_header?(name)
63
+ @env.key? name
64
+ end
83
65
 
84
- def host_with_port
85
- if forwarded = @env["HTTP_X_FORWARDED_HOST"]
86
- forwarded.split(/,\s?/).last
87
- else
88
- @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
66
+ # Get a request specific value for `name`.
67
+ def get_header(name)
68
+ @env[name]
89
69
  end
90
- end
91
70
 
92
- def port
93
- if port = host_with_port.split(/:/)[1]
94
- port.to_i
95
- elsif port = @env['HTTP_X_FORWARDED_PORT']
96
- port.to_i
97
- elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
98
- DEFAULT_PORTS[scheme]
99
- elsif @env.has_key?("HTTP_X_FORWARDED_PROTO")
100
- DEFAULT_PORTS[@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]]
101
- else
102
- @env["SERVER_PORT"].to_i
71
+ # If a block is given, it yields to the block if the value hasn't been set
72
+ # on the request.
73
+ def fetch_header(name, &block)
74
+ @env.fetch(name, &block)
103
75
  end
104
- end
105
76
 
106
- def host
107
- # Remove port number.
108
- host_with_port.to_s.sub(/:\d+\z/, '')
109
- end
77
+ # Loops through each key / value pair in the request specific data.
78
+ def each_header(&block)
79
+ @env.each(&block)
80
+ end
110
81
 
111
- def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
112
- def path_info=(s); @env["PATH_INFO"] = s.to_s end
82
+ # Set a request specific value for `name` to `v`
83
+ def set_header(name, v)
84
+ @env[name] = v
85
+ end
113
86
 
87
+ # Add a header that may have multiple values.
88
+ #
89
+ # Example:
90
+ # request.add_header 'Accept', 'image/png'
91
+ # request.add_header 'Accept', '*/*'
92
+ #
93
+ # assert_equal 'image/png,*/*', request.get_header('Accept')
94
+ #
95
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
96
+ def add_header key, v
97
+ if v.nil?
98
+ get_header key
99
+ elsif has_header? key
100
+ set_header key, "#{get_header key},#{v}"
101
+ else
102
+ set_header key, v
103
+ end
104
+ end
114
105
 
115
- # Checks the HTTP request method (or verb) to see if it was of type DELETE
116
- def delete?; request_method == "DELETE" end
106
+ # Delete a request specific value for `name`.
107
+ def delete_header(name)
108
+ @env.delete name
109
+ end
117
110
 
118
- # Checks the HTTP request method (or verb) to see if it was of type GET
119
- def get?; request_method == GET end
111
+ def initialize_copy(other)
112
+ @env = other.env.dup
113
+ end
114
+ end
120
115
 
121
- # Checks the HTTP request method (or verb) to see if it was of type HEAD
122
- def head?; request_method == HEAD end
116
+ module Helpers
117
+ # The set of form-data media-types. Requests that do not indicate
118
+ # one of the media types present in this list will not be eligible
119
+ # for form-data / param parsing.
120
+ FORM_DATA_MEDIA_TYPES = [
121
+ 'application/x-www-form-urlencoded',
122
+ 'multipart/form-data'
123
+ ]
123
124
 
124
- # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
125
- def options?; request_method == "OPTIONS" end
125
+ # The set of media-types. Requests that do not indicate
126
+ # one of the media types present in this list will not be eligible
127
+ # for param parsing like soap attachments or generic multiparts
128
+ PARSEABLE_DATA_MEDIA_TYPES = [
129
+ 'multipart/related',
130
+ 'multipart/mixed'
131
+ ]
126
132
 
127
- # Checks the HTTP request method (or verb) to see if it was of type LINK
128
- def link?; request_method == "LINK" end
133
+ # Default ports depending on scheme. Used to decide whether or not
134
+ # to include the port in a generated URI.
135
+ DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
136
+
137
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
138
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
139
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
140
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
141
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
142
+
143
+ def body; get_header(RACK_INPUT) end
144
+ def script_name; get_header(SCRIPT_NAME).to_s end
145
+ def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
146
+
147
+ def path_info; get_header(PATH_INFO).to_s end
148
+ def path_info=(s); set_header(PATH_INFO, s.to_s) end
149
+
150
+ def request_method; get_header(REQUEST_METHOD) end
151
+ def query_string; get_header(QUERY_STRING).to_s end
152
+ def content_length; get_header('CONTENT_LENGTH') end
153
+ def logger; get_header(RACK_LOGGER) end
154
+ def user_agent; get_header('HTTP_USER_AGENT') end
155
+ def multithread?; get_header(RACK_MULTITHREAD) end
156
+
157
+ # the referer of the client
158
+ def referer; get_header('HTTP_REFERER') end
159
+ alias referrer referer
160
+
161
+ def session
162
+ fetch_header(RACK_SESSION) do |k|
163
+ set_header RACK_SESSION, default_session
164
+ end
165
+ end
129
166
 
130
- # Checks the HTTP request method (or verb) to see if it was of type PATCH
131
- def patch?; request_method == "PATCH" end
167
+ def session_options
168
+ fetch_header(RACK_SESSION_OPTIONS) do |k|
169
+ set_header RACK_SESSION_OPTIONS, {}
170
+ end
171
+ end
132
172
 
133
- # Checks the HTTP request method (or verb) to see if it was of type POST
134
- def post?; request_method == "POST" end
173
+ # Checks the HTTP request method (or verb) to see if it was of type DELETE
174
+ def delete?; request_method == DELETE end
135
175
 
136
- # Checks the HTTP request method (or verb) to see if it was of type PUT
137
- def put?; request_method == "PUT" end
176
+ # Checks the HTTP request method (or verb) to see if it was of type GET
177
+ def get?; request_method == GET end
138
178
 
139
- # Checks the HTTP request method (or verb) to see if it was of type TRACE
140
- def trace?; request_method == "TRACE" end
179
+ # Checks the HTTP request method (or verb) to see if it was of type HEAD
180
+ def head?; request_method == HEAD end
141
181
 
142
- # Checks the HTTP request method (or verb) to see if it was of type UNLINK
143
- def unlink?; request_method == "UNLINK" end
182
+ # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
183
+ def options?; request_method == OPTIONS end
144
184
 
185
+ # Checks the HTTP request method (or verb) to see if it was of type LINK
186
+ def link?; request_method == LINK end
145
187
 
146
- # The set of form-data media-types. Requests that do not indicate
147
- # one of the media types presents in this list will not be eligible
148
- # for form-data / param parsing.
149
- FORM_DATA_MEDIA_TYPES = [
150
- 'application/x-www-form-urlencoded',
151
- 'multipart/form-data'
152
- ]
188
+ # Checks the HTTP request method (or verb) to see if it was of type PATCH
189
+ def patch?; request_method == PATCH end
153
190
 
154
- # The set of media-types. Requests that do not indicate
155
- # one of the media types presents in this list will not be eligible
156
- # for param parsing like soap attachments or generic multiparts
157
- PARSEABLE_DATA_MEDIA_TYPES = [
158
- 'multipart/related',
159
- 'multipart/mixed'
160
- ]
191
+ # Checks the HTTP request method (or verb) to see if it was of type POST
192
+ def post?; request_method == POST end
161
193
 
162
- # Default ports depending on scheme. Used to decide whether or not
163
- # to include the port in a generated URI.
164
- DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
194
+ # Checks the HTTP request method (or verb) to see if it was of type PUT
195
+ def put?; request_method == PUT end
165
196
 
166
- # Determine whether the request body contains form-data by checking
167
- # the request Content-Type for one of the media-types:
168
- # "application/x-www-form-urlencoded" or "multipart/form-data". The
169
- # list of form-data media types can be modified through the
170
- # +FORM_DATA_MEDIA_TYPES+ array.
171
- #
172
- # A request body is also assumed to contain form-data when no
173
- # Content-Type header is provided and the request_method is POST.
174
- def form_data?
175
- type = media_type
176
- meth = env["rack.methodoverride.original_method"] || env[REQUEST_METHOD]
177
- (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
178
- end
197
+ # Checks the HTTP request method (or verb) to see if it was of type TRACE
198
+ def trace?; request_method == TRACE end
179
199
 
180
- # Determine whether the request body contains data by checking
181
- # the request media_type against registered parse-data media-types
182
- def parseable_data?
183
- PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
184
- end
200
+ # Checks the HTTP request method (or verb) to see if it was of type UNLINK
201
+ def unlink?; request_method == UNLINK end
185
202
 
186
- # Returns the data received in the query string.
187
- def GET
188
- if @env["rack.request.query_string"] == query_string
189
- @env["rack.request.query_hash"]
190
- else
191
- p = parse_query({ :query => query_string, :separator => '&;' })
192
- @env["rack.request.query_string"] = query_string
193
- @env["rack.request.query_hash"] = p
203
+ def scheme
204
+ if get_header(HTTPS) == 'on'
205
+ 'https'
206
+ elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
207
+ 'https'
208
+ elsif forwarded_scheme
209
+ forwarded_scheme
210
+ else
211
+ get_header(RACK_URL_SCHEME)
212
+ end
194
213
  end
195
- end
196
214
 
197
- # Returns the data received in the request body.
198
- #
199
- # This method support both application/x-www-form-urlencoded and
200
- # multipart/form-data.
201
- def POST
202
- if @env["rack.input"].nil?
203
- raise "Missing rack.input"
204
- elsif @env["rack.request.form_input"].equal? @env["rack.input"]
205
- @env["rack.request.form_hash"]
206
- elsif form_data? || parseable_data?
207
- unless @env["rack.request.form_hash"] = parse_multipart(env)
208
- form_vars = @env["rack.input"].read
209
-
210
- # Fix for Safari Ajax postings that always append \0
211
- # form_vars.sub!(/\0\z/, '') # performance replacement:
212
- form_vars.slice!(-1) if form_vars[-1] == ?\0
213
-
214
- @env["rack.request.form_vars"] = form_vars
215
- @env["rack.request.form_hash"] = parse_query({ :query => form_vars, :separator => '&' })
216
-
217
- @env["rack.input"].rewind
215
+ def authority
216
+ get_header(SERVER_NAME) + ':' + get_header(SERVER_PORT)
217
+ end
218
+
219
+ def cookies
220
+ hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k|
221
+ set_header(k, {})
218
222
  end
219
- @env["rack.request.form_input"] = @env["rack.input"]
220
- @env["rack.request.form_hash"]
221
- else
222
- {}
223
+ string = get_header HTTP_COOKIE
224
+
225
+ return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
226
+ hash.replace Utils.parse_cookies_header string
227
+ set_header(RACK_REQUEST_COOKIE_STRING, string)
228
+ hash
223
229
  end
224
- end
225
230
 
226
- # The union of GET and POST data.
227
- #
228
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
229
- def params
230
- @params ||= self.GET.merge(self.POST)
231
- rescue EOFError
232
- self.GET.dup
233
- end
231
+ def content_type
232
+ content_type = get_header('CONTENT_TYPE')
233
+ content_type.nil? || content_type.empty? ? nil : content_type
234
+ end
234
235
 
235
- # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
236
- #
237
- # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
238
- #
239
- # env['rack.input'] is not touched.
240
- def update_param(k, v)
241
- found = false
242
- if self.GET.has_key?(k)
243
- found = true
244
- self.GET[k] = v
236
+ def xhr?
237
+ get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
245
238
  end
246
- if self.POST.has_key?(k)
247
- found = true
248
- self.POST[k] = v
239
+
240
+ def host_with_port
241
+ if forwarded = get_header(HTTP_X_FORWARDED_HOST)
242
+ forwarded.split(/,\s?/).last
243
+ else
244
+ get_header(HTTP_HOST) || "#{get_header(SERVER_NAME) || get_header(SERVER_ADDR)}:#{get_header(SERVER_PORT)}"
245
+ end
249
246
  end
250
- unless found
251
- self.GET[k] = v
247
+
248
+ def host
249
+ # Remove port number.
250
+ h = host_with_port
251
+ if colon_index = h.index(":")
252
+ h[0, colon_index]
253
+ else
254
+ h
255
+ end
252
256
  end
253
- @params = nil
254
- nil
255
- end
256
257
 
257
- # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
258
- #
259
- # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
260
- #
261
- # env['rack.input'] is not touched.
262
- def delete_param(k)
263
- v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first
264
- @params = nil
265
- v
266
- end
258
+ def port
259
+ if port = extract_port(host_with_port)
260
+ port.to_i
261
+ elsif port = get_header(HTTP_X_FORWARDED_PORT)
262
+ port.to_i
263
+ elsif has_header?(HTTP_X_FORWARDED_HOST)
264
+ DEFAULT_PORTS[scheme]
265
+ elsif has_header?(HTTP_X_FORWARDED_PROTO)
266
+ DEFAULT_PORTS[extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO))]
267
+ else
268
+ get_header(SERVER_PORT).to_i
269
+ end
270
+ end
267
271
 
268
- # shortcut for request.params[key]
269
- def [](key)
270
- params[key.to_s]
271
- end
272
+ def ssl?
273
+ scheme == 'https'
274
+ end
272
275
 
273
- # shortcut for request.params[key] = value
274
- #
275
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
276
- def []=(key, value)
277
- params[key.to_s] = value
278
- end
276
+ def ip
277
+ remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
278
+ remote_addrs = reject_trusted_ip_addresses(remote_addrs)
279
279
 
280
- # like Hash#values_at
281
- def values_at(*keys)
282
- keys.map{|key| params[key] }
283
- end
280
+ return remote_addrs.first if remote_addrs.any?
284
281
 
285
- # the referer of the client
286
- def referer
287
- @env['HTTP_REFERER']
288
- end
289
- alias referrer referer
282
+ forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
283
+ .map { |ip| strip_port(ip) }
290
284
 
291
- def user_agent
292
- @env['HTTP_USER_AGENT']
293
- end
285
+ return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
286
+ end
294
287
 
295
- def cookies
296
- hash = @env["rack.request.cookie_hash"] ||= {}
297
- string = @env["HTTP_COOKIE"]
298
-
299
- return hash if string == @env["rack.request.cookie_string"]
300
- hash.clear
301
-
302
- # According to RFC 2109:
303
- # If multiple cookies satisfy the criteria above, they are ordered in
304
- # the Cookie header such that those with more specific Path attributes
305
- # precede those with less specific. Ordering with respect to other
306
- # attributes (e.g., Domain) is unspecified.
307
- cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
308
- cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
309
- @env["rack.request.cookie_string"] = string
310
- hash
311
- end
288
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
289
+ # without any media type parameters. e.g., when CONTENT_TYPE is
290
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
291
+ #
292
+ # For more information on the use of media types in HTTP, see:
293
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
294
+ def media_type
295
+ MediaType.type(content_type)
296
+ end
312
297
 
313
- def xhr?
314
- @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
315
- end
298
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
299
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
300
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
301
+ # this method responds with the following Hash:
302
+ # { 'charset' => 'utf-8' }
303
+ def media_type_params
304
+ MediaType.params(content_type)
305
+ end
316
306
 
317
- def base_url
318
- url = "#{scheme}://#{host}"
319
- url << ":#{port}" if port != DEFAULT_PORTS[scheme]
320
- url
321
- end
307
+ # The character set of the request body if a "charset" media type
308
+ # parameter was given, or nil if no "charset" was specified. Note
309
+ # that, per RFC2616, text/* media types that specify no explicit
310
+ # charset are to be considered ISO-8859-1.
311
+ def content_charset
312
+ media_type_params['charset']
313
+ end
322
314
 
323
- # Tries to return a remake of the original request URL as a string.
324
- def url
325
- base_url + fullpath
326
- end
315
+ # Determine whether the request body contains form-data by checking
316
+ # the request Content-Type for one of the media-types:
317
+ # "application/x-www-form-urlencoded" or "multipart/form-data". The
318
+ # list of form-data media types can be modified through the
319
+ # +FORM_DATA_MEDIA_TYPES+ array.
320
+ #
321
+ # A request body is also assumed to contain form-data when no
322
+ # Content-Type header is provided and the request_method is POST.
323
+ def form_data?
324
+ type = media_type
325
+ meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
326
+ (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
327
+ end
327
328
 
328
- def path
329
- script_name + path_info
330
- end
329
+ # Determine whether the request body contains data by checking
330
+ # the request media_type against registered parse-data media-types
331
+ def parseable_data?
332
+ PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
333
+ end
331
334
 
332
- def fullpath
333
- query_string.empty? ? path : "#{path}?#{query_string}"
334
- end
335
+ # Returns the data received in the query string.
336
+ def GET
337
+ if get_header(RACK_REQUEST_QUERY_STRING) == query_string
338
+ get_header(RACK_REQUEST_QUERY_HASH)
339
+ else
340
+ query_hash = parse_query(query_string, '&;')
341
+ set_header(RACK_REQUEST_QUERY_STRING, query_string)
342
+ set_header(RACK_REQUEST_QUERY_HASH, query_hash)
343
+ end
344
+ end
335
345
 
336
- def accept_encoding
337
- parse_http_accept_header(@env["HTTP_ACCEPT_ENCODING"])
338
- end
346
+ # Returns the data received in the request body.
347
+ #
348
+ # This method support both application/x-www-form-urlencoded and
349
+ # multipart/form-data.
350
+ def POST
351
+ if get_header(RACK_INPUT).nil?
352
+ raise "Missing rack.input"
353
+ elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
354
+ get_header(RACK_REQUEST_FORM_HASH)
355
+ elsif form_data? || parseable_data?
356
+ unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
357
+ form_vars = get_header(RACK_INPUT).read
358
+
359
+ # Fix for Safari Ajax postings that always append \0
360
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
361
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
362
+
363
+ set_header RACK_REQUEST_FORM_VARS, form_vars
364
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
365
+
366
+ get_header(RACK_INPUT).rewind
367
+ end
368
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
369
+ get_header RACK_REQUEST_FORM_HASH
370
+ else
371
+ {}
372
+ end
373
+ end
339
374
 
340
- def accept_language
341
- parse_http_accept_header(@env["HTTP_ACCEPT_LANGUAGE"])
342
- end
375
+ # The union of GET and POST data.
376
+ #
377
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
378
+ def params
379
+ self.GET.merge(self.POST)
380
+ rescue EOFError
381
+ self.GET.dup
382
+ end
343
383
 
344
- def trusted_proxy?(ip)
345
- ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
346
- end
384
+ # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
385
+ #
386
+ # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
387
+ #
388
+ # <tt>env['rack.input']</tt> is not touched.
389
+ def update_param(k, v)
390
+ found = false
391
+ if self.GET.has_key?(k)
392
+ found = true
393
+ self.GET[k] = v
394
+ end
395
+ if self.POST.has_key?(k)
396
+ found = true
397
+ self.POST[k] = v
398
+ end
399
+ unless found
400
+ self.GET[k] = v
401
+ end
402
+ end
347
403
 
348
- def ip
349
- remote_addrs = split_ip_addresses(@env['REMOTE_ADDR'])
350
- remote_addrs = reject_trusted_ip_addresses(remote_addrs)
404
+ # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
405
+ #
406
+ # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
407
+ #
408
+ # <tt>env['rack.input']</tt> is not touched.
409
+ def delete_param(k)
410
+ post_value, get_value = self.POST.delete(k), self.GET.delete(k)
411
+ post_value || get_value
412
+ end
351
413
 
352
- return remote_addrs.first if remote_addrs.any?
414
+ def base_url
415
+ url = "#{scheme}://#{host}"
416
+ url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
417
+ url
418
+ end
353
419
 
354
- forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])
420
+ # Tries to return a remake of the original request URL as a string.
421
+ def url
422
+ base_url + fullpath
423
+ end
355
424
 
356
- return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
357
- end
425
+ def path
426
+ script_name + path_info
427
+ end
358
428
 
359
- protected
360
- def split_ip_addresses(ip_addresses)
361
- ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
429
+ def fullpath
430
+ query_string.empty? ? path : "#{path}?#{query_string}"
362
431
  end
363
432
 
364
- def reject_trusted_ip_addresses(ip_addresses)
365
- ip_addresses.reject { |ip| trusted_proxy?(ip) }
433
+ def accept_encoding
434
+ parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
435
+ end
436
+
437
+ def accept_language
438
+ parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
439
+ end
440
+
441
+ def trusted_proxy?(ip)
442
+ Rack::Request.ip_filter.call(ip)
443
+ end
444
+
445
+ # shortcut for <tt>request.params[key]</tt>
446
+ def [](key)
447
+ if $VERBOSE
448
+ warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
449
+ end
450
+
451
+ params[key.to_s]
366
452
  end
367
453
 
368
- def parse_query(qs)
369
- d = '&'
370
- qs, d = qs[:query], qs[:separator] if Hash === qs
371
- Utils.parse_nested_query(qs, d)
454
+ # shortcut for <tt>request.params[key] = value</tt>
455
+ #
456
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
457
+ def []=(key, value)
458
+ if $VERBOSE
459
+ warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
460
+ end
461
+
462
+ params[key.to_s] = value
372
463
  end
373
464
 
374
- def parse_multipart(env)
375
- Rack::Multipart.parse_multipart(env)
465
+ # like Hash#values_at
466
+ def values_at(*keys)
467
+ keys.map { |key| params[key] }
376
468
  end
377
469
 
470
+ private
471
+
472
+ def default_session; {}; end
473
+
378
474
  def parse_http_accept_header(header)
379
475
  header.to_s.split(/\s*,\s*/).map do |part|
380
476
  attribute, parameters = part.split(/\s*;\s*/, 2)
@@ -386,26 +482,72 @@ module Rack
386
482
  end
387
483
  end
388
484
 
389
- private
390
- def strip_doublequotes(s)
391
- if s[0] == ?" && s[-1] == ?"
392
- s[1..-2]
393
- else
394
- s
485
+ def query_parser
486
+ Utils.default_query_parser
395
487
  end
396
- end
397
488
 
398
- def forwarded_scheme
399
- scheme_headers = [
400
- @env['HTTP_X_FORWARDED_SCHEME'],
401
- @env['HTTP_X_FORWARDED_PROTO'].to_s.split(',')[0]
402
- ]
489
+ def parse_query(qs, d = '&')
490
+ query_parser.parse_nested_query(qs, d)
491
+ end
492
+
493
+ def parse_multipart
494
+ Rack::Multipart.extract_multipart(self, query_parser)
495
+ end
496
+
497
+ def split_ip_addresses(ip_addresses)
498
+ ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
499
+ end
500
+
501
+ def strip_port(ip_address)
502
+ # IPv6 format with optional port: "[2001:db8:cafe::17]:47011"
503
+ # returns: "2001:db8:cafe::17"
504
+ sep_start = ip_address.index('[')
505
+ sep_end = ip_address.index(']')
506
+ if (sep_start && sep_end)
507
+ return ip_address[sep_start + 1, sep_end - 1]
508
+ end
403
509
 
404
- scheme_headers.each do |header|
405
- return header if SCHEME_WHITELIST.include?(header)
510
+ # IPv4 format with optional port: "192.0.2.43:47011"
511
+ # returns: "192.0.2.43"
512
+ sep = ip_address.index(':')
513
+ if (sep && ip_address.count(':') == 1)
514
+ return ip_address[0, sep]
515
+ end
516
+
517
+ ip_address
406
518
  end
407
519
 
408
- nil
520
+ def reject_trusted_ip_addresses(ip_addresses)
521
+ ip_addresses.reject { |ip| trusted_proxy?(ip) }
522
+ end
523
+
524
+ def forwarded_scheme
525
+ allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
526
+ allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
527
+ end
528
+
529
+ def allowed_scheme(header)
530
+ header if ALLOWED_SCHEMES.include?(header)
531
+ end
532
+
533
+ def extract_proto_header(header)
534
+ if header
535
+ if (comma_index = header.index(','))
536
+ header[0, comma_index]
537
+ else
538
+ header
539
+ end
540
+ end
541
+ end
542
+
543
+ def extract_port(uri)
544
+ if (colon_index = uri.index(':'))
545
+ uri[colon_index + 1, uri.length]
546
+ end
547
+ end
409
548
  end
549
+
550
+ include Env
551
+ include Helpers
410
552
  end
411
553
  end