rack 1.1.6 → 1.6.9

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 (212) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +1 -1
  3. data/HISTORY.md +375 -0
  4. data/KNOWN-ISSUES +23 -0
  5. data/README.rdoc +312 -0
  6. data/Rakefile +124 -0
  7. data/SPEC +125 -32
  8. data/contrib/rack.png +0 -0
  9. data/contrib/rack.svg +150 -0
  10. data/contrib/rack_logo.svg +1 -1
  11. data/contrib/rdoc.css +412 -0
  12. data/example/protectedlobster.rb +1 -1
  13. data/lib/rack/auth/abstract/handler.rb +4 -4
  14. data/lib/rack/auth/abstract/request.rb +7 -5
  15. data/lib/rack/auth/basic.rb +1 -1
  16. data/lib/rack/auth/digest/md5.rb +7 -3
  17. data/lib/rack/auth/digest/nonce.rb +1 -1
  18. data/lib/rack/auth/digest/params.rb +7 -9
  19. data/lib/rack/auth/digest/request.rb +10 -9
  20. data/lib/rack/backports/uri/common_18.rb +56 -0
  21. data/lib/rack/backports/uri/common_192.rb +52 -0
  22. data/lib/rack/backports/uri/common_193.rb +29 -0
  23. data/lib/rack/body_proxy.rb +39 -0
  24. data/lib/rack/builder.rb +106 -22
  25. data/lib/rack/cascade.rb +17 -6
  26. data/lib/rack/chunked.rb +44 -24
  27. data/lib/rack/commonlogger.rb +36 -13
  28. data/lib/rack/conditionalget.rb +49 -17
  29. data/lib/rack/config.rb +5 -0
  30. data/lib/rack/content_length.rb +14 -6
  31. data/lib/rack/content_type.rb +7 -1
  32. data/lib/rack/deflater.rb +73 -15
  33. data/lib/rack/directory.rb +18 -8
  34. data/lib/rack/etag.rb +59 -9
  35. data/lib/rack/file.rb +106 -44
  36. data/lib/rack/handler/cgi.rb +11 -11
  37. data/lib/rack/handler/fastcgi.rb +18 -6
  38. data/lib/rack/handler/lsws.rb +2 -4
  39. data/lib/rack/handler/mongrel.rb +22 -6
  40. data/lib/rack/handler/scgi.rb +16 -8
  41. data/lib/rack/handler/thin.rb +19 -4
  42. data/lib/rack/handler/webrick.rb +72 -19
  43. data/lib/rack/handler.rb +47 -14
  44. data/lib/rack/head.rb +10 -2
  45. data/lib/rack/lint.rb +260 -75
  46. data/lib/rack/lobster.rb +13 -8
  47. data/lib/rack/lock.rb +13 -3
  48. data/lib/rack/logger.rb +0 -2
  49. data/lib/rack/methodoverride.rb +27 -8
  50. data/lib/rack/mime.rb +625 -167
  51. data/lib/rack/mock.rb +78 -53
  52. data/lib/rack/multipart/generator.rb +93 -0
  53. data/lib/rack/multipart/parser.rb +253 -0
  54. data/lib/rack/multipart/uploaded_file.rb +34 -0
  55. data/lib/rack/multipart.rb +34 -0
  56. data/lib/rack/nulllogger.rb +21 -2
  57. data/lib/rack/recursive.rb +10 -5
  58. data/lib/rack/reloader.rb +3 -2
  59. data/lib/rack/request.rb +201 -74
  60. data/lib/rack/response.rb +41 -28
  61. data/lib/rack/rewindable_input.rb +15 -11
  62. data/lib/rack/runtime.rb +16 -3
  63. data/lib/rack/sendfile.rb +47 -29
  64. data/lib/rack/server.rb +223 -47
  65. data/lib/rack/session/abstract/id.rb +289 -30
  66. data/lib/rack/session/cookie.rb +133 -44
  67. data/lib/rack/session/memcache.rb +30 -56
  68. data/lib/rack/session/pool.rb +19 -43
  69. data/lib/rack/showexceptions.rb +53 -15
  70. data/lib/rack/showstatus.rb +14 -7
  71. data/lib/rack/static.rb +124 -12
  72. data/lib/rack/tempfile_reaper.rb +22 -0
  73. data/lib/rack/urlmap.rb +49 -15
  74. data/lib/rack/utils/okjson.rb +600 -0
  75. data/lib/rack/utils.rb +363 -361
  76. data/lib/rack.rb +17 -23
  77. data/rack.gemspec +11 -20
  78. data/test/builder/anything.rb +5 -0
  79. data/test/builder/comment.ru +4 -0
  80. data/test/builder/end.ru +5 -0
  81. data/test/builder/line.ru +1 -0
  82. data/test/builder/options.ru +2 -0
  83. data/test/cgi/assets/folder/test.js +1 -0
  84. data/test/cgi/assets/fonts/font.eot +1 -0
  85. data/test/cgi/assets/images/image.png +1 -0
  86. data/test/cgi/assets/index.html +1 -0
  87. data/test/cgi/assets/javascripts/app.js +1 -0
  88. data/test/cgi/assets/stylesheets/app.css +1 -0
  89. data/test/cgi/lighttpd.conf +26 -0
  90. data/test/cgi/rackup_stub.rb +6 -0
  91. data/test/cgi/sample_rackup.ru +5 -0
  92. data/test/cgi/test +9 -0
  93. data/test/cgi/test+directory/test+file +1 -0
  94. data/test/cgi/test.fcgi +8 -0
  95. data/test/cgi/test.ru +5 -0
  96. data/test/gemloader.rb +10 -0
  97. data/test/multipart/bad_robots +259 -0
  98. data/test/multipart/binary +0 -0
  99. data/test/multipart/content_type_and_no_filename +6 -0
  100. data/test/multipart/empty +10 -0
  101. data/test/multipart/fail_16384_nofile +814 -0
  102. data/test/multipart/file1.txt +1 -0
  103. data/test/multipart/filename_and_modification_param +7 -0
  104. data/test/multipart/filename_and_no_name +6 -0
  105. data/test/multipart/filename_with_escaped_quotes +6 -0
  106. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  107. data/test/multipart/filename_with_null_byte +7 -0
  108. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  109. data/test/multipart/filename_with_unescaped_percentages +6 -0
  110. data/test/multipart/filename_with_unescaped_percentages2 +6 -0
  111. data/test/multipart/filename_with_unescaped_percentages3 +6 -0
  112. data/test/multipart/filename_with_unescaped_quotes +6 -0
  113. data/test/multipart/ie +6 -0
  114. data/test/multipart/invalid_character +6 -0
  115. data/test/multipart/mixed_files +21 -0
  116. data/test/multipart/nested +10 -0
  117. data/test/multipart/none +9 -0
  118. data/test/multipart/semicolon +6 -0
  119. data/test/multipart/text +15 -0
  120. data/test/multipart/three_files_three_fields +31 -0
  121. data/test/multipart/webkit +32 -0
  122. data/test/rackup/config.ru +31 -0
  123. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  124. data/test/{spec_rack_auth_basic.rb → spec_auth_basic.rb} +23 -15
  125. data/test/{spec_rack_auth_digest.rb → spec_auth_digest.rb} +56 -29
  126. data/test/spec_body_proxy.rb +85 -0
  127. data/test/spec_builder.rb +223 -0
  128. data/test/{spec_rack_cascade.rb → spec_cascade.rb} +28 -15
  129. data/test/{spec_rack_cgi.rb → spec_cgi.rb} +44 -31
  130. data/test/spec_chunked.rb +101 -0
  131. data/test/spec_commonlogger.rb +93 -0
  132. data/test/spec_conditionalget.rb +102 -0
  133. data/test/{spec_rack_config.rb → spec_config.rb} +6 -8
  134. data/test/spec_content_length.rb +85 -0
  135. data/test/spec_content_type.rb +45 -0
  136. data/test/spec_deflater.rb +339 -0
  137. data/test/{spec_rack_directory.rb → spec_directory.rb} +37 -10
  138. data/test/spec_etag.rb +107 -0
  139. data/test/{spec_rack_fastcgi.rb → spec_fastcgi.rb} +47 -29
  140. data/test/spec_file.rb +221 -0
  141. data/test/spec_handler.rb +72 -0
  142. data/test/spec_head.rb +45 -0
  143. data/test/{spec_rack_lint.rb → spec_lint.rb} +82 -60
  144. data/test/spec_lobster.rb +58 -0
  145. data/test/spec_lock.rb +164 -0
  146. data/test/spec_logger.rb +23 -0
  147. data/test/spec_methodoverride.rb +95 -0
  148. data/test/spec_mime.rb +51 -0
  149. data/test/{spec_rack_mock.rb → spec_mock.rb} +92 -38
  150. data/test/{spec_rack_mongrel.rb → spec_mongrel.rb} +46 -53
  151. data/test/spec_multipart.rb +600 -0
  152. data/test/spec_nulllogger.rb +20 -0
  153. data/test/spec_recursive.rb +72 -0
  154. data/test/spec_request.rb +1227 -0
  155. data/test/spec_response.rb +407 -0
  156. data/test/spec_rewindable_input.rb +118 -0
  157. data/test/spec_runtime.rb +49 -0
  158. data/test/spec_sendfile.rb +130 -0
  159. data/test/spec_server.rb +167 -0
  160. data/test/spec_session_abstract_id.rb +53 -0
  161. data/test/spec_session_cookie.rb +410 -0
  162. data/test/{spec_rack_session_memcache.rb → spec_session_memcache.rb} +119 -71
  163. data/test/{spec_rack_session_pool.rb → spec_session_pool.rb} +106 -69
  164. data/test/spec_showexceptions.rb +85 -0
  165. data/test/spec_showstatus.rb +103 -0
  166. data/test/spec_static.rb +145 -0
  167. data/test/spec_tempfile_reaper.rb +63 -0
  168. data/test/{spec_rack_thin.rb → spec_thin.rb} +35 -35
  169. data/test/{spec_rack_urlmap.rb → spec_urlmap.rb} +40 -19
  170. data/test/spec_utils.rb +647 -0
  171. data/test/spec_version.rb +17 -0
  172. data/test/spec_webrick.rb +184 -0
  173. data/test/static/another/index.html +1 -0
  174. data/test/static/index.html +1 -0
  175. data/test/testrequest.rb +78 -0
  176. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  177. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  178. metadata +220 -239
  179. data/RDOX +0 -0
  180. data/README +0 -592
  181. data/lib/rack/adapter/camping.rb +0 -22
  182. data/test/spec_auth.rb +0 -57
  183. data/test/spec_rack_builder.rb +0 -84
  184. data/test/spec_rack_camping.rb +0 -55
  185. data/test/spec_rack_chunked.rb +0 -62
  186. data/test/spec_rack_commonlogger.rb +0 -61
  187. data/test/spec_rack_conditionalget.rb +0 -41
  188. data/test/spec_rack_content_length.rb +0 -43
  189. data/test/spec_rack_content_type.rb +0 -30
  190. data/test/spec_rack_deflater.rb +0 -127
  191. data/test/spec_rack_etag.rb +0 -17
  192. data/test/spec_rack_file.rb +0 -75
  193. data/test/spec_rack_handler.rb +0 -43
  194. data/test/spec_rack_head.rb +0 -30
  195. data/test/spec_rack_lobster.rb +0 -45
  196. data/test/spec_rack_lock.rb +0 -38
  197. data/test/spec_rack_logger.rb +0 -21
  198. data/test/spec_rack_methodoverride.rb +0 -60
  199. data/test/spec_rack_nulllogger.rb +0 -13
  200. data/test/spec_rack_recursive.rb +0 -77
  201. data/test/spec_rack_request.rb +0 -594
  202. data/test/spec_rack_response.rb +0 -221
  203. data/test/spec_rack_rewindable_input.rb +0 -118
  204. data/test/spec_rack_runtime.rb +0 -35
  205. data/test/spec_rack_sendfile.rb +0 -86
  206. data/test/spec_rack_session_cookie.rb +0 -92
  207. data/test/spec_rack_showexceptions.rb +0 -21
  208. data/test/spec_rack_showstatus.rb +0 -72
  209. data/test/spec_rack_static.rb +0 -37
  210. data/test/spec_rack_utils.rb +0 -557
  211. data/test/spec_rack_webrick.rb +0 -130
  212. data/test/spec_rackup.rb +0 -164
