9 enable :inline_templates
11 set :upload_password, '0e5f7d398e6f9cd1f6bac5cc823e363aec636495'
12 set :filename_length, 20
13 set :lockfile, Proc.new { Lockfile.new "#{depot_path}/.lock",
22 def self.open(path, pass)
23 StoredFile.new(path, pass)
28 yield @initial_content
29 @initial_content = nil
30 until (buf = @file.read(BUFFER_LEN)).nil?
31 yield @cipher.update(buf)
38 def self.create(src, pass, meta)
40 clear_meta = { "Coquelicot" => COQUELICOT_VERSION,
41 "Salt" => Base64.encode64(salt).strip }
42 yield YAML.dump(clear_meta) + YAML_START
44 cipher = get_cipher(pass, salt, :encrypt)
45 yield cipher.update(YAML.dump(meta) + YAML_START)
47 while not (buf = src.read(BUFFER_LEN)).nil?
48 yield cipher.update(buf)
56 CIPHER = 'AES-256-CBC'
59 COQUELICOT_VERSION = "1.0"
61 def self.get_cipher(pass, salt, method)
62 hmac = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, 2000, 48)
63 cipher = OpenSSL::Cipher.new CIPHER
64 cipher.method(method).call
65 cipher.key = hmac[0..31]
66 cipher.iv = hmac[32..-1]
71 OpenSSL::Random::random_bytes(SALT_LEN)
74 def initialize(path, pass)
75 @file = File.open(path)
76 if YAML_START != (buf = @file.read(YAML_START.length)) then
77 raise "unknown file, read #{buf.inspect}"
80 init_decrypt_cipher pass
86 until YAML_START == (line = @file.readline) do
89 @meta = YAML.load(meta)
90 if @meta["Coquelicot"].nil? or @meta["Coquelicot"] != COQUELICOT_VERSION then
95 def init_decrypt_cipher(pass)
96 salt = Base64.decode64(@meta["Salt"])
97 @cipher = StoredFile::get_cipher(pass, salt, :decrypt)
102 buf = @file.read(BUFFER_LEN)
103 content = @cipher.update(buf)
104 raise "bad key" unless content.start_with? YAML_START
106 block = content.split(YAML_START, 3)
108 if block.length == 3 then
109 @initial_content = block[2]
110 @meta.merge! YAML.load(yaml)
114 until (buf = @file.read(BUFFER_LEN)).nil? do
115 block = @cipher.update(buf).split(YAML_START, 3)
117 break if block.length == 2
119 @initial_content = block[1]
120 @meta.merge! YAML.load(yaml)
124 @cipher.reset unless @cipher.nil?
129 # Like RFC 4648 (Base32)
130 FILENAME_CHARS = %w(a b c d e f g h i j k l m n o p q r s t u v w x y z 2 3 4 5 6 7)
131 def gen_random_file_name
133 options.lockfile.lock do
136 OpenSSL::Random::random_bytes(options.filename_length).each_byte do |i|
137 name << FILENAME_CHARS[i % FILENAME_CHARS.length]
139 end while name.empty? or File.exists?(uploaded_file(name))
144 def password_match?(password)
145 return TRUE if settings.upload_password.nil?
146 (not password.nil?) && Digest::SHA1.hexdigest(password) == settings.upload_password
149 def uploaded_file(file)
150 "#{options.depot_path}/#{file}"
154 content_type 'text/css', :charset => 'utf-8'
162 get '/ready/:name' do |name|
163 path = uploaded_file(name)
164 unless File.exists? path then
167 base = request.url.gsub(/\/ready\/[^\/]*$/, '')
168 @url = "#{base}/#{name}"
172 get '/:name' do |name|
173 path = uploaded_file(name)
174 unless File.exists? path then
177 file = StoredFile.open(path, 'XXXsecret')
178 last_modified File.mtime(path).httpdate
179 attachment file.meta['Filename']
180 response['Content-Length'] = "#{file.meta['Length']}"
181 response['Content-Type'] = file.meta['Content-Type'] || 'application/octet-stream'
182 throw :halt, [200, file]
186 unless password_match? params[:upload_password] then
189 if params[:file] then
190 tmpfile = params[:file][:tempfile]
191 name = params[:file][:filename]
193 if tmpfile.nil? || name.nil? then
194 @error = "No file selected"
197 src = params[:file][:tempfile]
198 dst = gen_random_file_name
199 File.open(uploaded_file(dst), 'w') do |dest|
203 { "Filename" => params[:file][:filename],
204 "Length" => src.stat.size,
205 "Content-Type" => params[:file][:type]
206 }) { |data| dest.write data }
208 redirect "ready/#{dst}"
213 url = request.scheme + "://"
215 if request.scheme == "https" && request.port != 443 ||
216 request.scheme == "http" && request.port != 80
217 url << ":#{request.port}"
219 url << request.script_name
230 %base{ :href => base_href }
231 %link{ :rel => 'stylesheet', :href => "style.css", :type => 'text/css',
232 :media => "screen, projection" }
233 %script{ :type => 'text/javascript', :src => 'javascripts/jquery.min.js' }
234 %script{ :type => 'text/javascript', :src => 'javascripts/jquery.lightBoxFu.js' }
235 %script{ :type => 'text/javascript', :src => 'javascripts/jquery.uploadProgress.js' }
236 %script{ :type => 'text/javascript', :src => 'javascripts/coquelicot.js' }
245 %form#upload{ :enctype => 'multipart/form-data',
246 :action => 'upload', :method => 'post' }
248 %input{ :type => 'file', :name => 'file' }
250 %input{ :type => 'submit', :value => 'Send file' }
255 %a{ :href => @url }= @url
261 background-color: $green
266 text-decoration: underline
270 background-color: red
272 border: black solid 1px
280 background: url('images/ajax-loader.gif') no-repeat