~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/action/AttachFile.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
    @license: GNU GPL, see COPYING for details.
28
28
"""
29
29
 
30
 
import os, time, zipfile, errno, datetime
31
 
from StringIO import StringIO
32
 
 
33
 
from werkzeug import http_date
 
30
import os, time, zipfile, mimetypes, errno
34
31
 
35
32
from MoinMoin import log
36
33
logging = log.getLogger(__name__)
37
34
 
38
 
# keep both imports below as they are, order is important:
39
 
from MoinMoin import wikiutil
40
 
import mimetypes
41
 
 
42
 
from MoinMoin import config, packages
 
35
from MoinMoin import config, wikiutil, packages
43
36
from MoinMoin.Page import Page
44
37
from MoinMoin.util import filesys, timefuncs
45
38
from MoinMoin.security.textcha import TextCha
46
 
from MoinMoin.events import FileAttachedEvent, FileRemovedEvent, send_event
47
 
from MoinMoin.support import tarfile
 
39
from MoinMoin.events import FileAttachedEvent, send_event
48
40
 
49
41
action_name = __name__.split('.')[-1]
50
42
 
85
77
        return u"/".join(pieces[:-1]), pieces[-1]
86
78
 
87
79
 
88
 
def get_action(request, filename, do):
89
 
    generic_do_mapping = {
90
 
        # do -> action
91
 
        'get': action_name,
92
 
        'view': action_name,
93
 
        'move': action_name,
94
 
        'del': action_name,
95
 
        'unzip': action_name,
96
 
        'install': action_name,
97
 
        'upload_form': action_name,
98
 
    }
99
 
    basename, ext = os.path.splitext(filename)
100
 
    do_mapping = request.cfg.extensions_mapping.get(ext, {})
101
 
    action = do_mapping.get(do, None)
102
 
    if action is None:
103
 
        # we have no special support for this,
104
 
        # look up whether we have generic support:
105
 
        action = generic_do_mapping.get(do, None)
106
 
    return action
107
 
 
108
 
 
109
 
def getAttachUrl(pagename, filename, request, addts=0, do='get'):
110
 
    """ Get URL that points to attachment `filename` of page `pagename`.
111
 
        For upload url, call with do='upload_form'.
112
 
        Returns the URL to do the specified "do" action or None,
113
 
        if this action is not supported.
114
 
    """
115
 
    action = get_action(request, filename, do)
116
 
    if action:
117
 
        args = dict(action=action, do=do, target=filename)
118
 
        if do not in ['get', 'view', # harmless
119
 
                      'modify', # just renders the applet html, which has own ticket
120
 
                      'move', # renders rename form, which has own ticket
121
 
            ]:
122
 
            # create a ticket for the not so harmless operations
123
 
            # we need action= here because the current action (e.g. "show" page
124
 
            # with a macro AttachList) may not be the linked-to action, e.g.
125
 
            # "AttachFile". Also, AttachList can list attachments of another page,
126
 
            # thus we need to give pagename= also.
127
 
            args['ticket'] = wikiutil.createTicket(request,
128
 
                                                   pagename=pagename, action=action_name)
129
 
        url = request.href(pagename, **args)
130
 
        return url
 
80
def attachUrl(request, pagename, filename=None, **kw):
 
81
    # filename is not used yet, but should be used later to make a sub-item url
 
82
    if kw:
 
83
        qs = '?%s' % wikiutil.makeQueryString(kw, want_unicode=False)
 
84
    else:
 
85
        qs = ''
 
86
    return "%s/%s%s" % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), qs)
 
87
 
 
88
 
 
89
def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get', drawing='', upload=False):
 
90
    """ Get URL that points to attachment `filename` of page `pagename`. """
 
91
    if upload:
 
92
        if not drawing:
 
93
            url = attachUrl(request, pagename, filename,
 
94
                            rename=wikiutil.taintfilename(filename), action=action_name)
 
95
        else:
 
96
            url = attachUrl(request, pagename, filename,
 
97
                            rename=wikiutil.taintfilename(filename), drawing=drawing, action=action_name)
 
98
    else:
 
99
        if not drawing:
 
100
            url = attachUrl(request, pagename, filename,
 
101
                            target=filename, action=action_name, do=do)
 
102
        else:
 
103
            url = attachUrl(request, pagename, filename,
 
104
                            drawing=drawing, action=action_name)
 
105
    if escaped:
 
106
        url = wikiutil.escape(url)
 
107
    return url
131
108
 
132
109
 
133
110
def getIndicator(request, pagename):
146
123
    fmt = request.formatter
147
124
    attach_count = _('[%d attachments]') % len(files)
148
125
    attach_icon = request.theme.make_icon('attach', vars={'attach_count': attach_count})
149
 
    attach_link = (fmt.url(1, request.href(pagename, action=action_name), rel='nofollow') +
 
126
    attach_link = (fmt.url(1, attachUrl(request, pagename, action=action_name), rel='nofollow') +
150
127
                   attach_icon +
151
128
                   fmt.url(0))
152
129
    return attach_link
210
187
        filecontent can be either a str (in memory file content),
211
188
        or an open file object (file content in e.g. a tempfile).
212
189
    """
 
