egalite 0.0.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 (113) hide show
  1. data/.gitignore +17 -0
  2. data/README.md +91 -0
  3. data/auth/basic.rb +32 -0
  4. data/blank.rb +53 -0
  5. data/egalite.rb +742 -0
  6. data/errorconsole.rb +77 -0
  7. data/examples/simple/example.rb +39 -0
  8. data/examples/simple/pages/test.html +15 -0
  9. data/examples/simple_db/example_db.rb +103 -0
  10. data/examples/simple_db/pages/edit.html +6 -0
  11. data/helper.rb +251 -0
  12. data/keitai/keitai.rb +107 -0
  13. data/keitai/ketai.rb +11 -0
  14. data/keitai/rack/ketai/carrier/abstract.rb +131 -0
  15. data/keitai/rack/ketai/carrier/au.rb +78 -0
  16. data/keitai/rack/ketai/carrier/docomo.rb +80 -0
  17. data/keitai/rack/ketai/carrier/emoji/ausjisstrtoemojiid.rb +1391 -0
  18. data/keitai/rack/ketai/carrier/emoji/docomosjisstrtoemojiid.rb +759 -0
  19. data/keitai/rack/ketai/carrier/emoji/emojidata.rb +836 -0
  20. data/keitai/rack/ketai/carrier/emoji/softbankutf8strtoemojiid.rb +1119 -0
  21. data/keitai/rack/ketai/carrier/emoji/softbankwebcodetoutf8str.rb +499 -0
  22. data/keitai/rack/ketai/carrier/iphone.rb +8 -0
  23. data/keitai/rack/ketai/carrier/softbank.rb +82 -0
  24. data/keitai/rack/ketai/carrier.rb +17 -0
  25. data/keitai/rack/ketai/middleware.rb +24 -0
  26. data/m17n.rb +193 -0
  27. data/rack/auth/abstract/handler.rb +37 -0
  28. data/rack/auth/abstract/request.rb +37 -0
  29. data/rack/auth/basic.rb +58 -0
  30. data/rack/auth/digest/md5.rb +124 -0
  31. data/rack/auth/digest/nonce.rb +51 -0
  32. data/rack/auth/digest/params.rb +55 -0
  33. data/rack/auth/digest/request.rb +40 -0
  34. data/rack/builder.rb +80 -0
  35. data/rack/cascade.rb +41 -0
  36. data/rack/chunked.rb +49 -0
  37. data/rack/commonlogger.rb +49 -0
  38. data/rack/conditionalget.rb +47 -0
  39. data/rack/config.rb +15 -0
  40. data/rack/content_length.rb +29 -0
  41. data/rack/content_type.rb +23 -0
  42. data/rack/deflater.rb +96 -0
  43. data/rack/directory.rb +157 -0
  44. data/rack/etag.rb +32 -0
  45. data/rack/file.rb +92 -0
  46. data/rack/handler/cgi.rb +62 -0
  47. data/rack/handler/evented_mongrel.rb +8 -0
  48. data/rack/handler/fastcgi.rb +89 -0
  49. data/rack/handler/lsws.rb +63 -0
  50. data/rack/handler/mongrel.rb +90 -0
  51. data/rack/handler/scgi.rb +59 -0
  52. data/rack/handler/swiftiplied_mongrel.rb +8 -0
  53. data/rack/handler/thin.rb +18 -0
  54. data/rack/handler/webrick.rb +73 -0
  55. data/rack/handler.rb +88 -0
  56. data/rack/head.rb +19 -0
  57. data/rack/lint.rb +567 -0
  58. data/rack/lobster.rb +65 -0
  59. data/rack/lock.rb +16 -0
  60. data/rack/logger.rb +20 -0
  61. data/rack/methodoverride.rb +27 -0
  62. data/rack/mime.rb +208 -0
  63. data/rack/mock.rb +190 -0
  64. data/rack/nulllogger.rb +18 -0
  65. data/rack/recursive.rb +61 -0
  66. data/rack/reloader.rb +109 -0
  67. data/rack/request.rb +273 -0
  68. data/rack/response.rb +150 -0
  69. data/rack/rewindable_input.rb +103 -0
  70. data/rack/runtime.rb +27 -0
  71. data/rack/sendfile.rb +144 -0
  72. data/rack/server.rb +271 -0
  73. data/rack/session/abstract/id.rb +140 -0
  74. data/rack/session/cookie.rb +90 -0
  75. data/rack/session/memcache.rb +119 -0
  76. data/rack/session/pool.rb +100 -0
  77. data/rack/showexceptions.rb +349 -0
  78. data/rack/showstatus.rb +106 -0
  79. data/rack/static.rb +38 -0
  80. data/rack/urlmap.rb +55 -0
  81. data/rack/utils.rb +662 -0
  82. data/rack.rb +81 -0
  83. data/route.rb +231 -0
  84. data/sendmail.rb +222 -0
  85. data/sequel_helper.rb +20 -0
  86. data/session.rb +132 -0
  87. data/stringify_hash.rb +63 -0
  88. data/support.rb +35 -0
  89. data/template.rb +287 -0
  90. data/test/french.html +13 -0
  91. data/test/french_msg.html +3 -0
  92. data/test/m17n.txt +30 -0
  93. data/test/mobile.html +15 -0
  94. data/test/setup.rb +8 -0
  95. data/test/static/test.txt +1 -0
  96. data/test/template.html +58 -0
  97. data/test/template_inner.html +1 -0
  98. data/test/template_innerparam.html +1 -0
  99. data/test/test_auth.rb +43 -0
  100. data/test/test_blank.rb +44 -0
  101. data/test/test_csrf.rb +87 -0
  102. data/test/test_errorconsole.rb +91 -0
  103. data/test/test_handler.rb +155 -0
  104. data/test/test_helper.rb +296 -0
  105. data/test/test_keitai.rb +107 -0
  106. data/test/test_m17n.rb +129 -0
  107. data/test/test_route.rb +192 -0
  108. data/test/test_sendmail.rb +146 -0
  109. data/test/test_session.rb +83 -0
  110. data/test/test_stringify_hash.rb +67 -0
  111. data/test/test_template.rb +114 -0
  112. data/test.bat +2 -0
  113. metadata +240 -0
