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