rack 0.1.0 → 0.2.0

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 (48) hide show
  1. data/AUTHORS +2 -0
  2. data/RDOX +46 -1
  3. data/README +19 -4
  4. data/Rakefile +9 -8
  5. data/bin/rackup +2 -0
  6. data/example/protectedlobster.rb +14 -0
  7. data/lib/rack.rb +19 -0
  8. data/lib/rack/adapter/camping.rb +6 -0
  9. data/lib/rack/auth/abstract/handler.rb +28 -0
  10. data/lib/rack/auth/abstract/request.rb +39 -0
  11. data/lib/rack/auth/basic.rb +58 -0
  12. data/lib/rack/auth/digest/md5.rb +124 -0
  13. data/lib/rack/auth/digest/nonce.rb +52 -0
  14. data/lib/rack/auth/digest/params.rb +55 -0
  15. data/lib/rack/auth/digest/request.rb +40 -0
  16. data/lib/rack/commonlogger.rb +1 -1
  17. data/lib/rack/file.rb +1 -1
  18. data/lib/rack/handler/cgi.rb +7 -7
  19. data/lib/rack/handler/fastcgi.rb +1 -1
  20. data/lib/rack/handler/mongrel.rb +8 -7
  21. data/lib/rack/handler/webrick.rb +7 -6
  22. data/lib/rack/lint.rb +3 -3
  23. data/lib/rack/lobster.rb +4 -4
  24. data/lib/rack/mock.rb +9 -33
  25. data/lib/rack/recursive.rb +1 -1
  26. data/lib/rack/reloader.rb +1 -1
  27. data/lib/rack/request.rb +32 -9
  28. data/lib/rack/response.rb +54 -8
  29. data/lib/rack/session/cookie.rb +73 -0
  30. data/lib/rack/showexceptions.rb +2 -2
  31. data/lib/rack/showstatus.rb +103 -0
  32. data/lib/rack/static.rb +38 -0
  33. data/lib/rack/urlmap.rb +1 -1
  34. data/lib/rack/utils.rb +45 -3
  35. data/test/spec_rack_auth_basic.rb +68 -0
  36. data/test/spec_rack_auth_digest.rb +167 -0
  37. data/test/spec_rack_camping.rb +3 -0
  38. data/test/spec_rack_mock.rb +2 -0
  39. data/test/spec_rack_mongrel.rb +12 -0
  40. data/test/spec_rack_request.rb +60 -0
  41. data/test/spec_rack_response.rb +50 -1
  42. data/test/spec_rack_session_cookie.rb +49 -0
  43. data/test/spec_rack_showstatus.rb +71 -0
  44. data/test/spec_rack_static.rb +37 -0
  45. data/test/spec_rack_urlmap.rb +1 -7
  46. data/test/spec_rack_webrick.rb +17 -0
  47. metadata +23 -3
  48. data/lib/rack/adapter/rails.rb +0 -65
@@ -28,7 +28,7 @@ module Rack
28
28
  # include data from other applications (by using
29
29
  # <tt>rack['rack.recursive.include'][...]</tt> or raise a
30
30
  # ForwardRequest to redirect internally.
31
-
31
+
32
32
  class Recursive
33
33
  def initialize(app)
34
34
  @app = app
@@ -6,7 +6,7 @@ module Rack
6
6
  # rack.errors.
7
7
  #
8
8
  # It is recommended you use ShowExceptions to catch SyntaxErrors etc.
9
-
9
+
10
10
  class Reloader
11
11
  def initialize(app, secs=10)
12
12
  @app = app
@@ -8,11 +8,11 @@ module Rack
8
8
  # req = Rack::Request.new(env)
9
9
  # req.post?
10
10
  # req.params["data"]
11
-
11
+
12
12
  class Request
13
13
  # The environment of the request.
14
14
  attr_reader :env
15
-
15
+
16
16
  def initialize(env)
17
17
  @env = env
18
18
  end
@@ -59,7 +59,7 @@ module Rack
59
59
  else
60
60
  @env["rack.request.form_input"] = @env["rack.input"]
61
61
  unless @env["rack.request.form_hash"] =
62
- Utils::Multipart.parse_multipart(env)
62
+ Utils::Multipart.parse_multipart(env)
63
63
  @env["rack.request.form_vars"] = @env["rack.input"].read
64
64
  @env["rack.request.form_hash"] = Utils.parse_query(@env["rack.request.form_vars"])
65
65
  end
@@ -72,6 +72,28 @@ module Rack
72
72
  self.GET.update(self.POST)
