harbor 0.16.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.
- data/Rakefile +76 -0
- data/bin/harbor +7 -0
- data/lib/harbor.rb +17 -0
- data/lib/harbor/accessor_injector.rb +30 -0
- data/lib/harbor/application.rb +172 -0
- data/lib/harbor/auth/basic.rb +51 -0
- data/lib/harbor/block_io.rb +63 -0
- data/lib/harbor/cache.rb +90 -0
- data/lib/harbor/cache/disk.rb +99 -0
- data/lib/harbor/cache/item.rb +48 -0
- data/lib/harbor/cache/memory.rb +35 -0
- data/lib/harbor/cascade.rb +75 -0
- data/lib/harbor/console.rb +34 -0
- data/lib/harbor/container.rb +134 -0
- data/lib/harbor/contrib/debug.rb +236 -0
- data/lib/harbor/contrib/session/data_mapper.rb +74 -0
- data/lib/harbor/daemon.rb +105 -0
- data/lib/harbor/errors.rb +49 -0
- data/lib/harbor/events.rb +45 -0
- data/lib/harbor/exception_notifier.rb +59 -0
- data/lib/harbor/file.rb +66 -0
- data/lib/harbor/file_store.rb +69 -0
- data/lib/harbor/file_store/file.rb +100 -0
- data/lib/harbor/file_store/local.rb +71 -0
- data/lib/harbor/file_store/mosso.rb +154 -0
- data/lib/harbor/file_store/mosso/private.rb +8 -0
- data/lib/harbor/generator.rb +56 -0
- data/lib/harbor/generator/help.rb +34 -0
- data/lib/harbor/generator/setup.rb +82 -0
- data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
- data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
- data/lib/harbor/hooks.rb +105 -0
- data/lib/harbor/json_cookies.rb +37 -0
- data/lib/harbor/layouts.rb +61 -0
- data/lib/harbor/locale.rb +50 -0
- data/lib/harbor/locales.txt +22 -0
- data/lib/harbor/logging.rb +39 -0
- data/lib/harbor/logging/appenders/email.rb +84 -0
- data/lib/harbor/logging/request_logger.rb +34 -0
- data/lib/harbor/mail_servers/abstract.rb +12 -0
- data/lib/harbor/mail_servers/sendmail.rb +19 -0
- data/lib/harbor/mail_servers/smtp.rb +25 -0
- data/lib/harbor/mail_servers/test.rb +17 -0
- data/lib/harbor/mailer.rb +96 -0
- data/lib/harbor/messages.rb +17 -0
- data/lib/harbor/mime.rb +206 -0
- data/lib/harbor/plugin.rb +52 -0
- data/lib/harbor/plugin_list.rb +38 -0
- data/lib/harbor/request.rb +138 -0
- data/lib/harbor/response.rb +281 -0
- data/lib/harbor/router.rb +112 -0
- data/lib/harbor/script.rb +155 -0
- data/lib/harbor/session.rb +75 -0
- data/lib/harbor/session/abstract.rb +27 -0
- data/lib/harbor/session/cookie.rb +17 -0
- data/lib/harbor/shellwords.rb +26 -0
- data/lib/harbor/test/mailer.rb +10 -0
- data/lib/harbor/test/request.rb +28 -0
- data/lib/harbor/test/response.rb +17 -0
- data/lib/harbor/test/session.rb +11 -0
- data/lib/harbor/test/test.rb +22 -0
- data/lib/harbor/version.rb +3 -0
- data/lib/harbor/view.rb +89 -0
- data/lib/harbor/view_context.rb +134 -0
- data/lib/harbor/view_context/helpers.rb +7 -0
- data/lib/harbor/view_context/helpers/cache.rb +77 -0
- data/lib/harbor/view_context/helpers/form.rb +34 -0
- data/lib/harbor/view_context/helpers/html.rb +26 -0
- data/lib/harbor/view_context/helpers/text.rb +120 -0
- data/lib/harbor/view_context/helpers/url.rb +11 -0
- data/lib/harbor/xml_view.rb +57 -0
- data/lib/harbor/zipped_io.rb +203 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
- metadata +155 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require "harbor/accessor_injector"
|
2
|
+
|
3
|
+
module Harbor
|
4
|
+
class Plugin
|
5
|
+
|
6
|
+
class VariableMissingError < StandardError
|
7
|
+
def initialize(klass, variable)
|
8
|
+
super("#{klass} expected #{variable.inspect} to be present, but it wasn't.")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
include Harbor::AccessorInjector
|
13
|
+
include Harbor::Hooks
|
14
|
+
|
15
|
+
attr_accessor :context
|
16
|
+
|
17
|
+
def self.prepare(plugin, context, variables)
|
18
|
+
if plugin.is_a?(Class)
|
19
|
+
plugin.new(context).inject(variables)
|
20
|
+
else
|
21
|
+
plugin.inject({ :context => context }.merge(variables))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(context)
|
26
|
+
@context = context
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.requires(key)
|
30
|
+
before(:to_s) do |instance|
|
31
|
+
raise VariableMissingError.new(self, key) unless instance.instance_variable_defined?("@#{key}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
raise NotImplementedError.new("You must define your own #to_s method.")
|
37
|
+
end
|
38
|
+
|
39
|
+
class String < Plugin
|
40
|
+
|
41
|
+
def initialize(string)
|
42
|
+
@string = string
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
@string
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Harbor
|
2
|
+
class PluginList
|
3
|
+
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@plugins = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def each
|
11
|
+
@plugins.each do |plugin|
|
12
|
+
yield plugin
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def size
|
17
|
+
@plugins.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear
|
21
|
+
@plugins.clear
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(plugin)
|
25
|
+
case plugin
|
26
|
+
when String
|
27
|
+
plugin = Harbor::Plugin::String.new(plugin)
|
28
|
+
when Class
|
29
|
+
raise ArgumentError.new("#{plugin} must be a Plugin") unless Plugin > plugin
|
30
|
+
else
|
31
|
+
raise ArgumentError.new("#{plugin} must include Harbor::AccessorInjector") unless AccessorInjector > plugin
|
32
|
+
end
|
33
|
+
|
34
|
+
@plugins << plugin
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "rack/request"
|
2
|
+
require Pathname(__FILE__).dirname + "session"
|
3
|
+
|
4
|
+
module Harbor
|
5
|
+
class Request < Rack::Request
|
6
|
+
|
7
|
+
BOT_AGENTS = [
|
8
|
+
/yahoo.*slurp/i,
|
9
|
+
/googlebot/i,
|
10
|
+
/msnbot/i,
|
11
|
+
/charlotte.*searchme/i,
|
12
|
+
/twiceler.*robot/i,
|
13
|
+
/dotbot/i,
|
14
|
+
/gigabot/i,
|
15
|
+
/yanga.*bot/i,
|
16
|
+
/gaisbot/i,
|
17
|
+
/becomebot/i,
|
18
|
+
/yandex/i,
|
19
|
+
/catchbot/i,
|
20
|
+
/cazoodlebot/i,
|
21
|
+
/jumblebot/i,
|
22
|
+
/librabot/i,
|
23
|
+
/jyxobot/i,
|
24
|
+
/mlbot/i,
|
25
|
+
/cipinetbot/i,
|
26
|
+
/funnelbot/i,
|
27
|
+
/mj12bot/i,
|
28
|
+
/spinn3r/i,
|
29
|
+
/nutch.*bot/i,
|
30
|
+
/oozbot/i,
|
31
|
+
/robotgenius/i,
|
32
|
+
/snapbot/i,
|
33
|
+
/tmangobot/i,
|
34
|
+
/yacybot/i,
|
35
|
+
/rpt.*httpclient/i,
|
36
|
+
/indy.*library/i,
|
37
|
+
/baiduspider/i,
|
38
|
+
/WhistleBlower/i,
|
39
|
+
/Pingdom/
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
attr_accessor :layout
|
43
|
+
attr_accessor :application
|
44
|
+
|
45
|
+
def initialize(application, env)
|
46
|
+
raise ArgumentError.new("+env+ must be a Rack Environment Hash") unless env.is_a?(Hash)
|
47
|
+
@application = application
|
48
|
+
super(env)
|
49
|
+
end
|
50
|
+
|
51
|
+
def fetch(key, default_value = nil)
|
52
|
+
if (value = self[key]).nil? || value == ''
|
53
|
+
default_value
|
54
|
+
else
|
55
|
+
value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def bot?
|
60
|
+
user_agent = env["HTTP_USER_AGENT"]
|
61
|
+
BOT_AGENTS.any? { |bot_agent| user_agent =~ bot_agent }
|
62
|
+
end
|
63
|
+
|
64
|
+
def session
|
65
|
+
@session ||= Harbor::Session.new(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def session?
|
69
|
+
@session
|
70
|
+
end
|
71
|
+
|
72
|
+
def remote_ip
|
73
|
+
env["REMOTE_ADDR"] || env["HTTP_CLIENT_IP"] || env["HTTP_X_FORWARDED_FOR"]
|
74
|
+
end
|
75
|
+
|
76
|
+
def request_method
|
77
|
+
@env['REQUEST_METHOD'] = self.POST['_method'].upcase if request_method_in_params?
|
78
|
+
@env['REQUEST_METHOD']
|
79
|
+
end
|
80
|
+
|
81
|
+
def environment
|
82
|
+
@env['APP_ENVIRONMENT'] || (@application ? @application.environment : "development")
|
83
|
+
end
|
84
|
+
|
85
|
+
def params
|
86
|
+
params = begin
|
87
|
+
self.GET && self.GET.update(self.POST || {})
|
88
|
+
rescue EOFError => e
|
89
|
+
self.GET
|
90
|
+
end
|
91
|
+
|
92
|
+
params || {}
|
93
|
+
end
|
94
|
+
|
95
|
+
def protocol
|
96
|
+
ssl? ? 'https://' : 'http://'
|
97
|
+
end
|
98
|
+
|
99
|
+
def ssl?
|
100
|
+
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
101
|
+
end
|
102
|
+
|
103
|
+
def referer
|
104
|
+
@env['HTTP_REFERER']
|
105
|
+
end
|
106
|
+
|
107
|
+
def uri
|
108
|
+
@env['REQUEST_URI'] || @env['REQUEST_PATH']
|
109
|
+
end
|
110
|
+
|
111
|
+
def messages
|
112
|
+
@messages ||= if session?
|
113
|
+
session[:messages] = Messages.new(session[:messages])
|
114
|
+
else
|
115
|
+
params["messages"] = Messages.new(params["messages"])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def message(key)
|
120
|
+
messages[key]
|
121
|
+
end
|
122
|
+
|
123
|
+
# ==== Returns
|
124
|
+
# String::
|
125
|
+
# The URI without the query string. Strips trailing "/" and reduces
|
126
|
+
# duplicate "/" to a single "/".
|
127
|
+
def path
|
128
|
+
path = (uri.empty? ? '/' : uri.split('?').first).squeeze("/")
|
129
|
+
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
|
130
|
+
path
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
def request_method_in_params?
|
135
|
+
@env["REQUEST_METHOD"] == "POST" && self.POST && %w(PUT DELETE).include?((self.POST['_method'] || "").upcase)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
require "stringio"
|
2
|
+
require Pathname(__FILE__).dirname + "view"
|
3
|
+
|
4
|
+
module Harbor
|
5
|
+
class Response
|
6
|
+
|
7
|
+
attr_accessor :status, :headers, :errors
|
8
|
+
|
9
|
+
def initialize(request)
|
10
|
+
@request = request
|
11
|
+
@headers = {}
|
12
|
+
@headers["Content-Type"] = "text/html"
|
13
|
+
@status = 200
|
14
|
+
@errors = Harbor::Errors.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def headers
|
18
|
+
@headers
|
19
|
+
end
|
20
|
+
|
21
|
+
def flush
|
22
|
+
@io = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def size=(size)
|
26
|
+
@headers["Content-Length"] = size.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def size
|
30
|
+
(@headers["Content-Length"] || buffer.size).to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
def content_type=(content_type)
|
34
|
+
@headers["Content-Type"] = content_type
|
35
|
+
end
|
36
|
+
|
37
|
+
def content_type
|
38
|
+
@headers["Content-Type"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def puts(value)
|
42
|
+
string.puts(value)
|
43
|
+
self.size = string.length
|
44
|
+
end
|
45
|
+
|
46
|
+
def print(value)
|
47
|
+
string.print(value)
|
48
|
+
self.size = string.length
|
49
|
+
end
|
50
|
+
|
51
|
+
def buffer
|
52
|
+
if @io.is_a?(StringIO)
|
53
|
+
@io.string
|
54
|
+
else
|
55
|
+
@io || ""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def stream_file(path_or_io, content_type = nil)
|
60
|
+
io = BlockIO.new(path_or_io)
|
61
|
+
content_type ||= Harbor::Mime.mime_type(::File.extname(io.path.to_s))
|
62
|
+
|
63
|
+
if io.path && @request.env.has_key?("HTTP_X_SENDFILE_TYPE")
|
64
|
+
@headers["X-Sendfile"] = io.path
|
65
|
+
else
|
66
|
+
@io = io
|
67
|
+
end
|
68
|
+
|
69
|
+
self.size = io.size
|
70
|
+
self.content_type = content_type
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_file(name, path_or_io, content_type = nil)
|
75
|
+
stream_file(path_or_io, content_type)
|
76
|
+
|
77
|
+
@headers["Content-Disposition"] = "attachment; filename=\"#{escape_filename_for_http_header(name)}\""
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# Zip up the files (with no compression) and send it the client
|
82
|
+
# files should be an enumerable collection of Harbor::File instances
|
83
|
+
def send_files(name, files)
|
84
|
+
@io = ZippedIO.new(files)
|
85
|
+
self.size = @io.size
|
86
|
+
self.content_type = "application/zip"
|
87
|
+
@headers["Content-Disposition"] = "attachment; filename=\"#{escape_filename_for_http_header(name)}\""
|
88
|
+
end
|
89
|
+
|
90
|
+
def cache(key, last_modified, ttl = nil, max_age = nil)
|
91
|
+
raise ArgumentError.new("You must provide a block of code to cache.") unless block_given?
|
92
|
+
|
93
|
+
store = nil
|
94
|
+
if key && (ttl || max_age)
|
95
|
+
store = Harbor::View.cache
|
96
|
+
|
97
|
+
unless store
|
98
|
+
raise ArgumentError.new("Cache Store Not Defined. Please set Harbor::View.cache to your desired cache store.")
|
99
|
+
end
|
100
|
+
|
101
|
+
key = "page-#{key}"
|
102
|
+
end
|
103
|
+
|
104
|
+
last_modified = last_modified.httpdate
|
105
|
+
@headers["Last-Modified"] = last_modified
|
106
|
+
@headers["Cache-Control"] = "max-age=#{ttl}, must-revalidate" if ttl
|
107
|
+
|
108
|
+
modified_since = @request.env["HTTP_IF_MODIFIED_SINCE"]
|
109
|
+
|
110
|
+
if modified_since == last_modified && (!store || store.get(key))
|
111
|
+
not_modified!
|
112
|
+
elsif store && item = store.get(key)
|
113
|
+
return puts(item.content)
|
114
|
+
end
|
115
|
+
|
116
|
+
yield self
|
117
|
+
store.put(key, buffer, ttl, max_age) if store
|
118
|
+
end
|
119
|
+
|
120
|
+
def render(view, context = {})
|
121
|
+
if context[:layout].is_a?(Array)
|
122
|
+
warn "Passing multiple layouts to response.render has been deprecated. See Harbor::Layouts."
|
123
|
+
context[:layout] = context[:layout].first
|
124
|
+
end
|
125
|
+
|
126
|
+
case view
|
127
|
+
when View
|
128
|
+
view.context.merge(context)
|
129
|
+
else
|
130
|
+
view = View.new(view, context.merge({ :request => @request, :response => self }))
|
131
|
+
end
|
132
|
+
|
133
|
+
self.content_type = view.content_type
|
134
|
+
|
135
|
+
if context.has_key?(:layout) || @request.xhr?
|
136
|
+
puts view.to_s(context[:layout])
|
137
|
+
else
|
138
|
+
puts view.to_s(:search)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def redirect(url, params = {})
|
143
|
+
if @request && !@request.session? && !messages.empty? && !messages.expired?
|
144
|
+
messages.each { |key, value| params["messages[#{key}]"] = value }
|
145
|
+
end
|
146
|
+
|
147
|
+
self.status = 303
|
148
|
+
self.headers = {
|
149
|
+
"Location" => (params && params.any? ? "#{url}?#{Rack::Utils::build_query(params)}" : url),
|
150
|
+
"Content-Type" => "text/html"
|
151
|
+
}
|
152
|
+
self.flush
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
def redirect!(url, params = nil)
|
157
|
+
redirect(url, params) and throw(:abort_request)
|
158
|
+
end
|
159
|
+
|
160
|
+
def abort!(code)
|
161
|
+
if Harbor::View.exists?("exceptions/#{code}.html.erb")
|
162
|
+
render "exceptions/#{code}.html.erb"
|
163
|
+
end
|
164
|
+
|
165
|
+
self.status = code
|
166
|
+
throw(:abort_request)
|
167
|
+
end
|
168
|
+
|
169
|
+
def unauthorized
|
170
|
+
self.status = 401
|
171
|
+
end
|
172
|
+
|
173
|
+
def unauthorized!
|
174
|
+
unauthorized and throw(:abort_request)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Headers that MUST NOT be included with 304 Not Modified responses.
|
178
|
+
#
|
179
|
+
# http://tools.ietf.org/html/rfc2616#section-10.3.5
|
180
|
+
NOT_MODIFIED_OMIT_HEADERS = %w[
|
181
|
+
Allow
|
182
|
+
Content-Encoding
|
183
|
+
Content-Language
|
184
|
+
Content-Length
|
185
|
+
Content-MD5
|
186
|
+
Content-Type
|
187
|
+
Last-Modified
|
188
|
+
].to_set
|
189
|
+
|
190
|
+
def not_modified!
|
191
|
+
NOT_MODIFIED_OMIT_HEADERS.each { |name| headers.delete(name) }
|
192
|
+
self.status = 304
|
193
|
+
throw(:abort_request)
|
194
|
+
end
|
195
|
+
|
196
|
+
def inspect
|
197
|
+
"<#{self.class} headers=#{headers.inspect} content_type=#{content_type.inspect} status=#{status.inspect} body=#{buffer.inspect}>"
|
198
|
+
end
|
199
|
+
|
200
|
+
def messages
|
201
|
+
@messages ||= @request.messages
|
202
|
+
end
|
203
|
+
|
204
|
+
def message(key, message)
|
205
|
+
messages[key] = message
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_a
|
209
|
+
messages.clear if messages.expired?
|
210
|
+
|
211
|
+
if @request.session?
|
212
|
+
session = @request.session
|
213
|
+
set_cookie(session.key, session.save)
|
214
|
+
end
|
215
|
+
|
216
|
+
[self.status, self.headers, self.buffer]
|
217
|
+
end
|
218
|
+
|
219
|
+
def [](key)
|
220
|
+
headers[key]
|
221
|
+
end
|
222
|
+
|
223
|
+
def []=(key, value)
|
224
|
+
headers[key] = value
|
225
|
+
end
|
226
|
+
|
227
|
+
def set_cookie(key, value)
|
228
|
+
raise ArgumentError.new("+key+ must not be blank") if key.nil? || key.size == 0
|
229
|
+
|
230
|
+
case value
|
231
|
+
when Hash
|
232
|
+
domain = "; domain=" + value[:domain] if value[:domain]
|
233
|
+
path = "; path=" + value[:path] if value[:path]
|
234
|
+
http_only = value[:http_only] ? "; HTTPOnly=" : nil
|
235
|
+
# According to RFC 2109, we need dashes here.
|
236
|
+
# N.B.: cgi.rb uses spaces...
|
237
|
+
expires_on = if (defined?(DateTime) && value[:expires].is_a?(DateTime))
|
238
|
+
value[:expires].clone.new_offset(0)
|
239
|
+
elsif value[:expires].is_a?(Time)
|
240
|
+
value[:expires].clone.gmtime
|
241
|
+
elsif value[:expires].nil?
|
242
|
+
nil
|
243
|
+
else
|
244
|
+
raise ArgumentError.new("The value hash for set_cookie contains an invalid +expires+ attribute, Time or DateTime expected, but got: #{value[:expires].inspect}")
|
245
|
+
end
|
246
|
+
expires = "; expires=" + expires_on.strftime("%a, %d-%b-%Y %H:%M:%S GMT") if expires_on
|
247
|
+
|
248
|
+
value = value[:value]
|
249
|
+
end
|
250
|
+
value = [value] unless Array === value
|
251
|
+
cookie = Rack::Utils.escape(key) + "=" +
|
252
|
+
value.map { |v| Rack::Utils.escape v }.join("&") +
|
253
|
+
"#{domain}#{path}#{expires}#{http_only}"
|
254
|
+
|
255
|
+
case self["Set-Cookie"]
|
256
|
+
when Array
|
257
|
+
self["Set-Cookie"] << cookie
|
258
|
+
when String
|
259
|
+
self["Set-Cookie"] = [self["Set-Cookie"], cookie]
|
260
|
+
when nil
|
261
|
+
self["Set-Cookie"] = cookie
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def string
|
268
|
+
@io ||= StringIO.new("")
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
def escape_filename_for_http_header(filename)
|
273
|
+
# This would work great if IE6 could unescape the Content-Disposition filename field properly,
|
274
|
+
# but it can't, so we use the terribly weak version instead, until IE6 dies off...
|
275
|
+
#filename.gsub(/["\\\x0]/,'\\\\\0')
|
276
|
+
|
277
|
+
filename.gsub(/[^\w\.]/, '_')
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
end
|