rack 1.0.1 → 2.2.21

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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +829 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +355 -0
  6. data/Rakefile +64 -98
  7. data/SPEC.rdoc +292 -0
  8. data/bin/rackup +3 -174
  9. data/contrib/rack.png +0 -0
  10. data/contrib/rack.svg +150 -0
  11. data/contrib/rack_logo.svg +164 -111
  12. data/contrib/rdoc.css +412 -0
  13. data/example/lobster.ru +2 -0
  14. data/example/protectedlobster.rb +4 -2
  15. data/example/protectedlobster.ru +3 -1
  16. data/lib/rack/auth/abstract/handler.rb +7 -5
  17. data/lib/rack/auth/abstract/request.rb +12 -2
  18. data/lib/rack/auth/basic.rb +6 -4
  19. data/lib/rack/auth/digest/md5.rb +20 -13
  20. data/lib/rack/auth/digest/nonce.rb +6 -4
  21. data/lib/rack/auth/digest/params.rb +11 -12
  22. data/lib/rack/auth/digest/request.rb +15 -12
  23. data/lib/rack/body_proxy.rb +45 -0
  24. data/lib/rack/builder.rb +213 -19
  25. data/lib/rack/cascade.rb +50 -18
  26. data/lib/rack/chunked.rb +95 -27
  27. data/lib/rack/common_logger.rb +84 -0
  28. data/lib/rack/conditional_get.rb +83 -0
  29. data/lib/rack/config.rb +22 -0
  30. data/lib/rack/content_length.rb +19 -10
  31. data/lib/rack/content_type.rb +12 -5
  32. data/lib/rack/core_ext/regexp.rb +14 -0
  33. data/lib/rack/deflater.rb +90 -42
  34. data/lib/rack/directory.rb +118 -72
  35. data/lib/rack/etag.rb +77 -0
  36. data/lib/rack/events.rb +153 -0
  37. data/lib/rack/file.rb +4 -85
  38. data/lib/rack/files.rb +218 -0
  39. data/lib/rack/handler/cgi.rb +22 -24
  40. data/lib/rack/handler/fastcgi.rb +34 -24
  41. data/lib/rack/handler/lsws.rb +16 -15
  42. data/lib/rack/handler/scgi.rb +38 -29
  43. data/lib/rack/handler/thin.rb +23 -7
  44. data/lib/rack/handler/webrick.rb +95 -37
  45. data/lib/rack/handler.rb +49 -14
  46. data/lib/rack/head.rb +19 -13
  47. data/lib/rack/lint.rb +466 -217
  48. data/lib/rack/lobster.rb +19 -14
  49. data/lib/rack/lock.rb +24 -8
  50. data/lib/rack/logger.rb +20 -0
  51. data/lib/rack/media_type.rb +53 -0
  52. data/lib/rack/method_override.rb +52 -0
  53. data/lib/rack/mime.rb +652 -171
  54. data/lib/rack/mock.rb +203 -64
  55. data/lib/rack/multipart/generator.rb +97 -0
  56. data/lib/rack/multipart/parser.rb +440 -0
  57. data/lib/rack/multipart/uploaded_file.rb +41 -0
  58. data/lib/rack/multipart.rb +64 -0
  59. data/lib/rack/null_logger.rb +39 -0
  60. data/lib/rack/query_parser.rb +266 -0
  61. data/lib/rack/recursive.rb +18 -11
  62. data/lib/rack/reloader.rb +13 -5
  63. data/lib/rack/request.rb +607 -191
  64. data/lib/rack/response.rb +245 -110
  65. data/lib/rack/rewindable_input.rb +15 -21
  66. data/lib/rack/runtime.rb +34 -0
  67. data/lib/rack/sendfile.rb +192 -0
  68. data/lib/rack/server.rb +466 -0
  69. data/lib/rack/session/abstract/id.rb +449 -68
  70. data/lib/rack/session/cookie.rb +159 -47
  71. data/lib/rack/session/memcache.rb +4 -103
  72. data/lib/rack/session/pool.rb +39 -49
  73. data/lib/rack/show_exceptions.rb +390 -0
  74. data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
  75. data/lib/rack/static.rb +164 -14
  76. data/lib/rack/tempfile_reaper.rb +22 -0
  77. data/lib/rack/urlmap.rb +60 -18
  78. data/lib/rack/utils.rb +508 -277
  79. data/lib/rack/version.rb +29 -0
  80. data/lib/rack.rb +85 -34
  81. data/rack.gemspec +43 -51
  82. metadata +119 -207
  83. data/KNOWN-ISSUES +0 -18
  84. data/RDOX +0 -428
  85. data/README +0 -364
  86. data/SPEC +0 -164
  87. data/lib/rack/adapter/camping.rb +0 -22
  88. data/lib/rack/auth/openid.rb +0 -480
  89. data/lib/rack/commonlogger.rb +0 -61
  90. data/lib/rack/conditionalget.rb +0 -47
  91. data/lib/rack/handler/evented_mongrel.rb +0 -8
  92. data/lib/rack/handler/mongrel.rb +0 -87
  93. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  94. data/lib/rack/methodoverride.rb +0 -27
  95. data/lib/rack/showexceptions.rb +0 -349
  96. data/test/cgi/lighttpd.conf +0 -20
  97. data/test/cgi/test +0 -9
  98. data/test/cgi/test.fcgi +0 -8
  99. data/test/cgi/test.ru +0 -7
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/empty +0 -10
  102. data/test/multipart/ie +0 -6
  103. data/test/multipart/nested +0 -10
  104. data/test/multipart/none +0 -9
  105. data/test/multipart/semicolon +0 -6
  106. data/test/multipart/text +0 -10
  107. data/test/spec_rack_auth_basic.rb +0 -73
  108. data/test/spec_rack_auth_digest.rb +0 -226
  109. data/test/spec_rack_auth_openid.rb +0 -84
  110. data/test/spec_rack_builder.rb +0 -84
  111. data/test/spec_rack_camping.rb +0 -51
  112. data/test/spec_rack_cascade.rb +0 -50
  113. data/test/spec_rack_cgi.rb +0 -89
  114. data/test/spec_rack_chunked.rb +0 -62
  115. data/test/spec_rack_commonlogger.rb +0 -32
  116. data/test/spec_rack_conditionalget.rb +0 -41
  117. data/test/spec_rack_content_length.rb +0 -43
  118. data/test/spec_rack_content_type.rb +0 -30
  119. data/test/spec_rack_deflater.rb +0 -127
  120. data/test/spec_rack_directory.rb +0 -61
  121. data/test/spec_rack_fastcgi.rb +0 -89
  122. data/test/spec_rack_file.rb +0 -75
  123. data/test/spec_rack_handler.rb +0 -43
  124. data/test/spec_rack_head.rb +0 -30
  125. data/test/spec_rack_lint.rb +0 -521
  126. data/test/spec_rack_lobster.rb +0 -45
  127. data/test/spec_rack_lock.rb +0 -38
  128. data/test/spec_rack_methodoverride.rb +0 -60
  129. data/test/spec_rack_mock.rb +0 -157
  130. data/test/spec_rack_mongrel.rb +0 -189
  131. data/test/spec_rack_recursive.rb +0 -77
  132. data/test/spec_rack_request.rb +0 -504
  133. data/test/spec_rack_response.rb +0 -218
  134. data/test/spec_rack_rewindable_input.rb +0 -118
  135. data/test/spec_rack_session_cookie.rb +0 -82
  136. data/test/spec_rack_session_memcache.rb +0 -240
  137. data/test/spec_rack_session_pool.rb +0 -172
  138. data/test/spec_rack_showexceptions.rb +0 -21
  139. data/test/spec_rack_showstatus.rb +0 -72
  140. data/test/spec_rack_static.rb +0 -37
  141. data/test/spec_rack_thin.rb +0 -91
  142. data/test/spec_rack_urlmap.rb +0 -185
  143. data/test/spec_rack_utils.rb +0 -387
  144. data/test/spec_rack_webrick.rb +0 -130
  145. data/test/testrequest.rb +0 -57
  146. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  147. 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