190
    _ = request.getText
 
191
 
213
192
    # replace illegal chars
214
193
    target = wikiutil.taintfilename(target)
215
194
 
216
195
    # get directory, and possibly create it
217
196
    attach_dir = getAttachDir(request, pagename, create=1)
 
197
    # save file
218
198
    fpath = os.path.join(attach_dir, target).encode(config.charset)
219
 
 
220
199
    exists = os.path.exists(fpath)
221
 
    if exists:
222
 
        if overwrite:
223
 
            remove_attachment(request, pagename, target)
224
 
        else:
225
 
            raise AttachmentAlreadyExists
226
 
 
227
 
    # save file
228
 
    stream = open(fpath, 'wb')
229
 
    try:
230
 
        _write_stream(filecontent, stream)
231
 
    finally:
232
 
        stream.close()
233
 
 
234
 
    _addLogEntry(request, 'ATTNEW', pagename, target)
235
 
 
236
 
    filesize = os.path.getsize(fpath)
237
 
    event = FileAttachedEvent(request, pagename, target, filesize)
238
 
    send_event(event)
239
 
 
240
 
    return target, filesize
241
 
 
242
 
 
243
 
def remove_attachment(request, pagename, target):
244
 
    """ remove attachment <target> of page <pagename>
245
 
    """
246
 
    # replace illegal chars
247
 
    target = wikiutil.taintfilename(target)
248
 
 
249
 
    # get directory, do not create it
250
 
    attach_dir = getAttachDir(request, pagename, create=0)
251
 
    # remove file
252
 
    fpath = os.path.join(attach_dir, target).encode(config.charset)
253
 
    try:
 
200
    if exists and not overwrite:
 
201
        raise AttachmentAlreadyExists
 
202
    else:
 
203
        if exists:
 
204
            try:
 
205
                os.remove(fpath)
 
206
            except:
 
207
                pass
 
208
        stream = open(fpath, 'wb')
 
209
        try:
 
210
            _write_stream(filecontent, stream)
 
211
        finally:
 
212
            stream.close()
 
213
 
 
214
        _addLogEntry(request, 'ATTNEW', pagename, target)
 
215
 
254
216
        filesize = os.path.getsize(fpath)
255
 
        os.remove(fpath)
256
 
    except:
257
 
        # either it is gone already or we have no rights - not much we can do about it
258
 
        filesize = 0
259
 
    else:
260
 
        _addLogEntry(request, 'ATTDEL', pagename, target)
261
 
 
262
 
        event = FileRemovedEvent(request, pagename, target, filesize)
 
217
        event = FileAttachedEvent(request, pagename, target, filesize)
263
218
        send_event(event)
264
219
 
265
220
    return target, filesize