73
73
  end
74
74
 
75
+ # shortcut for request.params[key]
76
+ def [](key)
77
+ params[key.to_s]
78
+ end
79
+
80
+ # shortcut for request.params[key] = value
81
+ def []=(key, value)
82
+ params[key.to_s] = value
83
+ end
84
+
85
+ # like Hash#values_at
86
+ def values_at(*keys)
87
+ keys.map{|key| params[key] }
88
+ end
89
+
90
+ # the referer of the client or '/'
91
+ def referer
92
+ @env['HTTP_REFERER'] || '/'
93
+ end
94
+ alias referrer referer
95
+
96
+
75
97
  def cookies
76
98
  return {} unless @env["HTTP_COOKIE"]
77
99
 
@@ -99,14 +121,15 @@ module Rack
99
121
  url << ":#{port}"
100
122
  end
101
123
 
102
- url << script_name
103
- url << path_info
104
-
105
- unless query_string.empty?
106
- url << "?" << query_string
107
- end
124
+ url << fullpath
108
125
 
109
126
  url
110
127
  end
128
+
129
+ def fullpath
130
+ path = script_name + path_info
131
+ path << "?" << query_string unless query_string.empty?
132
+ path
133
+ end
111
134
  end
112
135
  end
@@ -2,18 +2,18 @@ require 'rack/request'
2
2
  require 'rack/utils'
3
3
 
4
4
  module Rack
5
- # Rack::Request provides a convenient interface to create a Rack
5
+ # Rack::Response provides a convenient interface to create a Rack
6
6
  # response.
7
7
  #
8
8
  # It allows setting of headers and cookies, and provides useful
9
9
  # defaults (a OK response containing HTML).
10
10
  #
11
- # You can use Request#write to iteratively generate your response,
12
- # but note that this is buffered by Rack::Request until you call
11
+ # You can use Response#write to iteratively generate your response,
12
+ # but note that this is buffered by Rack::Response until you call
13
13
  # +finish+. +finish+ however can take a block inside which calls to
14
14
  # +write+ are syncronous with the Rack response.
15
15
  #
16
- # Your application's +call+ should end returning Request#finish.
16
+ # Your application's +call+ should end returning Response#finish.
17
17
 
18
18
  class Response
19
19
  def initialize(body=[], status=200, header={}, &block)
@@ -22,17 +22,18 @@ module Rack
22
22
  merge(header))
23
23
 
24
24
  @writer = lambda { |x| @body << x }
25
+ @block = nil
25
26
 
26
27
  @body = []
27
28
 
28
- if body.kind_of?(String)
29
- write body
29
+ if body.respond_to? :to_str
30
+ write body.to_str
30
31
  elsif body.respond_to?(:each)
31
32
  body.each { |part|
32
33
  write part.to_s
33
34
  }
34
35
  else
35
- raise TypeError, "String or iterable required"
36
+ raise TypeError, "stringable or iterable required"
36
37
  end
37
38
 
38
39
  yield self if block_given?
@@ -107,8 +108,53 @@ module Rack
107
108
  end
108
109
 
109
110
  def write(str)
110
- @writer.call str
111
+ @writer.call str.to_s
111
112
  str
112
113
  end
114
+
115
+ def empty?
116
+ @block == nil && @body.empty?
117
+ end
118
+
119
+ alias headers header
120
+
121
+ module Helpers
122
+ def invalid?; @status < 100 || @status >= 600; end
123
+
124
+ def informational?; @status >= 100 && @status < 200; end
125
+ def successful?; @status >= 200 && @status < 300; end
126
+ def redirection?; @status >= 300 && @status < 400; end
127
+ def client_error?; @status >= 400 && @status < 500; end
128
+ def server_error?; @status >= 500 && @status < 600; end
129
+
130
+ def ok?; @status == 200; end
131
+ def forbidden?; @status == 403; end
132
+ def not_found?; @status == 404; end
133
+
134
+ def redirect?; [301, 302, 303, 307].include? @status; end
135
+ def empty?; [201, 204, 304].include? @status; end
136
+
137
+ # Headers
138
+ attr_reader :headers, :original_headers
139
+
140
+ def include?(header)
141
+ !!headers[header]
142
+ end
143
+
144
+ def content_type
145
+ headers["Content-Type"]
146
+ end
147
+
148
+ def content_length
149
+ cl = headers["Content-Length"]
150
+ cl ? cl.to_i : cl
151
+ end
152
+
153
+ def location
154
+ headers["Location"]
155
+ end
156
+ end
157
+
158
+ include Helpers
113
159
  end
