rack 1.6.12 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (142) hide show
  1. checksums.yaml +5 -5
  2. data/COPYING +1 -1
  3. data/HISTORY.md +138 -8
  4. data/README.rdoc +17 -25
  5. data/Rakefile +6 -14
  6. data/SPEC +10 -11
  7. data/contrib/rack_logo.svg +164 -111
  8. data/example/protectedlobster.rb +1 -1
  9. data/example/protectedlobster.ru +1 -1
  10. data/lib/rack.rb +70 -21
  11. data/lib/rack/auth/abstract/request.rb +5 -1
  12. data/lib/rack/auth/digest/params.rb +2 -3
  13. data/lib/rack/auth/digest/request.rb +1 -1
  14. data/lib/rack/body_proxy.rb +14 -9
  15. data/lib/rack/builder.rb +3 -3
  16. data/lib/rack/chunked.rb +5 -5
  17. data/lib/rack/{commonlogger.rb → common_logger.rb} +3 -3
  18. data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
  19. data/lib/rack/content_length.rb +2 -2
  20. data/lib/rack/deflater.rb +4 -39
  21. data/lib/rack/directory.rb +66 -54
  22. data/lib/rack/etag.rb +4 -3
  23. data/lib/rack/events.rb +154 -0
  24. data/lib/rack/file.rb +64 -40
  25. data/lib/rack/handler.rb +3 -25
  26. data/lib/rack/handler/cgi.rb +15 -16
  27. data/lib/rack/handler/fastcgi.rb +13 -14
  28. data/lib/rack/handler/lsws.rb +11 -11
  29. data/lib/rack/handler/scgi.rb +15 -15
  30. data/lib/rack/handler/thin.rb +3 -0
  31. data/lib/rack/handler/webrick.rb +24 -26
  32. data/lib/rack/head.rb +15 -17
  33. data/lib/rack/lint.rb +40 -40
  34. data/lib/rack/lobster.rb +1 -1
  35. data/lib/rack/lock.rb +6 -10
  36. data/lib/rack/logger.rb +2 -2
  37. data/lib/rack/media_type.rb +38 -0
  38. data/lib/rack/{methodoverride.rb → method_override.rb} +6 -10
  39. data/lib/rack/mime.rb +18 -5
  40. data/lib/rack/mock.rb +36 -54
  41. data/lib/rack/multipart.rb +35 -6
  42. data/lib/rack/multipart/generator.rb +5 -5
  43. data/lib/rack/multipart/parser.rb +272 -158
  44. data/lib/rack/multipart/uploaded_file.rb +1 -2
  45. data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
  46. data/lib/rack/query_parser.rb +192 -0
  47. data/lib/rack/recursive.rb +8 -8
  48. data/lib/rack/request.rb +383 -307
  49. data/lib/rack/response.rb +130 -57
  50. data/lib/rack/rewindable_input.rb +1 -12
  51. data/lib/rack/runtime.rb +10 -18
  52. data/lib/rack/sendfile.rb +5 -7
  53. data/lib/rack/server.rb +30 -23
  54. data/lib/rack/session/abstract/id.rb +108 -138
  55. data/lib/rack/session/cookie.rb +26 -28
  56. data/lib/rack/session/memcache.rb +8 -14
  57. data/lib/rack/session/pool.rb +14 -21
  58. data/lib/rack/show_exceptions.rb +386 -0
  59. data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
  60. data/lib/rack/static.rb +30 -5
  61. data/lib/rack/tempfile_reaper.rb +2 -2
  62. data/lib/rack/urlmap.rb +15 -14
  63. data/lib/rack/utils.rb +136 -211
  64. data/rack.gemspec +7 -5
  65. data/test/builder/an_underscore_app.rb +5 -0
  66. data/test/builder/options.ru +1 -1
  67. data/test/cgi/test.fcgi +1 -0
  68. data/test/cgi/test.gz +0 -0
  69. data/test/helper.rb +34 -0
  70. data/test/multipart/filename_with_encoded_words +7 -0
  71. data/test/multipart/filename_with_single_quote +7 -0
  72. data/test/multipart/quoted +15 -0
  73. data/test/multipart/rack-logo.png +0 -0
  74. data/test/multipart/unity3d_wwwform +11 -0
  75. data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
  76. data/test/spec_auth_basic.rb +27 -19
  77. data/test/spec_auth_digest.rb +47 -46
  78. data/test/spec_body_proxy.rb +27 -27
  79. data/test/spec_builder.rb +51 -41
  80. data/test/spec_cascade.rb +24 -22
  81. data/test/spec_cgi.rb +49 -67
  82. data/test/spec_chunked.rb +37 -35
  83. data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
  84. data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
  85. data/test/spec_config.rb +3 -2
  86. data/test/spec_content_length.rb +18 -17
  87. data/test/spec_content_type.rb +13 -12
  88. data/test/spec_deflater.rb +85 -49
  89. data/test/spec_directory.rb +87 -27
  90. data/test/spec_etag.rb +32 -31
  91. data/test/spec_events.rb +133 -0
  92. data/test/spec_fastcgi.rb +50 -72
  93. data/test/spec_file.rb +120 -77
  94. data/test/spec_handler.rb +19 -34
  95. data/test/spec_head.rb +15 -14
  96. data/test/spec_lint.rb +164 -199
  97. data/test/spec_lobster.rb +24 -23
  98. data/test/spec_lock.rb +69 -39
  99. data/test/spec_logger.rb +4 -3
  100. data/test/spec_media_type.rb +42 -0
  101. data/test/{spec_methodoverride.rb → spec_method_override.rb} +22 -37
  102. data/test/spec_mime.rb +19 -19
  103. data/test/spec_mock.rb +206 -144
  104. data/test/spec_multipart.rb +322 -200
  105. data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
  106. data/test/spec_recursive.rb +17 -14
  107. data/test/spec_request.rb +768 -607
  108. data/test/spec_response.rb +215 -112
  109. data/test/spec_rewindable_input.rb +50 -40
  110. data/test/spec_runtime.rb +11 -10
  111. data/test/spec_sendfile.rb +30 -35
  112. data/test/spec_server.rb +78 -52
  113. data/test/spec_session_abstract_id.rb +11 -33
  114. data/test/spec_session_abstract_session_hash.rb +45 -0
  115. data/test/spec_session_cookie.rb +99 -67
  116. data/test/spec_session_memcache.rb +63 -101
  117. data/test/spec_session_pool.rb +48 -84
  118. data/test/spec_show_exceptions.rb +80 -0
  119. data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
  120. data/test/spec_static.rb +71 -32
  121. data/test/spec_tempfile_reaper.rb +11 -10
  122. data/test/spec_thin.rb +55 -50
  123. data/test/spec_urlmap.rb +79 -78
  124. data/test/spec_utils.rb +441 -346
  125. data/test/spec_version.rb +2 -8
  126. data/test/spec_webrick.rb +93 -68
  127. data/test/static/foo.html +1 -0
  128. data/test/testrequest.rb +1 -1
  129. data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
  130. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
  131. metadata +102 -66
  132. data/KNOWN-ISSUES +0 -44
  133. data/lib/rack/backports/uri/common_18.rb +0 -56
  134. data/lib/rack/backports/uri/common_192.rb +0 -52
  135. data/lib/rack/backports/uri/common_193.rb +0 -29
  136. data/lib/rack/handler/evented_mongrel.rb +0 -8
  137. data/lib/rack/handler/mongrel.rb +0 -106
  138. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  139. data/lib/rack/showexceptions.rb +0 -387
  140. data/lib/rack/utils/okjson.rb +0 -600
  141. data/test/spec_mongrel.rb +0 -182
  142. data/test/spec_showexceptions.rb +0 -98
