tw-public@0: # -*- coding: iso-8859-1 -*- tw-public@0: """ tw-public@0: MoinMoin - AttachFile action tw-public@0: tw-public@0: This action lets a page have multiple attachment files. tw-public@0: It creates a folder /pages//attachments tw-public@0: and keeps everything in there. tw-public@0: tw-public@0: Form values: action=Attachment tw-public@0: 1. with no 'do' key: returns file upload form tw-public@0: 2. do=attach: accept file upload and saves the file in tw-public@0: ../attachment/pagename/ tw-public@0: 3. /pagename/fname?action=Attachment&do=get[&mimetype=type]: tw-public@0: return contents of the attachment file with the name fname. tw-public@0: 4. /pathname/fname, do=view[&mimetype=type]:create a page tw-public@0: to view the content of the file tw-public@0: tw@1911: To insert an attachment into the page, use the "attachment:" pseudo schema. tw-public@0: tw@1911: @copyright: 2001 by Ken Sugino (sugino@mediaone.net), tw@1918: 2001-2004 by Juergen Hermann , tw@1911: 2005 MoinMoin:AlexanderSchremmer, tw@1911: 2005 DiegoOngaro at ETSZONE (diego@etszone.com), rb@1952: 2005-2007 MoinMoin:ReimarBauer, tw@1911: 2007 MoinMoin:ThomasWaldmann tw-public@0: @license: GNU GPL, see COPYING for details. tw-public@0: """ tw-public@0: rb@1952: import os, time, zipfile, mimetypes tw@1791: from MoinMoin import config, wikiutil, packages tw-public@0: from MoinMoin.Page import Page tw@1115: from MoinMoin.util import filesys, timefuncs grzywacz@2106: from MoinMoin.events import FileAttachedEvent, send_event tw-public@0: tw-public@0: action_name = __name__.split('.')[-1] tw-public@0: tw-public@0: def htdocs_access(request): tw-public@0: return isinstance(request.cfg.attachments, type({})) tw-public@0: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### External interface - these are called from the core code tw-public@0: ############################################################################# tw-public@0: alex@691: class AttachmentAlreadyExists(Exception): alex@691: pass alex@691: tw-public@0: def getBasePath(request): tw-public@0: """ Get base path where page dirs for attachments are stored. tw-public@0: """ tw-public@0: if htdocs_access(request): tw-public@0: return request.cfg.attachments['dir'] tw-public@0: else: tw-public@0: return request.rootpage.getPagePath('pages') tw-public@0: tw-public@0: tw-public@0: def getAttachDir(request, pagename, create=0): tw-public@0: """ Get directory where attachments for page `pagename` are stored. tw-public@0: """ tw-public@0: if htdocs_access(request): tw-public@0: # direct file access via webserver, from public htdocs area tw-public@0: pagename = wikiutil.quoteWikinameFS(pagename) tw-public@0: attach_dir = os.path.join(request.cfg.attachments['dir'], pagename, "attachments") tw-public@0: if create and not os.path.isdir(attach_dir): tw@1428: os.makedirs(attach_dir) tw-public@0: else: tw-public@0: # send file via CGI, from page storage area tw@1904: if request.page and pagename == request.page.page_name: rb@1952: page = request.page # reusing existing page obj is faster tw@1813: else: tw@1813: page = Page(request, pagename) tw@1813: attach_dir = page.getPagePath("attachments", check_create=create) tw-public@0: tw-public@0: return attach_dir tw-public@0: Florian@143: def absoluteName(url, pagename): tw@281: """ Get (pagename, filename) of an attachment: link tw@281: @param url: PageName/filename.ext or filename.ext (unicode) tw@281: @param pagename: name of the currently processed page (unicode) tw@281: @rtype: tuple of unicode tw@281: @return: PageName, filename.ext Florian@143: """ tw@281: pieces = url.split(u'/') tw@281: if len(pieces) == 1: Florian@143: return pagename, pieces[0] Florian@143: else: Florian@145: return u"/".join(pieces[:-1]), pieces[-1] tw-public@0: R@1611: def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get'): tw-public@0: """ Get URL that points to attachment `filename` of page `pagename`. tw-public@0: tw-public@0: If 'addts' is true, a timestamp with the file's modification time tw-public@0: is added, so that browsers reload a changed file. tw-public@0: """ tw-public@0: if htdocs_access(request): tw-public@0: # direct file access via webserver tw-public@0: timestamp = '' tw-public@0: if addts: tw-public@0: try: tw-public@0: timestamp = '?ts=%s' % os.path.getmtime( tw-public@0: getFilename(request, pagename, filename)) tw-public@0: except IOError: tw-public@0: pass tw-public@0: tw-public@0: url = "%s/%s/attachments/%s%s" % ( tw-public@0: request.cfg.attachments['url'], wikiutil.quoteWikinameFS(pagename), tw@101: wikiutil.url_quote(filename), timestamp) tw-public@0: else: tw-public@0: # send file via CGI R@1611: if do not in ['get', 'view']: R@1611: do = 'get' R@1611: R@1611: url = "%s/%s?action=%s&do=%s&target=%s" % ( tw-public@0: request.getScriptname(), wikiutil.quoteWikinameURL(pagename), R@1611: action_name, do, wikiutil.url_quote_plus(filename)) tw-public@0: if escaped: tw-public@0: url = wikiutil.escape(url) tw-public@0: return url tw-public@0: tw-public@0: def getIndicator(request, pagename): tw-public@0: """ Get an attachment indicator for a page (linked clip image) or tw-public@0: an empty string if not attachments exist. tw-public@0: """ tw-public@0: _ = request.getText tw-public@0: attach_dir = getAttachDir(request, pagename) tw@1920: if not os.path.exists(attach_dir): tw@1920: return '' tw-public@0: tw-public@0: files = os.listdir(attach_dir) tw@1920: if not files: tw@1920: return '' tw-public@0: tw-public@0: attach_count = _('[%d attachments]') % len(files) tw@889: attach_icon = request.theme.make_icon('attach', vars={'attach_count': attach_count}) tw-public@0: attach_link = wikiutil.link_tag(request, tw-public@0: "%s?action=AttachFile" % wikiutil.quoteWikinameURL(pagename), tw@869: attach_icon, tw@869: request.formatter, rel='nofollow') tw-public@0: tw-public@0: return attach_link tw-public@0: tw-public@0: tw@281: def getFilename(request, pagename, filename): tw@281: """ make complete pathfilename of file "name" attached to some page "pagename" tw@281: @param request: request object tw@281: @param pagename: name of page where the file is attached to (unicode) tw@281: @param filename: filename of attached file (unicode) tw@281: @rtype: string (in config.charset encoding) tw@281: @return: complete path/filename of attached file tw@281: """ tw@1911: if isinstance(filename, unicode): tw@1911: filename = filename.encode(config.charset) tw@1911: return os.path.join(getAttachDir(request, pagename), filename) tw-public@0: tw@1910: def exists(request, pagename, filename): tw@1910: """ check if page has a file attached """ tw@1910: fpath = getFilename(request, pagename, filename) tw@1910: return os.path.exists(fpath) tw-public@0: tw@1911: def size(request, pagename, filename): tw@1911: """ return file size of file attachment """ tw@1911: fpath = getFilename(request, pagename, filename) tw@1911: return os.path.getsize(fpath) tw@1911: tw-public@0: def info(pagename, request): tw-public@0: """ Generate snippet with info on the attachment for page `pagename`. tw-public@0: """ tw-public@0: _ = request.getText tw-public@0: tw-public@0: attach_dir = getAttachDir(request, pagename) tw-public@0: files = [] tw-public@0: if os.path.isdir(attach_dir): tw-public@0: files = os.listdir(attach_dir) tw-public@0: page = Page(request, pagename) tw@1816: link = page.url(request, {'action': 'AttachFile'}) tw-public@0: attach_info = _('There are %(count)s attachment(s) stored for this page.', formatted=False) % { tw-public@0: 'count': len(files), tw-public@0: 'link': wikiutil.escape(link) tw-public@0: } tw-public@0: return "\n

