explicitely require 'sass'
[coquelicot.git] / coquelicot_app.rb
1 $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
3 require 'sinatra'
4 require 'haml'
5 require 'sass'
6 require 'digest/sha1'
7 require 'gettext'
8 require 'coquelicot'
9 require 'haml_gettext'
10
11 set :upload_password, '0e5f7d398e6f9cd1f6bac5cc823e363aec636495'
12
13 def password_match?(password)
14   return TRUE if settings.upload_password.nil?
15   (not password.nil?) && Digest::SHA1.hexdigest(password) == settings.upload_password
16 end
17
18 GetText::bindtextdomain('coquelicot')
19 before do
20   GetText::set_current_locale(params[:lang] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
21 end
22
23 get '/style.css' do
24   content_type 'text/css', :charset => 'utf-8'
25   sass :style
26 end
27
28 get '/' do
29   haml :index
30 end
31
32 get '/random_pass' do
33   "#{Coquelicot.gen_random_pass}"
34 end
35
36 get '/ready/:link' do |link|
37   link, pass = link.split '-' if link.include? '-'
38   begin
39     file = Coquelicot.depot.get_file(link, nil)
40   rescue Errno::ENOENT => ex
41     not_found
42   end
43   @expire_at = file.expire_at
44   @base = request.url.gsub(/\/ready\/[^\/]*$/, '')
45   @name = "#{link}"
46   unless pass.nil?
47     @name << "-#{pass}"
48     @unprotected = true
49   end
50   @url = "#{@base}/#{@name}"
51   haml :ready
52 end
53
54 post '/authenticate' do
55   pass unless request.xhr?
56   unless password_match? params[:upload_password] then
57     error 403, "Forbidden"
58   end
59   'OK'
60 end
61
62 post '/upload' do
63   unless password_match? params[:upload_password] then
64     error 403
65   end
66   if params[:file] then
67     tmpfile = params[:file][:tempfile]
68     name = params[:file][:filename]
69   end
70   if tmpfile.nil? || name.nil? then
71     @error = "No file selected"
72     return haml(:index)
73   end
74   if params[:expire].nil? or params[:expire].to_i == 0 then
75     params[:expire] = Coquelicot.settings.default_expire
76   elsif params[:expire].to_i > Coquelicot.settings.maximum_expire then
77     error 403
78   end
79   expire_at = Time.now + 60 * params[:expire].to_i
80   one_time_only = params[:one_time] and params[:one_time] == 'true'
81   if params[:file_key].nil? or params[:file_key].empty?then
82     pass = Coquelicot.gen_random_pass
83   else
84     pass = params[:file_key]
85   end
86   src = params[:file][:tempfile]
87   link = Coquelicot.depot.add_file(
88      src, pass,
89      { "Expire-at" => expire_at.to_i,
90        "One-time-only" => one_time_only,
91        "Filename" => params[:file][:filename],
92        "Length" => src.stat.size,
93        "Content-Type" => params[:file][:type],
94      })
95   redirect "ready/#{link}-#{pass}" if params[:file_key].nil? or params[:file_key].empty?
96   redirect "ready/#{link}"
97 end
98
99 def expired
100   throw :halt, [410, haml(:expired)]
101 end
102
103 def send_stored_file(file)
104   last_modified file.created_at.httpdate
105   attachment file.meta['Filename']
106   response['Content-Length'] = "#{file.meta['Length']}"
107   response['Content-Type'] = file.meta['Content-Type'] || 'application/octet-stream'
108   throw :halt, [200, file]
109 end
110
111 module Coquelicot
112   class StoredFile
113     def lockfile
114       @lockfile ||= Lockfile.new "#{File.expand_path(@path)}.lock", :timeout => 4
115     end
116
117     def each
118       # output content
119       yield @initial_content
120       @initial_content = nil
121       until (buf = @file.read(BUFFER_LEN)).nil?
122         yield @cipher.update(buf)
123       end
124       yield @cipher.final
125       @fully_sent = true
126     end
127
128     def close
129       if @cipher
130         @cipher.reset
131         @cipher = nil
132       end
133       @file.close
134       if one_time_only?
135         empty! if @fully_sent
136         lockfile.unlock
137       end
138     end
139   end
140 end
141
142 def send_link(link, pass)
143   file = Coquelicot.depot.get_file(link, pass)
144   return false if file.nil?
145   return expired if file.expired?
146
147   if file.one_time_only?
148     begin
149       # unlocking done in file.close
150       file.lockfile.lock
151     rescue Lockfile::TimeoutLockError
152       error 409, "Download currently in progress"
153     end
154   end
155   send_stored_file(file)
156 end
157
158 get '/:link-:pass' do |link, pass|
159   link = Coquelicot.remap_base32_extra_characters(link)
160   pass = Coquelicot.remap_base32_extra_characters(pass)
161   not_found unless send_link(link, pass)
162 end
163
164 get '/:link' do |link|
165   link = Coquelicot.remap_base32_extra_characters(link)
166   not_found unless Coquelicot.depot.file_exists? link
167   @link = link
168   haml :enter_file_key
169 end
170
171 post '/:link' do |link|
172   pass = params[:file_key]
173   return 403 if pass.nil? or pass.empty?
174   begin
175     # send Forbidden even if file is not found
176     return 403 unless send_link(link, pass)
177   rescue Coquelicot::BadKey => ex
178     403
179   end
180 end
181
182 helpers do
183   def base_href
184     url = request.scheme + "://"
185     url << request.host
186     if request.scheme == "https" && request.port != 443 ||
187         request.scheme == "http" && request.port != 80
188       url << ":#{request.port}"
189     end
190     url << request.script_name
191     "#{url}/"
192   end
193 end