@@ -0,0 +1,100 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+ # THANKS:
3
+ # apeiros, for session id generation, expiry setup, and threadiness
4
+ # sergio, threadiness and bugreps
5
+
6
+ require 'rack/session/abstract/id'
7
+ require 'thread'
8
+
9
+ module Rack
10
+ module Session
11
+ # Rack::Session::Pool provides simple cookie based session management.
12
+ # Session data is stored in a hash held by @pool.
13
+ # In the context of a multithreaded environment, sessions being
14
+ # committed to the pool is done in a merging manner.
15
+ #
16
+ # The :drop option is available in rack.session.options if you wish to
17
+ # explicitly remove the session from the session cache.
18
+ #
19
+ # Example:
20
+ # myapp = MyRackApp.new
21
+ # sessioned = Rack::Session::Pool.new(myapp,
22
+ # :domain => 'foo.com',
23
+ # :expire_after => 2592000
24
+ # )
25
+ # Rack::Handler::WEBrick.run sessioned
26
+
27
+ class Pool < Abstract::ID
28
+ attr_reader :mutex, :pool
29
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
30
+
31
+ def initialize(app, options={})
32
+ super
33
+ @pool = Hash.new
34
+ @mutex = Mutex.new
35
+ end
36
+
37
+ def generate_sid
38
+ loop do
39
+ sid = super
40
+ break sid unless @pool.key? sid
41
+ end
42
+ end
43
+
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
52
+ end
53
+ session.instance_variable_set('@old', {}.merge(session))
54
+ return [sid, session]
55
+ ensure
56
+ @mutex.unlock if env['rack.multithread']
57
+ end
58
+
59
+ 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
67
+ 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
+ end
78
+
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
86
+ end
87
+
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
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,349 @@
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
+ @template = ERB.new(TEMPLATE)
21
+ end
22
+
23
+ def call(env)
24
+ @app.call(env)
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]
31
+ end
32
+
33
+ def pretty(env, exception)
34
+ req = Rack::Request.new(env)
35
+ path = (req.script_name + req.path_info).squeeze("/")
36
+
37
+ frames = exception.backtrace.map { |line|
38
+ frame = OpenStruct.new
39
+ if line =~ /(.*?):(\d+)(:in `(.*)')?/
40
+ frame.filename = $1
41
+ frame.lineno = $2.to_i
42
+ frame.function = $4
43
+
44
+ begin
45
+ lineno = frame.lineno-1
46
+ lines = ::File.readlines(frame.filename)
47
+ frame.pre_context_lineno = [lineno-CONTEXT, 0].max
48
+ frame.pre_context = lines[frame.pre_context_lineno...lineno]
49
+ frame.context_line = lines[lineno].chomp
50
+ frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
51
+ frame.post_context = lines[lineno+1..frame.post_context_lineno]
52
+ rescue
53
+ end
54
+
55
+ frame
56
+ else
57
+ nil
58
+ end
59
+ }.compact
60
+
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)]
66
+ end
67
+
68
+ def h(obj) # :nodoc:
69
+ case obj
70
+ when String
71
+ Utils.escape_html(obj)
72
+ else
73
+ Utils.escape_html(obj.inspect)
74
+ end
75
+ end
76
+
77
+ # :stopdoc:
78
+
79
+ # adapted from Django <djangoproject.com>
80
+ # Copyright (c) 2005, the Lawrence Journal-World
81
+ # Used under the modified BSD license:
82
+ # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
83
+ TEMPLATE = <<'HTML'
84
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
85
+ <html lang="en">
86
+ <head>
87
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
88
+ <meta name="robots" content="NONE,NOARCHIVE" />
89
+ <title><%=h exception.class %> at <%=h path %></title>
90
+ <style type="text/css">
91
+ html * { padding:0; margin:0; }
92
+ body * { padding:10px 20px; }
93
+ body * * { padding:0; }
94
+ body { font:small sans-serif; }
95
+ body>div { border-bottom:1px solid #ddd; }
96
+ h1 { font-weight:normal; }
97
+ h2 { margin-bottom:.8em; }
98
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
99
+ h3 { margin:1em 0 .5em 0; }
100
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
101
+ table {
102
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
103
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
104
+ thead th {
105
+ padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
106
+ font-weight:normal; font-size:11px; border:1px solid #ddd; }
107
+ tbody th { text-align:right; color:#666; padding-right:.5em; }
108
+ table.vars { margin:5px 0 2px 40px; }
109
+ table.vars td, table.req td { font-family:monospace; }
110
+ table td.code { width:100%;}
111
+ table td.code div { overflow:hidden; }
112
+ table.source th { color:#666; }
113
+ table.source td {
114
+ font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
115
+ ul.traceback { list-style-type:none; }
116
+ ul.traceback li.frame { margin-bottom:1em; }
117
+ div.context { margin: 10px 0; }
118
+ div.context ol {
119
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
120
+ div.context ol li {
121
+ font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
122
+ div.context ol.context-line li { color:black; background-color:#ccc; }
123
+ div.context ol.context-line li span { float: right; }
124
+ div.commands { margin-left: 40px; }
125
+ div.commands a { color:black; text-decoration:none; }
126
+ #summary { background: #ffc; }
127
+ #summary h2 { font-weight: normal; color: #666; }
128
+ #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
129
+ #summary ul#quicklinks li { float: left; padding: 0 1em; }
130
+ #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
131
+ #explanation { background:#eee; }
132
+ #template, #template-not-exist { background:#f6f6f6; }
133
+ #template-not-exist ul { margin: 0 0 0 20px; }
134
+ #traceback { background:#eee; }
135
+ #requestinfo { background:#f6f6f6; padding-left:120px; }
136
+ #summary table { border:none; background:transparent; }
137
+ #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
138
+ #requestinfo h3 { margin-bottom:-1em; }
139
+ .error { background: #ffc; }
140
+ .specific { color:#cc3300; font-weight:bold; }
141
+ </style>
142
+ <script type="text/javascript">
143
+ //<!--
144
+ function getElementsByClassName(oElm, strTagName, strClassName){
145
+ // Written by Jonathan Snook, http://www.snook.ca/jon;
146
+ // Add-ons by Robert Nyman, http://www.robertnyman.com
147
+ var arrElements = (strTagName == "*" && document.all)? document.all :
148
+ oElm.getElementsByTagName(strTagName);
149
+ var arrReturnElements = new Array();
150
+ strClassName = strClassName.replace(/\-/g, "\\-");
151
+ var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
152
+ var oElement;
153
+ for(var i=0; i<arrElements.length; i++){
154
+ oElement = arrElements[i];
155
+ if(oRegExp.test(oElement.className)){
156
+ arrReturnElements.push(oElement);
157
+ }
158
+ }
159
+ return (arrReturnElements)
160
+ }
161
+ function hideAll(elems) {
162
+ for (var e = 0; e < elems.length; e++) {
163
+ elems[e].style.display = 'none';
164
+ }
165
+ }
166
+ window.onload = function() {
167
+ hideAll(getElementsByClassName(document, 'table', 'vars'));
168
+ hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
169
+ hideAll(getElementsByClassName(document, 'ol', 'post-context'));
170
+ }
171
+ function toggle() {
172
+ for (var i = 0; i < arguments.length; i++) {
173
+ var e = document.getElementById(arguments[i]);
174
+ if (e) {
175
+ e.style.display = e.style.display == 'none' ? 'block' : 'none';
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+ function varToggle(link, id) {
181
+ toggle('v' + id);
182
+ var s = link.getElementsByTagName('span')[0];
183
+ var uarr = String.fromCharCode(0x25b6);
184
+ var darr = String.fromCharCode(0x25bc);
185
+ s.innerHTML = s.innerHTML == uarr ? darr : uarr;
186
+ return false;
187
+ }
188
+ //-->
189
+ </script>
190
+ </head>
191
+ <body>
192
+
193
+ <div id="summary">
194
+ <h1><%=h exception.class %> at <%=h path %></h1>
195
+ <h2><%=h exception.message %></h2>
196
+ <table><tr>
197
+ <th>Ruby</th>
198
+ <td><code><%=h frames.first.filename %></code>: in <code><%=h frames.first.function %></code>, line <%=h frames.first.lineno %></td>
199
+ </tr><tr>
200
+ <th>Web</th>
201
+ <td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
202
+ </tr></table>
203
+
204
+ <h3>Jump to:</h3>
205
+ <ul id="quicklinks">
206
+ <li><a href="#get-info">GET</a></li>
207
+ <li><a href="#post-info">POST</a></li>
208
+ <li><a href="#cookie-info">Cookies</a></li>
209
+ <li><a href="#env-info">ENV</a></li>
210
+ </ul>
211
+ </div>
212
+
213
+ <div id="traceback">
214
+ <h2>Traceback <span>(innermost first)</span></h2>
215
+ <ul class="traceback">
216
+ <% frames.each { |frame| %>
217
+ <li class="frame">
218
+ <code><%=h frame.filename %></code>: in <code><%=h frame.function %></code>
219
+
220
+ <% if frame.context_line %>
221
+ <div class="context" id="c<%=h frame.object_id %>">
222
+ <% if frame.pre_context %>
223
+ <ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
224
+ <% frame.pre_context.each { |line| %>
225
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
226
+ <% } %>
227
+ </ol>
228
+ <% end %>
229
+
230
+ <ol start="<%=h frame.lineno %>" class="context-line">
231
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %><span>...</span></li></ol>
232
+
233
+ <% if frame.post_context %>
234
+ <ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
235
+ <% frame.post_context.each { |line| %>
236
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
237
+ <% } %>
238
+ </ol>
239
+ <% end %>
240
+ </div>
241
+ <% end %>
242
+ </li>
243
+ <% } %>
244
+ </ul>
245
+ </div>
246
+
247
+ <div id="requestinfo">
248
+ <h2>Request information</h2>
249
+
250
+ <h3 id="get-info">GET</h3>
251
+ <% unless req.GET.empty? %>
252
+ <table class="req">
253
+ <thead>
254
+ <tr>
255
+ <th>Variable</th>
256
+ <th>Value</th>
257
+ </tr>
258
+ </thead>
259
+ <tbody>
260
+ <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
261
+ <tr>
262
+ <td><%=h key %></td>
263
+ <td class="code"><div><%=h val.inspect %></div></td>
264
+ </tr>
265
+ <% } %>
266
+ </tbody>
267
+ </table>
268
+ <% else %>
269
+ <p>No GET data.</p>
270
+ <% end %>
271
+
272
+ <h3 id="post-info">POST</h3>
273
+ <% unless req.POST.empty? %>
274
+ <table class="req">
275
+ <thead>
276
+ <tr>
277
+ <th>Variable</th>
278
+ <th>Value</th>
279
+ </tr>
280
+ </thead>
281
+ <tbody>
282
+ <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
283
+ <tr>
284
+ <td><%=h key %></td>
285
+ <td class="code"><div><%=h val.inspect %></div></td>
286
+ </tr>
287
+ <% } %>
288
+ </tbody>
289
+ </table>
290
+ <% else %>
291
+ <p>No POST data.</p>
292
+ <% end %>
293
+
294
+
295
+ <h3 id="cookie-info">COOKIES</h3>
296
+ <% unless req.cookies.empty? %>
297
+ <table class="req">
298
+ <thead>
299
+ <tr>
300
+ <th>Variable</th>
301
+ <th>Value</th>
302
+ </tr>
303
+ </thead>
304
+ <tbody>
305
+ <% req.cookies.each { |key, val| %>
306
+ <tr>
307
+ <td><%=h key %></td>
308
+ <td class="code"><div><%=h val.inspect %></div></td>
309
+ </tr>
310
+ <% } %>
311
+ </tbody>
312
+ </table>
313
+ <% else %>
314
+ <p>No cookie data.</p>
315
+ <% end %>
316
+
317
+ <h3 id="env-info">Rack ENV</h3>
318
+ <table class="req">
319
+ <thead>
320
+ <tr>
321
+ <th>Variable</th>
322
+ <th>Value</th>
323
+ </tr>
324
+ </thead>
325
+ <tbody>
326
+ <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
327
+ <tr>
328
+ <td><%=h key %></td>
329
+ <td class="code"><div><%=h val %></div></td>
330
+ </tr>
331
+ <% } %>
332
+ </tbody>
333
+ </table>
334
+
335
+ </div>
336
+
337
+ <div id="explanation">
338
+ <p>
339
+ You're seeing this error because you use <code>Rack::ShowExceptions</code>.
340
+ </p>
341
+ </div>
342
+
343
+ </body>
344
+ </html>
345
+ HTML
346
+
347
+ # :startdoc:
348
+ end
349
+ end
@@ -0,0 +1,106 @@
1
+ require 'erb'
2
+ require 'rack/request'
3
+ require 'rack/utils'
4
+
5
+ module Rack
6
+ # Rack::ShowStatus catches all empty responses the app it wraps and
7
+ # replaces them with a site explaining the error.
8
+ #
9
+ # Additional details can be put into <tt>rack.showstatus.detail</tt>
10
+ # and will be shown as HTML. If such details exist, the error page
11
+ # is always rendered, even if the reply was not empty.
12
+
13
+ class ShowStatus
14
+ def initialize(app)
15
+ @app = app
16
+ @template = ERB.new(TEMPLATE)
17
+ end
18
+
19
+ def call(env)
20
+ status, headers, body = @app.call(env)
21
+ headers = Utils::HeaderHash.new(headers)
22
+ empty = headers['Content-Length'].to_i <= 0
23
+
24
+ # client or server error, or explicit message
25
+ if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
26
+ req = Rack::Request.new(env)
27
+ message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
28
+ detail = env["rack.showstatus.detail"] || message
29
+ body = @template.result(binding)
30
+ size = Rack::Utils.bytesize(body)
31
+ [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
32
+ else
33
+ [status, headers, body]
34
+ end
35
+ end
36
+
37
+ def h(obj) # :nodoc:
38
+ case obj
39
+ when String
40
+ Utils.escape_html(obj)
41
+ else
42
+ Utils.escape_html(obj.inspect)
43
+ end
44
+ end
45
+
46
+ # :stopdoc:
47
+
48
+ # adapted from Django <djangoproject.com>
49
+ # Copyright (c) 2005, the Lawrence Journal-World
50
+ # Used under the modified BSD license:
51
+ # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
52
+ TEMPLATE = <<'HTML'
53
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
54
+ <html lang="en">
55
+ <head>
56
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
57
+ <title><%=h message %> at <%=h req.script_name + req.path_info %></title>
58
+ <meta name="robots" content="NONE,NOARCHIVE" />
59
+ <style type="text/css">
60
+ html * { padding:0; margin:0; }
61
+ body * { padding:10px 20px; }
62
+ body * * { padding:0; }
63
+ body { font:small sans-serif; background:#eee; }
64
+ body>div { border-bottom:1px solid #ddd; }
65
+ h1 { font-weight:normal; margin-bottom:.4em; }
66
+ h1 span { font-size:60%; color:#666; font-weight:normal; }
67
+ table { border:none; border-collapse: collapse; width:100%; }
68
+ td, th { vertical-align:top; padding:2px 3px; }
69
+ th { width:12em; text-align:right; color:#666; padding-right:.5em; }
70
+ #info { background:#f6f6f6; }
71
+ #info ol { margin: 0.5em 4em; }
72
+ #info ol li { font-family: monospace; }
73
+ #summary { background: #ffc; }
74
+ #explanation { background:#eee; border-bottom: 0px none; }
75
+ </style>
76
+ </head>
77
+ <body>
78
+ <div id="summary">
79
+ <h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
80
+ <table class="meta">
81
+ <tr>
82
+ <th>Request Method:</th>
83
+ <td><%=h req.request_method %></td>
84
+ </tr>
85
+ <tr>
86
+ <th>Request URL:</th>
87
+ <td><%=h req.url %></td>
88
+ </tr>
89
+ </table>
90
+ </div>
91
+ <div id="info">
92
+ <p><%= detail %></p>
93
+ </div>
94
+
95
+ <div id="explanation">
96
+ <p>
97
+ You're seeing this error because you use <code>Rack::ShowStatus</code>.
98
+ </p>
99
+ </div>
100
+ </body>
101
+ </html>
102
+ HTML
103
+
104
+ # :startdoc:
105
+ end
106
+ end
data/rack/static.rb ADDED
@@ -0,0 +1,38 @@
1
+ module Rack
2
+
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.
7
+ #
8
+ # Examples:
9
+ # 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/*).
12
+ #
13
+ # 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
+
17
+ class Static
18
+
19
+ def initialize(app, options={})
20
+ @app = app
21
+ @urls = options[:urls] || ["/favicon.ico"]
22
+ root = options[:root] || Dir.pwd
23
+ @file_server = Rack::File.new(root)
24
+ end
25
+
26
+ def call(env)
27
+ path = env["PATH_INFO"]
28
+ can_serve = @urls.any? { |url| path.index(url) == 0 }
29
+
30
+ if can_serve
31
+ @file_server.call(env)
32
+ else
33
+ @app.call(env)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
data/rack/urlmap.rb ADDED
@@ -0,0 +1,55 @@
1
+ module Rack
2
+ # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
+ # dispatches accordingly. Support for HTTP/1.1 host names exists if
4
+ # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
5
+ #
6
+ # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
7
+ # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
8
+ # PATH_INFO. This should be taken care of when you need to
9
+ # reconstruct the URL in order to create links.
10
+ #
11
+ # URLMap dispatches in such a way that the longest paths are tried
12
+ # first, since they are most specific.
13
+
14
+ class URLMap
15
+ def initialize(map = {})
16
+ remap(map)
17
+ end
18
+
19
+ def remap(map)
20
+ @mapping = map.map { |location, app|
21
+ if location =~ %r{\Ahttps?://(.*?)(/.*)}
22
+ host, location = $1, $2
23
+ else
24
+ host = nil
25
+ end
26
+
27
+ unless location[0] == ?/
28
+ raise ArgumentError, "paths need to start with /"
29
+ end
30
+ location = location.chomp('/')
31
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
32
+
33
+ [host, location, match, app]
34
+ }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
35
+ end
36
+
37
+ def call(env)
38
+ path = env["PATH_INFO"]
39
+ 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.to_s =~ match && rest = $1
45
+ next unless rest.empty? || rest[0] == ?/
46
+ env.merge!('SCRIPT_NAME' => (script_name + location), 'PATH_INFO' => rest)
47
+ return app.call(env)
48
+ }
49
+ [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
50
+ ensure
51
+ env.merge! 'PATH_INFO' => path, 'SCRIPT_NAME' => script_name
52
+ end
53
+ end
54
+ end
55
+