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