diff --git a/configtest.py b/configtest.py new file mode 100644 index 0000000..c0971f9 --- /dev/null +++ b/configtest.py @@ -0,0 +1,139 @@ +import os +import settings as settings + + +# Default settings +defaults = { + "UPLOAD_FOLDER": "/var/www/img", + "ALLOWED_EXTENSIONS": [".png", ".jpg", ".jpeg", ".svg", ".bmp", ".gif", ".ico", ".webp"], + "ROOTURL": "https://img.bbaovanc.com/", + "SAVELOG": "savelog.log", + "SAVELOG_CHMOD": "0o644", + "UPLOADKEYS_CHMOD": "0o400", + "SAVELOG_KEYPREFIX": 4, +} + +deftypes = { + "UPLOAD_FOLDER": str, + "ALLOWED_EXTENSIONS": list, + "ROOTURL": str, + "SAVELOG": str, + "SAVELOG_CHMOD": int, + "UPLOADKEYS_CHMOD": int, + "SAVELOG_KEYPREFIX": int, +} + + +# Check for unset settings +checksettings = list(defaults.keys()) +unset_settings = [i for i in defaults.keys() if i not in dir(settings)] +if len(unset_settings) > 0: + for unset in unset_settings: + checksettings.remove(unset) + print("[!] {0} is unset. The default value is type {1} with value {2}".format(unset, deftypes[unset].__name__, defaults[unset])) +else: + print("[" + u"\u2713" + "] Found all required settings!") + + +# Check if types of settings are correct +typesgood = True +typeswrong = [] +for testtype in checksettings: + if type(getattr(settings, testtype)) is not deftypes[testtype]: + print("[!] {0} requires {1}, but is {2}".format(testtype, deftypes[testtype].__name__, type(getattr(settings, testtype)).__name__)) + typeswrong.append(testtype) + typesgood = False + +if typesgood: + print("[" + u"\u2713" + "] Types are good!") + + +# Check if allowed extensions all start with a . +invalid_exts = [] +if "ALLOWED_EXTENSIONS" in checksettings: + for e in settings.ALLOWED_EXTENSIONS: + if not e.startswith("."): + invalid_exts.append(e) + + if len(invalid_exts) > 0: + print("[!] The following extensions listed in ALLOWED_EXTENSIONS are invalid:") + for e in invalid_exts: + print(" {0} is listed in ALLOWED_EXTENSIONS, but doesn't start with a .".format(e)) + else: + print("[" + u"\u2713" + "] ALLOWED_EXTENSIONS is good!") + + +# Check if UPLOAD_FOLDER exists +uploadfolder_exists = True +if "UPLOAD_FOLDER" in checksettings: + if not os.path.isdir(settings.UPLOAD_FOLDER): + uploadfolder_exists = False + print("[!] The directory set in UPLOAD_FOLDER ('{0}') doesn't exist!".format(settings.UPLOAD_FOLDER)) + else: + print("[" + u"\u2713" + "] UPLOAD_FOLDER exists!") + + +# Check if ROOTURL starts with http(s):// and ends with / +rooturl_good = True +if "ROOTURL" in checksettings: + if settings.ROOTURL.startswith("http://") or settings.ROOTURL.startswith("https://"): + pass + else: + rooturl_good = False + print(settings.ROOTURL) + print(settings.ROOTURL.startswith("https://")) + print("[!] ROOTURL does not start with `http://` or `https://`! This may cause issues!") + if not settings.ROOTURL.endswith("/"): + rooturl_good = False + print("[!] ROOTURL does not end with a `/`. This WILL cause issues!") + + if not rooturl_good: + print(" With your current settings, this is what a generated url would look like:") + print(" {0}example.png".format(settings.ROOTURL)) + else: + print("[" + u"\u2713" + "] ROOTURL is good!") + + +# Ask the user if SAVELOG is the intended filename +if "SAVELOG" in checksettings: + print("[*] SAVELOG was interpreted to be {0}".format(settings.SAVELOG)) + print("[*] If this is not the intended filename, please fix it.") + + +# Show summary +print() +print("----- SUMMARY -----") +summarygood = True +if len(unset_settings) > 0: + summarygood = False + print("Unset settings:") + for unset in unset_settings: + print(" {0}".format(unset)) + +if len(typeswrong) > 0: + summarygood = False + print("Incorrect types:") + for wtype in typeswrong: + print(" {0}".format(wtype)) + +if len(invalid_exts) > 0: + summarygood = False + print("Invalid extensions:") + for wext in invalid_exts: + print(" '{0}'".format(wext)) + +if not uploadfolder_exists: + summarygood = False + print("UPLOAD_FOLDER ({0}) does not exist!".format(settings.UPLOAD_FOLDER)) + +if not rooturl_good: + summarygood = False + print("ROOTURL may cause issues!") + print("With current settings, this is what a generated URL would look like:") + print("{0}example.png".format(settings.ROOTURL)) + +if "SAVELOG" in checksettings: + print("[*] SAVELOG is {0}".format(settings.SAVELOG)) + +if summarygood: + print("[" + u"\u2713" + "] This configuration passes all tests!") diff --git a/imgupload.py b/imgupload.py new file mode 100644 index 0000000..b877f0a --- /dev/null +++ b/imgupload.py @@ -0,0 +1,114 @@ +from flask import Flask, request, jsonify, abort, Response +from flask_api import status +from pathlib import Path +import string +import random +import os +import datetime + +import settings # app settings (such as allowed extensions) + + +ALPHANUMERIC = string.ascii_letters + string.digits # uppercase, lowercase, and numbers + +app = Flask(__name__) # app is the app + + +def allowed_extension(testext): + if testext in settings.ALLOWED_EXTENSIONS: + return True + else: + return False + + +def generate_name(extension): + namefound = False + while not namefound: + fname = ''.join((random.choice(ALPHANUMERIC) for i in range(8))) + str(extension) + if not Path(fname).is_file(): + namefound = True + return fname + + +def log_savelog(key, ip, savedname): + if settings.SAVELOG_KEYPREFIX > 0: + with open(settings.SAVELOG, "a+") as slogf: + slogf.write("[{0}] {1}: {2} - {3}\n".format(datetime.datetime.now(), key[:settings.SAVELOG_KEYPREFIX], ip, savedname)) + os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD) + else: + with open(settings.SAVELOG, "a+") as slogf: + slogf.write("[{0}] {1} - {2}\n".format(datetime.datetime.now(), ip, savedname)) + os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD) + + +@app.route("/upload", methods = ["POST"]) +def upload(): + if request.method == "POST": # sanity check: make sure it's a POST request + print("Request method was POST!") + + os.chmod("uploadkeys", settings.UPLOADKEYS_CHMOD) + print("Changed permissions of `uploadkeys`") + with open("uploadkeys", "r") as keyfile: # load valid keys + validkeys = keyfile.readlines() + validkeys = [x.strip("\n") for x in validkeys] + while "" in validkeys: + validkeys.remove("") + print("removed blank key") + print("Valid keys: {0}".format(validkeys)) + print("Loaded validkeys") + + if "uploadKey" in request.form: # if an uploadKey was provided + if request.form["uploadKey"] in validkeys: # check if uploadKey is valid + print("Key is valid!") + + if "imageUpload" in request.files: # check if image to upload was provided + f = request.files["imageUpload"] # f is the image to upload + print("Found uploaded image") + else: + print("No image upload was found!") + return jsonify({'status': 'error', 'error': 'NO_IMAGE_UPLOADED'}), status.HTTP_400_BAD_REQUEST + + if f.filename == "": # make sure the filename isn't blank + print("Filename is blank") + return jsonify({'status': 'error', 'error': 'FILENAME_BLANK'}), status.HTTP_400_BAD_REQUEST + + fext = Path(f.filename).suffix # get the uploaded extension + print("Uploaded file extensions is {0}".format(fext)) + if allowed_extension(fext): # if the extension is allowed + fname = generate_name(fext) # generate file name + print("Generated name: {0}".format(fname)) + + if f: # if the uploaded image exists + print("Uploaded image exists, obviously.") + f.save(os.path.join(settings.UPLOAD_FOLDER, fname)) # save the image + print("Saved to {0}".format(fname)) + url = settings.ROOTURL + fname # construct the url to the image + if settings.SAVELOG != "/dev/null": + log_savelog(request.form["uploadKey"], request.remote_addr, fname) + print("Logged message to savelog") + print("Returning json response") + return jsonify({'status': 'success', 'url': url, 'name': fname, 'uploadedName': f.filename}), status.HTTP_201_CREATED + else: # this shouldn't happen + print("Um... uploaded image is nonexistent..? Please report this error!") + return jsonify({'status': 'error', 'error': 'UPLOADED_IMAGE_FAILED_SANITY_CHECK_1'}), status.HTTP_400_BAD_REQUEST + + else: # if the extension was invalid + print("Uploaded extension is invalid!") + abort(415) + + else: # if the key was not valid + print("Key is invalid!") + print("Request key: {0}".format(request.form["uploadKey"])) + abort(401) + + else: # if uploadKey was not found in request body + print("No uploadKey found in request!") + abort(401) + + + else: # if the request method wasn't post + print("Request method was not POST!") + abort(405) + +if __name__ == "__main__": + print("Run with `flask` or a WSGI server!") diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..55be603 --- /dev/null +++ b/settings.py @@ -0,0 +1,7 @@ +UPLOAD_FOLDER = "/var/www/img" +ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".svg", ".bmp", ".gif", ".ico", ".webp"] +ROOTURL = "https://img.bbaovanc.com/" +SAVELOG = "savelog.log" +SAVELOG_CHMOD = 0o644 +UPLOADKEYS_CHMOD = 0o600 +SAVELOG_KEYPREFIX = 4