@@ -3,6 +3,7 @@ require 'zlib'
3
3
  require 'rack/request'
4
4
  require 'rack/response'
5
5
  require 'rack/session/abstract/id'
6
+ require 'json'
6
7
 
7
8
  module Rack
8
9
 
@@ -44,7 +45,7 @@ module Rack
44
45
  # })
45
46
  #
46
47
 
47
- class Cookie < Abstract::PersistedSecure
48
+ class Cookie < Abstract::Persisted
48
49
  # Encode session cookies as Base64
49
50
  class Base64
50
51
  def encode(str)
@@ -71,23 +72,23 @@ module Rack
71
72
  # valid JSON composite type, either a Hash or an Array.
72
73
  class JSON < Base64
73
74
  def encode(obj)
74
- super(::Rack::Utils::OkJson.encode(obj))
75
+ super(::JSON.dump(obj))
75
76
  end
76
77
 
77
78
  def decode(str)
78
79
  return unless str
79
- ::Rack::Utils::OkJson.decode(super(str)) rescue nil
80
+ ::JSON.parse(super(str)) rescue nil
80
81
  end
81
82
  end
82
83
 
83
84
  class ZipJSON < Base64
84
85
  def encode(obj)
85
- super(Zlib::Deflate.deflate(::Rack::Utils::OkJson.encode(obj)))
86
+ super(Zlib::Deflate.deflate(::JSON.dump(obj)))
86
87
  end
