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&do=del&target=%(urlfile)s">%(label_del)s</a> | ' % parmdict
264 if request.user.may.delete(pagename) and not readonly:
265 move_link = '<a href="%(baseurl)s/%(urlpagename)s' \
266 '?action=%(action)s&do=move&target=%(urlfile)s">%(label_move)s</a> | ' % parmdict
267 else:
268 move_link = ''
269 if ext == '.draw':
270 viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&drawing=%(base)s">%(label_edit)s</a>' % parmdict
271 else:
272 viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&do=view&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&do=install&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&do=unzip&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> | %(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 = '&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.You are not allowed to attach a file to this page.