data/lib/rack/reloader.rb CHANGED
@@ -26,6 +26,7 @@ module Rack
26
26
  @last = (Time.now - cooldown)
27
27
  @cache = {}
28
28
  @mtimes = {}
29
+ @reload_mutex = Mutex.new
29
30
 
30
31
  extend backend
31
32
  end
@@ -33,7 +34,7 @@ module Rack
33
34
  def call(env)
34
35
  if @cooldown and Time.now > @last + @cooldown
35
36
  if Thread.list.size > 1
36
- Thread.exclusive{ reload! }
37
+ @reload_mutex.synchronize{ reload! }
37
38
  else
38
39
  reload!
39
40
  end
@@ -101,7 +102,7 @@ module Rack
101
102
  return unless file
102
103
  stat = ::File.stat(file)
103
104
  return file, stat if stat.file?
104
- rescue Errno::ENOENT, Errno::ENOTDIR
105
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH
105
106
  @cache.delete(file) and false
106
107
  end
107
108
  end
data/lib/rack/request.rb CHANGED
@@ -8,10 +8,6 @@ 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
13
  # The environment of the request.
@@ -22,14 +18,17 @@ module Rack
22
18
  end
23
19
 
24
20
  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
21
+ def script_name; @env[SCRIPT_NAME].to_s end
22
+ def path_info; @env[PATH_INFO].to_s end
29
23
  def request_method; @env["REQUEST_METHOD"] end
