HEX
Server: nginx/1.22.0
System: Linux iZuf6jdxbygmf6cco977lcZ 5.10.84-10.4.al8.x86_64 #1 SMP Tue Apr 12 12:31:07 CST 2022 x86_64
User: root (0)
PHP: 7.4.29
Disabled: passthru,exec,system,chroot,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_restore,dl,readlink,symlink,popepassthru,stream_socket_server,fsocket,popen
Upload Files
File: //usr/local/aegis/PythonLoader/third_party/apacheconfig/wloader.py
#
# This file is part of apacheconfig software.
#
# Copyright (c) 2018-2019, Ilya Etingof <etingof@gmail.com>
# License: https://github.com/etingof/apacheconfig/LICENSE.rst
#
from __future__ import unicode_literals

import abc

import six

from apacheconfig import error
from apacheconfig.reader import LocalHostReader


class ApacheConfigWritableLoader(object):
    """Manages a writable object represented by a single config file.

    Args:
        parser (:class:`apacheconfig.ApacheConfigParser`): compiled parser
            to use when loading configuration directives.
        options (dict): keyword args of options to set for loader. Should be
            same set of options passed to parser.
    """

    def __init__(self, parser, **options):
        self._parser = parser
        self._options = dict(options)
        if 'reader' in self._options:
            self._reader = self._options['reader']
        else:
            self._reader = LocalHostReader()

    def load(self, filepath):
        """Loads config file into a raw modifiable AST.

        Args:
            filepath (Text): path of config file to load. Expects UTF-8
                encoding.

        Returns:
            :class:`apacheconfig.ListNode` AST containing parsed config file.

        Raises:
            IOError: If there is a problem opening the file specified.
        """
        with self._reader.open(filepath) as f:
            return self.loads(f.read())

    def loads(self, text):
        """Loads config text into a raw modifiable AST.

        Args:
            text (Text): (Text) containing the configuration to load.

        Returns:
            :class:`apacheconfig.ListNode` AST containing parsed config.
        """
        ast = self._parser.parse(text)
        return ListNode(ast, self._parser)


def _restore_original(word):
    """If the `word` is a Quoted string, restores it to original.
    """
    if getattr(word, 'is_single_quoted', False):
        return "'%s'" % word
    if getattr(word, 'is_double_quoted', False):
        return '"%s"' % word
    return word


@six.add_metaclass(abc.ABCMeta)
class AbstractASTNode(object):
    """Generic class containing data that represents a node in the config AST.

    There are three subclasses: :class:`apacheconfig.ListNode`,
    :class:`apacheconfig.BlockNode`, and :class:`apacheconfig.LeafNode`.

    Every AST should have a :class:`apacheconfig.ListNode` at its root. A
    :class:`apacheconfig.ListNode` or :class:`apacheconfig.BlockNode` can have
    other :class:`apacheconfig.BlockNode`s and
    :class:`apacheconfig.LeafNode`s as children.

    In general, a tree might look like::

                      +----------+
                      | ListNode |
                      +----------+
                       |        |
                       v        v
               +---------+    +--------+
               |BlockNode|    |LeafNode|
               +---------+    +--------+
                |       |
                v       v
        +---------+   +--------+
        |BlockNode|   |LeafNode|
        +---------+   +--------+
           |
           v
          etc...

    Both :class:`apacheconfig.ListNode` and :class:`apacheconfig.BlockNode`
    may contain an ordered list of other nodes, but
    :class:`apacheconfig.LeafNode`s are terminal.

    Each :class:`apacheconfig.AbstractASTNode` class also has their own
    properties and functions. In general, :class:`apacheconfig.LeafNode`
    corresponds with scalar data such as directives/options or comments.
    :class:`apacheconfig.BlockNode` corresponds with an open/close tag and
    its contents.
    """

    @abc.abstractmethod
    def dump(self):
        """Dumps contents of this node.

        Returns:
            (Text) with the contents of this node, as in a config file.
        """

    @abc.abstractproperty
    def typestring(self):
        """Object typestring as defined by the apacheconfig parser.
        """