@@ -8,239 +8,655 @@ module Rack
8
8
  # req = Rack::Request.new(env)
9
9
  # req.post?
10
10
  # req.params["data"]
11
- #
12
- # The environment hash passed will store a reference to the Request object
13
- # instantiated so that it will only instantiate if an instance of the Request
14
- # object doesn't already exist.
15
11
 
16
12
  class Request
17
- # The environment of the request.
18
- attr_reader :env
13
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
19
14
 
20
- def initialize(env)
21
- @env = env
15
+ class << self
16
+ attr_accessor :ip_filter
22
17
  end
23
18
 
24
- def body; @env["rack.input"] end
25
- def scheme; @env["rack.url_scheme"] end
26
- def script_name; @env["SCRIPT_NAME"].to_s end
27
- def path_info; @env["PATH_INFO"].to_s end
28
- def port; @env["SERVER_PORT"].to_i end
29
- def request_method; @env["REQUEST_METHOD"] end
30
- def query_string; @env["QUERY_STRING"].to_s end
31
- def content_length; @env['CONTENT_LENGTH'] end
32
- def content_type; @env['CONTENT_TYPE'] end
33
- def session; @env['rack.session'] ||= {} end
34
- def session_options; @env['rack.session.options'] ||= {} end
35
-
36
- # The media type (type/subtype) portion of the CONTENT_TYPE header
37
- # without any media type parameters. e.g., when CONTENT_TYPE is
38
- # "text/plain;charset=utf-8", the media-type is "text/plain".
39
- #
40
- # For more information on the use of media types in HTTP, see:
41
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
42
- def media_type
43
- content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
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
44
24
  end
