edgar-rack 1.2.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 (138) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +401 -0
  4. data/Rakefile +101 -0
  5. data/SPEC +171 -0
  6. data/bin/rackup +4 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +81 -0
  12. data/lib/rack/auth/abstract/handler.rb +37 -0
  13. data/lib/rack/auth/abstract/request.rb +43 -0
  14. data/lib/rack/auth/basic.rb +58 -0
  15. data/lib/rack/auth/digest/md5.rb +124 -0
  16. data/lib/rack/auth/digest/nonce.rb +51 -0
  17. data/lib/rack/auth/digest/params.rb +53 -0
  18. data/lib/rack/auth/digest/request.rb +40 -0
  19. data/lib/rack/builder.rb +80 -0
  20. data/lib/rack/cascade.rb +41 -0
  21. data/lib/rack/chunked.rb +52 -0
  22. data/lib/rack/commonlogger.rb +49 -0
  23. data/lib/rack/conditionalget.rb +63 -0
  24. data/lib/rack/config.rb +15 -0
  25. data/lib/rack/content_length.rb +29 -0
  26. data/lib/rack/content_type.rb +23 -0
  27. data/lib/rack/deflater.rb +96 -0
  28. data/lib/rack/directory.rb +157 -0
  29. data/lib/rack/etag.rb +59 -0
  30. data/lib/rack/file.rb +118 -0
  31. data/lib/rack/handler.rb +88 -0
  32. data/lib/rack/handler/cgi.rb +61 -0
  33. data/lib/rack/handler/evented_mongrel.rb +8 -0
  34. data/lib/rack/handler/fastcgi.rb +90 -0
  35. data/lib/rack/handler/lsws.rb +61 -0
  36. data/lib/rack/handler/mongrel.rb +90 -0
  37. data/lib/rack/handler/scgi.rb +59 -0
  38. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  39. data/lib/rack/handler/thin.rb +17 -0
  40. data/lib/rack/handler/webrick.rb +73 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +567 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +44 -0
  45. data/lib/rack/logger.rb +18 -0
  46. data/lib/rack/methodoverride.rb +27 -0
  47. data/lib/rack/mime.rb +210 -0
  48. data/lib/rack/mock.rb +185 -0
  49. data/lib/rack/nulllogger.rb +18 -0
  50. data/lib/rack/recursive.rb +61 -0
  51. data/lib/rack/reloader.rb +109 -0
  52. data/lib/rack/request.rb +307 -0
  53. data/lib/rack/response.rb +151 -0
  54. data/lib/rack/rewindable_input.rb +104 -0
  55. data/lib/rack/runtime.rb +27 -0
  56. data/lib/rack/sendfile.rb +139 -0
  57. data/lib/rack/server.rb +289 -0
  58. data/lib/rack/session/abstract/id.rb +348 -0
  59. data/lib/rack/session/cookie.rb +152 -0
  60. data/lib/rack/session/memcache.rb +93 -0
  61. data/lib/rack/session/pool.rb +79 -0
  62. data/lib/rack/showexceptions.rb +378 -0
  63. data/lib/rack/showstatus.rb +113 -0
  64. data/lib/rack/static.rb +53 -0
  65. data/lib/rack/urlmap.rb +55 -0
  66. data/lib/rack/utils.rb +698 -0
  67. data/rack.gemspec +39 -0
  68. data/test/cgi/lighttpd.conf +25 -0
  69. data/test/cgi/rackup_stub.rb +6 -0
  70. data/test/cgi/sample_rackup.ru +5 -0
  71. data/test/cgi/test +9 -0
  72. data/test/cgi/test.fcgi +8 -0
  73. data/test/cgi/test.ru +5 -0
  74. data/test/gemloader.rb +6 -0
  75. data/test/multipart/bad_robots +259 -0
  76. data/test/multipart/binary +0 -0
  77. data/test/multipart/empty +10 -0
  78. data/test/multipart/fail_16384_nofile +814 -0
  79. data/test/multipart/file1.txt +1 -0
  80. data/test/multipart/filename_and_modification_param +7 -0
  81. data/test/multipart/filename_with_escaped_quotes +6 -0
  82. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  83. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  84. data/test/multipart/filename_with_unescaped_quotes +6 -0
  85. data/test/multipart/ie +6 -0
  86. data/test/multipart/nested +10 -0
  87. data/test/multipart/none +9 -0
  88. data/test/multipart/semicolon +6 -0
  89. data/test/multipart/text +15 -0
  90. data/test/rackup/config.ru +31 -0
  91. data/test/spec_auth_basic.rb +70 -0
  92. data/test/spec_auth_digest.rb +241 -0
  93. data/test/spec_builder.rb +123 -0
  94. data/test/spec_cascade.rb +45 -0
  95. data/test/spec_cgi.rb +102 -0
  96. data/test/spec_chunked.rb +60 -0
  97. data/test/spec_commonlogger.rb +56 -0
  98. data/test/spec_conditionalget.rb +86 -0
  99. data/test/spec_config.rb +23 -0
  100. data/test/spec_content_length.rb +36 -0
  101. data/test/spec_content_type.rb +29 -0
  102. data/test/spec_deflater.rb +125 -0
  103. data/test/spec_directory.rb +57 -0
  104. data/test/spec_etag.rb +75 -0
  105. data/test/spec_fastcgi.rb +107 -0
  106. data/test/spec_file.rb +92 -0
  107. data/test/spec_handler.rb +49 -0
  108. data/test/spec_head.rb +30 -0
  109. data/test/spec_lint.rb +515 -0
  110. data/test/spec_lobster.rb +43 -0
  111. data/test/spec_lock.rb +142 -0
  112. data/test/spec_logger.rb +28 -0
  113. data/test/spec_methodoverride.rb +58 -0
  114. data/test/spec_mock.rb +241 -0
  115. data/test/spec_mongrel.rb +182 -0
  116. data/test/spec_nulllogger.rb +12 -0
  117. data/test/spec_recursive.rb +69 -0
  118. data/test/spec_request.rb +774 -0
  119. data/test/spec_response.rb +245 -0
  120. data/test/spec_rewindable_input.rb +118 -0
  121. data/test/spec_runtime.rb +39 -0
  122. data/test/spec_sendfile.rb +83 -0
  123. data/test/spec_server.rb +8 -0
  124. data/test/spec_session_abstract_id.rb +43 -0
  125. data/test/spec_session_cookie.rb +171 -0
  126. data/test/spec_session_memcache.rb +289 -0
  127. data/test/spec_session_pool.rb +200 -0
  128. data/test/spec_showexceptions.rb +87 -0
  129. data/test/spec_showstatus.rb +79 -0
  130. data/test/spec_static.rb +48 -0
  131. data/test/spec_thin.rb +86 -0
  132. data/test/spec_urlmap.rb +213 -0
  133. data/test/spec_utils.rb +678 -0
  134. data/test/spec_webrick.rb +141 -0
  135. data/test/testrequest.rb +78 -0
  136. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  137. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  138. metadata +329 -0
