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