87
88
 
88
89
  def decode(str)
89
90
  return unless str
90
- ::Rack::Utils::OkJson.decode(Zlib::Inflate.inflate(super(str)))
91
+ ::JSON.parse(Zlib::Inflate.inflate(super(str)))
91
92
  rescue
92
93
  nil
93
94
  end
@@ -104,7 +105,9 @@ module Rack
104
105
 
105
106
  def initialize(app, options={})
106
107
  @secrets = options.values_at(:secret, :old_secret).compact
107
- warn <<-MSG unless @secrets.size >= 1
108
+ @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
109
+
110
+ warn <<-MSG unless secure?(options)
108
111
  SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
109
112
  This poses a security threat. It is strongly recommended that you
110
113
  provide a secret to prevent exploits that may be possible from crafted
@@ -119,19 +122,18 @@ module Rack
119
122
 
120
123
  private
121
124
 
122
- def get_session(env, sid)
123
- data = unpacked_cookie_data(env)
125
+ def find_session(req, sid)
126
+ data = unpacked_cookie_data(req)
124
127
  data = persistent_session_id!(data)
125
128
  [data["session_id"], data]
126
129
  end
127
130
 
128
- def extract_session_id(env)
129
- unpacked_cookie_data(env)["session_id"]
131
+ def extract_session_id(request)
132
+ unpacked_cookie_data(request)["session_id"]
130
133
  end
131
134
 
132
- def unpacked_cookie_data(env)
133
- env["rack.session.unpacked_cookie_data"] ||= begin
134
- request = Rack::Request.new(env)
135
+ def unpacked_cookie_data(request)
136
+ request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
135
137
  session_data = request.cookies[@key]
136
138
 
137
139
  if @secrets.size > 0 && session_data
@@ -141,7 +143,7 @@ module Rack
141
143
  session_data = nil unless digest_match?(session_data, digest)
142
144
  end
143
145
 
144
- coder.decode(session_data) || {}
146
+ request.set_header(k, coder.decode(session_data) || {})
145
147
  end
146
148
  end
147
149
 
@@ -151,16 +153,7 @@ module Rack
151
153
  data
152
154
  end
153
155
 
154
- class SessionId < DelegateClass(Session::SessionId)
155
- attr_reader :cookie_value
156
-
157
- def initialize(session_id, cookie_value)
158
- super(session_id)
159
- @cookie_value = cookie_value
160
- end
161
- end
162
-
163
- def set_session(env, session_id, session, options)
156
+ def write_session(req, session_id, session, options)
164
157
  session = session.merge("session_id" => session_id)