276
231
    """
277
232
    from MoinMoin.logfile import editlog
278
233
    t = wikiutil.timestamp2version(time.time())
279
 
    fname = wikiutil.url_quote(filename)
 
234
    fname = wikiutil.url_quote(filename, want_unicode=True)
280
235
 
281
236
    # Write to global log
282
237
    log = editlog.EditLog(request)
296
251
    _ = request.getText
297
252
 
298
253
    error = None
299
 
    if not request.values.get('target'):
 
254
    if not request.form.get('target', [''])[0]:
300
255
        error = _("Filename of attachment not specified!")
301
256
    else:
302
 
        filename = wikiutil.taintfilename(request.values['target'])
 
257
        filename = wikiutil.taintfilename(request.form['target'][0])
303
258
        fpath = getFilename(request, pagename, filename)
304
259
 
305
260
        if os.path.isfile(fpath):
340
295
        label_unzip = _("unzip")
341
296
        label_install = _("install")
342
297
 
343
 
        may_read = request.user.may.read(pagename)
344
 
        may_write = request.user.may.write(pagename)
345
 
        may_delete = request.user.may.delete(pagename)
346
 
 
347
298
        html.append(fmt.bullet_list(1))
348
299
        for file in files:
349
300
            mt = wikiutil.MimeType(filename=file)
356
307
                       }
357
308
 
358
309
            links = []
 
310
            may_delete = request.user.may.delete(pagename)
359
311
            if may_delete and not readonly:
360
312
                links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='del')) +
361
313
                             fmt.text(label_del) +
370
322
                         fmt.text(label_get) +
371
323
                         fmt.url(0))
372
324
 
373
 
            links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='view')) +
374
 
                         fmt.text(label_view) +
375
 
                         fmt.url(0))
376
 
 
377
 
            if may_write and not readonly:
378
 
                edit_url = getAttachUrl(pagename, file, request, do='modify')
379
 
                if edit_url:
380
 
                    links.append(fmt.url(1, edit_url) +
381
 
                                 fmt.text(label_edit) +
382
 
                                 fmt.url(0))
 
325
            if ext == '.draw':
 
326
                links.append(fmt.url(1, getAttachUrl(pagename, file, request, drawing=base)) +
 
327
                             fmt.text(label_edit) +
 
328
                             fmt.url(0))
 
329
            else:
 
330
                links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='view')) +
 
331
                             fmt.text(label_view) +
 
332
                             fmt.url(0))
383
333
 
384
334
            try:
385
335
                is_zipfile = zipfile.is_zipfile(fullpath)
386
 
                if is_zipfile and not readonly:
 
336
                if is_zipfile:
387
337
                    is_package = packages.ZipPackage(request, fullpath).isPackage()
388
338
                    if is_package and request.user.isSuperUser():
389
339
                        links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='install')) +
390
340
                                     fmt.text(label_install) +
391
341
                                     fmt.url(0))
392
342
                    elif (not is_package and mt.minor == 'zip' and
393
 
                          may_read and may_write and may_delete):
 
343
                          may_delete and
 
344
                          request.user.may.read(pagename) and
 
345
                          request.user.may.write(pagename)):
394
346
                        links.append(fmt.url(1, getAttachUrl(pagename, file, request, do='unzip')) +
395
347
                                     fmt.text(label_unzip) +
396
348
                                     fmt.url(0))
435
387
 
436
388
 
437
389
def error_msg(pagename, request, msg):
438
 
    msg = wikiutil.escape(msg)
439
390
    request.theme.add_msg(msg, "error")
440
391
    Page(request, pagename).send_page()
441
392
 
447
398
def send_link_rel(request, pagename):
448
399
    files = _get_files(request, pagename)
449
400
    for fname in files:
450
 
        url = getAttachUrl(pagename, fname, request, do='view')
 
401
        url = getAttachUrl(pagename, fname, request, do='view', escaped=1)
451
402
        request.write(u'<link rel="Appendix" title="%s" href="%s">\n' % (
452
 
                      wikiutil.escape(fname, 1),
453
 
                      wikiutil.escape(url, 1)))
 
403
                      wikiutil.escape(fname), url))
 
404
 
 
405
 
 
406
def send_hotdraw(pagename, request):
 
407
    _ = request.getText
 
408
 
 
409
    now = time.time()
 
410
    pubpath = request.cfg.url_prefix_static + "/applets/TWikiDrawPlugin"
 
411
    basename = request.form['drawing'][0]
 
412
    drawpath = getAttachUrl(pagename, basename + '.draw', request, escaped=1)
 
413
    pngpath = getAttachUrl(pagename, basename + '.png', request, escaped=1)
 
414
    pagelink = attachUrl(request, pagename, '', action=action_name, ts=now)
 
415
    helplink = Page(request, "HelpOnActions/AttachFile").url(request)
 
416
    savelink = attachUrl(request, pagename, '', action=action_name, do='savedrawing')
 
417
    #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
 
418
                                           # request, {'savename': request.form['drawing'][0]+'.draw'}
 
419
    #savelink = '/cgi-bin/dumpform.bat'
 
420
 
 
421
    timestamp = '&amp;ts=%s' % now
 
422
 
 
423
    request.write('<h2>' + _("Edit drawing") + '</h2>')
 
424
    request.write("""
 
425
<p>
 
426
<img src="%(pngpath)s%(timestamp)s">
 
427
<applet code="CH.ifa.draw.twiki.TWikiDraw.class"
 
428
        archive="%(pubpath)s/twikidraw.jar" width="640" height="480">
 
429
<param name="drawpath" value="%(drawpath)s">
 
430
<param name="pngpath"  value="%(pngpath)s">
 
431
<param name="savepath" value="%(savelink)s">
 
432
<param name="basename" value="%(basename)s">
 
433
<param name="viewpath" value="%(pagelink)s">
 
434
<param name="helppath" value="%(helplink)s">
 
435
<strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example.
 
436
</applet>
 
437
</p>""" % {
 
438
    'pngpath': pngpath, 'timestamp': timestamp,
 
439
    'pubpath': pubpath, 'drawpath': drawpath,
 
440
    'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink,
 
441
    'basename': basename
 
442
})
 
443
 
454
444
 
455
445
def send_uploadform(pagename, request):
456
446
    """ Send the HTML code for the list of already stored attachments and
470
460
    if writeable:
471
461
        request.write('<h2>' + _("New Attachment") + '</h2>')
