Attachment 'AttachFile_base170_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 link to an attachment, use [[attachment:file.txt]],
  19     to embed an attachment, use {{attachment:file.png}}.
  20 
  21     This module uses optional configure;
  22         attach_fname_method:
  23             to support unicode filename for attached file.
  24             "170compatible", "u16le", "punycode", "punycodesplit",
  25             and "urllib" available. if not set, "punycode" is
  26             used. see pydoc for details.
  27         attach_fname_prefix:
  28             used for prefix of unicode filename on server's
  29             filesystem. if not set, default prefix of each method
  30             used("le_", "xn--", or "ps_"). "170compatible" and
  31             "urllib" don't use this parameter.
  32 
  33     @copyright: 2001 by Ken Sugino (sugino@mediaone.net),
  34                 2001-2004 by Juergen Hermann <jh@web.de>,
  35                 2005 MoinMoin:AlexanderSchremmer,
  36                 2005 DiegoOngaro at ETSZONE (diego@etszone.com),
  37                 2005-2007 MoinMoin:ReimarBauer,
  38                 2007-2008 MoinMoin:ThomasWaldmann
  39                 (FILENAME_CODING work by 2008 Suzumizaki-Kimitaka)
  40     @license: GNU GPL, see COPYING for details.
  41 """
  42 
  43 import os, time, zipfile, mimetypes, errno, urllib
  44 
  45 from MoinMoin import log
  46 logging = log.getLogger(__name__)
  47 
  48 from MoinMoin import config, wikiutil, packages
  49 from MoinMoin.Page import Page
  50 from MoinMoin.util import filesys, timefuncs
  51 from MoinMoin.security.textcha import TextCha
  52 from MoinMoin.events import FileAttachedEvent, send_event
  53 
  54 action_name = __name__.split('.')[-1]
  55 
  56 #############################################################################
  57 ### Encoding filename support
  58 #############################################################################
  59 
  60 class CodingBase:
  61     def __init__(self, prefix = "", default = ""):
  62         if prefix == "empty":
  63             prefix = ""
  64         elif prefix == "":
  65             prefix = default
  66         self.my_prefix = prefix
  67         return
  68 
  69     def isPrefixMatch(self, s):
  70         return s.startswith(self.my_prefix)
  71 
  72     def eatPrefix(self, s):
  73         return s[len(self.my_prefix):]
  74 
  75     def assumeNonUnicodeObject(self, s):
  76         """make sure the string is not unicode object.
  77 
  78         This function gives because punycode_decode raises
  79         UnicodeError on the line:
  80             base = unicode(base, 'ascii', errors)
  81         Of course, given s must be consists of ascii chars.
  82         """
  83         if isinstance(s, str):
  84             return s
  85         r = ""
  86         for i in s:
  87             r += chr(ord(i))
  88         return r
  89     
  90     def isAscii(self, s):
  91         for i in s:
  92             if ord(i)>0x7f:
  93                 return False
  94         return True
  95 
  96 class Invalid170Coding(CodingBase):
  97     """Encode/Decode with Illegal method. Only used for compatibility.
  98 
  99     This method is used during about 1.5.8 - 1.7.0, but simply
 100     WRONG implementation. because built-in functions open(), file(),
 101     or the functions in "os" modules should NOT be used with 
 102     unicode filename which is byte-encoded. Only 'unicode string type'
 103     is allowed as unicode filename(or until be raised Exception, you
 104     can use encode(sys.getfilesystemencoding()).
 105     Again, this method is wrong and deprecated. Don't use with new wiki.
 106     """
 107     def __init__(self, prefix=""):
 108         CodingBase.__init__(self, prefix, "")
 109         
 110     def encode(self, s):
 111         if isinstance(s, unicode):
 112             return s.encode(config.charset)
 113         return s
 114 
 115     def decode(self, s):
 116         return s.decode(config.charset)
 117 
 118 class Utf16leCoding(CodingBase):
 119     """Encode/Decode with Simple 16bit little endian hex.
 120 
 121     This method is simple one and easy to understand.
 122     You can use instead of punycode that requires python 2.3,
 123     and when wiki uses Non-Ascii names a lot with attached file,
 124     this method makes more shorter file name than urllib.quote().
 125     Default prefix is "le_".
 126     """
 127     def __init__(self, prefix=""):
 128         CodingBase.__init__(self, prefix, "le_")
 129         return
 130     
 131     def encode(self, s):
 132         if self.isAscii(s):
 133             if not self.isPrefixMatch(s):
 134                 return s
 135         return self.my_prefix+unicode(s).encode("UTF-16LE").encode("hex")
 136 
 137     def decode(self, s):
 138         if self.isPrefixMatch(s):
 139             return self.eatPrefix(s).decode("hex").decode("UTF-16LE")
 140         return s
 141 
 142 class PunycodeCoding(CodingBase):
 143     """Encode/Decode with punycode. Requires Python 2.3 or later.
 144 
 145     This method makes filename shorter than others. The method
 146     correcting ascii characters first, so extentions on file system
 147     looks like many file types(but just "looks like").
 148     Default prefix is "xn--".
 149     """
 150     def __init__(self, prefix=""):
 151         CodingBase.__init__(self, prefix, "xn--")
 152         return
 153 
 154     def encode(self, s):
 155         if self.isAscii(s):
 156             if not self.isPrefixMatch(s):
 157                 return s
 158         return self.my_prefix+s.encode("punycode")
 159 
 160     def decode(self, s):
 161         s = self.assumeNonUnicodeObject(s)
 162         if self.isPrefixMatch(s):
 163             return self.eatPrefix(s).decode("punycode")
 164         return s
 165 
 166 class PunycodeSplitCoding(CodingBase):
 167     """Encode/Decode with punycode but splitting filename extention.
 168 
 169     Requires Python 2.3 or later.
 170     This method is similer with using simple punycode, but extension
 171     is splitted before encode. This makes easier to find filetype on
 172     file system used by wiki, but encoding/decoding are complex some.
 173     And of course, this method losts compatibility with punycoded
 174     uris, just be ascii uris.
 175     Default prefix is "ps_".
 176     """
 177     def __init__(self, prefix=""):
 178         CodingBase.__init__(self, prefix, "ps_")
 179         return
 180 
 181     def encode(self, s):
 182         r = ""
 183         for i, c in enumerate(os.path.splitext(s)):
 184             if self.isPrefixMatch(c[i:]) or not self.isAscii(c):
 185                 r += c[:i]+self.my_prefix+ \
 186                      (unicode(c[i:]).encode("punycode"))
 187             else:
 188                 r += c
 189         return r
 190 
 191     def decode(self, s):
 192         s = self.assumeNonUnicodeObject(s)
 193         r = ""
 194         for i, c in enumerate(os.path.splitext(s)):
 195             if self.isPrefixMatch(c[i:]):
 196                 r += c[:i]+(self.eatPrefix(c[i:]).decode("punycode"))
 197             else:
 198                 r += c
 199         return r
 200 
 201 class UrllibCoding(CodingBase):
 202     """Encode/Decode with urllib.quote/unquote.
 203 
 204     This method no needs to use prefix like others.
 205     This method may be compatible with Trac.
 206     This method makes filename very longer than others.
 207     """
 208     def __init__(self, prefix=""):
 209         CodingBase.__init__(self, prefix, "")
 210         
 211     def encode(self, s):
 212         return urllib.quote(unicode(s).encode("utf-8"))
 213     
 214     def decode(self, s):
 215         return urllib.unquote(s).decode("utf-8")
 216 
 217 coding_obj = ""
 218 def _co(request):
 219     global coding_obj
 220     if coding_obj:
 221         return coding_obj
 222     coding_names = { "170compatible":Invalid170Coding,
 223                      "u16le":Utf16leCoding,
 224                      "punycode":PunycodeCoding,
 225                      "punycodesplit":PunycodeSplitCoding,
 226                      "urllib":UrllibCoding }
 227     coding_pf = ""
 228     coding_method = "punycode"
 229     if hasattr(request.cfg, "attach_fname_prefix"):
 230         coding_pf = request.cfg.attach_fname_prefix
 231     if hasattr(request.cfg, "attach_fname_method"):
 232         if request.cfg.attach_fname_method in coding_names:
 233             coding_method = request.cfg.attach_fname_method
 234         else:
 235             s = reduce(lambda x,y: x+", "+y, \
 236                        coding_names.keys()[1:], coding_names.keys()[0])
 237             logging.error(\
 238                 'attach_fname_method="%(n)s" is invalid.' \
 239                 ' Should be one of %(g)s.' \
 240                 % { "n": request.cfg.attach_fname_method, "g":s })
 241     coding_obj = coding_names[coding_method](coding_pf)
 242     logging.info("Name coding method used by attached file is %s" % coding_obj)
 243     return coding_obj
 244 
 245 #############################################################################
 246 ### External interface - these are called from the core code
 247 #############################################################################
 248 
 249 class AttachmentAlreadyExists(Exception):
 250     pass
 251 
 252 
 253 def getBasePath(request):
 254     """ Get base path where page dirs for attachments are stored. """
 255     return request.rootpage.getPagePath('pages')
 256 
 257 
 258 def getAttachDir(request, pagename, create=0):
 259     """ Get directory where attachments for page `pagename` are stored. """
 260     if request.page and pagename == request.page.page_name:
 261         page = request.page # reusing existing page obj is faster
 262     else:
 263         page = Page(request, pagename)
 264     return page.getPagePath("attachments", check_create=create)
 265 
 266 
 267 def absoluteName(url, pagename):
 268     """ Get (pagename, filename) of an attachment: link
 269         @param url: PageName/filename.ext or filename.ext (unicode)
 270         @param pagename: name of the currently processed page (unicode)
 271         @rtype: tuple of unicode
 272         @return: PageName, filename.ext
 273     """
 274     url = wikiutil.AbsPageName(pagename, url)
 275     pieces = url.split(u'/')
 276     if len(pieces) == 1:
 277         return pagename, pieces[0]
 278     else:
 279         return u"/".join(pieces[:-1]), pieces[-1]
 280 
 281 
 282 def attachUrl(request, pagename, filename=None, **kw):
 283     # filename is not used yet, but should be used later to make a sub-item url
 284     if kw:
 285         qs = '?%s' % wikiutil.makeQueryString(kw, want_unicode=False)
 286     else:
 287         qs = ''
 288     return "%s/%s%s" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), qs)
 289 
 290 
 291 def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get', drawing='', upload=False):
 292     """ Get URL that points to attachment `filename` of page `pagename`. """
 293     if upload:
 294         if not drawing:
 295             url = attachUrl(request, pagename, filename,
 296                             rename=wikiutil.taintfilename(filename), action=action_name)
 297         else:
 298             url = attachUrl(request, pagename, filename,
 299                             rename=wikiutil.taintfilename(filename), drawing=drawing, action=action_name)
 300     else:
 301         if not drawing:
 302             url = attachUrl(request, pagename, filename,
 303                             target=filename, action=action_name, do=do)
 304         else:
 305             url = attachUrl(request, pagename, filename,
 306                             drawing=drawing, action=action_name)
 307     if escaped:
 308         url = wikiutil.escape(url)
 309     return url
 310 
 311 
 312 def getIndicator(request, pagename):
 313     """ Get an attachment indicator for a page (linked clip image) or
 314         an empty string if not attachments exist.
 315     """
 316     _ = request.getText
 317     attach_dir = getAttachDir(request, pagename)
 318     if not os.path.exists(attach_dir):
 319         return ''
 320 
 321     files = os.listdir(attach_dir)
 322     if not files:
 323         return ''
 324 
 325     fmt = request.formatter
 326     attach_count = _('[%d attachments]') % len(files)
 327     attach_icon = request.theme.make_icon('attach', vars={'attach_count': attach_count})
 328     attach_link = (fmt.url(1, attachUrl(request, pagename, action=action_name), rel='nofollow') +
 329                    attach_icon +
 330                    fmt.url(0))
 331     return attach_link
 332 
 333 
 334 def getFilename(request, pagename, filename):
 335     """ make complete pathfilename of file "name" attached to some page "pagename"
 336         @param request: request object
 337         @param pagename: name of page where the file is attached to (unicode)
 338         @param filename: filename of attached file (unicode)
 339         @rtype: string (in filename_coding encoding)
 340         @return: complete path/filename of attached file
 341     """
 342     filename = _co(request).encode(filename)
 343     return os.path.join(getAttachDir(request, pagename, create=1), filename)
 344 
 345 
 346 def exists(request, pagename, filename):
 347     """ check if page <pagename> has a file <filename> attached """
 348     fpath = getFilename(request, pagename, filename)
 349     return os.path.exists(fpath)
 350 
 351 
 352 def size(request, pagename, filename):
 353     """ return file size of file attachment """
 354     fpath = getFilename(request, pagename, filename)
 355     return os.path.getsize(fpath)
 356 
 357 
 358 def info(pagename, request):
 359     """ Generate snippet with info on the attachment for page `pagename`. """
 360     _ = request.getText
 361 
 362     attach_dir = getAttachDir(request, pagename)
 363     files = []
 364     if os.path.isdir(attach_dir):
 365         files = os.listdir(attach_dir)
 366     page = Page(request, pagename)
 367     link = page.url(request, {'action': action_name})
 368     attach_info = _('There are <a href="%(link)s">%(count)s attachment(s)</a> stored for this page.') % {
 369         'count': len(files),
 370         'link': wikiutil.escape(link)
 371         }
 372     return "\n<p>\n%s\n</p>\n" % attach_info
 373 
 374 
 375 def _write_stream(content, stream, bufsize=8192):
 376     if hasattr(content, 'read'): # looks file-like
 377         import shutil
 378         shutil.copyfileobj(content, stream, bufsize)
 379     elif isinstance(content, str):
 380         stream.write(content)
 381     else:
 382         logging.error("unsupported content object: %r" % content)
 383         raise
 384 
 385 def add_attachment(request, pagename, target, filecontent, overwrite=0):
 386     """ save <filecontent> to an attachment <target> of page <pagename>
 387 
 388         filecontent can be either a str (in memory file content),
 389         or an open file object (file content in e.g. a tempfile).
 390     """
 391     _ = request.getText
 392 
 393     # replace illegal chars
 394     target = wikiutil.taintfilename(target)
 395 
 396     # get directory, and possibly create it
 397     attach_dir = getAttachDir(request, pagename, create=1)
 398     # save file
 399     fpath = os.path.join(attach_dir, _co(request).encode(target))
 400     exists = os.path.exists(fpath)
 401     if exists and not overwrite:
 402         raise AttachmentAlreadyExists
 403     else:
 404         if exists:
 405             try:
 406                 os.remove(fpath)
 407             except:
 408                 pass
 409         stream = open(fpath, 'wb')
 410         try:
 411             _write_stream(filecontent, stream)
 412         finally:
 413             stream.close()
 414 
 415         _addLogEntry(request, 'ATTNEW', pagename, target)
 416 
 417         filesize = os.path.getsize(fpath)
 418         event = FileAttachedEvent(request, pagename, target, filesize)
 419         send_event(event)
 420 
 421     return target, filesize
 422 
 423 
 424 #############################################################################
 425 ### Internal helpers
 426 #############################################################################
 427 
 428 def _addLogEntry(request, action, pagename, filename):
 429     """ Add an entry to the edit log on uploads and deletes.
 430 
 431         `action` should be "ATTNEW" or "ATTDEL"
 432     """
 433     from MoinMoin.logfile import editlog
 434     t = wikiutil.timestamp2version(time.time())
 435     fname = wikiutil.url_quote(filename, want_unicode=True)
 436 
 437     # Write to global log
 438     log = editlog.EditLog(request)
 439     log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)
 440 
 441     # Write to local log
 442     log = editlog.EditLog(request, rootpagename=pagename)
 443     log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)
 444 
 445 
 446 def _access_file(pagename, request):
 447     """ Check form parameter `target` and return a tuple of
 448         `(pagename, filename, filepath)` for an existing attachment.
 449 
 450         Return `(pagename, None, None)` if an error occurs.
 451     """
 452     _ = request.getText
 453 
 454     error = None
 455     if not request.form.get('target', [''])[0]:
 456         error = _("Filename of attachment not specified!")
 457     else:
 458         filename = wikiutil.taintfilename(request.form['target'][0])
 459         fpath = getFilename(request, pagename, filename)
 460 
 461         if os.path.isfile(fpath):
 462             return (pagename, filename, fpath)
 463         error = _("Attachment '%(filename)s' does not exist!") % {'filename': filename}
 464 
 465     error_msg(pagename, request, error)
 466     return (pagename, None, None)
 467 
 468 
 469 def _build_filelist(request, pagename, showheader, readonly, mime_type='*'):
 470     _ = request.getText
 471     fmt = request.html_formatter
 472 
 473     # access directory
 474     attach_dir = getAttachDir(request, pagename)
 475     files = _get_files(request, pagename)
 476 
 477     if mime_type != '*':
 478         files = [fname for fname in files if mime_type == mimetypes.guess_type(fname)[0]]
 479 
 480     html = []
 481     if files:
 482         if showheader:
 483             html.append(fmt.rawHTML(_(
 484                 "To refer to attachments on a page, use '''{{{attachment:filename}}}''', \n"
 485                 "as shown below in the list of files. \n"
 486                 "Do '''NOT''' use the URL of the {{{[get]}}} link, \n"
 487                 "since this is subject to change and can break easily.",
 488                 wiki=True
 489             )))
 490 
 491         label_del = _("del")
 492         label_move = _("move")
 493         label_get = _("get")
 494         label_edit = _("edit")
 495         label_view = _("view")
 496         label_unzip = _("unzip")
 497         label_install = _("install")
 498 
 499         html.append(fmt.bullet_list(1))
 500         for file in files:
 501             mt = wikiutil.MimeType(filename=file)
 502             fullpath = os.path.join(attach_dir, _co(request).encode(file))
 503             st = os.stat(fullpath)
 504             base, ext = os.path.splitext(file)
 505             parmdict = {'file': wikiutil.escape(file),
 506                         'fsize': "%.1f" % (float(st.st_size) / 1024),
 507                         'fmtime': request.user.getFormattedDateTime(st.st_mtime),
 508                        }
 509 
 510             links = []
 511             may_delete = request.user.may.delete(pagename)
 512             if may_delete and not readonly:
 513                 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='del')) +
 514                              fmt.text(label_del) +
 515                              fmt.url(0))
 516 
 517             if may_delete and not readonly:
 518                 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='move')) +
 519                              fmt.text(label_move) +
 520                              fmt.url(0))
 521 
 522             links.append(fmt.url(1, getAttachUrl(pagename, file, request)) +
 523                          fmt.text(label_get) +
 524                          fmt.url(0))
 525 
 526             if ext == '.draw':
 527                 links.append(fmt.url(1, getAttachUrl(pagename, file, request, drawing=base)) +
 528                              fmt.text(label_edit) +
 529                              fmt.url(0))
 530             else:
 531                 links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='view')) +
 532                              fmt.text(label_view) +
 533                              fmt.url(0))
 534 
 535             try:
 536                 is_zipfile = zipfile.is_zipfile(fullpath)
 537                 if is_zipfile:
 538                     is_package = packages.ZipPackage(request, fullpath).isPackage()
 539                     if is_package and request.user.isSuperUser():
 540                         links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='install')) +
 541                                      fmt.text(label_install) +
 542                                      fmt.url(0))
 543                     elif (not is_package and mt.minor == 'zip' and
 544                           may_delete and
 545                           request.user.may.read(pagename) and
 546                           request.user.may.write(pagename)):
 547                         links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='unzip')) +
 548                                      fmt.text(label_unzip) +
 549                                      fmt.url(0))
 550             except RuntimeError:
 551                 # We don't want to crash with a traceback here (an exception
 552                 # here could be caused by an uploaded defective zip file - and
 553                 # if we crash here, the user does not get a UI to remove the
 554                 # defective zip file again).
 555                 # RuntimeError is raised by zipfile stdlib module in case of
 556                 # problems (like inconsistent slash and backslash usage in the
 557                 # archive).
 558                 logging.exception("An exception within zip file attachment handling occurred:")
 559 
 560             html.append(fmt.listitem(1))
 561             html.append("[%s]" % "&nbsp;| ".join(links))
 562             html.append(" (%(fmtime)s, %(fsize)s KB) [[attachment:%(file)s]]" % parmdict)
 563             html.append(fmt.listitem(0))
 564         html.append(fmt.bullet_list(0))
 565 
 566     else:
 567         if showheader:
 568             html.append(fmt.paragraph(1))
 569             html.append(fmt.text(_("No attachments stored for %(pagename)s") % {
 570                                    'pagename': pagename}))
 571             html.append(fmt.paragraph(0))
 572 
 573     return ''.join(html)
 574 
 575 
 576 def _get_files(request, pagename):
 577     attach_dir = getAttachDir(request, pagename)
 578     if os.path.isdir(attach_dir):
 579         files = [_co(request).decode(fn) for fn in os.listdir(attach_dir)]
 580         files.sort()
 581     else:
 582         files = []
 583     return files
 584 
 585 
 586 def _get_filelist(request, pagename):
 587     return _build_filelist(request, pagename, 1, 0)
 588 
 589 
 590 def error_msg(pagename, request, msg):
 591     request.theme.add_msg(msg, "error")
 592     Page(request, pagename).send_page()
 593 
 594 
 595 #############################################################################
 596 ### Create parts of the Web interface
 597 #############################################################################
 598 
 599 def send_link_rel(request, pagename):
 600     files = _get_files(request, pagename)
 601     for fname in files:
 602         url = getAttachUrl(pagename, fname, request, do='view', escaped=1)
 603         request.write(u'<link rel="Appendix" title="%s" href="%s">\n' % (
 604                       wikiutil.escape(fname), url))
 605 
 606 
 607 def send_hotdraw(pagename, request):
 608     _ = request.getText
 609 
 610     now = time.time()
 611     pubpath = request.cfg.url_prefix_static + "/applets/TWikiDrawPlugin"
 612     basename = request.form['drawing'][0]
 613     drawpath = getAttachUrl(pagename, basename + '.draw', request, escaped=1)
 614     pngpath = getAttachUrl(pagename, basename + '.png', request, escaped=1)
 615     pagelink = attachUrl(request, pagename, '', action=action_name, ts=now)
 616     helplink = Page(request, "HelpOnActions/AttachFile").url(request)
 617     savelink = attachUrl(request, pagename, '', action=action_name, do='savedrawing')
 618     #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
 619                                            # request, {'savename': request.form['drawing'][0]+'.draw'}
 620     #savelink = '/cgi-bin/dumpform.bat'
 621 
 622     timestamp = '&amp;ts=%s' % now
 623 
 624     request.write('<h2>' + _("Edit drawing") + '</h2>')
 625     request.write("""
 626 <p>
 627 <img src="%(pngpath)s%(timestamp)s">
 628 <applet code="CH.ifa.draw.twiki.TWikiDraw.class"
 629         archive="%(pubpath)s/twikidraw.jar" width="640" height="480">
 630 <param name="drawpath" value="%(drawpath)s">
 631 <param name="pngpath"  value="%(pngpath)s">
 632 <param name="savepath" value="%(savelink)s">
 633 <param name="basename" value="%(basename)s">
 634 <param name="viewpath" value="%(pagelink)s">
 635 <param name="helppath" value="%(helplink)s">
 636 <strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example.
 637 </applet>
 638 </p>""" % {
 639     'pngpath': pngpath, 'timestamp': timestamp,
 640     'pubpath': pubpath, 'drawpath': drawpath,
 641     'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink,
 642     'basename': basename
 643 })
 644 
 645 
 646 def send_uploadform(pagename, request):
 647     """ Send the HTML code for the list of already stored attachments and
 648         the file upload form.
 649     """
 650     _ = request.getText
 651 
 652     if not request.user.may.read(pagename):
 653         request.write('<p>%s</p>' % _('You are not allowed to view this page.'))
 654         return
 655 
 656     writeable = request.user.may.write(pagename)
 657 
 658     # First send out the upload new attachment form on top of everything else.
 659     # This avoids usability issues if you have to scroll down a lot to upload
 660     # a new file when the page already has lots of attachments:
 661     if writeable:
 662         request.write('<h2>' + _("New Attachment") + '</h2>')
 663         request.write("""
 664 <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
 665 <dl>
 666 <dt>%(upload_label_file)s</dt>
 667 <dd><input type="file" name="file" size="50"></dd>
 668 <dt>%(upload_label_rename)s</dt>
 669 <dd><input type="text" name="rename" size="50" value="%(rename)s"></dd>
 670 <dt>%(upload_label_overwrite)s</dt>
 671 <dd><input type="checkbox" name="overwrite" value="1" %(overwrite_checked)s></dd>
 672 </dl>
 673 %(textcha)s
 674 <p>
 675 <input type="hidden" name="action" value="%(action_name)s">
 676 <input type="hidden" name="do" value="upload">
 677 <input type="submit" value="%(upload_button)s">
 678 </p>
 679 </form>
 680 """ % {
 681     'baseurl': request.getScriptname(),
 682     'pagename': wikiutil.quoteWikinameURL(pagename),
 683     'action_name': action_name,
 684     'upload_label_file': _('File to upload'),
 685     'upload_label_rename': _('Rename to'),
 686     'rename': request.form.get('rename', [''])[0],
 687     'upload_label_overwrite': _('Overwrite existing attachment of same name'),
 688     'overwrite_checked': ('', 'checked')[request.form.get('overwrite', ['0'])[0] == '1'],
 689     'upload_button': _('Upload'),
 690     'textcha': TextCha(request).render(),
 691 })
 692 
 693     request.write('<h2>' + _("Attached Files") + '</h2>')
 694     request.write(_get_filelist(request, pagename))
 695 
 696     if not writeable:
 697         request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.'))
 698 
 699     if writeable and request.form.get('drawing', [None])[0]:
 700         send_hotdraw(pagename, request)
 701 
 702 
 703 #############################################################################
 704 ### Web interface for file upload, viewing and deletion
 705 #############################################################################
 706 
 707 def execute(pagename, request):
 708     """ Main dispatcher for the 'AttachFile' action. """
 709     _ = request.getText
 710 
 711     do = request.form.get('do', ['upload_form'])
 712     handler = globals().get('_do_%s' % do[0])
 713     if handler:
 714         msg = handler(pagename, request)
 715     else:
 716         msg = _('Unsupported AttachFile sub-action: %s') % (wikiutil.escape(do[0]), )
 717     if msg:
 718         error_msg(pagename, request, msg)
 719 
 720 
 721 def _do_upload_form(pagename, request):
 722     upload_form(pagename, request)
 723 
 724 
 725 def upload_form(pagename, request, msg=''):
 726     _ = request.getText
 727 
 728     request.emit_http_headers()
 729     # Use user interface language for this generated page
 730     request.setContentLanguage(request.lang)
 731     request.theme.add_msg(msg, "dialog")
 732     request.theme.send_title(_('Attachments for "%(pagename)s"') % {'pagename': pagename}, pagename=pagename)
 733     request.write('<div id="content">\n') # start content div
 734     send_uploadform(pagename, request)
 735     request.write('</div>\n') # end content div
 736     request.theme.send_footer(pagename)
 737     request.theme.send_closing_html()
 738 
 739 
 740 def preprocess_filename(filename):
 741     """ preprocess the filename we got from upload form,
 742         strip leading drive and path (IE misbehaviour)
 743     """
 744     if filename and len(filename) > 1 and (filename[1] == ':' or filename[0] == '\\'): # C:.... or \path... or \\server\...
 745         bsindex = filename.rfind('\\')
 746         if bsindex >= 0:
 747             filename = filename[bsindex+1:]
 748     return filename
 749 
 750 
 751 def _do_upload(pagename, request):
 752     _ = request.getText
 753     # Currently we only check TextCha for upload (this is what spammers ususally do),
 754     # but it could be extended to more/all attachment write access
 755     if not TextCha(request).check_answer_from_form():
 756         return _('TextCha: Wrong answer! Go back and try again...')
 757 
 758     form = request.form
 759     overwrite = form.get('overwrite', [u'0'])[0]
 760     try:
 761         overwrite = int(overwrite)
 762     except:
 763         overwrite = 0
 764 
 765     if not request.user.may.write(pagename):
 766         return _('You are not allowed to attach a file to this page.')
 767 
 768     if overwrite and not request.user.may.delete(pagename):
 769         return _('You are not allowed to overwrite a file attachment of this page.')
 770 
 771     filename = form.get('file__filename__')
 772     rename = form.get('rename', [u''])[0].strip()
 773     if rename:
 774         target = rename
 775     else:
 776         target = filename
 777 
 778     target = preprocess_filename(target)
 779     target = wikiutil.clean_input(target)
 780 
 781     if not target:
 782         return _("Filename of attachment not specified!")
 783 
 784     # get file content
 785     filecontent = request.form.get('file', [None])[0]
 786     if filecontent is None:
 787         # This might happen when trying to upload file names
 788         # with non-ascii characters on Safari.
 789         return _("No file content. Delete non ASCII characters from the file name and try again.")
 790 
 791     # add the attachment
 792     try:
 793         target, bytes = add_attachment(request, pagename, target, filecontent, overwrite=overwrite)
 794         msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
 795                 " with %(bytes)d bytes saved.") % {
 796                 'target': target, 'filename': filename, 'bytes': bytes}
 797     except AttachmentAlreadyExists:
 798         msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
 799             'target': target, 'filename': filename}
 800 
 801     # return attachment list
 802     upload_form(pagename, request, msg)
 803 
 804 
 805 def _do_savedrawing(pagename, request):
 806     _ = request.getText
 807 
 808     if not request.user.may.write(pagename):
 809         return _('You are not allowed to save a drawing on this page.')
 810 
 811     filename = request.form['filename'][0]
 812     filecontent = request.form['filepath'][0]
 813 
 814     basepath, basename = os.path.split(filename)
 815     basename, ext = os.path.splitext(basename)
 816 
 817     # get directory, and possibly create it
 818     attach_dir = getAttachDir(request, pagename, create=1)
 819     savepath = os.path.join(attach_dir, basename + ext)
 820 
 821     if ext == '.draw':
 822         _addLogEntry(request, 'ATTDRW', pagename, basename + ext)
 823         filecontent = filecontent.read() # read file completely into memory
 824         filecontent = filecontent.replace("\r", "")
 825     elif ext == '.map':
 826         filecontent = filecontent.read() # read file completely into memory
 827         filecontent = filecontent.strip()
 828 
 829     if filecontent:
 830         # filecontent is either a file or a non-empty string
 831         stream = open(savepath, 'wb')
 832         try:
 833             _write_stream(filecontent, stream)
 834         finally:
 835             stream.close()
 836     else:
 837         # filecontent is empty string (e.g. empty map file), delete the target file
 838         try:
 839             os.unlink(savepath)
 840         except OSError, err:
 841             if err.errno != errno.ENOENT: # no such file
 842                 raise
 843 
 844     # touch attachment directory to invalidate cache if new map is saved
 845     if ext == '.map':
 846         os.utime(attach_dir, None)
 847 
 848     request.emit_http_headers()
 849     request.write("OK")
 850 
 851 
 852 def _do_del(pagename, request):
 853     _ = request.getText
 854 
 855     pagename, filename, fpath = _access_file(pagename, request)
 856     if not request.user.may.delete(pagename):
 857         return _('You are not allowed to delete attachments on this page.')
 858     if not filename:
 859         return # error msg already sent in _access_file
 860 
 861     # delete file
 862     os.remove(fpath)
 863     _addLogEntry(request, 'ATTDEL', pagename, filename)
 864 
 865     if request.cfg.xapian_search:
 866         from MoinMoin.search.Xapian import Index
 867         index = Index(request)
 868         if index.exists:
 869             index.remove_item(pagename, filename)
 870 
 871     upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename})
 872 
 873 
 874 def move_file(request, pagename, new_pagename, attachment, new_attachment):
 875     _ = request.getText
 876 
 877     newpage = Page(request, new_pagename)
 878     if newpage.exists(includeDeleted=1) and request.user.may.write(new_pagename) and request.user.may.delete(pagename):
 879         new_attachment_path = os.path.join(getAttachDir(request, new_pagename,
 880                               create=1), _co(request).encode(new_attachment))
 881         attachment_path = os.path.join(getAttachDir(request, pagename),
 882                           _co(request).encode(attachment))
 883 
 884         if os.path.exists(new_attachment_path):
 885             upload_form(pagename, request,
 886                 msg=_("Attachment '%(new_pagename)s/%(new_filename)s' already exists.") % {
 887                     'new_pagename': new_pagename,
 888                     'new_filename': new_attachment})
 889             return
 890 
 891         if new_attachment_path != attachment_path:
 892             # move file
 893             filesys.rename(attachment_path, new_attachment_path)
 894             _addLogEntry(request, 'ATTDEL', pagename, attachment)
 895             _addLogEntry(request, 'ATTNEW', new_pagename, new_attachment)
 896             upload_form(pagename, request,
 897                         msg=_("Attachment '%(pagename)s/%(filename)s' moved to '%(new_pagename)s/%(new_filename)s'.") % {
 898                             'pagename': pagename,
 899                             'filename': attachment,
 900                             'new_pagename': new_pagename,
 901                             'new_filename': new_attachment})
 902         else:
 903             upload_form(pagename, request, msg=_("Nothing changed"))
 904     else:
 905         upload_form(pagename, request, msg=_("Page '%(new_pagename)s' does not exist or you don't have enough rights.") % {
 906             'new_pagename': new_pagename})
 907 
 908 
 909 def _do_attachment_move(pagename, request):
 910     _ = request.getText
 911 
 912     if 'cancel' in request.form:
 913         return _('Move aborted!')
 914     if not wikiutil.checkTicket(request, request.form['ticket'][0]):
 915         return _('Please use the interactive user interface to move attachments!')
 916     if not request.user.may.delete(pagename):
 917         return _('You are not allowed to move attachments from this page.')
 918 
 919     if 'newpagename' in request.form:
 920         new_pagename = request.form.get('newpagename')[0]
 921     else:
 922         upload_form(pagename, request, msg=_("Move aborted because new page name is empty."))
 923     if 'newattachmentname' in request.form:
 924         new_attachment = request.form.get('newattachmentname')[0]
 925         if new_attachment != wikiutil.taintfilename(new_attachment):
 926             upload_form(pagename, request, msg=_("Please use a valid filename for attachment '%(filename)s'.") % {
 927                                   'filename': new_attachment})
 928             return
 929     else:
 930         upload_form(pagename, request, msg=_("Move aborted because new attachment name is empty."))
 931 
 932     attachment = request.form.get('oldattachmentname')[0]
 933     move_file(request, pagename, new_pagename, attachment, new_attachment)
 934 
 935 
 936 def _do_move(pagename, request):
 937     _ = request.getText
 938 
 939     pagename, filename, fpath = _access_file(pagename, request)
 940     if not request.user.may.delete(pagename):
 941         return _('You are not allowed to move attachments from this page.')
 942     if not filename:
 943         return # error msg already sent in _access_file
 944 
 945     # move file
 946     d = {'action': action_name,
 947          'baseurl': request.getScriptname(),
 948          'do': 'attachment_move',
 949          'ticket': wikiutil.createTicket(request),
 950          'pagename': pagename,
 951          'pagename_quoted': wikiutil.quoteWikinameURL(pagename),
 952          'attachment_name': filename,
 953          'move': _('Move'),
 954          'cancel': _('Cancel'),
 955          'newname_label': _("New page name"),
 956          'attachment_label': _("New attachment name"),
 957         }
 958     formhtml = '''
 959 <form action="%(baseurl)s/%(pagename_quoted)s" method="POST">
 960 <input type="hidden" name="action" value="%(action)s">
 961 <input type="hidden" name="do" value="%(do)s">
 962 <input type="hidden" name="ticket" value="%(ticket)s">
 963 <table>
 964     <tr>
 965         <td class="label"><label>%(newname_label)s</label></td>
 966         <td class="content">
 967             <input type="text" name="newpagename" value="%(pagename)s" size="80">
 968         </td>
 969     </tr>
 970     <tr>
 971         <td class="label"><label>%(attachment_label)s</label></td>
 972         <td class="content">
 973             <input type="text" name="newattachmentname" value="%(attachment_name)s" size="80">
 974         </td>
 975     </tr>
 976     <tr>
 977         <td></td>
 978         <td class="buttons">
 979             <input type="hidden" name="oldattachmentname" value="%(attachment_name)s">
 980             <input type="submit" name="move" value="%(move)s">
 981             <input type="submit" name="cancel" value="%(cancel)s">
 982         </td>
 983     </tr>
 984 </table>
 985 </form>''' % d
 986     thispage = Page(request, pagename)
 987     request.theme.add_msg(formhtml, "dialog")
 988     return thispage.send_page()
 989 
 990 
 991 def _do_get(pagename, request):
 992     _ = request.getText
 993 
 994     pagename, filename, fpath = _access_file(pagename, request)
 995     if not request.user.may.read(pagename):
 996         return _('You are not allowed to get attachments from this page.')
 997     if not filename:
 998         return # error msg already sent in _access_file
 999 
1000     timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath)))
1001     if request.if_modified_since == timestamp:
1002         request.emit_http_headers(["Status: 304 Not modified"])
1003     else:
1004         mt = wikiutil.MimeType(filename=filename)
1005         content_type = mt.content_type()
1006         mime_type = mt.mime_type()
1007 
1008         # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
1009         # There is no solution that is compatible to IE except stripping non-ascii chars
1010         # filename_enc = filename.encode(config. charset)
1011         filename_enc = _co(request).encode(filename)
1012 
1013         # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
1014         # we just let the user store them to disk ('attachment').
1015         # For safe files, we directly show them inline (this also works better for IE).
1016         dangerous = mime_type in request.cfg.mimetypes_xss_protect
1017         content_dispo = dangerous and 'attachment' or 'inline'
1018 
1019         request.emit_http_headers([
1020             'Content-Type: %s' % content_type,
1021             'Last-Modified: %s' % timestamp,
1022             'Content-Length: %d' % os.path.getsize(fpath),
1023             'Content-Disposition: %s; filename="%s"' % (content_dispo, filename_enc),
1024         ])
1025 
1026         # send data
1027         request.send_file(open(fpath, 'rb'))
1028 
1029 
1030 def _do_install(pagename, request):
1031     _ = request.getText
1032 
1033     pagename, target, targetpath = _access_file(pagename, request)
1034     if not request.user.isSuperUser():
1035         return _('You are not allowed to install files.')
1036     if not target:
1037         return
1038 
1039     package = packages.ZipPackage(request, targetpath)
1040 
1041     if package.isPackage():
1042         if package.installPackage():
1043             msg = _("Attachment '%(filename)s' installed.") % {'filename': wikiutil.escape(target)}
1044         else:
1045             msg = _("Installation of '%(filename)s' failed.") % {'filename': wikiutil.escape(target)}
1046         if package.msg:
1047             msg += "<br><pre>%s</pre>" % wikiutil.escape(package.msg)
1048     else:
1049         msg = _('The file %s is not a MoinMoin package file.') % wikiutil.escape(target)
1050 
1051     upload_form(pagename, request, msg=msg)
1052 
1053 
1054 def _do_unzip(pagename, request, overwrite=False):
1055     _ = request.getText
1056     pagename, filename, fpath = _access_file(pagename, request)
1057 
1058     if not (request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename)):
1059         return _('You are not allowed to unzip attachments of this page.')
1060 
1061     if not filename:
1062         return # error msg already sent in _access_file
1063 
1064     try:
1065         if not zipfile.is_zipfile(fpath):
1066             return _('The file %(filename)s is not a .zip file.') % {'filename': filename}
1067 
1068         # determine how which attachment names we have and how much space each is occupying
1069         curr_fsizes = dict([(f, size(request, pagename, f)) for f in _get_files(request, pagename)])
1070 
1071         # Checks for the existance of one common prefix path shared among
1072         # all files in the zip file. If this is the case, remove the common prefix.
1073         # We also prepare a dict of the new filenames->filesizes.
1074         zip_path_sep = '/'  # we assume '/' is as zip standard suggests
1075         fname_index = None
1076         mapping = []
1077         new_fsizes = {}
1078         zf = zipfile.ZipFile(fpath)
1079         for zi in zf.infolist():
1080             name = zi.filename
1081             if not name.endswith(zip_path_sep):  # a file (not a directory)
1082                 if fname_index is None:
1083                     fname_index = name.rfind(zip_path_sep) + 1
1084                     path = name[:fname_index]
1085                 if (name.rfind(zip_path_sep) + 1 != fname_index  # different prefix len
1086                     or
1087                     name[:fname_index] != path): # same len, but still different
1088                     mapping = []  # zip is not acceptable
1089                     break
1090                 if zi.file_size >= request.cfg.unzip_single_file_size:  # file too big
1091                     mapping = []  # zip is not acceptable
1092                     break
1093                 finalname = name[fname_index:]  # remove common path prefix
1094                 finalname = _co(request).decode(finalname)
1095                 mapping.append((name, finalname))
1096                 new_fsizes[finalname] = zi.file_size
1097 
1098         # now we either have an empty mapping (if the zip is not acceptable),
1099         # an identity mapping (no subdirs in zip, just all flat), or
1100         # a mapping (origname, finalname) where origname is the zip member filename
1101         # (including some prefix path) and finalname is a simple filename.
1102 
1103         # calculate resulting total file size / count after unzipping:
1104         if overwrite:
1105             curr_fsizes.update(new_fsizes)
1106             total = curr_fsizes
1107         else:
1108             new_fsizes.update(curr_fsizes)
1109             total = new_fsizes
1110         total_count = len(total)
1111         total_size = sum(total.values())
1112 
1113         if not mapping:
1114             msg = _("Attachment '%(filename)s' not unzipped because some files in the zip "
1115                     "are either not in the same directory or exceeded the single file size limit (%(maxsize_file)d kB)."
1116                    ) % {'filename': filename,
1117                         'maxsize_file': request.cfg.unzip_single_file_size / 1000, }
1118         elif total_size > request.cfg.unzip_attachments_space:
1119             msg = _("Attachment '%(filename)s' not unzipped because it would have exceeded "
1120                     "the per page attachment storage size limit (%(size)d kB).") % {
1121                         'filename': filename,
1122                         'size': request.cfg.unzip_attachments_space / 1000, }
1123         elif total_count > request.cfg.unzip_attachments_count:
1124             msg = _("Attachment '%(filename)s' not unzipped because it would have exceeded "
1125                     "the per page attachment count limit (%(count)d).") % {
1126                         'filename': filename,
1127                         'count': request.cfg.unzip_attachments_count, }
1128         else:
1129             not_overwritten = []
1130             for origname, finalname in mapping:
1131                 try:
1132                     # Note: reads complete zip member file into memory. ZipFile does not offer block-wise reading:
1133                     add_attachment(request, pagename, finalname, zf.read(origname), overwrite)
1134                 except AttachmentAlreadyExists:
1135                     not_overwritten.append(finalname)
1136             if not_overwritten:
1137                 msg = _("Attachment '%(filename)s' partially unzipped (did not overwrite: %(filelist)s).") % {
1138                         'filename': filename,
1139                         'filelist': ', '.join(not_overwritten), }
1140             else:
1141                 msg = _("Attachment '%(filename)s' unzipped.") % {'filename': filename}
1142     except RuntimeError, err:
1143         # We don't want to crash with a traceback here (an exception
1144         # here could be caused by an uploaded defective zip file - and
1145         # if we crash here, the user does not get a UI to remove the
1146         # defective zip file again).
1147         # RuntimeError is raised by zipfile stdlib module in case of
1148         # problems (like inconsistent slash and backslash usage in the
1149         # archive).
1150         logging.exception("An exception within zip file attachment handling occurred:")
1151         msg = _("A severe error occurred:") + ' ' + str(err)
1152 
1153     upload_form(pagename, request, msg=wikiutil.escape(msg))
1154 
1155 
1156 def send_viewfile(pagename, request):
1157     _ = request.getText
1158     fmt = request.html_formatter
1159 
1160     pagename, filename, fpath = _access_file(pagename, request)
1161     if not filename:
1162         return
1163 
1164     request.write('<h2>' + _("Attachment '%(filename)s'") % {'filename': filename} + '</h2>')
1165     # show a download link above the content
1166     label = _('Download')
1167     link = (fmt.url(1, getAttachUrl(pagename, filename, request, do='get'), css_class="download") +
1168             fmt.text(label) +
1169             fmt.url(0))
1170     request.write('%s<br><br>' % link)
1171 
1172     mt = wikiutil.MimeType(filename=filename)
1173 
1174     # destinguishs if browser need a plugin in place
1175     if mt.major == 'image' and mt.minor in config.browser_supported_images:
1176         request.write('<img src="%s" alt="%s">' % (
1177             getAttachUrl(pagename, filename, request, escaped=1),
1178             wikiutil.escape(filename, 1)))
1179         return
1180     elif mt.major == 'text':
1181         ext = os.path.splitext(filename)[1]
1182         Parser = wikiutil.getParserForExtension(request.cfg, ext)
1183         if Parser is not None:
1184             try:
1185                 content = file(fpath, 'r').read()
1186                 content = wikiutil.decodeUnknownInput(content)
1187                 colorizer = Parser(content, request, filename=filename)
1188                 colorizer.format(request.formatter)
1189                 return
1190             except IOError:
1191                 pass
1192 
1193         request.write(request.formatter.preformatted(1))
1194         # If we have text but no colorizing parser we try to decode file contents.
1195         content = open(fpath, 'r').read()
1196         content = wikiutil.decodeUnknownInput(content)
1197         content = wikiutil.escape(content)
1198         request.write(request.formatter.text(content))
1199         request.write(request.formatter.preformatted(0))
1200         return
1201 
1202     try:
1203         package = packages.ZipPackage(request, fpath)
1204         if package.isPackage():
1205             request.write("<pre><b>%s</b>\n%s</pre>" % (_("Package script:"), wikiutil.escape(package.getScript())))
1206             return
1207 
1208         if zipfile.is_zipfile(fpath) and mt.minor == 'zip':
1209             zf = zipfile.ZipFile(fpath, mode='r')
1210             request.write("<pre>%-46s %19s %12s\n" % (_("File Name"), _("Modified")+" "*5, _("Size")))
1211             for zinfo in zf.filelist:
1212                 date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
1213                 request.write(wikiutil.escape("%-46s %s %12d\n" % (zinfo.filename, date, zinfo.file_size)))
1214             request.write("</pre>")
1215             return
1216     except RuntimeError:
1217         # We don't want to crash with a traceback here (an exception
1218         # here could be caused by an uploaded defective zip file - and
1219         # if we crash here, the user does not get a UI to remove the
1220         # defective zip file again).
1221         # RuntimeError is raised by zipfile stdlib module in case of
1222         # problems (like inconsistent slash and backslash usage in the
1223         # archive).
1224         logging.exception("An exception within zip file attachment handling occurred:")
1225         return
1226 
1227     from MoinMoin import macro
1228     from MoinMoin.parser.text import Parser
1229 
1230     macro.request = request
1231     macro.formatter = request.html_formatter
1232     p = Parser("##\n", request)
1233     m = macro.Macro(p)
1234 
1235     # use EmbedObject to view valid mime types
1236     if mt is None:
1237         request.write('<p>' + _("Unknown file type, cannot display this attachment inline.") + '</p>')
1238         link = (fmt.url(1, getAttachUrl(pagename, filename, request)) +
1239                 fmt.text(filename) +
1240                 fmt.url(0))
1241         request.write('For using an external program follow this link %s' % link)
1242         return
1243     request.write(m.execute('EmbedObject', u'target=%s, pagename=%s' % (filename, pagename)))
1244     return
1245 
1246 
1247 def _do_view(pagename, request):
1248     _ = request.getText
1249 
1250     orig_pagename = pagename
1251     pagename, filename, fpath = _access_file(pagename, request)
1252     if not request.user.may.read(pagename):
1253         return _('You are not allowed to view attachments of this page.')
1254     if not filename:
1255         return
1256 
1257     # send header & title
1258     request.emit_http_headers()
1259     # Use user interface language for this generated page
1260     request.setContentLanguage(request.lang)
1261     title = _('attachment:%(filename)s of %(pagename)s') % {
1262         'filename': filename, 'pagename': pagename}
1263     request.theme.send_title(title, pagename=pagename)
1264 
1265     # send body
1266     request.write(request.formatter.startContent())
1267     send_viewfile(orig_pagename, request)
1268     send_uploadform(pagename, request)
1269     request.write(request.formatter.endContent())
1270 
1271     request.theme.send_footer(pagename)
1272     request.theme.send_closing_html()
1273 
1274 
1275 #############################################################################
1276 ### File attachment administration
1277 #############################################################################
1278 
1279 def do_admin_browser(request):
1280     """ Browser for SystemAdmin macro. """
1281     from MoinMoin.util.dataset import TupleDataset, Column
1282     _ = request.getText
1283 
1284     data = TupleDataset()
1285     data.columns = [
1286         Column('page', label=('Page')),
1287         Column('file', label=('Filename')),
1288         Column('size', label=_('Size'), align='right'),
1289     ]
1290 
1291     # iterate over pages that might have attachments
1292     pages = request.rootpage.getPageList()
1293     for pagename in pages:
1294         # check for attachments directory
1295         page_dir = getAttachDir(request, pagename)
1296         if os.path.isdir(page_dir):
1297             # iterate over files of the page
1298             files = os.listdir(page_dir)
1299             for filename in files:
1300                 filepath = os.path.join(page_dir, filename)
1301                 data.addRow((
1302                     Page(request, pagename).link_to(request, querystr="action=AttachFile"),
1303                     wikiutil.escape(_co(request).decode(filename)),
1304                     os.path.getsize(filepath),
1305                 ))
1306 
1307     if data:
1308         from MoinMoin.widget.browser import DataBrowserWidget
1309 
1310         browser = DataBrowserWidget(request)
1311         browser.setData(data)
1312         return browser.toHTML()
1313 
1314     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.