NAS直链分享

本文导入:近期在使用NAS的时候,想将NAS上的文件共享给好友,但好友没有连接NAS的客户端,我也不想留下NAS的账户名和密码,于是想到使用直链来分享,网上并没有好的解决方案,遂写了一个并开源


代码解析

#---导入目标库---the libraries you need to import
import datetime
from flask import Flask, redirect,render_template,request, url_for
from flask_login import LoginManager, current_user,login_user,UserMixin,login_required, logout_user
import sqlite3
import hashlib
import yaml
import os

#---基础配置---base settings---

#特别说明,本程序并不针对多用户服务开发,仅支持单用户管理
#Explain:this program is not developed for multipe users.Only support one user.
class User(UserMixin):
    def __init__(self,user_id):
        self.id = user_id

app = Flask(__name__)

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

with open('config.yaml', 'r') as f:
    config = yaml.safe_load(f)
app.secret_key =  str(os.environ.get('FLASK_SECRET_KEY')) #密钥(secret key)
app.config['PORT'] = int(config['port']) #服务端口(server port)
app.config['DATABASE'] = str(config['database']) #数据库名和位置(database name and location)
app.config['ROOT'] = str(config['root']) #分享文件根目录(the files root path which you need to share.Usually the NAS root path)
app.config['READ'] = int(config['read']) #SHA计算时每次读取的文件大小,默认8KB(the sha256 calculator read file size each time.Default 8KB)
app.config['PASSWORD'] = str(config['password']) #用户密码(User's password)

#---基本函数---base functions---

@login_manager.user_loader
def load_user(user_id):
    return User(1)

def opreat_db():#数据库操作基本函数(database opreation function)
    con = sqlite3.connect(app.config['DATABASE'])
    con.row_factory = sqlite3.Row
    return con

def sha_calculate(file_path):#sha256计算器(sha256 calculator)
    if not os.path.isfile(file_path):
        print("文件不存在。")
        return None
    sha512_256 = hashlib.sha256()
    with open(file_path, 'rb') as f:
        while chunk := f.read(app.config['READ']*8):
            sha512_256.update(chunk)

    return sha512_256.hexdigest()

#---路由设置---route settings---

@app.route('/login', methods=['GET', 'POST'])#登录(login)
def login():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    if request.method == 'POST':
        password = request.form['pwd']
        if password == app.config['PASSWORD']:
            user = User(1)
            login_user(user)
            return redirect(url_for('dashboard'))
        else:
            msg = "密码错误!"#Error message!Password is not correct
            return render_template('result.html',msg=msg)
    return render_template('login.html')

@app.route('/logout')#登出(logout)
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

@app.route("/dashboard")#仪表盘逻辑(dashboard logic)
@login_required
def dashboard():
    con = opreat_db()
    cur = con.cursor()
    cur.execute("SELECT * FROM shares")
    rows = cur.fetchall()
    total = con.execute('SELECT COUNT(*) as count FROM shares').fetchone()['count']
    con.close()
    return render_template("dashboard.html",rows = rows,total = total)
@login_manager.unauthorized_handler
def unauthorized():
    return '您没有权限访问!'

@app.route("/create")#单击创建分享时的新建分享页面(page which you click ths create button)
@login_required
def create():
    return render_template('create.html')
@login_manager.unauthorized_handler
def unauthorized():
    return '您没有权限访问!'

@app.route("/<sha256>/delet",methods=['GET'])
@login_required
def Delet(sha256):
    db = opreat_db()
    share = db.execute('SELECT * FROM shares WHERE sha256 = ?', (sha256,)).fetchone()
    if not share:
        db.close()
        msg = '共享不存在或已被删除'
        return render_template('result.html',msg=msg)
    db.execute('DELETE FROM shares WHERE sha256 = ?', (sha256,))
    db.commit()
    db.close()
    msg = '共享删除成功'
    return render_template('result.html',msg=msg)
@login_manager.unauthorized_handler
def unauthorized():
    return '您没有权限访问!'
@app.route('/result',methods = ['GET', 'POST'])#接受创建表单并返回结果(receive ths request and return results)
@login_required
def Add():
    msg = '已成功创建分享'#Success message!
    path = request.form['path']
    if not os.path.exists(path):
        msg = '指定的文件路径不存在!'#Error message!the path is not existed
        return render_template('result.html',msg=msg)
    sha256 = sha_calculate(path)
    name = os.path.basename(path)
    expire_days = int(request.form['days'])
    expire_time = (datetime.datetime.now() + datetime.timedelta(days=expire_days)).strftime('%Y-%m-%d %H:%M:%S')
    Max = request.form['max']
    try:
        db = opreat_db()
        db.execute('''
                INSERT INTO shares 
                (sha256, file_path, file_name, expire_time, max_downloads)
                VALUES (?, ?, ?, ?, ?)
            ''', (sha256, path, name, expire_time, Max))
        db.commit()
        db.close()
        return render_template('result.html',msg = msg)
    except sqlite3.IntegrityError:
        msg = '已共享该文件!'#Error Message!the file is repeated
        return render_template('result.html',msg=msg)
    except Exception as e:#Error Message!database error
        msg = '数据库错误!抛出异常:<br>'+str(e)
        return render_template('result.html',msg=msg)
