Compare commits
No commits in common. "master" and "v2.3" have entirely different histories.
27
.gitignore
vendored
27
.gitignore
vendored
@ -1,8 +1,3 @@
|
|||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python
|
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python
|
|
||||||
|
|
||||||
### Python ###
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
@ -55,7 +50,6 @@ coverage.xml
|
|||||||
*.py,cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
pytestdebug.log
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
@ -76,7 +70,6 @@ instance/
|
|||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
doc/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
@ -116,7 +109,6 @@ venv/
|
|||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
pythonenv*
|
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
@ -136,25 +128,6 @@ dmypy.json
|
|||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# profiling data
|
|
||||||
.prof
|
|
||||||
|
|
||||||
### VisualStudioCode ###
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
### VisualStudioCode Patch ###
|
|
||||||
# Ignore all local history of files
|
|
||||||
.history
|
|
||||||
.ionide
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python
|
|
||||||
|
|
||||||
# imgupload custom
|
# imgupload custom
|
||||||
uploadkeys
|
uploadkeys
|
||||||
savelog.log
|
savelog.log
|
||||||
|
47
README.md
47
README.md
@ -35,25 +35,23 @@ First, fork [this repository](https://git.bbaovanc.com/bbaovanc/imgupload). If y
|
|||||||
|
|
||||||
Note: replace `www-data` with whatever user your webserver runs as.
|
Note: replace `www-data` with whatever user your webserver runs as.
|
||||||
|
|
||||||
1. Go to /srv: `cd /srv`
|
1. Make /srv/imgupload: `sudo mkdir /srv/imgupload`
|
||||||
2. Clone the repository: `git clone https://git.bbaovanc.com/bbaovanc/imgupload.git`
|
2. Change ownership of /srv/imgupload: `sudo chown www-data:www-data /srv/imgupload`
|
||||||
3. Change ownership of /srv/imgupload: `sudo chown www-data:www-data /srv/imgupload`
|
3. Enter www-data user: `sudo su www-data`
|
||||||
4. Enter www-data user: `sudo su www-data`
|
4. Change directories to /srv/imgupload: `cd /srv/imgupload`
|
||||||
5. Change directories to /srv/imgupload: `cd /srv/imgupload`
|
5. Clone the repository: `git clone https://git.bbaovanc.com/bbaovanc/imgupload.git`
|
||||||
6. Checkout the version you want (replace [version] with desired version tag: `git checkout [version]`
|
6. Enter the imgupload directory: `cd imgupload`
|
||||||
7. Enter the imgupload directory: `cd imgupload`
|
7. Create a virtualenv: `python3 -m venv env`
|
||||||
8. Create a virtualenv: `python3 -m venv env`
|
8. Enter the virtualenv: `source env/bin/activate`
|
||||||
9. Enter the virtualenv: `source env/bin/activate`
|
9. Install dependencies: `python3 -m pip install -r requirements.txt`
|
||||||
10. Install dependencies: `python3 -m pip install -r requirements.txt`
|
10. Leave the www-data user: `exit`
|
||||||
11. Leave the www-data user: `exit`
|
11. Copy the default uWSGI configuration: `sudo cp /srv/imgupload/uwsgi.ini.default /etc/uwsgi/apps-available/imgupload.ini`
|
||||||
12. Copy the default uWSGI configuration: `sudo cp /srv/imgupload/uwsgi.ini.default /etc/uwsgi/apps-available/imgupload.ini`
|
12. Modify `/etc/uwsgi/apps-available/imgupload.ini` to your preferences
|
||||||
13. Modify `/etc/uwsgi/apps-available/imgupload.ini` to your preferences
|
13. Enable imgupload: `sudo ln -s /etc/uwsgi/apps-available/imgupload.ini /etc/uwsgi/apps-enabled/`
|
||||||
14. Enable imgupload: `sudo ln -s /etc/uwsgi/apps-available/imgupload.ini /etc/uwsgi/apps-enabled/`
|
14. Restart uWSGI: `sudo systemctl restart uwsgi`
|
||||||
15. Restart uWSGI: `sudo systemctl restart uwsgi`
|
15. Set up your webserver to proxy the uwsgi.sock
|
||||||
16. Set up your webserver to proxy the uwsgi.sock
|
|
||||||
|
|
||||||
Example NGINX location block:
|
Example NGINX location block:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
location /upload {
|
location /upload {
|
||||||
include uwsgi_params;
|
include uwsgi_params;
|
||||||
@ -64,21 +62,22 @@ location /upload {
|
|||||||
|
|
||||||
### Using Flask development server
|
### Using Flask development server
|
||||||
|
|
||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://git.bbaovanc.com/bbaovanc/imgupload.git
|
$ git clone https://git.bbaovanc.com/bbaovanc/imgupload.git
|
||||||
cd imgupload
|
$ cd imgupload
|
||||||
python3 -m venv env
|
$ python3 -m venv env
|
||||||
source env/bin/activate
|
$ source env/bin/activate
|
||||||
pip3 install -r requirements.txt
|
$ pip3 install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Run
|
#### Run
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
export FLASK_APP=imgupload.py
|
$ export FLASK_APP=imgupload.py
|
||||||
flask run
|
$ flask run
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -35,7 +35,7 @@ unset_settings = [i for i in defaults.keys() if i not in dir(settings)]
|
|||||||
if len(unset_settings) > 0:
|
if len(unset_settings) > 0:
|
||||||
for unset in unset_settings:
|
for unset in unset_settings:
|
||||||
checksettings.remove(unset)
|
checksettings.remove(unset)
|
||||||
print(f"[!] {unset} is unset. The default value is type {deftypes[unset].__name__} with value {defaults[unset]}")
|
print("[!] {0} is unset. The default value is type {1} with value {2}".format(unset, deftypes[unset].__name__, defaults[unset]))
|
||||||
else:
|
else:
|
||||||
print("[" + u"\u2713" + "] Found all required settings!")
|
print("[" + u"\u2713" + "] Found all required settings!")
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ typesgood = True
|
|||||||
typeswrong = []
|
typeswrong = []
|
||||||
for testtype in checksettings:
|
for testtype in checksettings:
|
||||||
if type(getattr(settings, testtype)) is not deftypes[testtype]:
|
if type(getattr(settings, testtype)) is not deftypes[testtype]:
|
||||||
print(f"[!] {testtype} requires {deftypes[testtype].__name__}, but is {type(getattr(settings, testtype)).__name__}")
|
print("[!] {0} requires {1}, but is {2}".format(testtype, deftypes[testtype].__name__, type(getattr(settings, testtype)).__name__))
|
||||||
typeswrong.append(testtype)
|
typeswrong.append(testtype)
|
||||||
typesgood = False
|
typesgood = False
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ if "ALLOWED_EXTENSIONS" in checksettings:
|
|||||||
if len(invalid_exts) > 0:
|
if len(invalid_exts) > 0:
|
||||||
print("[!] The following extensions listed in ALLOWED_EXTENSIONS are invalid:")
|
print("[!] The following extensions listed in ALLOWED_EXTENSIONS are invalid:")
|
||||||
for e in invalid_exts:
|
for e in invalid_exts:
|
||||||
print(f" {e} is listed in ALLOWED_EXTENSIONS, but doesn't start with a .")
|
print(" {0} is listed in ALLOWED_EXTENSIONS, but doesn't start with a .".format(e))
|
||||||
else:
|
else:
|
||||||
print("[" + u"\u2713" + "] ALLOWED_EXTENSIONS is good!")
|
print("[" + u"\u2713" + "] ALLOWED_EXTENSIONS is good!")
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ uploadfolder_exists = True
|
|||||||
if "UPLOAD_FOLDER" in checksettings:
|
if "UPLOAD_FOLDER" in checksettings:
|
||||||
if not os.path.isdir(settings.UPLOAD_FOLDER):
|
if not os.path.isdir(settings.UPLOAD_FOLDER):
|
||||||
uploadfolder_exists = False
|
uploadfolder_exists = False
|
||||||
print(f"[!] The directory set in UPLOAD_FOLDER ('{settings.UPLOAD_FOLDER}') doesn't exist!")
|
print("[!] The directory set in UPLOAD_FOLDER ('{0}') doesn't exist!".format(settings.UPLOAD_FOLDER))
|
||||||
else:
|
else:
|
||||||
print("[" + u"\u2713" + "] UPLOAD_FOLDER exists!")
|
print("[" + u"\u2713" + "] UPLOAD_FOLDER exists!")
|
||||||
|
|
||||||
@ -92,14 +92,14 @@ if "ROOTURL" in checksettings:
|
|||||||
|
|
||||||
if not rooturl_good:
|
if not rooturl_good:
|
||||||
print(" With your current settings, this is what a generated url would look like:")
|
print(" With your current settings, this is what a generated url would look like:")
|
||||||
print(f" {settings.ROOTURL}example.png")
|
print(" {0}example.png".format(settings.ROOTURL))
|
||||||
else:
|
else:
|
||||||
print("[" + u"\u2713" + "] ROOTURL is good!")
|
print("[" + u"\u2713" + "] ROOTURL is good!")
|
||||||
|
|
||||||
|
|
||||||
# Ask the user if SAVELOG is the intended filename
|
# Ask the user if SAVELOG is the intended filename
|
||||||
if "SAVELOG" in checksettings:
|
if "SAVELOG" in checksettings:
|
||||||
print(f"[*] SAVELOG was interpreted to be {settings.SAVELOG}")
|
print("[*] SAVELOG was interpreted to be {0}".format(settings.SAVELOG))
|
||||||
print("[*] If this is not the intended filename, please fix it.")
|
print("[*] If this is not the intended filename, please fix it.")
|
||||||
|
|
||||||
|
|
||||||
@ -111,32 +111,32 @@ if len(unset_settings) > 0:
|
|||||||
summarygood = False
|
summarygood = False
|
||||||
print("Unset settings:")
|
print("Unset settings:")
|
||||||
for unset in unset_settings:
|
for unset in unset_settings:
|
||||||
print(f" {unset}")
|
print(" {0}".format(unset))
|
||||||
|
|
||||||
if len(typeswrong) > 0:
|
if len(typeswrong) > 0:
|
||||||
summarygood = False
|
summarygood = False
|
||||||
print("Incorrect types:")
|
print("Incorrect types:")
|
||||||
for wtype in typeswrong:
|
for wtype in typeswrong:
|
||||||
print(f" {wtype}")
|
print(" {0}".format(wtype))
|
||||||
|
|
||||||
if len(invalid_exts) > 0:
|
if len(invalid_exts) > 0:
|
||||||
summarygood = False
|
summarygood = False
|
||||||
print("Invalid extensions:")
|
print("Invalid extensions:")
|
||||||
for wext in invalid_exts:
|
for wext in invalid_exts:
|
||||||
print(f" '{wext}'")
|
print(" '{0}'".format(wext))
|
||||||
|
|
||||||
if not uploadfolder_exists:
|
if not uploadfolder_exists:
|
||||||
summarygood = False
|
summarygood = False
|
||||||
print(f"UPLOAD_FOLDER ({settings.UPLOAD_FOLDER}) does not exist!")
|
print("UPLOAD_FOLDER ({0}) does not exist!".format(settings.UPLOAD_FOLDER))
|
||||||
|
|
||||||
if not rooturl_good:
|
if not rooturl_good:
|
||||||
summarygood = False
|
summarygood = False
|
||||||
print("ROOTURL may cause issues!")
|
print("ROOTURL may cause issues!")
|
||||||
print("With current settings, this is what a generated URL would look like:")
|
print("With current settings, this is what a generated URL would look like:")
|
||||||
print(f"{settings.ROOTURL}example.png")
|
print("{0}example.png".format(settings.ROOTURL))
|
||||||
|
|
||||||
if "SAVELOG" in checksettings:
|
if "SAVELOG" in checksettings:
|
||||||
print(f"[*] SAVELOG is {settings.SAVELOG}")
|
print("[*] SAVELOG is {0}".format(settings.SAVELOG))
|
||||||
|
|
||||||
if summarygood:
|
if summarygood:
|
||||||
print("[" + u"\u2713" + "] This configuration passes all tests!")
|
print("[" + u"\u2713" + "] This configuration passes all tests!")
|
||||||
|
30
imgupload.py
30
imgupload.py
@ -5,12 +5,11 @@ imgupload.py
|
|||||||
Flask application for processing images uploaded through POST requests.
|
Flask application for processing images uploaded through POST requests.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify, Response
|
||||||
from flask_api import status
|
from flask_api import status
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import settings # app settings (such as allowed extensions)
|
import settings # app settings (such as allowed extensions)
|
||||||
import functions # custom functions
|
import functions # custom functions
|
||||||
@ -28,11 +27,11 @@ def allowed_extension(testext):
|
|||||||
def log_savelog(key, ip, savedname):
|
def log_savelog(key, ip, savedname):
|
||||||
if settings.SAVELOG_KEYPREFIX > 0:
|
if settings.SAVELOG_KEYPREFIX > 0:
|
||||||
with open(settings.SAVELOG, "a+") as slogf:
|
with open(settings.SAVELOG, "a+") as slogf:
|
||||||
slogf.write(f"[{datetime.datetime.now()}] {key[:settings.SAVELOG_KEYPREFIX]}: {ip} - {savedname}\n")
|
slogf.write("[{0}] {1}: {2} - {3}\n".format(datetime.datetime.now(), key[:settings.SAVELOG_KEYPREFIX], ip, savedname))
|
||||||
os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD)
|
os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD)
|
||||||
else:
|
else:
|
||||||
with open(settings.SAVELOG, "a+") as slogf:
|
with open(settings.SAVELOG, "a+") as slogf:
|
||||||
slogf.write(f"[{datetime.datetime.now()}] {ip} - {savedname}\n")
|
slogf.write("[{0}] {1} - {2}\n".format(datetime.datetime.now(), ip, savedname))
|
||||||
os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD)
|
os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD)
|
||||||
|
|
||||||
@app.route("/upload", methods = ["POST"])
|
@app.route("/upload", methods = ["POST"])
|
||||||
@ -69,35 +68,28 @@ def upload():
|
|||||||
fext = Path(f.filename).suffix # get the uploaded extension
|
fext = Path(f.filename).suffix # get the uploaded extension
|
||||||
if allowed_extension(fext): # if the extension is allowed
|
if allowed_extension(fext): # if the extension is allowed
|
||||||
if not "imageName" in request.form.keys():
|
if not "imageName" in request.form.keys():
|
||||||
print(f"Generating file with extension {fext}")
|
print("Generating file with extension {0}".format(fext))
|
||||||
fname = functions.generate_name() + fext # generate file name
|
fname = functions.generate_name() + fext # generate file name
|
||||||
print(f"Generated name: {fname}")
|
print("Generated name: {0}".format(fname))
|
||||||
else:
|
else:
|
||||||
fname = request.form["imageName"]
|
fname = request.form["imageName"]
|
||||||
if len(fname) > 0:
|
if len(fname) > 0:
|
||||||
print(f"Request imageName: {fname}")
|
print("Request imageName: {0}".format(fname))
|
||||||
if not fname.lower().endswith(fext.lower()): # if requested name doesn't have the correct extension
|
if not fname.lower().endswith(fext.lower()): # if requested name doesn't have the correct extension
|
||||||
fname += fext # add the extension
|
fname += fext # add the extension
|
||||||
print(f"Added extension; new filename: {fname}")
|
print("Added extension; new filename: {0}".format(fname))
|
||||||
else:
|
else:
|
||||||
print("Requested filename is blank!")
|
print("Requested filename is blank!")
|
||||||
fname = functions.generate_name() + fext # generate a valid filename
|
fname = functions.generate_name() + fext # generate a valid filename
|
||||||
print(f"Generated name: {fname}")
|
print("Generated name: {0}".format(fname))
|
||||||
|
|
||||||
if f: # if the uploaded image exists
|
if f: # if the uploaded image exists
|
||||||
print("Uploaded image exists")
|
print("Uploaded image exists")
|
||||||
if Path(os.path.join(settings.UPLOAD_FOLDER, fname)).is_file():
|
if Path(os.path.join(settings.UPLOAD_FOLDER, fname)).is_file():
|
||||||
print("Requested filename already exists!")
|
print("Requested filename already exists!")
|
||||||
return jsonify({'status': 'error', 'error': 'FILENAME_TAKEN'}), status.HTTP_409_CONFLICT
|
return jsonify({'status': 'error', 'error': 'FILENAME_TAKEN'}), status.HTTP_409_CONFLICT
|
||||||
|
f.save(os.path.join(settings.UPLOAD_FOLDER, fname)) # save the image
|
||||||
f.save(f"/tmp/{fname}") # save the image temporarily (before removing EXIF)
|
print("Saved to {0}".format(fname))
|
||||||
image = Image.open(f"/tmp/{fname}")
|
|
||||||
data = list(image.getdata())
|
|
||||||
stripped = Image.new(image.mode, image.size)
|
|
||||||
stripped.putdata(data)
|
|
||||||
stripped.save(os.path.join(settings.UPLOAD_FOLDER, fname)) # save the image without EXIF
|
|
||||||
|
|
||||||
print(f"Saved to {fname}")
|
|
||||||
url = settings.ROOTURL + fname # construct the url to the image
|
url = settings.ROOTURL + fname # construct the url to the image
|
||||||
if settings.SAVELOG != "/dev/null":
|
if settings.SAVELOG != "/dev/null":
|
||||||
print("Saving to savelog")
|
print("Saving to savelog")
|
||||||
@ -114,7 +106,7 @@ def upload():
|
|||||||
|
|
||||||
else: # if the key was not valid
|
else: # if the key was not valid
|
||||||
print("Key is invalid!")
|
print("Key is invalid!")
|
||||||
print(f"Request key: {request.form['uploadKey']}")
|
print("Request key: {0}".format(request.form["uploadKey"]))
|
||||||
return jsonify({'status': 'error', 'error': 'UNAUTHORIZED'}), status.HTTP_401_UNAUTHORIZED
|
return jsonify({'status': 'error', 'error': 'UNAUTHORIZED'}), status.HTTP_401_UNAUTHORIZED
|
||||||
|
|
||||||
else: # if uploadKey was not found in request body
|
else: # if uploadKey was not found in request body
|
||||||
|
13
keyctl.py
13
keyctl.py
@ -31,7 +31,7 @@ def savekey(key):
|
|||||||
logging.info("uploadkeys file doesn't exist, it will be created.")
|
logging.info("uploadkeys file doesn't exist, it will be created.")
|
||||||
with open("uploadkeys", "a+") as keyfile:
|
with open("uploadkeys", "a+") as keyfile:
|
||||||
keyfile.write(str(key) + "\n") # add the key
|
keyfile.write(str(key) + "\n") # add the key
|
||||||
logging.debug(f"Saved a key to uploadkeys: {key}")
|
logging.debug("Saved a key to uploadkeys: {0}".format(key))
|
||||||
|
|
||||||
|
|
||||||
def rmkey(delkey):
|
def rmkey(delkey):
|
||||||
@ -85,14 +85,14 @@ def cmd_list(args):
|
|||||||
if len(validkeys[i]) > 6:
|
if len(validkeys[i]) > 6:
|
||||||
showkey += "..." # add ellipses since the key was shortened in list
|
showkey += "..." # add ellipses since the key was shortened in list
|
||||||
|
|
||||||
print(f" [{i+1}] {showkey}")
|
print(" [{0}] {1}".format(i+1, showkey))
|
||||||
|
|
||||||
|
|
||||||
def cmd_generate(args):
|
def cmd_generate(args):
|
||||||
k = genkey(args.length)
|
k = genkey(args.length)
|
||||||
logging.debug(f"Generated a new key: {k}")
|
logging.debug("Generated a new key: {0}".format(k))
|
||||||
savekey(k)
|
savekey(k)
|
||||||
print(f"Your new key is: {k}")
|
print("Your new key is: {0}".format(k))
|
||||||
|
|
||||||
|
|
||||||
def cmd_add(args):
|
def cmd_add(args):
|
||||||
@ -104,6 +104,7 @@ def cmd_add(args):
|
|||||||
print(ak)
|
print(ak)
|
||||||
if input("Is the above key correct? [y/N] ").lower() == "y":
|
if input("Is the above key correct? [y/N] ").lower() == "y":
|
||||||
logging.debug("Interpreted as yes")
|
logging.debug("Interpreted as yes")
|
||||||
|
ask_for_key = False
|
||||||
savekey(ak)
|
savekey(ak)
|
||||||
logging.info("Added.")
|
logging.info("Added.")
|
||||||
else:
|
else:
|
||||||
@ -123,14 +124,14 @@ def cmd_dedupe(args):
|
|||||||
for d in dupes:
|
for d in dupes:
|
||||||
r = rmkey(d)
|
r = rmkey(d)
|
||||||
logging.debug(r)
|
logging.debug(r)
|
||||||
logging.info(f"Removed duplicate key: {d}")
|
logging.info("Removed duplicate key: {0}".format(d))
|
||||||
else:
|
else:
|
||||||
logging.info("[" + u"\u2713" + "] No duplicate keys found!")
|
logging.info("[" + u"\u2713" + "] No duplicate keys found!")
|
||||||
|
|
||||||
def cmd_show(args):
|
def cmd_show(args):
|
||||||
for k in get_keys():
|
for k in get_keys():
|
||||||
if k[:6] == args.prefix:
|
if k[:6] == args.prefix:
|
||||||
print(f"Key: {k}")
|
print("Key: {0}".format(k))
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
Flask_API==2.0
|
Flask_API==2.0
|
||||||
Flask==1.1.2
|
Flask==1.1.2
|
||||||
Pillow==8.0.1
|
|
||||||
|
Reference in New Issue
Block a user