From e582c743630def90f29c342baba05cbf6b9f5aae Mon Sep 17 00:00:00 2001 From: Jack Eilles Date: Thu, 14 Dec 2023 11:39:23 +0000 Subject: [PATCH] kinda working??? cant pass a string through curl:( --- .flaskenv | 1 + .gitignore | 5 ++ README.md | 5 ++ app/__init__.py | 5 ++ app/routes.py | 82 +++++++++++++++++++++++++++++ app/templates/index.html | 1 + app/worker.py | 111 +++++++++++++++++++++++++++++++++++++++ config.py | 75 ++++++++++++++++++++++++++ requirements.txt | 4 ++ 9 files changed, 289 insertions(+) create mode 100644 .flaskenv create mode 100644 app/__init__.py create mode 100644 app/routes.py create mode 100644 app/templates/index.html create mode 100644 app/worker.py create mode 100644 config.py create mode 100644 requirements.txt diff --git a/.flaskenv b/.flaskenv new file mode 100644 index 0000000..97d8d12 --- /dev/null +++ b/.flaskenv @@ -0,0 +1 @@ +FLASK_APP=main.py \ No newline at end of file diff --git a/.gitignore b/.gitignore index 68bc17f..769c384 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ __pycache__/ # C extensions *.so +.vscode/ + # Distribution / packaging .Python build/ @@ -158,3 +160,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# xygt Files +data/ diff --git a/README.md b/README.md index e757376..d4f2f95 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ This repository hosts all of the code for the file hosting site 'xygt.cc'. +## About +xygt.cc is a simple, anonymous, temporary file-hosting service, designed with Python and Flask. + +This uses MongoDB by default for the file index, user database, and the URL shortening DB, I'm **not** adding support for SQL. + ## Website You can access the site on [https://xygt.cc](https://xygt.cc). diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..82b47ea --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,5 @@ +from flask import Flask + +app = Flask(__name__) + +from app import routes \ No newline at end of file diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..0622308 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,82 @@ +from app import app, worker +from config import Config, Errors +from flask import render_template, request, send_file +import os +import io +import random +import magic + +@app.route('/', methods=["GET", "POST"]) +def index(): + + # Check for a GET or POST request + if request.method == "GET": + return render_template('index.html') + + elif request.method == "POST": + + # Before anything else, we want to take the IP if the logging is enabled + if Config.ipLogEnabled == True: + ip = request.remote_addr + else: + # If not then return a 0 + ip = 0 + + # Init variables before they're passed + userid = request.form.get("userid") if request.form.get("userid") else None + filename = request.form.get("filename") if request.form.get("filename") else None + retention = request.form.get("retention") if request.form.get("retention") else None + id = request.form.get("filename") if Config.files.find_one({"id": filename}) is None else None + + # We got a file or a url? + if 'file' in request.files: + + # Grab the file and store it, this is a FileStorage object + file = request.files['file'] + + # Call the function to upload the file, this will return either HTTP Status codes or a 200 with a URL. + result, status = worker.uploadFile(file, ip, userid, filename, id, retention) + + result = "https://xygt.cc/{}".format(result) + + return result, status + + elif 'url' in request.form: + result, status = worker.shortURL(url, ip, userid, id, retention) + +@app.route('/') +def getData(id): + + # Does it exist in the files DB? + if Config.files.find_one({"id": id}) is not None: + data = Config.files.find_one({"id": id}) + + with open(os.path.join(Config.fileDir, id), "rb") as f: + file = f.read() + + # Get MIME type from file, if fails then use magic + try: + mimetype = data["mimetype"] + except KeyError: + mimetype = magic.from_buffer(file, mime=True) + + # Return the file with the correct MIME type + return send_file(io.BytesIO(file), mimetype=mimetype) + + # If not then check the URL Shortening DB + elif Config.url.find_one({"id": id}) is not None: + data = Config.url.find_one({"id": id}) + + return redirect(data["url"]) + + else: + return random.choice(Errors.file404) + +@app.route('//info') +def getInfo(id): + + return worker.idInfo(id) + +@app.route('/teapot') +def teapot(): + return 'I\'m a teapot. 418.', 418 \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..23769ba --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1 @@ +Under Construction - No frontend available yet. \ No newline at end of file diff --git a/app/worker.py b/app/worker.py new file mode 100644 index 0000000..6c0c8c8 --- /dev/null +++ b/app/worker.py @@ -0,0 +1,111 @@ +from config import disallowedMimeTypes, Errors, Config +import secrets +import magic +import datetime +import random +import time +import os + +def uploadFile(file, ip, userid, filename, id, retention): + + # Is the MIME and file size good? + if file.content_type not in disallowedMimeTypes: + if file.content_length <= Config.maxFileSize: + # We're going to check whether the id variable has been filled + + while True: # Loop to find an available file ID + id = randomHex() # Prevent conflicts if 2 of the same get made + if Config.files.find_one({'id': id}) is None: + filename = id + break + + if userid == None: + userid = 0 + elif Config.users.find_one({'userid': userid}) == None: + userid = 0 + + # Calculate retention before the file is written, we'll grab the filesize here as it's needed for the equation. + fileSize = round(float(file.content_length) / 1024, 2) + + if retention == None: + retention = (Config.minretention+(-Config.maxretention + Config.minretention)*pow((fileSize / Config.maxFileSize -1), 3)) + elif retention > (Config.minretention+(-Config.maxretention + Config.minretention)*pow((fileSize / Config.maxFileSize -1), 3)): + retention = (Config.minretention+(-Config.maxretention + Config.minretention)*pow((fileSize / Config.maxFileSize -1), 3)) + + if file. + # Create the file + with open(f"{os.path.abspath(Config.fileDir)}/{filename}", "wb") as f: + f.write(file.read()) + + date = time.mktime(datetime.datetime.now().timetuple()) + + # Create the dictionary that we'll insert into the db + data = { + 'id': id, + 'filename': filename, + 'filesize': fileSize, + 'retention': round(retention * 86400), # Convert to seconds + 'userid': userid, + 'ip': ip, + 'date': date, + 'expiry': date + round(retention * 86400) + } + + # Add the data and verify its there. + Config.files.insert_one(data) + print(Config.files.find_one({"id": id})) + + return id, 200 + else: + return random.choice(Errors.fileTooLarge), 400 + else: + return random.choice(Errors.fileTypeNotAllowed), 400 + +def shortenURL(url, ip, userid, id, retention): + # We're going to check whether the id variable has been filled + # If not then we'll generate one. (The ID variable will be the same as the filename if not rejected earlier.) + if id == None: + while True: # Loop to find an available file ID + id = randomHex() # Prevent conflicts if 2 of the same get made + if Config.files.find_one({'id': id}) is None: + filename = id + break + + if userid == None: + userid = 0 + elif Config.users.find_one({'userid': userid}) == None: + userid = 0 + + if retention == None: + retention = 14 + elif retention > 365: + retention = 365 + + data = { + "id": id, + "url": url, + "userid": userid, + "retention": retention, + "ip": ip + } + + Config.url.insert_one(data) + print(Config.url.find_one({"id": data["id"]})) + + return id + +def idInfo(id): + # Check files and url for the ID + if Config.files.find_one({"id": id}) is not None: + check = Config.files.find_one({"id": id}, {'_id': False}, {"ip": False}) + # "ip": False removes the IP from the returned data. + # If it's not there then check url + elif Config.url.find_one({"id": id}) is not None: + check = Config.url.find_one({"id": id}, {'_id': False}, {"ip": False}) + + # Return the mongodb info about the file, removing IP if its present + return check + +def randomHex(): + hexRand = ''.join(secrets.choice('0123456789abcdef') for _ in range(6)) + return hexRand diff --git a/config.py b/config.py new file mode 100644 index 0000000..f606a34 --- /dev/null +++ b/config.py @@ -0,0 +1,75 @@ +import os +from pymongo import MongoClient + +class Config: + # MongoDB init stuff + client = MongoClient("mongodb://localhost:27017/") + db = client["xygt"] + files = db["file"] + url = db["url"] + users = db["users"] + + # Basic configs + maxFileSize = 256 + premMaxFileSize = 512 + maxretention = 365 + minretention = 7 + fileDir = "./data" + ipLogEnabled = False + +quotes = { + "Anon /g/": "Biometrics are shit, you lose a limb and you're fucked.", + "Jack": "i named it xygt because it sounds cool lmao", + "Cow": "Does this server run Gentoo? (no it doesn't)", + "uname -a": "Linux xygt 6.1.0-12-arm64 #1 SMP Debian 6.1.52-1 (2023-09-07) aarch64 GNU/Linux", + "Luna": "shit you moth", + "Maze": "Mein Gott Leute, meine Mama hat mir einfach erlaubt dass ich Cola trinken darf! Wie cool ist das bitte? Jetzt zocke ich Fortnite und trinke Cola! YIPPEE!", +} + +disallowedMimeTypes = [ + "application/x-dosexec", + "application/java-archive", + "application/java-vm" +] + +class Errors: + file404 = [ + "The file you seek does not exist...", + "Nope, can't find it.", + "AVE FOOKIN LOST IT", + "My shitty filehost can't find this, sorry lmao", + "Your file could not be found.", + "You fucked up somewhere, this link doesn't work.", + "If someone gave you this link, go shout at them, it's broken.", + "404.", + "The file isn't in our db, so it's probably expired or just never existed in the first place." + ] + + fileTooLarge = [ + "Too big, nah.", + "File size goes over the limit, you're not uploading this" + "Your file is too large, get it under 256mb first.", + "I don't know what the hell you're trying to upload but it's over 256mb, so no.", + "Your file is over 256mb, remember, we don't store your files forever!", + "File is too big, 265mb is the limit.", + "nuh uh, too big" + ] + + fileTypeNotAllowed = [ + "Nice try idiot. You're not uploading that onto my server.", + "No executables allowed, NO EXCEPTIONS.", + "So bud... what you trying to do there? You can't upload executables you know.", + "Nah, not getting that on here today.", + "Stop trying to upload executables, goddamnit.", + "Executables can suck my dick, you're not uploading that" + "nuh uh (executables not allowed)" + ] + + def file404Error(): + return random.choice(self.file404.items()) + + def fileTooLargeError(): + return random.choice(self.fileTooLarge.items()) + + def fileTypeNotAllowedError(): + return random.choice(self.fileTypeNotAllowed.items()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e0d330f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +flask +flask-dotenv +pymongo +python-magic \ No newline at end of file