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
@@ -21,6 +21,7 @@ module Rack
21
21
 
22
22
  class Memcache < Abstract::ID
23
23
  attr_reader :mutex, :pool
24
+
24
25
  DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
25
26
  :namespace => 'rack:session',
26
27
  :memcache_server => 'localhost:11211'
@@ -30,9 +31,9 @@ module Rack
30
31
 
31
32
  @mutex = Mutex.new
32
33
  mserv = @default_options[:memcache_server]
33
- mopts = @default_options.
34
- reject{|k,v| MemCache::DEFAULT_OPTIONS.include? k }
35
- @pool = MemCache.new mserv, mopts
34
+ mopts = @default_options.reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
35
+
36
+ @pool = options[:cache] || MemCache.new(mserv, mopts)
36
37
  unless @pool.active? and @pool.servers.any?{|c| c.alive? }
37
38
  raise 'No memcache servers'
38
39
  end
@@ -45,75 +46,48 @@ module Rack
45
46
  end
46
47
  end
47
48
 
48
- def get_session(env, session_id)
49
- @mutex.lock if env['rack.multithread']
50
- unless session_id and session = @pool.get(session_id)
51
- session_id, session = generate_sid, {}
52
- unless /^STORED/ =~ @pool.add(session_id, session)
53
- raise "Session collision on '#{session_id.inspect}'"
49
+ def get_session(env, sid)
50
+ with_lock(env) do
51
+ unless sid and session = @pool.get(sid)
52
+ sid, session = generate_sid, {}
53
+ unless /^STORED/ =~ @pool.add(sid, session)
54
+ raise "Session collision on '#{sid.inspect}'"
55
+ end
54
56
  end
57
+ [sid, session]
55
58
  end
56
- session.instance_variable_set '@old', @pool.get(session_id, true)
57
- return [session_id, session]
58
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED
59
- # MemCache server cannot be contacted
60
- warn "#{self} is unable to find memcached server."
61
- warn $!.inspect
62
- return [ nil, {} ]
63
- ensure
64
- @mutex.unlock if @mutex.locked?
65
59
  end
66
60
 
67
61
  def set_session(env, session_id, new_session, options)
68
62
  expiry = options[:expire_after]
69
63
  expiry = expiry.nil? ? 0 : expiry + 1
70
64
 
71
- @mutex.lock if env['rack.multithread']
72
- if options[:renew] or options[:drop]
73
- @pool.delete session_id
74
- return false if options[:drop]
75
- session_id = generate_sid
76
- @pool.add session_id, {} # so we don't worry about cache miss on #set
65
+ with_lock(env) do
66
+ @pool.set session_id, new_session, expiry
67
+ session_id
77
68
  end
69
+ end
78
70
 
79
- session = @pool.get(session_id) || {}
80
- old_session = new_session.instance_variable_get '@old'
81
- old_session = old_session ? Marshal.load(old_session) : {}
82
-
83
- unless Hash === old_session and Hash === new_session
84
- env['rack.errors'].
85
- puts 'Bad old_session or new_session sessions provided.'
86
- else # merge sessions
87
- # alterations are either update or delete, making as few changes as
88
- # possible to prevent possible issues.
89
-
90
- # removed keys
91
- delete = old_session.keys - new_session.keys
92
- if $VERBOSE and not delete.empty?
93
- env['rack.errors'].
94
- puts "//@#{session_id}: delete #{delete*','}"
95
- end
96
- delete.each{|k| session.delete k }
97
-
98
- # added or altered keys
99
- update = new_session.keys.
100
- select{|k| new_session[k] != old_session[k] }
101
- if $VERBOSE and not update.empty?
102
- env['rack.errors'].puts "//@#{session_id}: update #{update*','}"
103
- end
104
- update.each{|k| session[k] = new_session[k] }
71
+ def destroy_session(env, session_id, options)
72
+ with_lock(env) do
73
+ @pool.delete(session_id)
74
+ generate_sid unless options[:drop]
105
75
  end
76
+ end
106
77
 
107
- @pool.set session_id, session, expiry
108
- return session_id
78
+ def with_lock(env)
79
+ @mutex.lock if env['rack.multithread']
80
+ yield
109
81
  rescue MemCache::MemCacheError, Errno::ECONNREFUSED
110
- # MemCache server cannot be contacted
111
- warn "#{self} is unable to find memcached server."
112
- warn $!.inspect
113
- return false
82
+ if $VERBOSE
83
+ warn "#{self} is unable to find memcached server."
84
+ warn $!.inspect
85
+ end
86
+ raise
114
87
  ensure
115
88
  @mutex.unlock if @mutex.locked?