45
25
 
46
- # The media type parameters provided in CONTENT_TYPE as a Hash, or
47
- # an empty Hash if no CONTENT_TYPE or media-type parameters were
48
- # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
49
- # this method responds with the following Hash:
50
- # { 'charset' => 'utf-8' }
51
- def media_type_params
52
- return {} if content_type.nil?
53
- content_type.split(/\s*[;,]\s*/)[1..-1].
54
- collect { |s| s.split('=', 2) }.
55
- inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash }
26
+ def initialize(env)
27
+ @params = nil
28
+ super(env)
56
29
  end
57
30
 
58
- # The character set of the request body if a "charset" media type
59
- # parameter was given, or nil if no "charset" was specified. Note
60
- # that, per RFC2616, text/* media types that specify no explicit
61
- # charset are to be considered ISO-8859-1.
62
- def content_charset
63
- media_type_params['charset']
31
+ def params
32
+ @params ||= super
64
33
  end
65
34
 
66
- def host
67
- # Remove port number.
68
- (@env["HTTP_HOST"] || @env["SERVER_NAME"]).to_s.gsub(/:\d+\z/, '')
35
+ def update_param(k, v)
36
+ super
37
+ @params = nil
69
38
  end
70
39
 
71
- def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
72
- def path_info=(s); @env["PATH_INFO"] = s.to_s end
73
-
74
- def get?; request_method == "GET" end
75
- def post?; request_method == "POST" end
76
- def put?; request_method == "PUT" end
77
- def delete?; request_method == "DELETE" end
78
- def head?; request_method == "HEAD" end
79
-
80
- # The set of form-data media-types. Requests that do not indicate
81
- # one of the media types presents in this list will not be eligible
82
- # for form-data / param parsing.
83
- FORM_DATA_MEDIA_TYPES = [
84
- nil,
85
- 'application/x-www-form-urlencoded',
86
- 'multipart/form-data'
87
- ]
88
-
89
- # The set of media-types. Requests that do not indicate
90
- # one of the media types presents in this list will not be eligible
91
- # for param parsing like soap attachments or generic multiparts
92
- PARSEABLE_DATA_MEDIA_TYPES = [
93
- 'multipart/related',
94
- 'multipart/mixed'
95
- ]
96
-
97
- # Determine whether the request body contains form-data by checking
98
- # the request media_type against registered form-data media-types:
99
- # "application/x-www-form-urlencoded" and "multipart/form-data". The
100
- # list of form-data media types can be modified through the
101
- # +FORM_DATA_MEDIA_TYPES+ array.
102
- def form_data?
103
- FORM_DATA_MEDIA_TYPES.include?(media_type)
40
+ def delete_param(k)
41
+ v = super
42
+ @params = nil
43
+ v
104
44
  end
105
45
 
106
- # Determine whether the request body contains data by checking
107
- # the request media_type against registered parse-data media-types
108
- def parseable_data?
109
- PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
110
- end
46
+ module Env
47
+ # The environment of the request.
48
+ attr_reader :env
111
49
 
112
- # Returns the data recieved in the query string.
113
- def GET
114
- if @env["rack.request.query_string"] == query_string
115
- @env["rack.request.query_hash"]
116
- else
117
- @env["rack.request.query_string"] = query_string
118
- @env["rack.request.query_hash"] =
119
- Utils.parse_nested_query(query_string)
50
+ def initialize(env)
51
+ @env = env
52
+ super()
120
53
  end
