Compare commits

...

6 Commits

Author SHA1 Message Date
Todor Bogosavljević
75d35e66b0 fixed further formatting in configuration part 2024-08-23 16:44:51 +02:00
Todor Bogosavljević
e6b825b05e fixed markdown formatting 2024-08-23 16:28:49 +02:00
Todor Bogosavljević
571aebf027 added README.md and more documentation 2024-08-23 16:26:14 +02:00
Todor Bogosavljević
427342a47a added example configuration 2024-08-23 12:28:28 +02:00
Todor Bogosavljević
f25d49b0e7 added main script + html 2024-08-23 12:28:12 +02:00
Todor Bogosavljević
d4bbeb043f updated .gitignore to handle defaults 2024-08-23 12:27:40 +02:00
5 changed files with 186 additions and 51 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
data.db
uploads/
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@@ -1,3 +1,77 @@
# upload.x1b.dev
# x1b / upload
A simple one file upload script to push images to the cloud.
A very minimal script to host a fully-fledged file upload & URL shortening service. It is under a 100 lines of Python code, and is fully customizable with an external configuration file.
It features:
- File upload
- URL shortening
- Size limits
- Time limits
- A landing page
- Customizable with an external file
## Usage
You can push files to it using the following command:
`curl -F file=@your_file up.x1b.dev.com`
You can shorten URL's by using this command and providing the target URL:
`curl -d "url=https://example.com" up.x1b.dev`
After any action you will get a URL with a 6-character key like:
`https://up.x1b.dev.com/xg29a6`
## Requirements
To run this you will need the following packages:
- Default Python packages (os, requests, etc.)
- Flask
- SQlite3
## Setup
After installing the required packages, you can configure your instance, and run the main python script with:
`python3 main.py`
Everything will be set up automatically for a first-time run.
## Configuration
The example configuration file looks like this:
```json
{
"upload_folder": "./uploads",
"max_file_size": 104857600,
"expiration_time": 604800,
"data_file": "data.db",
"base_url": "http://localhost:5000/",
"name": "up.x1b.dev"
}
```
And here is what the fields mean:
`upload_folder` - the folder where the uploads will be stored
`max_file_size` - maximum upload file size in bytes
`expiration_time` - how long files take to expire in seconds
`data_file` - sqlite3 database file
`base_url` - your domain url
`name` - what your website is called (doesn't have to be same as domain name)
#### Reverse proxy
You may want to set this up on a domain name, and not using the server IP. You can do this with a reverse proxy.
You can find documentation on how to set up a [reverse proxy with nginx here](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/).
## Contribution
Feel free to open pull requests to implement features, or issues to request features / report problems. The only rule is to keep the `main.py` file under 100 lines of code.
Thanks for reading, and happy uploading!

8
config.json Normal file
View File

@@ -0,0 +1,8 @@
{
"upload_folder": "./uploads",
"max_file_size": 104857600,
"expiration_time": 604800,
"data_file": "data.db",
"base_url": "http://localhost:5000/",
"name": "up.x1b.dev"
}

49
landing.html Normal file
View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{name}</title>
</head>
<body>
<pre style="font-size: 1.5em;">
{name} File Upload and URL Shortener
Welcome to the {name} file upload and URL shortening service.
This service allows you to easily upload files or shorten URLs for quick access.
Files are stored for {expiration_days} days, and URLs are shortened for convenient sharing.
Usage:
Upload a file:
curl -F file=@yourfile.ext {base_url}/
Shorten a URL:
curl -d "url=https://example.com" {base_url}/shorten
Details:
Files uploaded to this service are stored for {expiration_days} days or until they are automatically cleaned up.
The maximum file size is {max_file_size}MB.
URLs shortened with this service will redirect to the original URL for the same period.
Examples:
File upload:
curl -F file=@document.pdf {base_url}/
Response:
{base_url}/abcdef
URL shortening:
curl -d "url=https://example.com" {base_url}/shorten
Response:
{base_url}/ghijkl
Disclaimer:
This service is provided as-is with no guarantees.
Uploaded files and shortened URLs are not private and can be accessed by anyone with the link.
Use at your own risk.
</pre>
</body>
</html>

99
main.py
View File

@@ -1,67 +1,73 @@
import os
import uuid
import time
import json
import threading
from flask import Flask, request, redirect, send_from_directory, abort
import os, uuid, time, json, sqlite3, threading
from flask import Flask, request, redirect, send_from_directory, abort, render_template_string
app = Flask(__name__)
# config
UPLOAD_FOLDER = './uploads'
MAX_FILE_SIZE = 1024 * 1024 * 100 # last number = megabytes
EXPIRATION_TIME = 60 * 60 * 24 * 7 # last number = days
DATA_FILE = 'data.json'
URL_PREFIX = 'http://localhost:5000/'
with open('config.json') as f:
config = json.load(f)
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
UPLOAD_FOLDER = config['upload_folder']
MAX_FILE_SIZE = config['max_file_size']
EXPIRATION_TIME = config['expiration_time']
DATA_FILE = config['data_file']
URL_PREFIX = config['base_url']
NAME = config['name']
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def init_db():
with sqlite3.connect(DATA_FILE) as conn:
conn.execute('CREATE TABLE IF NOT EXISTS files (key TEXT PRIMARY KEY, type TEXT NOT NULL, path TEXT, url TEXT, expiry REAL)')
init_db()
def load_data():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r') as f:
return json.load(f)
return {}
with sqlite3.connect(DATA_FILE) as conn:
return {row[0]: {'type': row[1], 'path': row[2], 'url': row[3], 'expiry': row[4]} for row in conn.execute('SELECT * FROM files')}
def save_data():
with sqlite3.connect(DATA_FILE) as conn:
conn.execute('DELETE FROM files')
conn.executemany('INSERT INTO files VALUES (?, ?, ?, ?, ?)',
[(k, v['type'], v.get('path'), v.get('url'), v['expiry']) for k, v in data.items()])
data = load_data()
def save_data():
with open(DATA_FILE, 'w') as f:
json.dump(data, f)
def cleanup_files():
while True:
time.sleep(60 * 60)
time.sleep(3600)
now = time.time()
for key, item in list(data.items()):
if 'expiry' in item and now > item['expiry']:
try:
os.remove(item['path'])
except Exception as e:
print(f"Error removing file {item['path']}: {e}")
del data[key]
expired_keys = [k for k, v in data.items() if v['expiry'] < now]
for k in expired_keys:
if data[k]['type'] == 'file':
os.remove(data[k]['path'])
data.pop(k)
save_data()
threading.Thread(target=cleanup_files, daemon=True).start()
@app.route('/', methods=['GET'])
def landing_page():
with open('landing.html') as f:
html = f.read().format(
name=NAME,
base_url=URL_PREFIX.rstrip('/'),
expiration_days=EXPIRATION_TIME // 86400,
max_file_size=MAX_FILE_SIZE // 1048576
)
return render_template_string(html)
@app.route('/', methods=['POST'])
def upload_file():
file = request.files.get('file')
if not file:
return "No file uploaded\n", 400
if file.content_length > MAX_FILE_SIZE:
return "File too large\n", 400
if not file or file.content_length > MAX_FILE_SIZE:
return "No file uploaded or file too large\n", 400
filename = file.filename
key = uuid.uuid4().hex[:6]
filepath = os.path.join(UPLOAD_FOLDER, f"{key}_{filename}")
filepath = os.path.join(UPLOAD_FOLDER, f"{key}_{file.filename}")
file.save(filepath)
data[key] = {
'type': 'file',
'path': filepath,
'expiry': time.time() + EXPIRATION_TIME
}
data[key] = {'type': 'file', 'path': filepath, 'expiry': time.time() + EXPIRATION_TIME}
save_data()
return f"{URL_PREFIX}{key}\n"
@@ -72,11 +78,7 @@ def shorten_url():
return "No URL provided\n", 400
key = uuid.uuid4().hex[:6]
data[key] = {
'type': 'url',
'url': url,
'expiry': time.time() + EXPIRATION_TIME
}
data[key] = {'type': 'url', 'url': url, 'expiry': time.time() + EXPIRATION_TIME}
save_data()
return f"{URL_PREFIX}{key}\n"
@@ -88,10 +90,9 @@ def get_content(key):
if item['type'] == 'file':
return send_from_directory(UPLOAD_FOLDER, os.path.basename(item['path']))
elif item['type'] == 'url':
if item['type'] == 'url':
return redirect(item['url'])
else:
abort(404)
abort(404)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)