qoobaa-rack 1.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +18 -0
  3. data/RDOX +0 -0
  4. data/README +353 -0
  5. data/Rakefile +164 -0
  6. data/SPEC +164 -0
  7. data/bin/rackup +176 -0
  8. data/contrib/rack_logo.svg +111 -0
  9. data/example/lobster.ru +4 -0
  10. data/example/protectedlobster.rb +14 -0
  11. data/example/protectedlobster.ru +8 -0
  12. data/lib/rack/adapter/camping.rb +22 -0
  13. data/lib/rack/auth/abstract/handler.rb +37 -0
  14. data/lib/rack/auth/abstract/request.rb +37 -0
  15. data/lib/rack/auth/basic.rb +58 -0
  16. data/lib/rack/auth/digest/md5.rb +124 -0
  17. data/lib/rack/auth/digest/nonce.rb +51 -0
  18. data/lib/rack/auth/digest/params.rb +55 -0
  19. data/lib/rack/auth/digest/request.rb +40 -0
  20. data/lib/rack/auth/openid.rb +487 -0
  21. data/lib/rack/builder.rb +63 -0
  22. data/lib/rack/cascade.rb +41 -0
  23. data/lib/rack/chunked.rb +49 -0
  24. data/lib/rack/commonlogger.rb +52 -0
  25. data/lib/rack/conditionalget.rb +47 -0
  26. data/lib/rack/content_length.rb +29 -0
  27. data/lib/rack/content_type.rb +23 -0
  28. data/lib/rack/deflater.rb +96 -0
  29. data/lib/rack/directory.rb +153 -0
  30. data/lib/rack/file.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 +88 -0
  34. data/lib/rack/handler/lsws.rb +60 -0
  35. data/lib/rack/handler/mongrel.rb +87 -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 +71 -0
  40. data/lib/rack/handler.rb +69 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +546 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +16 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +204 -0
  47. data/lib/rack/mock.rb +187 -0
  48. data/lib/rack/recursive.rb +57 -0
  49. data/lib/rack/reloader.rb +107 -0
  50. data/lib/rack/request.rb +248 -0
  51. data/lib/rack/response.rb +183 -0
  52. data/lib/rack/rewindable_input.rb +100 -0
  53. data/lib/rack/session/abstract/id.rb +142 -0
  54. data/lib/rack/session/cookie.rb +91 -0
  55. data/lib/rack/session/memcache.rb +109 -0
  56. data/lib/rack/session/pool.rb +100 -0
  57. data/lib/rack/showexceptions.rb +349 -0
  58. data/lib/rack/showstatus.rb +106 -0
  59. data/lib/rack/static.rb +38 -0
  60. data/lib/rack/urlmap.rb +55 -0
  61. data/lib/rack/utils.rb +528 -0
  62. data/lib/rack.rb +90 -0
  63. data/rack.gemspec +60 -0
  64. data/test/cgi/lighttpd.conf +20 -0
  65. data/test/cgi/test +9 -0
  66. data/test/cgi/test.fcgi +8 -0
  67. data/test/cgi/test.ru +7 -0
  68. data/test/multipart/binary +0 -0
  69. data/test/multipart/empty +10 -0
  70. data/test/multipart/file1.txt +1 -0
  71. data/test/multipart/ie +6 -0
  72. data/test/multipart/nested +10 -0
  73. data/test/multipart/none +9 -0
  74. data/test/multipart/text +10 -0
  75. data/test/spec_rack_auth_basic.rb +73 -0
  76. data/test/spec_rack_auth_digest.rb +226 -0
  77. data/test/spec_rack_auth_openid.rb +84 -0
  78. data/test/spec_rack_builder.rb +84 -0
  79. data/test/spec_rack_camping.rb +51 -0
  80. data/test/spec_rack_cascade.rb +48 -0
  81. data/test/spec_rack_cgi.rb +89 -0
  82. data/test/spec_rack_chunked.rb +62 -0
  83. data/test/spec_rack_commonlogger.rb +61 -0
  84. data/test/spec_rack_conditionalget.rb +41 -0
  85. data/test/spec_rack_content_length.rb +43 -0
  86. data/test/spec_rack_content_type.rb +30 -0
  87. data/test/spec_rack_deflater.rb +127 -0
  88. data/test/spec_rack_directory.rb +61 -0
  89. data/test/spec_rack_fastcgi.rb +89 -0
  90. data/test/spec_rack_file.rb +75 -0
  91. data/test/spec_rack_handler.rb +43 -0
  92. data/test/spec_rack_head.rb +30 -0
  93. data/test/spec_rack_lint.rb +521 -0
  94. data/test/spec_rack_lobster.rb +45 -0
  95. data/test/spec_rack_lock.rb +38 -0
  96. data/test/spec_rack_methodoverride.rb +60 -0
  97. data/test/spec_rack_mock.rb +243 -0
  98. data/test/spec_rack_mongrel.rb +189 -0
  99. data/test/spec_rack_recursive.rb +77 -0
  100. data/test/spec_rack_request.rb +504 -0
  101. data/test/spec_rack_response.rb +218 -0
  102. data/test/spec_rack_rewindable_input.rb +118 -0
  103. data/test/spec_rack_session_cookie.rb +82 -0
  104. data/test/spec_rack_session_memcache.rb +250 -0
  105. data/test/spec_rack_session_pool.rb +172 -0
  106. data/test/spec_rack_showexceptions.rb +21 -0
  107. data/test/spec_rack_showstatus.rb +72 -0
  108. data/test/spec_rack_static.rb +37 -0
  109. data/test/spec_rack_thin.rb +91 -0
  110. data/test/spec_rack_urlmap.rb +185 -0
  111. data/test/spec_rack_utils.rb +467 -0
  112. data/test/spec_rack_webrick.rb +130 -0
  113. data/test/testrequest.rb +57 -0
  114. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  115. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  116. metadata +276 -0