30
- def query_string; @env["QUERY_STRING"].to_s end
24
+ def query_string; @env[QUERY_STRING].to_s end
31
25
  def content_length; @env['CONTENT_LENGTH'] end
32
- def content_type; @env['CONTENT_TYPE'] end
26
+
27
+ def content_type
28
+ content_type = @env['CONTENT_TYPE']
29
+ content_type.nil? || content_type.empty? ? nil : content_type
30
+ end
31
+
33
32
  def session; @env['rack.session'] ||= {} end
34
33
  def session_options; @env['rack.session.options'] ||= {} end
35
34
  def logger; @env['rack.logger'] end
@@ -51,9 +50,9 @@ module Rack
51
50
  # { 'charset' => 'utf-8' }
52
51
  def media_type_params
53
52
  return {} if content_type.nil?
54
- content_type.split(/\s*[;,]\s*/)[1..-1].
53
+ Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
55
54
  collect { |s| s.split('=', 2) }.
56
- inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash }
55
+ map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
57
56
  end
58
57
 
59
58
  # The character set of the request body if a "charset" media type
@@ -64,6 +63,24 @@ module Rack
64
63
  media_type_params['charset']
65
64
  end
66
65
 
66
+ def scheme
67
+ if @env['HTTPS'] == 'on'
68
+ 'https'
69
+ elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
70
+ 'https'
71
+ elsif @env['HTTP_X_FORWARDED_SCHEME']
72
+ @env['HTTP_X_FORWARDED_SCHEME']
73
+ elsif @env['HTTP_X_FORWARDED_PROTO']
74
+ @env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
75
+ else
76
+ @env["rack.url_scheme"]
77
+ end
78
+ end
79
+
80
+ def ssl?
81
+ scheme == 'https'
82
+ end
83
+
67
84
  def host_with_port