@@ -0,0 +1,348 @@
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
+ begin
8
+ require 'securerandom'
9
+ rescue LoadError
10
+ # We just won't get securerandom
11
+ end
12
+
13
+ module Rack
14
+
15
+ module Session
16
+
17
+ module Abstract
18
+ ENV_SESSION_KEY = 'rack.session'.freeze
19
+ ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
20
+
21
+ # Thin wrapper around Hash that allows us to lazily load session id into session_options.
22
+
23
+ class OptionsHash < Hash #:nodoc:
24
+ def initialize(by, env, default_options)
25
+ @by = by
26
+ @env = env
27
+ @session_id_loaded = false
28
+ merge!(default_options)
29
+ end
30
+
31
+ def [](key)
32
+ load_session_id! if key == :id && session_id_not_loaded?
33
+ super
34
+ end
35
+
36
+ private
37
+
38
+ def session_id_not_loaded?
39
+ !key?(:id) && !@session_id_loaded
40
+ end
41
+
42
+ def load_session_id!
43
+ self[:id] = @by.send(:extract_session_id, @env)
44
+ @session_id_loaded = true
45
+ end
46
+ end
47
+
48
+ # SessionHash is responsible to lazily load the session from store.
49
+
50
+ class SessionHash < Hash
51
+ def initialize(by, env)
52
+ super()
53
+ @by = by
54
+ @env = env
55
+ @loaded = false
56
+ end
57
+
58
+ def [](key)
59
+ load_for_read!
60
+ super(key.to_s)
61
+ end
62
+
63
+ def has_key?(key)
64
+ load_for_read!
65
+ super(key.to_s)
66
+ end
67
+ alias :key? :has_key?
68
+ alias :include? :has_key?
69
+
70
+ def []=(key, value)
71
+ load_for_write!
72
+ super(key.to_s, value)
73
+ end
74
+
75
+ def clear
76
+ load_for_write!
77
+ super
78
+ end
79
+
80
+ def to_hash
81
+ load_for_read!
82
+ h = {}.replace(self)
83
+ h.delete_if { |k,v| v.nil? }
84
+ h
85
+ end
86
+
87
+ def update(hash)
88
+ load_for_write!
89
+ super(stringify_keys(hash))
90
+ end
91
+
92
+ def delete(key)
93
+ load_for_write!
94
+ super(key.to_s)
95
+ end
96
+
97
+ def inspect
98
+ load_for_read!
99
+ super
100
+ end
101
+
102
+ def exists?
103
+ return @exists if instance_variable_defined?(:@exists)
104
+ @exists = @by.send(:session_exists?, @env)
105
+ end
106
+
107
+ def loaded?
108
+ @loaded
109
+ end
110
+
111
+ private
112
+
113
+ def load_for_read!
114
+ load! if !loaded? && exists?
115
+ end
116
+
117
+ def load_for_write!
118
+ load! unless loaded?
119
+ end
120
+
121
+ def load!
122
+ id, session = @by.send(:load_session, @env)
123
+ @env[ENV_SESSION_OPTIONS_KEY][:id] = id
124
+ replace(stringify_keys(session))
125
+ @loaded = true
126
+ end
127
+
128
+ def stringify_keys(other)
129
+ hash = {}
130
+ other.each do |key, value|
131
+ hash[key.to_s] = value
132
+ end
133
+ hash
134
+ end
135
+ end
136
+
137
+ # ID sets up a basic framework for implementing an id based sessioning
138
+ # service. Cookies sent to the client for maintaining sessions will only
139
+ # contain an id reference. Only #get_session and #set_session are
140
+ # required to be overwritten.
141
+ #
142
+ # All parameters are optional.
143
+ # * :key determines the name of the cookie, by default it is
144
+ # 'rack.session'
145
+ # * :path, :domain, :expire_after, :secure, and :httponly set the related
146
+ # cookie options as by Rack::Response#add_cookie
147
+ # * :defer will not set a cookie in the response.
148
+ # * :renew (implementation dependent) will prompt the generation of a new
149
+ # session id, and migration of data to be referenced at the new id. If
150
+ # :defer is set, it will be overridden and the cookie will be set.
151
+ # * :sidbits sets the number of bits in length that a generated session
152
+ # id will be.
153
+ #
154
+ # These options can be set on a per request basis, at the location of
155
+ # env['rack.session.options']. Additionally the id of the session can be
156
+ # found within the options hash at the key :id. It is highly not
157
+ # recommended to change its value.
158
+ #
159
+ # Is Rack::Utils::Context compatible.
160
+
161
+ class ID
162
+ DEFAULT_OPTIONS = {
163
+ :key => 'rack.session',
164
+ :path => '/',
165
+ :domain => nil,
166
+ :expire_after => nil,
167
+ :secure => false,
168
+ :httponly => true,
169
+ :defer => false,
170
+ :renew => false,
171
+ :sidbits => 128,
172
+ :cookie_only => true,
173
+ :secure_random => begin ::SecureRandom rescue false end
174
+ }
175
+
176
+ attr_reader :key, :default_options
177
+
178
+ def initialize(app, options={})
179
+ @app = app
180
+ @default_options = self.class::DEFAULT_OPTIONS.merge(options)
181
+ @key = options[:key] || "rack.session"
182
+ @cookie_only = @default_options.delete(:cookie_only)
183
+ initialize_sid
184
+ end
185
+
186
+ def call(env)
187
+ context(env)
188
+ end
189
+
190
+ def context(env, app=@app)
191
+ prepare_session(env)
192
+ status, headers, body = app.call(env)
193
+ commit_session(env, status, headers, body)
194
+ end
195
+
196
+ private
197
+
198
+ def initialize_sid
199
+ sidbits = @default_options.delete(:sidbits)
200
+ @sid_secure = @default_options.delete(:secure_random)
201
+ @sid_template = "%0#{sidbits / 4}x"
202
+ @sid_rand_width = (2**sidbits - 1)
203
+ end
204
+
205
+ # Generate a new session id using Ruby #rand. The size of the
206
+ # session id is controlled by the :sidbits option.
207
+ # Monkey patch this to use custom methods for session id generation.
208
+
209
+ def generate_sid
210
+ r = if @sid_secure
211
+ SecureRandom.random_number(@sid_rand_width)
212
+ else
213
+ Kernel.rand(@sid_rand_width)
214
+ end
215
+ @sid_template % r
216
+ end
217
+
218
+ # Sets the lazy session at 'rack.session' and places options and session
219
+ # metadata into 'rack.session.options'.
220
+
221
+ def prepare_session(env)
222
+ env[ENV_SESSION_KEY] = SessionHash.new(self, env)
223
+ env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
224
+ end
225
+
226
+ # Extracts the session id from provided cookies and passes it and the
227
+ # environment to #get_session.
228
+
229
+ def load_session(env)
230
+ sid = current_session_id(env)
231
+ sid, session = get_session(env, sid)
232
+ [sid, session || {}]
233
+ end
234
+
235
+ # Extract session id from request object.
236
+
237
+ def extract_session_id(env)
238
+ request = Rack::Request.new(env)
239
+ sid = request.cookies[@key]
240
+ sid ||= request.params[@key] unless @cookie_only
241
+ sid
242
+ end
243
+
244
+ # Returns the current session id from the OptionsHash.
245
+
246
+ def current_session_id(env)
247
+ env[ENV_SESSION_OPTIONS_KEY][:id]
248
+ end
249
+
250
+ # Check if the session exists or not.
251
+
252
+ def session_exists?(env)
253
+ value = current_session_id(env)
254
+ value && !value.empty?
255
+ end
256
+
257
+ # Session should be commited if it was loaded, any of specific options like :renew, :drop
258
+ # or :expire_after was given and the security permissions match.
259
+
260
+ def commit_session?(env, session, options)
261
+ (loaded_session?(session) || force_options?(options)) && secure_session?(env, options)
262
+ end
263
+
264
+ def loaded_session?(session)
265
+ !session.is_a?(SessionHash) || session.loaded?
266
+ end
267
+
268
+ def force_options?(options)
269
+ options.values_at(:renew, :drop, :defer, :expire_after).any?
270
+ end
271
+
272
+ def secure_session?(env, options)
273
+ return true unless options[:secure]
274
+ request = Rack::Request.new(env)
275
+ request.ssl?
276
+ end
277
+
278
+ # Acquires the session from the environment and the session id from
279
+ # the session options and passes them to #set_session. If successful
280
+ # and the :defer option is not true, a cookie will be added to the
281
+ # response with the session's id.
282
+
283
+ def commit_session(env, status, headers, body)
284
+ session = env['rack.session']
285
+ options = env['rack.session.options']
286
+
287
+ if options[:drop] || options[:renew]
288
+ session_id = destroy_session(env, options[:id] || generate_sid, options)
289
+ return [status, headers, body] unless session_id
290
+ end
291
+
292
+ return [status, headers, body] unless commit_session?(env, session, options)
293
+
294
+ session.send(:load!) unless loaded_session?(session)
295
+ session = session.to_hash
296
+ session_id ||= options[:id] || generate_sid
297
+
298
+ if not data = set_session(env, session_id, session, options)
299
+ env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
300
+ elsif options[:defer] and not options[:renew]
301
+ env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
302
+ else
303
+ cookie = Hash.new
304
+ cookie[:value] = data
305
+ cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
306
+ set_cookie(env, headers, cookie.merge!(options))
307
+ end
308
+
309
+ [status, headers, body]
310
+ end
311
+
312
+ # Sets the cookie back to the client with session id. We skip the cookie
313
+ # setting if the value didn't change (sid is the same) or expires was given.
314
+
315
+ def set_cookie(env, headers, cookie)
316
+ request = Rack::Request.new(env)
317
+ if request.cookies[@key] != cookie[:value] || cookie[:expires]
318
+ Utils.set_cookie_header!(headers, @key, cookie)
319
+ end
320
+ end
321
+
322
+ # All thread safety and session retrival proceedures should occur here.
323
+ # Should return [session_id, session].
324
+ # If nil is provided as the session id, generation of a new valid id
325
+ # should occur within.
326
+
327
+ def get_session(env, sid)
328
+ raise '#get_session not implemented.'
329
+ end
330
+
331
+ # All thread safety and session storage proceedures should occur here.
332
+ # Should return true or false dependant on whether or not the session
333
+ # was saved or not.
334
+
335
+ def set_session(env, sid, session, options)
336
+ raise '#set_session not implemented.'
337
+ end
338
+
339
+ # All thread safety and session destroy proceedures should occur here.
340
+ # Should return a new session id or nil if options[:drop]
341
+
342
+ def destroy_session(env, sid, options)
343
+ raise '#destroy_session not implemented'
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,152 @@
1
+ require 'openssl'
2
+ require 'rack/request'
3
+ require 'rack/response'
4
+ require 'rack/session/abstract/id'
5
+
6
+ module Rack
7
+
8
+ module Session
9
+
10
+ # Rack::Session::Cookie provides simple cookie based session management.
11
+ # By default, the session is a Ruby Hash stored as base64 encoded marshalled
12
+ # data set to :key (default: rack.session). The object that encodes the
13
+ # session data is configurable and must respond to +encode+ and +decode+.
14
+ # Both methods must take a string and return a string.
15
+ #
16
+ # When the secret key is set, cookie data is checked for data integrity.
17
+ #
18
+ # Example:
19
+ #
20
+ # use Rack::Session::Cookie, :key => 'rack.session',
21
+ # :domain => 'foo.com',
22
+ # :path => '/',
23
+ # :expire_after => 2592000,
24
+ # :secret => 'change_me'
25
+ #
26
+ # All parameters are optional.
27
+ #
28
+ # Example of a cookie with no encoding:
29
+ #
30
+ # Rack::Session::Cookie.new(application, {
31
+ # :coder => Racke::Session::Cookie::Identity.new
32
+ # })
33
+ #
34
+ # Example of a cookie with custom encoding:
35
+ #
36
+ # Rack::Session::Cookie.new(application, {
37
+ # :coder => Class.new {
38
+ # def encode(str); str.reverse; end
39
+ # def decode(str); str.reverse; end
40
+ # }.new
41
+ # })
42
+ #
43
+
44
+ class Cookie < Abstract::ID
45
+ # Encode session cookies as Base64
46
+ class Base64
47
+ def encode(str)
48
+ [str].pack('m')
49
+ end
50
+
51
+ def decode(str)
52
+ str.unpack('m').first
53
+ end
54
+
55
+ # Encode session cookies as Marshaled Base64 data
56
+ class Marshal < Base64
57
+ def encode(str)
58
+ super(::Marshal.dump(str))
59
+ end
60
+
61
+ def decode(str)
62
+ ::Marshal.load(super(str)) rescue nil
63
+ end
64
+ end
65
+ end
66
+
67
+ # Use no encoding for session cookies
68
+ class Identity
69
+ def encode(str); str; end
70
+ def decode(str); str; end
71
+ end
72
+
73
+ # Reverse string encoding. (trollface)
74
+ class Reverse
75
+ def encode(str); str.reverse; end
76
+ def decode(str); str.reverse; end
77
+ end
78
+
79
+ attr_reader :coder
80
+
81
+ def initialize(app, options={})
82
+ @secret = options.delete(:secret)
83
+ @coder = options.delete(:coder) || Base64::Marshal.new
84
+ super(app, options.merge!(:cookie_only => true))
85
+ end
86
+
87
+ private
88
+
89
+ def load_session(env)
90
+ data = unpacked_cookie_data(env)
91
+ data = persistent_session_id!(data)
92
+ [data["session_id"], data]
93
+ end
94
+
95
+ def extract_session_id(env)
96
+ unpacked_cookie_data(env)["session_id"]
97
+ end
98
+
99
+ def unpacked_cookie_data(env)
100
+ env["rack.session.unpacked_cookie_data"] ||= begin
101
+ request = Rack::Request.new(env)
102
+ session_data = request.cookies[@key]
103
+
104
+ if @secret && session_data
105
+ session_data, digest = session_data.split("--")
106
+ session_data = nil unless digest == generate_hmac(session_data)
107
+ end
108
+
109
+ coder.decode(session_data) || {}
110
+ end
111
+ end
112
+
113
+ def persistent_session_id!(data, sid=nil)
114
+ data ||= {}
115
+ data["session_id"] ||= sid || generate_sid
116
+ data
117
+ end
118
+
119
+ # Overwrite set cookie to bypass content equality and always stream the cookie.
120
+
121
+ def set_cookie(env, headers, cookie)
122
+ Utils.set_cookie_header!(headers, @key, cookie)
123
+ end
124
+
125
+ def set_session(env, session_id, session, options)
126
+ session = persistent_session_id!(session, session_id)
127
+ session_data = coder.encode(session)
128
+
129
+ if @secret
130
+ session_data = "#{session_data}--#{generate_hmac(session_data)}"
131
+ end
132
+
133
+ if session_data.size > (4096 - @key.size)
134
+ env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
135
+ nil
136
+ else
137
+ session_data
138
+ end
139
+ end
140
+
141
+ def destroy_session(env, session_id, options)
142
+ # Nothing to do here, data is in the client
143
+ generate_sid unless options[:drop]
144
+ end
145
+
146
+ def generate_hmac(data)
147
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
148
+ end
149
+
150
+ end
151
+ end
152
+ end