first commit

main
pporcheret 2023-06-22 08:10:29 +00:00
commit abf7741b16
15 changed files with 597 additions and 0 deletions

83
README.md 100644
View File

@ -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)

190
app.py 100755
View File

@ -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/<link>')
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/<link>')
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='<p id="alert" style="color:red">Root not allowed </p>'
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='<p id="alert" style="color:red">Invalid Username/Password ! </p>'
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')

BIN
images/screen0.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
images/screen1.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
images/screen2.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

8
requirement.txt 100755
View File

@ -0,0 +1,8 @@
flask_qrcode
flask
flask_fontawesome
simplepam
flask_navigation
flask_dropzone
pyopenssl

113
static/style.css 100644
View File

@ -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;
}

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link href="{{url_for('static', filename = 'style.css')}}" rel="stylesheet">
{{ fontawesome_css() }}
{{ dropzone.load_css() }}
{{ dropzone.style('border: 2px dashed #000; min-height: 200px;text-align:center;') }}
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<title>{{ title }}</title>
<br>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet" type="text/css">
<body>
<div id="content">
<center>

View File

@ -0,0 +1,17 @@
{% include 'base.html' %}
{% block main %}
<h5 class="display-8">Files to Download</h5>
<div style="padding:40px">
<div class="quard">
<div class="card">
<div class="card-body">
<br>
File name : {{ filename }}<br><br>
Direct URL : <a href="{{ url_todl }}">{{ url_todl }}</a><br><br>
<img src="{{ qrcode }}">
<br>
</div></div></div></div>
{% endblock %}

View File

@ -0,0 +1,11 @@
</div>
{{ fontawesome_js() }}
<script
src="https://code.jquery.com/jquery-3.6.1.slim.min.js"
integrity="sha256-w8CvhFs7iHNVUtnSP0YKEg00p9Ih13rlL9zGqvLdePA="
crossorigin="anonymous"></script>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
{% include 'base.html' %}
{% block main %}
{% include 'menu.html' %}
<h5 class="display-8">Files list</h5>
<br>
<div style="padding:40px;">
<table class="table">
<thead>
<tr>
<td>Name</td>
<td>Retention (hours)</td>
<td>Upload date</td>
<td>Link</td>
<td>End Download Time</td>
<td>Download page</td>
<td>Single Download</td>
</tr>
</thead>
<tbody>
{%for file in listdir %}
<tr>
{%for item in file %}
<td>{{ item }}</td>
{%endfor%}
</tr>
{%endfor%}
</tbody>
</table>
</div>
{% endblock %}
{% include 'foot.html' %}

View File

@ -0,0 +1,33 @@
{% include 'base.html' %}
{% block main %}
<div class="space"></div>
{{ alertmessage | safe }}
<br>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet" type="text/c>
<link href='https://fonts.googleapis.com/css?family=Dosis' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Nunito' rel='stylesheet' type='text/css'>
<body>
<center>
<div class="space"></div>
<center>
<div class="login">
<h3>Login</h3>
<br><br>
<form action="/login" method="post">
<div class="form-group">
<input type="text" name="username" placeholder="Username" required>
</div>
<div class="form-group">
<input type="password" name="password" placeholder="Password" required>
</div>
<br>
<button name="Login" type="submit" class="btn btn-info"><span class="fas fa-user">&nbsp;&nbsp;&nbsp; Login</span></button>
</form>
</div>
</center>
{% endblock %}
{% include 'foot.html' %}

View File

@ -0,0 +1,20 @@
{% block main %}
<div class="logout">
<ul>
{% for item in nav.top %}
<li class="{{ 'active' if item.is_active else '' }}">
<a href="{{ item.url }}">{{ item.label }}</a>
</li>
{% endfor %}
<li style="float:right;">
<a href="logout"><span class="fas fa-user" style="display: inline;"></span> Logout</a>
</li>
<li style="float:right;">
<a href="javascript:window.location.href=window.location.href"><span class="fas fa-sync" style="display: inline;"></span></a>
</li>
</ul>
</div>
<br>
<br>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% include 'base.html' %}
{% block main %}
<h5 class="display-8">No files to Download</h5>
<div style="padding:40px">
<div class="quard">
<div class="card">
<div class="card-body">
Nothing to download, file missing or already deleted.<br>
Contact your sender if you think it's a mistake.
</div></div></div></div>
{% endblock %}

View File

@ -0,0 +1,59 @@
{% include 'base.html' %}
{% include 'menu.html' %}
{% block main %}
{{ dropzone.load_js() }}
{{ dropzone.config() }}
<script type="text/javascript">
Dropzone.options.myDropzone = {
autoProcessQueue: false,
maxFilesize: 10240,
acceptedFiles: ".zip,.rar,.tar,.gz",
init: function() {
var submitButton = document.querySelector("#submit-all")
myDropzone = this; // closure
submitButton.addEventListener("click", function() {
myDropzone.processQueue(); // Tell Dropzone to process all queued files.
});
this.on("addedfile", function() {
});
}
};
</script>
<h5 class="display-8">Upload</h5>
<div style="padding:40px">
<div class="quard">
<div class="card">
<div class="card-body">
<form action="/upload" class="dropzone" id="my-dropzone" method="POST">
<label for="console_type">Retention</label>
<select id="retention" name="retention" class="form-control">
<option value="1">1 hour</option>
<option value="24">1 day</option>
<option value="168">1 week</option>
<option value="730">1 month</option>
<option value="0">Unlimited</option>
</select>
<div class="col-auto my-1">
<div class="custom-control custom-checkbox mr-sm-2">
<input type="checkbox" class="form-check-input" id="uniqd" name="uniqd">
<label for="uniqd">Single download </label>
</div>
</div>
</form>
<br>
<button id="submit-all" class="btn btn-info">Send</button>
</div>
</div>
</div>
</div>
<!-- Avoid a Misalignment -->
<div style="clear: both;"></div>
<!-- -->
{% endblock %}
{% include 'foot.html' %}