\n%s\n

\n" % attach_info tw-public@0: tw@1791: def add_attachment(request, pagename, target, filecontent, overwrite=0): alex@691: # replace illegal chars rb@1797: rb@1797: _ = request.getText rb@1797: alex@691: target = wikiutil.taintfilename(target) tw-public@0: alex@691: # set mimetype from extension, or from given mimetype tw@802: #type, encoding = wikiutil.guess_type(target) alex@691: #if not type: alex@691: # ext = None tw@1868: # if 'mime' in request.form: tw@802: # ext = wikiutil.guess_extension(request.form['mime'][0]) alex@691: # if not ext: tw@802: # type, encoding = wikiutil.guess_type(filename) alex@691: # if type: tw@802: # ext = wikiutil.guess_extension(type) alex@691: # else: alex@691: # ext = '' alex@691: # target = target + ext alex@691: alex@691: # get directory, and possibly create it alex@691: attach_dir = getAttachDir(request, pagename, create=1) alex@691: # save file alex@691: fpath = os.path.join(attach_dir, target).encode(config.charset) tw@1765: exists = os.path.exists(fpath) tw@1765: if exists and not overwrite: tw@2286: msg = _("Attachment '%(target)s' already exists.") % {'target': target, } alex@691: else: tw@1765: if exists: tw@1765: try: tw@1765: os.remove(fpath) tw@1765: except: tw@1765: pass alex@691: stream = open(fpath, 'wb') alex@691: try: alex@691: stream.write(filecontent) alex@691: finally: alex@691: stream.close() alex@691: alex@691: _addLogEntry(request, 'ATTNEW', pagename, target) tw@2286: grzywacz@2106: event = FileAttachedEvent(request, pagename, target, len(filecontent)) grzywacz@2106: messages = send_event(event) grzywacz@2106: msg = "".join(messages) tw@889: fpletz@1473: if request.cfg.xapian_search: fpletz@1473: from MoinMoin.search.Xapian import Index fpletz@1473: index = Index(request) fpletz@1473: if index.exists(): fpletz@1473: index.update_page(pagename) fpletz@1473: alex@691: return target tw@802: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Internal helpers tw-public@0: ############################################################################# tw-public@0: tw-public@0: def _addLogEntry(request, action, pagename, filename): tw-public@0: """ Add an entry to the edit log on uploads and deletes. tw-public@0: tw-public@0: `action` should be "ATTNEW" or "ATTDEL" tw-public@0: """ tw-public@0: from MoinMoin.logfile import editlog tw-public@0: t = wikiutil.timestamp2version(time.time()) tw@101: fname = wikiutil.url_quote(filename, want_unicode=True) tw-public@0: tw-public@0: # Write to global log tw-public@0: log = editlog.EditLog(request) tw-public@0: log.add(request, t, 99999999, action, pagename, request.remote_addr, fname) tw-public@0: tw-public@0: # Write to local log tw-public@0: log = editlog.EditLog(request, rootpagename=pagename) tw-public@0: log.add(request, t, 99999999, action, pagename, request.remote_addr, fname) tw-public@0: tw-public@0: tw-public@0: def _access_file(pagename, request): tw-public@0: """ Check form parameter `target` and return a tuple of tw-public@0: `(filename, filepath)` for an existing attachment. tw-public@0: tw-public@0: Return `(None, None)` if an error occurs. tw-public@0: """ tw-public@0: _ = request.getText tw-public@0: tw-public@0: error = None tw-public@0: if not request.form.get('target', [''])[0]: tw-public@0: error = _("Filename of attachment not specified!") tw-public@0: else: tw-public@0: filename = wikiutil.taintfilename(request.form['target'][0]) tw-public@0: fpath = getFilename(request, pagename, filename) tw-public@0: tw-public@0: if os.path.isfile(fpath): tw-public@0: return (filename, fpath) tw-public@0: error = _("Attachment '%(filename)s' does not exist!") % {'filename': filename} tw-public@0: tw-public@0: error_msg(pagename, request, error) tw-public@0: return (None, None) tw-public@0: tw-public@0: tw@1653: def _build_filelist(request, pagename, showheader, readonly, mime_type='*'): tw-public@0: _ = request.getText tw-public@0: tw-public@0: # access directory tw-public@0: attach_dir = getAttachDir(request, pagename) tw-public@0: files = _get_files(request, pagename) tw-public@0: tw@1653: if mime_type != '*': tw@1653: files = [fname for fname in files if mime_type == mimetypes.guess_type(fname)[0]] tw@1653: tw@1920: html = "" tw-public@0: if files: tw-public@0: if showheader: tw@1920: html += _( tw-public@0: "To refer to attachments on a page, use '''{{{attachment:filename}}}''', \n" tw-public@0: "as shown below in the list of files. \n" tw-public@0: "Do '''NOT''' use the URL of the {{{[get]}}} link, \n" tw-public@0: "since this is subject to change and can break easily." tw-public@0: ) tw@1920: html += "
    " tw-public@0: tw-public@0: label_del = _("del") R@1608: label_move = _("move") tw-public@0: label_get = _("get") tw-public@0: label_edit = _("edit") tw-public@0: label_view = _("view") alex@80: label_unzip = _("unzip") alex@80: label_install = _("install") tw-public@0: tw-public@0: for file in files: rb@1985: mt = wikiutil.MimeType(filename=file) tw@1864: st = os.stat(os.path.join(attach_dir, file).encode(config.charset)) tw@1864: fsize = "%.1f" % (float(st.st_size) / 1024) tw@1864: fmtime = request.user.getFormattedDateTime(st.st_mtime) tw-public@0: baseurl = request.getScriptname() tw-public@0: action = action_name tw-public@0: urlpagename = wikiutil.quoteWikinameURL(pagename) tw@101: urlfile = wikiutil.url_quote_plus(file) tw-public@0: tw-public@0: base, ext = os.path.splitext(file) tw-public@0: get_url = getAttachUrl(pagename, file, request, escaped=1) tw-public@0: parmdict = {'baseurl': baseurl, 'urlpagename': urlpagename, 'action': action, tw-public@0: 'urlfile': urlfile, 'label_del': label_del, R@1608: 'label_move': label_move, tw-public@0: 'base': base, 'label_edit': label_edit, tw-public@0: 'label_view': label_view, alex@80: 'label_unzip': label_unzip, alex@80: 'label_install': label_install, tw-public@0: 'get_url': get_url, 'label_get': label_get, tw@375: 'file': wikiutil.escape(file).replace(' ', '%20'), tw@375: 'fsize': fsize, tw@1864: 'fmtime': fmtime, tw-public@0: 'pagename': pagename} tw-public@0: tw-public@0: del_link = '' tw-public@0: if request.user.may.delete(pagename) and not readonly: tw-public@0: del_link = '%(label_del)s | ' % parmdict R@1608: if request.user.may.delete(pagename) and not readonly: R@1608: move_link = '%(label_move)s | ' % parmdict R@1608: else: R@1608: move_link = '' tw-public@0: if ext == '.draw': tw-public@0: viewlink = '%(label_edit)s' % parmdict tw-public@0: else: tw-public@0: viewlink = '%(label_view)s' % parmdict tw-public@0: rb@2017: if (packages.ZipPackage(request, os.path.join(attach_dir, file).encode(config.charset)).isPackage() and rb@2017: request.user.isSuperUser()): alex@80: viewlink += ' | %(label_install)s' % parmdict tw@889: elif (zipfile.is_zipfile(os.path.join(attach_dir, file).encode(config.charset)) and rb@1985: mt.minor == 'zip' and request.user.may.read(pagename) and request.user.may.delete(pagename) alex@80: and request.user.may.write(pagename)): alex@80: viewlink += ' | %(label_unzip)s' % parmdict alex@80: alex@80: tw-public@0: parmdict['viewlink'] = viewlink tw-public@0: parmdict['del_link'] = del_link R@1608: parmdict['move_link'] = move_link tw@1920: html += ('
  • [%(del_link)s%(move_link)s' tw-public@0: '%(label_get)s | %(viewlink)s]' tw@1864: ' (%(fmtime)s, %(fsize)s KB) attachment:%(file)s
  • ') % parmdict tw@1920: html += "
" tw-public@0: else: tw-public@0: if showheader: tw@1920: html += '

%s

' % (_("No attachments stored for %(pagename)s") % {'pagename': wikiutil.escape(pagename)}) tw-public@0: tw@1920: return html tw-public@0: tw-public@0: tw-public@0: def _get_files(request, pagename): tw-public@0: attach_dir = getAttachDir(request, pagename) tw-public@0: if os.path.isdir(attach_dir): tw@1866: files = [fn.decode(config.charset) for fn in os.listdir(attach_dir)] tw-public@0: files.sort() tw-public@0: return files tw-public@0: return [] tw-public@0: tw-public@0: tw-public@0: def _get_filelist(request, pagename): tw-public@0: return _build_filelist(request, pagename, 1, 0) tw-public@0: alex@280: def _subdir_exception(zf): alex@280: """ alex@280: Checks for the existance of one common subdirectory shared among alex@280: all files in the zip file. If this is the case, returns a dict of alex@280: original names to modified names so that such files can be unpacked alex@280: as the user would expect. alex@280: """ alex@280: alex@280: b = zf.namelist() alex@280: if not '/' in b[0]: alex@280: return False #No directory alex@280: slashoffset = b[0].index('/') alex@280: directory = b[0][:slashoffset] alex@280: for origname in b: alex@280: if origname.rfind('/') != slashoffset or origname[:slashoffset] != directory: alex@280: return False #Multiple directories or different directory alex@280: names = {} alex@280: for origname in b: alex@280: names[origname] = origname[slashoffset+1:] alex@280: return names #Returns dict of {origname: safename} tw-public@0: tw-public@0: def error_msg(pagename, request, msg): tw@1777: Page(request, pagename).send_page(msg=msg) tw-public@0: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Create parts of the Web interface tw-public@0: ############################################################################# tw-public@0: tw-public@0: def send_link_rel(request, pagename): tw-public@0: files = _get_files(request, pagename) tw-public@0: if len(files) > 0 and not htdocs_access(request): tw-public@0: scriptName = request.getScriptname() tw-public@0: pagename_quoted = wikiutil.quoteWikinameURL(pagename) tw-public@0: tw@1986: for fname in files: tw-public@0: url = "%s/%s?action=%s&do=view&target=%s" % ( tw-public@0: scriptName, pagename_quoted, tw@1986: action_name, wikiutil.url_quote_plus(fname)) tw-public@0: tw-public@0: request.write(u'\n' % ( tw@1986: wikiutil.escape(fname), wikiutil.escape(url))) tw-public@0: tw-public@0: tw-public@0: def send_hotdraw(pagename, request): tw-public@0: _ = request.getText tw-public@0: tw-public@0: now = time.time() tw@1318: pubpath = request.cfg.url_prefix_static + "/applets/TWikiDrawPlugin" tw-public@0: basename = request.form['drawing'][0] tw-public@0: drawpath = getAttachUrl(pagename, basename + '.draw', request, escaped=1) tw-public@0: pngpath = getAttachUrl(pagename, basename + '.png', request, escaped=1) tw-public@0: querystr = {'action': 'AttachFile', 'ts': now} tw@102: querystr = wikiutil.escape(wikiutil.makeQueryString(querystr)) tw-public@0: pagelink = '%s/%s?%s' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), querystr) tw-public@0: helplink = Page(request, "HelpOnActions/AttachFile").url(request) tw@623: querystr = {'action': 'AttachFile', 'do': 'savedrawing'} tw@623: querystr = wikiutil.escape(wikiutil.makeQueryString(querystr)) tw@623: savelink = '%s/%s?%s' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), querystr) tw@623: #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted tw-public@0: # request, {'savename': request.form['drawing'][0]+'.draw'} tw-public@0: #savelink = '/cgi-bin/dumpform.bat' tw-public@0: tw-public@0: if htdocs_access(request): tw-public@0: timestamp = '?ts=%s' % now tw-public@0: else: tw-public@0: timestamp = '&ts=%s' % now tw-public@0: tw-public@0: request.write('

' + _("Edit drawing") + '

') tw-public@0: request.write(""" tw-public@0:

