edgar-rack 1.2.1

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 (138) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +401 -0
  4. data/Rakefile +101 -0
  5. data/SPEC +171 -0
  6. data/bin/rackup +4 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +81 -0
  12. data/lib/rack/auth/abstract/handler.rb +37 -0
  13. data/lib/rack/auth/abstract/request.rb +43 -0
  14. data/lib/rack/auth/basic.rb +58 -0
  15. data/lib/rack/auth/digest/md5.rb +124 -0
  16. data/lib/rack/auth/digest/nonce.rb +51 -0
  17. data/lib/rack/auth/digest/params.rb +53 -0
  18. data/lib/rack/auth/digest/request.rb +40 -0
  19. data/lib/rack/builder.rb +80 -0
  20. data/lib/rack/cascade.rb +41 -0
  21. data/lib/rack/chunked.rb +52 -0
  22. data/lib/rack/commonlogger.rb +49 -0
  23. data/lib/rack/conditionalget.rb +63 -0
  24. data/lib/rack/config.rb +15 -0
  25. data/lib/rack/content_length.rb +29 -0
  26. data/lib/rack/content_type.rb +23 -0
  27. data/lib/rack/deflater.rb +96 -0
  28. data/lib/rack/directory.rb +157 -0
  29. data/lib/rack/etag.rb +59 -0
  30. data/lib/rack/file.rb +118 -0
  31. data/lib/rack/handler.rb +88 -0
  32. data/lib/rack/handler/cgi.rb +61 -0
  33. data/lib/rack/handler/evented_mongrel.rb +8 -0
  34. data/lib/rack/handler/fastcgi.rb +90 -0
  35. data/lib/rack/handler/lsws.rb +61 -0
  36. data/lib/rack/handler/mongrel.rb +90 -0
  37. data/lib/rack/handler/scgi.rb +59 -0
  38. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  39. data/lib/rack/handler/thin.rb +17 -0
  40. data/lib/rack/handler/webrick.rb +73 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +567 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +44 -0
  45. data/lib/rack/logger.rb +18 -0
  46. data/lib/rack/methodoverride.rb +27 -0
  47. data/lib/rack/mime.rb +210 -0
  48. data/lib/rack/mock.rb +185 -0
  49. data/lib/rack/nulllogger.rb +18 -0
  50. data/lib/rack/recursive.rb +61 -0
  51. data/lib/rack/reloader.rb +109 -0
  52. data/lib/rack/request.rb +307 -0
  53. data/lib/rack/response.rb +151 -0
  54. data/lib/rack/rewindable_input.rb +104 -0
  55. data/lib/rack/runtime.rb +27 -0
  56. data/lib/rack/sendfile.rb +139 -0
  57. data/lib/rack/server.rb +289 -0
  58. data/lib/rack/session/abstract/id.rb +348 -0
  59. data/lib/rack/session/cookie.rb +152 -0
  60. data/lib/rack/session/memcache.rb +93 -0
  61. data/lib/rack/session/pool.rb +79 -0
  62. data/lib/rack/showexceptions.rb +378 -0
  63. data/lib/rack/showstatus.rb +113 -0
  64. data/lib/rack/static.rb +53 -0
  65. data/lib/rack/urlmap.rb +55 -0
  66. data/lib/rack/utils.rb +698 -0
  67. data/rack.gemspec +39 -0
  68. data/test/cgi/lighttpd.conf +25 -0
  69. data/test/cgi/rackup_stub.rb +6 -0
  70. data/test/cgi/sample_rackup.ru +5 -0
  71. data/test/cgi/test +9 -0
  72. data/test/cgi/test.fcgi +8 -0
  73. data/test/cgi/test.ru +5 -0
  74. data/test/gemloader.rb +6 -0
  75. data/test/multipart/bad_robots +259 -0
  76. data/test/multipart/binary +0 -0
  77. data/test/multipart/empty +10 -0
  78. data/test/multipart/fail_16384_nofile +814 -0
  79. data/test/multipart/file1.txt +1 -0
  80. data/test/multipart/filename_and_modification_param +7 -0
  81. data/test/multipart/filename_with_escaped_quotes +6 -0
  82. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  83. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  84. data/test/multipart/filename_with_unescaped_quotes +6 -0
  85. data/test/multipart/ie +6 -0
  86. data/test/multipart/nested +10 -0
  87. data/test/multipart/none +9 -0
  88. data/test/multipart/semicolon +6 -0
  89. data/test/multipart/text +15 -0
  90. data/test/rackup/config.ru +31 -0
  91. data/test/spec_auth_basic.rb +70 -0
  92. data/test/spec_auth_digest.rb +241 -0
  93. data/test/spec_builder.rb +123 -0
  94. data/test/spec_cascade.rb +45 -0
  95. data/test/spec_cgi.rb +102 -0
  96. data/test/spec_chunked.rb +60 -0
  97. data/test/spec_commonlogger.rb +56 -0
  98. data/test/spec_conditionalget.rb +86 -0
  99. data/test/spec_config.rb +23 -0
  100. data/test/spec_content_length.rb +36 -0
  101. data/test/spec_content_type.rb +29 -0
  102. data/test/spec_deflater.rb +125 -0
  103. data/test/spec_directory.rb +57 -0
  104. data/test/spec_etag.rb +75 -0
  105. data/test/spec_fastcgi.rb +107 -0
  106. data/test/spec_file.rb +92 -0
  107. data/test/spec_handler.rb +49 -0
  108. data/test/spec_head.rb +30 -0
  109. data/test/spec_lint.rb +515 -0
  110. data/test/spec_lobster.rb +43 -0
  111. data/test/spec_lock.rb +142 -0
  112. data/test/spec_logger.rb +28 -0
  113. data/test/spec_methodoverride.rb +58 -0
  114. data/test/spec_mock.rb +241 -0
  115. data/test/spec_mongrel.rb +182 -0
  116. data/test/spec_nulllogger.rb +12 -0
  117. data/test/spec_recursive.rb +69 -0
  118. data/test/spec_request.rb +774 -0
  119. data/test/spec_response.rb +245 -0
  120. data/test/spec_rewindable_input.rb +118 -0
  121. data/test/spec_runtime.rb +39 -0
  122. data/test/spec_sendfile.rb +83 -0
  123. data/test/spec_server.rb +8 -0
  124. data/test/spec_session_abstract_id.rb +43 -0
  125. data/test/spec_session_cookie.rb +171 -0
  126. data/test/spec_session_memcache.rb +289 -0
  127. data/test/spec_session_pool.rb +200 -0
  128. data/test/spec_showexceptions.rb +87 -0
  129. data/test/spec_showstatus.rb +79 -0
  130. data/test/spec_static.rb +48 -0
  131. data/test/spec_thin.rb +86 -0
  132. data/test/spec_urlmap.rb +213 -0
  133. data/test/spec_utils.rb +678 -0
  134. data/test/spec_webrick.rb +141 -0
  135. data/test/testrequest.rb +78 -0
  136. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  137. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  138. metadata +329 -0
