use a lightbox when available to enter upload password
[coquelicot.git] / test_coquelicot.rb
1 $:.unshift File.join(File.dirname(__FILE__), '../rack-test/lib')
2 $:.unshift File.join(File.dirname(__FILE__), '../timecop/lib')
3
4 require 'sinatra'
5 require 'coquelicot_app'
6 require 'spec'
7 require 'rack/test'
8 require 'timecop'
9 require 'hpricot'
10 require 'tmpdir'
11
12 UPLOAD_PASSWORD = 'secret'
13
14 set :environment, :test
15 set :upload_password, Digest::SHA1.hexdigest(UPLOAD_PASSWORD)
16
17 describe 'Coquelicot' do
18   include Rack::Test::Methods
19
20   def app
21     Sinatra::Application
22   end
23
24   def upload(opts={})
25     opts = { :file => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
26              :upload_password => UPLOAD_PASSWORD
27            }.merge(opts)
28     post '/upload', opts
29     return nil unless last_response.redirect?
30     follow_redirect!
31     last_response.should be_ok
32     doc = Hpricot(last_response.body)
33     return (doc/'a').collect { |a| a.attributes['href'] }.
34              select { |h| h.start_with? "http://#{last_request.host}/" }[0]
35   end
36
37   before do
38     Coquelicot.setup :depot_path => Dir.mktmpdir('coquelicot') #"#{Time.now.to_f}"
39   end
40
41   after do
42     FileUtils.remove_entry_secure Coquelicot.depot.path
43   end
44
45   it "should offer an upload form" do
46     get '/'
47     last_response.should be_ok
48     doc = Hpricot(last_response.body)
49     (doc/"form#upload").should have(1).items
50   end
51
52   it "should allow retrieval of an uploaded file" do
53     url = upload
54     get url
55     last_response.should be_ok
56     last_response['Content-Type'].should eql('text/x-script.ruby')
57     last_response.body.should eql(File.new(__FILE__).read)
58   end
59
60   it "should correctly set Last-Modified header when downloading" do
61     url = upload
62     get url
63     last_modified = last_response['Last-Modified']
64     last_modified.should_not be_nil
65     get url
66     last_response['Last-Modified'].should eql(last_modified)
67   end
68
69   it "should prevent upload without a password" do
70     url = upload :upload_password => ''
71     url.should be_nil
72     last_response.status.should eql(403)
73   end
74
75   it "should prevent upload with a wrong password" do
76     url = upload :upload_password => "bad"
77     url.should be_nil
78     last_response.status.should eql(403)
79   end
80
81   it "should allow AJAX upload password verification" do
82     request "/authenticate", :method => "POST", :xhr => true,
83                              :params => { :upload_password => UPLOAD_PASSWORD }
84     last_response.should be_ok
85     request "/authenticate", :method => "POST", :xhr => true,
86                              :params => { :upload_password => '' }
87     last_response.status.should eql(403)
88     request "/authenticate", :method => "POST", :xhr => true,
89                              :params => { :upload_password => 'wrong' }
90     last_response.status.should eql(403)
91   end
92
93   it "should not store an uploaded file in cleartext" do
94     upload
95     files = Dir.glob("#{Coquelicot.depot.path}/*")
96     files.should have(1).items
97     File.new(files[0]).read().should_not include('should not store an uploaded file')
98   end
99
100   it "should generate a random URL to retrieve a file" do
101     url = upload
102     url.should_not include(File.basename(__FILE__))
103   end
104
105   it "should store files with a different name than then one in URL" do
106     url = upload
107     url_name = url.split('/')[-1]
108     files = Dir.glob("#{Coquelicot.depot.path}/*")
109     files.should have(1).items
110     url_name.should_not eql(File.basename(files[0]))
111   end
112
113   it "should encode the encryption key in URL when no password has been specified" do
114     url = upload
115     url_name = url.split('/')[-1]
116     url_name.split('-').should have(2).items
117   end
118
119   it "should not encode the encryption key in URL when a password has been specified" do
120     url = upload :file_key => 'somethingSecret'
121     url_name = url.split('/')[-1]
122     url_name.split('-').should have(1).items
123   end
124
125   it "should only allow one time download to be retrieved once" do
126     url = upload :one_time => true
127     get url
128     last_response.should be_ok
129     last_response['Content-Type'].should eql('text/x-script.ruby')
130     last_response.body.should eql(File.new(__FILE__).read)
131     get url
132     last_response.status.should eql(410)
133   end
134
135   it "should have files zero'ed after 'one time' download" do
136     url = upload :one_time => true
137     get url
138     files = Dir.glob("#{Coquelicot.depot.path}/*")
139     files.should have(1).items
140     File.lstat(files[0]).size.should eql(0)
141   end
142
143   it "should allow retrieval of a password protected file" do
144     url = upload :file_key => 'somethingSecret'
145     get url
146     last_response.should be_ok
147     doc = Hpricot(last_response.body)
148     (doc/'input#file_key').should have(1).items
149     url = (doc/'form')[0].attributes['action']
150     post url, :file_key => 'somethingSecret'
151     last_response.should be_ok
152     last_response['Content-Type'].should eql('text/x-script.ruby')
153     last_response.body.should eql(File.new(__FILE__).read)
154   end
155
156   it "should not allow retrieval of a password protected file without the password" do
157     url = upload :file_key => 'somethingSecret'
158     get url
159     last_response.should be_ok
160     last_response['Content-Type'].should_not eql('text/x-script.ruby')
161     post url
162     last_response.status.should eql(403)
163   end
164
165   it "should not allow retrieval of a password protected file with a wrong password" do
166     url = upload :file_key => 'somethingSecret'
167     post url, :file_key => 'BAD'
168     last_response.status.should eql(403)
169   end
170
171   it "should not allow retrieval after the time limit has expired" do
172     url = upload :expire => 60 # 1 hour
173     # let's be tomorrow
174     Timecop.travel(Date.today + 1) do
175       get url
176       last_response.status.should eql(410)
177     end
178   end
179
180   it "should not allow an expiration time longer than the maximum" do
181     upload :expire => 60 * 24 * 31 * 12 # 1 year
182     last_response.status.should eql(403)
183   end
184
185   it "should cleanup expired files" do
186     url = upload :expire => 60, :file_key => 'test' # 1 hour
187     url_name = url.split('/')[-1]
188     Dir.glob("#{Coquelicot.depot.path}/*").should have(1).items
189     # let's be tomorrow
190     Timecop.travel(Date.today + 1) do
191       Coquelicot.depot.gc!
192       files = Dir.glob("#{Coquelicot.depot.path}/*")
193       files.should have(1).items
194       File.lstat(files[0]).size.should eql(0)
195       Coquelicot.depot.get_file(url_name).expired?.should be_true
196     end
197     # let's be after 'gone' period
198     Timecop.travel(Time.now + (Coquelicot.settings.gone_period * 60)) do
199       Coquelicot.depot.gc!
200       Dir.glob("#{Coquelicot.depot.path}/*").should have(0).items
201       Coquelicot.depot.get_file(url_name).should be_nil
202     end
203   end
204
205   it "should map extra base32 characters to filenames" do
206     url = upload :expire => 60 # 1 hour
207     splitted = url.split('/')
208     name = splitted[-1].upcase.gsub(/O/, '0').gsub(/L/, '1')
209     get "#{splitted[0..-2].join '/'}/#{name}"
210     last_response.should be_ok
211     last_response['Content-Type'].should eql('text/x-script.ruby')
212   end
213 end