Initial commit
This commit is contained in:
commit
3e72ecab20
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.history
|
||||||
|
.venv
|
||||||
|
src/upload/__pycache__
|
12
Makefile
Normal file
12
Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
all: ensure_env build serve
|
||||||
|
|
||||||
|
ensure_env:
|
||||||
|
-@python3 -m venv .venv
|
||||||
|
|
||||||
|
build:
|
||||||
|
./.venv/bin/python -m pip install build
|
||||||
|
./.venv/bin/python -m build .
|
||||||
|
./.venv/bin/python -m pip install -e .
|
||||||
|
|
||||||
|
serve:
|
||||||
|
./.venv/bin/rupload.serve
|
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# RUpload
|
||||||
|
|
||||||
|
## About
|
||||||
|
Easiest way to upload files to a server.
|
||||||
|
|
||||||
|
Since it doesn't have captcha or whatsoever, it's only usable with people you trust on a not listed domain. It's destined for public use, as long the domain isn't listed in a search engine. It has a limitation for file size, so it would never go terribly wrong.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```
|
||||||
|
python3 -m venv .venv
|
||||||
|
./venv/bin/python -m pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```
|
||||||
|
rupload.serve [host(127.0.0.1)] [port] [destination path for uploads] [max file size in bytes]
|
||||||
|
```
|
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
25
setup.cfg
Normal file
25
setup.cfg
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[metadata]
|
||||||
|
name = rupload
|
||||||
|
version = 1.3.37
|
||||||
|
description = upload tool
|
||||||
|
author = Retoor
|
||||||
|
author_email = retoor@molodetz.nl
|
||||||
|
license = MIT
|
||||||
|
long_description = file: README.md
|
||||||
|
long_description_content_type = text/markdown
|
||||||
|
|
||||||
|
[options]
|
||||||
|
packages = find:
|
||||||
|
package_dir =
|
||||||
|
= src
|
||||||
|
python_requires = >=3.7
|
||||||
|
install_requires =
|
||||||
|
aiohttp==3.10.10
|
||||||
|
dataset==1.6.2
|
||||||
|
|
||||||
|
[options.packages.find]
|
||||||
|
where = src
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
rupload.serve = rupload.cli:main
|
29
src/rupload.egg-info/PKG-INFO
Normal file
29
src/rupload.egg-info/PKG-INFO
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: rupload
|
||||||
|
Version: 1.3.37
|
||||||
|
Summary: upload tool
|
||||||
|
Author: Retoor
|
||||||
|
Author-email: retoor@molodetz.nl
|
||||||
|
License: MIT
|
||||||
|
Requires-Python: >=3.7
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: aiohttp==3.10.10
|
||||||
|
Requires-Dist: dataset==1.6.2
|
||||||
|
|
||||||
|
# RUpload
|
||||||
|
|
||||||
|
## About
|
||||||
|
Easiest way to upload files to a server.
|
||||||
|
|
||||||
|
Since it doesn't have captcha or whatsoever, it's only usable with people you trust on a not listed domain. It's destined for public use, as long the domain isn't listed in a search engine. It has a limitation for file size, so it would never go terribly wrong.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```
|
||||||
|
python3 -m venv .venv
|
||||||
|
./venv/bin/python -m pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```
|
||||||
|
rupload.serve [host(127.0.0.1)] [port] [destination path for uploads] [max file size in bytes]
|
||||||
|
```
|
13
src/rupload.egg-info/SOURCES.txt
Normal file
13
src/rupload.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
README.md
|
||||||
|
pyproject.toml
|
||||||
|
setup.cfg
|
||||||
|
src/rupload/__init__.py
|
||||||
|
src/rupload/__main__.py
|
||||||
|
src/rupload/app.py
|
||||||
|
src/rupload/cli.py
|
||||||
|
src/rupload.egg-info/PKG-INFO
|
||||||
|
src/rupload.egg-info/SOURCES.txt
|
||||||
|
src/rupload.egg-info/dependency_links.txt
|
||||||
|
src/rupload.egg-info/entry_points.txt
|
||||||
|
src/rupload.egg-info/requires.txt
|
||||||
|
src/rupload.egg-info/top_level.txt
|
1
src/rupload.egg-info/dependency_links.txt
Normal file
1
src/rupload.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
2
src/rupload.egg-info/entry_points.txt
Normal file
2
src/rupload.egg-info/entry_points.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[console_scripts]
|
||||||
|
rupload.serve = rupload.cli:main
|
2
src/rupload.egg-info/requires.txt
Normal file
2
src/rupload.egg-info/requires.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
aiohttp==3.10.10
|
||||||
|
dataset==1.6.2
|
1
src/rupload.egg-info/top_level.txt
Normal file
1
src/rupload.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
rupload
|
0
src/rupload/__init__.py
Normal file
0
src/rupload/__init__.py
Normal file
0
src/rupload/__main__.py
Normal file
0
src/rupload/__main__.py
Normal file
BIN
src/rupload/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
src/rupload/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/rupload/__pycache__/app.cpython-312.pyc
Normal file
BIN
src/rupload/__pycache__/app.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/rupload/__pycache__/cli.cpython-312.pyc
Normal file
BIN
src/rupload/__pycache__/cli.cpython-312.pyc
Normal file
Binary file not shown.
151
src/rupload/app.py
Normal file
151
src/rupload/app.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import aiohttp
|
||||||
|
from aiohttp import web
|
||||||
|
import asyncio
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
class Rupload(web.Application):
|
||||||
|
def __init__(self, upload_path, max_file_size):
|
||||||
|
self.upload_path = upload_path
|
||||||
|
self.max_file_size = max_file_size
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
|
||||||
|
UPLOAD_FOLDER = "uploads"
|
||||||
|
pathlib.Path(UPLOAD_FOLDER).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
UPLOAD_PAGE = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>File Upload</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f4f4f9;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Upload Your File</h1>
|
||||||
|
<form action="/upload" method="POST" enctype="multipart/form-data">
|
||||||
|
<label for="file">Choose a file to upload:</label>
|
||||||
|
<input type="file" name="file" id="file" required>
|
||||||
|
<button type="submit">Upload</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_upload(request):
|
||||||
|
reader = await request.multipart()
|
||||||
|
field = await reader.next()
|
||||||
|
|
||||||
|
if field.name == "file":
|
||||||
|
|
||||||
|
filename = field.filename
|
||||||
|
print(filename)
|
||||||
|
if ".." or "/" in filename:
|
||||||
|
return web.Response(status=400, text="Invalid filename.")
|
||||||
|
|
||||||
|
filepath = pathlib.Path(UPLOAD_FOLDER).joinpath(filename)
|
||||||
|
|
||||||
|
if filepath.exists():
|
||||||
|
return web.Response(status=400, text="File already exists.")
|
||||||
|
|
||||||
|
with filepath.open("wb") as f:
|
||||||
|
file_size = 0
|
||||||
|
while True:
|
||||||
|
chunk = await field.read_chunk()
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
file_size += len(chunk)
|
||||||
|
if file_size > max_file_size:
|
||||||
|
f.close()
|
||||||
|
f.unlink()
|
||||||
|
return web.Response(
|
||||||
|
status=413,
|
||||||
|
text="File is too large. Maximum file size is {} bytes.".format(
|
||||||
|
max_file_size
|
||||||
|
),
|
||||||
|
)
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
return web.Response(text=f"File {filename} uploaded successfully!")
|
||||||
|
return web.Response(status=400, text="No file uploaded.")
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_index(request):
|
||||||
|
return web.Response(text=UPLOAD_PAGE, content_type="text/html")
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(upload_path="upload", max_file_size=1024 * 1024 * 50):
|
||||||
|
app = Rupload(upload_path=upload_path, max_file_size=max_file_size)
|
||||||
|
app.add_routes([web.get("/", handle_index), web.post("/upload", handle_upload)])
|
||||||
|
return app
|
36
src/rupload/cli.py
Normal file
36
src/rupload/cli.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import argparse
|
||||||
|
from aiohttp import web
|
||||||
|
from rupload.app import create_app
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description="Start the file upload server.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--hostname", type=str, default="127.0.0.1", help="The hostname for the server."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--port", type=int, default=8081, help="The port to bind the server to."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--upload_folder",
|
||||||
|
type=str,
|
||||||
|
default="uploads",
|
||||||
|
help="Directory to store uploaded files.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max_file_size",
|
||||||
|
type=int,
|
||||||
|
default=50 * 1024 * 1024,
|
||||||
|
help="Maximum file size in bytes (default is 50MB).",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
app = create_app(upload_path=args.upload_folder, max_file_size=args.max_file_size)
|
||||||
|
web.run_app(app, host=args.hostname, port=args.port)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user