165
158
  session_data = coder.encode(session)
166
159
 
@@ -169,14 +162,14 @@ module Rack
169
162
  end
170
163
 
171
164
  if session_data.size > (4096 - @key.size)
172
- env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
165
+ req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
173
166
  nil
174
167
  else
175
- SessionId.new(session_id, session_data)
168
+ session_data
176
169
  end
177
170
  end
178
171
 
179
- def destroy_session(env, session_id, options)
172
+ def delete_session(req, session_id, options)
180
173
  # Nothing to do here, data is in the client
181
174
  generate_sid unless options[:drop]
182
175
  end
@@ -189,7 +182,12 @@ module Rack
189
182
  end
190
183
 
191
184
  def generate_hmac(data, secret)
192
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
185
+ OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
186
+ end
187
+
188
+ def secure?(options)
189
+ @secrets.size >= 1 ||
190
+ (options[:coder] && options[:let_coder_handle_secure_encoding])
193
191
  end
194
192
 
195
193
  end
@@ -19,7 +19,7 @@ module Rack
19
19
  # Note that memcache does drop data before it may be listed to expire. For
20
20
  # a full description of behaviour, please see memcache's documentation.
21
21
 
22
- class Memcache < Abstract::PersistedSecure
22
+ class Memcache < Abstract::ID
23
23
  attr_reader :mutex, :pool
24
24
 
25
25
  DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
@@ -34,7 +34,7 @@ module Rack
34
34
  mopts = @default_options.reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
35
35
 
36
36
  @pool = options[:cache] || MemCache.new(mserv, mopts)
37
- unless @pool.active? and @pool.servers.any?{|c| c.alive? }
37
+ unless @pool.active? and @pool.servers.any?(&:alive?)
38
38
  raise 'No memcache servers'
39
39
  end
40
40
  end
@@ -42,15 +42,15 @@ module Rack
42
42
  def generate_sid
43
43
  loop do
44
44
  sid = super
45
- break sid unless @pool.get(sid.private_id, true)
45
+ break sid unless @pool.get(sid, true)
46
46
  end
47
47
  end
48
48
 
49
49
  def get_session(env, sid)
50
50
  with_lock(env) do
51
- unless sid and session = get_session_with_fallback(sid)
51
+ unless sid and session = @pool.get(sid)
52
52
  sid, session = generate_sid, {}
53
- unless /^STORED/ =~ @pool.add(sid.private_id, session)
53
+ unless /^STORED/ =~ @pool.add(sid, session)
54
54
  raise "Session collision on '#{sid.inspect}'"
55
55
  end
56
56
  end
@@ -63,21 +63,20 @@ module Rack
63
63
  expiry = expiry.nil? ? 0 : expiry + 1
64
64
 
65
65
  with_lock(env) do
66
- @pool.set session_id.private_id, new_session, expiry
66
+ @pool.set session_id, new_session, expiry
67
67
  session_id
68
68
  end
69
69
  end
70
70
 
71
71
  def destroy_session(env, session_id, options)
72
72
  with_lock(env) do
73
- @pool.delete(session_id.public_id)
74
- @pool.delete(session_id.private_id)
73
+ @pool.delete(session_id)
75
74
  generate_sid unless options[:drop]
76
75
  end
77
76
  end
78
77
 
79
78
  def with_lock(env)
80
- @mutex.lock if env['rack.multithread']
79
+ @mutex.lock if env[RACK_MULTITHREAD]
81
80
  yield
82
81
  rescue MemCache::MemCacheError, Errno::ECONNREFUSED
83
82
  if $VERBOSE
@@ -89,11 +88,6 @@ module Rack
89
88
  @mutex.unlock if @mutex.locked?
90
89
  end
91
90
 
92
- private
93
-
94
- def get_session_with_fallback(sid)
95
- @pool.get(sid.private_id) || @pool.get(sid.public_id)
96
- end
97
91
  end