class ListNode(AbstractASTNode):
    """Creates object for an ordered list of ``apacheconfig.AbstractASTNode``s.

    Every configuration file's root should be a ``apacheconfig.ListNode``.
    Children can be ``apacheconfig.BlockNode`` or ``apacheconfig.LeafNode``.

    Args:
        raw (list): Data returned from ``apacheconfig.parser``. To construct
            from a string containing config directives, use the `parse` factory
            function.

    Raises:
        ApacheConfigError: If `raw` is not formed as expected. In particular,
            if `raw` is too short, or has the wrong `typestring`, or if
            one of this list's children is not formed as expected.
    """
    def __init__(self, raw, parser):
        if len(raw) < 2:
            raise error.ApacheConfigError(
                "Expected properly-formatted `contents` data returned from "
                "``apacheconfig.parser``. Got a list that is too short.")
        self._type = raw[0]
        if self._type != "contents":
            raise error.ApacheConfigError(
                "Expected properly-formatted `contents` data returned from "
                "``apacheconfig.parser``. First element of data is not "
                "\"contents\" typestring.")
        self._contents = []
        self._trailing_whitespace = ""
        self._parser = parser
        for elem in raw[1:]:
            if isinstance(elem, six.string_types) and elem.isspace():
                self._trailing_whitespace = elem
            elif elem[0] == "block":
                self._contents.append(BlockNode(elem, parser))
            elif elem[0] == "contents":
                raise error.ApacheConfigError(
                    "Expected properly-formatted `contents` data returned "
                    "from ``apacheconfig.parser``. Got `contents` data as "
                    "a child of this `contents` data.")
            else:
                self._contents.append(LeafNode(elem))

    @classmethod
    def parse(cls, raw_str, parser):
        """Factory for :class:`apacheconfig.ListNode` from a config string.

        Args:
            raw_str (str): Config string to parse.
            parser (:class:`apacheconfig.ApacheConfigParser`): parser object
                to use.
        Returns:
            :class:`apacheconfig.ListNode` containing data parsed from
            ``raw_str``.
        """
        raw = parser.parse(raw_str)
        return cls(raw, parser)

    def add(self, index, raw_str):
        """Parses and adds child element at given index.

        Parses given string into an :class:`apacheconfig.AbstractASTNode`
        object, then adds to list at specified index.

        Args:
            index (int): index of list at which to insert the node.
            raw_str (str): string to parse. The parser will automatically
                determine whether it's a :class:`apacheconfig.BlockNode` or
                :class:`apacheconfig.LeafNode`.

        Returns:
            The :class:`apacheconfig.AbstractASTNode` created from parsing
                ``raw_str``.

        Raises:
            ApacheConfigError: If `raw_str` cannot be parsed into a
                :class:`apacheconfig.BlockNode` or
                :class:`apacheconfig.LeafNode`.
            IndexError: If `index` is not within bounds [0, len(self)].
        """
        if index < 0 or index > len(self):
            raise IndexError("supplied index is out of range")
        raw = self._parser.parse(raw_str)
        if len(raw) != 2:
            raise error.ApacheConfigError("Given raw_str should be "
                                          "parsable into a single node.")
        raw = self._parser.parse(raw_str)[1]
        if raw[0] == "block":
            node = BlockNode(raw, self._parser)
        else:
            node = LeafNode(raw)

        # If we're adding an element to the beginning of list, the first
        # item may not have a preceding newline-- so we add one in case.
        # For instance, something like:
        #   Contents("line1\nline2").add(0, "\nline0")
        # should end up as "\nline0\nline1\nline2"
        if (index == 0 and self._contents and
           '\n' not in self._contents[0].whitespace):
            whitespace_after = self._contents[0].whitespace
            self._contents[0].whitespace = '\n' + whitespace_after
        self._contents.insert(index, node)
        return node

    def remove(self, index):
        """Removes node from supplied index.

        Args:
            index (int): index of node to remove from list.

        Returns:
            The :class:`apacheconfig.AbstractASTNode` that was removed.

        Raises:
            IndexError: If `index` is not within bounds [0, len(self)).
        """
        if index < 0 or index >= len(self):
            raise IndexError("supplied index is out of range")
        return self._contents.pop(index)

    def __len__(self):
        """Number of :class:`apacheconfig.ASTNode` children."""
        return len(self._contents)

    def __iter__(self):
        """Iterator over :class:`apacheconfig.ASTNode` children."""
        return iter(self._contents)

    def dump(self):
        return ("".join([item.dump() for item in self._contents])
                + self.trailing_whitespace)

    @property
    def typestring(self):
        """See base class.

        Returns:
            ``"contents"`` for :class:`apacheconfig.ListNode`.
        """
        return self._type

    @property
    def trailing_whitespace(self):
        """Trailing whitespace after this list of config items.

        For instance, the following valid configuration::

            \\tkey value # comment\\n

        will be processed into something like::

            ListNode([
                LeafNode(['\\t', 'key', ' ', 'value']),
                LeafNode([' ', '# comment']),
                '\\n'])

        where the ``trailing_whitespace`` property would return '\\n'.

        Returns:
            String containing trailing whitespace after this list in the
            config file.
        """
        return self._trailing_whitespace

    @trailing_whitespace.setter
    def trailing_whitespace(self, value):
        """Sets trailing whitespace after this list of config items.

        Args:
            value (Text): Trailing whitespace for this list of config items.
        """
        self._trailing_whitespace = value