472
462
        request.write("""
473
 
<form action="%(url)s" method="POST" enctype="multipart/form-data">
 
463
<form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
474
464
<dl>
475
465
<dt>%(upload_label_file)s</dt>
476
466
<dd><input type="file" name="file" size="50"></dd>
477
 
<dt>%(upload_label_target)s</dt>
478
 
<dd><input type="text" name="target" size="50" value="%(target)s"></dd>
 
467
<dt>%(upload_label_rename)s</dt>
 
468
<dd><input type="text" name="rename" size="50" value="%(rename)s"></dd>
479
469
<dt>%(upload_label_overwrite)s</dt>
480
470
<dd><input type="checkbox" name="overwrite" value="1" %(overwrite_checked)s></dd>
481
471
</dl>
483
473
<p>
484
474
<input type="hidden" name="action" value="%(action_name)s">
485
475
<input type="hidden" name="do" value="upload">
486
 
<input type="hidden" name="ticket" value="%(ticket)s">
487
476
<input type="submit" value="%(upload_button)s">
488
477
</p>
489
478
</form>
490
479
""" % {
491
 
    'url': request.href(pagename),
 
480
    'baseurl': request.getScriptname(),
 
481
    'pagename': wikiutil.quoteWikinameURL(pagename),
492
482
    'action_name': action_name,
493
483
    'upload_label_file': _('File to upload'),
494
 
    'upload_label_target': _('Rename to'),
495
 
    'target': wikiutil.escape(request.values.get('target', ''), 1),
 
484
    'upload_label_rename': _('Rename to'),
 
485
    'rename': request.form.get('rename', [''])[0],
496
486
    'upload_label_overwrite': _('Overwrite existing attachment of same name'),
497
 
    'overwrite_checked': ('', 'checked')[request.form.get('overwrite', '0') == '1'],
 
487
    'overwrite_checked': ('', 'checked')[request.form.get('overwrite', ['0'])[0] == '1'],
498
488
    'upload_button': _('Upload'),
499
489
    'textcha': TextCha(request).render(),
500
 
    'ticket': wikiutil.createTicket(request),
501
490
})
502
491
 
503
492
    request.write('<h2>' + _("Attached Files") + '</h2>')
506
495
    if not writeable:
507
496
        request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.'))
508
497
 
 
498
    if writeable and request.form.get('drawing', [None])[0]:
 
499
        send_hotdraw(pagename, request)
 
500
 
 
501
 
509
502
#############################################################################
510
503
### Web interface for file upload, viewing and deletion
511
504
#############################################################################
514
507
    """ Main dispatcher for the 'AttachFile' action. """
515
508
    _ = request.getText
516
509
 
517
 
    do = request.values.get('do', 'upload_form')
518
 
    handler = globals().get('_do_%s' % do)
 
510
    do = request.form.get('do', ['upload_form'])
 
511
    handler = globals().get('_do_%s' % do[0])
519
512
    if handler:
520
513
        msg = handler(pagename, request)
521
514
    else:
522
 
        msg = _('Unsupported AttachFile sub-action: %s') % do
 
515
        msg = _('Unsupported AttachFile sub-action: %s') % (wikiutil.escape(do[0]), )
523
516
    if msg:
524
517
        error_msg(pagename, request, msg)
525
518
 
529
522
 
530
523
 
531
524
def upload_form(pagename, request, msg=''):
532
 
    if msg:
533
 
        msg = wikiutil.escape(msg)
534
525
    _ = request.getText
535
526
 
 
527
    request.emit_http_headers()
536
528
    # Use user interface language for this generated page
537
529
    request.setContentLanguage(request.lang)
538
530
    request.theme.add_msg(msg, "dialog")
544
536
    request.theme.send_closing_html()
545
537
 
546
538
 
 
539
def preprocess_filename(filename):
 
540
    """ preprocess the filename we got from upload form,
 
541
        strip leading drive and path (IE misbehaviour)
 
542
    """
 
543
    if filename and len(filename) > 1 and (filename[1] == ':' or filename[0] == '\\'): # C:.... or \path... or \\server\...
 
544
        bsindex = filename.rfind('\\')
 
545
        if bsindex >= 0:
 
546
            filename = filename[bsindex+1:]
 
547
    return filename
 
548
 
 
549
 
547
550
def _do_upload(pagename, request):
548
551
    _ = request.getText
549
 
 
550
 
    if not wikiutil.checkTicket(request, request.form.get('ticket', '')):
551
 
        return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.upload' }
552
 
 
553
552
    # Currently we only check TextCha for upload (this is what spammers ususally do),
554
553
    # but it could be extended to more/all attachment write access
555
554
    if not TextCha(request).check_answer_from_form():
556
555
        return _('TextCha: Wrong answer! Go back and try again...')
557
556
 
558
557
    form = request.form
559
 
 
560
 
    file_upload = request.files.get('file')
561
 
    if not file_upload:
562
 
        # This might happen when trying to upload file names
563
 
        # with non-ascii characters on Safari.
564
 
        return _("No file content. Delete non ASCII characters from the file name and try again.")
565
 
 
 
558
    overwrite = form.get('overwrite', [u'0'])[0]