68
85
  if forwarded = @env["HTTP_X_FORWARDED_HOST"]
69
86
  forwarded.split(/,\s?/).last
@@ -72,19 +89,59 @@ module Rack
72
89
  end
73
90
  end
74
91
 
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
103
+ end
104
+ end
105
+
75
106
  def host
76
107
  # Remove port number.
77
- host_with_port.to_s.gsub(/:\d+\z/, '')
108
+ host_with_port.to_s.sub(/:\d+\z/, '')
78
109
  end
79
110
 
80
111
  def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
81
112
  def path_info=(s); @env["PATH_INFO"] = s.to_s end
82
113
 
83
- def get?; request_method == "GET" end
84
- def post?; request_method == "POST" end
85
- def put?; request_method == "PUT" end
86
- def delete?; request_method == "DELETE" end
87
- def head?; request_method == "HEAD" end
114
+
115
+ # Checks the HTTP request method (or verb) to see if it was of type DELETE
116
+ def delete?; request_method == "DELETE" end
117
+
118
+ # Checks the HTTP request method (or verb) to see if it was of type GET
119
+ def get?; request_method == GET end
120
+
121
+ # Checks the HTTP request method (or verb) to see if it was of type HEAD
122
+ def head?; request_method == HEAD end
123
+
124
+ # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
125
+ def options?; request_method == "OPTIONS" end
126
+
127
+ # Checks the HTTP request method (or verb) to see if it was of type LINK
128
+ def link?; request_method == "LINK" end
129
+
130
+ # Checks the HTTP request method (or verb) to see if it was of type PATCH
131
+ def patch?; request_method == "PATCH" end
132
+
133
+ # Checks the HTTP request method (or verb) to see if it was of type POST
134
+ def post?; request_method == "POST" end
135
+
136
+ # Checks the HTTP request method (or verb) to see if it was of type PUT
137
+ def put?; request_method == "PUT" end
138
+
139
+ # Checks the HTTP request method (or verb) to see if it was of type TRACE
140
+ def trace?; request_method == "TRACE" end
141
+
142
+ # Checks the HTTP request method (or verb) to see if it was of type UNLINK
143
+ def unlink?; request_method == "UNLINK" end
144
+
88
145
 