116
89
  end
90
+
117
91
  end
118
92
  end
119
93
  end
@@ -42,58 +42,34 @@ module Rack
42
42
  end
43
43
 
44
44
  def get_session(env, sid)
45
- session = @pool[sid] if sid
46
- @mutex.lock if env['rack.multithread']
47
- unless sid and session
48
- env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
49
- session = {}
50
- sid = generate_sid
51
- @pool.store sid, session
45
+ with_lock(env) do
46
+ unless sid and session = @pool[sid]
47
+ sid, session = generate_sid, {}
48
+ @pool.store sid, session
49
+ end
50
+ [sid, session]
52
51
  end
53
- session.instance_variable_set('@old', {}.merge(session))
54
- return [sid, session]
55
- ensure
56
- @mutex.unlock if env['rack.multithread']
57
52
  end
58
53
 
59
54
  def set_session(env, session_id, new_session, options)
60
- @mutex.lock if env['rack.multithread']
61
- session = @pool[session_id]
62
- if options[:renew] or options[:drop]
63
- @pool.delete session_id
64
- return false if options[:drop]
65
- session_id = generate_sid
66
- @pool.store session_id, 0
55
+ with_lock(env) do
56
+ @pool.store session_id, new_session
57
+ session_id
67
58
  end
68
- old_session = new_session.instance_variable_get('@old') || {}
69
- session = merge_sessions session_id, old_session, new_session, session
70
- @pool.store session_id, session
71
- return session_id
72
- rescue
73
- warn "#{new_session.inspect} has been lost."
74
- warn $!.inspect
75
- ensure
76
- @mutex.unlock if env['rack.multithread']
77
59
  end
78
60
 
79
- private
80
-
81
- def merge_sessions sid, old, new, cur=nil
82
- cur ||= {}
83
- unless Hash === old and Hash === new
84
- warn 'Bad old or new sessions provided.'
85
- return cur
61
+ def destroy_session(env, session_id, options)
62
+ with_lock(env) do
63
+ @pool.delete(session_id)
64
+ generate_sid unless options[:drop]
86
65
  end
66
+ end
87
67
 
88
- delete = old.keys - new.keys
89
- warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
90
- delete.each{|k| cur.delete k }
91
-
92
- update = new.keys.select{|k| new[k] != old[k] }
93
- warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
94
- update.each{|k| cur[k] = new[k] }
95
-
96
- cur
68
+ def with_lock(env)
69
+ @mutex.lock if env['rack.multithread']
70
+ yield
71
+ ensure
72
+ @mutex.unlock if @mutex.locked?
97
73
  end
98
74
  end
99
75
  end
@@ -23,18 +23,54 @@ module Rack
23
23
  def call(env)
24
24
  @app.call(env)
25
25
  rescue StandardError, LoadError, SyntaxError => e
26
- backtrace = pretty(env, e)
27
- [500,
28
- {"Content-Type" => "text/html",
29
- "Content-Length" => backtrace.join.size.to_s},
30
- backtrace]
26
+ exception_string = dump_exception(e)
27
+
28
+ env["rack.errors"].puts(exception_string)
29
+ env["rack.errors"].flush
30
+
31
+ if accepts_html?(env)
32
+ content_type = "text/html"
33
+ body = pretty(env, e)
34
+ else
35
+ content_type = "text/plain"
36
+ body = exception_string
37
+ end
38
+
39
+ [
40
+ 500,
41
+ {
42
+ CONTENT_TYPE => content_type,
43
+ CONTENT_LENGTH => Rack::Utils.bytesize(body).to_s,
44
+ },
45
+ [body],
46
+ ]
47
+ end
48
+
49
+ def prefers_plaintext?(env)
50
+ !accepts_html(env)
51
+ end
52
+
53
+ def accepts_html?(env)
54
+ Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html])
55
+ end
56
+ private :accepts_html?
57
+
58
+ def dump_exception(exception)
59
+ string = "#{exception.class}: #{exception.message}\n"
60
+ string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
61
+ string
31
62
  end
32
63
 
33
64
  def pretty(env, exception)
34
65
  req = Rack::Request.new(env)
35
- path = (req.script_name + req.path_info).squeeze("/")
36
66
 
