first commit
commit
abf7741b16
|
@ -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)
|
||||
|
||||
|
|
@ -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')
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,8 @@
|
|||
|
||||
flask_qrcode
|
||||
flask
|
||||
flask_fontawesome
|
||||
simplepam
|
||||
flask_navigation
|
||||
flask_dropzone
|
||||
pyopenssl
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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' %}
|
|
@ -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"> Login</span></button>
|
||||
</form>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
{% endblock %}
|
||||
{% include 'foot.html' %}
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
@ -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' %}
|
Loading…
Reference in New Issue