566
559
    try:
567
 
        overwrite = int(form.get('overwrite', '0'))
 
560
        overwrite = int(overwrite)
568
561
    except:
569
562
        overwrite = 0
570
563
 
574
567
    if overwrite and not request.user.may.delete(pagename):
575
568
        return _('You are not allowed to overwrite a file attachment of this page.')
576
569
 
577
 
    target = form.get('target', u'').strip()
578
 
    if not target:
579
 
        target = file_upload.filename or u''
 
570
    filename = form.get('file__filename__')
 
571
    rename = form.get('rename', [u''])[0].strip()
 
572
    if rename:
 
573
        target = rename
 
574
    else:
 
575
        target = filename
580
576
 
 
577
    target = preprocess_filename(target)
581
578
    target = wikiutil.clean_input(target)
582
579
 
583
580
    if not target:
584
581
        return _("Filename of attachment not specified!")
585
582
 
 
583
    # get file content
 
584
    filecontent = request.form.get('file', [None])[0]
 
585
    if filecontent is None:
 
586
        # This might happen when trying to upload file names
 
587
        # with non-ascii characters on Safari.
 
588
        return _("No file content. Delete non ASCII characters from the file name and try again.")
 
589
 
586
590
    # add the attachment
587
591
    try:
588
 
        target, bytes = add_attachment(request, pagename, target, file_upload.stream, overwrite=overwrite)
 
592
        target, bytes = add_attachment(request, pagename, target, filecontent, overwrite=overwrite)
589
593
        msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
590
594
                " with %(bytes)d bytes saved.") % {
591
 
                'target': target, 'filename': file_upload.filename, 'bytes': bytes}
 
595
                'target': target, 'filename': filename, 'bytes': bytes}
592
596
    except AttachmentAlreadyExists:
593
597
        msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
594
 
            'target': target, 'filename': file_upload.filename}
 
598
            'target': target, 'filename': filename}
595
599
 
596
600
    # return attachment list
597
601
    upload_form(pagename, request, msg)
598
602
 
599
603
 
600
 
class ContainerItem:
601
 
    """ A storage container (multiple objects in 1 tarfile) """
602
 
 
603
 
    def __init__(self, request, pagename, containername):
604
 
        self.request = request
605
 
        self.pagename = pagename
606
 
        self.containername = containername
607
 
        self.container_filename = getFilename(request, pagename, containername)
608
 
 
609
 
    def member_url(self, member):
610
 
        """ return URL for accessing container member
611
 
            (we use same URL for get (GET) and put (POST))
612
 
        """
613
 
        url = Page(self.request, self.pagename).url(self.request, {
614
 
            'action': 'AttachFile',
615
 
            'do': 'box',  # shorter to type than 'container'
616
 
            'target': self.containername,
617
 
            #'member': member,
618
 
        })
619
 
        return url + '&member=%s' % member
620
 
        # member needs to be last in qs because twikidraw looks for "file extension" at the end
621
 
 
622
 
    def get(self, member):
623
 
        """ return a file-like object with the member file data
624
 
        """
625
 
        tf = tarfile.TarFile(self.container_filename)
626
 
        return tf.extractfile(member)
627
 
 
628
 
    def put(self, member, content, content_length=None):
629
 
        """ save data into a container's member """
630
 
        tf = tarfile.TarFile(self.container_filename, mode='a')
631
 
        if isinstance(member, unicode):
632
 
            member = member.encode('utf-8')
633
 
        ti = tarfile.TarInfo(member)
634
 
        if isinstance(content, str):
635
 
            if content_length is None:
636
 
                content_length = len(content)
637
 
            content = StringIO(content) # we need a file obj
638
 
        elif not hasattr(content, 'read'):
639
 
            logging.error("unsupported content object: %r" % content)
640
 
            raise
641
 
        assert content_length >= 0  # we don't want -1 interpreted as 4G-1
642
 
        ti.size = content_length
643
 
        tf.addfile(ti, content)
644
 
        tf.close()
645
 
 
646
 
    def truncate(self):
647
 
        f = open(self.container_filename, 'w')
648
 
        f.close()
649
 
 
650
 
    def exists(self):
651
 
        return os.path.exists(self.container_filename)
 
604
def _do_savedrawing(pagename, request):
 
605
    _ = request.getText
 
606
 
 
607
    if not request.user.may.write(pagename):
 
608
        return _('You are not allowed to save a drawing on this page.')
 
609
 
 
610
    filename = request.form['filename'][0]
 
611
    filecontent = request.form['filepath'][0]
 
612
 
 
613
    basepath, basename = os.path.split(filename)
 
614
    basename, ext = os.path.splitext(basename)
 
615
 
 
616
    # get directory, and possibly create it
 
617
    attach_dir = getAttachDir(request, pagename, create=1)
 
618
    savepath = os.path.join(attach_dir, basename + ext)
 
