Merge lp:~wgrant/loggerhead/bug-929275 into lp:loggerhead

Proposed by William Grant
Status: Merged
Merged at revision: 468
Proposed branch: lp:~wgrant/loggerhead/bug-929275
Merge into: lp:loggerhead
Diff against target: 308 lines (+97/-56)
4 files modified
NEWS (+3/-2)
loggerhead/controllers/download_ui.py (+8/-2)
loggerhead/controllers/view_ui.py (+19/-13)
loggerhead/tests/test_controllers.py (+67/-39)
To merge this branch: bzr merge lp:~wgrant/loggerhead/bug-929275
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+92194@code.launchpad.net

Description of the change

Fix more non-existent fileid or revid crashes.

To post a comment you must log in.
lp:~wgrant/loggerhead/bug-929275 updated
469. By William Grant

blah

Revision history for this message
Steve Kowalik (stevenk) wrote :

This looks like excellent work. And thanks for fixing the import I mentioned on IRC.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2012-02-03 01:29:46 +0000
3+++ NEWS 2012-02-09 03:39:18 +0000
4@@ -55,8 +55,9 @@
5 - Make tz calculations consistent and use UTC in the UI everywhere we show
6 a precise timestamp. (Robert Collins, #594591)
7
8- - Avoid crashing when viewing or annotating a non-existent file.
9- (William Grant, #728209)
10+ - Avoid crashing when viewing, annotating or downloading a
11+ non-existent file or revision.
12+ (William Grant, #728209, #929275)
13
14
15 1.18.1 [24Mar2011]
16
17=== modified file 'loggerhead/controllers/download_ui.py'
18--- loggerhead/controllers/download_ui.py 2011-11-25 01:41:07 +0000
19+++ loggerhead/controllers/download_ui.py 2012-02-09 03:39:18 +0000
20@@ -19,9 +19,12 @@
21
22 import logging
23 import mimetypes
24-import os
25 import urllib
26
27+from bzrlib.errors import (
28+ NoSuchId,
29+ NoSuchRevision,
30+ )
31 from paste import httpexceptions
32 from paste.request import path_info_pop
33
34@@ -55,7 +58,10 @@
35 self._branch.absolute_url('/changes'))
36 revid = h.fix_revid(args[0])
37 file_id = args[1]
38- path, filename, content = h.get_file(file_id, revid)
39+ try:
40+ path, filename, content = h.get_file(file_id, revid)
41+ except (NoSuchId, NoSuchRevision):
42+ raise httpexceptions.HTTPNotFound()
43 mime_type, encoding = mimetypes.guess_type(filename)
44 if mime_type is None:
45 mime_type = 'application/octet-stream'
46
47=== modified file 'loggerhead/controllers/view_ui.py'
48--- loggerhead/controllers/view_ui.py 2012-02-03 01:25:58 +0000
49+++ loggerhead/controllers/view_ui.py 2012-02-09 03:39:18 +0000
50@@ -18,9 +18,12 @@
51 #
52
53 import os
54-import time
55
56-import bzrlib.errors
57+from bzrlib.errors import (
58+ BinaryFile,
59+ NoSuchId,
60+ NoSuchRevision,
61+ )
62 import bzrlib.textfile
63 import bzrlib.osutils
64
65@@ -76,7 +79,7 @@
66 def file_contents(self, file_id, revid):
67 try:
68 file_lines = self.text_lines(file_id, revid)
69- except bzrlib.errors.BinaryFile:
70+ except BinaryFile:
71 # bail out; this isn't displayable text
72 return ['(This is a binary file.)']
73
74@@ -92,17 +95,17 @@
75 raise HTTPBadRequest('No file_id or filename '
76 'provided to view')
77
78- if file_id is None:
79- file_id = history.get_file_id(revid, path)
80-
81- # no navbar for revisions
82- navigation = util.Container()
83-
84- if path is None:
85- path = history.get_path(revid, file_id)
86+ try:
87+ if file_id is None:
88+ file_id = history.get_file_id(revid, path)
89+ if path is None:
90+ path = history.get_path(revid, file_id)
91+ except (NoSuchId, NoSuchRevision):
92+ raise HTTPNotFound()
93+
94 filename = os.path.basename(path)
95
96- change = history.get_changes([ revid ])[0]
97+ change = history.get_changes([revid])[0]
98 # If we're looking at the tip, use head: in the URL instead
99 if revid == branch.last_revision():
100 revno_url = 'head:'
101@@ -126,12 +129,15 @@
102
103 try:
104 file = inv[file_id]
105- except bzrlib.errors.NoSuchId:
106+ except NoSuchId:
107 raise HTTPNotFound()
108
109 if file.kind == "directory":
110 raise HTTPMovedPermanently(self._branch.context_url(['/files', revno_url, path]))
111
112+ # no navbar for revisions
113+ navigation = util.Container()
114+
115 return {
116 # In AnnotateUI, "annotated" is a dictionary mapping lines to changes.
117 # We exploit the fact that bool({}) is False when checking whether
118
119=== modified file 'loggerhead/tests/test_controllers.py'
120--- loggerhead/tests/test_controllers.py 2012-02-03 01:25:58 +0000
121+++ loggerhead/tests/test_controllers.py 2012-02-09 03:39:18 +0000
122@@ -14,27 +14,19 @@
123 # along with this program; if not, write to the Free Software
124 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
125
126-from cStringIO import StringIO
127-import logging
128 import tarfile
129 import tempfile
130
131 from paste.fixture import (
132 AppError,
133 )
134-from paste.httpexceptions import (
135- HTTPNotFound,
136- HTTPServerError,
137- )
138+from paste.httpexceptions import HTTPNotFound
139
140 from testtools.matchers import (
141 Matcher,
142 Mismatch,
143 )
144
145-from bzrlib import errors
146-import simplejson
147-
148 from loggerhead.apps.branch import BranchWSGIApp
149 from loggerhead.controllers.annotate_ui import AnnotateUI
150 from loggerhead.controllers.inventory_ui import InventoryUI
151@@ -159,7 +151,6 @@
152 self.assertOkJsonResponse(revision_ui, env)
153
154
155-
156 class TestAnnotateUI(BasicTests):
157
158 def make_annotate_ui_for_file_history(self, file_id, rev_ids_texts):
159@@ -192,12 +183,13 @@
160 history = [('rev1', 'old\nold\n', '.'), ('rev2', 'new\nold\n', '')]
161 ann_ui = self.make_annotate_ui_for_file_history('file_id', history)
162 ann_ui.args = ['rev2']
163- annotate_info = ann_ui.get_values('filename',
164- kwargs={'file_id': 'file_id'}, headers={})
165+ ann_ui.get_values(
166+ 'filename', kwargs={'file_id': 'file_id'}, headers={})
167
168 def test_annotate_file_zero_sized(self):
169- # Test against a zero-sized file without breaking. No annotation must be present.
170- history = [('rev1', '' , '.')]
171+ # Test against a zero-sized file without breaking. No annotation
172+ # must be present.
173+ history = [('rev1', '', '.')]
174 ann_ui = self.make_annotate_ui_for_file_history('file_id', history)
175 ann_ui.args = ['rev1']
176 annotate_info = ann_ui.get_values('filename',
177@@ -206,12 +198,19 @@
178 self.assertEqual(0, len(annotated))
179
180 def test_annotate_nonexistent_file(self):
181- history = [('rev1', '' , '.')]
182+ history = [('rev1', '', '.')]
183 ann_ui = self.make_annotate_ui_for_file_history('file_id', history)
184 ann_ui.args = ['rev1']
185 self.assertRaises(
186 HTTPNotFound, ann_ui.get_values, 'not-filename', {}, {})
187
188+ def test_annotate_nonexistent_rev(self):
189+ history = [('rev1', '', '.')]
190+ ann_ui = self.make_annotate_ui_for_file_history('file_id', history)
191+ ann_ui.args = ['norev']
192+ self.assertRaises(
193+ HTTPNotFound, ann_ui.get_values, 'not-filename', {}, {})
194+
195
196 class TestFileDiffUI(BasicTests):
197
198@@ -308,6 +307,55 @@
199 self.assertEquals("I am hooked", app.lookup_app(env))
200
201
202+class MatchesDownloadHeaders(Matcher):
203+
204+ def __init__(self, expect_filename, expect_mimetype):
205+ self.expect_filename = expect_filename
206+ self.expect_mimetype = expect_mimetype
207+
208+ def match(self, response):
209+ # Maybe the c-t should be more specific, but this is probably good for
210+ # making sure it gets saved without the client trying to decompress it
211+ # or anything.
212+ if (response.header('Content-Type') == self.expect_mimetype
213+ and response.header('Content-Disposition') ==
214+ "attachment; filename*=utf-8''" + self.expect_filename):
215+ pass
216+ else:
217+ return Mismatch("wrong response headers: %r"
218+ % response.headers)
219+
220+ def __str__(self):
221+ return 'MatchesDownloadHeaders(%r, %r)' % (
222+ self.expect_filename, self.expect_mimetype)
223+
224+
225+class TestDownloadUI(TestWithSimpleTree):
226+
227+ def test_download(self):
228+ app = self.setUpLoggerhead()
229+ response = app.get('/download/1/myfilename-id/myfilename')
230+ self.assertEqual(
231+ 'some\nmultiline\ndata\nwith<htmlspecialchars\n', response.body)
232+ self.assertThat(
233+ response,
234+ MatchesDownloadHeaders('myfilename', 'application/octet-stream'))
235+
236+ def test_download_bad_revision(self):
237+ app = self.setUpLoggerhead()
238+ e = self.assertRaises(
239+ AppError,
240+ app.get, '/download/norev/myfilename-id/myfilename')
241+ self.assertContainsRe(str(e), '404 Not Found')
242+
243+ def test_download_bad_fileid(self):
244+ app = self.setUpLoggerhead()
245+ e = self.assertRaises(
246+ AppError,
247+ app.get, '/download/1/myfilename-notid/myfilename')
248+ self.assertContainsRe(str(e), '404 Not Found')
249+
250+
251 class IsTarfile(Matcher):
252
253 def __init__(self, compression):
254@@ -323,29 +371,8 @@
255 f.close()
256
257
258-class MatchesTarballHeaders(Matcher):
259-
260- def __init__(self, expect_filename):
261- self.expect_filename = expect_filename
262-
263- def match(self, response):
264- # Maybe the c-t should be more specific, but this is probably good for
265- # making sure it gets saved without the client trying to decompress it
266- # or anything.
267- if (response.header('Content-Type') == 'application/octet-stream'
268- and response.header('Content-Disposition') ==
269- "attachment; filename*=utf-8''" + self.expect_filename):
270- pass
271- else:
272- return Mismatch("wrong response headers: %r"
273- % response.headers)
274-
275-
276 class TestDownloadTarballUI(TestWithSimpleTree):
277
278- def setUp(self):
279- super(TestDownloadTarballUI, self).setUp()
280-
281 def test_download_tarball(self):
282 # Tarball downloads are enabled by default.
283 app = self.setUpLoggerhead()
284@@ -355,7 +382,7 @@
285 IsTarfile('gz'))
286 self.assertThat(
287 response,
288- MatchesTarballHeaders('branch.tgz'))
289+ MatchesDownloadHeaders('branch.tgz', 'application/octet-stream'))
290
291 def test_download_tarball_of_version(self):
292 app = self.setUpLoggerhead()
293@@ -365,12 +392,13 @@
294 IsTarfile('gz'))
295 self.assertThat(
296 response,
297- MatchesTarballHeaders('branch-r1.tgz'))
298+ MatchesDownloadHeaders(
299+ 'branch-r1.tgz', 'application/octet-stream'))
300
301 def test_download_tarball_forbidden(self):
302 app = self.setUpLoggerhead(export_tarballs=False)
303 e = self.assertRaises(
304- AppError,
305+ AppError,
306 app.get,
307 '/tarball')
308 self.assertContainsRe(

Subscribers

People subscribed via source and target branches