eac-rack 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +399 -0
  4. data/bin/rackup +4 -0
  5. data/contrib/rack_logo.svg +111 -0
  6. data/example/lobster.ru +4 -0
  7. data/example/protectedlobster.rb +14 -0
  8. data/example/protectedlobster.ru +8 -0
  9. data/lib/rack.rb +92 -0
  10. data/lib/rack/adapter/camping.rb +22 -0
  11. data/lib/rack/auth/abstract/handler.rb +37 -0
  12. data/lib/rack/auth/abstract/request.rb +37 -0
  13. data/lib/rack/auth/basic.rb +58 -0
  14. data/lib/rack/auth/digest/md5.rb +124 -0
  15. data/lib/rack/auth/digest/nonce.rb +51 -0
  16. data/lib/rack/auth/digest/params.rb +55 -0
  17. data/lib/rack/auth/digest/request.rb +40 -0
  18. data/lib/rack/builder.rb +80 -0
  19. data/lib/rack/cascade.rb +41 -0
  20. data/lib/rack/chunked.rb +49 -0
  21. data/lib/rack/commonlogger.rb +49 -0
  22. data/lib/rack/conditionalget.rb +47 -0
  23. data/lib/rack/config.rb +15 -0
  24. data/lib/rack/content_length.rb +29 -0
  25. data/lib/rack/content_type.rb +23 -0
  26. data/lib/rack/deflater.rb +96 -0
  27. data/lib/rack/directory.rb +157 -0
  28. data/lib/rack/etag.rb +23 -0
  29. data/lib/rack/file.rb +90 -0
  30. data/lib/rack/handler.rb +88 -0
  31. data/lib/rack/handler/cgi.rb +61 -0
  32. data/lib/rack/handler/evented_mongrel.rb +8 -0
  33. data/lib/rack/handler/fastcgi.rb +89 -0
  34. data/lib/rack/handler/lsws.rb +63 -0
  35. data/lib/rack/handler/mongrel.rb +90 -0
  36. data/lib/rack/handler/scgi.rb +62 -0
  37. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  38. data/lib/rack/handler/thin.rb +18 -0
  39. data/lib/rack/handler/webrick.rb +69 -0
  40. data/lib/rack/head.rb +19 -0
  41. data/lib/rack/lint.rb +575 -0
  42. data/lib/rack/lobster.rb +65 -0
  43. data/lib/rack/lock.rb +16 -0
  44. data/lib/rack/logger.rb +20 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +206 -0
  47. data/lib/rack/mock.rb +189 -0
  48. data/lib/rack/nulllogger.rb +18 -0
  49. data/lib/rack/recursive.rb +57 -0
  50. data/lib/rack/reloader.rb +109 -0
  51. data/lib/rack/request.rb +271 -0
  52. data/lib/rack/response.rb +149 -0
  53. data/lib/rack/rewindable_input.rb +100 -0
  54. data/lib/rack/runtime.rb +27 -0
  55. data/lib/rack/sendfile.rb +142 -0
  56. data/lib/rack/server.rb +212 -0
  57. data/lib/rack/session/abstract/id.rb +140 -0
  58. data/lib/rack/session/cookie.rb +90 -0
  59. data/lib/rack/session/memcache.rb +119 -0
  60. data/lib/rack/session/pool.rb +100 -0
  61. data/lib/rack/showexceptions.rb +349 -0
  62. data/lib/rack/showstatus.rb +106 -0
  63. data/lib/rack/static.rb +38 -0
  64. data/lib/rack/urlmap.rb +56 -0
  65. data/lib/rack/utils.rb +614 -0
  66. data/rack.gemspec +38 -0
  67. data/test/spec_rack_auth_basic.rb +73 -0
  68. data/test/spec_rack_auth_digest.rb +226 -0
  69. data/test/spec_rack_builder.rb +84 -0
  70. data/test/spec_rack_camping.rb +51 -0
  71. data/test/spec_rack_cascade.rb +48 -0
  72. data/test/spec_rack_cgi.rb +89 -0
  73. data/test/spec_rack_chunked.rb +62 -0
  74. data/test/spec_rack_commonlogger.rb +61 -0
  75. data/test/spec_rack_conditionalget.rb +41 -0
  76. data/test/spec_rack_config.rb +24 -0
  77. data/test/spec_rack_content_length.rb +43 -0
  78. data/test/spec_rack_content_type.rb +30 -0
  79. data/test/spec_rack_deflater.rb +127 -0
  80. data/test/spec_rack_directory.rb +61 -0
  81. data/test/spec_rack_etag.rb +17 -0
  82. data/test/spec_rack_fastcgi.rb +89 -0
  83. data/test/spec_rack_file.rb +75 -0
  84. data/test/spec_rack_handler.rb +43 -0
  85. data/test/spec_rack_head.rb +30 -0
  86. data/test/spec_rack_lint.rb +528 -0
  87. data/test/spec_rack_lobster.rb +45 -0
  88. data/test/spec_rack_lock.rb +38 -0
  89. data/test/spec_rack_logger.rb +21 -0
  90. data/test/spec_rack_methodoverride.rb +60 -0
  91. data/test/spec_rack_mock.rb +243 -0
  92. data/test/spec_rack_mongrel.rb +189 -0
  93. data/test/spec_rack_nulllogger.rb +13 -0
  94. data/test/spec_rack_recursive.rb +77 -0
  95. data/test/spec_rack_request.rb +545 -0
  96. data/test/spec_rack_response.rb +221 -0
  97. data/test/spec_rack_rewindable_input.rb +118 -0
  98. data/test/spec_rack_runtime.rb +35 -0
  99. data/test/spec_rack_sendfile.rb +86 -0
  100. data/test/spec_rack_session_cookie.rb +73 -0
  101. data/test/spec_rack_session_memcache.rb +273 -0
  102. data/test/spec_rack_session_pool.rb +172 -0
  103. data/test/spec_rack_showexceptions.rb +21 -0
  104. data/test/spec_rack_showstatus.rb +72 -0
  105. data/test/spec_rack_static.rb +37 -0
  106. data/test/spec_rack_thin.rb +91 -0
  107. data/test/spec_rack_urlmap.rb +215 -0
  108. data/test/spec_rack_utils.rb +554 -0
  109. data/test/spec_rack_webrick.rb +130 -0
  110. data/test/spec_rackup.rb +154 -0
  111. metadata +311 -0
