witten
/
luminotes
Archived
1
0
Fork 0

Conditionally quoting download filenames based on detected browser.

This commit is contained in:
Dan Helfman 2008-03-18 22:22:19 +00:00
parent 39a9cba457
commit 2cae1faf2d
5 changed files with 57 additions and 12 deletions

5
NEWS
View File

@ -1,3 +1,8 @@
1.2.17: March 18, 2008
* Internet Explorer expects quoted download filenames, while Firefox
doesn't. So I took that into account by quoting conditionally based on the
detected browser.
1.2.16: March 18, 2008 1.2.16: March 18, 2008
* Fixed a bug that prevented the upload of filenames with special characters * Fixed a bug that prevented the upload of filenames with special characters
in them. in them.

View File

@ -2,11 +2,12 @@ import os
import re import re
import cgi import cgi
import time import time
import urllib
import tempfile import tempfile
import cherrypy import cherrypy
from threading import Lock, Event from threading import Lock, Event
from Expose import expose from Expose import expose
from Validate import validate, Valid_int, Validation_error from Validate import validate, Valid_int, Valid_bool, Validation_error
from Database import Valid_id, end_transaction from Database import Valid_id, end_transaction
from Users import grab_user_id from Users import grab_user_id
from Expire import strongly_expire from Expire import strongly_expire
@ -236,14 +237,19 @@ class Files( object ):
@grab_user_id @grab_user_id
@validate( @validate(
file_id = Valid_id(), file_id = Valid_id(),
quote_filename = Valid_bool( none_okay = True ),
user_id = Valid_id( none_okay = True ), user_id = Valid_id( none_okay = True ),
) )
def download( self, file_id, user_id = None ): def download( self, file_id, quote_filename = False, user_id = None ):
""" """
Return the contents of file that a user has previously uploaded. Return the contents of file that a user has previously uploaded.
@type file_id: unicode @type file_id: unicode
@param file_id: id of the file to download @param file_id: id of the file to download
@type quote_filename: bool
@param quote_filename: True to URL quote the filename of the downloaded file, False to leave it
as UTF-8. IE expects quoting while Firefox doesn't (optional, defaults
to False)
@type user_id: unicode or NoneType @type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any) @param user_id: id of current logged-in user (if any)
@rtype: unicode @rtype: unicode
@ -265,9 +271,12 @@ class Files( object ):
db_file = self.__database.load( File, file_id ) db_file = self.__database.load( File, file_id )
cherrypy.response.headerMap[ u"Content-Type" ] = db_file.content_type cherrypy.response.headerMap[ u"Content-Type" ] = db_file.content_type
disposition = u'attachment; filename="%s"' % db_file.filename.replace( '"', r"\"" )
disposition = disposition.encode( "utf8" ) filename = db_file.filename.replace( '"', r"\"" ).encode( "utf8" )
cherrypy.response.headerMap[ u"Content-Disposition" ] = disposition if quote_filename:
filename = urllib.quote( filename, safe = "" )
cherrypy.response.headerMap[ u"Content-Disposition" ] = 'attachment; filename="%s"' % filename
cherrypy.response.headerMap[ u"Content-Length" ] = db_file.size_bytes cherrypy.response.headerMap[ u"Content-Length" ] = db_file.size_bytes
def stream(): def stream():

View File

@ -125,8 +125,12 @@ class Valid_bool( object ):
""" """
Validator for a boolean value. Validator for a boolean value.
""" """
def __init__( self, none_okay = False ):
self.__none_okay = none_okay
def __call__( self, value ): def __call__( self, value ):
value = value.strip() value = value.strip()
if self.__none_okay and value in ( None, "None", "" ): return None
if value in ( u"True", u"true" ): return True if value in ( u"True", u"true" ): return True
if value in ( u"False", u"false" ): return False if value in ( u"False", u"false" ): return False

View File

@ -2,6 +2,7 @@
import time import time
import types import types
import urllib
import cherrypy import cherrypy
from threading import Thread from threading import Thread
from StringIO import StringIO from StringIO import StringIO
@ -119,7 +120,7 @@ class Test_files( Test_controller ):
if self.upload_thread: if self.upload_thread:
self.upload_thread.join() self.upload_thread.join()
def test_download( self, filename = None ): def test_download( self, filename = None, quote_filename = None ):
self.login() self.login()
self.http_upload( self.http_upload(
@ -134,15 +135,30 @@ class Test_files( Test_controller ):
session_id = self.session_id, session_id = self.session_id,
) )
result = self.http_get( if quote_filename is None:
"/files/download?file_id=%s" % self.file_id, result = self.http_get(
session_id = self.session_id, "/files/download?file_id=%s" % self.file_id,
) session_id = self.session_id,
)
elif quote_filename is True:
result = self.http_get(
"/files/download?file_id=%s&quote_filename=true" % self.file_id,
session_id = self.session_id,
)
else:
result = self.http_get(
"/files/download?file_id=%s&quote_filename=false" % self.file_id,
session_id = self.session_id,
)
headers = result[ u"headers" ] headers = result[ u"headers" ]
assert headers assert headers
assert headers[ u"Content-Type" ] == self.content_type assert headers[ u"Content-Type" ] == self.content_type
assert headers[ u"Content-Disposition" ] == ( u'attachment; filename="%s"' % ( filename or self.filename ) ).encode( "utf8" )
filename = ( filename or self.filename ).encode( "utf8" )
if quote_filename is True:
filename = urllib.quote( filename )
assert headers[ u"Content-Disposition" ] == 'attachment; filename="%s"' % filename
gen = result[ u"body" ] gen = result[ u"body" ]
assert isinstance( gen, types.GeneratorType ) assert isinstance( gen, types.GeneratorType )
@ -161,6 +177,12 @@ class Test_files( Test_controller ):
def test_download_with_unicode_filename( self ): def test_download_with_unicode_filename( self ):
self.test_download( self.unicode_filename ) self.test_download( self.unicode_filename )
def test_download_with_unicode_quoted_filename( self ):
self.test_download( self.unicode_filename, quote_filename = True )
def test_download_with_unicode_unquoted_filename( self ):
self.test_download( self.unicode_filename, quote_filename = False )
def test_download_without_login( self ): def test_download_without_login( self ):
self.login() self.login()

View File

@ -2454,9 +2454,14 @@ Upload_pulldown.prototype.upload_started = function ( file_id ) {
} }
Upload_pulldown.prototype.upload_complete = function () { Upload_pulldown.prototype.upload_complete = function () {
if ( /MSIE/.test( navigator.userAgent ) )
var quote_filename = true;
else
var quote_filename = false;
// now that the upload is done, the file link should point to the uploaded file // now that the upload is done, the file link should point to the uploaded file
this.uploading = false; this.uploading = false;
this.link.href = "/files/download?file_id=" + this.file_id this.link.href = "/files/download?file_id=" + this.file_id + "&quote_filename=" + quote_filename;
new File_link_pulldown( this.wiki, this.notebook_id, this.invoker, this.editor, this.link ); new File_link_pulldown( this.wiki, this.notebook_id, this.invoker, this.editor, this.link );
this.shutdown(); this.shutdown();