619
 
 
620
    if ext == '.draw':
 
621
        _addLogEntry(request, 'ATTDRW', pagename, basename + ext)
 
622
        filecontent = filecontent.read() # read file completely into memory
 
623
        filecontent = filecontent.replace("\r", "")
 
624
    elif ext == '.map':
 
625
        filecontent = filecontent.read() # read file completely into memory
 
626
        filecontent = filecontent.strip()
 
627
 
 
628
    if filecontent:
 
629
        # filecontent is either a file or a non-empty string
 
630
        stream = open(savepath, 'wb')
 
631
        try:
 
632
            _write_stream(filecontent, stream)
 
633
        finally:
 
634
            stream.close()
 
635
    else:
 
636
        # filecontent is empty string (e.g. empty map file), delete the target file
 
637
        try:
 
638
            os.unlink(savepath)
 
639
        except OSError, err:
 
640
            if err.errno != errno.ENOENT: # no such file
 
641
                raise
 
642
 
 
643
    # touch attachment directory to invalidate cache if new map is saved
 
644
    if ext == '.map':
 
645
        os.utime(attach_dir, None)
 
646
 
 
647
    request.emit_http_headers()
 
648
    request.write("OK")
 
649
 
652
650
 
653
651
def _do_del(pagename, request):
654
652
    _ = request.getText
655
653
 
656
 
    if not wikiutil.checkTicket(request, request.args.get('ticket', '')):
657
 
        return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.del' }
658
 
 
659
654
    pagename, filename, fpath = _access_file(pagename, request)
660
655
    if not request.user.may.delete(pagename):
661
656
        return _('You are not allowed to delete attachments on this page.')
662
657
    if not filename:
663
658
        return # error msg already sent in _access_file
664
659
 
665
 
    remove_attachment(request, pagename, filename)
 
660
    # delete file
 
661
    os.remove(fpath)
 
662
    _addLogEntry(request, 'ATTDEL', pagename, filename)
 
663
 
 
664
    if request.cfg.xapian_search:
 
665
        from MoinMoin.search.Xapian import Index
 
666
        index = Index(request)
 
667
        if index.exists:
 
668
            index.remove_item(pagename, filename)
666
669
 
667
670
    upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename})
668
671
 
685
688
            return
686
689
 
687
690
        if new_attachment_path != attachment_path:
688
 
            filesize = os.path.getsize(attachment_path)
 
691
            # move file
689
692
            filesys.rename(attachment_path, new_attachment_path)
690
693
            _addLogEntry(request, 'ATTDEL', pagename, attachment)
691
 
            event = FileRemovedEvent(request, pagename, attachment, filesize)
692
 
            send_event(event)
693
694
            _addLogEntry(request, 'ATTNEW', new_pagename, new_attachment)
694
 
            event = FileAttachedEvent(request, new_pagename, new_attachment, filesize)
695
 
            send_event(event)
696
695
            upload_form(pagename, request,
697
696
                        msg=_("Attachment '%(pagename)s/%(filename)s' moved to '%(new_pagename)s/%(new_filename)s'.") % {
698
697
                            'pagename': pagename,
711
710
 
712
711
    if 'cancel' in request.form:
713
712
        return _('Move aborted!')
714
 
    if not wikiutil.checkTicket(request, request.form.get('ticket', '')):
715
 
        return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.move' }
 
713
    if not wikiutil.checkTicket(request, request.form['ticket'][0]):
 
714
        return _('Please use the interactive user interface to move attachments!')
716
715
    if not request.user.may.delete(pagename):
717
716
        return _('You are not allowed to move attachments from this page.')
718
717
 
719
718
    if 'newpagename' in request.form:
720
 
        new_pagename = request.form.get('newpagename')
 
719
        new_pagename = request.form.get('newpagename')[0]
721
720
    else:
722
721
        upload_form(pagename, request, msg=_("Move aborted because new page name is empty."))
723
722
    if 'newattachmentname' in request.form:
724
 
        new_attachment = request.form.get('newattachmentname')
 
723
        new_attachment = request.form.get('newattachmentname')[0]
725
724
        if new_attachment != wikiutil.taintfilename(new_attachment):
726
725
            upload_form(pagename, request, msg=_("Please use a valid filename for attachment '%(filename)s'.") % {
727
726
                                  'filename': new_attachment})
729
728
    else:
730
729
        upload_form(pagename, request, msg=_("Move aborted because new attachment name is empty."))
731
730
 
732
 
    attachment = request.form.get('oldattachmentname')
 
731
    attachment = request.form.get('oldattachmentname')[0]
733
732
    move_file(request, pagename, new_pagename, attachment, new_attachment)
734
733
 
735
734
 
744
743
 
745
744
    # move file
746
745
    d = {'action': action_name,
747
 
         'url': request.href(pagename),
 
746
         'baseurl': request.getScriptname(),
748
747
         'do': 'attachment_move',
749
748
         'ticket': wikiutil.createTicket(request),
750
 
         'pagename': wikiutil.escape(pagename, 1),
751
 
         'attachment_name': wikiutil.escape(filename, 1),
 
749
         'pagename': pagename,
 
750
         'pagename_quoted': wikiutil.quoteWikinameURL(pagename),
 
751
         'attachment_name': filename,
752
752
         'move': _('Move'),
753
753
         'cancel': _('Cancel'),
754
754
         'newname_label': _("New page name"),
755
755
         'attachment_label': _("New attachment name"),
756
756
        }
757
757
    formhtml = '''
758
 
<form action="%(url)s" method="POST">
 
758
<form action="%(baseurl)s/%(pagename_quoted)s" method="POST">
759
759
<input type="hidden" name="action" value="%(action)s">
760
760
<input type="hidden" name="do" value="%(do)s">
761
761
<input type="hidden" name="ticket" value="%(ticket)s">
787
787
    return thispage.send_page()
788
788
 
789
789
 
790
 
def _do_box(pagename, request):
791
 
    _ = request.getText
792
 
 
793
 
    pagename, filename, fpath = _access_file(pagename, request)
794
 
    if not request.user.may.read(pagename):
795
 
        return _('You are not allowed to get attachments from this page.')
796
 
    if not filename:
797
 
        return # error msg already sent in _access_file
798
 
 
799
 
    timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
800
 
    if_modified = request.if_modified_since
801
 
    if if_modified and if_modified >= timestamp:
802
 
        request.status_code = 304
803
 
    else:
804
 
        ci = ContainerItem(request, pagename, filename)
805
 
        filename = wikiutil.taintfilename(request.values['member'])
806
 
        mt = wikiutil.MimeType(filename=filename)
807
 
        content_type = mt.content_type()
808
 
        mime_type = mt.mime_type()
809
 
 
810
 
        # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
811
 
        # There is no solution that is compatible to IE except stripping non-ascii chars
812
 
        filename_enc = filename.encode(config.charset)
813
 
 
814
 
        # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
815
 
        # we just let the user store them to disk ('attachment').
816
 
        # For safe files, we directly show them inline (this also works better for IE).
817
 
        dangerous = mime_type in request.cfg.mimetypes_xss_protect
818
 
        content_dispo = dangerous and 'attachment' or 'inline'
819
 
 
820
 
        now = time.time()
821
 
        request.headers['Date'] = http_date(now)
822
 
        request.headers['Content-Type'] = content_type
823
 
        request.headers['Last-Modified'] = http_date(timestamp)
824
 
        request.headers['Expires'] = http_date(now - 365 * 24 * 3600)
825
 
        #request.headers['Content-Length'] = os.path.getsize(fpath)
826
 
        content_dispo_string = '%s; filename="%s"' % (content_dispo, filename_enc)
827
 
        request.headers['Content-Disposition'] = content_dispo_string
828
 
 
829
 
        # send data
830
 
        request.send_file(ci.get(filename))
831
 
 
832
 
 
833
790
def _do_get(pagename, request):
834
791
    _ = request.getText
835
792
 
839
796
    if not filename:
840
797
        return # error msg already sent in _access_file
841
798
 
842
 
    timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
843
 
    if_modified = request.if_modified_since
844
 
    if if_modified and if_modified >= timestamp:
845
 
        request.status_code = 304
 
799
    timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath)))
 
800
    if request.if_modified_since == timestamp:
 
801
        request.emit_http_headers(["Status: 304 Not modified"])
846
802
    else:
847
803
        mt = wikiutil.MimeType(filename=filename)
848
804
        content_type = mt.content_type()
858
814
        dangerous = mime_type in request.cfg.mimetypes_xss_protect
859
815
        content_dispo = dangerous and 'attachment' or 'inline'
860
816
 
861
 
        now = time.time()
862
 
        request.headers['Date'] = http_date(now)
863
 
        request.headers['Content-Type'] = content_type
864
 
        request.headers['Last-Modified'] = http_date(timestamp)
865
 
        request.headers['Expires'] = http_date(now - 365 * 24 * 3600)
866
 
        request.headers['Content-Length'] = os.path.getsize(fpath)
867
 
        content_dispo_string = '%s; filename="%s"' % (content_dispo, filename_enc)
868
 
        request.headers['Content-Disposition'] = content_dispo_string
 
817
        request.emit_http_headers([
 
818
            'Content-Type: %s' % content_type,
 
819
            'Last-Modified: %s' % timestamp,
 
820
            'Content-Length: %d' % os.path.getsize(fpath),
 
821
            'Content-Disposition: %s; filename="%s"' % (content_dispo, filename_enc),
 
822
        ])
869
823
 
870
824
        # send data
871
825
        request.send_file(open(fpath, 'rb'))
874
828
def _do_install(pagename, request):
875
829
    _ = request.getText
876
830
 
877
 
    if not wikiutil.checkTicket(request, request.args.get('ticket', '')):
878
 
        return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.install' }
879
 
 
880
831
    pagename, target, targetpath = _access_file(pagename, request)
881
832
    if not request.user.isSuperUser():
882
833
        return _('You are not allowed to install files.')
887
838
 
888
839
    if package.isPackage():
889
840
        if package.installPackage():
890
 
            msg = _("Attachment '%(filename)s' installed.") % {'filename': target}
 
841
            msg = _("Attachment '%(filename)s' installed.") % {'filename': wikiutil.escape(target)}
891
842
        else:
892
 
            msg = _("Installation of '%(filename)s' failed.") % {'filename': target}
 
843
            msg = _("Installation of '%(filename)s' failed.") % {'filename': wikiutil.escape(target)}
893
844
        if package.msg:
894
 
            msg += " " + package.msg
 
845
            msg += "<br><pre>%s</pre>" % wikiutil.escape(package.msg)
895
846
    else:
896
 
        msg = _('The file %s is not a MoinMoin package file.') % target
 
847
        msg = _('The file %s is not a MoinMoin package file.') % wikiutil.escape(target)
897
848
 
898
849
    upload_form(pagename, request, msg=msg)
899
850
 
900
851
 
901
852
def _do_unzip(pagename, request, overwrite=False):
902
853
    _ = request.getText
903
 
 
904
 
    if not wikiutil.checkTicket(request, request.args.get('ticket', '')):
905
 
        return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.unzip' }
906
 
 
907
854
    pagename, filename, fpath = _access_file(pagename, request)
 
855
 
908
856
    if not (request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename)):
909
857
        return _('You are not allowed to unzip attachments of this page.')
910
858
 
1000
948
        logging.exception("An exception within zip file attachment handling occurred:")
1001
949
        msg = _("A severe error occurred:") + ' ' + str(err)
1002
950
 
1003
 
    upload_form(pagename, request, msg=msg)
 
951
    upload_form(pagename, request, msg=wikiutil.escape(msg))
1004
952
 
1005
953
 
1006
954
def send_viewfile(pagename, request):
1019
967
            fmt.url(0))
1020
968
    request.write('%s<br><br>' % link)
1021
969
 
1022
 
    if filename.endswith('.tdraw') or filename.endswith('.adraw'):
1023
 
        request.write(fmt.attachment_drawing(filename, ''))
1024
 
        return
1025
 
 
1026
970
    mt = wikiutil.MimeType(filename=filename)
1027
971
 
1028
972
    # destinguishs if browser need a plugin in place
1029
973
    if mt.major == 'image' and mt.minor in config.browser_supported_images:
1030
 
        url = getAttachUrl(pagename, filename, request)
1031
974
        request.write('<img src="%s" alt="%s">' % (
1032
 
            wikiutil.escape(url, 1),
 
975
            getAttachUrl(pagename, filename, request, escaped=1),
1033
976
            wikiutil.escape(filename, 1)))
1034
977
        return
1035
978
    elif mt.major == 'text':
1095
1038
                fmt.url(0))
1096
1039
        request.write('For using an external program follow this link %s' % link)
1097
1040
        return
1098
 
    request.write(m.execute('EmbedObject', u'target="%s", pagename="%s"' % (filename, pagename)))
 
1041
    request.write(m.execute('EmbedObject', u'target=%s, pagename=%s' % (filename, pagename)))
1099
1042
    return
1100
1043
 
1101
1044
 
1109
1052
    if not filename:
1110
1053
        return
1111
1054
 
1112
 
    request.formatter.page = Page(request, pagename)
1113
 
 
1114
1055
    # send header & title
 
1056
    request.emit_http_headers()
1115
1057
    # Use user interface language for this generated page
1116
1058
    request.setContentLanguage(request.lang)
1117
1059
    title = _('attachment:%(filename)s of %(pagename)s') % {
1155
1097
            for filename in files:
1156
1098
                filepath = os.path.join(page_dir, filename)
1157
1099
                data.addRow((
1158
 
                    (Page(request, pagename).link_to(request,
1159
 
                                querystr="action=AttachFile"), wikiutil.escape(pagename, 1)),
 
1100
                    Page(request, pagename).link_to(request, querystr="action=AttachFile"),
1160
1101
                    wikiutil.escape(filename.decode(config.charset)),
1161
1102
                    os.path.getsize(filepath),
1162
1103
                ))
1165
1106
        from MoinMoin.widget.browser import DataBrowserWidget
1166
1107
 
1167
1108
        browser = DataBrowserWidget(request)
1168
 
        browser.setData(data, sort_columns=[0, 1])
1169
 
        return browser.render(method="GET")
 
1109
        browser.setData(data)
 
1110
        return browser.toHTML()
1170
1111
 
1171
1112
    return ''
1172
1113