98
92
  end
99
93
  end
@@ -24,7 +24,7 @@ module Rack
24
24
  # )
25
25
  # Rack::Handler::WEBrick.run sessioned
26
26
 
27
- class Pool < Abstract::PersistedSecure
27
+ class Pool < Abstract::Persisted
28
28
  attr_reader :mutex, :pool
29
29
  DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
30
30
 
@@ -37,47 +37,40 @@ module Rack
37
37
  def generate_sid
38
38
  loop do
39
39
  sid = super
40
- break sid unless @pool.key? sid.private_id
40
+ break sid unless @pool.key? sid
41
41
  end
42
42
  end
43
43
 
44
- def get_session(env, sid)
45
- with_lock(env) do
46
- unless sid and session = get_session_with_fallback(sid)
44
+ def find_session(req, sid)
45
+ with_lock(req) do
46
+ unless sid and session = @pool[sid]
47
47
  sid, session = generate_sid, {}
48
- @pool.store sid.private_id, session
48
+ @pool.store sid, session
49
49
  end
50
50
  [sid, session]
51
51
  end
52
52
  end
53
53
 
54
- def set_session(env, session_id, new_session, options)
55
- with_lock(env) do
56
- @pool.store session_id.private_id, new_session
54
+ def write_session(req, session_id, new_session, options)
55
+ with_lock(req) do
56
+ @pool.store session_id, new_session
57
57
  session_id
58
58
  end
59
59
  end
60
60
 
61
- def destroy_session(env, session_id, options)
62
- with_lock(env) do
63
- @pool.delete(session_id.public_id)
64
- @pool.delete(session_id.private_id)
61
+ def delete_session(req, session_id, options)
62
+ with_lock(req) do
63
+ @pool.delete(session_id)
65
64
  generate_sid unless options[:drop]
66
65
  end
67
66
  end
68
67
 
69
- def with_lock(env)
70
- @mutex.lock if env['rack.multithread']
68
+ def with_lock(req)
69
+ @mutex.lock if req.multithread?
71
70
  yield
72
71
  ensure
73
72
  @mutex.unlock if @mutex.locked?
74
73
  end
75
-
76
- private
77
-
78
- def get_session_with_fallback(sid)
79
- @pool[sid.private_id] || @pool[sid.public_id]
80
- end
81
74
  end
82
75
  end
83
76
  end