tw-public@0: tw-public@0: tw-public@0: tw-public@0: tw-public@0: tw-public@0: tw-public@0: tw-public@0: tw-public@0: NOTE: You need a Java enabled browser to edit the drawing example. tw-public@0: tw-public@0:

""" % { tw-public@0: 'pngpath': pngpath, 'timestamp': timestamp, tw-public@0: 'pubpath': pubpath, 'drawpath': drawpath, tw-public@0: 'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink, tw-public@0: 'basename': basename tw-public@0: }) tw-public@0: tw-public@0: tw-public@0: def send_uploadform(pagename, request): tw-public@0: """ Send the HTML code for the list of already stored attachments and tw-public@0: the file upload form. tw-public@0: """ tw-public@0: _ = request.getText tw-public@0: tw-public@0: if not request.user.may.read(pagename): tw-public@0: request.write('

%s

' % _('You are not allowed to view this page.')) tw-public@0: return tw-public@0: tw@1635: writeable = request.user.may.write(pagename) tw-public@0: tw@1635: # First send out the upload new attachment form on top of everything else. tw@1635: # This avoids usability issues if you have to scroll down a lot to upload tw@1635: # a new file when the page already has lots of attachments: tw@1635: if writeable: tw@1635: request.write('

' + _("New Attachment") + '

' + tw-public@0: _("""An upload will never overwrite an existing file. If there is a name tw-public@0: conflict, you have to rename the file that you want to upload. tw-public@0: Otherwise, if "Rename to" is left blank, the original filename will be used.""") + '

') tw@1635: request.write(""" tw-public@0:
tw-public@0:
tw-public@0:
%(upload_label_file)s
tw-public@0:
tw-public@0:
%(upload_label_rename)s
tw-public@0:
tw@1765:
%(upload_label_overwrite)s
tw@1765:
tw-public@0:
tw-public@0:

