view libervia/web/pages/_browser/file_uploader.py @ 1598:86c7a3a625d5

server: always start a new session on connection: The session was kept when a user was connecting from service profile (but not from other profiles), this was leading to session fixation vulnerability (an attacker on the same machine could get service profile session cookie, and use it when a victim would log-in). This patch fixes it by always starting a new session on connection. fix 443
author Goffi <goffi@goffi.org>
date Fri, 23 Feb 2024 13:35:24 +0100
parents 106945841fbc
children
line wrap: on
line source

import json
from bridge import Bridge
from browser import console as log, window
import dialog
from template import Template
bridge = Bridge()

log.warning = log.warn


class FileUploader:
    """Class handling HTTP upload and progress display"""

    def __init__(self, files_service, template_file, files_path=None, on_delete_cb=None):
        self.files_service = files_service
        self.files_path = files_path
        self.item_tpl = Template(template_file)
        self.on_delete_cb = on_delete_cb
        self.uploaded = {}

    def on_progress(self, ev, item_elt):
        if ev.lengthComputable:
            percent = int(ev.loaded/ev.total*100)
            self.update_progress(item_elt, percent)

    def on_load(self, file_, item_elt):
        self.update_progress(item_elt, 100)
        item_elt.classList.add("progress_finished")
        item_elt.classList.remove("progress_started")
        if self.on_delete_cb is not None:
            delete_btn = item_elt.select_one('.action_delete')
            if delete_btn is not None:
                delete_btn.bind("click", self.on_delete_cb)
        log.info(f"file {file_.name} uploaded correctly")

    def on_error(self, failure, file_, item_elt):
        dialog.notification.show(
            f"can't upload {file_.name}: {failure}",
            level="error"
        )

    def update_progress(self, item_elt, new_value):
        progress_elt = item_elt.select_one("progress")
        if progress_elt is not None:
            progress_elt.value = new_value
            progress_elt.text = f"{new_value}%"

    def on_slot_cb(self, file_, upload_slot, item_elt):
        put_url, get_url, headers = upload_slot
        xhr = window.XMLHttpRequest.new()
        xhr.open("PUT", put_url, True)
        xhr.upload.bind('progress', lambda ev: self.on_progress(ev, item_elt))
        xhr.upload.bind('load', lambda ev: self.on_load(file_, item_elt))
        xhr.upload.bind('error', lambda ev: self.on_error(xhr.response, file_, item_elt))
        if self.files_path is not None:
            xhr.setRequestHeader(
                'Xmpp-File-Path',
                window.encodeURIComponent(self.files_path)
            )
            xhr.setRequestHeader('Xmpp-File-No-Http', "true")
        xhr.send(file_)

        # we update file data with the get URL
        file_data = json.loads(item_elt.getAttribute("data-file"))
        file_data["url"] = get_url
        item_elt.setAttribute("data-file", json.dumps(file_data))

    def on_slot_eb(self, file_, failure, item_elt):
        dialog.notification.show(
            f"Can't get upload slot: {failure['message']}",
            level="error"
        )
        item_elt.remove()

    def upload_files(self, files, container_elt):
        """Start file upload, and add item_tpl to container_elt"""
        log.info(f"uploading {len(files)} files")
        for file_ in files:
            url = window.URL.createObjectURL(file_)

            file_data = {
                "name": file_.name,
                "mime_type": file_.type,
            }
            template_file_data = file_data.copy()
            # we don't want to open the file on click, it's not yet the
            # uploaded URL
            template_file_data["url"] = url
            # we have no thumb yet, so we use the whole image
            # TODO: reduce image for preview
            template_file_data["thumb_url"] = url

            item_elt = self.item_tpl.get_elt({
                "file": template_file_data,
                "uploading": True,
            })
            item_elt.setAttribute("data-file", json.dumps(file_data))
            item_elt.classList.add("progress_started")
            container_elt <= item_elt

            bridge.file_http_upload_get_slot(
                file_.name,
                file_.size,
                file_.type or '',
                self.files_service,
                callback=lambda upload_slot, file_=file_, item_elt=item_elt:
                    self.on_slot_cb(file_, upload_slot, item_elt),
                errback=lambda failure, file_=file_, item_elt=item_elt:
                    self.on_slot_eb(file_, failure, item_elt),
            )