commit abf7741b16141c18bdb545b598793ec3da58cda3 Author: pporcheret Date: Thu Jun 22 08:10:29 2023 +0000 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..04199e7 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Transfert + +## About + +I used to transfert big files using Jirafeau. But I decided to create my own tool. + +I wanted : + - Something modern and safer than PHP + - with QRcode possibility + - No database + - Clean expired files automatically + - Manage single Download + - SSL Connexion + +[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)](https://opensource.org/licenses/) [![AGPL License](https://img.shields.io/badge/license-AGPL-blue.svg)](http://www.gnu.org/licenses/agpl-3.0) + + +## Install +```sh + +Ubuntu/Debian: + +git clone https://git.nerkdesign.com/pporcheret/Transfert.git +cd ./Transfert/ +mkdir storage +apt install python3 python3-pip +pip3 install -r requirement.txt + +Redhat: + +git clone https://git.nerkdesign.com/pporcheret/Transfert.git +cd ./Transfert/ +mkdir storage +yum install python3 python3-pip +pip3 install -r requirement.txt + + +``` + +## Start + +python3 app.py + +Use your browser to go to : +```sh + https://{{IP}}:5006 + +``` + +## Config + +You can edit the Flask port/host : +```sh +flask_port=5006 +flask_host='0.0.0.0' +flask_thread=True +flask_debug=False +``` +The size of the random link : +```sh +random_size = 12 +``` +The storage location of your files : +```sh +path = os.path.dirname(__file__) +storage_path= path+'/storage/' +``` + +## Login + + +Be carefull !! +By default, the authentification is PAM. +This PAM auth is based on the library [simplepam](https://github.com/leonnnn/python3-simplepam). +This means that by default, the allowed users, are the user in the server. + +## Screenshot + +![ScreenShot](./images/screen0.png) +![ScreenShot2](./images/screen1.png) +![ScreenShot2](./images/screen2.png) + + diff --git a/app.py b/app.py new file mode 100755 index 0000000..c0bf8e1 --- /dev/null +++ b/app.py @@ -0,0 +1,190 @@ + +#!/usr/bin/python3 +import os +import sys +import time +import string +import random +import datetime +from datetime import timedelta +from flask_qrcode import QRcode +from werkzeug.utils import secure_filename +from flask import Flask, render_template, Response, request, redirect, url_for, session, escape, send_from_directory, send_file, after_this_request +from flask_fontawesome import FontAwesome +from simplepam import authenticate +from flask_navigation import Navigation +from flask_dropzone import Dropzone + +##Variables +#Flask config +flask_port=5006 +flask_host='0.0.0.0' +flask_thread=True +flask_debug=False +#Other +random_size = 12 + +#path base for service lauch +path = os.path.dirname(__file__) +storage_path= path+'/storage/' + +#Flask init +app = Flask(__name__,static_url_path='',static_folder='static',template_folder='templates') + +#Init QRcode +QRcode(app) + +#Init FontAwsome +fa = FontAwesome(app) + +#Init Nav +nav = Navigation(app) + +nav.Bar('top', [ + nav.Item('Index', 'index'), + nav.Item('Upload', 'uploads'), +]) + +#DropZone +app.config.update( + UPLOADED_PATH= storage_path) +dropzone = Dropzone(app) + +##Functions +def today_format(): + x = datetime.datetime.now() + return x.strftime("%d%m%y%H%M%S") + +def time_end(date_up,retention): + retention = int(retention) + tdate = datetime.datetime.strptime(date_up, '%d%m%y%H%M%S') + end_date = tdate + timedelta(hours = retention) + end_date = end_date.strftime("%d%m%y%H%M%S") + return end_date + +def del_expired_files(): + files=os.listdir(storage_path) + for file in files: + list_file=file.split("_") + retention = ''.join(list_file[-2:-1]) + if retention != '0': + date_up = ''.join(list_file[-3:-2]) + end_date = time_end(date_up,retention) + end_date = datetime.datetime.strptime(end_date,"%d%m%y%H%M%S") + actual_date = datetime.datetime.now() + if end_date < actual_date: + if os.path.exists(storage_path+file): + os.remove(storage_path+file) + +def random_link(): + res = ''.join(random.choices(string.ascii_letters+string.digits, k=random_size)) + return res + +def list_files(): + out_files=[] + files=os.listdir(storage_path) + for file in files: + middle=[] + list_file=file.split("_") + singled = ''.join(list_file[-1:]) + retention=''.join(list_file[-2:-1]) + date_up=''.join(list_file[-3:-2]) + link=''.join(list_file[-4:-3]) + list_file_name=list_file[:-4] + list_file_name='_'.join(list_file_name) + end_date = time_end(date_up,retention) + middle.append(list_file_name) + middle.append(retention) + middle.append(date_up) + middle.append(link) + middle.append(end_date) + middle.append(request.url_root+"download/"+link) + middle.append(singled) + out_files.append(middle) + return out_files + +##Pages +@app.route('/') +def index(): + if 'username' in session: + del_expired_files() + return render_template('index.html',loguser=session['username'],listdir=list_files()) + return render_template('login.html',title="Login") + +@app.route('/down/') +def down(link): + del_expired_files() + files=list_files() + for file in files: + if link == file[3]: + filename=file[0]+"_"+file[3]+"_"+file[2]+"_"+file[1]+"_"+file[6] + if file[6] == 'YES': + @after_this_request + def remove_file(reponse): + os.remove(storage_path+filename) + return reponse + return send_file(storage_path+filename, download_name=secure_filename(file[0])) + return render_template('nodown.html',title='No Download') + +@app.route('/download/') +def download(link): + del_expired_files() + files=list_files() + for file in files: + if link == file[3]: + filename=file[0] + url_todl=request.url_root+"down/"+link + return render_template('download.html',title="Download",filename=filename,url_todl=url_todl,qrcode=QRcode.qrcode(url_todl, mode="base64")) + +@app.route('/uploads') +def uploads(): + if 'username' in session: + del_expired_files() + return render_template('upload.html',loguser=session['username']) + return render_template('login.html',title="Login") + +@app.route('/upload', methods = ['POST']) +def upload(): + del_expired_files() + retention = request.form['retention'] + r = ['on'] + if request.form.getlist('uniqd') == r: + singled = 'YES' + else: + singled = 'NO' + my_files = request.files + for item in my_files: + uploaded_file = my_files.get(item) + uploaded_file.filename = secure_filename(uploaded_file.filename) + link = random_link() + today = today_format() + print(uploaded_file.filename+"_"+link+"_"+today+"_"+retention+"_"+singled) + uploaded_file.save(os.path.join(app.config['UPLOADED_PATH'],uploaded_file.filename+"_"+link+"_"+today+"_"+retention+"_"+singled)) + return render_template('upload.html',title="Upload") + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + username = request.form['username'] + if username == 'root': + resultats='

