Merge lp:~mkanat/loggerhead/raw-controller into lp:loggerhead
- raw-controller
- Merge into trunk-rich
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Max Kanat-Alexander | ||||
Approved revision: | 439 | ||||
Merged at revision: | 439 | ||||
Proposed branch: | lp:~mkanat/loggerhead/raw-controller | ||||
Merge into: | lp:loggerhead | ||||
Diff against target: |
396 lines (+140/-60) 8 files modified
loggerhead/apps/branch.py (+2/-0) loggerhead/controllers/__init__.py (+37/-6) loggerhead/controllers/download_ui.py (+5/-14) loggerhead/controllers/raw_ui.py (+60/-0) loggerhead/controllers/view_ui.py (+16/-30) loggerhead/static/css/files.css (+7/-0) loggerhead/templates/inventory.pt (+9/-10) loggerhead/templates/view.pt (+4/-0) |
||||
To merge this branch: | bzr merge lp:~mkanat/loggerhead/raw-controller | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Approve | ||
Review via email: mp+42675@code.launchpad.net |
Commit message
Description of the change
This addresses bug 605775 by adding a new "raw" controller that displays raw data. It's surprisingly fast--about 0.05s to display almost any file. It does its best to determine the correct MIME type to serve a file with, although it will often fall back to text/plain or application/
The only thing that this patch is missing is XSS protection (serving the raw content from a different domain), which is something I want to do in a separate bug and MP, because this MP is already complex enough as it is. There's also some preliminary refactoring involved in this patch that was necessary to get a "raw" controller that worked well without copying-and-pasting code. (After this, there's also some refactoring that can be done to make the "download" controller a subclass of RawUI, but that will involve changing the download URLs and how they work, so that's also something I wanted to do separately.)
John A Meinel (jameinel) wrote : | # |
Robert Collins (lifeless) wrote : | # |
application/
-Rob
Robert Collins (lifeless) wrote : | # |
See http://
*one* documented behaviour about content-sniffing.
Basically you need one domain per security context - and for us that
means pretty much one domain per file content. See the
publicrestricte
-Rob
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 12/3/2010 4:07 PM, Robert Collins wrote:
> application/
>
> -Rob
We already have a way to get that, though. The 'download' urls do that.
Or are they somehow already protected against XSS, and new stuff
wouldn't be?
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkz
w5IAoJoQ/
=SKtw
-----END PGP SIGNATURE-----
Robert Collins (lifeless) wrote : | # |
They probably set Content-
browsers honour, at least to the extent of not rendering the thing in
the source context.
-Rob
Max Kanat-Alexander (mkanat) wrote : | # |
> If we are worried about XSS, why not start by having raw just return
> everything as application/
> protect against it?
Because we already have /download/ for that. This is about displaying content in the browser.
Martin Pool (mbp) wrote : | # |
The diff itself looks reasonable to me. The main issue seems to be XSS.
I don't think it is a good idea to merge something into loggerhead trunk that makes it 'dangerous by default'. Some people run from trunk; someone else might make a release without realizing this was not yet finished. If there was an option to enable it, with a clear warning, that would be fine.
(Tangentially, I think we should have a discussion about how we're going to manage qa of the recent loggerhead changes before they're generally available, but that can be separate from this.)
It's great that this is so fast.
John's bug is really two bugs (as he acknowledges, but perhaps we should have split it before continuing.)
> 1) Something like the 'download' link that doesn't require including the file id.
It looks like that is fixed by this, which is great.
Really this is probably mostly independent of the download issue, and it's equally applicable to wanting to make up a url that gives you annotations or any other view.
If Loggerhead still generates urls containing file ids by default it may not be obvious to users they can be left out?
> 2) A way to view raw content in-the-browser, rather than always being an octet stream. So 'raw' vs 'download'.
This one seems to have inherent XSS-type problems, and we should perhaps narrow it down in the context of that bug. What do you want to see in the browser? What are the use cases? I would say almost certainly not arbitrary HTML.
To me the most obvious use case is to get plain text files from which you can easily copy/paste. Perhaps that can be accommodated well by escaping them into HTML?
The second most important case is to get a download URL that you could paste into a shell eg wget command. I think that c-d: attachment will still let this work?
The third might be to click through to png/jpg images within loggerhead. That seems problematic wrt abuse, and opens up to content sniffing.
I don't think we should merge this until we understand more clearly what the bug is trying to fix. If we do decide that a plain raw view is useful to some users, it should be off by default.
Max Kanat-Alexander (mkanat) wrote : | # |
I've addressed all of the questions and comments on bug 605775 comment 5, I believe.
The only point that I didn't make there is that most loggerhead installations would not generally be able to do anything harmful to the user by injecting scripts into the page, I'm somewhat sure. I do agree that we shouldn't ship as dangerous by default, though.
Also, to be clear, loggerhead does not generate URLs that use file ids by default, with the only current exception being /download/. However, I was planning on fixing /download/ after this, which will be pretty simple since it can become a subclass of RawUI. But I didn't want to do two changes in one, particularly since one is a behavioral change for URLs.
I don't see why a plain raw view should be off by default--it seems like something that every VCS browser does. I do think that there should be a way for people to protect themselves against XSS, though. But the fix is complex, somewhat, so I wanted to separate it out so that we could do a good security review on just that fix, and not be distracted by the surrounding refactoring and new /raw/ code.
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
> I don't see why a plain raw view should be off by default--it seems like something that every VCS browser does. I do think that there should be a way for people to protect themselves against XSS, though. But the fix is complex, somewhat, so I wanted to separate it out so that we could do a good security review on just that fix, and not be distracted by the surrounding refactoring and new /raw/ code.
I would say as long as something is "text/plain" then you could serve it
by default. Otherwise make it an attachment?
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkz
k+UAn3TLhE5t3mQ
=YnD3
-----END PGP SIGNATURE-----
Martin Pool (mbp) wrote : | # |
Let's get this unblocked.
> > I don't see why a plain raw view should be off by default--it seems like
> something that every VCS browser does. I do think that there should be a way
> for people to protect themselves against XSS, though. But the fix is complex,
> somewhat, so I wanted to separate it out so that we could do a good security
> review on just that fix, and not be distracted by the surrounding refactoring
> and new /raw/ code.
I think it's clear some people will want this on and some will want it off. Therefore we need an option. If there is an option that chooses between "invisibly dangerous" and "obviously broken" I think the second is a much better default.
> I would say as long as something is "text/plain" then you could serve it
> by default. Otherwise make it an attachment?
This is the whole problem with xss and content-sniffing: the server can mark it as text/plain, but some browsers will still interpret it as something else if it looks like it's not actually text. They may interpret it as something that lets malicious content steal people's credentials. See Robert's link.
Can you please add an option?
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 12/14/2010 9:31 PM, Martin Pool wrote:
> Let's get this unblocked.
>
>>> I don't see why a plain raw view should be off by default--it seems like
>> something that every VCS browser does. I do think that there should be a way
>> for people to protect themselves against XSS, though. But the fix is complex,
>> somewhat, so I wanted to separate it out so that we could do a good security
>> review on just that fix, and not be distracted by the surrounding refactoring
>> and new /raw/ code.
>
> I think it's clear some people will want this on and some will want it off. Therefore we need an option. If there is an option that chooses between "invisibly dangerous" and "obviously broken" I think the second is a much better default.
>
>> I would say as long as something is "text/plain" then you could serve it
>> by default. Otherwise make it an attachment?
>
> This is the whole problem with xss and content-sniffing: the server can mark it as text/plain, but some browsers will still interpret it as something else if it looks like it's not actually text. They may interpret it as something that lets malicious content steal people's credentials. See Robert's link.
>
> Can you please add an option?
Matthew Nuzum just put up a test, and I confirmed,
http://
Is displayed as a text file in my copies of Firefox and Chrome, but
shows up as an HTML page (showing the js content) from Internet Explorer.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAk0
xrwAoKmJJMJRXYS
=7rJs
-----END PGP SIGNATURE-----
Martin Pool (mbp) wrote : | # |
Ok, so in another branch max has added an option that seems suitable for use on Launchpad.
Max makes a case that this is in fact safe to be on by default, because most Loggerhead instances either don't serve untrusted content, or don't have anything that could be exploited by XSS. He has handled something similar in bugzilla in <https:/
I think this ought to be mentioned in README but aside from that I'm ok with merging this.
Max Kanat-Alexander (mkanat) wrote : | # |
Accidentally pushed instead of merged, but the end result should be the same.
Preview Diff
1 | === modified file 'loggerhead/apps/branch.py' |
2 | --- loggerhead/apps/branch.py 2010-11-30 10:18:40 +0000 |
3 | +++ loggerhead/apps/branch.py 2010-12-03 19:41:25 +0000 |
4 | @@ -36,6 +36,7 @@ |
5 | from loggerhead.controllers.download_ui import DownloadUI |
6 | from loggerhead.controllers.filediff_ui import FileDiffUI |
7 | from loggerhead.controllers.inventory_ui import InventoryUI |
8 | +from loggerhead.controllers.raw_ui import RawUI |
9 | from loggerhead.controllers.revision_ui import RevisionUI |
10 | from loggerhead.controllers.revlog_ui import RevLogUI |
11 | from loggerhead.controllers.search_ui import SearchUI |
12 | @@ -113,6 +114,7 @@ |
13 | 'diff': DiffUI, |
14 | 'download': DownloadUI, |
15 | 'files': InventoryUI, |
16 | + 'raw': RawUI, |
17 | 'revision': RevisionUI, |
18 | 'search': SearchUI, |
19 | 'view': ViewUI, |
20 | |
21 | === modified file 'loggerhead/controllers/__init__.py' |
22 | --- loggerhead/controllers/__init__.py 2010-04-26 19:37:26 +0000 |
23 | +++ loggerhead/controllers/__init__.py 2010-12-03 19:41:25 +0000 |
24 | @@ -17,6 +17,7 @@ |
25 | # along with this program; if not, write to the Free Software |
26 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
27 | |
28 | +import os |
29 | import time |
30 | |
31 | from paste.request import path_info_pop, parse_querystring |
32 | @@ -66,7 +67,7 @@ |
33 | return self.__history |
34 | |
35 | def __call__(self, environ, start_response): |
36 | - z = time.time() |
37 | + self._call_start = time.time() |
38 | kwargs = dict(parse_querystring(environ)) |
39 | util.set_context(kwargs) |
40 | args = [] |
41 | @@ -75,12 +76,15 @@ |
42 | if arg is None: |
43 | break |
44 | args.append(arg) |
45 | - |
46 | + self.args = args |
47 | + |
48 | path = None |
49 | if len(args) > 1: |
50 | path = unicode('/'.join(args[1:]), 'utf-8') |
51 | - self.args = args |
52 | - |
53 | + |
54 | + return self.get_output(path, kwargs, start_response) |
55 | + |
56 | + def get_output(self, path, kwargs, start_response): |
57 | vals = { |
58 | 'static_url': self._branch.static_url, |
59 | 'branch': self._branch, |
60 | @@ -93,7 +97,7 @@ |
61 | vals.update(self.get_values(path, kwargs, headers)) |
62 | |
63 | self.log.info('Getting information for %s: %.3f secs' % ( |
64 | - self.__class__.__name__, time.time() - z)) |
65 | + self.__class__.__name__, time.time() - self._call_start)) |
66 | if 'Content-Type' not in headers: |
67 | headers['Content-Type'] = 'text/html' |
68 | writer = start_response("200 OK", headers.items()) |
69 | @@ -105,8 +109,31 @@ |
70 | self.log.info( |
71 | 'Rendering %s: %.3f secs, %s bytes' % ( |
72 | self.__class__.__name__, time.time() - z, w.bytes)) |
73 | - return [] |
74 | + return [] |
75 | + |
76 | + def file_info(self, path, kwargs): |
77 | + history = self._history |
78 | + revid = self.get_revid() |
79 | + revid = history.fix_revid(revid) |
80 | + file_id = kwargs.get('file_id', None) |
81 | + if (file_id is None) and (path is None): |
82 | + raise HTTPBadRequest('No file_id or filename provided') |
83 | |
84 | + if file_id is None: |
85 | + file_id = history.get_file_id(revid, path) |
86 | + |
87 | + if path is None: |
88 | + path = history.get_path(revid, file_id) |
89 | + |
90 | + filename = os.path.basename(path) |
91 | + |
92 | + return { |
93 | + 'file_id': file_id, |
94 | + 'revid': revid, |
95 | + 'filename': filename, |
96 | + 'path': path, |
97 | + } |
98 | + |
99 | def get_revid(self): |
100 | h = self._history |
101 | if h is None: |
102 | @@ -115,3 +142,7 @@ |
103 | return h.fix_revid(self.args[0]) |
104 | else: |
105 | return h.last_revid |
106 | + |
107 | + def tree_for(self, file_id, revid): |
108 | + file_revid = self._history.get_inventory(revid)[file_id].revision |
109 | + return self._history._branch.repository.revision_tree(file_revid) |
110 | \ No newline at end of file |
111 | |
112 | === modified file 'loggerhead/controllers/download_ui.py' |
113 | --- loggerhead/controllers/download_ui.py 2010-05-05 19:03:40 +0000 |
114 | +++ loggerhead/controllers/download_ui.py 2010-12-03 19:41:25 +0000 |
115 | @@ -26,23 +26,14 @@ |
116 | |
117 | from loggerhead.controllers import TemplatedBranchView |
118 | |
119 | -log = logging.getLogger("loggerhead.controllers") |
120 | - |
121 | - |
122 | -class DownloadUI (TemplatedBranchView): |
123 | - |
124 | - def __call__(self, environ, start_response): |
125 | +class DownloadUI(TemplatedBranchView): |
126 | + |
127 | + def get_output(self, path, kwargs, start_response): |
128 | # /download/<rev_id>/<file_id>/[filename] |
129 | |
130 | h = self._history |
131 | - |
132 | - args = [] |
133 | - while True: |
134 | - arg = path_info_pop(environ) |
135 | - if arg is None: |
136 | - break |
137 | - args.append(arg) |
138 | - |
139 | + args = self.args |
140 | + |
141 | if len(args) < 2: |
142 | raise httpexceptions.HTTPMovedPermanently( |
143 | self._branch.absolute_url('/changes')) |
144 | |
145 | === added file 'loggerhead/controllers/raw_ui.py' |
146 | --- loggerhead/controllers/raw_ui.py 1970-01-01 00:00:00 +0000 |
147 | +++ loggerhead/controllers/raw_ui.py 2010-12-03 19:41:25 +0000 |
148 | @@ -0,0 +1,60 @@ |
149 | +# |
150 | +# Copyright (C) 2010 Canonical Ltd. |
151 | +# |
152 | +# This program is free software; you can redistribute it and/or modify |
153 | +# it under the terms of the GNU General Public License as published by |
154 | +# the Free Software Foundation; either version 2 of the License, or |
155 | +# (at your option) any later version. |
156 | +# |
157 | +# This program is distributed in the hope that it will be useful, |
158 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
159 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
160 | +# GNU General Public License for more details. |
161 | +# |
162 | +# You should have received a copy of the GNU General Public License |
163 | +# along with this program; if not, write to the Free Software |
164 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
165 | +# |
166 | + |
167 | +import mimetypes |
168 | +import time |
169 | + |
170 | +import bzrlib.errors |
171 | +import bzrlib.textfile |
172 | + |
173 | +from loggerhead.controllers import TemplatedBranchView |
174 | + |
175 | +class RawUI(TemplatedBranchView): |
176 | + |
177 | + def get_output(self, path, kwargs, start_response): |
178 | + # /raw/<revno>/[path] |
179 | + file_info = self.file_info(path, kwargs) |
180 | + revid = file_info['revid'] |
181 | + file_id = file_info['file_id'] |
182 | + filename = file_info['filename'] |
183 | + |
184 | + h = self._history |
185 | + tree = self.tree_for(file_id, revid) |
186 | + content = tree.get_file_lines(file_id) |
187 | + size = tree.inventory[file_id].text_size |
188 | + |
189 | + mime_type, encoding = mimetypes.guess_type(filename) |
190 | + if mime_type is None: |
191 | + try: |
192 | + bzrlib.textfile.check_text_lines(content) |
193 | + except bzrlib.errors.BinaryFile: |
194 | + mime_type = 'application/octet-stream' |
195 | + else: |
196 | + mime_type = 'text/plain' |
197 | + |
198 | + headers = [ |
199 | + ('Content-Type', mime_type), |
200 | + ('Content-Length', str(size)), |
201 | + ('X-Content-Type-Options', 'nosniff'), |
202 | + ] |
203 | + |
204 | + self.log.info('/raw %s @ %s (%d bytes): %.3f secs', |
205 | + file_info['path'], revid, size, time.time() - self._call_start) |
206 | + |
207 | + start_response('200 OK', headers) |
208 | + return content |
209 | |
210 | === modified file 'loggerhead/controllers/view_ui.py' |
211 | --- loggerhead/controllers/view_ui.py 2010-12-01 07:13:52 +0000 |
212 | +++ loggerhead/controllers/view_ui.py 2010-12-03 19:41:25 +0000 |
213 | @@ -18,7 +18,6 @@ |
214 | # |
215 | |
216 | import cgi |
217 | -import os |
218 | import time |
219 | |
220 | import bzrlib.errors |
221 | @@ -39,12 +38,9 @@ |
222 | |
223 | template_path = 'loggerhead.templates.view' |
224 | |
225 | - def tree_for(self, file_id, revid): |
226 | - file_revid = self._history.get_inventory(revid)[file_id].revision |
227 | - return self._history._branch.repository.revision_tree(file_revid) |
228 | - |
229 | - def text_lines(self, file_id, revid): |
230 | - file_name = os.path.basename(self._history.get_path(revid, file_id)) |
231 | + def text_lines(self, file_info): |
232 | + file_id = file_info['file_id'] |
233 | + revid = file_info['revid'] |
234 | |
235 | tree = self.tree_for(file_id, revid) |
236 | file_text = tree.get_file_text(file_id) |
237 | @@ -60,7 +56,7 @@ |
238 | bzrlib.textfile.check_text_lines(file_lines) |
239 | |
240 | if highlight is not None: |
241 | - hl_lines = highlight(file_name, file_text, encoding) |
242 | + hl_lines = highlight(file_info['filename'], file_text, encoding) |
243 | # highlight strips off extra newlines at the end of the file. |
244 | extra_lines = len(file_lines) - len(hl_lines) |
245 | hl_lines.extend([u''] * extra_lines) |
246 | @@ -69,9 +65,9 @@ |
247 | |
248 | return hl_lines; |
249 | |
250 | - def file_contents(self, file_id, revid): |
251 | + def file_contents(self, file_info): |
252 | try: |
253 | - file_lines = self.text_lines(file_id, revid) |
254 | + file_lines = self.text_lines(file_info) |
255 | except bzrlib.errors.BinaryFile: |
256 | # bail out; this isn't displayable text |
257 | return ['(This is a binary file.)'] |
258 | @@ -79,25 +75,15 @@ |
259 | return file_lines |
260 | |
261 | def get_values(self, path, kwargs, headers): |
262 | + file_info = self.file_info(path, kwargs) |
263 | + path = file_info['path'] |
264 | + revid = file_info['revid'] |
265 | + |
266 | + # no navbar for revisions |
267 | + navigation = util.Container() |
268 | + |
269 | history = self._history |
270 | branch = history._branch |
271 | - revid = self.get_revid() |
272 | - revid = history.fix_revid(revid) |
273 | - file_id = kwargs.get('file_id', None) |
274 | - if (file_id is None) and (path is None): |
275 | - raise HTTPBadRequest('No file_id or filename ' |
276 | - 'provided to view') |
277 | - |
278 | - if file_id is None: |
279 | - file_id = history.get_file_id(revid, path) |
280 | - |
281 | - # no navbar for revisions |
282 | - navigation = util.Container() |
283 | - |
284 | - if path is None: |
285 | - path = history.get_path(revid, file_id) |
286 | - filename = os.path.basename(path) |
287 | - |
288 | change = history.get_changes([ revid ])[0] |
289 | # If we're looking at the tip, use head: in the URL instead |
290 | if revid == branch.last_revision(): |
291 | @@ -126,12 +112,12 @@ |
292 | # true or not before using it, so we have to define it here also. |
293 | 'annotated': False, |
294 | 'revno_url': revno_url, |
295 | - 'file_id': file_id, |
296 | + 'file_id': file_info['file_id'], |
297 | 'file_path': path, |
298 | - 'filename': filename, |
299 | + 'filename': file_info['filename'], |
300 | 'navigation': navigation, |
301 | 'change': change, |
302 | - 'contents': self.file_contents(file_id, revid), |
303 | + 'contents': self.file_contents(file_info), |
304 | 'fileview_active': True, |
305 | 'directory_breadcrumbs': directory_breadcrumbs, |
306 | 'branch_breadcrumbs': branch_breadcrumbs, |
307 | |
308 | === modified file 'loggerhead/static/css/files.css' |
309 | --- loggerhead/static/css/files.css 2009-06-17 20:28:20 +0000 |
310 | +++ loggerhead/static/css/files.css 2010-12-03 19:41:25 +0000 |
311 | @@ -48,3 +48,10 @@ |
312 | .bug { |
313 | background:url(../images/ico_bug.png) no-repeat top left; |
314 | } |
315 | +#inventory .expcell { |
316 | + width: 28px; |
317 | + text-align: left; |
318 | +} |
319 | +.view_link, .raw_link { |
320 | + margin-right: 10px; |
321 | +} |
322 | \ No newline at end of file |
323 | |
324 | === modified file 'loggerhead/templates/inventory.pt' |
325 | --- loggerhead/templates/inventory.pt 2010-11-30 10:18:40 +0000 |
326 | +++ loggerhead/templates/inventory.pt 2010-12-03 19:41:25 +0000 |
327 | @@ -27,7 +27,7 @@ |
328 | </tal:block> |
329 | </h1> |
330 | |
331 | - <div metal:fill-slot="content"> |
332 | + <div metal:fill-slot="content" id="inventory"> |
333 | <tal:branch-info replace="structure python:branchinfo(branch)" /> |
334 | |
335 | <p tal:condition="python:not change"> |
336 | @@ -52,7 +52,6 @@ |
337 | <th class="datecell"><a tal:attributes="href python:url(['/files', revid], sort='date')">Last Changed</a></th> |
338 | <th class="timedate"><a tal:attributes="href python:url(['/files', revid], sort='size')">Size</a></th> |
339 | <th class="expandcell"></th> |
340 | - <th class="expandcell"></th> |
341 | </tr> |
342 | |
343 | <tr class="blueRow0" tal:condition="python:updir is not None"> |
344 | @@ -84,7 +83,6 @@ |
345 | <img tal:attributes="src python:branch.static_url('/static/images/ico_planilla.gif')" alt="Diff" /> |
346 | </a> |
347 | </td> |
348 | - <td class="expcell"></td> |
349 | </tr> |
350 | |
351 | <!-- Show this if it's a symlink --> |
352 | @@ -108,7 +106,6 @@ |
353 | title string:Show revision ${file/change/revno}" /> |
354 | </a> |
355 | </td> |
356 | - <td class="expcell"></td> |
357 | </tr> |
358 | |
359 | <!-- Show this if it's a regular file --> |
360 | @@ -131,13 +128,15 @@ |
361 | </td> |
362 | <td class="date" tal:content="python:util.date_time(file.change.date)"></td> |
363 | <td class="timedate2" tal:content="python:util.human_size(file.size)"></td> |
364 | - <td class="expcell"><a tal:attributes="href python:url(['/view', revno_url, file.absolutepath]); |
365 | - title string:View ${file/filename}"> |
366 | - <img tal:attributes="src python:branch.static_url('/static/images/ico_planilla.gif')" alt="Diff" /> |
367 | - </a> |
368 | - </td> |
369 | <td class="expcell"> |
370 | - <a tal:attributes="href python:url(['/download', file.revid, file.file_id, file.filename]); |
371 | + <a class="raw_link" |
372 | + tal:attributes="href python:url(['/raw', revno_url, file.absolutepath]); |
373 | + title string:Raw content of ${file/filename}"> |
374 | + <img tal:attributes="src python:branch.static_url('/static/images/ico_file.gif')" |
375 | + alt="View raw file" /> |
376 | + </a> |
377 | + <a class="download_link" |
378 | + tal:attributes="href python:url(['/download', file.revid, file.file_id, file.filename]); |
379 | title string:Download ${file/filename} at revision ${file/change/revno}"> |
380 | <img tal:attributes="src python:branch.static_url('/static/images/ico_file_download.gif')" alt="Download File" /> |
381 | </a> |
382 | |
383 | === modified file 'loggerhead/templates/view.pt' |
384 | --- loggerhead/templates/view.pt 2010-12-01 08:10:46 +0000 |
385 | +++ loggerhead/templates/view.pt 2010-12-03 19:41:25 +0000 |
386 | @@ -51,6 +51,10 @@ |
387 | <a tal:attributes="href python:url(['/changes'], clear=1, filter_file_id=file_id)" |
388 | >view changes to this file</a> |
389 | </li> |
390 | + <li> |
391 | + <a tal:attributes="href python:url(['/raw', revno_url, file_path], clear=1)" |
392 | + >view raw content</a> |
393 | + </li> |
394 | <li id="last"> |
395 | <a tal:attributes="href python:url(['/download', |
396 | revno_url, file_id, filename])" |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 12/3/2010 1:41 PM, Max Kanat-Alexander wrote: octet-stream.
> Max Kanat-Alexander has proposed merging lp:~mkanat/loggerhead/raw-controller into lp:loggerhead.
>
> Requested reviews:
> Bazaar Developers (bzr)
>
>
> This addresses bug 605775 by adding a new "raw" controller that displays raw data. It's surprisingly fast--about 0.05s to display almost any file. It does its best to determine the correct MIME type to serve a file with, although it will often fall back to text/plain or application/
>
> The only thing that this patch is missing is XSS protection (serving the raw content from a different domain), which is something I want to do in a separate bug and MP, because this MP is already complex enough as it is. There's also some preliminary refactoring involved in this patch that was necessary to get a "raw" controller that worked well without copying-and-pasting code. (After this, there's also some refactoring that can be done to make the "download" controller a subclass of RawUI, but that will involve changing the download URLs and how they work, so that's also something I wanted to do separately.)
If we are worried about XSS, why not start by having raw just return octet-stream until we figure out how to
everything as application/
protect against it?
John enigmail. mozdev. org/
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkz 5Y50ACgkQJdeBCY SNAAMItACfUNGl+ AHZBIBTQpIfDr9l giwO Rq0D8/cRHssArTx Bo
wUkAni4f6kZji70
=JeEQ
-----END PGP SIGNATURE-----