121
- end
122
54
 
123
- # Returns the data recieved in the request body.
124
- #
125
- # This method support both application/x-www-form-urlencoded and
126
- # multipart/form-data.
127
- def POST
128
- if @env["rack.request.form_input"].eql? @env["rack.input"]
129
- @env["rack.request.form_hash"]
130
- elsif form_data? || parseable_data?
131
- @env["rack.request.form_input"] = @env["rack.input"]
132
- unless @env["rack.request.form_hash"] =
133
- Utils::Multipart.parse_multipart(env)
134
- form_vars = @env["rack.input"].read
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
60
+
61
+ # Get a request specific value for `name`.
62
+ def get_header(name)
63
+ @env[name]
64
+ end
65
+
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
135
71
 
136
- # Fix for Safari Ajax postings that always append \0
137
- form_vars.sub!(/\0\z/, '')
72
+ # Loops through each key / value pair in the request specific data.
73
+ def each_header(&block)
74
+ @env.each(&block)
75
+ end
138
76
 
139
- @env["rack.request.form_vars"] = form_vars
140
- @env["rack.request.form_hash"] = Utils.parse_nested_query(form_vars)
77
+ # Set a request specific value for `name` to `v`
78
+ def set_header(name, v)
79
+ @env[name] = v
80
+ end
141
81
 
142
- @env["rack.input"].rewind
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
143
98
  end
144
- @env["rack.request.form_hash"]
145
- else
146
- {}
147
99
  end
148
- end
149
100
 
150
- # The union of GET and POST data.
151
- def params
152
- self.put? ? self.GET : self.GET.update(self.POST)
153
- rescue EOFError => e
154
- self.GET
155
- end
101
+ # Delete a request specific value for `name`.
102
+ def delete_header(name)
103
+ @env.delete name
104
+ end
156
105
 
157
- # shortcut for request.params[key]
158
- def [](key)
159
- params[key.to_s]
106
+ def initialize_copy(other)
107
+ @env = other.env.dup
108
+ end
160
109
  end
161
110
 
162
- # shortcut for request.params[key] = value
163
- def []=(key, value)
164
- params[key.to_s] = value
165
- 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'
137
+
138
+ # The value of the scheme sent to the proxy.
139
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
140
+
141
+ # The protocol used to connect to the proxy.
142
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
143
+
144
+ # The port used to connect to the proxy.
145
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
146
+
147
+ # Another way for specifing https scheme was used.
148
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
149
+
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
153
+
154
+ def path_info; get_header(PATH_INFO).to_s end
155
+ def path_info=(s); set_header(PATH_INFO, s.to_s) end
156
+
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
163
+
164
+ # the referer of the client
165
+ def referer; get_header('HTTP_REFERER') end
166
+ alias referrer referer
167
+
168
+ def session
169
+ fetch_header(RACK_SESSION) do |k|
170
+ set_header RACK_SESSION, default_session
171
+ end
172
+ end
166
173
 
167
- # like Hash#values_at
168
- def values_at(*keys)
169
- keys.map{|key| params[key] }
170
- end
174
+ def session_options
175
+ fetch_header(RACK_SESSION_OPTIONS) do |k|
176
+ set_header RACK_SESSION_OPTIONS, {}
177
+ end
178
+ end
171
179
 
172
- # the referer of the client or '/'
173
- def referer
174
- @env['HTTP_REFERER'] || '/'
175
- end
176
- alias referrer referer
177
-
178
-
179
- def cookies
180
- return {} unless @env["HTTP_COOKIE"]
181
-
182
- if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
183
- @env["rack.request.cookie_hash"]
184
- else
185
- @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
186
- # According to RFC 2109:
187
- # If multiple cookies satisfy the criteria above, they are ordered in
188
- # the Cookie header such that those with more specific Path attributes
189
- # precede those with less specific. Ordering with respect to other
190
- # attributes (e.g., Domain) is unspecified.
191
- @env["rack.request.cookie_hash"] =
192
- Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)|
193
- h[k] = Array === v ? v.first : v
194
- h
195
- }
180
+ # Checks the HTTP request method (or verb) to see if it was of type DELETE
181
+ def delete?; request_method == DELETE end
182
+
183
+ # Checks the HTTP request method (or verb) to see if it was of type GET
184
+ def get?; request_method == GET end
185
+
186
+ # Checks the HTTP request method (or verb) to see if it was of type HEAD
187
+ def head?; request_method == HEAD end
188
+
189
+ # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
190
+ def options?; request_method == OPTIONS end
191
+
192
+ # Checks the HTTP request method (or verb) to see if it was of type LINK
193
+ def link?; request_method == LINK end
194
+
195
+ # Checks the HTTP request method (or verb) to see if it was of type PATCH
196
+ def patch?; request_method == PATCH end
197
+
198
+ # Checks the HTTP request method (or verb) to see if it was of type POST
199
+ def post?; request_method == POST end
200
+
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
196
220
  end