@login_manager.unauthorized_handler
def unauthorized():
    return '您没有权限访问!'

if __name__ == "__main__":
    app.run(port=app.config['PORT'],debug=False)

对应注释写的很清楚,因为要开源到github上因此注释作了双语版的

本部分代码是control.py的,用于网页管理分享,需要访问“地址/login”才能进入登录界面,特意设置的直接访问是进不去的

#---导入目标库---the libraries you need to import
from flask import Flask, send_from_directory, abort
import sqlite3
import yaml
from datetime import datetime
import os

#---基础配置---base settings---
app = Flask(__name__)

with open('config.yaml', 'r') as f:
    config = yaml.safe_load(f)
app.config['DATABASE'] = str(config['database'])
app.config['ROOT'] = str(config['root'])

#---基本函数---base functions---
def get_file_path(sha256):
    """查询数据库,返回文件路径及限制信息"""
    conn = sqlite3.connect(app.config['DATABASE'])
    cursor = conn.cursor()
    cursor.execute("""
        SELECT file_path, expire_time, max_downloads, current_downloads 
        FROM shares WHERE sha256 = ?
    """, (sha256,))
    result = cursor.fetchone()
    conn.close()
    if not result:
        return None  # 哈希不存在
    file_path, expire_time, max_downloads, current_downloads = result
    # 检查过期时间
    if expire_time and datetime.now() > datetime.fromisoformat(expire_time):
        return "expired"
    # 检查下载次数
    if max_downloads and current_downloads >= max_downloads:
        return "max_downloads"
    # 更新下载次数
    conn = sqlite3.connect(app.config['DATABASE'])
    cursor = conn.cursor()
    cursor.execute("""
        UPDATE shares SET current_downloads = current_downloads + 1 
        WHERE sha256 = ?
    """, (sha256,))
    conn.commit()
    conn.close()
    return file_path

#---路由设置---route settings---
@app.route('/<sha256>')
def download_file(sha256):
    file_path = get_file_path(sha256)
    if file_path is None:
        abort(404, description="文件不存在或已取消共享")
    if file_path in ["expired", "max_downloads"]:
        abort(403, description="文件已过期或超过最大下载次数")
    if not file_path.startswith(app.config['ROOT']):
        abort(403, description="非法文件路径")
    dir_path = os.path.dirname(file_path)
    file_name = os.path.basename(file_path)
    return send_from_directory(dir_path, file_name, as_attachment=True)

if __name__ == '__main__':
    app.run(port=5000, debug=False)

本部分是download.py,用于处理下载请求,在config.yaml输入的NAS根目录就是为了防止遍历目录攻击,同样的注释已经写的十分清楚


使用说明

下面将演示在linux中运行本服务

首先在github的Releases中找到最新版的.tar.gz包,我们下载它,或者使用wget命令下载

wget https://github.com/super-inventor/NASDirectLinkShareTool/releases/download/official/Linux-0.1.0.tar.gz

注意:因为一些广为人知的原因,下载速率过慢很正常,本项目也不大,略微等待即可

下面我们来解压

tar -zxvf ./Linux-0.1.0.tar.gz

这个文件夹名不好听,并且里面嵌套了一层文件夹(我保证我会在下一个版本修复这一问题)于是乎我们把文件全部移出来,并给文件夹起一个好听的名字

cd ./Linux-0.1.0/NAS直链分享工具
mv ./* ../
mv ./templates ../
cd ../
rm -r ./NAS直链分享工具
cd ../
mv ./Linux-0.1.0 ./NDLT

完成之后,我们来检查一些系统是否安装了Python虚拟环境,笔者是 Debian系用户,因此这里以Ubuntu为例

sudo apt install python3-venv

如果没安装会自动安装,安装完之后我们就可以进入文件夹,运行install.sh来自动化配置了

cd ./NDLT
bash ./install.sh

笔者这里因为已经配置过一遍,所以什么就几句话,实际第一次配置会引入很多的库,所以会比这个长

注意,我们还没有完成配置工作,我们需要在templates里面找到dashboard.html里面找到第109行,修改里面的<>内容,你也发现了本项目是两个py文件,端口是分开的,目的就是为了防止路径遍历攻击而盗取数据

<!--注意!请在此处填写您的下载地址!--><a href="<你的下载地址>/{{ row["sha256"] }}" 

这个下载地址是指向5000端口的域名或者IPv4地址:5000也可以(我不推荐这么做,宁肯多部署一个反向代理)

在这之后,我们便可以开启服务了

bash ./start.sh

出现形如这样的输出,并且有两遍,就是启动成功了,停止使用

bash ./stop.sh

即可停止服务


使用效果

本项目可以高度自定义,修改html文件即可更改样式,整体用下来十分方便,以后点对点分享文件不需要用百度网盘等等,只需要传到NAS上,点一下创建共享就可以拿到直链,享受全速下载


不足之处

本项目仍然有不足之处,不支持密码下载等等功能的缺失,如果您有什么好的意见欢迎通过Issue提交,项目采用Apache2.0协议,您也可以直接提交修改,欢迎与我交流!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