class LeafNode(AbstractASTNode):
    """Creates object containing a simple list of tokens.

    Also manages any preceding whitespace. Can represent a key/value option,
    a comment, or an include/includeoptional directive.

    Examples of what LeafNode might look like for different directives::

        "option"
            name: "option", value: None, whitespace: ""
        "include relative/path/*"
            name: "include", value: "relative/path/*", whitespace: ""
        "\\n  option = value"
            name: "option", value: "value", whitespace: "\\n  "
        "# here is a comment"
            name: "# here is a comment", value: None, whitespace: ""
        "\\n  # here is a comment"
            name: "# here is a comment", value: None, whitespace: "\\n  "

    To construct from a raw string, use the `parse` constructor. The regular
    constructor receives data from the internal apacheconfig parser.

    Args:
        raw (list): Raw data returned from ``apacheconfig.parser``.

    Raises:
        ApacheConfigError: If `raw` is not formed as expected. In particular,
            if `raw` is too short or has the wrong `typestring`.
    """

    def __init__(self, raw):
        if len(raw) < 2:
            raise error.ApacheConfigError(
                "Expected properly-formatted data returned from "
                "``apacheconfig.parser``. Got a list that is too short.")
        self._type = raw[0]
        if self._type == "contents" or self._type == "block":
            raise error.ApacheConfigError(
                "Expected properly-formatted data returned from "
                "``apacheconfig.parser``. First element of data cannot "
                "be \"contents\" or \"block\" typestring.")
        self._raw = tuple(raw[1:])
        self._whitespace = ""
        if len(raw) > 1 and raw[1].isspace():
            self._whitespace = raw[1]
            self._raw = tuple(raw[2:])

    @property
    def typestring(self):
        """See base class.

        Returns:
            The typestring (as defined by the apacheconfig parser) for
            this node. The possible values for this are ``"comment"``,
            ``"statement"``, ``"include"``, ``"includeoptional"``.
        """
        return self._type

    @property
    def whitespace(self):
        """Whitespace preceding this element in the config file.

        For example::

            LeafNode('\\n  option value').whitespace => "\\n  "
            LeafNode('option value').whitespace => ""
            LeafNode('\\n  # comment').whitespace => "\\n  "

        Returns:
            String containing preceding whitespace for this node.
        """
        return self._whitespace

    @whitespace.setter
    def whitespace(self, value):
        """Sets whitespace preceding this element in the config file.

        Args:
            value (str): New whitespace to set.
        """
        self._whitespace = value

    @classmethod
    def parse(cls, raw_str, parser):
        """Factory for :class:`apacheconfig.LeafNode` from a config string.

        Args:
            raw_str (Text): The text to parse, as a unicode string.
            parser (:class:`apacheconfig.ApacheConfigParser`): specify the
                parser to use. Can be created by ``native_apache_parser()``.
        Returns:
            :class:`apacheconfig.LeafNode` containing metadata parsed from
            ``raw_str``.
        """
        raw = parser.parse(raw_str)
        return cls(raw[1])

    @property
    def name(self):
        """Returns the name of this node.

        The name is the first non-whitespace token in the directive. Cannot be
        written. For comments, is the entire comment.
        """
        return self._raw[0]

    @property
    def has_value(self):
        """Returns ``True`` if this :class:`apacheconfig.LeafNode` has a value.

        ``LeafNode`` objects don't have to have a value, like option/value
        directives with no value, or comments.
        """
        return len(self._raw) > 1

    @property
    def value(self):
        """Returns the value of this item as a unicode string.

        The "value" is anything but the name. Can be overwritten.
        """
        if not self.has_value:
            return
        return self._raw[-1]

    @value.setter
    def value(self, value):
        """Sets for the value of this item.

        Args:
            value (Text): string to set new value to.

          .. todo:: (sydneyli) convert `value` to quotedstring when quoted
        """
        if not self.has_value:
            self._raw = self._raw + (" ", value,)
        self._raw = self._raw[0:-1] + (value,)

    def dump(self):
        return (self.whitespace +
                "".join([_restore_original(word) for word in self._raw]))

    def __str__(self):
        contents = [_restore_original(word) for word in self._raw]
        return ("%s(%s)"
                % (str(self.__class__.__name__),
                   str([self._type] + contents)))

    def __unicode__(self):
        contents = [_restore_original(word) for word in self._raw]
        return ("%s(%s)"
                % (six.text_type(self.__class__.__name__),
                   six.text_type([self._type] + contents)))


