# Copyright (C) 2022 DigeeX
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Plugin to read from files.
"""
from functools import partial
from typing import Callable, Optional, Union
from raider.plugins.common import Plugin
[docs]class File(Plugin):
""":class:`Plugin <raider.plugins.common.Plugin>` used for getting
data from the filesystem.
Use :class:`File: :class:`Plugin <raider.plugins.common.Plugin>`
when needing to upload something, or sending a :term:`Request`
with lots of data that would better be stored on the filesystem
instead of :term:`hyfiles`.
Attributes:
name:
A String with the :class:`Plugin's
<raider.plugins.common.Plugin>` name. For :class:`File`
objects that's the ``path`` to the file.
function:
A Callable which will be called to extract the ``value`` of
the :class:`Plugin` when used as an input in a :ref:`Flow
<flows>`. The function should set ``self.value`` and also
return it. By default for :class:`File` :class:`Plugins
<raider.plugins.common.Plugin>` it puts the unmodified
contents of the :class:`File` found in the ``path``.
name_function:
A Callable which will be called to extract the ``name`` of the
:class:`Plugin` when it's not known in advance and the flag
``NAME_NOT_KNOWN_IN_ADVANCE`` is set. :class:`File` doesn't
use this.
plugins:
A List of :class:`Plugins <Plugin>` whose ``value`` needs to be
extracted first before current :class:`Cookie's <Cookie>`
value can be extracted. Used when the flag
``DEPENDS_ON_OTHER_PLUGINS`` is set.
value:
A string containing the :class:`File's <File>` output
``value`` to be used as input in the HTTP :term:`Requests
<Request>` which is just the :class:`File` contents.
flags:
An integer containing the flags that define the
:class:`Plugin's <raider.plugins.common.Plugin>`
behaviour. For :class:`File` :class:`Plugins no flags are set
by default.
"""
[docs] def __init__(
self,
path: str,
function: Callable[..., Optional[Union[str, bytes]]] = None,
flags: int = 0,
) -> None:
"""Initializes the :class:`File` :class:`Plugin
<raider.plugins.common.Plugin>`.
Creates a :class:`File` :class:`Plugin
<raider.plugins.common.Plugin>`, and populates its ``value``
with the contents of a :class:`File`` from the filesystem.
Args:
path:
A String with the path of the :class:`File`.
function:
An Optional Callable which is used to get the ``value`` of
the :class:`File` on runtime.
flags:
An integer containing the ``flags`` that define the
:class:`Plugin's <raider.plugins.common.Plugin>`
behaviour. By default no flag is set.
"""
self.path = path
if not function:
super().__init__(name=path, function=self.read_file, flags=flags)
else:
super().__init__(name=path, function=function, flags=flags)
[docs] def read_file(self) -> bytes:
"""Sets the :class:`Plugin's <raider.plugins.common.Plugin>`
``value`` to the file contents.
Returns:
A Bytes string containing the raw file contents.
"""
with open(self.path, "rb") as finput:
self.value = finput.read()
return self.value
[docs] @classmethod
def replace(
cls, path: str, old_value: str, new_value: Union[str, int, Plugin]
) -> "File":
"""Read a :class:`File` and replace strings with new ``value``s.
Use this in case the :class:`File` is a template that needs
some part of it replaced with a new string, for example:
If we have the file ``data.json``:
.. code-block::
{"data":
"username": $USERNAME$,
"nickname": "nickname",
[...]
}
And we want to replace ``$USERNAME$`` with the real username,
we can use:
.. code-block::
(File.replace "/path/to/data.json"
"$USERNAME"
"admin")
To replace every instance of ``$USERNAME$`` with our chosen
``value`` in ``new_value``.
Args:
path:
A String with the ``path`` of the :class:`File`.
old_value:
A String with the old ``value`` to be replaced.
new_value:
A String with the new ``value`` to be replaced.
"""
def replace_string(
original: bytes, old: str, new: Union[str, int, Plugin]
) -> Optional[bytes]:
if isinstance(new, Plugin):
if not new.value:
return None
return original.replace(
old.encode("utf-8"), new.value.encode("utf-8")
)
return original.replace(
old.encode("utf-8"), str(new).encode("utf-8")
)
with open(path, "rb") as finput:
file_contents = finput.read()
file_replace_plugin = cls(
path=path,
function=partial(
replace_string,
original=file_contents,
old=old_value,
new=new_value,
),
flags=Plugin.DEPENDS_ON_OTHER_PLUGINS,
)
if isinstance(new_value, Plugin):
file_replace_plugin.plugins.append(new_value)
return file_replace_plugin