sharedserver 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sharedserver/__init__.py +4 -0
- sharedserver/__main__.py +6 -0
- sharedserver/cli.py +76 -0
- sharedserver/clipboard.py +32 -0
- sharedserver/config.py +151 -0
- sharedserver/file_manager.py +131 -0
- sharedserver/models.py +41 -0
- sharedserver/security.py +51 -0
- sharedserver/server.py +142 -0
- sharedserver/static/app.css +226 -0
- sharedserver/static/app.js +358 -0
- sharedserver/templates/index.html +89 -0
- sharedserver/websocket.py +57 -0
- sharedserver-0.1.0.dist-info/METADATA +177 -0
- sharedserver-0.1.0.dist-info/RECORD +19 -0
- sharedserver-0.1.0.dist-info/WHEEL +5 -0
- sharedserver-0.1.0.dist-info/entry_points.txt +2 -0
- sharedserver-0.1.0.dist-info/licenses/LICENSE +22 -0
- sharedserver-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from fastapi import WebSocket, WebSocketDisconnect
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from .models import ClipboardUpdate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClipboardHub:
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self.content = ""
|
|
14
|
+
self.clients: set[WebSocket] = set()
|
|
15
|
+
self.lock = asyncio.Lock()
|
|
16
|
+
|
|
17
|
+
async def connect(self, websocket: WebSocket) -> None:
|
|
18
|
+
await websocket.accept()
|
|
19
|
+
async with self.lock:
|
|
20
|
+
self.clients.add(websocket)
|
|
21
|
+
content = self.content
|
|
22
|
+
await websocket.send_json({"type": "clipboard", "content": content})
|
|
23
|
+
|
|
24
|
+
async def disconnect(self, websocket: WebSocket) -> None:
|
|
25
|
+
async with self.lock:
|
|
26
|
+
self.clients.discard(websocket)
|
|
27
|
+
|
|
28
|
+
async def broadcast(self, content: str) -> None:
|
|
29
|
+
async with self.lock:
|
|
30
|
+
self.content = content
|
|
31
|
+
clients = tuple(self.clients)
|
|
32
|
+
failed: list[WebSocket] = []
|
|
33
|
+
for client in clients:
|
|
34
|
+
try:
|
|
35
|
+
await client.send_json({"type": "clipboard", "content": content})
|
|
36
|
+
except (RuntimeError, WebSocketDisconnect):
|
|
37
|
+
failed.append(client)
|
|
38
|
+
if failed:
|
|
39
|
+
async with self.lock:
|
|
40
|
+
self.clients.difference_update(failed)
|
|
41
|
+
|
|
42
|
+
async def handle(self, websocket: WebSocket) -> None:
|
|
43
|
+
await self.connect(websocket)
|
|
44
|
+
try:
|
|
45
|
+
while True:
|
|
46
|
+
try:
|
|
47
|
+
message = ClipboardUpdate.model_validate(await websocket.receive_json())
|
|
48
|
+
except (ValidationError, ValueError):
|
|
49
|
+
await websocket.send_json(
|
|
50
|
+
{"type": "error", "message": "invalid clipboard message"}
|
|
51
|
+
)
|
|
52
|
+
continue
|
|
53
|
+
await self.broadcast(message.content)
|
|
54
|
+
except WebSocketDisconnect:
|
|
55
|
+
pass
|
|
56
|
+
finally:
|
|
57
|
+
await self.disconnect(websocket)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sharedserver
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-configuration LAN file sharing and real-time text clipboard
|
|
5
|
+
Author: Kyi Wong
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/NihaoKangkang/sharedserver
|
|
8
|
+
Project-URL: Repository, https://github.com/NihaoKangkang/sharedserver
|
|
9
|
+
Project-URL: Issues, https://github.com/NihaoKangkang/sharedserver/issues
|
|
10
|
+
Keywords: file-sharing,clipboard,lan,fastapi,websocket
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: fastapi<1,>=0.115
|
|
25
|
+
Requires-Dist: PyYAML<7,>=6
|
|
26
|
+
Requires-Dist: uvicorn<1,>=0.30
|
|
27
|
+
Requires-Dist: websockets<17,>=13
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build<2,>=1.2; extra == "dev"
|
|
30
|
+
Requires-Dist: httpx2<3,>=2; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest<9,>=8; extra == "dev"
|
|
32
|
+
Requires-Dist: twine<7,>=5; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# SharedServer
|
|
36
|
+
|
|
37
|
+
SharedServer is a zero-configuration, login-free LAN file server and real-time text clipboard. It is as easy to start as `python -m http.server`, while adding uploads, directory browsing, live text synchronization, and allowlisted clipboard actions.
|
|
38
|
+
|
|
39
|
+
> SharedServer has no authentication. Run it only on a trusted home, lab, or development network. Never expose it directly to the public internet.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- A shared text clipboard synchronized between all open pages over WebSocket
|
|
44
|
+
- A responsive file browser with directory navigation, file sizes, and modification times
|
|
45
|
+
- File uploads from desktop and mobile browsers with live progress
|
|
46
|
+
- Folder creation and safe, non-overwriting uploads
|
|
47
|
+
- Copy actions for allowlisted text and image formats
|
|
48
|
+
- Download-only behavior for other formats such as ZIP, RAR, 7z, EXE, APK, and ISO
|
|
49
|
+
- Protection against path traversal, directory escape, symlink escape, and unsafe filenames
|
|
50
|
+
- No accounts, database, device discovery, template engine, or frontend framework
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
- Python 3.10 or newer
|
|
55
|
+
- A modern desktop or mobile browser
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Install from PyPI:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install sharedserver
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Install from source:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/NihaoKangkang/sharedserver.git
|
|
69
|
+
cd sharedserver
|
|
70
|
+
pip install .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
Run this command from the directory you want to share:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m sharedserver 8080
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## CLI reference
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
usage: sharedserver [-h] [--host HOST] [-d DIRECTORY] [-c CONFIG]
|
|
86
|
+
[--max-upload-size MB] [--version]
|
|
87
|
+
[port]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Argument | Description |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `port` | Listening port. Uses the configuration value or `8000` by default. |
|
|
93
|
+
| `--host HOST` | Bind address. Defaults to `0.0.0.0`. |
|
|
94
|
+
| `-d, --directory PATH` | Directory to share. Defaults to the current directory. |
|
|
95
|
+
| `-c, --config PATH` | YAML configuration file. |
|
|
96
|
+
| `--max-upload-size MB` | Maximum upload size in MiB. Defaults to 1024 MiB. |
|
|
97
|
+
| `--version` | Print the installed version. |
|
|
98
|
+
|
|
99
|
+
CLI arguments override configuration file values. Without `--config`, SharedServer automatically loads `sharedserver.yaml` from the current directory when it exists.
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
Copy `sharedserver.example.yaml` to `sharedserver.yaml` and edit it as needed:
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
server:
|
|
107
|
+
host: 0.0.0.0
|
|
108
|
+
port: 8080
|
|
109
|
+
|
|
110
|
+
share:
|
|
111
|
+
directory: ./
|
|
112
|
+
max_upload_size: 1073741824
|
|
113
|
+
|
|
114
|
+
clipboard:
|
|
115
|
+
max_text_size: 2097152
|
|
116
|
+
text_extensions:
|
|
117
|
+
- .txt
|
|
118
|
+
- .md
|
|
119
|
+
image_extensions:
|
|
120
|
+
- .png
|
|
121
|
+
- .jpg
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Relative shared directory paths are resolved from the configuration file's directory. Custom extension lists replace the defaults, and matching is case-insensitive.
|
|
125
|
+
|
|
126
|
+
Default text extensions:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
.txt .md .json .xml .yaml .yml .csv .log .py .js .html .css .java .c .cpp
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Default image extensions:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
.png .jpg .jpeg .webp
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Security model
|
|
139
|
+
|
|
140
|
+
- Every user-supplied path is resolved with `Path.resolve()` and checked against the shared root.
|
|
141
|
+
- Absolute paths and `..` path segments are rejected.
|
|
142
|
+
- Symlinks are hidden from directory listings and rejected by file operations.
|
|
143
|
+
- Upload filenames are stripped of path components, control characters, and unsafe cross-platform characters.
|
|
144
|
+
- Uploads are written to a temporary file and published without overwriting an existing file.
|
|
145
|
+
- Both `Content-Length` validation and streamed byte counting enforce the upload limit.
|
|
146
|
+
- Clipboard actions and server read endpoints use the same strict extension allowlists.
|
|
147
|
+
- Browser WebSocket connections from a different origin are rejected.
|
|
148
|
+
- Shared text exists only in process memory and is cleared when the server stops.
|
|
149
|
+
|
|
150
|
+
SharedServer intentionally has no login or authorization layer. Every device that can reach the server can read and write the shared directory and shared text.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
Clone the project and create a virtual environment:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
git clone https://github.com/NihaoKangkang/sharedserver.git
|
|
158
|
+
cd sharedserver
|
|
159
|
+
python -m venv .venv
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Activate the environment, then install development dependencies:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pip install -e ".[dev]"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Start a development server:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
python -m sharedserver 8080 --directory .
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
sharedserver/__init__.py,sha256=fcXQwomL1kmmlLnKRSx_v3eeMDHiskXHGWOdqrgPpBA,52
|
|
2
|
+
sharedserver/__main__.py,sha256=14FfnaF7zY550dRxcOV5XinMWDf5fRBJZkgTBoqeVj8,63
|
|
3
|
+
sharedserver/cli.py,sha256=InIP39ACgt_YL9xjHHqCStjqJdqT8NSbbNuCq0HbVg8,2447
|
|
4
|
+
sharedserver/clipboard.py,sha256=y_Kn30yJnJbhNL1isybDLiKv_6XdZ5Xh__vjIrbxkXY,1102
|
|
5
|
+
sharedserver/config.py,sha256=pPMbLFuSiQhT5mL52ro8rxhKPKdSguFDumVdcWIWF-M,5018
|
|
6
|
+
sharedserver/file_manager.py,sha256=wAKIPy-yY1iQaBTbEQsqUIrDrBEK8Ca5bhV-K8S1T8U,5049
|
|
7
|
+
sharedserver/models.py,sha256=8UNpOFRBFUsZKTEcPD5qN5w_xX9BFlZLHHyGovlTsbA,725
|
|
8
|
+
sharedserver/security.py,sha256=qrH-wiST8rOQLAJY4S3zU12wlWk5SNnWRryXnnZXbB0,1734
|
|
9
|
+
sharedserver/server.py,sha256=rO-9Pw2dxOmCN7A1OhVXtHxzYELOTM7NKKuBaGrZlTA,5902
|
|
10
|
+
sharedserver/websocket.py,sha256=o7G9ZWFYGncxN9cpSWP0mqC8SOohSCugkvQe-Tzs3Xg,1930
|
|
11
|
+
sharedserver/static/app.css,sha256=Ll-940sgztrniR-1HUxzcaLMqjDdc6pftzJd78q3uPs,9186
|
|
12
|
+
sharedserver/static/app.js,sha256=6e4nUsZPPjJhihUz4X4Cq-OKb_dmlk9Gy5Ew7IlYQZI,12818
|
|
13
|
+
sharedserver/templates/index.html,sha256=5rG2p52LH2tuulrhjOtz8gbfhuof0TOfdha1mJYf_uw,3380
|
|
14
|
+
sharedserver-0.1.0.dist-info/licenses/LICENSE,sha256=inYxby017tQkLDQchOc_uUz2nDVLJSY___IjpZcGid4,1066
|
|
15
|
+
sharedserver-0.1.0.dist-info/METADATA,sha256=WDcyZRyLVmtJ6Z89kke03G7eAbgm3f0LnX-0Lmw5Huc,5553
|
|
16
|
+
sharedserver-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
17
|
+
sharedserver-0.1.0.dist-info/entry_points.txt,sha256=rsNs9N4tdGREmL84F9dD50h-8eUBjD9L_MAsJzmtY6c,55
|
|
18
|
+
sharedserver-0.1.0.dist-info/top_level.txt,sha256=TRb9rewpeC-6OHWWPSBGa-b2CrV3hMmusWV-KhpRgX0,13
|
|
19
|
+
sharedserver-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kyi Wong
|
|
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
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sharedserver
|