@@ -0,0 +1,18 @@
1
+ module Rack
2
+ class NullLogger
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ env['rack.logger'] = self
9
+ @app.call(env)
10
+ end
11
+
12
+ def info(progname = nil, &block); end
13
+ def debug(progname = nil, &block); end
14
+ def warn(progname = nil, &block); end
15
+ def error(progname = nil, &block); end
16
+ def fatal(progname = nil, &block); end
17
+ end
18
+ end
@@ -0,0 +1,61 @@
1
+ require 'uri'
2
+
3
+ module Rack
4
+ # Rack::ForwardRequest gets caught by Rack::Recursive and redirects
5
+ # the current request to the app at +url+.
6
+ #
7
+ # raise ForwardRequest.new("/not-found")
8
+ #
9
+
10
+ class ForwardRequest < Exception
11
+ attr_reader :url, :env
12
+
13
+ def initialize(url, env={})
14
+ @url = URI(url)
15
+ @env = env
16
+
17
+ @env["PATH_INFO"] = @url.path
18
+ @env["QUERY_STRING"] = @url.query if @url.query
19
+ @env["HTTP_HOST"] = @url.host if @url.host
20
+ @env["HTTP_PORT"] = @url.port if @url.port
21
+ @env["rack.url_scheme"] = @url.scheme if @url.scheme
22
+
23
+ super "forwarding to #{url}"
24
+ end
25
+ end
26
+
27
+ # Rack::Recursive allows applications called down the chain to
28
+ # include data from other applications (by using
29
+ # <tt>rack['rack.recursive.include'][...]</tt> or raise a
30
+ # ForwardRequest to redirect internally.
31
+
32
+ class Recursive
33
+ def initialize(app)
34
+ @app = app
35
+ end
36
+
37
+ def call(env)
38
+ dup._call(env)
39
+ end
40
+
41
+ def _call(env)
42
+ @script_name = env["SCRIPT_NAME"]
43
+ @app.call(env.merge('rack.recursive.include' => method(:include)))
44
+ rescue ForwardRequest => req
45
+ call(env.merge(req.env))
46
+ end
47
+
48
+ def include(env, path)
49
+ unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ ||
50
+ path[@script_name.size].nil?)
51
+ raise ArgumentError, "can only include below #{@script_name}, not #{path}"
52
+ end
53
+
54
+ env = env.merge("PATH_INFO" => path, "SCRIPT_NAME" => @script_name,
55
+ "REQUEST_METHOD" => "GET",
56
+ "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "",
57
+ "rack.input" => StringIO.new(""))
58
+ @app.call(env)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,109 @@
1
+ # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
2
+ # Rack::Reloader is subject to the terms of an MIT-style license.
3
+ # See COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ require 'pathname'
6
+
7
+ module Rack
8
+
9
+ # High performant source reloader
10
+ #
11
+ # This class acts as Rack middleware.
12
+ #
13
+ # What makes it especially suited for use in a production environment is that
14
+ # any file will only be checked once and there will only be made one system
15
+ # call stat(2).
16
+ #
17
+ # Please note that this will not reload files in the background, it does so
18
+ # only when actively called.
19
+ #
20
+ # It is performing a check/reload cycle at the start of every request, but
21
+ # also respects a cool down time, during which nothing will be done.
22
+ class Reloader
23
+ def initialize(app, cooldown = 10, backend = Stat)
24
+ @app = app
25
+ @cooldown = cooldown
26
+ @last = (Time.now - cooldown)
27
+ @cache = {}
28
+ @mtimes = {}
29
+
30
+ extend backend
31
+ end
32
+
33
+ def call(env)
34
+ if @cooldown and Time.now > @last + @cooldown
35
+ if Thread.list.size > 1
36
+ Thread.exclusive{ reload! }
37
+ else
38
+ reload!
39
+ end
40
+
41
+ @last = Time.now
42
+ end
43
+
44
+ @app.call(env)
45
+ end
46
+
47
+ def reload!(stderr = $stderr)
48
+ rotation do |file, mtime|
49
+ previous_mtime = @mtimes[file] ||= mtime
50
+ safe_load(file, mtime, stderr) if mtime > previous_mtime
51
+ end
52
+ end
53
+
54
+ # A safe Kernel::load, issuing the hooks depending on the results
55
+ def safe_load(file, mtime, stderr = $stderr)
56
+ load(file)
57
+ stderr.puts "#{self.class}: reloaded `#{file}'"
58
+ file
59
+ rescue LoadError, SyntaxError => ex
60
+ stderr.puts ex
61
+ ensure
62
+ @mtimes[file] = mtime
63
+ end
64
+
65
+ module Stat
66
+ def rotation
67
+ files = [$0, *$LOADED_FEATURES].uniq
68
+ paths = ['./', *$LOAD_PATH].uniq
69
+
70
+ files.map{|file|
71
+ next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
72
+
73
+ found, stat = figure_path(file, paths)
74
+ next unless found && stat && mtime = stat.mtime
75
+
76
+ @cache[file] = found
77
+
78
+ yield(found, mtime)
79
+ }.compact
80
+ end
81
+
82
+ # Takes a relative or absolute +file+ name, a couple possible +paths+ that
83
+ # the +file+ might reside in. Returns the full path and File::Stat for the
84
+ # path.
85
+ def figure_path(file, paths)
86
+ found = @cache[file]
87
+ found = file if !found and Pathname.new(file).absolute?
88
+ found, stat = safe_stat(found)
89
+ return found, stat if found
90
+
91
+ paths.find do |possible_path|
92
+ path = ::File.join(possible_path, file)
93
+ found, stat = safe_stat(path)
94
+ return ::File.expand_path(found), stat if found
95
+ end
96
+
97
+ return false, false
98
+ end
99
+
100
+ def safe_stat(file)
101
+ return unless file
102
+ stat = ::File.stat(file)
103
+ return file, stat if stat.file?
104
+ rescue Errno::ENOENT, Errno::ENOTDIR
105
+ @cache.delete(file) and false
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,307 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack
4
+ # Rack::Request provides a convenient interface to a Rack
5
+ # environment. It is stateless, the environment +env+ passed to the
6
+ # constructor will be directly modified.
7
+ #
8
+ # req = Rack::Request.new(env)
9
+ # req.post?
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
+
16
+ class Request
17
+ # The environment of the request.
18
+ attr_reader :env
19
+
20
+ def initialize(env)
21
+ @env = env
22
+ end
23
+
24
+ def body; @env["rack.input"] end
25
+ def script_name; @env["SCRIPT_NAME"].to_s end
26
+ def path_info; @env["PATH_INFO"].to_s end
27
+ def request_method; @env["REQUEST_METHOD"] end
28
+ def query_string; @env["QUERY_STRING"].to_s end
29
+ def content_length; @env['CONTENT_LENGTH'] end
30
+
31
+ def content_type
32
+ content_type = @env['CONTENT_TYPE']
33
+ content_type.nil? || content_type.empty? ? nil : content_type
34
+ end
35
+
36
+ def session; @env['rack.session'] ||= {} end
37
+ def session_options; @env['rack.session.options'] ||= {} end
38
+ def logger; @env['rack.logger'] end
39
+
40
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
41
+ # without any media type parameters. e.g., when CONTENT_TYPE is
42
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
43
+ #
44
+ # For more information on the use of media types in HTTP, see:
45
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
46
+ def media_type
47
+ content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
48
+ end
49
+
50
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
51
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
52
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
53
+ # this method responds with the following Hash:
54
+ # { 'charset' => 'utf-8' }
55
+ def media_type_params
56
+ return {} if content_type.nil?
57
+ Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
58
+ collect { |s| s.split('=', 2) }.
59
+ map { |k,v| [k.downcase, v] }.flatten]
60
+ end
61
+
62
+ # The character set of the request body if a "charset" media type
63
+ # parameter was given, or nil if no "charset" was specified. Note
64
+ # that, per RFC2616, text/* media types that specify no explicit
65
+ # charset are to be considered ISO-8859-1.
66
+ def content_charset
67
+ media_type_params['charset']
68
+ end
69
+
70
+ def scheme
71
+ if @env['HTTPS'] == 'on'
72
+ 'https'
73
+ elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
74
+ 'https'
75
+ elsif @env['HTTP_X_FORWARDED_PROTO']
76
+ @env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
77
+ else
78
+ @env["rack.url_scheme"]
79
+ end
80
+ end
81
+
82
+ def ssl?
83
+ scheme == 'https'
84
+ end
85
+
86
+ def host_with_port
87
+ if forwarded = @env["HTTP_X_FORWARDED_HOST"]
88
+ forwarded.split(/,\s?/).last
89
+ else
90
+ @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
91
+ end
92
+ end
93
+
94
+ def port
95
+ if port = host_with_port.split(/:/)[1]
96
+ port.to_i
97
+ elsif port = @env['HTTP_X_FORWARDED_PORT']
98
+ port.to_i
99
+ elsif ssl?
100
+ 443
101
+ elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
102
+ 80
103
+ else
104
+ @env["SERVER_PORT"].to_i
105
+ end
106
+ end
107
+
108
+ def host
109
+ # Remove port number.
110
+ host_with_port.to_s.gsub(/:\d+\z/, '')
111
+ end
112
+
113
+ def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
114
+ def path_info=(s); @env["PATH_INFO"] = s.to_s end
115
+
116
+ def delete?; request_method == "DELETE" end
117
+ def get?; request_method == "GET" end
118
+ def head?; request_method == "HEAD" end
119
+ def options?; request_method == "OPTIONS" end
120
+ def post?; request_method == "POST" end
121
+ def put?; request_method == "PUT" end
122
+ def trace?; request_method == "TRACE" end
123
+
124
+ # The set of form-data media-types. Requests that do not indicate
125
+ # one of the media types presents in this list will not be eligible
126
+ # for form-data / param parsing.
127
+ FORM_DATA_MEDIA_TYPES = [
128
+ 'application/x-www-form-urlencoded',
129
+ 'multipart/form-data'
130
+ ]
131
+
132
+ # The set of media-types. Requests that do not indicate
133
+ # one of the media types presents in this list will not be eligible
134
+ # for param parsing like soap attachments or generic multiparts
135
+ PARSEABLE_DATA_MEDIA_TYPES = [
136
+ 'multipart/related',
137
+ 'multipart/mixed'
138
+ ]
139
+
140
+ # Determine whether the request body contains form-data by checking
141
+ # the request Content-Type for one of the media-types:
142
+ # "application/x-www-form-urlencoded" or "multipart/form-data". The
143
+ # list of form-data media types can be modified through the
144
+ # +FORM_DATA_MEDIA_TYPES+ array.
145
+ #
146
+ # A request body is also assumed to contain form-data when no
147
+ # Content-Type header is provided and the request_method is POST.
148
+ def form_data?
149
+ type = media_type
150
+ meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
151
+ (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
152
+ end
153
+
154
+ # Determine whether the request body contains data by checking
155
+ # the request media_type against registered parse-data media-types
156
+ def parseable_data?
157
+ PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
158
+ end
159
+
160
+ # Returns the data recieved in the query string.
161
+ def GET
162
+ if @env["rack.request.query_string"] == query_string
163
+ @env["rack.request.query_hash"]
164
+ else
165
+ @env["rack.request.query_string"] = query_string
166
+ @env["rack.request.query_hash"] = parse_query(query_string)
167
+ end
168
+ end
169
+
170
+ # Returns the data recieved in the request body.
171
+ #
172
+ # This method support both application/x-www-form-urlencoded and
173
+ # multipart/form-data.
174
+ def POST
175
+ if @env["rack.input"].nil?
176
+ raise "Missing rack.input"
177
+ elsif @env["rack.request.form_input"].eql? @env["rack.input"]
178
+ @env["rack.request.form_hash"]
179
+ elsif form_data? || parseable_data?
180
+ @env["rack.request.form_input"] = @env["rack.input"]
181
+ unless @env["rack.request.form_hash"] = parse_multipart(env)
182
+ form_vars = @env["rack.input"].read
183
+
184
+ # Fix for Safari Ajax postings that always append \0
185
+ form_vars.sub!(/\0\z/, '')
186
+
187
+ @env["rack.request.form_vars"] = form_vars
188
+ @env["rack.request.form_hash"] = parse_query(form_vars)
189
+
190
+ @env["rack.input"].rewind
191
+ end
192
+ @env["rack.request.form_hash"]
193
+ else
194
+ {}
195
+ end
196
+ end
197
+
198
+ # The union of GET and POST data.
199
+ def params
200
+ self.GET.update(self.POST)
201
+ rescue EOFError
202
+ self.GET
203
+ end
204
+
205
+ # shortcut for request.params[key]
206
+ def [](key)
207
+ params[key.to_s]
208
+ end
209
+
210
+ # shortcut for request.params[key] = value
211
+ def []=(key, value)
212
+ params[key.to_s] = value
213
+ end
214
+
215
+ # like Hash#values_at
216
+ def values_at(*keys)
217
+ keys.map{|key| params[key] }
218
+ end
219
+
220
+ # the referer of the client
221
+ def referer
222
+ @env['HTTP_REFERER']
223
+ end
224
+ alias referrer referer
225
+
226
+ def user_agent
227
+ @env['HTTP_USER_AGENT']
228
+ end
229
+
230
+ def cookies
231
+ return {} unless @env["HTTP_COOKIE"]
232
+
233
+ if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
234
+ @env["rack.request.cookie_hash"]
235
+ else
236
+ @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
237
+ # According to RFC 2109:
238
+ # If multiple cookies satisfy the criteria above, they are ordered in
239
+ # the Cookie header such that those with more specific Path attributes
240
+ # precede those with less specific. Ordering with respect to other
241
+ # attributes (e.g., Domain) is unspecified.
242
+ @env["rack.request.cookie_hash"] =
243
+ Hash[*Utils.parse_query(@env["rack.request.cookie_string"], ';,').map {|k,v|
244
+ [k, Array === v ? v.first : v]
245
+ }.flatten]
246
+ end
247
+ end
248
+
249
+ def xhr?
250
+ @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
251
+ end
252
+
253
+ def base_url
254
+ url = scheme + "://"
255
+ url << host
256
+
257
+ if scheme == "https" && port != 443 ||
258
+ scheme == "http" && port != 80
259
+ url << ":#{port}"
260
+ end
261
+
262
+ url
263
+ end
264
+
265
+ # Tries to return a remake of the original request URL as a string.
266
+ def url
267
+ base_url + fullpath
268
+ end
269
+
270
+ def path
271
+ script_name + path_info
272
+ end
273
+
274
+ def fullpath
275
+ query_string.empty? ? path : "#{path}?#{query_string}"
276
+ end
277
+
278
+ def accept_encoding
279
+ @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
280
+ m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
281
+
282
+ if m
283
+ [m[1], (m[2] || 1.0).to_f]
284
+ else
285
+ raise "Invalid value for Accept-Encoding: #{part.inspect}"
286
+ end
287
+ end
288
+ end
289
+
290
+ def ip
291
+ if addr = @env['HTTP_X_FORWARDED_FOR']
292
+ (addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip
293
+ else
294
+ @env['REMOTE_ADDR']
295
+ end
296
+ end
297
+
298
+ protected
299
+ def parse_query(qs)
300
+ Utils.parse_nested_query(qs)
301
+ end
302
+
303
+ def parse_multipart(env)
304
+ Utils::Multipart.parse_multipart(env)
305
+ end
306
+ end
307
+ end