tw-public@0: tw-public@0: tw-public@0: tw-public@0:

tw-public@0:
tw-public@0: """ % { tw-public@0: 'baseurl': request.getScriptname(), tw-public@0: 'pagename': wikiutil.quoteWikinameURL(pagename), tw-public@0: 'action_name': action_name, tw-public@0: 'upload_label_file': _('File to upload'), tw-public@0: 'upload_label_rename': _('Rename to'), tw-public@0: 'rename': request.form.get('rename', [''])[0], tw@1765: 'upload_label_overwrite': _('Overwrite existing attachment of same name'), tw@1765: 'overwrite_checked': ('', 'checked')[request.form.get('overwrite', ['0'])[0] == '1'], tw-public@0: 'upload_button': _('Upload'), tw-public@0: }) tw-public@0: tw-public@0: #
%(upload_label_mime)s
tw-public@0: #
tw-public@0: # 'upload_label_mime': _('MIME Type (optional)'), tw-public@0: tw@1635: request.write('

' + _("Attached Files") + '

') tw@1635: request.write(_get_filelist(request, pagename)) tw@1635: tw@1635: if not writeable: tw@1635: request.write('

%s

' % _('You are not allowed to attach a file to this page.')) tw@1635: tw@1635: if writeable and request.form.get('drawing', [None])[0]: tw@1635: send_hotdraw(pagename, request) tw@1635: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Web interface for file upload, viewing and deletion tw-public@0: ############################################################################# tw-public@0: tw-public@0: def execute(pagename, request): tw-public@0: """ Main dispatcher for the 'AttachFile' action. tw-public@0: """ tw-public@0: _ = request.getText tw-public@0: tw-public@0: msg = None tw@2210: do = request.form.get('do') tw@2210: if do is not None: tw@2210: do = do[0] tw-public@0: if action_name in request.cfg.actions_excluded: tw-public@0: msg = _('File attachments are not allowed in this wiki!') tw@1868: elif 'do' not in request.form: tw@623: upload_form(pagename, request) tw@2210: elif do == 'savedrawing': tw-public@0: if request.user.may.write(pagename): tw-public@0: save_drawing(pagename, request) tw@1068: request.emit_http_headers() tw-public@0: request.write("OK") tw-public@0: else: tw-public@0: msg = _('You are not allowed to save a drawing on this page.') tw@2210: elif do == 'upload': tw-public@0: if request.user.may.write(pagename): tw@1868: if 'file' in request.form: tw-public@0: do_upload(pagename, request) tw-public@0: else: tw-public@0: # This might happen when trying to upload file names tw-public@0: # with non-ascii characters on Safari. tw-public@0: msg = _("No file content. Delete non ASCII characters from the file name and try again.") tw-public@0: else: tw-public@0: msg = _('You are not allowed to attach a file to this page.') tw@2210: elif do == 'del': tw-public@0: if request.user.may.delete(pagename): tw-public@0: del_file(pagename, request) tw-public@0: else: tw-public@0: msg = _('You are not allowed to delete attachments on this page.') tw@2210: elif do == 'move': R@1608: if request.user.may.delete(pagename): R@1608: send_moveform(pagename, request) R@1608: else: R@1608: msg = _('You are not allowed to move attachments from this page.') tw@2210: elif do == 'attachment_move': tw@1868: if 'cancel' in request.form: R@1608: msg = _('Move aborted!') R@1608: error_msg(pagename, request, msg) R@1608: return tw@1610: if not wikiutil.checkTicket(request, request.form['ticket'][0]): R@1608: msg = _('Please use the interactive user interface to move attachments!') R@1608: error_msg(pagename, request, msg) R@1608: return R@1608: if request.user.may.delete(pagename): R@1608: attachment_move(pagename, request) R@1608: else: R@1608: msg = _('You are not allowed to move attachments from this page.') tw@2210: elif do == 'get': tw-public@0: if request.user.may.read(pagename): tw-public@0: get_file(pagename, request) tw-public@0: else: tw-public@0: msg = _('You are not allowed to get attachments from this page.') tw@2210: elif do == 'unzip': tw@1920: if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename): alex@80: unzip_file(pagename, request) tw@1920: else: alex@80: msg = _('You are not allowed to unzip attachments of this page.') tw@2210: elif do == 'install': tw@1920: if request.user.isSuperUser(): alex@80: install_package(pagename, request) tw@1920: else: alex@80: msg = _('You are not allowed to install files.') tw@2210: elif do == 'view': tw-public@0: if request.user.may.read(pagename): tw-public@0: view_file(pagename, request) tw-public@0: else: tw-public@0: msg = _('You are not allowed to view attachments of this page.') tw-public@0: else: tw@2286: msg = _('Unsupported upload action: %s') % (wikiutil.escape(do), ) tw-public@0: tw-public@0: if msg: tw-public@0: error_msg(pagename, request, msg) tw-public@0: tw-public@0: def upload_form(pagename, request, msg=''): tw-public@0: _ = request.getText tw-public@0: tw@1068: request.emit_http_headers() tw-public@0: # Use user interface language for this generated page tw-public@0: request.setContentLanguage(request.lang) tw@616: request.theme.send_title(_('Attachments for "%(pagename)s"') % {'pagename': pagename}, pagename=pagename, msg=msg) tw-public@0: request.write('
\n') # start content div tw-public@0: send_uploadform(pagename, request) tw-public@0: request.write('
\n') # end content div tw@616: request.theme.send_footer(pagename) tw@617: request.theme.send_closing_html() tw-public@0: tw-public@0: def do_upload(pagename, request): tw-public@0: _ = request.getText tw-public@0: tw-public@0: # make filename tw-public@0: filename = None tw@1868: if 'file__filename__' in request.form: tw-public@0: filename = request.form['file__filename__'] tw-public@0: rename = None tw@1868: if 'rename' in request.form: tw-public@0: rename = request.form['rename'][0].strip() tw@1765: overwrite = 0 tw@1868: if 'overwrite' in request.form: tw@1765: try: tw@1765: overwrite = int(request.form['overwrite'][0]) tw@1765: except: tw@1765: pass tw-public@0: tw-public@0: # if we use twisted, "rename" field is NOT optional, because we tw-public@0: # can't access the client filename tw-public@0: if rename: tw-public@0: target = rename rb@1891: # clear rename its only once wanted rb@1891: request.form['rename'][0] = u'' tw-public@0: elif filename: tw-public@0: target = filename tw-public@0: else: tw-public@0: error_msg(pagename, request, _("Filename of attachment not specified!")) tw-public@0: return tw-public@0: tw-public@0: # get file content tw-public@0: filecontent = request.form['file'][0] tw-public@0: tw-public@0: # preprocess the filename alex@691: # strip leading drive and path (IE misbehaviour) tw-public@0: if len(target) > 1 and (target[1] == ':' or target[0] == '\\'): # C:.... or \path... or \\server\... tw-public@0: bsindex = target.rfind('\\') tw-public@0: if bsindex >= 0: tw-public@0: target = target[bsindex+1:] tw@889: alex@691: # add the attachment alex@691: try: tw@1791: add_attachment(request, pagename, target, filecontent, overwrite=overwrite) tw-public@0: tw-public@0: bytes = len(filecontent) tw-public@0: msg = _("Attachment '%(target)s' (remote name '%(filename)s')" tw-public@0: " with %(bytes)d bytes saved.") % { tw-public@0: 'target': target, 'filename': filename, 'bytes': bytes} alex@691: except AttachmentAlreadyExists: alex@691: msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % { alex@691: 'target': target, 'filename': filename} tw-public@0: tw-public@0: # return attachment list tw-public@0: upload_form(pagename, request, msg) tw-public@0: tw-public@0: tw-public@0: def save_drawing(pagename, request): tw-public@0: tw-public@0: filename = request.form['filename'][0] tw-public@0: filecontent = request.form['filepath'][0] tw-public@0: tw-public@0: # there should be no difference in filename parsing with or without tw-public@0: # htdocs_access, cause the filename param is used tw-public@0: basepath, basename = os.path.split(filename) tw-public@0: basename, ext = os.path.splitext(basename) tw-public@0: tw-public@0: # get directory, and possibly create it tw-public@0: attach_dir = getAttachDir(request, pagename, create=1) tw-public@0: tw-public@0: if ext == '.draw': tw-public@0: _addLogEntry(request, 'ATTDRW', pagename, basename + ext) tw@889: filecontent = filecontent.replace("\r", "") tw-public@0: tw@1793: savepath = os.path.join(attach_dir, basename + ext) tw@889: if ext == '.map' and not filecontent.strip(): tw-public@0: # delete map file if it is empty tw-public@0: os.unlink(savepath) tw-public@0: else: tw@1006: stream = open(savepath, 'wb') tw-public@0: try: tw@1006: stream.write(filecontent) tw-public@0: finally: tw@1006: stream.close() tw-public@0: tw-public@0: # touch attachment directory to invalidate cache if new map is saved tw-public@0: if ext == '.map': tw-public@0: os.utime(getAttachDir(request, pagename), None) tw-public@0: tw-public@0: def del_file(pagename, request): tw-public@0: _ = request.getText tw-public@0: tw-public@0: filename, fpath = _access_file(pagename, request) tw@1920: if not filename: tw@1920: return # error msg already sent in _access_file tw-public@0: tw-public@0: # delete file tw-public@0: os.remove(fpath) tw-public@0: _addLogEntry(request, 'ATTDEL', pagename, filename) tw-public@0: fpletz@1479: if request.cfg.xapian_search: fpletz@1479: from MoinMoin.search.Xapian import Index fpletz@1479: index = Index(request) fpletz@1479: if index.exists: fpletz@1479: index.remove_item(pagename, filename) fpletz@1479: tw-public@0: upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename}) tw-public@0: R@1608: def move_file(request, pagename, new_pagename, attachment, new_attachment): R@1608: _ = request.getText R@1608: tw@1609: newpage = Page(request, new_pagename) R@1608: if newpage.exists(includeDeleted=1) and request.user.may.write(new_pagename) and request.user.may.delete(pagename): tw@1609: new_attachment_path = os.path.join(getAttachDir(request, new_pagename, R@1608: create=1), new_attachment).encode(config.charset) tw@1609: attachment_path = os.path.join(getAttachDir(request, pagename), R@1608: attachment).encode(config.charset) R@1608: R@1608: if os.path.exists(new_attachment_path): R@1608: upload_form(pagename, request, msg=_("Attachment '%(filename)s' already exists.") % { R@1608: 'filename': new_attachment}) R@1608: return R@1608: R@1608: if new_attachment_path != attachment_path: tw@2286: # move file R@1608: filesys.rename(attachment_path, new_attachment_path) R@1608: _addLogEntry(request, 'ATTDEL', pagename, attachment) R@1608: _addLogEntry(request, 'ATTNEW', new_pagename, new_attachment) R@1608: upload_form(pagename, request, msg=_("Attachment '%(filename)s' moved to %(page)s.") % { R@1608: 'filename': new_attachment, R@1608: 'page': new_pagename}) R@1608: else: R@1608: upload_form(pagename, request, msg=_("Nothing changed")) R@1608: else: tw@1920: upload_form(pagename, request, msg=_("Page %(newpagename)s does not exists or you don't have enough rights.") % { tw@1920: 'newpagename': new_pagename}) R@1608: R@1608: def attachment_move(pagename, request): R@1608: _ = request.getText tw@1868: if 'newpagename' in request.form: R@1608: new_pagename = request.form.get('newpagename')[0] R@1608: else: R@1608: upload_form(pagename, request, msg=_("Move aborted because empty page name")) tw@1868: if 'newattachmentname' in request.form: R@1608: new_attachment = request.form.get('newattachmentname')[0] R@1608: if new_attachment != wikiutil.taintfilename(new_attachment): tw@1662: upload_form(pagename, request, msg=_("Please use a valid filename for attachment '%(filename)s'.") % { R@1608: 'filename': new_attachment}) R@1608: return R@1608: else: R@1608: upload_form(pagename, request, msg=_("Move aborted because empty attachment name")) R@1608: R@1608: attachment = request.form.get('oldattachmentname')[0] R@1608: move_file(request, pagename, new_pagename, attachment, new_attachment) R@1608: R@1608: def send_moveform(pagename, request): R@1608: _ = request.getText R@1608: R@1608: filename, fpath = _access_file(pagename, request) tw@1920: if not filename: tw@1920: return # error msg already sent in _access_file R@1608: R@1608: # move file R@1608: d = {'action': 'AttachFile', R@1608: 'do': 'attachment_move', tw@1610: 'ticket': wikiutil.createTicket(request), R@1608: 'pagename': pagename, R@1608: 'attachment_name': filename, R@1608: 'move': _('Move'), R@1608: 'cancel': _('Cancel'), R@1608: 'newname_label': _("New page name"), R@1608: 'attachment_label': _("New attachment name"), R@1608: } R@1608: formhtml = ''' R@1608:
R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608: R@1608:
R@1608: R@1608:
R@1608: R@1608:
R@1608: R@1608: R@1608: R@1608:
R@1608:
''' % d R@1608: thispage = Page(request, pagename) tw@1777: return thispage.send_page(msg=formhtml) tw-public@0: tw-public@0: def get_file(pagename, request): tw-public@0: import shutil tw-public@0: tw-public@0: filename, fpath = _access_file(pagename, request) tw@801: if not filename: tw@801: return # error msg already sent in _access_file tw-public@0: tw@1115: timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath))) tw@1115: if request.if_modified_since == timestamp: tw@1115: request.emit_http_headers(["Status: 304 Not modified"]) tw@1115: else: tw@1115: mt = wikiutil.MimeType(filename=filename) tw@1548: content_type = mt.content_type() tw@1548: mime_type = mt.mime_type() tw@1548: tw@1548: # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs tw@1548: # There is no solution that is compatible to IE except stripping non-ascii chars tw@1548: filename_enc = filename.encode(config.charset) tw@1548: tw@1548: # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks, tw@1548: # we just let the user store them to disk ('attachment'). tw@1548: # For safe files, we directly show them inline (this also works better for IE). tw@1548: dangerous = mime_type in request.cfg.mimetypes_xss_protect tw@1548: content_dispo = dangerous and 'attachment' or 'inline' tw@1548: tw@1115: request.emit_http_headers([ tw@1548: 'Content-Type: %s' % content_type, rb@1798: 'Last-Modified: %s' % timestamp, tw@1548: 'Content-Length: %d' % os.path.getsize(fpath), tw@1548: 'Content-Disposition: %s; filename="%s"' % (content_dispo, filename_enc), tw@1115: ]) tw-public@0: tw@1115: # send data tw@1115: shutil.copyfileobj(open(fpath, 'rb'), request, 8192) tw-public@0: alex@80: def install_package(pagename, request): alex@80: _ = request.getText alex@80: alex@80: target, targetpath = _access_file(pagename, request) alex@80: if not target: alex@80: return alex@80: alex@80: package = packages.ZipPackage(request, targetpath) alex@80: alex@80: if package.isPackage(): alex@80: if package.installPackage(): tw@889: msg = _("Attachment '%(filename)s' installed.") % {'filename': wikiutil.escape(target)} alex@80: else: tw@889: msg = _("Installation of '%(filename)s' failed.") % {'filename': wikiutil.escape(target)} alex@80: if package.msg != "": alex@80: msg += "
" + wikiutil.escape(package.msg) + "
" alex@80: else: alex@80: msg = _('The file %s is not a MoinMoin package file.' % wikiutil.escape(target)) alex@80: alex@80: upload_form(pagename, request, msg=msg) alex@80: alex@80: def unzip_file(pagename, request): alex@80: _ = request.getText tw@1419: valid_pathname = lambda name: ('/' not in name) and ('\\' not in name) alex@80: alex@80: filename, fpath = _access_file(pagename, request) tw@116: if not filename: tw@116: return # error msg already sent in _access_file alex@80: alex@113: single_file_size = request.cfg.unzip_single_file_size alex@113: attachments_file_space = request.cfg.unzip_attachments_space tw@117: attachments_file_count = request.cfg.unzip_attachments_count tw@116: alex@80: files = _get_files(request, pagename) alex@80: alex@80: msg = "" alex@80: if files: alex@80: fsize = 0.0 tw@117: fcount = 0 tw@1920: for f in files: tw@1920: fsize += float(size(request, pagename, f)) tw@117: fcount += 1 alex@80: alex@80: available_attachments_file_space = attachments_file_space - fsize tw@117: available_attachments_file_count = attachments_file_count - fcount tw@889: alex@80: if zipfile.is_zipfile(fpath): alex@80: zf = zipfile.ZipFile(fpath) alex@80: sum_size_over_all_valid_files = 0.0 tw@116: count_valid_files = 0 alex@280: namelist = _subdir_exception(zf) alex@280: if not namelist: #if it's not handled by _subdir_exception() alex@280: #Convert normal zf.namelist() to {origname:finalname} dict alex@280: namelist = {} alex@280: for name in zf.namelist(): alex@280: namelist[name] = name alex@280: for (origname, finalname) in namelist.iteritems(): alex@280: if valid_pathname(finalname): alex@280: sum_size_over_all_valid_files += zf.getinfo(origname).file_size tw@116: count_valid_files += 1 alex@80: tw@117: if sum_size_over_all_valid_files > available_attachments_file_space: tw@889: msg = _("Attachment '%(filename)s' could not be unzipped because" tw@889: " the resulting files would be too large (%(space)d kB" tw@889: " missing).") % { tw@889: 'filename': filename, tw@889: 'space': (sum_size_over_all_valid_files - tw@889: available_attachments_file_space) / 1000 } tw@117: elif count_valid_files > available_attachments_file_count: tw@889: msg = _("Attachment '%(filename)s' could not be unzipped because" tw@889: " the resulting files would be too many (%(count)d " tw@889: "missing).") % { tw@889: 'filename': filename, tw@889: 'count': (count_valid_files - tw@889: available_attachments_file_count) } tw@117: else: alex@80: valid_name = False alex@373: for (origname, finalname) in namelist.iteritems(): alex@280: if valid_pathname(finalname): alex@280: zi = zf.getinfo(origname) alex@80: if zi.file_size < single_file_size: alex@280: new_file = getFilename(request, pagename, finalname) alex@80: if not os.path.exists(new_file): alex@80: outfile = open(new_file, 'wb') alex@280: outfile.write(zf.read(origname)) alex@80: outfile.close() alex@80: # it's not allowed to zip a zip file so it is dropped alex@80: if zipfile.is_zipfile(new_file): alex@80: os.unlink(new_file) alex@80: else: alex@80: valid_name = True alex@280: _addLogEntry(request, 'ATTNEW', pagename, finalname) alex@80: alex@80: if valid_name: tw@889: msg = _("Attachment '%(filename)s' unzipped.") % {'filename': filename} alex@80: else: tw@889: msg = _("Attachment '%(filename)s' not unzipped because the " tw@889: "files are too big, .zip files only, exist already or " tw@889: "reside in folders.") % {'filename': filename} alex@80: else: tw@1791: msg = _('The file %(filename)s is not a .zip file.' % {'filename': filename}) alex@80: alex@80: upload_form(pagename, request, msg=wikiutil.escape(msg)) alex@80: tw-public@0: def send_viewfile(pagename, request): tw-public@0: _ = request.getText tw-public@0: tw-public@0: filename, fpath = _access_file(pagename, request) tw@1920: if not filename: tw@1920: return tw-public@0: tw-public@0: request.write('

' + _("Attachment '%(filename)s'") % {'filename': filename} + '

') tw-public@0: tw@801: mt = wikiutil.MimeType(filename=filename) tw@801: if mt.major == 'image': tw@801: timestamp = htdocs_access(request) and "?%s" % time.time() or '' tw@801: request.write('%s' % ( tw@801: getAttachUrl(pagename, filename, request, escaped=1), timestamp, wikiutil.escape(filename, 1))) tw@801: return tw@801: elif mt.major == 'text': rb@1989: ext = os.path.splitext(filename)[1] rb@1989: Parser = wikiutil.getParserForExtension(request.cfg, ext) rb@1989: if Parser is not None: rb@1989: try: rb@1989: content = file(fpath, 'r').read() rb@1989: content = wikiutil.decodeUnknownInput(content) rb@1989: colorizer = Parser(content, request, filename=filename) rb@1989: colorizer.format(request.formatter) rb@1989: return rb@1989: except IOError: rb@1989: pass rb@1989: rb@1989: tw@1833: request.write(request.formatter.preformatted(1)) tw@2286: # If we have text but no colorizing parser we try to decode file contents. tw@801: content = open(fpath, 'r').read() tw@801: content = wikiutil.decodeUnknownInput(content) tw@801: content = wikiutil.escape(content) tw@1833: request.write(request.formatter.text(content)) tw@1833: request.write(request.formatter.preformatted(0)) tw@801: return tw-public@0: alex@80: package = packages.ZipPackage(request, fpath) rb@2015: if package.isPackage(): tw@889: request.write("
%s\n%s
" % (_("Package script:"), wikiutil.escape(package.getScript()))) alex@80: return alex@80: rb@1985: if zipfile.is_zipfile(fpath) and mt.minor == 'zip': alex@80: zf = zipfile.ZipFile(fpath, mode='r') alex@80: request.write("
%-46s %19s %12s\n" % (_("File Name"), _("Modified")+" "*5, _("Size")))
alex@80:         for zinfo in zf.filelist:
alex@80:             date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
alex@80:             request.write(wikiutil.escape("%-46s %s %12d\n" % (zinfo.filename, date, zinfo.file_size)))
alex@80:         request.write("
") alex@80: return alex@80: rb@1952: # reuse class tmp from Despam to define macro rb@1952: from MoinMoin.action.Despam import tmp rb@1952: macro = tmp() rb@1952: macro.request = request rb@1952: macro.formatter = request.html_formatter rb@1952: tw@2286: # use EmbedObject to view valid mime types rb@1952: from MoinMoin.macro.EmbedObject import EmbedObject rb@1967: if mt is None: rb@1952: request.write('

' + _("Unknown file type, cannot display this attachment inline.") + '

') rb@1952: request.write('For using an external program follow this link %s' % ( rb@1952: getAttachUrl(pagename, filename, request, escaped=1), wikiutil.escape(filename))) rb@1952: return rb@1952: rb@1952: url = getAttachUrl(pagename, filename, request, escaped=1) rb@1952: rb@1967: request.write(request.formatter.rawHTML(EmbedObject.embed(EmbedObject(macro, wikiutil.escape(filename)), mt, url))) rb@1952: return tw-public@0: tw-public@0: tw-public@0: def view_file(pagename, request): tw-public@0: _ = request.getText tw-public@0: tw-public@0: filename, fpath = _access_file(pagename, request) tw@1920: if not filename: tw@1920: return tw-public@0: tw-public@0: # send header & title tw@1068: request.emit_http_headers() tw-public@0: # Use user interface language for this generated page tw-public@0: request.setContentLanguage(request.lang) tw-public@0: title = _('attachment:%(filename)s of %(pagename)s', formatted=True) % { tw-public@0: 'filename': filename, 'pagename': pagename} tw@616: request.theme.send_title(title, pagename=pagename) tw-public@0: tw-public@0: # send body tw@1833: request.write(request.formatter.startContent()) tw-public@0: send_viewfile(pagename, request) tw-public@0: send_uploadform(pagename, request) tw@1833: request.write(request.formatter.endContent()) tw-public@0: tw@616: request.theme.send_footer(pagename) tw@617: request.theme.send_closing_html() tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### File attachment administration tw-public@0: ############################################################################# tw-public@0: tw-public@0: def do_admin_browser(request): tw-public@0: """ Browser for SystemAdmin macro. tw-public@0: """ tw-public@0: from MoinMoin.util.dataset import TupleDataset, Column tw-public@0: _ = request.getText tw-public@0: tw-public@0: data = TupleDataset() tw-public@0: data.columns = [ tw-public@0: Column('page', label=('Page')), tw-public@0: Column('file', label=('Filename')), tw@889: Column('size', label=_('Size'), align='right'), tw-public@0: #Column('action', label=_('Action')), tw-public@0: ] tw-public@0: tw-public@0: # iterate over pages that might have attachments tw-public@0: pages = request.rootpage.getPageList() tw-public@0: for pagename in pages: tw-public@0: # check for attachments directory tw-public@0: page_dir = getAttachDir(request, pagename) tw-public@0: if os.path.isdir(page_dir): tw-public@0: # iterate over files of the page tw-public@0: files = os.listdir(page_dir) tw-public@0: for filename in files: tw-public@0: filepath = os.path.join(page_dir, filename) tw-public@0: data.addRow(( tw-public@0: Page(request, pagename).link_to(request, querystr="action=AttachFile"), tw-public@0: wikiutil.escape(filename.decode(config.charset)), tw-public@0: os.path.getsize(filepath), tw-public@0: # '', tw-public@0: )) tw-public@0: tw-public@0: if data: tw-public@0: from MoinMoin.widget.browser import DataBrowserWidget tw-public@0: tw-public@0: browser = DataBrowserWidget(request) tw-public@0: browser.setData(data) tw-public@0: return browser.toHTML() tw-public@0: tw-public@0: return '' tw-public@0: