Created
February 14, 2020 21:01
-
-
Save twilight-sparkle-irl/8a2068d237406629e34cf57afafb647f to your computer and use it in GitHub Desktop.
Simple upload/download Flask thing, for when you don't want to set up an FTP server and just want to transfer files back-and-forth real quick.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# THIS IS NOT MEANT TO BE HOSTED FOR PUBLIC USE. | |
# THIS IS A TESTING TOOL. | |
# BY DEFAULT, DO NOT USE THIS FOR ANY PURPOSES. | |
###### REVIEW BOTTOM OF FILE FOR LICENSE! ###### | |
# This file is standalone and only requires Flask be installed. | |
# Simply run it and it should work out of the box. | |
# It will create an uploads folder in the current directory by default, but that can be changed in UPLOAD_FOLDER. | |
import os, secrets | |
from functools import wraps | |
from flask import Flask, flash, request, redirect, url_for, send_from_directory, render_template_string | |
from werkzeug.utils import secure_filename | |
UPLOAD_FOLDER = './uploads/' | |
AUTH = [] # it is recommended to let the program automatically generate a safe username + password | |
ONLY_RESTRICT_UP = False # it is not recommended to change this. | |
app = Flask(__name__) | |
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER | |
if not AUTH or len(AUTH) != 2: | |
app.config['AUTH_USERNAME'] = secrets.token_urlsafe(secrets.randbelow(4)+8) | |
app.config['AUTH_PASSWORD'] = secrets.token_urlsafe(secrets.randbelow(16)+16) | |
else: | |
app.config['AUTH_USERNAME'] = AUTH[0] | |
app.config['AUTH_PASSWORD'] = AUTH[1] | |
print(f"Username is {app.config['AUTH_USERNAME']}{chr(0x0a)}Password is {app.config['AUTH_PASSWORD']}") | |
app.config['ONLY_RESTRICT_UP'] = ONLY_RESTRICT_UP | |
style=""" | |
<style>body{font-family:sans-serif;margin:40px auto;max-width:650px;line-height:1.6;font-size:18px;color:#444;padding:0 10px}a{text-decoration:none}h1,h4{line-height:1}img{image-rendering:crisp-edges;image-rendering:pixelated;width:11px;}center>img{width:88px}ul{list-style-image:url(https://www.w3.org/icons/generic.gif)}:target{background-color:rgba(255,255,0,0.3);font-weight:bold;};}</style> | |
""" | |
dirtree_template = """<!doctype html><title>simple updown - down</title>"""+style+""" | |
<center><img src="https://www.w3.org/icons/folder.gif"><h4><a href="/up">[ Upload File ]</a></h4></center><hr /> | |
<ul> | |
{%- for item in tree.children %} | |
<li id="{{ item.name }}"><a href="/uploads/{{ item.name }}">{{ item.name }}</a> <a href="/uploads/{{ item.name }}" download><img src="https://www.w3.org/icons/down.gif" alt="download" title="download"></a></li> | |
{%- endfor %} | |
</ul>""" | |
upload_template = """<!doctype html><title>simple updown - up</title>"""+style+""" | |
<center><img src="https://www.w3.org/icons/folder.open.gif"><hr/> | |
<form method=post enctype=multipart/form-data><input type=file name=file><input type=submit value=Upload></form> | |
</center>""" | |
def make_tree(path): | |
tree = dict(name=os.path.basename(path), children=[]) | |
try: lst = os.listdir(path) | |
except OSError: | |
pass #ignore errors | |
else: | |
for name in lst: | |
fn = os.path.join(path, name) | |
if os.path.isdir(fn): | |
tree['children'].append(make_tree(fn)) | |
else: | |
tree['children'].append(dict(name=name)) | |
return tree | |
def login_required(f): | |
@wraps(f) | |
def wrapped_view(**kwargs): | |
auth = request.authorization | |
if not (auth and auth.username == app.config['AUTH_USERNAME'] and auth.password == app.config['AUTH_PASSWORD']): | |
return ('Unauthorized', 401, { | |
'WWW-Authenticate': 'Basic realm="Login required."' | |
}) | |
return f(**kwargs) | |
return wrapped_view | |
def login_required_maybe(f): | |
@wraps(f) | |
def wrapped_view(**kwargs): | |
auth = request.authorization | |
if not app.config['ONLY_RESTRICT_UP'] and not (auth and auth.username == app.config['AUTH_USERNAME'] and auth.password == app.config['AUTH_PASSWORD']): | |
return ('Unauthorized', 401, { | |
'WWW-Authenticate': 'Basic realm="Login required."' | |
}) | |
return f(**kwargs) | |
return wrapped_view | |
@app.route('/') | |
@login_required_maybe | |
def index(): | |
return render_template_string(dirtree_template, tree=make_tree(app.config['UPLOAD_FOLDER'])) | |
@app.route('/up', methods=['GET', 'POST']) | |
@login_required | |
def upload_file(): | |
if request.method == 'POST': | |
# check if the post request has the file part | |
if 'file' not in request.files: | |
flash('No file part') | |
return redirect(request.url) | |
file = request.files['file'] | |
# if user does not select file, browser also | |
# submit an empty part without filename | |
if file.filename == '': | |
flash('No selected file') | |
return redirect(request.url) | |
if file: | |
filename = secure_filename(file.filename) | |
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) | |
return redirect('/#'+filename) | |
return upload_template | |
@app.route('/uploads/<filename>') | |
@login_required_maybe | |
def uploaded_file(filename): | |
return send_from_directory(app.config['UPLOAD_FOLDER'], | |
filename) | |
if __name__=="__main__": | |
if not os.path.isdir(app.config['UPLOAD_FOLDER']): | |
os.mkdir(app.config['UPLOAD_FOLDER']) | |
app.run(host='0.0.0.0', port=39270) | |
# This software is licensed under the following license. | |
# | |
# PAULA BEAN OPEN SOURCE LICENSE | |
# Version 2, Feburary 2020 | |
# | |
# Copyright (C) 2020 Starlight Glimmer | |
# | |
# Everyone is permitted to copy and distribute verbatim or modified | |
# copies of this license document and the corresponding source code | |
# or whatever under the following conditions. | |
# | |
# PAULA BEAN OPEN SOURCE LICENSE | |
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
# | |
# 0. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
# OTHER DEALINGS IN THE SOFTWARE. | |
# 1. Brillant | |
# |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment