switch to FastGettext for translations
[coquelicot.git] / lib / coquelicot / app.rb
1 require 'lockfile'
2 require 'sinatra/base'
3 require 'sinatra/config_file'
4 require 'haml'
5 require 'haml/magic_translations'
6 require 'sass'
7 require 'digest/sha1'
8 require 'fast_gettext'
9
10 module Coquelicot
11   class << self
12     def settings
13       (class << self; Application; end)
14     end
15     def depot
16       @depot = Depot.new(settings.depot_path) if @depot.nil? || settings.depot_path != @depot.path
17       @depot
18     end
19   end
20
21   class Application < Sinatra::Base
22     register Sinatra::ConfigFile
23     register Coquelicot::Auth::Extension
24
25     set :root, Proc.new { app_file && File.expand_path('../../..', app_file) }
26     set :depot_path, Proc.new { File.join(root, 'files') }
27     set :default_expire, 60
28     set :maximum_expire, 60 * 24 * 30 # 1 month
29     set :gone_period, 60 * 24 * 7 # 1 week
30     set :filename_length, 20
31     set :random_pass_length, 16
32     set :about_text, ''
33     set :additional_css, ''
34     set :authentication_method, :name => :simplepass,
35                                 :upload_password => 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
36
37     config_file File.expand_path('../../../conf/settings.yml', __FILE__)
38
39     FastGettext.add_text_domain 'coquelicot', :path => 'po', :type => 'po'
40     FastGettext.available_locales = [ 'en', 'fr', 'de' ]
41     Haml::MagicTranslations.enable(:fast_gettext)
42     before do
43       FastGettext.text_domain = 'coquelicot'
44       FastGettext.locale = params[:lang] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en'
45     end
46
47     not_found do
48       'Not found'
49     end
50
51     get '/style.css' do
52       content_type 'text/css', :charset => 'utf-8'
53       sass :style
54     end
55
56     get '/' do
57       haml :index
58     end
59
60     get '/random_pass' do
61       "#{Coquelicot.gen_random_pass}"
62     end
63
64     get '/ready/:link' do |link|
65       not_found if link.nil?
66
67       link, pass = link.split '-' if link.include? '-'
68       begin
69         file = Coquelicot.depot.get_file(link, nil)
70       rescue Errno::ENOENT => ex
71         not_found
72       end
73       @expire_at = file.expire_at
74       @base = request.url.gsub(/\/ready\/[^\/]*$/, '')
75       @name = "#{link}"
76       unless pass.nil?
77         @name << "-#{pass}"
78         @unprotected = true
79       end
80       @url = "#{@base}/#{@name}"
81       haml :ready
82     end
83
84     post '/authenticate' do
85       pass unless request.xhr?
86       begin
87         unless authenticate(params)
88           error 403, "Forbidden"
89         end
90         'OK'
91       rescue Coquelicot::Auth::Error => ex
92         error 503, ex.message
93       end
94     end
95
96     post '/upload' do
97       begin
98         unless authenticate(params)
99           error 403, "Forbidden"
100         end
101       rescue Coquelicot::Auth::Error => ex
102         error 503, ex.message
103       end
104
105       if params[:file] then
106         tmpfile = params[:file][:tempfile]
107         name = params[:file][:filename]
108       end
109       if tmpfile.nil? || name.nil? then
110         @error = "No file selected"
111         return haml(:index)
112       end
113       if tmpfile.lstat.size == 0 then
114         @error = "#{name} is empty"
115         return haml(:index)
116       end
117       if params[:expire].nil? or params[:expire].to_i == 0 then
118         params[:expire] = settings.default_expire
119       elsif params[:expire].to_i > settings.maximum_expire then
120         error 403
121       end
122       expire_at = Time.now + 60 * params[:expire].to_i
123       one_time_only = params[:one_time] and params[:one_time] == 'true'
124       if params[:file_key].nil? or params[:file_key].empty?then
125         pass = Coquelicot.gen_random_pass
126       else
127         pass = params[:file_key]
128       end
129       src = params[:file][:tempfile]
130       link = Coquelicot.depot.add_file(
131          src, pass,
132          { "Expire-at" => expire_at.to_i,
133            "One-time-only" => one_time_only,
134            "Filename" => params[:file][:filename],
135            "Length" => src.stat.size,
136            "Content-Type" => params[:file][:type],
137          })
138       redirect "ready/#{link}-#{pass}" if params[:file_key].nil? or params[:file_key].empty?
139       redirect "ready/#{link}"
140     end
141
142     def expired
143       throw :halt, [410, haml(:expired)]
144     end
145
146     def send_stored_file(file)
147       last_modified file.created_at.httpdate
148       attachment file.meta['Filename']
149       response['Content-Length'] = "#{file.meta['Length']}"
150       response['Content-Type'] = file.meta['Content-Type'] || 'application/octet-stream'
151       throw :halt, [200, file]
152     end
153
154     def send_link(link, pass)
155       file = Coquelicot.depot.get_file(link, pass)
156       return false if file.nil?
157       return expired if file.expired?
158
159       if file.one_time_only?
160         begin
161           # unlocking done in file.close
162           file.lockfile.lock
163         rescue Lockfile::TimeoutLockError
164           error 409, "Download currently in progress"
165         end
166       end
167       send_stored_file(file)
168     end
169
170     get '/:link-:pass' do |link, pass|
171       not_found if link.nil? || pass.nil?
172
173       link = Coquelicot.remap_base32_extra_characters(link)
174       pass = Coquelicot.remap_base32_extra_characters(pass)
175       not_found unless send_link(link, pass)
176     end
177
178     get '/:link' do |link|
179       not_found if link.nil?
180
181       link = Coquelicot.remap_base32_extra_characters(link)
182       not_found unless Coquelicot.depot.file_exists? link
183       @link = link
184       haml :enter_file_key
185     end
186
187     post '/:link' do |link|
188       pass = params[:file_key]
189       return 403 if pass.nil? or pass.empty?
190       begin
191         # send Forbidden even if file is not found
192         return 403 unless send_link(link, pass)
193       rescue Coquelicot::BadKey => ex
194         403
195       end
196     end
197
198     helpers do
199       def base_href
200         url = request.scheme + "://"
201         url << request.host
202         if request.scheme == "https" && request.port != 443 ||
203             request.scheme == "http" && request.port != 80
204           url << ":#{request.port}"
205         end
206         url << request.script_name
207         "#{url}/"
208       end
209
210       def clone_url
211         settings.respond_to?(:clone_url) ? settings.clone_url : "#{base_href}coquelicot.git"
212       end
213
214       def authenticate(params)
215         Coquelicot.settings.authenticator.authenticate(params)
216       end
217
218       def auth_method
219         Coquelicot.settings.authenticator.class.name.gsub(/Coquelicot::Auth::([A-z0-9]+)Authenticator$/, '\1').downcase
220       end
221     end
222   end
223 end
224
225 Coquelicot::Application.run! if __FILE__ == $0