89
146
  # The set of form-data media-types. Requests that do not indicate
90
147
  # one of the media types presents in this list will not be eligible
@@ -102,6 +159,10 @@ module Rack
102
159
  'multipart/mixed'
103
160
  ]
104
161
 
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 }
165
+
105
166
  # Determine whether the request body contains form-data by checking
106
167
  # the request Content-Type for one of the media-types:
107
168
  # "application/x-www-form-urlencoded" or "multipart/form-data". The
@@ -112,7 +173,7 @@ module Rack
112
173
  # Content-Type header is provided and the request_method is POST.
113
174
  def form_data?
114
175
  type = media_type
115
- meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
176
+ meth = env["rack.methodoverride.original_method"] || env[REQUEST_METHOD]
116
177
  (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
117
178
  end
118
179
 
@@ -122,38 +183,40 @@ module Rack
122
183
  PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
123
184
  end
124
185
 
125
- # Returns the data recieved in the query string.
186
+ # Returns the data received in the query string.
126
187
  def GET
127
188
  if @env["rack.request.query_string"] == query_string
128
189
  @env["rack.request.query_hash"]
129
190
  else
191
+ p = parse_query({ :query => query_string, :separator => '&;' })
130
192
  @env["rack.request.query_string"] = query_string
131
- @env["rack.request.query_hash"] = parse_query(query_string)
193
+ @env["rack.request.query_hash"] = p
132
194
  end
133
195
  end
134
196
 
135
- # Returns the data recieved in the request body.
197
+ # Returns the data received in the request body.
136
198
  #
137
199
  # This method support both application/x-www-form-urlencoded and
138
200
  # multipart/form-data.
139
201
  def POST
140
202
  if @env["rack.input"].nil?
141
203
  raise "Missing rack.input"
142
- elsif @env["rack.request.form_input"].eql? @env["rack.input"]
204
+ elsif @env["rack.request.form_input"].equal? @env["rack.input"]
143
205
  @env["rack.request.form_hash"]
144
206
  elsif form_data? || parseable_data?
145
- @env["rack.request.form_input"] = @env["rack.input"]
146
207
  unless @env["rack.request.form_hash"] = parse_multipart(env)
147
208
  form_vars = @env["rack.input"].read
148
209
 
149
210
  # Fix for Safari Ajax postings that always append \0
150
- form_vars.sub!(/\0\z/, '')
211
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
212
+ form_vars.slice!(-1) if form_vars[-1] == ?\0
151
213
 
152
214
  @env["rack.request.form_vars"] = form_vars
153
- @env["rack.request.form_hash"] = parse_query(form_vars)
215
+ @env["rack.request.form_hash"] = parse_query({ :query => form_vars, :separator => '&' })
154
216
 
155
217
  @env["rack.input"].rewind
156
218
  end
219
+ @env["rack.request.form_input"] = @env["rack.input"]
157
220
  @env["rack.request.form_hash"]
158
221
  else
159
222
  {}
@@ -161,10 +224,45 @@ module Rack
161
224
  end
162
225
 
163
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.
164
229
  def params
165
- self.GET.update(self.POST)
166
- rescue EOFError => e
167
- self.GET
230
+ @params ||= self.GET.merge(self.POST)
231
+ rescue EOFError
232
+ self.GET.dup
233
+ end
234
+
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
245
+ end
246
+ if self.POST.has_key?(k)
247
+ found = true
248
+ self.POST[k] = v
249
+ end
250
+ unless found
251
+ self.GET[k] = v
252
+ end
253
+ @params = nil
254
+ nil
255
+ end
256
+
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
168
266
  end
169
267
 
170
268
  # shortcut for request.params[key]
@@ -173,6 +271,8 @@ module Rack
173
271
  end
174
272
 
175
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.
176
276
  def []=(key, value)
177
277
  params[key.to_s] = value
178
278
  end
@@ -182,9 +282,9 @@ module Rack
182
282
  keys.map{|key| params[key] }
183
283
  end
184
284
 
185
- # the referer of the client or '/'
285
+ # the referer of the client
186
286
  def referer
187
- @env['HTTP_REFERER'] || '/'
287
+ @env['HTTP_REFERER']
188
288
  end
189
289
  alias referrer referer
190
290
 
@@ -193,42 +293,36 @@ module Rack
193
293
  end
194
294
 
195
295
  def cookies
196
- return {} unless @env["HTTP_COOKIE"]
197
-
198
- if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
199
- @env["rack.request.cookie_hash"]
200
- else
201
- @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
202
- # According to RFC 2109:
203
- # If multiple cookies satisfy the criteria above, they are ordered in
204
- # the Cookie header such that those with more specific Path attributes
205
- # precede those with less specific. Ordering with respect to other
206
- # attributes (e.g., Domain) is unspecified.
207
- @env["rack.request.cookie_hash"] =
208
- Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)|
209
- h[k] = Array === v ? v.first : v
210
- h
211
- }
212
- end
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
213
311
  end
