comparison MoinMoin/action/AttachFile.py @ 4210:d9de4fa12f23

Merged: 1.8
author Florian Krupicka <florian.krupicka@googlemail.com>
date Wed, 09 Jul 2008 20:43:29 +0200
parents 40acd13fb3d6 b6dcdf55795e
children f77469d98cd2
comparison
equal deleted inserted replaced
4209:7abbc2bb328d 4210:d9de4fa12f23
329 else: 329 else:
330 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='view')) + 330 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='view')) +
331 fmt.text(label_view) + 331 fmt.text(label_view) +
332 fmt.url(0)) 332 fmt.url(0))
333 333
334 is_zipfile = zipfile.is_zipfile(fullpath) 334 try:
335 if is_zipfile: 335 is_zipfile = zipfile.is_zipfile(fullpath)
336 is_package = packages.ZipPackage(request, fullpath).isPackage() 336 if is_zipfile:
337 if is_package and request.user.isSuperUser(): 337 is_package = packages.ZipPackage(request, fullpath).isPackage()
338 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='install')) + 338 if is_package and request.user.isSuperUser():
339 fmt.text(label_install) + 339 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='install')) +
340 fmt.url(0)) 340 fmt.text(label_install) +
341 elif (not is_package and mt.minor == 'zip' and 341 fmt.url(0))
342 may_delete and 342 elif (not is_package and mt.minor == 'zip' and
343 request.user.may.read(pagename) and 343 may_delete and
344 request.user.may.write(pagename)): 344 request.user.may.read(pagename) and
345 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='unzip')) + 345 request.user.may.write(pagename)):
346 fmt.text(label_unzip) + 346 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='unzip')) +
347 fmt.url(0)) 347 fmt.text(label_unzip) +
348 fmt.url(0))
349 except RuntimeError:
350 # We don't want to crash with a traceback here (an exception
351 # here could be caused by an uploaded defective zip file - and
352 # if we crash here, the user does not get a UI to remove the
353 # defective zip file again).
354 # RuntimeError is raised by zipfile stdlib module in case of
355 # problems (like inconsistent slash and backslash usage in the
356 # archive).
357 logging.exception("An exception within zip file attachment handling occurred:")
348 358
349 html.append(fmt.listitem(1)) 359 html.append(fmt.listitem(1))
350 html.append("[%s]" % "&nbsp;| ".join(links)) 360 html.append("[%s]" % "&nbsp;| ".join(links))
351 html.append(" (%(fmtime)s, %(fsize)s KB) [[attachment:%(file)s]]" % parmdict) 361 html.append(" (%(fmtime)s, %(fsize)s KB) [[attachment:%(file)s]]" % parmdict)
352 html.append(fmt.listitem(0)) 362 html.append(fmt.listitem(0))
372 return files 382 return files
373 383
374 384
375 def _get_filelist(request, pagename): 385 def _get_filelist(request, pagename):
376 return _build_filelist(request, pagename, 1, 0) 386 return _build_filelist(request, pagename, 1, 0)
377
378
379 def _subdir_exception(zf):
380 """
381 Checks for the existance of one common subdirectory shared among
382 all files in the zip file. If this is the case, returns a dict of
383 original names to modified names so that such files can be unpacked
384 as the user would expect.
385 """
386
387 b = zf.namelist()
388 if not '/' in b[0]:
389 return False # no directory
390 slashoffset = b[0].index('/')
391 directory = b[0][:slashoffset]
392 for origname in b:
393 if origname.rfind('/') != slashoffset or origname[:slashoffset] != directory:
394 return False # multiple directories or different directory
395 names = {}
396 for origname in b:
397 names[origname] = origname[slashoffset+1:]
398 return names # returns dict of {origname: safename}
399 387
400 388
401 def error_msg(pagename, request, msg): 389 def error_msg(pagename, request, msg):
402 request.theme.add_msg(msg, "error") 390 request.theme.add_msg(msg, "error")
403 Page(request, pagename).send_page() 391 Page(request, pagename).send_page()
857 msg = _('The file %s is not a MoinMoin package file.') % wikiutil.escape(target) 845 msg = _('The file %s is not a MoinMoin package file.') % wikiutil.escape(target)
858 846
859 upload_form(pagename, request, msg=msg) 847 upload_form(pagename, request, msg=msg)
860 848
861 849
862 def _do_unzip(pagename, request): 850 def _do_unzip(pagename, request, overwrite=False):
863 _ = request.getText 851 _ = request.getText
864 valid_pathname = lambda name: ('/' not in name) and ('\\' not in name)
865
866 pagename, filename, fpath = _access_file(pagename, request) 852 pagename, filename, fpath = _access_file(pagename, request)
853
867 if not (request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename)): 854 if not (request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename)):
868 return _('You are not allowed to unzip attachments of this page.') 855 return _('You are not allowed to unzip attachments of this page.')
856
869 if not filename: 857 if not filename:
870 return # error msg already sent in _access_file 858 return # error msg already sent in _access_file
871 859
872 single_file_size = request.cfg.unzip_single_file_size 860 try:
873 attachments_file_space = request.cfg.unzip_attachments_space 861 if not zipfile.is_zipfile(fpath):
874 attachments_file_count = request.cfg.unzip_attachments_count 862 return _('The file %(filename)s is not a .zip file.') % {'filename': filename}
875 863
876 files = _get_files(request, pagename) 864 # determine how which attachment names we have and how much space each is occupying
877 865 curr_fsizes = dict([(f, size(request, pagename, f)) for f in _get_files(request, pagename)])
878 msg = "" 866
879 if files: 867 # Checks for the existance of one common prefix path shared among
880 fsize = 0.0 868 # all files in the zip file. If this is the case, remove the common prefix.
881 fcount = 0 869 # We also prepare a dict of the new filenames->filesizes.
882 for f in files: 870 zip_path_sep = '/' # we assume '/' is as zip standard suggests
883 fsize += float(size(request, pagename, f)) 871 fname_index = None
884 fcount += 1 872 mapping = []
885 873 new_fsizes = {}
886 available_attachments_file_space = attachments_file_space - fsize 874 zf = zipfile.ZipFile(fpath)
887 available_attachments_file_count = attachments_file_count - fcount 875 for zi in zf.infolist():
888 876 name = zi.filename
889 if zipfile.is_zipfile(fpath): 877 if not name.endswith(zip_path_sep): # a file (not a directory)
890 zf = zipfile.ZipFile(fpath) 878 if fname_index is None:
891 sum_size_over_all_valid_files = 0.0 879 fname_index = name.rfind(zip_path_sep) + 1
892 count_valid_files = 0 880 path = name[:fname_index]
893 namelist = _subdir_exception(zf) 881 if (name.rfind(zip_path_sep) + 1 != fname_index # different prefix len
894 if not namelist: # if it's not handled by _subdir_exception() 882 or
895 # convert normal zf.namelist() to {origname:finalname} dict 883 name[:fname_index] != path): # same len, but still different
896 namelist = {} 884 mapping = [] # zip is not acceptable
897 for name in zf.namelist(): 885 break
898 namelist[name] = name 886 if zi.file_size >= request.cfg.unzip_single_file_size: # file too big
899 for (origname, finalname) in namelist.iteritems(): 887 mapping = [] # zip is not acceptable
900 if valid_pathname(finalname): 888 break
901 sum_size_over_all_valid_files += zf.getinfo(origname).file_size 889 finalname = name[fname_index:] # remove common path prefix
902 count_valid_files += 1 890 finalname = finalname.decode(config.charset, 'replace') # replaces trash with \uFFFD char
903 891 mapping.append((name, finalname))
904 if sum_size_over_all_valid_files > available_attachments_file_space: 892 new_fsizes[finalname] = zi.file_size
905 msg = _("Attachment '%(filename)s' could not be unzipped because" 893
906 " the resulting files would be too large (%(space)d kB" 894 # now we either have an empty mapping (if the zip is not acceptable),
907 " missing).") % { 895 # an identity mapping (no subdirs in zip, just all flat), or
908 'filename': filename, 896 # a mapping (origname, finalname) where origname is the zip member filename
909 'space': (sum_size_over_all_valid_files - 897 # (including some prefix path) and finalname is a simple filename.
910 available_attachments_file_space) / 1000 } 898
911 elif count_valid_files > available_attachments_file_count: 899 # calculate resulting total file size / count after unzipping:
912 msg = _("Attachment '%(filename)s' could not be unzipped because" 900 if overwrite:
913 " the resulting files would be too many (%(count)d " 901 curr_fsizes.update(new_fsizes)
914 "missing).") % { 902 total = curr_fsizes
915 'filename': filename, 903 else:
916 'count': (count_valid_files - 904 new_fsizes.update(curr_fsizes)
917 available_attachments_file_count) } 905 total = new_fsizes
906 total_count = len(total)
907 total_size = sum(total.values())
908
909 if not mapping:
910 msg = _("Attachment '%(filename)s' not unzipped because some files in the zip "
911 "are either not in the same directory or exceeded the single file size limit (%(maxsize_file)d kB)."
912 ) % {'filename': filename,
913 'maxsize_file': request.cfg.unzip_single_file_size / 1000, }
914 elif total_size > request.cfg.unzip_attachments_space:
915 msg = _("Attachment '%(filename)s' not unzipped because it would have exceeded "
916 "the per page attachment storage size limit (%(size)d kB).") % {
917 'filename': filename,
918 'size': request.cfg.unzip_attachments_space / 1000, }
919 elif total_count > request.cfg.unzip_attachments_count:
920 msg = _("Attachment '%(filename)s' not unzipped because it would have exceeded "
921 "the per page attachment count limit (%(count)d).") % {
922 'filename': filename,
923 'count': request.cfg.unzip_attachments_count, }
924 else:
925 not_overwritten = []
926 for origname, finalname in mapping:
927 try:
928 # Note: reads complete zip member file into memory. ZipFile does not offer block-wise reading:
929 add_attachment(request, pagename, finalname, zf.read(origname), overwrite)
930 except AttachmentAlreadyExists:
931 not_overwritten.append(finalname)
932 if not_overwritten:
933 msg = _("Attachment '%(filename)s' partially unzipped (did not overwrite: %(filelist)s).") % {
934 'filename': filename,
935 'filelist': ', '.join(not_overwritten), }
918 else: 936 else:
919 valid_name = False 937 msg = _("Attachment '%(filename)s' unzipped.") % {'filename': filename}
920 for (origname, finalname) in namelist.iteritems(): 938 except RuntimeError, err:
921 if valid_pathname(finalname): 939 # We don't want to crash with a traceback here (an exception
922 zi = zf.getinfo(origname) 940 # here could be caused by an uploaded defective zip file - and
923 if zi.file_size < single_file_size: 941 # if we crash here, the user does not get a UI to remove the
924 new_file = getFilename(request, pagename, finalname) 942 # defective zip file again).
925 if not os.path.exists(new_file): 943 # RuntimeError is raised by zipfile stdlib module in case of
926 outfile = open(new_file, 'wb') 944 # problems (like inconsistent slash and backslash usage in the
927 outfile.write(zf.read(origname)) 945 # archive).
928 outfile.close() 946 logging.exception("An exception within zip file attachment handling occurred:")
929 # it's not allowed to zip a zip file so it is dropped 947 msg = _("A severe error occurred:") + ' ' + str(err)
930 if zipfile.is_zipfile(new_file):
931 os.unlink(new_file)
932 else:
933 valid_name = True
934 _addLogEntry(request, 'ATTNEW', pagename, finalname)
935
936 if valid_name:
937 msg = _("Attachment '%(filename)s' unzipped.") % {'filename': filename}
938 else:
939 msg = _("Attachment '%(filename)s' not unzipped because the "
940 "files are too big, .zip files only, exist already or "
941 "reside in folders.") % {'filename': filename}
942 else:
943 msg = _('The file %(filename)s is not a .zip file.') % {'filename': filename}
944 948
945 upload_form(pagename, request, msg=wikiutil.escape(msg)) 949 upload_form(pagename, request, msg=wikiutil.escape(msg))
946 950
947 951
948 def send_viewfile(pagename, request): 952 def send_viewfile(pagename, request):
989 content = wikiutil.escape(content) 993 content = wikiutil.escape(content)
990 request.write(request.formatter.text(content)) 994 request.write(request.formatter.text(content))
991 request.write(request.formatter.preformatted(0)) 995 request.write(request.formatter.preformatted(0))
992 return 996 return
993 997
994 package = packages.ZipPackage(request, fpath) 998 try:
995 if package.isPackage(): 999 package = packages.ZipPackage(request, fpath)
996 request.write("<pre><b>%s</b>\n%s</pre>" % (_("Package script:"), wikiutil.escape(package.getScript()))) 1000 if package.isPackage():
997 return 1001 request.write("<pre><b>%s</b>\n%s</pre>" % (_("Package script:"), wikiutil.escape(package.getScript())))
998 1002 return
999 if zipfile.is_zipfile(fpath) and mt.minor == 'zip': 1003
1000 zf = zipfile.ZipFile(fpath, mode='r') 1004 if zipfile.is_zipfile(fpath) and mt.minor == 'zip':
1001 request.write("<pre>%-46s %19s %12s\n" % (_("File Name"), _("Modified")+" "*5, _("Size"))) 1005 zf = zipfile.ZipFile(fpath, mode='r')
1002 for zinfo in zf.filelist: 1006 request.write("<pre>%-46s %19s %12s\n" % (_("File Name"), _("Modified")+" "*5, _("Size")))
1003 date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time 1007 for zinfo in zf.filelist:
1004 request.write(wikiutil.escape("%-46s %s %12d\n" % (zinfo.filename, date, zinfo.file_size))) 1008 date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
1005 request.write("</pre>") 1009 request.write(wikiutil.escape("%-46s %s %12d\n" % (zinfo.filename, date, zinfo.file_size)))
1010 request.write("</pre>")
1011 return
1012 except RuntimeError:
1013 # We don't want to crash with a traceback here (an exception
1014 # here could be caused by an uploaded defective zip file - and
1015 # if we crash here, the user does not get a UI to remove the
1016 # defective zip file again).
1017 # RuntimeError is raised by zipfile stdlib module in case of
1018 # problems (like inconsistent slash and backslash usage in the
1019 # archive).
1020 logging.exception("An exception within zip file attachment handling occurred:")
1006 return 1021 return
1007 1022
1008 from MoinMoin import macro 1023 from MoinMoin import macro
1009 from MoinMoin.parser.text import Parser 1024 from MoinMoin.parser.text import Parser
1010 1025