Attachment 'AttachFile_base1.5.8_modified.py'

Download

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - AttachFile action
   4 
   5     This action lets a page have multiple attachment files.
   6     It creates a folder <data>/pages/<pagename>/attachments
   7     and keeps everything in there.
   8 
   9     Form values: action=Attachment
  10     1. with no 'do' key: returns file upload form
  11     2. do=attach: accept file upload and saves the file in
  12        ../attachment/pagename/
  13     3. /pagename/fname?action=Attachment&do=get[&mimetype=type]:
  14        return contents of the attachment file with the name fname.
  15     4. /pathname/fname, do=view[&mimetype=type]:create a page
  16        to view the content of the file
  17 
  18     To insert an attachment into the page, use the "attachment:" pseudo
  19     schema.
  20 
  21     @copyright: 2001 by Ken Sugino (sugino@mediaone.net)
  22     @copyright: 2001-2004 by Jürgen Hermann <jh@web.de>
  23     @copyright: 2005 MoinMoin:ReimarBauer
  24     @copyright: 2005 MoinMoin:AlexanderSchremmer
  25     @copyright: 2005 DiegoOngaro at ETSZONE (diego@etszone.com)
  26     @copyright: 2006 MoinMoin:ReimarBauer
  27     @license: GNU GPL, see COPYING for details.
  28 """
  29 
  30 import os, mimetypes, time, zipfile
  31 from MoinMoin import config, user, util, wikiutil, packages
  32 from MoinMoin.Page import Page
  33 from MoinMoin.util import MoinMoinNoFooter, filesys, timefuncs
  34 import sys
  35 action_name = __name__.split('.')[-1]
  36 file_charset = sys.getfilesystemencoding()
  37 def htdocs_access(request):
  38     return isinstance(request.cfg.attachments, type({}))
  39 
  40 
  41 #############################################################################
  42 ### External interface - these are called from the core code
  43 #############################################################################
  44 
  45 def getBasePath(request):
  46     """ Get base path where page dirs for attachments are stored.
  47     """
  48     if htdocs_access(request):
  49         return request.cfg.attachments['dir']
  50     else:
  51         return request.rootpage.getPagePath('pages')
  52 
  53 
  54 def getAttachDir(request, pagename, create=0):
  55     """ Get directory where attachments for page `pagename` are stored.
  56     """
  57     if htdocs_access(request):
  58         # direct file access via webserver, from public htdocs area
  59         pagename = wikiutil.quoteWikinameFS(pagename)
  60         attach_dir = os.path.join(request.cfg.attachments['dir'], pagename, "attachments")
  61         if create and not os.path.isdir(attach_dir):
  62             filesys.makeDirs(attach_dir)
  63     else:
  64         # send file via CGI, from page storage area
  65         attach_dir = Page(request, pagename).getPagePath("attachments", check_create=create)
  66 
  67     return attach_dir
  68 
  69 def absoluteName(url, pagename):
  70     """ Get (pagename, filename) of an attachment: link
  71         @param url: PageName/filename.ext or filename.ext (unicode)
  72         @param pagename: name of the currently processed page (unicode)
  73         @rtype: tuple of unicode
  74         @return: PageName, filename.ext
  75     """
  76     pieces = url.split(u'/')
  77     if len(pieces) == 1:
  78         return pagename, pieces[0]
  79     else:
  80         return u"/".join(pieces[:-1]), pieces[-1]
  81 
  82 def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get'):
  83     """ Get URL that points to attachment `filename` of page `pagename`.
  84 
  85         If 'addts' is true, a timestamp with the file's modification time
  86         is added, so that browsers reload a changed file.
  87     """
  88     if htdocs_access(request):
  89         # direct file access via webserver
  90         timestamp = ''
  91         if addts:
  92             try:
  93                 timestamp = '?ts=%s' % os.path.getmtime(
  94                     getFilename(request, pagename, filename))
  95             except IOError:
  96                 pass
  97 
  98         url = "%s/%s/attachments/%s%s" % (
  99             request.cfg.attachments['url'], wikiutil.quoteWikinameFS(pagename),
 100             wikiutil.url_quote(filename), timestamp)
 101     else:
 102         # send file via CGI
 103         if do not in ['get', 'view']:
 104             do = 'get'
 105 
 106         url = "%s/%s?action=%s&do=%s&target=%s" % (
 107             request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
 108             action_name, do, wikiutil.url_quote_plus(filename))
 109     if escaped:
 110         url = wikiutil.escape(url)
 111     return url
 112 
 113 def getIndicator(request, pagename):
 114     """ Get an attachment indicator for a page (linked clip image) or
 115         an empty string if not attachments exist.
 116     """
 117     _ = request.getText
 118     attach_dir = getAttachDir(request, pagename)
 119     if not os.path.exists(attach_dir): return ''
 120 
 121     files = os.listdir(attach_dir)
 122     if not files: return ''
 123 
 124     attach_count = _('[%d attachments]') % len(files)
 125     attach_icon = request.theme.make_icon('attach', vars={ 'attach_count': attach_count })
 126     attach_link = wikiutil.link_tag(request,
 127         "%s?action=AttachFile" % wikiutil.quoteWikinameURL(pagename),
 128         attach_icon)
 129 
 130     return attach_link
 131 
 132 
 133 def getFilename(request, pagename, filename):
 134     """ make complete pathfilename of file "name" attached to some page "pagename"
 135         @param request: request object
 136         @param pagename: name of page where the file is attached to (unicode)
 137         @param filename: filename of attached file (unicode)
 138         @rtype: string (in config.charset encoding)
 139         @return: complete path/filename of attached file
 140     """
 141     return os.path.join(getAttachDir(request, pagename), filename).encode(file_charset)
 142 
 143 
 144 def info(pagename, request):
 145     """ Generate snippet with info on the attachment for page `pagename`.
 146     """
 147     _ = request.getText
 148 
 149     attach_dir = getAttachDir(request, pagename)
 150     files = []
 151     if os.path.isdir(attach_dir):
 152         files = os.listdir(attach_dir)
 153     page = Page(request, pagename)
 154     # TODO: remove escape=0 in 2.0
 155     link = page.url(request, {'action': 'AttachFile'}, escape=0)
 156     attach_info = _('There are <a href="%(link)s">%(count)s attachment(s)</a> stored for this page.', formatted=False) % {
 157         'count': len(files),
 158         'link': wikiutil.escape(link)
 159         }
 160     return "\n<p>\n%s\n</p>\n" % attach_info
 161 
 162 
 163 #############################################################################
 164 ### Internal helpers
 165 #############################################################################
 166 
 167 def _addLogEntry(request, action, pagename, filename):
 168     """ Add an entry to the edit log on uploads and deletes.
 169 
 170         `action` should be "ATTNEW" or "ATTDEL"
 171     """
 172     from MoinMoin.logfile import editlog
 173     t = wikiutil.timestamp2version(time.time())
 174     fname = wikiutil.url_quote(filename, want_unicode=True)
 175 
 176     # TODO: for now we simply write 2 logs, maybe better use some multilog stuff
 177     # Write to global log
 178     log = editlog.EditLog(request)
 179     log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)
 180 
 181     # Write to local log
 182     log = editlog.EditLog(request, rootpagename=pagename)
 183     log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)
 184 
 185 
 186 def _access_file(pagename, request):
 187     """ Check form parameter `target` and return a tuple of
 188         `(filename, filepath)` for an existing attachment.
 189 
 190         Return `(None, None)` if an error occurs.
 191     """
 192     _ = request.getText
 193 
 194     error = None
 195     if not request.form.get('target', [''])[0]:
 196         error = _("Filename of attachment not specified!")
 197     else:
 198         filename = wikiutil.taintfilename(request.form['target'][0])
 199         fpath = getFilename(request, pagename, filename)
 200 
 201         if os.path.isfile(fpath):
 202             return (filename, fpath)
 203         error = _("Attachment '%(filename)s' does not exist!") % {'filename': filename}
 204 
 205     error_msg(pagename, request, error)
 206     return (None, None)
 207 
 208 
 209 def _build_filelist(request, pagename, showheader, readonly, mime_type='*'):
 210     _ = request.getText
 211 
 212     # access directory
 213     attach_dir = getAttachDir(request, pagename)
 214     files = _get_files(request, pagename)
 215 
 216     if mime_type != '*':
 217         files = [fname for fname in files if mime_type == mimetypes.guess_type(fname)[0]]
 218 
 219     str = ""
 220     if files:
 221         if showheader:
 222             str = str + _(
 223                 "To refer to attachments on a page, use '''{{{attachment:filename}}}''', \n"
 224                 "as shown below in the list of files. \n"
 225                 "Do '''NOT''' use the URL of the {{{[get]}}} link, \n"
 226                 "since this is subject to change and can break easily."
 227             )
 228         str = str + "<ul>"
 229 
 230         label_del = _("del")
 231         label_move = _("move")
 232         label_get = _("get")
 233         label_edit = _("edit")
 234         label_view = _("view")
 235         label_unzip = _("unzip")
 236         label_install = _("install")
 237 
 238         for file in files:
 239             fsize = float(os.stat(os.path.join(attach_dir,file).encode(file_charset))[6]) # in byte
 240             fsize = "%.1f" % (fsize / 1024)
 241             baseurl = request.getScriptname()
 242             action = action_name
 243             urlpagename = wikiutil.quoteWikinameURL(pagename)
 244             urlfile = wikiutil.url_quote_plus(file)
 245 
 246             base, ext = os.path.splitext(file)
 247             get_url = getAttachUrl(pagename, file, request, escaped=1)
 248             parmdict = {'baseurl': baseurl, 'urlpagename': urlpagename, 'action': action,
 249                         'urlfile': urlfile, 'label_del': label_del,
 250                         'label_move': label_move,
 251                         'base': base, 'label_edit': label_edit,
 252                         'label_view': label_view,
 253                         'label_unzip': label_unzip,
 254                         'label_install': label_install,
 255                         'get_url': get_url, 'label_get': label_get,
 256                         'file': wikiutil.escape(file).replace(' ', '%20'),
 257                         'fsize': fsize,
 258                         'pagename': pagename}
 259 
 260             del_link = ''
 261             if request.user.may.delete(pagename) and not readonly:
 262                 del_link = '<a href="%(baseurl)s/%(urlpagename)s' \
 263                     '?action=%(action)s&amp;do=del&amp;target=%(urlfile)s">%(label_del)s</a>&nbsp;| ' % parmdict
 264             if request.user.may.delete(pagename) and not readonly:
 265                 move_link = '<a href="%(baseurl)s/%(urlpagename)s' \
 266                     '?action=%(action)s&amp;do=move&amp;target=%(urlfile)s">%(label_move)s</a>&nbsp;| ' % parmdict
 267             else:
 268                 move_link = ''
 269             if ext == '.draw':
 270                 viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;drawing=%(base)s">%(label_edit)s</a>' % parmdict
 271             else:
 272                 viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=view&amp;target=%(urlfile)s">%(label_view)s</a>' % parmdict
 273 
 274             if (packages.ZipPackage(request, os.path.join(attach_dir, file).encode(file_charset)).isPackage() and
 275                 request.user.isSuperUser()):
 276                 viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=install&amp;target=%(urlfile)s">%(label_install)s</a>' % parmdict
 277             elif (zipfile.is_zipfile(os.path.join(attach_dir,file).encode(file_charset)) and
 278                 request.user.may.read(pagename) and request.user.may.delete(pagename)
 279                 and request.user.may.write(pagename)):
 280                 viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=unzip&amp;target=%(urlfile)s">%(label_unzip)s</a>' % parmdict
 281 
 282 
 283             parmdict['viewlink'] = viewlink
 284             parmdict['del_link'] = del_link
 285             parmdict['move_link'] = move_link
 286             str = str + ('<li>[%(del_link)s%(move_link)s'
 287                 '<a href="%(get_url)s">%(label_get)s</a>&nbsp;| %(viewlink)s]'
 288                 ' (%(fsize)s KB) attachment:<strong>%(file)s</strong></li>') % parmdict
 289         str = str + "</ul>"
 290     else:
 291         if showheader:
 292             str = '%s<p>%s</p>' % (str, _("No attachments stored for %(pagename)s") % {'pagename': wikiutil.escape(pagename)})
 293 
 294     return str
 295 
 296 
 297 def _get_files(request, pagename):
 298     attach_dir = getAttachDir(request, pagename)
 299     if os.path.isdir(attach_dir):
 300         files = map(lambda a: a.decode(file_charset), os.listdir(attach_dir))
 301         files.sort()
 302         return files
 303     return []
 304 
 305 
 306 def _get_filelist(request, pagename):
 307     return _build_filelist(request, pagename, 1, 0)
 308 
 309 def _subdir_exception(zf):
 310     """
 311     Checks for the existance of one common subdirectory shared among
 312     all files in the zip file. If this is the case, returns a dict of
 313     original names to modified names so that such files can be unpacked
 314     as the user would expect.
 315     """
 316 
 317     b = zf.namelist()
 318     if not '/' in b[0]:
 319         return False #No directory
 320     slashoffset = b[0].index('/')
 321     directory = b[0][:slashoffset]
 322     for origname in b:
 323         if origname.rfind('/') != slashoffset or origname[:slashoffset] != directory:
 324             return False #Multiple directories or different directory
 325     names = {}
 326     for origname in b:
 327         names[origname] = origname[slashoffset+1:]
 328     return names #Returns dict of {origname: safename}
 329 
 330 def error_msg(pagename, request, msg):
 331     Page(request, pagename).send_page(request, msg=msg)
 332 
 333 
 334 #############################################################################
 335 ### Create parts of the Web interface
 336 #############################################################################
 337 
 338 def send_link_rel(request, pagename):
 339     files = _get_files(request, pagename)
 340     if len(files) > 0 and not htdocs_access(request):
 341         scriptName = request.getScriptname()
 342         pagename_quoted = wikiutil.quoteWikinameURL(pagename)
 343 
 344         for file in files:
 345             url = "%s/%s?action=%s&do=view&target=%s" % (
 346                 scriptName, pagename_quoted,
 347                 action_name, wikiutil.url_quote_plus(file))
 348 
 349             request.write(u'<link rel="Appendix" title="%s" href="%s">\n' % (
 350                 wikiutil.escape(file), wikiutil.escape(url)))
 351 
 352 
 353 def send_hotdraw(pagename, request):
 354     _ = request.getText
 355 
 356     now = time.time()
 357     pubpath = request.cfg.url_prefix + "/applets/TWikiDrawPlugin"
 358     basename = request.form['drawing'][0]
 359     drawpath = getAttachUrl(pagename, basename + '.draw', request, escaped=1)
 360     pngpath = getAttachUrl(pagename, basename + '.png', request, escaped=1)
 361     querystr = {'action': 'AttachFile', 'ts': now}
 362     querystr = wikiutil.escape(wikiutil.makeQueryString(querystr))
 363     pagelink = '%s/%s?%s' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), querystr)
 364     helplink = Page(request, "HelpOnActions/AttachFile").url(request)
 365     savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
 366                                            # request, {'savename': request.form['drawing'][0]+'.draw'}
 367     #savelink = '/cgi-bin/dumpform.bat'
 368 
 369     if htdocs_access(request):
 370         timestamp = '?ts=%s' % now
 371     else:
 372         timestamp = '&amp;ts=%s' % now
 373 
 374     request.write('<h2>' + _("Edit drawing") + '</h2>')
 375     request.write("""
 376 <p>
 377 <img src="%(pngpath)s%(timestamp)s">
 378 <applet code="CH.ifa.draw.twiki.TWikiDraw.class"
 379         archive="%(pubpath)s/twikidraw.jar" width="640" height="480">
 380 <param name="drawpath" value="%(drawpath)s">
 381 <param name="pngpath"  value="%(pngpath)s">
 382 <param name="savepath" value="%(savelink)s">
 383 <param name="basename" value="%(basename)s">
 384 <param name="viewpath" value="%(pagelink)s">
 385 <param name="helppath" value="%(helplink)s">
 386 <strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example.
 387 </applet>
 388 </p>""" % {
 389     'pngpath': pngpath, 'timestamp': timestamp,
 390     'pubpath': pubpath, 'drawpath': drawpath,
 391     'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink,
 392     'basename': basename
 393 })
 394 
 395 
 396 def send_uploadform(pagename, request):
 397     """ Send the HTML code for the list of already stored attachments and
 398         the file upload form.
 399     """
 400     _ = request.getText
 401 
 402     if not request.user.may.read(pagename):
 403         request.write('<p>%s</p>' % _('You are not allowed to view this page.'))
 404         return
 405 
 406     request.write('<h2>' + _("Attached Files") + '</h2>')
 407     request.write(_get_filelist(request, pagename))
 408 
 409     if not request.user.may.write(pagename):
 410         request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.'))
 411         return
 412 
 413     if request.form.get('drawing', [None])[0]:
 414         send_hotdraw(pagename, request)
 415         return
 416 
 417     request.write('<h2>' + _("New Attachment") + '</h2><p>' +
 418 _("""An upload will never overwrite an existing file. If there is a name
 419 conflict, you have to rename the file that you want to upload.
 420 Otherwise, if "Rename to" is left blank, the original filename will be used.""") + '</p>')
 421     request.write("""
 422 <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 423 <dl>
 424 <dt>%(upload_label_file)s</dt>
 425 <dd><input type="file" name="file" size="50"></dd>
 426 <dt>%(upload_label_rename)s</dt>
 427 <dd><input type="text" name="rename" size="50" value="%(rename)s"></dd>
 428 <dt>%(upload_label_overwrite)s</dt>
 429 <dd><input type="checkbox" name="overwrite" value="1" %(overwrite_checked)s></dd>
 430 </dl>
 431 <p>
 432 <input type="hidden" name="action" value="%(action_name)s">
 433 <input type="hidden" name="do" value="upload">
 434 <input type="submit" value="%(upload_button)s">
 435 </p>
 436 </form>
 437 """ % {
 438     'baseurl': request.getScriptname(),
 439     'pagename': wikiutil.quoteWikinameURL(pagename),
 440     'action_name': action_name,
 441     'upload_label_file': _('File to upload'),
 442     'upload_label_rename': _('Rename to'),
 443     'rename': request.form.get('rename', [''])[0],
 444     'upload_label_overwrite': _('Overwrite existing attachment of same name'),
 445     'overwrite_checked': ('', 'checked')[request.form.get('overwrite', ['0'])[0] == '1'],
 446     'upload_button': _('Upload'),
 447 })
 448 
 449 #<dt>%(upload_label_mime)s</dt>
 450 #<dd><input type="text" name="mime" size="50"></dd>
 451 #    'upload_label_mime': _('MIME Type (optional)'),
 452 
 453 
 454 #############################################################################
 455 ### Web interface for file upload, viewing and deletion
 456 #############################################################################
 457 
 458 def execute(pagename, request):
 459     """ Main dispatcher for the 'AttachFile' action.
 460     """
 461     _ = request.getText
 462 
 463     msg = None
 464     do = request.form.get('do')
 465     if do is not None:
 466         do = do[0]
 467     if action_name in request.cfg.actions_excluded:
 468         msg = _('File attachments are not allowed in this wiki!')
 469     elif request.form.has_key('filepath'):
 470         if request.user.may.write(pagename):
 471             save_drawing(pagename, request)
 472             request.http_headers()
 473             request.write("OK")
 474         else:
 475             msg = _('You are not allowed to save a drawing on this page.')
 476     elif do is None:
 477         upload_form(pagename, request)
 478     elif do == 'upload':
 479         if request.user.may.write(pagename):
 480             if request.form.has_key('file'):
 481                 do_upload(pagename, request)
 482             else:
 483                 # This might happen when trying to upload file names
 484                 # with non-ascii characters on Safari.
 485                 msg = _("No file content. Delete non ASCII characters from the file name and try again.")
 486         else:
 487             msg = _('You are not allowed to attach a file to this page.')
 488     elif do == 'del':
 489         if request.user.may.delete(pagename):
 490             del_file(pagename, request)
 491         else:
 492             msg = _('You are not allowed to delete attachments on this page.')
 493     elif do == 'move':
 494         if request.user.may.delete(pagename):
 495             send_moveform(pagename, request)
 496         else:
 497             msg = _('You are not allowed to move attachments from this page.')
 498     elif do == 'attachment_move':
 499         if request.form.has_key('cancel'):
 500             msg = _('Move aborted!')
 501             error_msg(pagename, request, msg)
 502             return
 503         if not wikiutil.checkTicket(request, request.form['ticket'][0]):
 504             msg = _('Please use the interactive user interface to move attachments!')
 505             error_msg(pagename, request, msg)
 506             return
 507         if request.user.may.delete(pagename):
 508             attachment_move(pagename, request)
 509         else:
 510             msg = _('You are not allowed to move attachments from this page.')
 511     elif do == 'get':
 512         if request.user.may.read(pagename):
 513             get_file(pagename, request)
 514         else:
 515             msg = _('You are not allowed to get attachments from this page.')
 516     elif do == 'unzip':
 517          if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename):
 518             unzip_file(pagename, request)
 519          else:
 520             msg = _('You are not allowed to unzip attachments of this page.')
 521     elif do == 'install':
 522          if request.user.isSuperUser():
 523             install_package(pagename, request)
 524          else:
 525             msg = _('You are not allowed to install files.')
 526     elif do == 'view':
 527         if request.user.may.read(pagename):
 528             view_file(pagename, request)
 529         else:
 530             msg = _('You are not allowed to view attachments of this page.')
 531     else:
 532         msg = _('Unsupported upload action: %s') % (wikiutil.escape(do),)
 533 
 534     if msg:
 535         error_msg(pagename, request, msg)
 536 
 537 
 538 def upload_form(pagename, request, msg=''):
 539     _ = request.getText
 540 
 541     request.http_headers()
 542     # Use user interface language for this generated page
 543     request.setContentLanguage(request.lang)
 544     wikiutil.send_title(request, _('Attachments for "%(pagename)s"') % {'pagename': pagename}, pagename=pagename, msg=msg)
 545     request.write('<div id="content">\n') # start content div
 546     send_uploadform(pagename, request)
 547     request.write('</div>\n') # end content div
 548     wikiutil.send_footer(request, pagename)
 549 
 550 
 551 def do_upload(pagename, request):
 552     _ = request.getText
 553     # make filename
 554     filename = None
 555     if request.form.has_key('file__filename__'):
 556         filename = request.form['file__filename__']
 557     rename = None
 558     if request.form.has_key('rename'):
 559         rename = request.form['rename'][0].strip()
 560         
 561     overwrite = 0
 562     if request.form.has_key('overwrite'):
 563         try:
 564             overwrite = int(request.form['overwrite'][0])
 565         except:
 566             pass
 567 
 568     # if we use twisted, "rename" field is NOT optional, because we
 569     # can't access the client filename
 570     if rename:
 571         target = rename
 572     elif filename:
 573         target = filename
 574     else:
 575         error_msg(pagename, request, _("Filename of attachment not specified!"))
 576         return
 577 
 578     # get file content
 579     filecontent = request.form['file'][0]
 580 
 581     # preprocess the filename
 582     # 1. strip leading drive and path (IE misbehaviour)
 583     if len(target) > 1 and (target[1] == ':' or target[0] == '\\'): # C:.... or \path... or \\server\...
 584         bsindex = target.rfind('\\')
 585         if bsindex >= 0:
 586             target = target[bsindex+1:]
 587         
 588     # 2. replace illegal chars
 589     target = wikiutil.taintfilename(target)
 590 
 591     # set mimetype from extension, or from given mimetype
 592     #type, encoding = mimetypes.guess_type(target)
 593     #if not type:
 594     #    ext = None
 595     #    if request.form.has_key('mime'):
 596     #        ext = mimetypes.guess_extension(request.form['mime'][0])
 597     #    if not ext:
 598     #        type, encoding = mimetypes.guess_type(filename)
 599     #        if type:
 600     #            ext = mimetypes.guess_extension(type)
 601     #        else:
 602     #            ext = ''
 603     #    target = target + ext
 604 
 605     # get directory, and possibly create it
 606     attach_dir = getAttachDir(request, pagename, create=1)
 607     # save file
 608     import sys
 609     fpath = os.path.join(attach_dir, target).encode(file_charset)
 610     exists = os.path.exists(fpath)
 611     if exists and not overwrite:
 612         msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
 613             'target': target, 'filename': filename}
 614     else:
 615         if exists:
 616             try:
 617                 os.remove(fpath)
 618             except:
 619                 pass
 620         stream = open(fpath, 'wb')
 621         try:
 622             stream.write(filecontent)
 623         finally:
 624             stream.close()
 625 
 626         bytes = len(filecontent)
 627         msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
 628                 " with %(bytes)d bytes saved.") % {
 629                 'target': target, 'filename': filename, 'bytes': bytes}
 630         _addLogEntry(request, 'ATTNEW', pagename, target)
 631 
 632     # return attachment list
 633     upload_form(pagename, request, msg)
 634 
 635 
 636 def save_drawing(pagename, request):
 637 
 638     filename = request.form['filename'][0]
 639     filecontent = request.form['filepath'][0]
 640 
 641     # there should be no difference in filename parsing with or without
 642     # htdocs_access, cause the filename param is used
 643     basepath, basename = os.path.split(filename)
 644     basename, ext = os.path.splitext(basename)
 645 
 646     # get directory, and possibly create it
 647     attach_dir = getAttachDir(request, pagename, create=1)
 648 
 649     if ext == '.draw':
 650         _addLogEntry(request, 'ATTDRW', pagename, basename + ext)
 651         filecontent = filecontent.replace("\r","")
 652 
 653     savepath = os.path.join(getAttachDir(request, pagename), basename + ext)
 654     if ext == '.map' and filecontent.strip()=='':
 655         # delete map file if it is empty
 656         os.unlink(savepath)
 657     else:
 658         stream = open(savepath, 'wb')
 659         try:
 660             stream.write(filecontent)
 661         finally:
 662             stream.close()
 663 
 664     # touch attachment directory to invalidate cache if new map is saved
 665     if ext == '.map':
 666         os.utime(getAttachDir(request, pagename), None)
 667 
 668 def del_file(pagename, request):
 669     _ = request.getText
 670 
 671     filename, fpath = _access_file(pagename, request)
 672     if not filename: return # error msg already sent in _access_file
 673 
 674     # delete file
 675     os.remove(fpath)
 676     _addLogEntry(request, 'ATTDEL', pagename, filename)
 677 
 678     upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename})
 679 
 680 def move_file(request, pagename, new_pagename, attachment, new_attachment):
 681     _ = request.getText
 682 
 683     newpage = Page(request, new_pagename)
 684     if newpage.exists(includeDeleted=1) and request.user.may.write(new_pagename) and request.user.may.delete(pagename):
 685         new_attachment_path = os.path.join(getAttachDir(request, new_pagename,
 686                               create=1), new_attachment).encode(file_charset)
 687         attachment_path = os.path.join(getAttachDir(request, pagename),
 688                           attachment).encode(file_charset)
 689 
 690         if os.path.exists(new_attachment_path):
 691             upload_form(pagename, request, msg=_("Attachment '%(filename)s' already exists.") % {
 692                                    'filename': new_attachment})
 693             return
 694 
 695         if new_attachment_path != attachment_path:
 696         # move file  
 697             filesys.rename(attachment_path, new_attachment_path)
 698             _addLogEntry(request, 'ATTDEL', pagename, attachment)
 699             _addLogEntry(request, 'ATTNEW', new_pagename, new_attachment)
 700             upload_form(pagename, request, msg=_("Attachment '%(filename)s' moved to %(page)s.") % {
 701                                                  'filename': new_attachment,
 702                                                  'page': new_pagename})
 703         else:
 704             upload_form(pagename, request, msg=_("Nothing changed"))
 705     else:
 706          upload_form(pagename, request, msg=_("Page %(newpagename)s does not exists or you don't have enough rights.") % {
 707              'newpagename': new_pagename})
 708 
 709 def attachment_move(pagename, request):
 710     _ = request.getText
 711     if request.form.has_key('newpagename'):
 712         new_pagename = request.form.get('newpagename')[0]
 713     else:
 714         upload_form(pagename, request, msg=_("Move aborted because empty page name"))
 715     if request.form.has_key('newattachmentname'):
 716         new_attachment = request.form.get('newattachmentname')[0]
 717         if new_attachment != wikiutil.taintfilename(new_attachment):
 718             upload_form(pagename, request, msg=_("Please use proper signs in attachment '%(filename)s'.") % {
 719                                   'filename': new_attachment})
 720             return
 721     else:
 722         upload_form(pagename, request, msg=_("Move aborted because empty attachment name"))
 723 
 724     attachment = request.form.get('oldattachmentname')[0]
 725     move_file(request, pagename, new_pagename, attachment, new_attachment)
 726 
 727 def send_moveform(pagename, request):
 728     _ = request.getText
 729 
 730     filename, fpath = _access_file(pagename, request)
 731     if not filename: return # error msg already sent in _access_file
 732 
 733     # move file
 734     d = {'action': 'AttachFile',
 735          'do': 'attachment_move',
 736          'ticket': wikiutil.createTicket(request),
 737          'pagename': pagename,
 738          'attachment_name': filename,
 739          'move': _('Move'),
 740          'cancel': _('Cancel'),
 741          'newname_label': _("New page name"),
 742          'attachment_label': _("New attachment name"),
 743         }
 744     formhtml = '''
 745 <form method="post" action="">
 746 <input type="hidden" name="action" value="%(action)s">
 747 <input type="hidden" name="do" value="%(do)s">
 748 <input type="hidden" name="ticket" value="%(ticket)s">
 749 <table>
 750     <tr>
 751         <td class="label"><label>%(newname_label)s</label></td>
 752         <td class="content">
 753             <input type="text" name="newpagename" value="%(pagename)s">
 754         </td>
 755     </tr>
 756     <tr>
 757         <td class="label"><label>%(attachment_label)s</label></td>
 758         <td class="content">
 759             <input type="text" name="newattachmentname" value="%(attachment_name)s">
 760         </td>
 761     </tr>
 762     <tr>
 763         <td></td>
 764         <td class="buttons">
 765             <input type="hidden" name="oldattachmentname" value="%(attachment_name)s">
 766             <input type="submit" name="move" value="%(move)s">
 767             <input type="submit" name="cancel" value="%(cancel)s">
 768         </td>
 769     </tr>
 770 </table>
 771 </form>''' % d
 772     thispage = Page(request, pagename)
 773     return thispage.send_page(request, msg=formhtml)
 774 
 775 def get_file(pagename, request):
 776     import shutil
 777 
 778     filename, fpath = _access_file(pagename, request)
 779     if not filename: return # error msg already sent in _access_file
 780 
 781     timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath)))
 782     if request.if_modified_since == timestamp:
 783         request.http_headers(["Status: 304 Not modified"])
 784         request.setResponseCode(304)
 785     else:
 786         # get mimetype
 787         mt, enc = mimetypes.guess_type(filename)
 788         if not mt:
 789             mt = "application/octet-stream"
 790 
 791         # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
 792         # There is no solution that is compatible to IE except stripping non-ascii chars
 793         filename_enc = filename.encode(file_charset)
 794 
 795         # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
 796         # we just let the user store them to disk ('attachment').
 797         # For safe files, we directly show them inline (this also works better for IE).
 798         dangerous = mt in request.cfg.mimetypes_xss_protect
 799         content_dispo = dangerous and 'attachment' or 'inline'
 800 
 801         request.http_headers([
 802             'Content-Type: %s' % mt,
 803             'Last-Modified: %s' % timestamp, # TODO maybe add a short Expires: header here?
 804             'Content-Length: %d' % os.path.getsize(fpath),
 805             'Content-Disposition: %s; filename="%s"' % (content_dispo, filename_enc),
 806         ])
 807 
 808         # send data
 809         shutil.copyfileobj(open(fpath, 'rb'), request, 8192)
 810 
 811     raise MoinMoinNoFooter
 812 
 813 def install_package(pagename, request):
 814     _ = request.getText
 815 
 816     target, targetpath = _access_file(pagename, request)
 817     if not target:
 818         return
 819 
 820     package = packages.ZipPackage(request, targetpath)
 821 
 822     if package.isPackage():
 823         if package.installPackage():
 824             msg=_("Attachment '%(filename)s' installed.") % {'filename': wikiutil.escape(target)}
 825         else:
 826             msg=_("Installation of '%(filename)s' failed.") % {'filename': wikiutil.escape(target)}
 827         if package.msg != "":
 828             msg += "<br><pre>" + wikiutil.escape(package.msg) + "</pre>"
 829     else:
 830         msg = _('The file %s is not a MoinMoin package file.' % wikiutil.escape(target))
 831 
 832     upload_form(pagename, request, msg=msg)
 833 
 834 def unzip_file(pagename, request):
 835     _ = request.getText
 836     valid_pathname = lambda name: (name.find('/') == -1) and (name.find('\\') == -1)
 837 
 838     filename, fpath = _access_file(pagename, request)
 839     if not filename:
 840         return # error msg already sent in _access_file
 841 
 842     attachment_path = getAttachDir(request, pagename)
 843     single_file_size = request.cfg.unzip_single_file_size
 844     attachments_file_space = request.cfg.unzip_attachments_space
 845     attachments_file_count = request.cfg.unzip_attachments_count
 846 
 847     files = _get_files(request, pagename)
 848 
 849     msg = ""
 850     if files:
 851         fsize = 0.0
 852         fcount = 0
 853         for file in files:
 854             fsize += float(os.stat(getFilename(request, pagename, file))[6]) # in byte
 855             fcount += 1
 856 
 857         available_attachments_file_space = attachments_file_space - fsize
 858         available_attachments_file_count = attachments_file_count - fcount
 859         
 860         if zipfile.is_zipfile(fpath):
 861             zf = zipfile.ZipFile(fpath)
 862             sum_size_over_all_valid_files = 0.0
 863             count_valid_files = 0
 864             namelist = _subdir_exception(zf)
 865             if not namelist: #if it's not handled by _subdir_exception()
 866                 #Convert normal zf.namelist() to {origname:finalname} dict
 867                 namelist = {}
 868                 for name in zf.namelist():
 869                     namelist[name] = name
 870             for (origname, finalname) in namelist.iteritems():
 871                 if valid_pathname(finalname):
 872                     sum_size_over_all_valid_files += zf.getinfo(origname).file_size
 873                     count_valid_files += 1
 874 
 875             if sum_size_over_all_valid_files > available_attachments_file_space:
 876                 msg=_("Attachment '%(filename)s' could not be unzipped because"
 877                       " the resulting files would be too large (%(space)d kB"
 878                       " missing).") % {
 879                         'filename': filename,
 880                         'space': (sum_size_over_all_valid_files -
 881                               available_attachments_file_space) / 1000 }
 882             elif count_valid_files > available_attachments_file_count:
 883                 msg=_("Attachment '%(filename)s' could not be unzipped because"
 884                       " the resulting files would be too many (%(count)d "
 885                       "missing).") % {
 886                         'filename': filename,
 887                         'count': (count_valid_files -
 888                                   available_attachments_file_count) }
 889             else:
 890                 valid_name = False
 891                 for (origname, finalname) in namelist.iteritems():
 892                     if valid_pathname(finalname):
 893                         zi = zf.getinfo(origname)
 894                         if zi.file_size < single_file_size:
 895                             new_file = getFilename(request, pagename, finalname)
 896                             if not os.path.exists(new_file):
 897                                 outfile = open(new_file, 'wb')
 898                                 outfile.write(zf.read(origname))
 899                                 outfile.close()
 900                                 # it's not allowed to zip a zip file so it is dropped
 901                                 if zipfile.is_zipfile(new_file):
 902                                     os.unlink(new_file)
 903                                 else:
 904                                     valid_name = True
 905                                     _addLogEntry(request, 'ATTNEW', pagename, finalname)
 906 
 907                 if valid_name:
 908                     msg=_("Attachment '%(filename)s' unzipped.") % {'filename': filename}
 909                 else:
 910                     msg=_("Attachment '%(filename)s' not unzipped because the "
 911                           "files are too big, .zip files only, exist already or "
 912                           "reside in folders.") % {'filename': filename}
 913         else:
 914             msg = _('The file %(target)s is not a .zip file.' % target)
 915 
 916     upload_form(pagename, request, msg=wikiutil.escape(msg))
 917 
 918 def send_viewfile(pagename, request):
 919     _ = request.getText
 920 
 921     filename, fpath = _access_file(pagename, request)
 922     if not filename: return
 923 
 924     request.write('<h2>' + _("Attachment '%(filename)s'") % {'filename': filename} + '</h2>')
 925 
 926     type, enc = mimetypes.guess_type(filename)
 927     if type:
 928         if type[:5] == 'image':
 929             timestamp = htdocs_access(request) and "?%s" % time.time() or ''
 930             request.write('<img src="%s%s" alt="%s">' % (
 931                 getAttachUrl(pagename, filename, request, escaped=1), timestamp, wikiutil.escape(filename, 1)))
 932             return
 933         elif type[:4] == 'text':
 934             # TODO: should use formatter here!
 935             request.write("<pre>")
 936             # Try to decode file contents. It may return junk, but we
 937             # don't have enough information on attachments.
 938             content = open(fpath, 'r').read()
 939             content = wikiutil.decodeUnknownInput(content)
 940             content = wikiutil.escape(content)
 941             request.write(content)
 942             request.write("</pre>")
 943             return
 944 
 945     package = packages.ZipPackage(request, fpath)
 946     if package.isPackage():
 947         request.write("<pre><b>%s</b>\n%s</pre>" % (_("Package script:"),wikiutil.escape(package.getScript())))
 948         return
 949 
 950     import zipfile
 951     if zipfile.is_zipfile(fpath):
 952         zf = zipfile.ZipFile(fpath, mode='r')
 953         request.write("<pre>%-46s %19s %12s\n" % (_("File Name"), _("Modified")+" "*5, _("Size")))
 954         for zinfo in zf.filelist:
 955             date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
 956             request.write(wikiutil.escape("%-46s %s %12d\n" % (zinfo.filename, date, zinfo.file_size)))
 957         request.write("</pre>")
 958         return
 959 
 960     request.write('<p>' + _("Unknown file type, cannot display this attachment inline.") + '</p>')
 961     request.write('<a href="%s">%s</a>' % (
 962         getAttachUrl(pagename, filename, request, escaped=1), wikiutil.escape(filename)))
 963 
 964 
 965 def view_file(pagename, request):
 966     _ = request.getText
 967 
 968     filename, fpath = _access_file(pagename, request)
 969     if not filename: return
 970 
 971     # send header & title
 972     request.http_headers()
 973     # Use user interface language for this generated page
 974     request.setContentLanguage(request.lang)
 975     title = _('attachment:%(filename)s of %(pagename)s', formatted=True) % {
 976         'filename': filename, 'pagename': pagename}
 977     wikiutil.send_title(request, title, pagename=pagename)
 978 
 979     # send body
 980     # TODO: use formatter startContent?
 981     request.write('<div id="content">\n') # start content div
 982     send_viewfile(pagename, request)
 983     send_uploadform(pagename, request)
 984     request.write('</div>\n') # end content div
 985 
 986     # send footer
 987     wikiutil.send_footer(request, pagename)
 988 
 989 
 990 #############################################################################
 991 ### File attachment administration
 992 #############################################################################
 993 
 994 def do_admin_browser(request):
 995     """ Browser for SystemAdmin macro.
 996     """
 997     from MoinMoin.util.dataset import TupleDataset, Column
 998     _ = request.getText
 999 
1000     data = TupleDataset()
1001     data.columns = [
1002         Column('page', label=('Page')),
1003         Column('file', label=('Filename')),
1004         Column('size',  label=_('Size'), align='right'),
1005         #Column('action', label=_('Action')),
1006     ]
1007 
1008     # iterate over pages that might have attachments
1009     pages = request.rootpage.getPageList()
1010     for pagename in pages:
1011         # check for attachments directory
1012         page_dir = getAttachDir(request, pagename)
1013         if os.path.isdir(page_dir):
1014             # iterate over files of the page
1015             files = os.listdir(page_dir)
1016             for filename in files:
1017                 filepath = os.path.join(page_dir, filename)
1018                 data.addRow((
1019                     Page(request, pagename).link_to(request, querystr="action=AttachFile"),
1020                     wikiutil.escape(filename.decode(file_charset)),
1021                     os.path.getsize(filepath),
1022                     # '',
1023                 ))
1024 
1025     if data:
1026         from MoinMoin.widget.browser import DataBrowserWidget
1027 
1028         browser = DataBrowserWidget(request)
1029         browser.setData(data)
1030         return browser.toHTML()
1031 
1032     return ''

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2007-10-23 01:08:58, 39.7 KB) [[attachment:AttachFile_base1.5.8_modified.py]]
  • [get | view] (2008-06-26 00:20:48, 49.5 KB) [[attachment:AttachFile_base170_modified.py]]
  • [get | view] (2007-10-23 07:39:15, 19.9 KB) [[attachment:traceback.html]]
  • [get | view] (2008-06-12 11:59:25, 8.4 KB) [[attachment:traceback_attached_jpn.txt]]
  • [get | view] (2007-10-24 03:10:29, 3.9 KB) [[attachment:עברית.png]]
  • [get | view] (2007-10-24 03:07:46, 3.9 KB) [[attachment:这是一个附件.png]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.