|
Lines 39-44
Link Here
|
| 39 |
import os |
39 |
import os |
| 40 |
import time |
40 |
import time |
| 41 |
import json |
41 |
import json |
|
|
42 |
import tempfile |
| 43 |
import os.path |
| 42 |
|
44 |
|
| 43 |
import notifier |
45 |
import notifier |
| 44 |
import notifier.signals as signals |
46 |
import notifier.signals as signals |
|
Lines 70-75
Link Here
|
| 70 |
|
72 |
|
| 71 |
TEMPUPLOADDIR = '/var/tmp/univention-management-console-frontend' |
73 |
TEMPUPLOADDIR = '/var/tmp/univention-management-console-frontend' |
| 72 |
|
74 |
|
|
|
75 |
class UploadManager(dict): |
| 76 |
def add(self, request_id, data): |
| 77 |
tmpfile = tempfile.NamedTemporaryFile(prefix=request_id, dir=TEMPUPLOADDIR, delete=False) |
| 78 |
tmpfile.write(data) |
| 79 |
tmpfile.close() |
| 80 |
if request_id in self: |
| 81 |
self[request_id].append(tmpfile.name) |
| 82 |
else: |
| 83 |
self[request_id] = [tmpfile.name] |
| 84 |
|
| 85 |
return tmpfile.name |
| 86 |
|
| 87 |
def cleanup(self, request_id): |
| 88 |
if request_id in self: |
| 89 |
filenames = self[request_id] |
| 90 |
for filename in filenames: |
| 91 |
if os.path.isfile(filename): |
| 92 |
os.unlink(filename) |
| 93 |
del self[request_id] |
| 94 |
return True |
| 95 |
|
| 96 |
return False |
| 97 |
|
| 98 |
_upload_manager = UploadManager() |
| 99 |
|
| 73 |
class State( signals.Provider ): |
100 |
class State( signals.Provider ): |
| 74 |
"""Holds information about the state of an active session |
101 |
"""Holds information about the state of an active session |
| 75 |
|
102 |
|
|
Lines 165-170
Link Here
|
| 165 |
if msg.command == 'EXIT' and 'internal' in msg.arguments: |
192 |
if msg.command == 'EXIT' and 'internal' in msg.arguments: |
| 166 |
return |
193 |
return |
| 167 |
|
194 |
|
|
|
195 |
if msg.command == 'UPLOAD': |
| 196 |
# remove temporary uploaded files |
| 197 |
_upload_manager.cleanup(msg.id) |
| 198 |
|
| 168 |
self.signal_emit( 'result', msg ) |
199 |
self.signal_emit( 'result', msg ) |
| 169 |
|
200 |
|
| 170 |
def pid( self ): |
201 |
def pid( self ): |
|
Lines 522-587
Link Here
|
| 522 |
module._inactivity_counter = MODULE_INACTIVITY_TIMER |
553 |
module._inactivity_counter = MODULE_INACTIVITY_TIMER |
| 523 |
|
554 |
|
| 524 |
def handle_request_upload( self, msg ): |
555 |
def handle_request_upload( self, msg ): |
| 525 |
"""Handles an UPLOAD request. The command is used for the HTTP |
556 |
"""Handles an UPLOAD request. |
| 526 |
access to the UMC server. Incoming HTTP requests that send a |
557 |
The request options is a dictionary of dictionaries which |
| 527 |
list of files are passed on to the UMC server by storing the |
558 |
can either be treaten as files or as normal strings |
| 528 |
files in temporary files and passing the information about the |
|
|
| 529 |
files to the UMC server in the options of the request. The |
| 530 |
request options must be a list of dictionaries. Each dictionary |
| 531 |
must contain the following keys: |
| 532 |
|
559 |
|
|
|
560 |
|
| 533 |
* *filename* -- the original name of the file |
561 |
* *filename* -- the original name of the file |
| 534 |
* *name* -- name of the form field |
562 |
* *name* -- name of the form field |
| 535 |
* *tmpfile* -- filename of the temporary file |
563 |
* *content* -- the content of the file |
|
|
564 |
* *mimetype* -- the file mimetype |
| 565 |
* *enconding* -- encoding of the content if the content is encoded |
| 536 |
|
566 |
|
| 537 |
:param Request msg: UMCP request |
567 |
:param Request msg: UMCP request |
| 538 |
""" |
568 |
""" |
| 539 |
# request.options = ( { 'filename' : store.filename, 'name' : store.name, 'tmpfile' : tmpfile } ) |
|
|
| 540 |
|
569 |
|
| 541 |
if not isinstance( msg.options, ( list, tuple ) ): |
570 |
if isinstance(msg.options, list): |
|
|
571 |
# backward compatibility |
| 572 |
try: |
| 573 |
msg.options = dict((val['name'], val) for val in msg.options) |
| 574 |
except (TypeError, KeyError): |
| 575 |
raise InvalidOptionsError |
| 576 |
|
| 577 |
if not isinstance(msg.options, dict): |
| 542 |
raise InvalidOptionsError |
578 |
raise InvalidOptionsError |
| 543 |
|
579 |
|
| 544 |
for file_obj in msg.options: |
580 |
def sanitize_item(item): |
| 545 |
# check if required options exists and file_obj is a dict |
581 |
if not isinstance(item, dict): |
| 546 |
try: |
582 |
raise InvalidOptionsError |
| 547 |
tmpfilename, filename, name = file_obj['tmpfile'], file_obj['filename'], file_obj['name'] |
|
|
| 548 |
except: |
| 549 |
raise InvalidOptionsError('required options "tmpfile", "filename" or "name" is missing') |
| 550 |
|
583 |
|
| 551 |
# limit files to tmpdir |
584 |
res = dict((key, item.get(key, '')) for key in ('filename', 'name', 'content', 'mimetype')) |
| 552 |
if not os.path.realpath(tmpfilename).startswith(TEMPUPLOADDIR): |
585 |
# make sure some fields are strings |
| 553 |
raise InvalidOptionsError('invalid file: invalid path') |
586 |
if not all(isinstance(res[k], str) for k in ('filename', 'name', 'mimetype')): |
|
|
587 |
raise InvalidOptionsError |
| 554 |
|
588 |
|
| 555 |
# check if file exists |
589 |
# if the field is a file the content must be a string |
| 556 |
if not os.path.isfile( tmpfilename ): |
590 |
if res['filename'] and not isinstance(res['content'], str): |
| 557 |
raise InvalidOptionsError('invalid file: file does not exists') |
591 |
raise InvalidOptionsError |
| 558 |
|
592 |
|
| 559 |
# don't accept files bigger than umc/server/upload/max |
593 |
# remove the possibility of XSS and path overwriting |
| 560 |
st = os.stat( tmpfilename ) |
594 |
for c in '<>/': |
| 561 |
max_size = int( ucr.get( 'umc/server/upload/max', 64 ) ) * 1024 |
595 |
res['filename'] = res['filename'].replace(c, '_') |
| 562 |
if st.st_size > max_size: |
|
|
| 563 |
raise InvalidOptionsError('filesize is too large, maximum allowed filesize is %d' % (max_size,)) |
| 564 |
|
596 |
|
| 565 |
if msg.arguments and msg.arguments[0] not in ('', '/'): |
597 |
return res |
| 566 |
# The request has arguments, so it will be treaten as COMMAND |
598 |
|
| 567 |
self.handle_request_command(msg) |
599 |
if not msg.arguments or msg.arguments[0] in ('', '/'): |
|
|
600 |
# The request is an generic UPLOAD command ('/upload') |
| 601 |
result = {} |
| 602 |
for item in msg.options.values(): |
| 603 |
# read tmpfile and convert to base64 |
| 604 |
res = sanitize_item(item) |
| 605 |
name = res['name'] |
| 606 |
|
| 607 |
# bring content into base64 (!) |
| 608 |
if item.get('encoding') != 'base64': |
| 609 |
res['content'] = base64.encodestring(res['content']) |
| 610 |
result[name] = res |
| 611 |
|
| 612 |
response = Response( msg ) |
| 613 |
response.result = result |
| 614 |
response.status = SUCCESS |
| 615 |
|
| 616 |
self.signal_emit( 'response', response ) |
| 568 |
return |
617 |
return |
| 569 |
|
618 |
|
| 570 |
# The request is an generic UPLOAD command (/upload) |
619 |
ucr.load() |
| 571 |
result = [] |
620 |
max_upload_size = int(ucr.get('umc/server/upload/max', 64)) * 1024 |
| 572 |
for file_obj in msg.options: |
621 |
min_free_space = int(ucr.get('umc/server/upload/min_free_space', 51200)) # kilobyte |
| 573 |
# read tmpfile and convert to base64 |
|
|
| 574 |
tmpfilename, filename, name = file_obj['tmpfile'], file_obj['filename'], file_obj['name'] |
| 575 |
with open( tmpfilename ) as buf: |
| 576 |
b64buf = base64.b64encode( buf.read() ) |
| 577 |
result.append( { 'filename' : filename, 'name' : name, 'content' : b64buf } ) |
| 578 |
|
622 |
|
| 579 |
response = Response( msg ) |
623 |
# check if enough free space is available |
| 580 |
response.result = result |
624 |
s = os.statvfs(TEMPUPLOADDIR) |
| 581 |
response.status = SUCCESS |
625 |
free_disk_space = s.f_bavail * s.f_frsize / 1024 # kilobyte |
|
|
626 |
if free_disk_space < min_free_space: |
| 627 |
CORE.error('There is not enough free space on disk to upload files') |
| 628 |
raise InvalidOptionsError('There is not enough free space on disk to upload files') |
| 582 |
|
629 |
|
| 583 |
self.signal_emit( 'response', response ) |
630 |
options = {} |
|
|
631 |
for item in msg.options.values(): |
| 632 |
res = sanitize_item(item) |
| 633 |
name, filename = res['name'], res['filename'] |
| 584 |
|
634 |
|
|
|
635 |
if not filename: |
| 636 |
# field is not a file, it's a string |
| 637 |
res = res['content'] |
| 638 |
else: |
| 639 |
content = res.pop('content') |
| 640 |
|
| 641 |
# don't accept files bigger than umc/server/upload/max |
| 642 |
clen = len(content) |
| 643 |
if clen > max_upload_size: |
| 644 |
CORE.warn('File of size %d could not be uploaded' % (clen)) |
| 645 |
raise InvalidOptionsError('Filesize is too large, maximum allowed filesize is %d' % (max_upload_size,)) |
| 646 |
|
| 647 |
# decode the file |
| 648 |
if item.get('encoding') == 'base64': |
| 649 |
content = base64.decodestring(content) |
| 650 |
|
| 651 |
# write content to disk |
| 652 |
tmpfile = _upload_manager.add(msg.id, content) |
| 653 |
res['tmpfile'] = tmpfile |
| 654 |
|
| 655 |
options[name] = res |
| 656 |
msg.options = options |
| 657 |
|
| 658 |
self.handle_request_command(msg) |
| 659 |
|
| 585 |
def handle_request_command( self, msg ): |
660 |
def handle_request_command( self, msg ): |
| 586 |
"""Handles a COMMAND request. The request must contain a valid |
661 |
"""Handles a COMMAND request. The request must contain a valid |
| 587 |
and known command that can be accessed by the current user. If |
662 |
and known command that can be accessed by the current user. If |