class BlockNode(ListNode):
    """Creates object containing data for a block.

    Manages any preceding whitespace before the opening block tag, and
    superclass :class:`apacheconfig.ListNode` methods will manipulate
    block contents.

    Args:
        raw (list): Data returned from ``apacheconfig.parser``. To construct
            from a string containing a config block, use the `parse` factory
            function.

    Raises:
        ApacheConfigError: If `raw` is not formed as expected. In particular,
            if `raw` is too short, or has the wrong `typestring`, or if
            data for this block or one of this block's children is not formed
            as expected.
    """

    def __init__(self, raw, parser):
        if len(raw) < 4:
            raise error.ApacheConfigError(
                "Expected properly-formatted data returned from "
                "``apacheconfig.parser``. Got a list that is too short.")
        self._whitespace = ""
        self._type = raw[0]
        if self._type != "block":
            raise error.ApacheConfigError(
                "Expected properly-formatted data returned from "
                "``apacheconfig.parser``. First element of data is not "
                "\"block\" typestring.")
        start = 1
        if isinstance(raw[start], six.string_types) and raw[start].isspace():
            self._whitespace = raw[start]
            start += 1
        self._full_tag = LeafNode(('statement',) + raw[start])
        self._close_tag = raw[-1]
        self._contents = None
        if raw[start + 1]:  # If we have a list of elements to process.
            super(BlockNode, self).__init__(raw[start + 1], parser)
        self._type = raw[0]

    @classmethod
    def parse(cls, raw_str, parser):
        """Factory for :class:`apacheconfig.BlockNode` from a config string.

        Args:
            raw_str (str): The text to parse.
            parser (:class:`apacheconfig.ApacheConfigParser`): parser object
                to use.
        Returns:
            :class:`apacheconfig.BlockNode` containing metadata parsed from
            ``raw_str``.
        """
        raw = parser.parse(raw_str)
        return cls(raw[1], parser)

    @property
    def tag(self):
        """Tag name for this block.

        Returns:
            Tag name for this blog as a string. For instance,
            ``<block details>\\n</block>`` has tag ``"block"``.
        """
        return self._full_tag.name

    @property
    def arguments(self):
        """Arguments of this block.

        Returns:
            Arguments for this block as a string. For instance,
            ``<block lots of \tdetails>\\n</block>`` returns arguments
            ``"lots of \tdetails"``. Can be overwritten.
        """
        return self._full_tag.value

    @arguments.setter
    def arguments(self, arguments):
        """Sets or overwrites arguments for this block.

        Args:
            arguments (str): New arguments for this block.
        """
        self._full_tag.value = arguments

    @property
    def typestring(self):
        """See base class.

        Returns:
            ``"block"`` for :class:`apacheconfig.BlockNode`.
        """
        return self._type

    @property
    def whitespace(self):
        """Whitespace preceding this element in the config file.

        Returns:
            String containing preceding whitespace for this node.
        """
        return self._whitespace

    @whitespace.setter
    def whitespace(self, value):
        """Sets whitespace preceding this element in the config file.

        Args:
            value (str): New whitespace to set.
        """
        self._whitespace = value

    def dump(self):
        if self._contents is None:
            return "%s<%s/>" % (self.whitespace, self._full_tag.dump())
        contents = super(BlockNode, self).dump()
        return "%s<%s>%s</%s>" % (self.whitespace, self._full_tag.dump(),
                                  contents, self._close_tag)