37
- frames = exception.backtrace.map { |line|
67
+ # This double assignment is to prevent an "unused variable" warning on
68
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
69
+ path = path = (req.script_name + req.path_info).squeeze("/")
70
+
71
+ # This double assignment is to prevent an "unused variable" warning on
72
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
73
+ frames = frames = exception.backtrace.map { |line|
38
74
  frame = OpenStruct.new
39
75
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
40
76
  frame.filename = $1
@@ -58,11 +94,7 @@ module Rack
58
94
  end
59
95
  }.compact
60
96
 
61
- env["rack.errors"].puts "#{exception.class}: #{exception.message}"
62
- env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
63
- env["rack.errors"].flush
64
-
65
- [@template.result(binding)]
97
+ @template.result(binding)
66
98
  end
67
99
 
68
100
  def h(obj) # :nodoc:
@@ -195,7 +227,13 @@ TEMPLATE = <<'HTML'
195
227
  <h2><%=h exception.message %></h2>
196
228
  <table><tr>
197
229
  <th>Ruby</th>
198
- <td><code><%=h frames.first.filename %></code>: in <code><%=h frames.first.function %></code>, line <%=h frames.first.lineno %></td>
230
+ <td>
231
+ <% if first = frames.first %>
232
+ <code><%=h first.filename %></code>: in <code><%=h first.function %></code>, line <%=h frames.first.lineno %>
233
+ <% else %>
234
+ unknown location
235
+ <% end %>
236
+ </td>
199
237
  </tr><tr>
200
238
  <th>Web</th>
201
239
  <td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
@@ -248,7 +286,7 @@ TEMPLATE = <<'HTML'
248
286
  <h2>Request information</h2>
249
287
 
250
288
  <h3 id="get-info">GET</h3>
251
- <% unless req.GET.empty? %>
289
+ <% if req.GET and not req.GET.empty? %>
252
290
  <table class="req">
253
291
  <thead>
254
292
  <tr>
@@ -270,7 +308,7 @@ TEMPLATE = <<'HTML'
270
308
  <% end %>
271
309
 
272
310
  <h3 id="post-info">POST</h3>
273
- <% unless req.POST.empty? %>
311
+ <% if req.POST and not req.POST.empty? %>
274
312
  <table class="req">
275
313
  <thead>
276
314
  <tr>
@@ -3,8 +3,8 @@ require 'rack/request'
3
3
  require 'rack/utils'
4
4
 
5
5
  module Rack
6
- # Rack::ShowStatus catches all empty responses the app it wraps and
7
- # replaces them with a site explaining the error.
6
+ # Rack::ShowStatus catches all empty responses and replaces them
7
+ # with a site explaining the error.
8
8
  #
9
9
  # Additional details can be put into <tt>rack.showstatus.detail</tt>
10
10
  # and will be shown as HTML. If such details exist, the error page
@@ -19,16 +19,23 @@ module Rack
19
19
  def call(env)
20
20
  status, headers, body = @app.call(env)
21
21
  headers = Utils::HeaderHash.new(headers)
22
- empty = headers['Content-Length'].to_i <= 0
22
+ empty = headers[CONTENT_LENGTH].to_i <= 0
23
23
 
24
24
  # client or server error, or explicit message
25
25
  if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
26
- req = Rack::Request.new(env)
26
+ # This double assignment is to prevent an "unused variable" warning on
27
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
28
+ req = req = Rack::Request.new(env)
29
+
27
30
  message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
28
- detail = env["rack.showstatus.detail"] || message
31
+
32
+ # This double assignment is to prevent an "unused variable" warning on
33
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
34
+ detail = detail = env["rack.showstatus.detail"] || message
35
+
29
36
  body = @template.result(binding)
30
37
  size = Rack::Utils.bytesize(body)
31
- [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
38
+ [status, headers.merge(CONTENT_TYPE => "text/html", CONTENT_LENGTH => size.to_s), [body]]
32
39
  else
33
40
  [status, headers, body]
34
41
  end
@@ -89,7 +96,7 @@ TEMPLATE = <<'HTML'
89
96
  </table>
90
97
  </div>
91
98
  <div id="info">
92
- <p><%= detail %></p>
99
+ <p><%=h detail %></p>
93
100
  </div>
94
101
 
95
102
  <div id="explanation">
data/lib/rack/static.rb CHANGED
@@ -1,38 +1,150 @@
1
1
  module Rack
2
2
 
3
3
  # The Rack::Static middleware intercepts requests for static files
4
- # (javascript files, images, stylesheets, etc) based on the url prefixes
5
- # passed in the options, and serves them using a Rack::File object. This
6
- # allows a Rack stack to serve both static and dynamic content.
4
+ # (javascript files, images, stylesheets, etc) based on the url prefixes or
5
+ # route mappings passed in the options, and serves them using a Rack::File
6
+ # object. This allows a Rack stack to serve both static and dynamic content.
7
7
  #
8
8
  # Examples:
9
+ #
10
+ # Serve all requests beginning with /media from the "media" folder located
11
+ # in the current directory (ie media/*):
12
+ #
9
13
  # use Rack::Static, :urls => ["/media"]
10
- # will serve all requests beginning with /media from the "media" folder
11
- # located in the current directory (ie media/*).
14
+ #
15
+ # Serve all requests beginning with /css or /images from the folder "public"
16
+ # in the current directory (ie public/css/* and public/images/*):
12
17
  #
13
18
  # use Rack::Static, :urls => ["/css", "/images"], :root => "public"
14
- # will serve all requests beginning with /css or /images from the folder
15
- # "public" in the current directory (ie public/css/* and public/images/*)
16
-
19
+ #
20
+ # Serve all requests to / with "index.html" from the folder "public" in the
21
+ # current directory (ie public/index.html):
22
+ #
23
+ # use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
24
+ #
25
+ # Serve all requests normally from the folder "public" in the current
26
+ # directory but uses index.html as default route for "/"
27
+ #
28
+ # use Rack::Static, :urls => [""], :root => 'public', :index =>
29
+ # 'index.html'
30
+ #
31
+ # Set custom HTTP Headers for based on rules:
32
+ #
33
+ # use Rack::Static, :root => 'public',
34
+ # :header_rules => [
35
+ # [rule, {header_field => content, header_field => content}],
36
+ # [rule, {header_field => content}]
37
+ # ]
38
+ #
39
+ # Rules for selecting files:
40
+ #
41
+ # 1) All files
42
+ # Provide the :all symbol
43
+ # :all => Matches every file
44
+ #
45
+ # 2) Folders
46
+ # Provide the folder path as a string
47
+ # '/folder' or '/folder/subfolder' => Matches files in a certain folder
48
+ #
49
+ # 3) File Extensions
50
+ # Provide the file extensions as an array
51
+ # ['css', 'js'] or %w(css js) => Matches files ending in .css or .js
52
+ #
53
+ # 4) Regular Expressions / Regexp
54
+ # Provide a regular expression
55
+ # %r{\.(?:css|js)\z} => Matches files ending in .css or .js
56
+ # /\.(?:eot|ttf|otf|woff2|woff|svg)\z/ => Matches files ending in
57
+ # the most common web font formats (.eot, .ttf, .otf, .woff2, .woff, .svg)
58
+ # Note: This Regexp is available as a shortcut, using the :fonts rule
59
+ #
60
+ # 5) Font Shortcut
61
+ # Provide the :fonts symbol
62
+ # :fonts => Uses the Regexp rule stated right above to match all common web font endings
63
+ #
64
+ # Rule Ordering:
65
+ # Rules are applied in the order that they are provided.
66
+ # List rather general rules above special ones.
67
+ #
68
+ # Complete example use case including HTTP header rules:
69
+ #
70
+ # use Rack::Static, :root => 'public',
71
+ # :header_rules => [
72
+ # # Cache all static files in public caches (e.g. Rack::Cache)
73
+ # # as well as in the browser
74
+ # [:all, {'Cache-Control' => 'public, max-age=31536000'}],
75
+ #
76
+ # # Provide web fonts with cross-origin access-control-headers
77
+ # # Firefox requires this when serving assets using a Content Delivery Network
78
+ # [:fonts, {'Access-Control-Allow-Origin' => '*'}]
79
+ # ]
80
+ #
17
81
  class Static
18
82
 
19
83
  def initialize(app, options={})
20
84
  @app = app
21
85
  @urls = options[:urls] || ["/favicon.ico"]
86
+ @index = options[:index]
22
87
  root = options[:root] || Dir.pwd
88
+
89
+ # HTTP Headers
90
+ @header_rules = options[:header_rules] || []
91
+ # Allow for legacy :cache_control option while prioritizing global header_rules setting
92
+ @header_rules.insert(0, [:all, {'Cache-Control' => options[:cache_control]}]) if options[:cache_control]
93
+
23
94
  @file_server = Rack::File.new(root)
24
95
  end
25
96
 
97
+ def overwrite_file_path(path)
98
+ @urls.kind_of?(Hash) && @urls.key?(path) || @index && path =~ /\/$/
99
+ end
100
+
101
+ def route_file(path)
102
+ @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
103
+ end
104
+
105
+ def can_serve(path)
106
+ route_file(path) || overwrite_file_path(path)
107
+ end
108
+
26
109
  def call(env)
27
- path = env["PATH_INFO"]
28
- can_serve = @urls.any? { |url| path.index(url) == 0 }
110
+ path = env[PATH_INFO]
111
+
112
+ if can_serve(path)
113
+ env["PATH_INFO"] = (path =~ /\/$/ ? path + @index : @urls[path]) if overwrite_file_path(path)
114
+ path = env["PATH_INFO"]
115
+ response = @file_server.call(env)
29
116
 
30
- if can_serve
31
- @file_server.call(env)
117
+ headers = response[1]
118
+ applicable_rules(path).each do |rule, new_headers|
119
+ new_headers.each { |field, content| headers[field] = content }
120
+ end
121
+
122
+ response
32
123
  else
33
124
  @app.call(env)
34
125
  end
35
126
  end
36
127
 
128
+ # Convert HTTP header rules to HTTP headers
129
+ def applicable_rules(path)
130
+ @header_rules.find_all do |rule, new_headers|
131
+ case rule
132
+ when :all
133
+ true
134
+ when :fonts
135
+ path =~ /\.(?:ttf|otf|eot|woff2|woff|svg)\z/
136
+ when String
137
+ path = ::Rack::Utils.unescape(path)
138
+ path.start_with?(rule) || path.start_with?('/' + rule)
139
+ when Array
140
+ path =~ /\.(#{rule.join('|')})\z/
141
+ when Regexp
142
+ path =~ rule
143
+ else
144
+ false
145
+ end
146
+ end
147
+ end
148
+
37
149
  end
38
150
  end
@@ -0,0 +1,22 @@
1
+ require 'rack/body_proxy'
2
+
3
+ module Rack
4
+
5
+ # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
6
+ # Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter
7
+ # https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ
8
+ class TempfileReaper
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ env['rack.tempfiles'] ||= []
15
+ status, headers, body = @app.call(env)
16
+ body_proxy = BodyProxy.new(body) do
17
+ env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
18
+ end
19
+ [status, headers, body_proxy]
20
+ end
21
+ end
22
+ end
data/lib/rack/urlmap.rb CHANGED
@@ -12,6 +12,9 @@ module Rack
12
12
  # first, since they are most specific.
13
13
 
14
14
  class URLMap
15
+ NEGATIVE_INFINITY = -1.0 / 0.0
16
+ INFINITY = 1.0 / 0.0
17
+
15
18
  def initialize(map = {})
16
19
  remap(map)
17
20
  end
@@ -27,29 +30,60 @@ module Rack
27
30
  unless location[0] == ?/
28
31
  raise ArgumentError, "paths need to start with /"
29
32
  end
33
+
30
34
  location = location.chomp('/')
31
35
  match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
32
36
 
33
37
  [host, location, match, app]
34
- }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
38
+ }.sort_by do |(host, location, _, _)|
39
+ [host ? -host.size : INFINITY, -location.size]
40
+ end
35
41
  end
36
42
 
37
43
  def call(env)
38
- path = env["PATH_INFO"].to_s
44
+ path = env[PATH_INFO]
39
45
  script_name = env['SCRIPT_NAME']
40
- hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
41
- @mapping.each { |host, location, match, app|
42
- next unless (hHost == host || sName == host \
43
- || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
44
- next unless path =~ match && rest = $1
45
- next unless rest.empty? || rest[0] == ?/
46
-
47
- return app.call(
48
- env.merge(
49
- 'SCRIPT_NAME' => (script_name + location),
50
- 'PATH_INFO' => rest))
51
- }
52
- [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
46
+ hHost = env['HTTP_HOST']
47
+ sName = env['SERVER_NAME']
48
+ sPort = env['SERVER_PORT']
49
+
50
+ @mapping.each do |host, location, match, app|
51
+ unless casecmp?(hHost, host) \
52
+ || casecmp?(sName, host) \
53
+ || (!host && (casecmp?(hHost, sName) ||
54
+ casecmp?(hHost, sName+':'+sPort)))
55
+ next
56
+ end
57
+
58
+ next unless m = match.match(path.to_s)
59
+
60
+ rest = m[1]
61
+ next unless !rest || rest.empty? || rest[0] == ?/
62
+
63
+ env['SCRIPT_NAME'] = (script_name + location)
64
+ env['PATH_INFO'] = rest
65
+
66
+ return app.call(env)
67
+ end
68
+
69
+ [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
70
+
71
+ ensure
72
+ env['PATH_INFO'] = path
73
+ env['SCRIPT_NAME'] = script_name
74
+ end
75
+
76
+ private
77
+ def casecmp?(v1, v2)
78
+ # if both nil, or they're the same string
79
+ return true if v1 == v2
80
+
81
+ # if either are nil... (but they're not the same)
82
+ return false if v1.nil?
83
+ return false if v2.nil?
84
+
85
+ # otherwise check they're not case-insensitive the same
86
+ v1.casecmp(v2).zero?
53
87
  end
54
88
  end
55
89
  end