# -*- coding: utf8 -*-
"""
.. module:: burpui.misc.parser.burp1
    :platform: Unix
    :synopsis: Burp-UI configuration file parser for Burp1.
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
"""
import codecs
import json
import os
import re
import shutil
from glob import glob

from ...exceptions import BUIserverException
from ...utils import NOTIF_ERROR, NOTIF_OK, NOTIF_WARN
from .doc import Doc
from .openssl import OSSLAuth, OSSLConf
from .utils import Config


class Parser(Doc):
    """:class:`burpui.misc.parser.burp1.Parser` provides a consistent interface
    to parse burp configuration files.

    It implements :class:`burpui.misc.parser.interface.BUIparser`.
    """

    pver = 1

    _assume_server_version = ""
    _assume_client_version = ""

    def __init__(self, backend=None):
        """
        :param backend: Backend context
        :type backend: :class:`burpui.misc.backend.burp1.Burp`
        """
        self.backend = backend
        self.clients = []
        self._server_conf = {}
        self._client_conf = {}
        self._clients_conf = {}
        self._templates_conf = {}
        self._static_templates_conf = {}
        self.clientconfdir = None
        self.clientconfdir_mtime = None
        self.templates = []
        self.templates_dir = ".buitemplates"
        self.templates_path = None
        self.templates_mtime = None
        self.static_templates = []
        self.static_templates_dir = ".buistatictemplates"
        self.static_templates_path = None
        self.static_templates_mtime = None
        self.filescache = {}
        self._configs = {}
        self.root = None
        if self.backend:
            self.init_app()

    def init_app(self, confsrv=None, confcli=None):
        self.conf = confsrv or getattr(self.backend, "burpconfsrv", None)
        self.confcli = confcli or getattr(self.backend, "burpconfcli", None)
        self.logger.info("Parser initialized with: {}".format(self.conf))
        if self.conf:
            self.root = os.path.dirname(self.conf)
        # first run to setup vars
        self._load_all_conf()
        ca_conf = self.server_conf.get("ca_conf")
        if self._is_secure_path(ca_conf):
            self.openssl_conf = OSSLConf(ca_conf)
        else:
            self.openssl_conf = OSSLConf(os.devnull)
        self.openssl_auth = OSSLAuth(
            self.server_conf.get("ca_name"), self.openssl_conf, self.server_conf
        )

    @property
    def server_conf(self):
        if self._server_conf.changed:
            self._load_conf_srv()
        return self._server_conf

    @property
    def client_conf(self):
        if self._client_conf.changed:
            self._load_conf_cli()
        return self._client_conf

    @property
    def clients_conf(self):
        if self._clientconfdir_changed():
            self._load_conf_clients()
        for client, conf in self._clients_conf.items():
            if conf.changed:
                conf.parse(True)
        return self._clients_conf

    def _cleanup(self):
        self._server_conf.clear()
        self._client_conf.clear()
        self._clients_conf.clear()

    def _refresh_cache(self, purge=False):
        """Force cache refresh"""
        # empty all the caches
        if purge:
            self._cleanup()
        self._list_templates(True)
        self._list_static_templates(True)
        self._list_clients(True)

    def _load_conf_srv(self):
        """Load the server configuration file"""
        self._server_conf = Config(self.conf, self, "srv")
        self._server_conf.parse()
        self.clientconfdir = self._server_conf.get("clientconfdir")
        self.templates_path = os.path.join(self.clientconfdir, self.templates_dir)
        if not os.path.exists(self.templates_path):
            try:
                os.makedirs(self.templates_path, 0o755)
            except OSError as exp:
                self.logger.warning(str(exp))
        self.static_templates_path = os.path.join(
            self.clientconfdir, self.static_templates_dir
        )
        if not os.path.exists(self.static_templates_path):
            try:
                os.makedirs(self.static_templates_path, 0o755)
            except OSError as exp:
                self.logger.warning(str(exp))

    def _load_conf_cli(self):
        """Load the client configuration file"""
        self._client_conf = Config(self.confcli, self, "cli")
        self._client_conf.parse()

    def _load_conf_clients(self, name=None, in_path=None):
        """Load a given client configuration file (or all)"""
        if name:
            clients = [{"name": name, "value": in_path}]
        else:
            clients = self._list_clients(True)

        for cli in clients:
            conf = self.server_conf.clone()
            path = cli["value"] or cli["name"]
            if cli["name"] not in self._clients_conf:
                if not os.path.isabs(path):
                    path = os.path.join(self.clientconfdir, path)
                conf.add_file(path)
                conf.set_default(path)
                conf.parse()
                self._clients_conf[cli["name"]] = conf

    def _load_conf_templates(self, name=None, in_path=None):
        """Load all templates configuration"""
        if name:
            templates = [{"name": name, "value": in_path}]
        else:
            templates = self._list_templates(True)

        for template in templates:
            conf = self.server_conf.clone()
            conf.set_template(True)
            path = os.path.join(self.templates_path, template["name"])
            if template["name"] not in self._templates_conf:
                conf.add_file(path)
                conf.set_default(path)
                conf.parse()
                self._templates_conf[template["name"]] = conf

    def _load_conf_static_templates(self, name=None, in_path=None):
        """Load all static templates configuration"""
        if name:
            templates = [{"name": name, "value": in_path}]
        else:
            templates = self._list_static_templates(True)

        for template in templates:
            conf = self.server_conf.clone()
            path = os.path.join(self.static_templates_path, template["name"])
            if template["name"] not in self._static_templates_conf:
                conf.add_file(path)
                conf.set_default(path)
                conf.parse()
                self._static_templates_conf[template["name"]] = conf

    def _load_all_conf(self):
        """Load all configurations"""
        self._cleanup()
        self._load_conf_srv()
        self._load_conf_cli()
        self._load_conf_clients()
        self._load_conf_templates()

    def _new_client_conf(self, name, path):
        """Create new client conf"""
        self._load_conf_clients(name, path)
        return self.clients_conf[name]

    def _new_template_conf(self, name, path):
        """Create new template conf"""
        self._load_conf_templates(name, path)
        return self._templates_conf[name]

    def _new_static_template_conf(self, name, path):
        """Create new static template conf"""
        self._load_conf_static_templates(name, path)
        return self._static_templates_conf[name]

    def _clientconfdir_changed(self):
        """Detect changes in clientconfdir"""
        if not self.clientconfdir:
            return False
        mtime = os.path.getmtime(self.clientconfdir)
        changed = mtime != self.clientconfdir_mtime
        if changed:
            self.clientconfdir_mtime = mtime
            return True
        return False

    def _templates_changed(self):
        """Detect changes in templates_dir"""
        if not self.templates_path:
            return False
        mtime = os.path.getmtime(self.templates_path)
        changed = mtime != self.templates_mtime
        if changed:
            self.templates_mtime = mtime
            return True
        return False

    def _static_templates_changed(self):
        """Detect changes in static_templates_dir"""
        if not self.static_templates_path:
            return False
        mtime = os.path.getmtime(self.static_templates_path)
        changed = mtime != self.static_templates_mtime
        if changed:
            self.static_templates_mtime = mtime
            return True
        return False

    def _get_client(self, name, path):
        """Return client conf and refresh it if necessary

        :rtype: Config
        """
        if self._clientconfdir_changed() and name not in self._clients_conf:
            self._clients_conf.clear()
            self._load_conf_clients()
        if name not in self._clients_conf:
            return self._new_client_conf(name, path)
        if self._clients_conf[name].changed:
            self._clients_conf[name].parse()
        return self._clients_conf[name]

    def _get_template(self, name, path=None):
        """Return template conf and refresh it if necessary

        :rtype: Config
        """
        if self._clientconfdir_changed() and name not in self._templates_conf:
            self._templates_conf.clear()
            self._load_conf_templates()
        if name not in self._templates_conf:
            return self._new_template_conf(name, path)
        template = self._templates_conf[name]
        if template.changed:
            template.parse()
        return template

    def _get_static_template(self, name, path=None):
        """Return static template conf and refresh it if necessary

        :rtype: Config
        """
        if self._static_templates_changed() and name not in self._static_templates_conf:
            self._static_templates_conf.clear()
            self._load_conf_static_templates()
        if name not in self._static_templates_conf:
            return self._new_static_template_conf(name, path)
        template = self._static_templates_conf[name]
        if template.changed:
            template.parse()
        return template

    def _get_config(self, path, mode="cli"):
        """Return conf by it's path

        :rtype: Config
        """
        if path in self._configs:
            ret = self._configs[path]
        else:
            ret = Config(path, self, mode)
        if ret.changed:
            ret.parse()
        return ret

    def _is_secure_path(self, path=None):
        """Check if the accessed path is allowed or not"""
        if not path or not self.backend.includes:
            # don't check
            return True

        path = os.path.normpath(path)
        cond = (path.startswith(x) for x in self.backend.includes)
        if not any(cond) and self.backend.enforce:
            self.logger.warning("Tried to access non-allowed path: {}".format(path))
            return False

        return True

    def _list_clients(self, force=False):
        if not self.clientconfdir:
            return []

        if self.clients and not force and not self._clientconfdir_changed():
            return self.clients

        res = []
        for cli in os.listdir(self.clientconfdir):
            full_file = os.path.join(self.clientconfdir, cli)
            if (
                os.path.isfile(full_file)
                and not cli.startswith(".")
                and not cli.endswith("~")
            ):
                res.append({"name": cli, "value": full_file})

        self.clients = res
        self.clientconfdir_mtime = os.path.getmtime(self.clientconfdir)
        return res

    def _list_templates(self, force=False):
        res = []
        if not self.clientconfdir or not os.path.isdir(self.templates_path):
            return res

        if (
            self.templates
            and not force
            and not self._clientconfdir_changed()
            and not self._templates_changed()
        ):
            return self.templates

        for tpl in os.listdir(self.templates_path):
            full_file = os.path.join(self.templates_path, tpl)
            if (
                os.path.isfile(full_file)
                and not tpl.startswith(".")
                and not tpl.endswith("~")
            ):
                res.append(
                    {"name": tpl, "value": os.path.join(self.templates_dir, tpl)}
                )

        self.templates = res
        self.clientconfdir_mtime = os.path.getmtime(self.clientconfdir)
        self.templates_mtime = os.path.getmtime(self.templates_path)
        return res

    def _list_static_templates(self, force=False):
        res = []
        if not self.clientconfdir or not os.path.isdir(self.static_templates_path):
            return res

        if (
            self.static_templates
            and not force
            and not self._clientconfdir_changed()
            and not self._static_templates_changed()
        ):
            return self.static_templates

        for tpl in os.listdir(self.static_templates_path):
            full_file = os.path.join(self.static_templates_path, tpl)
            if (
                os.path.isfile(full_file)
                and not tpl.startswith(".")
                and not tpl.endswith("~")
            ):
                try:
                    with open(full_file) as template:
                        res.append(
                            {
                                "name": tpl,
                                "value": os.path.join(self.static_templates_dir, tpl),
                                "content": template.read(),
                            }
                        )
                except OSError as exp:
                    self.logger.warning(str(exp))

        self.static_templates = res
        self.clientconfdir_mtime = os.path.getmtime(self.clientconfdir)
        self.static_templates_mtime = os.path.getmtime(self.static_templates_path)
        return res

    def _get_server_path(self, name=None, fil=""):
        """Returns the path of the 'server *fil*' file"""
        if not name:
            raise BUIserverException("Missing name")

        conf = self.clients_conf.get(name)
        if not conf:
            if not self.conf:
                raise BUIserverException("No burp-server configuration found")
            elif not self.clientconfdir:
                raise BUIserverException("No 'clientconfdir' found in configuration")
            else:
                raise BUIserverException("Client '{}' not found".format(name))

        workingdir = conf.get("directory")
        if not workingdir:
            raise BUIserverException("Unable to find burp spool dir")

        return os.path.join(workingdir, name, fil)

    def _get_server_restore_path(self, name=None):
        """Returns the path of the 'server restore' file"""
        return self._get_server_path(name, "restore")

    def _get_server_backup_path(self, name=None):
        """Returns the path of the 'server backup' file"""
        return self._get_server_path(name, "backup")

    def path_expander(self, pattern=None, source=None, client=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.path_expander`"""
        if not pattern:
            return []
        if not os.path.isabs(pattern):
            if source and (
                source.startswith(self.clientconfdir) or source.startswith(self.root)
            ):
                pattern = os.path.join(os.path.dirname(source), pattern)
            elif client:
                pattern = os.path.join(self.clientconfdir, pattern)
            else:
                pattern = os.path.join(self.root, pattern)
        if not re.search(r"\?|\*|\[.*\]", pattern):
            return [pattern] if self._is_secure_path(pattern) else []
        else:
            return [
                x
                for x in glob(pattern)
                if os.path.isfile(x)
                and not x.endswith("~")
                and not x.endswith(".back")
                and self._is_secure_path(x)
            ]

    def is_client_revoked(self, client=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.is_client_revoked`"""
        if not client:
            return False
        return self.openssl_auth.check_client_revoked(client)

    def remove_client(
        self,
        client=None,
        keepconf=False,
        delcert=False,
        revoke=False,
        template=False,
        statictemplate=False,
        delete=False,
    ):
        """See :func:`burpui.misc.parser.interface.BUIparser.remove_client`"""
        res = []
        revoked = False
        removed = False
        if not client:
            return [[NOTIF_ERROR, "No client provided"]]
        try:
            data = self._get_server_path(client)
            if not keepconf:
                if template:
                    path = os.path.join(self.templates_path, client)
                elif statictemplate:
                    path = os.path.join(self.static_templates_path, client)
                else:
                    path = os.path.join(self.clientconfdir, client)
                os.unlink(path)
                res.append([NOTIF_OK, "'{}' successfully removed".format(client)])
                removed = True

                if client in self._clients_conf and not (template or statictemplate):
                    del self._clients_conf[client]
                elif template and client in self._templates_conf:
                    del self._templates_conf[client]
                elif statictemplate and client in self._static_templates_conf:
                    del self._static_templates_conf[client]
                if path in self.filescache:
                    del self.filescache[path]

                self._refresh_cache()

            if delete:
                shutil.rmtree(data)

        except OSError as exp:
            res.append([NOTIF_ERROR, str(exp)])

        if revoke and self.backend.revocation_enabled() and removed:
            # revoke cert
            revoked = self.openssl_auth.revoke_client(client)
            if revoked:
                res.append([NOTIF_OK, "'{}' successfully revoked".format(client)])
            else:
                res.append([NOTIF_ERROR, "Error while revoking the certificate"])

        if delcert:
            ca_dir = self.openssl_conf.values.get("CA_DIR")
            path = os.path.join(ca_dir, client)
            try:
                os.unlink("{}.csr".format(path))
            except OSError as exp:
                res.append([NOTIF_WARN, str(exp)])
            try:
                os.unlink("{}.crt".format(path))
            except OSError as exp:
                res.append([NOTIF_ERROR, str(exp)])
            if not revoked:
                res.append([NOTIF_WARN, "The client certificate may still be used!"])

        return res

    def rename_client(
        self,
        client=None,
        newname=None,
        template=False,
        statictemplate=False,
        keepcert=False,
        keepdata=False,
    ):
        """See :func:`burpui.misc.parser.interface.BUIparser.rename_client`"""
        res = []
        if not client:
            return [[NOTIF_ERROR, "No client provided"]]
        if not newname:
            return [[NOTIF_ERROR, "No newname provided"]]

        if template:
            path = os.path.join(self.templates_path, client)
            newpath = os.path.join(self.templates_path, newname)
        elif statictemplate:
            path = os.path.join(self.static_templates_path, client)
            newpath = os.path.join(self.static_templates_path, newname)
        else:
            data = self._get_server_path(client)
            conf = self.clients_conf.get(client)
            newdata = os.path.join(conf.get("directory"), newname)
            path = os.path.join(self.clientconfdir, client)
            newpath = os.path.join(self.clientconfdir, newname)
        try:
            os.rename(path, newpath)
            res.append(
                [NOTIF_OK, "'{}' successfully renamed '{}'".format(client, newname)]
            )
        except OSError as exp:
            res.append([NOTIF_ERROR, str(exp)])

        if client in self._clients_conf and not (template or statictemplate):
            del self._clients_conf[client]
        elif template and client in self._templates_conf:
            del self._templates_conf[client]
        elif statictemplate and client in self._static_templates_conf:
            del self._static_templates_conf[client]
        if path in self.filescache:
            del self.filescache[path]

        self._refresh_cache()

        if template or statictemplate:
            return res

        if keepdata:
            if os.path.exists(newdata):
                res.append([NOTIF_ERROR, "'{}' already exists".format(newdata)])
            else:
                try:
                    shutil.move(data, newdata)
                except OSError as exp:
                    res.append([NOTIF_ERROR, str(exp)])

        ca_dir = self.openssl_conf.values.get("CA_DIR")
        path = os.path.join(ca_dir, client)
        newpath = os.path.join(ca_dir, newname)
        if keepcert:
            config = self._get_client(newname, newpath)  # type: Config
            if "ssl_peer_cn" not in config:
                config["ssl_peer_cn"] = client
                config.store()
            try:
                os.rename(f"{path}.csr", f"{newpath}.csr")
            except OSError as exp:
                res.append([NOTIF_WARN, str(exp)])
            try:
                os.rename(f"{path}.crt", f"{newpath}.crt")
            except OSError as exp:
                res.append([NOTIF_ERROR, str(exp)])
        else:
            try:
                os.unlink("{}.csr".format(path))
            except OSError as exp:
                res.append([NOTIF_WARN, str(exp)])
            try:
                os.unlink("{}.crt".format(path))
            except OSError as exp:
                res.append([NOTIF_ERROR, str(exp)])

        return res

    def read_client_conf(
        self, client=None, conf=None, template=False, statictemplate=False
    ):
        """
        See :func:`burpui.misc.parser.interface.BUIparser.read_client_conf`
        """
        res = {
            "common": [],
            "boolean": [],
            "integer": [],
            "multi": [],
            "includes": [],
            "includes_ext": [],
            "templates": [],
            "hierarchy": [],
            "raw": None,
        }
        if not client and not conf:
            return res

        mconf = conf
        if not mconf:
            if not self.clientconfdir:
                return res
            if template:
                mconf = os.path.join(self.templates_path, client)
                config = self._get_template(client, mconf)
            elif statictemplate:
                mconf = os.path.join(self.static_templates_path, client)
                config = self._get_static_template(client, mconf)
            else:
                mconf = os.path.join(self.clientconfdir, client)
                config = self._get_client(client, mconf)
        else:
            config = self._get_config(mconf)

        parsed = config.get_file(mconf)
        if mconf in self.filescache and self.filescache[mconf]["md5"] == parsed.md5:
            return self.filescache[mconf]["dict"]

        res2 = {}
        res2["common"] = parsed.string
        res2["boolean"] = parsed.boolean
        res2["integer"] = parsed.integer
        res2["multi"] = parsed.multi
        res2["templates"] = parsed.template
        res2["includes"] = [x for x in parsed.flatten("include", False).keys()]
        res2["includes_ext"] = parsed.include
        res2["hierarchy"] = config.tree
        res2["raw"] = str(parsed)

        res.update(res2)
        self.filescache[mconf] = {"dict": res, "md5": parsed.md5}
        return res

    def read_server_conf(self, conf=None):
        """
        See :func:`burpui.misc.parser.interface.BUIparser.read_server_conf`
        """
        mconf = None
        res = {
            "common": [],
            "boolean": [],
            "integer": [],
            "multi": [],
            "pair": [],
            "includes": [],
            "includes_ext": [],
            "hierarchy": [],
            "raw": None,
        }
        if not conf:
            mconf = self.conf
        else:
            mconf = conf
        if not mconf:
            return res

        parsed = self.server_conf.get_file(mconf)
        if mconf in self.filescache and self.filescache[mconf]["md5"] == parsed.md5:
            return self.filescache[mconf]["dict"]

        res2 = {}
        res2["common"] = parsed.string
        res2["boolean"] = parsed.boolean
        res2["integer"] = parsed.integer
        res2["multi"] = parsed.multi
        res2["pair"] = parsed.pair
        res2["includes"] = [x for x in parsed.flatten("include", False).keys()]
        res2["includes_ext"] = parsed.include
        res2["hierarchy"] = self.server_conf.tree
        res2["raw"] = str(parsed)

        res.update(res2)
        self.filescache[mconf] = {"dict": res, "md5": parsed.md5}
        return res

    def list_clients(self):
        """See :func:`burpui.misc.parser.interface.BUIparser.list_clients`"""
        self.read_server_conf()
        return self._list_clients()

    def list_templates(self):
        """See :func:`burpui.misc.parser.interface.BUIparser.list_templates`"""
        self.read_server_conf()
        return self._list_templates()

    def list_static_templates(self):
        """See :func:`burpui.misc.parser.interface.BUIparser.list_static_templates`"""
        self.read_server_conf()
        return self._list_static_templates()

    def store_client_conf(
        self,
        data,
        client=None,
        conf=None,
        template=False,
        statictemplate=False,
        content="",
    ):
        """
        See :func:`burpui.misc.parser.interface.BUIparser.store_client_conf`
        """
        if conf and not os.path.isabs(conf):
            conf = os.path.join(self.clientconfdir, conf)
        if not conf and not client:
            if template:
                return [[NOTIF_ERROR, "Sorry, no template defined"]]
            elif statictemplate:
                return [[NOTIF_ERROR, "Sorry, no static template defined"]]
            return [[NOTIF_ERROR, "Sorry, no client defined"]]
        elif client and not conf:
            if template:
                if not self.templates_path:
                    return [[NOTIF_ERROR, "Sorry, no template directory found"]]
                conf = os.path.join(self.templates_path, client)
            elif statictemplate:
                if not self.static_templates_path:
                    return [[NOTIF_ERROR, "Sorry, no static template directory found"]]
                conf = os.path.join(self.static_templates_path, client)
            else:
                conf = os.path.join(self.clientconfdir, client)
        ret = self.store_conf(
            data,
            conf,
            client,
            mode="cli",
            template=template,
            statictemplate=statictemplate,
            content=content,
        )
        self._refresh_cache()  # refresh client list
        return ret

    def store_conf(
        self,
        data,
        conf=None,
        client=None,
        mode="srv",
        insecure=False,
        template=False,
        statictemplate=False,
        content="",
    ):
        """See :func:`burpui.misc.parser.interface.BUIparser.store_conf`"""
        ret = []
        mconf = None
        if not conf:
            mconf = self.conf
        else:
            mconf = conf
            if mconf != self.conf and not os.path.isabs(mconf):
                mconf = os.path.join(self.root, mconf)
        if not mconf:
            return [[NOTIF_WARN, "Sorry, no configuration file defined"]]

        if not self._is_secure_path(mconf) and not insecure:
            return [
                [
                    NOTIF_ERROR,
                    "Sorry you are not allowed to access this path:"
                    " {}".format(mconf),
                ]
            ]

        wrote_content = False
        if content:
            if os.path.exists(mconf):
                ret.append(
                    [NOTIF_WARN, "The file already exists, we won't override it"]
                )
            else:
                try:
                    with open(mconf, "w") as temp:
                        if not content.endswith("\n"):
                            content += "\n"
                        temp.write(content)
                        ret.append(
                            [NOTIF_OK, "File successfully written from template"]
                        )
                    wrote_content = True
                except IOError as exp:
                    ret.append([NOTIF_WARN, str(exp)])

        check = False
        if template:
            conffile = self._get_template(client, mconf).get_file(mconf)
        elif statictemplate:
            conffile = self._get_static_template(client, mconf).get_file(mconf)
        elif client:
            conffile = self._get_client(client, mconf).get_file(mconf)
        else:
            conffile = self.server_conf.get_file(mconf)
            check = True

        if wrote_content and data:
            for key in data.keys():
                tmp = data.getlist(key)
                if len(tmp) == 1:
                    conffile[key] = tmp[0]
                else:
                    conffile[key] = tmp
            ret += conffile.store(insecure=insecure)
        elif not wrote_content:
            ret += conffile.store_data(data, insecure)

        if check:
            clientconfdir = conffile.get("clientconfdir")
            if clientconfdir and clientconfdir.parse() != self.clientconfdir:
                self.clientconfdir = clientconfdir.parse()
                self.clientconfdir_mtime = -1

        return ret

    def remove_conf(self, path=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.remove_conf`"""
        if not path:
            return [[NOTIF_WARN, "No file selected for removal"]]
        if path == self.conf:
            return [
                [
                    NOTIF_ERROR,
                    "Removing the burp-server configuration file is not supported",
                ]
            ]

        parsed = self.server_conf.get_file(self.conf)
        includes = parsed.include
        if includes:
            for include in includes:
                if "value" in include and path in include["value"]:
                    try:
                        os.unlink(path)
                        if path in self.filescache:
                            del self.filescache[path]
                        return [
                            [NOTIF_OK, "File '{}' successfully removed".format(path)]
                        ]
                    except IOError as exp:
                        return [
                            [
                                NOTIF_ERROR,
                                "Unable to remove configuration file '{}': {}".format(
                                    path, str(exp)
                                ),
                            ]
                        ]
        return [[NOTIF_ERROR, "No file suited for removal"]]

    def cancel_restore(self, name=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.cancel_restore`"""
        path = self._get_server_restore_path(name)
        try:
            if os.path.exists(path):
                os.unlink(path)
            else:
                return [NOTIF_WARN, "There is no restoration scheduled for this client"]
        except OSError as exp:
            return [NOTIF_ERROR, "Unable to cancel restoration: {}".format(str(exp))]
        return [NOTIF_OK, "Restoration successfully canceled"]

    def read_restore(self, name=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.read_restore`"""
        path = self._get_server_restore_path(name)

        try:
            ret = {}
            with codecs.open(path, "r", "utf-8", errors="ignore") as restore:
                for line in restore.readlines():
                    line = line.rstrip()
                    (key, val) = re.split(r" *= *", line)
                    if key == "regex":
                        ret["list"] = []
                        for reg in val.split("|"):
                            reg = reg.replace("^", "", 1)
                            reg = reg.replace("\\", "")
                            if reg.endswith("$"):
                                ret["list"].append(
                                    {"key": reg.rstrip("$"), "folder": False}
                                )
                            else:
                                ret["list"].append({"key": reg, "folder": True})
                    else:
                        ret[key] = val
                ret["found"] = True
                ret["to"] = name

            return ret
        except Exception:
            return {}

    def server_initiated_restoration(
        self,
        name=None,
        backup=None,
        files=None,
        strip=None,
        force=None,
        prefix=None,
        restoreto=None,
    ):
        """See
        :func:`burpui.misc.parser.interface.BUIparser.server_initiated_restoration`
        """
        if not name or not backup or not files:
            raise BUIserverException("At least one argument is missing")

        if self.read_backup(name):
            raise BUIserverException(
                "A backup is already scheduled. Cannot schedule both restore"
                " and backup at the same time."
            )

        flist = json.loads(files)
        if "restore" not in flist:
            raise BUIserverException("Wrong call")

        full_reg = r""
        for rest in flist["restore"]:
            reg = r""
            if rest["folder"] and rest["key"] != "/":
                reg += "^" + re.escape(rest["key"]) + "/|"
            else:
                reg += "^" + re.escape(rest["key"]) + "$|"
            full_reg += reg

        try:
            client = name
            if restoreto:
                found = False
                for cli in self.clients:
                    if cli["name"] == restoreto:
                        found = True
                        break

                if not found:
                    raise BUIserverException("Client '{}' not found".format(restoreto))

                client = restoreto

            path = self._get_server_restore_path(client)
            with codecs.open(path, "w", "utf-8", errors="ignore") as fil:
                fil.write("backup = {}\n".format(backup))
                fil.write("regex = {}\n".format(full_reg.rstrip("|")))
                if strip and strip > 0:  # 0 is False, but we are sure now
                    fil.write("strip = {}\n".format(strip))
                if prefix:
                    fil.write("restoreprefix = {}\n".format(prefix))
                if force:
                    fil.write("overwrite = 1\n")
                if restoreto:
                    fil.write("orig_client = {}\n".format(name))

            return [NOTIF_OK, "Server-initiated restoration successfully scheduled"]

        except Exception as exp:
            return [
                NOTIF_ERROR,
                "Unable to schedule a server-initiated restoration:"
                " {}".format(str(exp)),
            ]

    def cancel_backup(self, name=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.cancel_backup`"""
        path = self._get_server_backup_path(name)
        try:
            if os.path.exists(path):
                os.unlink(path)
            else:
                return [NOTIF_WARN, "There is no backup scheduled for this client"]
        except OSError as exp:
            return [NOTIF_ERROR, "Unable to cancel backup: {}".format(str(exp))]
        return [NOTIF_OK, "Backup successfully canceled"]

    def read_backup(self, name=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.read_backup`"""
        path = self._get_server_backup_path(name)
        ret = os.path.exists(path)
        return ret

    def server_initiated_backup(self, name=None):
        """See
        :func:`burpui.misc.parser.interface.BUIparser.server_initiated_backup`
        """

        if self.read_restore(name):
            raise BUIserverException(
                "A restoration is already scheduled. Cannot schedule both"
                " restore and backup at the same time."
            )

        path = self._get_server_backup_path(name)
        try:
            with open(path, "w"):
                os.utime(path, None)
        except OSError as exp:
            return [
                NOTIF_ERROR,
                "Unable to schedule a server-initiated backup:" " {}".format(str(exp)),
            ]
        return [NOTIF_OK, "Backup successfully scheduled"]

    def param(self, name, obj="server_conf", client=None):
        """See :func:`burpui.misc.parser.interface.BUIparser.param`"""
        try:
            if client:
                obj = "clients_conf"
            my_obj = getattr(self, obj)
        except AttributeError:
            raise BUIserverException("The requested object could not be found")
        if client:
            return my_obj.get(client, {}).get(name, "")
        return my_obj.get(name, "")