Root not allowed

' + return render_template('login.html', alertmessage=resultats) + else: + password = request.form['password'] + if authenticate(str(username), str(password)): + session['username'] = request.form['username'] + return redirect(url_for('index')) + else: + resultats='

Invalid Username/Password !

' + return render_template('login.html', alertmessage=resultats) + +@app.route('/logout') +def logout(): + session.pop('username', None) + return redirect(url_for('index')) + +app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' + +if __name__ == '__main__': + app.run(host=flask_host, port=flask_port, threaded=flask_thread, debug=flask_debug, ssl_context='adhoc') + diff --git a/images/screen0.png b/images/screen0.png new file mode 100644 index 0000000..37d50dc Binary files /dev/null and b/images/screen0.png differ diff --git a/images/screen1.png b/images/screen1.png new file mode 100644 index 0000000..8e045d6 Binary files /dev/null and b/images/screen1.png differ diff --git a/images/screen2.png b/images/screen2.png new file mode 100644 index 0000000..7c4e882 Binary files /dev/null and b/images/screen2.png differ diff --git a/requirement.txt b/requirement.txt new file mode 100755 index 0000000..f1701b3 --- /dev/null +++ b/requirement.txt @@ -0,0 +1,8 @@ + +flask_qrcode +flask +flask_fontawesome +simplepam +flask_navigation +flask_dropzone +pyopenssl diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..8dd4dee --- /dev/null +++ b/static/style.css @@ -0,0 +1,113 @@ +body { + font-size: 13px; + font-weight: 300; + font-family: helvetica, arial, verdana, sans-serif; + text-align:center; + min-height: 100% !important; +} + +.not-underline{ + text-decoration:none; + } + +.dropzone { + box-shadow: 0px 2px 20px 0px #f2f2f2; + border: 1px dashed #c0ccda; + padding: 60x; + border-radius: 10px; + background-color: #fbfdff; + margin-left: 15px; + margin-bottom: 15px; + margin-top: 15px; + text-align:center; +} + +.tacts { + border:none; +} +.qard { + text-align:center; + width:100%; + padding:20px; + border:none; +} +.login { + box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; + width:300px; + height:400px; + padding:50px; + text-align:center; + margin:auto; + background-color:white; +} +button { + display: flex; + flex-direction: row; + align-items: center; + border-radius: 6px; + border: none; + width:100px; + background: Transparent; + box-shadow: 0px 0.5px 1px rgba(0, 0, 0, 0.1), inset 0px 0.5px 0.5px rgba(255, 255, 255, 0.5), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.12); + cursor: pointer; + margin:auto; + padding: 5px 5px; +} +select, option { + width: 243px; + padding: 12px 20px; + margin: 8px 0; +} +.logout { + text-align: right; + width:100%; + text-decoration:none; + height:33px; +} +.logout a { + text-decoration: none; +} +.logout ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #C0C0C0; + position: -webkit-sticky; /* Safari */ + position: sticky; + top: 0; + +} +.logout li { + float: left; +} + + +.logout li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +.logout li a:hover { + background-color: #111; +} + +.logout .active { + background-color: #336699; +} +.close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.close:hover, .close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..88cb562 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,19 @@ + + + + + + +{{ fontawesome_css() }} +{{ dropzone.load_css() }} +{{ dropzone.style('border: 2px dashed #000; min-height: 200px;text-align:center;') }} + + +{{ title }} +
+ + + + +
+
diff --git a/templates/download.html b/templates/download.html new file mode 100644 index 0000000..3004e63 --- /dev/null +++ b/templates/download.html @@ -0,0 +1,17 @@ +{% include 'base.html' %} + +{% block main %} + +
Files to Download
+
+
+
+
+
+File name : {{ filename }}

+Direct URL : {{ url_todl }}

+ + +
+
+{% endblock %} diff --git a/templates/foot.html b/templates/foot.html new file mode 100644 index 0000000..62b8883 --- /dev/null +++ b/templates/foot.html @@ -0,0 +1,11 @@ + +
+ +{{ fontawesome_js() }} + + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..1eb754e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,31 @@ +{% include 'base.html' %} +{% block main %} +{% include 'menu.html' %} +
Files list
+
+
+ + + + + + + + + + + + + +{%for file in listdir %} + +{%for item in file %} + +{%endfor%} + +{%endfor%} + +
NameRetention (hours)Upload dateLinkEnd Download TimeDownload pageSingle Download
{{ item }}
+
+{% endblock %} +{% include 'foot.html' %} diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..6f6d147 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,33 @@ +{% include 'base.html' %} +{% block main %} +
+{{ alertmessage | safe }} +
+ + + +
+ +
+ +{% endblock %} +{% include 'foot.html' %} diff --git a/templates/menu.html b/templates/menu.html new file mode 100644 index 0000000..cdbf030 --- /dev/null +++ b/templates/menu.html @@ -0,0 +1,20 @@ +{% block main %} +
+ +
+
+
+{% endblock %} diff --git a/templates/nodown.html b/templates/nodown.html new file mode 100644 index 0000000..35b204b --- /dev/null +++ b/templates/nodown.html @@ -0,0 +1,13 @@ +{% include 'base.html' %} +{% block main %} +
No files to Download
+
+
+
+
+Nothing to download, file missing or already deleted.
+Contact your sender if you think it's a mistake. +
+ +{% endblock %} + diff --git a/templates/upload.html b/templates/upload.html new file mode 100644 index 0000000..c576264 --- /dev/null +++ b/templates/upload.html @@ -0,0 +1,59 @@ +{% include 'base.html' %} +{% include 'menu.html' %} +{% block main %} + {{ dropzone.load_js() }} + {{ dropzone.config() }} + +
Upload
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+
+ +
+ + + +{% endblock %} +{% include 'foot.html' %}