214
312
 
215
313
  def xhr?
216
314
  @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
217
315
  end
218
316
 
317
+ def base_url
318
+ url = "#{scheme}://#{host}"
319
+ url << ":#{port}" if port != DEFAULT_PORTS[scheme]
320
+ url
321
+ end
322
+
219
323
  # Tries to return a remake of the original request URL as a string.
220
324
  def url
221
- url = scheme + "://"
222
- url << host
223
-
224
- if scheme == "https" && port != 443 ||
225
- scheme == "http" && port != 80
226
- url << ":#{port}"
227
- end
228
-
229
- url << fullpath
230
-
231
- url
325
+ base_url + fullpath
232
326
  end
233
327
 
234
328
  def path
@@ -240,32 +334,65 @@ module Rack
240
334
  end
241
335
 
242
336
  def accept_encoding
243
- @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
244
- m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
337
+ parse_http_accept_header(@env["HTTP_ACCEPT_ENCODING"])
338
+ end
245
339
 
246
- if m
247
- [m[1], (m[2] || 1.0).to_f]
248
- else
249
- raise "Invalid value for Accept-Encoding: #{part.inspect}"
250
- end
251
- end
340
+ def accept_language
341
+ parse_http_accept_header(@env["HTTP_ACCEPT_LANGUAGE"])
342
+ end
343
+
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
252
346
  end
253
347
 
254
348
  def ip
255
- if addr = @env['HTTP_X_FORWARDED_FOR']
256
- addr.split(',').last.strip
257
- else
258
- @env['REMOTE_ADDR']
259
- end
349
+ remote_addrs = split_ip_addresses(@env['REMOTE_ADDR'])
350
+ remote_addrs = reject_trusted_ip_addresses(remote_addrs)
351
+
352
+ return remote_addrs.first if remote_addrs.any?
353
+
354
+ forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])
355
+
356
+ return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
260
357
  end
261
358
 
262
359
  protected
360
+ def split_ip_addresses(ip_addresses)
361
+ ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
362
+ end
363
+
364
+ def reject_trusted_ip_addresses(ip_addresses)
365
+ ip_addresses.reject { |ip| trusted_proxy?(ip) }
366
+ end
367
+
263
368
  def parse_query(qs)
264
- Utils.parse_nested_query(qs)
369
+ d = '&'
370
+ qs, d = qs[:query], qs[:separator] if Hash === qs
371
+ Utils.parse_nested_query(qs, d)
265
372
  end
266
373
 
267
374
  def parse_multipart(env)
268
- Utils::Multipart.parse_multipart(env)
375
+ Rack::Multipart.parse_multipart(env)
376
+ end
377
+
378
+ def parse_http_accept_header(header)
379
+ header.to_s.split(/\s*,\s*/).map do |part|
380
+ attribute, parameters = part.split(/\s*;\s*/, 2)
381
+ quality = 1.0
382
+ if parameters and /\Aq=([\d.]+)/ =~ parameters
383
+ quality = $1.to_f
384
+ end
385
+ [attribute, quality]
386
+ end
269
387
  end
388
+
389
+ private
390
+ def strip_doublequotes(s)
391
+ if s[0] == ?" && s[-1] == ?"
392
+ s[1..-2]
393
+ else
394
+ s
395
+ end
396
+ end
270
397
  end
