rack 1.6.11 → 2.2.0

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