114
160
  end
@@ -0,0 +1,73 @@
1
+ require 'base64'
2
+
3
+ module Rack
4
+
5
+ module Session
6
+
7
+ # Rack::Session::Cookie provides simple cookie based session management.
8
+ # The session is a Ruby Hash stored as base64 encoded marshalled data
9
+ # set to :key (default: rack.session).
10
+ #
11
+ # Example:
12
+ #
13
+ # use Rack::Session::Cookie, :key => 'rack.session',
14
+ # :domain => 'foo.com',
15
+ # :path => '/',
16
+ # :expire_after => 2592000
17
+ #
18
+ # All parameters are optional.
19
+
20
+ class Cookie
21
+
22
+ def initialize(app, options={})
23
+ @app = app
24
+ @key = options[:key] || "rack.session"
25
+ @default_options = {:domain => nil,
26
+ :path => "/",
27
+ :expire_after => nil}.merge(options)
28
+ end
29
+
30
+ def call(env)
31
+ load_session(env)
32
+ status, headers, body = @app.call(env)
33
+ commit_session(env, status, headers, body)
34
+ end
35
+
36
+ private
37
+
38
+ def load_session(env)
39
+ request = Rack::Request.new(env)
40
+ session_data = request.cookies[@key]
41
+
42
+ begin
43
+ session_data = Base64.decode64(session_data)
44
+ session_data = Marshal.load(session_data)
45
+ env["rack.session"] = session_data
46
+ rescue
47
+ env["rack.session"] = Hash.new
48
+ end
49
+
50
+ env["rack.session.options"] = @default_options.dup
51
+ end
52
+
53
+ def commit_session(env, status, headers, body)
54
+ session_data = Marshal.dump(env["rack.session"])
55
+ session_data = Base64.encode64(session_data)
56
+
57
+ if session_data.size > (4096 - @key.size)
58
+ env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
59
+ [status, headers, body]
60
+ else
61
+ options = env["rack.session.options"]
62
+ cookie = Hash.new
63
+ cookie[:value] = session_data
64
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
65
+ response = Rack::Response.new(body, status, headers)
66
+ response.set_cookie(@key, cookie.merge(options))
67
+ response.to_a
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -10,7 +10,7 @@ module Rack
10
10
  #
11
11
  # Be careful when you use this on public-facing sites as it could
12
12
  # reveal information helpful to attackers.
13
-
13
+
14
14
  class ShowExceptions
15
15
  CONTEXT = 7
16
16
 
@@ -60,7 +60,7 @@ module Rack
60
60
  [@template.result(binding)]
61
61
  end
62
62
 
63
- def h(obj)
63
+ def h(obj) # :nodoc:
64
64
  case obj
65
65
  when String
66
66
  Utils.escape_html(obj)