271
398
  end
data/lib/rack/response.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'rack/request'
2
2
  require 'rack/utils'
3
+ require 'rack/body_proxy'
4
+ require 'time'
3
5
 
4
6
  module Rack
5
7
  # Rack::Response provides a convenient interface to create a Rack
@@ -11,21 +13,23 @@ module Rack
11
13
  # You can use Response#write to iteratively generate your response,
12
14
  # but note that this is buffered by Rack::Response until you call
13
15
  # +finish+. +finish+ however can take a block inside which calls to
14
- # +write+ are syncronous with the Rack response.
16
+ # +write+ are synchronous with the Rack response.
15
17
  #
16
18
  # Your application's +call+ should end returning Response#finish.
17
19
 
18
20
  class Response
19
21
  attr_accessor :length
20
22
 
21
- def initialize(body=[], status=200, header={}, &block)
23
+ CHUNKED = 'chunked'.freeze
24
+ TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
25
+ def initialize(body=[], status=200, header={})
22
26
  @status = status.to_i
23
- @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
24
- merge(header))
27
+ @header = Utils::HeaderHash.new.merge(header)
25
28
 
26
- @writer = lambda { |x| @body << x }
27
- @block = nil
28
- @length = 0
29
+ @chunked = CHUNKED == @header[TRANSFER_ENCODING]
30
+ @writer = lambda { |x| @body << x }
31
+ @block = nil
32
+ @length = 0
29
33
 
30
34
  @body = []
31
35
 
@@ -69,14 +73,17 @@ module Rack
69
73
  def finish(&block)
70
74
  @block = block
71
75
 
72
- if [204, 304].include?(status.to_i)
73
- header.delete "Content-Type"
76
+ if [204, 205, 304].include?(status.to_i)
77
+ header.delete CONTENT_TYPE
78
+ header.delete CONTENT_LENGTH
79
+ close
74
80
  [status.to_i, header, []]
75
81
  else
76
- [status.to_i, header, self]
82
+ [status.to_i, header, BodyProxy.new(self){}]
77
83
  end
78
84
  end
79
85
  alias to_a finish # For *response
86
+ alias to_ary finish # For implicit-splat on Ruby 1.9.2
80
87
 
81
88
  def each(&callback)
82
89
  @body.each(&callback)
@@ -90,10 +97,10 @@ module Rack
90
97
  #
91
98
  def write(str)
92
99
  s = str.to_s
93
- @length += Rack::Utils.bytesize(s)
100
+ @length += Rack::Utils.bytesize(s) unless @chunked
94
101
  @writer.call s
95
102
 
96
- header["Content-Length"] = @length.to_s
103
+ header[CONTENT_LENGTH] = @length.to_s unless @chunked
97
104
  str
98
105
  end
99
106
 
@@ -108,20 +115,26 @@ module Rack
108
115
  alias headers header
109
116
 
110
117
  module Helpers
111
- def invalid?; @status < 100 || @status >= 600; end
112
-
113
- def informational?; @status >= 100 && @status < 200; end
114
- def successful?; @status >= 200 && @status < 300; end
115
- def redirection?; @status >= 300 && @status < 400; end
116
- def client_error?; @status >= 400 && @status < 500; end
117
- def server_error?; @status >= 500 && @status < 600; end
118
-
119
- def ok?; @status == 200; end
120
- def forbidden?; @status == 403; end
121
- def not_found?; @status == 404; end
122
-
123
- def redirect?; [301, 302, 303, 307].include? @status; end
124
- def empty?; [201, 204, 304].include? @status; end
118
+ def invalid?; status < 100 || status >= 600; end
119
+
120
+ def informational?; status >= 100 && status < 200; end
121
+ def successful?; status >= 200 && status < 300; end
122
+ def redirection?; status >= 300 && status < 400; end
123
+ def client_error?; status >= 400 && status < 500; end
124
+ def server_error?; status >= 500 && status < 600; end
125
+
126
+ def ok?; status == 200; end
127
+ def created?; status == 201; end
128
+ def accepted?; status == 202; end
129
+ def bad_request?; status == 400; end
130
+ def unauthorized?; status == 401; end
131
+ def forbidden?; status == 403; end
132
+ def not_found?; status == 404; end
133
+ def method_not_allowed?; status == 405; end
134
+ def i_m_a_teapot?; status == 418; end
135
+ def unprocessable?; status == 422; end
136
+
137
+ def redirect?; [301, 302, 303, 307].include? status; end
125
138
 