@@ -0,0 +1,248 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack
4
+ # Rack::Request provides a convenient interface to a Rack
5
+ # environment. It is stateless, the environment +env+ passed to the
6
+ # constructor will be directly modified.
7
+ #
8
+ # req = Rack::Request.new(env)
9
+ # req.post?
10
+ # req.params["data"]
11
+ #
12
+ # The environment hash passed will store a reference to the Request object
13
+ # instantiated so that it will only instantiate if an instance of the Request
14
+ # object doesn't already exist.
15
+
16
+ class Request
17
+ # The environment of the request.
18
+ attr_reader :env
19
+
20
+ def initialize(env)
21
+ @env = env
22
+ end
23
+
24
+ def body; @env["rack.input"] end
25
+ def scheme; @env["rack.url_scheme"] end
26
+ def script_name; @env["SCRIPT_NAME"].to_s end
27
+ def path_info; @env["PATH_INFO"].to_s end
28
+ def port; @env["SERVER_PORT"].to_i end
29
+ def request_method; @env["REQUEST_METHOD"] end
30
+ def query_string; @env["QUERY_STRING"].to_s end
31
+ def content_length; @env['CONTENT_LENGTH'] end
32
+ def content_type; @env['CONTENT_TYPE'] end
33
+ def session; @env['rack.session'] ||= {} end
34
+ def session_options; @env['rack.session.options'] ||= {} end
35
+
36
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
37
+ # without any media type parameters. e.g., when CONTENT_TYPE is
38
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
39
+ #
40
+ # For more information on the use of media types in HTTP, see:
41
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
42
+ def media_type
43
+ content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
44
+ end
45
+
46
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
47
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
48
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
49
+ # this method responds with the following Hash:
50
+ # { 'charset' => 'utf-8' }
51
+ def media_type_params
52
+ return {} if content_type.nil?
53
+ content_type.split(/\s*[;,]\s*/)[1..-1].
54
+ collect { |s| s.split('=', 2) }.
55
+ inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash }
56
+ end
57
+
58
+ # The character set of the request body if a "charset" media type
59
+ # parameter was given, or nil if no "charset" was specified. Note
60
+ # that, per RFC2616, text/* media types that specify no explicit
61
+ # charset are to be considered ISO-8859-1.
62
+ def content_charset
63
+ media_type_params['charset']
64
+ end
65
+
66
+ def host
67
+ # Remove port number.
68
+ (@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '')
69
+ end
70
+
71
+ def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
72
+ def path_info=(s); @env["PATH_INFO"] = s.to_s end
73
+
74
+ def get?; request_method == "GET" end
75
+ def post?; request_method == "POST" end
76
+ def put?; request_method == "PUT" end
77
+ def delete?; request_method == "DELETE" end
78
+ def head?; request_method == "HEAD" end
79
+
80
+ # The set of form-data media-types. Requests that do not indicate
81
+ # one of the media types presents in this list will not be eligible
82
+ # for form-data / param parsing.
83
+ FORM_DATA_MEDIA_TYPES = [
84
+ nil,
85
+ 'application/x-www-form-urlencoded',
86
+ 'multipart/form-data'
87
+ ]
88
+
89
+ # The set of media-types. Requests that do not indicate
90
+ # one of the media types presents in this list will not be eligible
91
+ # for param parsing like soap attachments or generic multiparts
92
+ PARSEABLE_DATA_MEDIA_TYPES = [
93
+ 'multipart/related',
94
+ 'multipart/mixed'
95
+ ]
96
+
97
+ # Determine whether the request body contains form-data by checking
98
+ # the request media_type against registered form-data media-types:
99
+ # "application/x-www-form-urlencoded" and "multipart/form-data". The
100
+ # list of form-data media types can be modified through the
101
+ # +FORM_DATA_MEDIA_TYPES+ array.
102
+ def form_data?
103
+ FORM_DATA_MEDIA_TYPES.include?(media_type)
104
+ end
105
+
106
+ # Determine whether the request body contains data by checking
107
+ # the request media_type against registered parse-data media-types
108
+ def parseable_data?
109
+ PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
110
+ end
111
+
112
+ # Returns the data recieved in the query string.
113
+ def GET
114
+ if @env["rack.request.query_string"] == query_string
115
+ @env["rack.request.query_hash"]
116
+ else
117
+ @env["rack.request.query_string"] = query_string
118
+ @env["rack.request.query_hash"] =
119
+ Utils.parse_nested_query(query_string)
120
+ end
121
+ end
122
+
123
+ # Returns the data recieved in the request body.
124
+ #
125
+ # This method support both application/x-www-form-urlencoded and
126
+ # multipart/form-data.
127
+ def POST
128
+ if @env["rack.input"].nil?
129
+ raise "Missing rack.input"
130
+ elsif @env["rack.request.form_input"].eql? @env["rack.input"]
131
+ @env["rack.request.form_hash"]
132
+ elsif form_data? || parseable_data?
133
+ @env["rack.request.form_input"] = @env["rack.input"]
134
+ unless @env["rack.request.form_hash"] =
135
+ Utils::Multipart.parse_multipart(env)
136
+ form_vars = @env["rack.input"].read
137
+
138
+ # Fix for Safari Ajax postings that always append \0
139
+ form_vars.sub!(/\0\z/, '')
140
+
141
+ @env["rack.request.form_vars"] = form_vars
142
+ @env["rack.request.form_hash"] = Utils.parse_nested_query(form_vars)
143
+
144
+ @env["rack.input"].rewind
145
+ end
146
+ @env["rack.request.form_hash"]
147
+ else
148
+ {}
149
+ end
150
+ end
151
+
152
+ # The union of GET and POST data.
153
+ def params
154
+ self.put? ? self.GET : self.GET.update(self.POST)
155
+ rescue EOFError => e
156
+ self.GET
157
+ end
158
+
159
+ # shortcut for request.params[key]
160
+ def [](key)
161
+ params[key.to_s]
162
+ end
163
+
164
+ # shortcut for request.params[key] = value
165
+ def []=(key, value)
166
+ params[key.to_s] = value
167
+ end
168
+
169
+ # like Hash#values_at
170
+ def values_at(*keys)
171
+ keys.map{|key| params[key] }
172
+ end
173
+
174
+ # the referer of the client or '/'
175
+ def referer
176
+ @env['HTTP_REFERER'] || '/'
177
+ end
178
+ alias referrer referer
179
+
180
+
181
+ def cookies
182
+ return {} unless @env["HTTP_COOKIE"]
183
+
184
+ if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
185
+ @env["rack.request.cookie_hash"]
186
+ else
187
+ @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
188
+ # According to RFC 2109:
189
+ # If multiple cookies satisfy the criteria above, they are ordered in
190
+ # the Cookie header such that those with more specific Path attributes
191
+ # precede those with less specific. Ordering with respect to other
192
+ # attributes (e.g., Domain) is unspecified.
193
+ @env["rack.request.cookie_hash"] =
194
+ Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)|
195
+ h[k] = Array === v ? v.first : v
196
+ h
197
+ }
198
+ end
199
+ end
200
+
201
+ def xhr?
202
+ @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
203
+ end
204
+
205
+ # Tries to return a remake of the original request URL as a string.
206
+ def url
207
+ url = scheme + "://"
208
+ url << host
209
+
210
+ if scheme == "https" && port != 443 ||
211
+ scheme == "http" && port != 80
212
+ url << ":#{port}"
213
+ end
214
+
215
+ url << fullpath
216
+
217
+ url
218
+ end
219
+
220
+ def path
221
+ script_name + path_info
222
+ end
223
+
224
+ def fullpath
225
+ query_string.empty? ? path : "#{path}?#{query_string}"
226
+ end
227
+
228
+ def accept_encoding
229
+ @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
230
+ m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
231
+
232
+ if m
233
+ [m[1], (m[2] || 1.0).to_f]
234
+ else
235
+ raise "Invalid value for Accept-Encoding: #{part.inspect}"
236
+ end
237
+ end
238
+ end
239
+
240
+ def ip
241
+ if addr = @env['HTTP_X_FORWARDED_FOR']
242
+ addr.split(',').last.strip
243
+ else
244
+ @env['REMOTE_ADDR']
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,183 @@
1
+ require 'rack/request'
2
+ require 'rack/utils'
3
+
4
+ module Rack
5
+ # Rack::Response provides a convenient interface to create a Rack
6
+ # response.
7
+ #
8
+ # It allows setting of headers and cookies, and provides useful
9
+ # defaults (a OK response containing HTML).
10
+ #
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
+ # +finish+. +finish+ however can take a block inside which calls to
14
+ # +write+ are syncronous with the Rack response.
15
+ #
16
+ # Your application's +call+ should end returning Response#finish.
17
+
18
+ class Response
19
+ attr_accessor :length
20
+
21
+ def initialize(body=[], status=200, header={}, &block)
22
+ @status = status
23
+ @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
24
+ merge(header))
25
+
26
+ @writer = lambda { |x| @body << x }
27
+ @block = nil
28
+ @length = 0
29
+
30
+ @body = []
31
+
32
+ if body.respond_to? :to_str
33
+ write body.to_str
34
+ elsif body.respond_to?(:each)
35
+ body.each { |part|
36
+ write part.to_s
37
+ }
38
+ else
39
+ raise TypeError, "stringable or iterable required"
40
+ end
41
+
42
+ yield self if block_given?
43
+ end
44
+
45
+ attr_reader :header
46
+ attr_accessor :status, :body
47
+
48
+ def [](key)
49
+ header[key]
50
+ end
51
+
52
+ def []=(key, value)
53
+ header[key] = value
54
+ end
55
+
56
+ def set_cookie(key, value)
57
+ case value
58
+ when Hash
59
+ domain = "; domain=" + value[:domain] if value[:domain]
60
+ path = "; path=" + value[:path] if value[:path]
61
+ # According to RFC 2109, we need dashes here.
62
+ # N.B.: cgi.rb uses spaces...
63
+ expires = "; expires=" + value[:expires].clone.gmtime.
64
+ strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
65
+ secure = "; secure" if value[:secure]
66
+ httponly = "; HttpOnly" if value[:httponly]
67
+ value = value[:value]
68
+ end
69
+ value = [value] unless Array === value
70
+ cookie = Utils.escape(key) + "=" +
71
+ value.map { |v| Utils.escape v }.join("&") +
72
+ "#{domain}#{path}#{expires}#{secure}#{httponly}"
73
+
74
+ case self["Set-Cookie"]
75
+ when Array
76
+ self["Set-Cookie"] << cookie
77
+ when String
78
+ self["Set-Cookie"] = [self["Set-Cookie"], cookie]
79
+ when nil
80
+ self["Set-Cookie"] = cookie
81
+ end
82
+ end
83
+
84
+ def delete_cookie(key, value={})
85
+ unless Array === self["Set-Cookie"]
86
+ self["Set-Cookie"] = [self["Set-Cookie"]].compact
87
+ end
88
+
89
+ self["Set-Cookie"].reject! { |cookie|
90
+ cookie =~ /\A#{Utils.escape(key)}=/
91
+ }
92
+
93
+ set_cookie(key,
94
+ {:value => '', :path => nil, :domain => nil,
95
+ :expires => Time.at(0) }.merge(value))
96
+ end
97
+
98
+ def redirect(target, status=302)
99
+ self.status = status
100
+ self["Location"] = target
101
+ end
102
+
103
+ def finish(&block)
104
+ @block = block
105
+
106
+ if [204, 304].include?(status.to_i)
107
+ header.delete "Content-Type"
108
+ [status.to_i, header.to_hash, []]
109
+ else
110
+ [status.to_i, header.to_hash, self]
111
+ end
112
+ end
113
+ alias to_a finish # For *response
114
+
115
+ def each(&callback)
116
+ @body.each(&callback)
117
+ @writer = callback
118
+ @block.call(self) if @block
119
+ end
120
+
121
+ # Append to body and update Content-Length.
122
+ #
123
+ # NOTE: Do not mix #write and direct #body access!
124
+ #
125
+ def write(str)
126
+ s = str.to_s
127
+ @length += Rack::Utils.bytesize(s)
128
+ @writer.call s
129
+
130
+ header["Content-Length"] = @length.to_s
131
+ str
132
+ end
133
+
134
+ def close
135
+ body.close if body.respond_to?(:close)
136
+ end
137
+
138
+ def empty?
139
+ @block == nil && @body.empty?
140
+ end
141
+
142
+ alias headers header
143
+
144
+ module Helpers
145
+ def invalid?; @status < 100 || @status >= 600; end
146
+
147
+ def informational?; @status >= 100 && @status < 200; end
148
+ def successful?; @status >= 200 && @status < 300; end
149
+ def redirection?; @status >= 300 && @status < 400; end
150
+ def client_error?; @status >= 400 && @status < 500; end
151
+ def server_error?; @status >= 500 && @status < 600; end
152
+
153
+ def ok?; @status == 200; end
154
+ def forbidden?; @status == 403; end
155
+ def not_found?; @status == 404; end
156
+
157
+ def redirect?; [301, 302, 303, 307].include? @status; end
158
+ def empty?; [201, 204, 304].include? @status; end
159
+
160
+ # Headers
161
+ attr_reader :headers, :original_headers
162
+
163
+ def include?(header)
164
+ !!headers[header]
165
+ end
166
+
167
+ def content_type
168
+ headers["Content-Type"]
169
+ end
170
+
171
+ def content_length
172
+ cl = headers["Content-Length"]
173
+ cl ? cl.to_i : cl
174
+ end
175
+
176
+ def location
177
+ headers["Location"]
178
+ end
179
+ end
180
+
181
+ include Helpers
182
+ end
183
+ end
@@ -0,0 +1,100 @@
1
+ require 'tempfile'
2
+
3
+ module Rack
4
+ # Class which can make any IO object rewindable, including non-rewindable ones. It does
5
+ # this by buffering the data into a tempfile, which is rewindable.
6
+ #
7
+ # rack.input is required to be rewindable, so if your input stream IO is non-rewindable
8
+ # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
9
+ # to easily make it rewindable.
10
+ #
11
+ # Don't forget to call #close when you're done. This frees up temporary resources that
12
+ # RewindableInput uses, though it does *not* close the original IO object.
13
+ class RewindableInput
14
+ def initialize(io)
15
+ @io = io
16
+ @rewindable_io = nil
17
+ @unlinked = false
18
+ end
19
+
20
+ def gets
21
+ make_rewindable unless @rewindable_io
22
+ @rewindable_io.gets
23
+ end
24
+
25
+ def read(*args)
26
+ make_rewindable unless @rewindable_io
27
+ @rewindable_io.read(*args)
28
+ end
29
+
30
+ def each(&block)
31
+ make_rewindable unless @rewindable_io
32
+ @rewindable_io.each(&block)
33
+ end
34
+
35
+ def rewind
36
+ make_rewindable unless @rewindable_io
37
+ @rewindable_io.rewind
38
+ end
39
+
40
+ # Closes this RewindableInput object without closing the originally
41
+ # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
42
+ # has created.
43
+ #
44
+ # This method may be called multiple times. It does nothing on subsequent calls.
45
+ def close
46
+ if @rewindable_io
47
+ if @unlinked
48
+ @rewindable_io.close
49
+ else
50
+ @rewindable_io.close!
51
+ end
52
+ @rewindable_io = nil
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ # Ruby's Tempfile class has a bug. Subclass it and fix it.
59
+ class Tempfile < ::Tempfile
60
+ def _close
61
+ @tmpfile.close if @tmpfile
62
+ @data[1] = nil if @data
63
+ @tmpfile = nil
64
+ end
65
+ end
66
+
67
+ def make_rewindable
68
+ # Buffer all data into a tempfile. Since this tempfile is private to this
69
+ # RewindableInput object, we chmod it so that nobody else can read or write
70
+ # it. On POSIX filesystems we also unlink the file so that it doesn't
71
+ # even have a file entry on the filesystem anymore, though we can still
72
+ # access it because we have the file handle open.
73
+ @rewindable_io = Tempfile.new('RackRewindableInput')
74
+ @rewindable_io.chmod(0000)
75
+ @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
76
+ @rewindable_io.binmode
77
+ if filesystem_has_posix_semantics?
78
+ @rewindable_io.unlink
79
+ @unlinked = true
80
+ end
81
+
82
+ buffer = ""
83
+ while @io.read(1024 * 4, buffer)
84
+ entire_buffer_written_out = false
85
+ while !entire_buffer_written_out
86
+ written = @rewindable_io.write(buffer)
87
+ entire_buffer_written_out = written == buffer.size
88
+ if !entire_buffer_written_out
89
+ buffer.slice!(0 .. written - 1)
90
+ end
91
+ end
92
+ end
93
+ @rewindable_io.rewind
94
+ end
95
+
96
+ def filesystem_has_posix_semantics?
97
+ RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,142 @@
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
+ [status, headers, body]
111
+ elsif options[:defer] and not options[:renew]
112
+ env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
113
+ [status, headers, body]
114
+ else
115
+ cookie = Hash.new
116
+ cookie[:value] = session_id
117
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
118
+ response = Rack::Response.new(body, status, headers)
119
+ response.set_cookie(@key, cookie.merge(options))
120
+ response.to_a
121
+ end
122
+ end
123
+
124
+ # All thread safety and session retrival proceedures should occur here.
125
+ # Should return [session_id, session].
126
+ # If nil is provided as the session id, generation of a new valid id
127
+ # should occur within.
128
+
129
+ def get_session(env, sid)
130
+ raise '#get_session not implemented.'
131
+ end
132
+
133
+ # All thread safety and session storage proceedures should occur here.
134
+ # Should return true or false dependant on whether or not the session
135
+ # was saved or not.
136
+ def set_session(env, sid, session, options)
137
+ raise '#set_session not implemented.'
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end