@@ -0,0 +1,386 @@
1
+ require 'ostruct'
2
+ require 'erb'
3
+ require 'rack/request'
4
+ require 'rack/utils'
5
+
6
+ module Rack
7
+ # Rack::ShowExceptions catches all exceptions raised from the app it
8
+ # wraps. It shows a useful backtrace with the sourcefile and
9
+ # clickable context, the whole Rack environment and the request
10
+ # data.
11
+ #
12
+ # Be careful when you use this on public-facing sites as it could
13
+ # reveal information helpful to attackers.
14
+
15
+ class ShowExceptions
16
+ CONTEXT = 7
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ def call(env)
23
+ @app.call(env)
24
+ rescue StandardError, LoadError, SyntaxError => e
25
+ exception_string = dump_exception(e)
26
+
27
+ env[RACK_ERRORS].puts(exception_string)
28
+ env[RACK_ERRORS].flush
29
+
30
+ if accepts_html?(env)
31
+ content_type = "text/html"
32
+ body = pretty(env, e)
33
+ else
34
+ content_type = "text/plain"
35
+ body = exception_string
36
+ end
37
+
38
+ [
39
+ 500,
40
+ {
41
+ CONTENT_TYPE => content_type,
42
+ CONTENT_LENGTH => body.bytesize.to_s,
43
+ },
44
+ [body],
45
+ ]
46
+ end
47
+
48
+ def prefers_plaintext?(env)
49
+ !accepts_html(env)
50
+ end
51
+
52
+ def accepts_html?(env)
53
+ Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html])
54
+ end
55
+ private :accepts_html?
56
+
57
+ def dump_exception(exception)
58
+ string = "#{exception.class}: #{exception.message}\n"
59
+ string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
60
+ string
61
+ end
62
+
63
+ def pretty(env, exception)
64
+ req = Rack::Request.new(env)
65
+
66
+ # This double assignment is to prevent an "unused variable" warning on
67
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
68
+ path = path = (req.script_name + req.path_info).squeeze("/")
69
+
70
+ # This double assignment is to prevent an "unused variable" warning on
71
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
72
+ frames = frames = exception.backtrace.map { |line|
73
+ frame = OpenStruct.new
74
+ if line =~ /(.*?):(\d+)(:in `(.*)')?/
75
+ frame.filename = $1
76
+ frame.lineno = $2.to_i
77
+ frame.function = $4
78
+
79
+ begin
80
+ lineno = frame.lineno-1
81
+ lines = ::File.readlines(frame.filename)
82
+ frame.pre_context_lineno = [lineno-CONTEXT, 0].max
83
+ frame.pre_context = lines[frame.pre_context_lineno...lineno]
84
+ frame.context_line = lines[lineno].chomp
85
+ frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
86
+ frame.post_context = lines[lineno+1..frame.post_context_lineno]
87
+ rescue
88
+ end
89
+
90
+ frame
91
+ else
92
+ nil
93
+ end
94
+ }.compact
95
+
96
+ TEMPLATE.result(binding)
97
+ end
98
+
99
+ def h(obj) # :nodoc:
100
+ case obj
101
+ when String
102
+ Utils.escape_html(obj)
103
+ else
104
+ Utils.escape_html(obj.inspect)
105
+ end
106
+ end
107
+
108
+ # :stopdoc:
109
+
110
+ # adapted from Django <djangoproject.com>
111
+ # Copyright (c) 2005, the Lawrence Journal-World
112
+ # Used under the modified BSD license:
113
+ # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
114
+ TEMPLATE = ERB.new(<<-'HTML'.gsub(/^ /, ''))
115
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
116
+ <html lang="en">
117
+ <head>
118
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
119
+ <meta name="robots" content="NONE,NOARCHIVE" />
120
+ <title><%=h exception.class %> at <%=h path %></title>
121
+ <style type="text/css">
122
+ html * { padding:0; margin:0; }
123
+ body * { padding:10px 20px; }
124
+ body * * { padding:0; }
125
+ body { font:small sans-serif; }
126
+ body>div { border-bottom:1px solid #ddd; }
127
+ h1 { font-weight:normal; }
128
+ h2 { margin-bottom:.8em; }
129
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
130
+ h3 { margin:1em 0 .5em 0; }
131
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
132
+ table {
133
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
134
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
135
+ thead th {
136
+ padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
137
+ font-weight:normal; font-size:11px; border:1px solid #ddd; }
138
+ tbody th { text-align:right; color:#666; padding-right:.5em; }
139
+ table.vars { margin:5px 0 2px 40px; }
140
+ table.vars td, table.req td { font-family:monospace; }
141
+ table td.code { width:100%;}
142
+ table td.code div { overflow:hidden; }
143
+ table.source th { color:#666; }
144
+ table.source td {
145
+ font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
146
+ ul.traceback { list-style-type:none; }
147
+ ul.traceback li.frame { margin-bottom:1em; }
148
+ div.context { margin: 10px 0; }
149
+ div.context ol {
150
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
151
+ div.context ol li {
152
+ font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
153
+ div.context ol.context-line li { color:black; background-color:#ccc; }
154
+ div.context ol.context-line li span { float: right; }
155
+ div.commands { margin-left: 40px; }
156
+ div.commands a { color:black; text-decoration:none; }
157
+ #summary { background: #ffc; }
158
+ #summary h2 { font-weight: normal; color: #666; }
159
+ #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
160
+ #summary ul#quicklinks li { float: left; padding: 0 1em; }
161
+ #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
162
+ #explanation { background:#eee; }
163
+ #template, #template-not-exist { background:#f6f6f6; }
164
+ #template-not-exist ul { margin: 0 0 0 20px; }
165
+ #traceback { background:#eee; }
166
+ #requestinfo { background:#f6f6f6; padding-left:120px; }
167
+ #summary table { border:none; background:transparent; }
168
+ #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
169
+ #requestinfo h3 { margin-bottom:-1em; }
170
+ .error { background: #ffc; }
171
+ .specific { color:#cc3300; font-weight:bold; }
172
+ </style>
173
+ <script type="text/javascript">
174
+ //<!--
175
+ function getElementsByClassName(oElm, strTagName, strClassName){
176
+ // Written by Jonathan Snook, http://www.snook.ca/jon;
177
+ // Add-ons by Robert Nyman, http://www.robertnyman.com
178
+ var arrElements = (strTagName == "*" && document.all)? document.all :
179
+ oElm.getElementsByTagName(strTagName);
180
+ var arrReturnElements = new Array();
181
+ strClassName = strClassName.replace(/\-/g, "\\-");
182
+ var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
183
+ var oElement;
184
+ for(var i=0; i<arrElements.length; i++){
185
+ oElement = arrElements[i];
186
+ if(oRegExp.test(oElement.className)){
187
+ arrReturnElements.push(oElement);
188
+ }
189
+ }
190
+ return (arrReturnElements)
191
+ }
192
+ function hideAll(elems) {
193
+ for (var e = 0; e < elems.length; e++) {
194
+ elems[e].style.display = 'none';
195
+ }
196
+ }
197
+ window.onload = function() {
198
+ hideAll(getElementsByClassName(document, 'table', 'vars'));
199
+ hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
200
+ hideAll(getElementsByClassName(document, 'ol', 'post-context'));
201
+ }
202
+ function toggle() {
203
+ for (var i = 0; i < arguments.length; i++) {
204
+ var e = document.getElementById(arguments[i]);
205
+ if (e) {
206
+ e.style.display = e.style.display == 'none' ? 'block' : 'none';
207
+ }
208
+ }
209
+ return false;
210
+ }
211
+ function varToggle(link, id) {
212
+ toggle('v' + id);
213
+ var s = link.getElementsByTagName('span')[0];
214
+ var uarr = String.fromCharCode(0x25b6);
215
+ var darr = String.fromCharCode(0x25bc);
216
+ s.innerHTML = s.innerHTML == uarr ? darr : uarr;
217
+ return false;
218
+ }
219
+ //-->
220
+ </script>
221
+ </head>
222
+ <body>
223
+
224
+ <div id="summary">
225
+ <h1><%=h exception.class %> at <%=h path %></h1>
226
+ <h2><%=h exception.message %></h2>
227
+ <table><tr>
228
+ <th>Ruby</th>
229
+ <td>
230
+ <% if first = frames.first %>
231
+ <code><%=h first.filename %></code>: in <code><%=h first.function %></code>, line <%=h frames.first.lineno %>
232
+ <% else %>
233
+ unknown location
234
+ <% end %>
235
+ </td>
236
+ </tr><tr>
237
+ <th>Web</th>
238
+ <td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
239
+ </tr></table>
240
+
241
+ <h3>Jump to:</h3>
242
+ <ul id="quicklinks">
243
+ <li><a href="#get-info">GET</a></li>
244
+ <li><a href="#post-info">POST</a></li>
245
+ <li><a href="#cookie-info">Cookies</a></li>
246
+ <li><a href="#env-info">ENV</a></li>
247
+ </ul>
248
+ </div>
249
+
250
+ <div id="traceback">
251
+ <h2>Traceback <span>(innermost first)</span></h2>
252
+ <ul class="traceback">
253
+ <% frames.each { |frame| %>
254
+ <li class="frame">
255
+ <code><%=h frame.filename %></code>: in <code><%=h frame.function %></code>
256
+
257
+ <% if frame.context_line %>
258
+ <div class="context" id="c<%=h frame.object_id %>">
259
+ <% if frame.pre_context %>
260
+ <ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
261
+ <% frame.pre_context.each { |line| %>
262
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
263
+ <% } %>
264
+ </ol>
265
+ <% end %>
266
+
267
+ <ol start="<%=h frame.lineno %>" class="context-line">
268
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %><span>...</span></li></ol>
269
+
270
+ <% if frame.post_context %>
271
+ <ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
272
+ <% frame.post_context.each { |line| %>
273
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
274
+ <% } %>
275
+ </ol>
276
+ <% end %>
277
+ </div>
278
+ <% end %>
279
+ </li>
280
+ <% } %>
281
+ </ul>
282
+ </div>
283
+
284
+ <div id="requestinfo">
285
+ <h2>Request information</h2>
286
+
287
+ <h3 id="get-info">GET</h3>
288
+ <% if req.GET and not req.GET.empty? %>
289
+ <table class="req">
290
+ <thead>
291
+ <tr>
292
+ <th>Variable</th>
293
+ <th>Value</th>
294
+ </tr>
295
+ </thead>
296
+ <tbody>
297
+ <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
298
+ <tr>
299
+ <td><%=h key %></td>
300
+ <td class="code"><div><%=h val.inspect %></div></td>
301
+ </tr>
302
+ <% } %>
303
+ </tbody>
304
+ </table>
305
+ <% else %>
306
+ <p>No GET data.</p>
307
+ <% end %>
308
+
309
+ <h3 id="post-info">POST</h3>
310
+ <% if req.POST and not req.POST.empty? %>
311
+ <table class="req">
312
+ <thead>
313
+ <tr>
314
+ <th>Variable</th>
315
+ <th>Value</th>
316
+ </tr>
317
+ </thead>
318
+ <tbody>
319
+ <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
320
+ <tr>
321
+ <td><%=h key %></td>
322
+ <td class="code"><div><%=h val.inspect %></div></td>
323
+ </tr>
324
+ <% } %>
325
+ </tbody>
326
+ </table>
327
+ <% else %>
328
+ <p>No POST data.</p>
329
+ <% end %>
330
+
331
+
332
+ <h3 id="cookie-info">COOKIES</h3>
333
+ <% unless req.cookies.empty? %>
334
+ <table class="req">
335
+ <thead>
336
+ <tr>
337
+ <th>Variable</th>
338
+ <th>Value</th>
339
+ </tr>
340
+ </thead>
341
+ <tbody>
342
+ <% req.cookies.each { |key, val| %>
343
+ <tr>
344
+ <td><%=h key %></td>
345
+ <td class="code"><div><%=h val.inspect %></div></td>
346
+ </tr>
347
+ <% } %>
348
+ </tbody>
349
+ </table>
350
+ <% else %>
351
+ <p>No cookie data.</p>
352
+ <% end %>
353
+
354
+ <h3 id="env-info">Rack ENV</h3>
355
+ <table class="req">
356
+ <thead>
357
+ <tr>
358
+ <th>Variable</th>
359
+ <th>Value</th>
360
+ </tr>
361
+ </thead>
362
+ <tbody>
363
+ <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
364
+ <tr>
365
+ <td><%=h key %></td>
366
+ <td class="code"><div><%=h val %></div></td>
367
+ </tr>
368
+ <% } %>
369
+ </tbody>
370
+ </table>
371
+
372
+ </div>
373
+
374
+ <div id="explanation">
375
+ <p>
376
+ You're seeing this error because you use <code>Rack::ShowExceptions</code>.
377
+ </p>
378
+ </div>
379
+
380
+ </body>
381
+ </html>
382
+ HTML
383
+
384
+ # :startdoc:
385
+ end
386
+ end