@@ -0,0 +1,103 @@
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
+
22
+ # client or server error, or explicit message
23
+ if status.to_i >= 400 &&
24
+ (body.empty? rescue false) || env["rack.showstatus.detail"]
25
+ req = Rack::Request.new(env)
26
+ message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
27
+ detail = env["rack.showstatus.detail"] || message
28
+ [status, headers.merge("Content-Type" => "text/html"), [@template.result(binding)]]
29
+ else
30
+ [status, headers, body]
31
+ end
32
+ end
33
+
34
+ def h(obj) # :nodoc:
35
+ case obj
36
+ when String
37
+ Utils.escape_html(obj)
38
+ else
39
+ Utils.escape_html(obj.inspect)
40
+ end
41
+ end
42
+
43
+ # :stopdoc:
44
+
45
+ # adapted from Django <djangoproject.com>
46
+ # Copyright (c) 2005, the Lawrence Journal-World
47
+ # Used under the modified BSD license:
48
+ # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
49
+ TEMPLATE = <<'HTML'
50
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
51
+ <html lang="en">
52
+ <head>
53
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
54
+ <title><%=h message %> at <%=h req.script_name + req.path_info %></title>
55
+ <meta name="robots" content="NONE,NOARCHIVE" />
56
+ <style type="text/css">
57
+ html * { padding:0; margin:0; }
58
+ body * { padding:10px 20px; }
59
+ body * * { padding:0; }
60
+ body { font:small sans-serif; background:#eee; }
61
+ body>div { border-bottom:1px solid #ddd; }
62
+ h1 { font-weight:normal; margin-bottom:.4em; }
63
+ h1 span { font-size:60%; color:#666; font-weight:normal; }
64
+ table { border:none; border-collapse: collapse; width:100%; }
65
+ td, th { vertical-align:top; padding:2px 3px; }
66
+ th { width:12em; text-align:right; color:#666; padding-right:.5em; }
67
+ #info { background:#f6f6f6; }
68
+ #info ol { margin: 0.5em 4em; }
69
+ #info ol li { font-family: monospace; }
70
+ #summary { background: #ffc; }
71
+ #explanation { background:#eee; border-bottom: 0px none; }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <div id="summary">
76
+ <h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
77
+ <table class="meta">
78
+ <tr>
79
+ <th>Request Method:</th>
80
+ <td><%=h req.request_method %></td>
81
+ </tr>
82
+ <tr>
83
+ <th>Request URL:</th>
84
+ <td><%=h req.url %></td>
85
+ </tr>
86
+ </table>
87
+ </div>
88
+ <div id="info">
89
+ <p><%= detail %></p>
90
+ </div>
91
+
92
+ <div id="explanation">
93
+ <p>
94
+ You're seeing this error because you use <code>Rack::ShowStatus</code>.
95
+ </p>
96
+ </div>
97
+ </body>
98
+ </html>
99
+ HTML
100
+
101
+ # :startdoc:
102
+ end
103
+ end
@@ -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
@@ -10,7 +10,7 @@ module Rack
10
10
  #
11
11
  # URLMap dispatches in such a way that the longest paths are tried
12
12
  # first, since they are most specific.
13
-
13
+
14
14
  class URLMap
15
15
  def initialize(map)
16
16
  @mapping = map.map { |location, app|
@@ -3,7 +3,7 @@ require 'tempfile'
3
3
  module Rack
4
4
  # Rack::Utils contains a grab-bag of useful methods for writing web
5
5
  # applications adopted from all kinds of Ruby libraries.
6
-
6
+
7
7
  module Utils
8
8
  # Performs URI escaping so that you can construct proper
9
9
  # query strings faster. Use this rather than the cgi.rb
@@ -27,7 +27,7 @@ module Rack
27
27
  # Parses a query string by breaking it up at the '&'
28
28
  # and ';' characters. You can also use this to parse
29
29
  # cookies by changing the characters used in the second
30
- # parameter (which defaults to '&;'.
30
+ # parameter (which defaults to '&;').
31
31
 
32
32
  def parse_query(qs, d = '&;')
33
33
  params = {}
@@ -81,10 +81,52 @@ module Rack
81
81
  end
82
82
  end
83
83
 
84
+ # Every standard HTTP code mapped to the appropriate message.
85
+ # Stolen from Mongrel.
86
+ HTTP_STATUS_CODES = {
87
+ 100 => 'Continue',
88
+ 101 => 'Switching Protocols',
89
+ 200 => 'OK',
90
+ 201 => 'Created',
91
+ 202 => 'Accepted',
92
+ 203 => 'Non-Authoritative Information',
93
+ 204 => 'No Content',
94
+ 205 => 'Reset Content',
95
+ 206 => 'Partial Content',
96
+ 300 => 'Multiple Choices',
97
+ 301 => 'Moved Permanently',
98
+ 302 => 'Moved Temporarily',
99
+ 303 => 'See Other',
100
+ 304 => 'Not Modified',
101
+ 305 => 'Use Proxy',
102
+ 400 => 'Bad Request',
103
+ 401 => 'Unauthorized',
104
+ 402 => 'Payment Required',
105
+ 403 => 'Forbidden',
106
+ 404 => 'Not Found',
107
+ 405 => 'Method Not Allowed',
108
+ 406 => 'Not Acceptable',
109
+ 407 => 'Proxy Authentication Required',
110
+ 408 => 'Request Time-out',
111
+ 409 => 'Conflict',
112
+ 410 => 'Gone',
113
+ 411 => 'Length Required',
114
+ 412 => 'Precondition Failed',
115
+ 413 => 'Request Entity Too Large',
116
+ 414 => 'Request-URI Too Large',
117
+ 415 => 'Unsupported Media Type',
118
+ 500 => 'Internal Server Error',
119
+ 501 => 'Not Implemented',
120
+ 502 => 'Bad Gateway',
121
+ 503 => 'Service Unavailable',
122
+ 504 => 'Gateway Time-out',
123
+ 505 => 'HTTP Version not supported'
124
+ }
125
+
84
126
  # A multipart form data parser, adapted from IOWA.
85
127
  #
86
128
  # Usually, Rack::Request#POST takes care of calling this.
87
-
129
+
88
130
  module Multipart
89
131
  EOL = "\r\n"
90
132