initial
This commit is contained in:
14
code/Dockerfile
Normal file
14
code/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM alpine:3.5
|
||||||
|
RUN apk add --update \
|
||||||
|
python3 \
|
||||||
|
python3-dev \
|
||||||
|
py3-pip \
|
||||||
|
build-base
|
||||||
|
COPY requirements.txt /requirements.txt
|
||||||
|
RUN pip3 install -r /requirements.txt
|
||||||
|
COPY static /code/static
|
||||||
|
COPY templates /code/templates
|
||||||
|
COPY revprox.py /code/revprox.py
|
||||||
|
COPY app.py /code/app.py
|
||||||
|
WORKDIR /code
|
||||||
|
CMD gunicorn -b 0.0.0.0:80 --timeout 3600 -w 6 app:app
|
||||||
144
code/app.py
Normal file
144
code/app.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os,sys
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import Flask, render_template, jsonify, \
|
||||||
|
redirect, url_for, request, g, session, send_from_directory
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
import hashlib
|
||||||
|
from revprox import ReverseProxied
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(__name__)
|
||||||
|
app.config['UPLOAD_FOLDER'] = 'data/'
|
||||||
|
app.config['SHARES_FILE'] = 'data/shares.json'
|
||||||
|
app.secret_key = 'Cz2dw5NiRt3PSMFBSLTAJJi7U2CdW7iPQqEeOaU6'
|
||||||
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def before_request():
|
||||||
|
g.shares = json.load(open(app.config['SHARES_FILE'],'rt'))
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
printerr(g.shares)
|
||||||
|
public_shares = []
|
||||||
|
for share in g.shares:
|
||||||
|
public = get_or_none(share,'public')
|
||||||
|
if public:
|
||||||
|
public_shares.append({
|
||||||
|
'name': share['name'],
|
||||||
|
'expire': get_or_none(share,'expire'),
|
||||||
|
'upload': get_or_none(share,'upload')
|
||||||
|
})
|
||||||
|
|
||||||
|
return render_template("index.html", entries=public_shares)
|
||||||
|
|
||||||
|
@app.route('/authenticate/<name>', methods=['GET','POST'])
|
||||||
|
def authenticate(name):
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('authenticate.html',name=name)
|
||||||
|
if request.method == 'POST':
|
||||||
|
user_password = request.form['password'].encode('utf-8')
|
||||||
|
session[name] = hashlib.sha256(user_password).hexdigest()
|
||||||
|
return redirect(url_for('list_view',name=name))
|
||||||
|
|
||||||
|
@app.route('/upload', methods=['POST'])
|
||||||
|
def upload():
|
||||||
|
if request.method == 'POST':
|
||||||
|
file = request.files['file']
|
||||||
|
name = request.form['name']
|
||||||
|
(ok,share) = get_share(name)
|
||||||
|
if not ok:
|
||||||
|
return share
|
||||||
|
if file:
|
||||||
|
filename = os.path.join(
|
||||||
|
share['path'],
|
||||||
|
secure_filename(
|
||||||
|
file.filename
|
||||||
|
)
|
||||||
|
)
|
||||||
|
file.save(filename)
|
||||||
|
return redirect(url_for('list_view',name=name))
|
||||||
|
|
||||||
|
@app.route('/send/<name>', methods=['GET'])
|
||||||
|
def send(name):
|
||||||
|
(ok,share) = get_share(name)
|
||||||
|
if not ok:
|
||||||
|
return share
|
||||||
|
return render_template('send.html',name=name)
|
||||||
|
|
||||||
|
@app.route('/list/<name>', methods=['GET'])
|
||||||
|
def list_view(name):
|
||||||
|
(ok,share) = get_share(name)
|
||||||
|
if not ok:
|
||||||
|
return share
|
||||||
|
files = []
|
||||||
|
for file in sorted(os.listdir(share['path'])):
|
||||||
|
files.append(file_stat(os.path.join(share['path'],file)))
|
||||||
|
return render_template("list.html",name = share['name'], entries = files)
|
||||||
|
#~ return jsonify({"share":share, "files": files})
|
||||||
|
|
||||||
|
@app.route('/download/<name>/<filename>', methods=['GET'])
|
||||||
|
def download_file(name,filename):
|
||||||
|
(ok,share) = get_share(name)
|
||||||
|
if not ok:
|
||||||
|
return share
|
||||||
|
file_path = os.path.join(share['path'], filename)
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return 'no such file'
|
||||||
|
return send_from_directory(directory=share['path'], filename=filename)
|
||||||
|
|
||||||
|
def file_stat(filename):
|
||||||
|
s = os.stat(filename)
|
||||||
|
return {
|
||||||
|
'size': s.st_size,
|
||||||
|
'mtime': s.st_mtime,
|
||||||
|
'name': os.path.basename(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_or_none(d,key):
|
||||||
|
if key in d:
|
||||||
|
return d[key]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_share(name):
|
||||||
|
share = [x for x in g.shares if x['name'] == name]
|
||||||
|
if len(share) < 1:
|
||||||
|
return (False,'No such share')
|
||||||
|
share = share[0]
|
||||||
|
authenticated = True
|
||||||
|
if 'pass_hash' in share:
|
||||||
|
authenticated = False
|
||||||
|
printerr(session)
|
||||||
|
if name in session:
|
||||||
|
printerr(share['pass_hash'])
|
||||||
|
if session[name] == share['pass_hash']:
|
||||||
|
printerr(session[name])
|
||||||
|
printerr(share['pass_hash'])
|
||||||
|
authenticated = True
|
||||||
|
if not authenticated:
|
||||||
|
return (False,redirect(url_for('authenticate',name=name)))
|
||||||
|
if not 'path' in share:
|
||||||
|
return (False,'no path defined')
|
||||||
|
share.update({
|
||||||
|
"path": os.path.join(
|
||||||
|
app.config['UPLOAD_FOLDER'],
|
||||||
|
share['path']
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if not os.path.exists(share['path']):
|
||||||
|
os.makedirs(share['path'])
|
||||||
|
return (True,share)
|
||||||
|
|
||||||
|
def printerr(s):
|
||||||
|
sys.stderr.write(str(s)+"\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
|
|
||||||
|
|
||||||
2
code/requirements.txt
Normal file
2
code/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
flask
|
||||||
|
gunicorn
|
||||||
33
code/revprox.py
Normal file
33
code/revprox.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
class ReverseProxied(object):
|
||||||
|
'''Wrap the application in this middleware and configure the
|
||||||
|
front-end server to add these headers, to let you quietly bind
|
||||||
|
this to a URL other than / and to an HTTP scheme that is
|
||||||
|
different than what is used locally.
|
||||||
|
|
||||||
|
In nginx:
|
||||||
|
location /myprefix {
|
||||||
|
proxy_pass http://192.168.0.1:5001;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Scheme $scheme;
|
||||||
|
proxy_set_header X-Script-Name /myprefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
:param app: the WSGI application
|
||||||
|
'''
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
||||||
|
if script_name:
|
||||||
|
environ['SCRIPT_NAME'] = script_name
|
||||||
|
path_info = environ['PATH_INFO']
|
||||||
|
if path_info.startswith(script_name):
|
||||||
|
environ['PATH_INFO'] = path_info[len(script_name):]
|
||||||
|
|
||||||
|
scheme = environ.get('HTTP_X_SCHEME', '')
|
||||||
|
if scheme:
|
||||||
|
environ['wsgi.url_scheme'] = scheme
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
8
code/templates/authenticate.html
Normal file
8
code/templates/authenticate.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
authenticate to {{ name|safe }}
|
||||||
|
<form action={{ url_for('authenticate',name=name) }} method=post enctype=multipart/form-data>
|
||||||
|
<p><input type=password name=password>
|
||||||
|
<input type=submit value=Login>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
18
code/templates/index.html
Executable file
18
code/templates/index.html
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
<table class=entriesall>
|
||||||
|
<tr>
|
||||||
|
<th>name
|
||||||
|
<th>expires
|
||||||
|
<th>upload
|
||||||
|
</tr>
|
||||||
|
{% for entry in entries %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ url_for('list_view',name=entry.name) }}">{{ entry.name }}</a>
|
||||||
|
<td>{{ entry.expire|safe }}
|
||||||
|
<td>{{ entry.upload|safe }}
|
||||||
|
{% else %}
|
||||||
|
<li>no shares
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
15
code/templates/layout.html
Normal file
15
code/templates/layout.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<head>
|
||||||
|
<title>Flees</title>
|
||||||
|
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
<script src="{{ url_for('static', filename='script.js') }}" type="text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class=page>
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class=flash>{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
22
code/templates/list.html
Normal file
22
code/templates/list.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
<a href="{{ url_for('send', name=name) }}">+ upload</a>
|
||||||
|
<table class=entriesall>
|
||||||
|
<tr>
|
||||||
|
<th>name
|
||||||
|
<th>time
|
||||||
|
<th>trip
|
||||||
|
<th>kmδ
|
||||||
|
<th>daysδ
|
||||||
|
<th>km/d
|
||||||
|
</tr>
|
||||||
|
{% for entry in entries %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ entry.name }}
|
||||||
|
<td>{{ entry.size|safe }}
|
||||||
|
<td>{{ entry.mtime|safe }}
|
||||||
|
{% else %}
|
||||||
|
<li>no files
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
9
code/templates/send.html
Normal file
9
code/templates/send.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
upload to {{ name|safe }}
|
||||||
|
<form action={{ url_for('upload') }} method=post enctype=multipart/form-data>
|
||||||
|
<input type=hidden name=name value="{{ name|safe }}" />
|
||||||
|
<p><input type=file name=file>
|
||||||
|
<input type=submit value=Upload>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
15
docker-compose.yaml
Normal file
15
docker-compose.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: code
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:8136:80
|
||||||
|
volumes:
|
||||||
|
- ./data/:/code/data/
|
||||||
|
# restart: always
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user