commit 57e6bd5608e2ba071634aee96098624417d4996b Author: Ville Rantanen Date: Wed Jan 24 10:53:17 2018 +0200 initial diff --git a/code/Dockerfile b/code/Dockerfile new file mode 100644 index 0000000..e80733e --- /dev/null +++ b/code/Dockerfile @@ -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 diff --git a/code/app.py b/code/app.py new file mode 100644 index 0000000..ac7b7ce --- /dev/null +++ b/code/app.py @@ -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/', 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/', 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/', 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//', 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) + + diff --git a/code/requirements.txt b/code/requirements.txt new file mode 100644 index 0000000..e4a286c --- /dev/null +++ b/code/requirements.txt @@ -0,0 +1,2 @@ +flask +gunicorn diff --git a/code/revprox.py b/code/revprox.py new file mode 100644 index 0000000..e574dea --- /dev/null +++ b/code/revprox.py @@ -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) + diff --git a/code/templates/authenticate.html b/code/templates/authenticate.html new file mode 100644 index 0000000..4dc25b0 --- /dev/null +++ b/code/templates/authenticate.html @@ -0,0 +1,8 @@ +{% extends "layout.html" %} +{% block body %} + authenticate to {{ name|safe }} +
+

+ +

+{% endblock %} diff --git a/code/templates/index.html b/code/templates/index.html new file mode 100755 index 0000000..cdd4acc --- /dev/null +++ b/code/templates/index.html @@ -0,0 +1,18 @@ +{% extends "layout.html" %} +{% block body %} + + + + {% for entry in entries %} + +
name + expires + upload +
{{ entry.name }} + {{ entry.expire|safe }} + {{ entry.upload|safe }} + {% else %} +
  • no shares + {% endfor %} +
  • +{% endblock %} diff --git a/code/templates/layout.html b/code/templates/layout.html new file mode 100644 index 0000000..a4dde5c --- /dev/null +++ b/code/templates/layout.html @@ -0,0 +1,15 @@ + + +Flees + + + + +
    +{% for message in get_flashed_messages() %} +
    {{ message }}
    +{% endfor %} +{% block body %}{% endblock %} +
    + + diff --git a/code/templates/list.html b/code/templates/list.html new file mode 100644 index 0000000..59501d6 --- /dev/null +++ b/code/templates/list.html @@ -0,0 +1,22 @@ +{% extends "layout.html" %} +{% block body %} + + upload + + + + {% for entry in entries %} + +
    name + time + trip + kmδ + daysδ + km/d +
    {{ entry.name }} + {{ entry.size|safe }} + {{ entry.mtime|safe }} + {% else %} +
  • no files + {% endfor %} +
  • +{% endblock %} diff --git a/code/templates/send.html b/code/templates/send.html new file mode 100644 index 0000000..5441546 --- /dev/null +++ b/code/templates/send.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% block body %} + upload to {{ name|safe }} +
    + +

    + +

    +{% endblock %} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..28159da --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,15 @@ +version: '2' + + +services: + app: + build: + context: code + ports: + - 127.0.0.1:8136:80 + volumes: + - ./data/:/code/data/ + # restart: always + + +