197
- end
198
221
 
199
- def xhr?
200
- @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
201
- end
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
202
230
 
203
- # Tries to return a remake of the original request URL as a string.
204
- def url
205
- url = scheme + "://"
206
- url << host
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
243
+ end
244
+ end
207
245
 
208
- if scheme == "https" && port != 443 ||
209
- scheme == "http" && port != 80
210
- url << ":#{port}"
246
+ def server_name
247
+ get_header(SERVER_NAME)
211
248
  end
212
249
 
213
- url << fullpath
250
+ def server_port
251
+ if port = get_header(SERVER_PORT)
252
+ Integer(port)
253
+ end
254
+ end
214
255
 
215
- url
216
- end
217
-
218
- def path
219
- script_name + path_info
220
- end
221
-
222
- def fullpath
223
- query_string.empty? ? path : "#{path}?#{query_string}"
224
- end
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
269
+ end
270
+
271
+ def content_type
272
+ content_type = get_header('CONTENT_TYPE')
273
+ content_type.nil? || content_type.empty? ? nil : content_type
274
+ end
275
+
276
+ def xhr?
277
+ get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
278
+ end
225
279
 
226
- def accept_encoding
227
- @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
228
- m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
280
+ # The `HTTP_HOST` header.
281
+ def host_authority
282
+ get_header(HTTP_HOST)
283
+ end
284
+
285
+ def host_with_port(authority = self.authority)
286
+ host, _, port = split_authority(authority)
229
287
 
230
- if m
231
- [m[1], (m[2] || 1.0).to_f]
288
+ if port == DEFAULT_PORTS[self.scheme]
289
+ host
232
290
  else
233
- raise "Invalid value for Accept-Encoding: #{part.inspect}"
291
+ authority
234
292
  end
235
293
  end
236
- end
237
294
 
238
- def ip
239
- if addr = @env['HTTP_X_FORWARDED_FOR']
240
- addr.split(',').last.strip
241
- else
242
- @env['REMOTE_ADDR']
295
+ # Returns a formatted host, suitable for being used in a URI.
296
+ def host
297
+ split_authority(self.authority)[0]
298
+ end
299
+
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
307
+
308
+ def port
309
+ if authority = self.authority
310
+ _, _, port = split_authority(self.authority)
311
+
312
+ if port
313
+ return port
314
+ end
315
+ end
316
+
317
+ if forwarded_port = self.forwarded_port
318
+ return forwarded_port.first
319
+ end
320
+
321
+ if scheme = self.scheme
322
+ if port = DEFAULT_PORTS[self.scheme]
323
+ return port
324
+ end
325
+ end
326
+
327
+ self.server_port
328
+ end
329
+
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
337
+
338
+ def forwarded_port
339
+ if value = get_header(HTTP_X_FORWARDED_PORT)
340
+ split_header(value).map(&:to_i)
341
+ end
342
+ end
343
+
344
+ def forwarded_authority
345
+ if value = get_header(HTTP_X_FORWARDED_HOST)
346
+ wrap_ipv6(split_header(value).first)
347
+ end
348
+ end
349
+
350
+ def ssl?
351
+ scheme == 'https' || scheme == 'wss'
352
+ end
353
+
354
+ def ip
355
+ remote_addresses = split_header(get_header('REMOTE_ADDR'))
356
+ external_addresses = reject_trusted_ip_addresses(remote_addresses)
357
+
358
+ unless external_addresses.empty?
359
+ return external_addresses.first
360
+ end
361
+
362
+ if forwarded_for = self.forwarded_for
363
+ unless forwarded_for.empty?
364
+ # The forwarded for addresses are ordered: client, proxy1, proxy2.
365
+ # So we reject all the trusted addresses (proxy*) and return the
366
+ # last client. Or if we trust everyone, we just return the first
367
+ # address.
368
+ return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
369
+ end
370
+ end
371
+
372
+ # If all the addresses are trusted, and we aren't forwarded, just return
373
+ # the first remote address, which represents the source of the request.
374
+ remote_addresses.first
375
+ end
376
+
377
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
378
+ # without any media type parameters. e.g., when CONTENT_TYPE is
379
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
380
+ #
381
+ # For more information on the use of media types in HTTP, see:
382
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
383
+ def media_type
384
+ MediaType.type(content_type)
385
+ end
386
+
387
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
388
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
389
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
390
+ # this method responds with the following Hash:
391
+ # { 'charset' => 'utf-8' }
392
+ def media_type_params
393
+ MediaType.params(content_type)
394
+ end
395
+
396
+ # The character set of the request body if a "charset" media type
397
+ # parameter was given, or nil if no "charset" was specified. Note
398
+ # that, per RFC2616, text/* media types that specify no explicit
399
+ # charset are to be considered ISO-8859-1.
400
+ def content_charset
401
+ media_type_params['charset']
402
+ end
403
+
404
+ # Determine whether the request body contains form-data by checking
405
+ # the request Content-Type for one of the media-types:
406
+ # "application/x-www-form-urlencoded" or "multipart/form-data". The
407
+ # list of form-data media types can be modified through the
408
+ # +FORM_DATA_MEDIA_TYPES+ array.
409
+ #
410
+ # A request body is also assumed to contain form-data when no
411
+ # Content-Type header is provided and the request_method is POST.
412
+ def form_data?
413
+ type = media_type
414
+ meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
415
+
416
+ (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
417
+ end
418
+
419
+ # Determine whether the request body contains data by checking
420
+ # the request media_type against registered parse-data media-types
421
+ def parseable_data?
422
+ PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
423
+ end
424
+
425
+ # Returns the data received in the query string.
426
+ def GET
427
+ if get_header(RACK_REQUEST_QUERY_STRING) == query_string
428
+ get_header(RACK_REQUEST_QUERY_HASH)
429
+ else
430
+ query_hash = parse_query(query_string, '&;')
431
+ set_header(RACK_REQUEST_QUERY_STRING, query_string)
432
+ set_header(RACK_REQUEST_QUERY_HASH, query_hash)
433
+ end
434
+ end
435
+
436
+ # Returns the data received in the request body.
437
+ #
438
+ # This method support both application/x-www-form-urlencoded and
439
+ # multipart/form-data.
440
+ def POST
441
+ if get_header(RACK_INPUT).nil?
442
+ raise "Missing rack.input"
443
+ elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
444
+ get_header(RACK_REQUEST_FORM_HASH)
445
+ elsif form_data? || parseable_data?
446
+ unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
447
+ # Add 2 bytes. One to check whether it is over the limit, and a second
448
+ # in case the slice! call below removes the last byte
449
+ # If read returns nil, use the empty string
450
+ form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
451
+
452
+ # Fix for Safari Ajax postings that always append \0
453
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
454
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
455
+
456
+ set_header RACK_REQUEST_FORM_VARS, form_vars
457
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
458
+
459
+ get_header(RACK_INPUT).rewind
460
+ end
461
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
462
+ get_header RACK_REQUEST_FORM_HASH
463
+ else
464
+ {}
465
+ end
466
+ end
467
+
468
+ # The union of GET and POST data.
469
+ #
470
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
471
+ def params
472
+ self.GET.merge(self.POST)
473
+ end
474
+
475
+ # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
476
+ #
477
+ # 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.
478
+ #
479
+ # <tt>env['rack.input']</tt> is not touched.
480
+ def update_param(k, v)
481
+ found = false
482
+ if self.GET.has_key?(k)
483
+ found = true
484
+ self.GET[k] = v
485
+ end
486
+ if self.POST.has_key?(k)
487
+ found = true
488
+ self.POST[k] = v
489
+ end
490
+ unless found
491
+ self.GET[k] = v
492
+ end
493
+ end
494
+
495
+ # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
496
+ #
497
+ # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
498
+ #
499
+ # <tt>env['rack.input']</tt> is not touched.
500
+ def delete_param(k)
501
+ post_value, get_value = self.POST.delete(k), self.GET.delete(k)
502
+ post_value || get_value
503
+ end
504
+
505
+ def base_url
506
+ "#{scheme}://#{host_with_port}"
507
+ end
508
+
509
+ # Tries to return a remake of the original request URL as a string.
510
+ def url
511
+ base_url + fullpath
512
+ end
513
+
514
+ def path
515
+ script_name + path_info
516
+ end
517
+
518
+ def fullpath
519
+ query_string.empty? ? path : "#{path}?#{query_string}"
520
+ end
521
+
522
+ def accept_encoding
523
+ parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
524
+ end
525
+
526
+ def accept_language
527
+ parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
528
+ end
529
+
530
+ def trusted_proxy?(ip)
531
+ Rack::Request.ip_filter.call(ip)
532
+ end
533
+
534
+ # shortcut for <tt>request.params[key]</tt>
535
+ def [](key)
536
+ if $VERBOSE
537
+ warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
538
+ end
539
+
540
+ params[key.to_s]
541
+ end
542
+
543
+ # shortcut for <tt>request.params[key] = value</tt>
544
+ #
545
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
546
+ def []=(key, value)
547
+ if $VERBOSE
548
+ warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
549
+ end
550
+
551
+ params[key.to_s] = value
552
+ end
553
+
554
+ # like Hash#values_at
555
+ def values_at(*keys)
556
+ keys.map { |key| params[key] }
557
+ end
558
+
559
+ private
560
+
561
+ def default_session; {}; end
562
+
563
+ # Assist with compatibility when processing `X-Forwarded-For`.
564
+ def wrap_ipv6(host)
565
+ # Even thought IPv6 addresses should be wrapped in square brackets,
566
+ # sometimes this is not done in various legacy/underspecified headers.
567
+ # So we try to fix this situation for compatibility reasons.
568
+
569
+ # Try to detect IPv6 addresses which aren't escaped yet:
570
+ if !host.start_with?('[') && host.count(':') > 1
571
+ "[#{host}]"
572
+ else
573
+ host
574
+ end
575
+ end
576
+
577
+ def parse_http_accept_header(header)
578
+ header.to_s.split(",").each(&:strip!).map do |part|
579
+ attribute, parameters = part.split(";", 2).each(&:strip!)
580
+ quality = 1.0
581
+ if parameters and /\Aq=([\d.]+)/ =~ parameters
582
+ quality = $1.to_f
583
+ end
584
+ [attribute, quality]
585
+ end
586
+ end
587
+
588
+ def query_parser
589
+ Utils.default_query_parser
590
+ end
591
+
592
+ def parse_query(qs, d = '&')
593
+ query_parser.parse_nested_query(qs, d)
594
+ end
595
+
596
+ def parse_multipart
597
+ Rack::Multipart.extract_multipart(self, query_parser)
598
+ end
599
+
600
+ def split_header(value)
601
+ value ? value.strip.split(/[,\s]+/) : []
602
+ end
603
+
604
+ AUTHORITY = /^
605
+ # The host:
606
+ (?<host>
607
+ # An IPv6 address:
608
+ (\[(?<ip6>.*)\])
609
+ |
610
+ # An IPv4 address:
611
+ (?<ip4>[\d\.]+)
612
+ |
613
+ # A hostname:
614
+ (?<name>[a-zA-Z0-9\.\-_]+)
615
+ )
616
+ # The optional port:
617
+ (:(?<port>\d+))?
618
+ $/x
619
+
620
+ private_constant :AUTHORITY
621
+
622
+ def split_authority(authority)
623
+ if match = AUTHORITY.match(authority)
624
+ if address = match[:ip6]
625
+ return match[:host], address, match[:port]&.to_i
626
+ else
627
+ return match[:host], match[:host], match[:port]&.to_i
628
+ end
629
+ end
630
+
631
+ # Give up!
632
+ return authority, authority, nil
633
+ end
634
+
635
+ def reject_trusted_ip_addresses(ip_addresses)
636
+ ip_addresses.reject { |ip| trusted_proxy?(ip) }
637
+ end
638
+
639
+ def forwarded_scheme
640
+ allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
641
+ allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
642
+ end
643
+
644
+ def allowed_scheme(header)
645
+ header if ALLOWED_SCHEMES.include?(header)
646
+ end
647
+
648
+ def extract_proto_header(header)
649
+ if header
650
+ if (comma_index = header.index(','))
651
+ header[0, comma_index]
652
+ else
653
+ header
654
+ end
655
+ end
243
656
  end
244
657
  end
658
+
659
+ include Env
660
+ include Helpers
245
661
  end
246
662
  end