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