126
139
  # Headers
127
140
  attr_reader :headers, :original_headers
@@ -131,11 +144,11 @@ module Rack
131
144
  end
132
145
 
133
146
  def content_type
134
- headers["Content-Type"]
147
+ headers[CONTENT_TYPE]
135
148
  end
136
149
 
137
150
  def content_length
138
- cl = headers["Content-Length"]
151
+ cl = headers[CONTENT_LENGTH]
139
152
  cl ? cl.to_i : cl
140
153
  end
141
154
 
@@ -1,4 +1,6 @@
1
+ # -*- encoding: binary -*-
1
2
  require 'tempfile'
3
+ require 'rack/utils'
2
4
 
3
5
  module Rack
4
6
  # Class which can make any IO object rewindable, including non-rewindable ones. It does
@@ -16,27 +18,27 @@ module Rack
16
18
  @rewindable_io = nil
17
19
  @unlinked = false
18
20
  end
19
-
21
+
20
22
  def gets
21
23
  make_rewindable unless @rewindable_io
22
24
  @rewindable_io.gets
23
25
  end
24
-
26
+
25
27
  def read(*args)
26
28
  make_rewindable unless @rewindable_io
27
29
  @rewindable_io.read(*args)
28
30
  end
29
-
31
+
30
32
  def each(&block)
31
33
  make_rewindable unless @rewindable_io
32
34
  @rewindable_io.each(&block)
33
35
  end
34
-
36
+
35
37
  def rewind
36
38
  make_rewindable unless @rewindable_io
37
39
  @rewindable_io.rewind
38
40
  end
39
-
41
+
40
42
  # Closes this RewindableInput object without closing the originally
41
43
  # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
42
44
  # has created.
@@ -52,9 +54,9 @@ module Rack
52
54
  @rewindable_io = nil
53
55
  end
54
56
  end
55
-
57
+
56
58
  private
57
-
59
+
58
60
  # Ruby's Tempfile class has a bug. Subclass it and fix it.
59
61
  class Tempfile < ::Tempfile
60
62
  def _close
@@ -75,16 +77,18 @@ module Rack
75
77
  @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
76
78
  @rewindable_io.binmode
77
79
  if filesystem_has_posix_semantics?
78
- @rewindable_io.unlink
80
+ # Use ::File.unlink as 1.9.1 Tempfile has a bug where unlink closes the file!
81
+ ::File.unlink @rewindable_io.path
82
+ raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
79
83
  @unlinked = true
80
84
  end
81
-
85
+
82
86
  buffer = ""
83
87
  while @io.read(1024 * 4, buffer)
84
88
  entire_buffer_written_out = false
85
89
  while !entire_buffer_written_out
86
90
  written = @rewindable_io.write(buffer)
87
- entire_buffer_written_out = written == buffer.size
91
+ entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
88
92
  if !entire_buffer_written_out
89
93
  buffer.slice!(0 .. written - 1)
90
94
  end
@@ -92,7 +96,7 @@ module Rack
92
96
  end
93
97
  @rewindable_io.rewind
94
98
  end
95
-
99
+
96
100
  def filesystem_has_posix_semantics?
97
101
  RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
98
102
  end
data/lib/rack/runtime.rb CHANGED
@@ -12,16 +12,29 @@ module Rack
12
12
  @header_name << "-#{name}" if name
13
13
  end
14
14
 
15
+ FORMAT_STRING = "%0.6f"
15
16
  def call(env)
16
- start_time = Time.now
17
+ start_time = clock_time
17
18
  status, headers, body = @app.call(env)
18
- request_time = Time.now - start_time
19
+ request_time = clock_time - start_time
19
20
 
20
21
  if !headers.has_key?(@header_name)
21
- headers[@header_name] = "%0.6f" % request_time
22
+ headers[@header_name] = FORMAT_STRING % request_time
22
23
  end
23
24
 
24
25
  [status, headers, body]
25
26
  end
27
+
28
+ private
29
+
30
+ if defined?(Process::CLOCK_MONOTONIC)
31
+ def clock_time
32
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
33
+ end
34
+ else
35
+ def clock_time
36
+ Time.now.to_f
37
+ end
38
+ end
26
39
  end
27
40
  end