@@ -0,0 +1,140 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+ # bugrep: Andreas Zehnder
3
+
4
+ require 'time'
5
+ require 'rack/request'
6
+ require 'rack/response'
7
+
8
+ module Rack
9
+
10
+ module Session
11
+
12
+ module Abstract
13
+
14
+ # ID sets up a basic framework for implementing an id based sessioning
15
+ # service. Cookies sent to the client for maintaining sessions will only
16
+ # contain an id reference. Only #get_session and #set_session are
17
+ # required to be overwritten.
18
+ #
19
+ # All parameters are optional.
20
+ # * :key determines the name of the cookie, by default it is
21
+ # 'rack.session'
22
+ # * :path, :domain, :expire_after, :secure, and :httponly set the related
23
+ # cookie options as by Rack::Response#add_cookie
24
+ # * :defer will not set a cookie in the response.
25
+ # * :renew (implementation dependent) will prompt the generation of a new
26
+ # session id, and migration of data to be referenced at the new id. If
27
+ # :defer is set, it will be overridden and the cookie will be set.
28
+ # * :sidbits sets the number of bits in length that a generated session
29
+ # id will be.
30
+ #
31
+ # These options can be set on a per request basis, at the location of
32
+ # env['rack.session.options']. Additionally the id of the session can be
33
+ # found within the options hash at the key :id. It is highly not
34
+ # recommended to change its value.
35
+ #
36
+ # Is Rack::Utils::Context compatible.
37
+
38
+ class ID
39
+ DEFAULT_OPTIONS = {
40
+ :path => '/',
41
+ :domain => nil,
42
+ :expire_after => nil,
43
+ :secure => false,
44
+ :httponly => true,
45
+ :defer => false,
46
+ :renew => false,
47
+ :sidbits => 128
48
+ }
49
+
50
+ attr_reader :key, :default_options
51
+ def initialize(app, options={})
52
+ @app = app
53
+ @key = options[:key] || "rack.session"
54
+ @default_options = self.class::DEFAULT_OPTIONS.merge(options)
55
+ end
56
+
57
+ def call(env)
58
+ context(env)
59
+ end
60
+
61
+ def context(env, app=@app)
62
+ load_session(env)
63
+ status, headers, body = app.call(env)
64
+ commit_session(env, status, headers, body)
65
+ end
66
+
67
+ private
68
+
69
+ # Generate a new session id using Ruby #rand. The size of the
70
+ # session id is controlled by the :sidbits option.
71
+ # Monkey patch this to use custom methods for session id generation.
72
+
73
+ def generate_sid
74
+ "%0#{@default_options[:sidbits] / 4}x" %
75
+ rand(2**@default_options[:sidbits] - 1)
76
+ end
77
+
78
+ # Extracts the session id from provided cookies and passes it and the
79
+ # environment to #get_session. It then sets the resulting session into
80
+ # 'rack.session', and places options and session metadata into
81
+ # 'rack.session.options'.
82
+
83
+ def load_session(env)
84
+ request = Rack::Request.new(env)
85
+ session_id = request.cookies[@key]
86
+
87
+ begin
88
+ session_id, session = get_session(env, session_id)
89
+ env['rack.session'] = session
90
+ rescue
91
+ env['rack.session'] = Hash.new
92
+ end
93
+
94
+ env['rack.session.options'] = @default_options.
95
+ merge(:id => session_id)
96
+ end
97
+
98
+ # Acquires the session from the environment and the session id from
99
+ # the session options and passes them to #set_session. If successful
100
+ # and the :defer option is not true, a cookie will be added to the
101
+ # response with the session's id.
102
+
103
+ def commit_session(env, status, headers, body)
104
+ session = env['rack.session']
105
+ options = env['rack.session.options']
106
+ session_id = options[:id]
107
+
108
+ if not session_id = set_session(env, session_id, session, options)
109
+ env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
110
+ elsif options[:defer] and not options[:renew]
111
+ env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
112
+ else
113
+ cookie = Hash.new
114
+ cookie[:value] = session_id
115
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
116
+ Utils.set_cookie_header!(headers, @key, cookie.merge(options))
117
+ end
118
+
119
+ [status, headers, body]
120
+ end
121
+
122
+ # All thread safety and session retrival proceedures should occur here.
123
+ # Should return [session_id, session].
124
+ # If nil is provided as the session id, generation of a new valid id
125
+ # should occur within.
126
+
127
+ def get_session(env, sid)
128
+ raise '#get_session not implemented.'
129
+ end
130
+
131
+ # All thread safety and session storage proceedures should occur here.
132
+ # Should return true or false dependant on whether or not the session
133
+ # was saved or not.
134
+ def set_session(env, sid, session, options)
135
+ raise '#set_session not implemented.'
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,90 @@
1
+ require 'openssl'
2
+ require 'rack/request'
3
+ require 'rack/response'
4
+
5
+ module Rack
6
+
7
+ module Session
8
+
9
+ # Rack::Session::Cookie provides simple cookie based session management.
10
+ # The session is a Ruby Hash stored as base64 encoded marshalled data
11
+ # set to :key (default: rack.session).
12
+ # When the secret key is set, cookie data is checked for data integrity.
13
+ #
14
+ # Example:
15
+ #
16
+ # use Rack::Session::Cookie, :key => 'rack.session',
17
+ # :domain => 'foo.com',
18
+ # :path => '/',
19
+ # :expire_after => 2592000,
20
+ # :secret => 'change_me'
21
+ #
22
+ # All parameters are optional.
23
+
24
+ class Cookie
25
+
26
+ def initialize(app, options={})
27
+ @app = app
28
+ @key = options[:key] || "rack.session"
29
+ @secret = options[:secret]
30
+ @default_options = {:domain => nil,
31
+ :path => "/",
32
+ :expire_after => nil}.merge(options)
33
+ end
34
+
35
+ def call(env)
36
+ load_session(env)
37
+ status, headers, body = @app.call(env)
38
+ commit_session(env, status, headers, body)
39
+ end
40
+
41
+ private
42
+
43
+ def load_session(env)
44
+ request = Rack::Request.new(env)
45
+ session_data = request.cookies[@key]
46
+
47
+ if @secret && session_data
48
+ session_data, digest = session_data.split("--")
49
+ session_data = nil unless digest == generate_hmac(session_data)
50
+ end
51
+
52
+ begin
53
+ session_data = session_data.unpack("m*").first
54
+ session_data = Marshal.load(session_data)
55
+ env["rack.session"] = session_data
56
+ rescue
57
+ env["rack.session"] = Hash.new
58
+ end
59
+
60
+ env["rack.session.options"] = @default_options.dup
61
+ end
62
+
63
+ def commit_session(env, status, headers, body)
64
+ session_data = Marshal.dump(env["rack.session"])
65
+ session_data = [session_data].pack("m*")
66
+
67
+ if @secret
68
+ session_data = "#{session_data}--#{generate_hmac(session_data)}"
69
+ end
70
+
71
+ if session_data.size > (4096 - @key.size)
72
+ env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
73
+ else
74
+ options = env["rack.session.options"]
75
+ cookie = Hash.new
76
+ cookie[:value] = session_data
77
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
78
+ Utils.set_cookie_header!(headers, @key, cookie.merge(options))
79
+ end
80
+
81
+ [status, headers, body]
82
+ end
83
+
84
+ def generate_hmac(data)
85
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,119 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+
3
+ require 'rack/session/abstract/id'
4
+ require 'memcache'
5
+
6
+ module Rack
7
+ module Session
8
+ # Rack::Session::Memcache provides simple cookie based session management.
9
+ # Session data is stored in memcached. The corresponding session key is
10
+ # maintained in the cookie.
11
+ # You may treat Session::Memcache as you would Session::Pool with the
12
+ # following caveats.
13
+ #
14
+ # * Setting :expire_after to 0 would note to the Memcache server to hang
15
+ # onto the session data until it would drop it according to it's own
16
+ # specifications. However, the cookie sent to the client would expire
17
+ # immediately.
18
+ #
19
+ # Note that memcache does drop data before it may be listed to expire. For
20
+ # a full description of behaviour, please see memcache's documentation.
21
+
22
+ class Memcache < Abstract::ID
23
+ attr_reader :mutex, :pool
24
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
25
+ :namespace => 'rack:session',
26
+ :memcache_server => 'localhost:11211'
27
+
28
+ def initialize(app, options={})
29
+ super
30
+
31
+ @mutex = Mutex.new
32
+ mserv = @default_options[:memcache_server]
33
+ mopts = @default_options.
34
+ reject{|k,v| MemCache::DEFAULT_OPTIONS.include? k }
35
+ @pool = MemCache.new mserv, mopts
36
+ unless @pool.active? and @pool.servers.any?{|c| c.alive? }
37
+ raise 'No memcache servers'
38
+ end
39
+ end
40
+
41
+ def generate_sid
42
+ loop do
43
+ sid = super
44
+ break sid unless @pool.get(sid, true)
45
+ end
46
+ end
47
+
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}'"
54
+ end
55
+ 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
+ end
66
+
67
+ def set_session(env, session_id, new_session, options)
68
+ expiry = options[:expire_after]
69
+ expiry = expiry.nil? ? 0 : expiry + 1
70
+
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
77
+ end
78
+
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] }
105
+ end
106
+
107
+ @pool.set session_id, session, expiry
108
+ return session_id
109
+ 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
114
+ ensure
115
+ @mutex.unlock if @mutex.locked?
116
+ end
117
+ end
118
+ end
119
+ end
@@ -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