Files
BiliDown/internal/router/video.go
T
yw1573 a0ef988bd5 fix: resolve critical issues and improve task management
- Fix path traversal vulnerability in downloadVideo handler by adding
  download directory whitelist validation
- Add graceful shutdown with signal handling for task persistence
- Fix division by zero panic in progressBar.percent() when total <= 0
- Add GetDB() function that returns error instead of using log.Fatal
- Change deleteTask to only remove database records, preserve downloaded files
- Add paused status for interrupted tasks on shutdown

Co-Authored-By: Claude
2026-04-09 16:51:22 +08:00

215 lines
5.7 KiB
Go

package router
import (
"fmt"
"net/http"
"path/filepath"
"strconv"
"strings"
"bilidown/internal/bilibili"
"bilidown/internal/util"
"bilidown/internal/util/res_error"
)
// getVideoInfo 通过 BV 号获取视频信息
func getVideoInfo(w http.ResponseWriter, r *http.Request) {
if r.ParseForm() != nil {
res_error.Send(w, res_error.ParamError)
return
}
bvid := r.FormValue("bvid")
if !util.CheckBvidFormat(bvid) {
res_error.Send(w, res_error.BvidFormatError)
return
}
db := util.MustGetDB()
defer db.Close()
sessdata, err := bilibili.GetSessdata(db)
if err != nil || sessdata == "" {
res_error.Send(w, res_error.NotLogin)
return
}
client := bilibili.BiliClient{SESSDATA: sessdata}
videoInfo, err := client.GetVideoInfo(bvid)
if err != nil {
util.Res{Success: false, Message: err.Error()}.Write(w)
return
}
util.Res{Success: true, Message: "获取成功", Data: videoInfo}.Write(w)
}
// getSeasonInfo 通过 EP 号或 SS 号获取视频信息
func getSeasonInfo(w http.ResponseWriter, r *http.Request) {
if r.ParseForm() != nil {
util.Res{Success: false, Message: "参数错误"}.Write(w)
return
}
var epid int
epid, err := strconv.Atoi(r.FormValue("epid"))
if r.FormValue("epid") != "" && err != nil {
util.Res{Success: false, Message: "epid 格式错误"}.Write(w)
return
}
var ssid int
if epid == 0 {
ssid, err = strconv.Atoi(r.FormValue("ssid"))
if r.FormValue("ssid") != "" && err != nil {
util.Res{Success: false, Message: "ssid 格式错误"}.Write(w)
return
}
}
db := util.MustGetDB()
defer db.Close()
sessdata, err := bilibili.GetSessdata(db)
if err != nil || sessdata == "" {
res_error.Send(w, res_error.NotLogin)
return
}
client := bilibili.BiliClient{SESSDATA: sessdata}
seasonInfo, err := client.GetSeasonInfo(epid, ssid)
if err != nil {
util.Res{Success: false, Message: err.Error()}.Write(w)
return
}
util.Res{Success: true, Message: "获取成功", Data: seasonInfo}.Write(w)
}
// getPlayInfo 通过 BVID 和 CID 获取视频播放信息
func getPlayInfo(w http.ResponseWriter, r *http.Request) {
if r.ParseForm() != nil {
util.Res{Success: false, Message: "参数错误"}.Write(w)
return
}
bvid := r.FormValue("bvid")
if !util.CheckBvidFormat(bvid) {
util.Res{Success: false, Message: "bvid 格式错误"}.Write(w)
return
}
cid, err := strconv.Atoi(r.FormValue("cid"))
if err != nil {
util.Res{Success: false, Message: "cid 格式错误"}.Write(w)
return
}
db := util.MustGetDB()
defer db.Close()
sessdata, err := bilibili.GetSessdata(db)
if err != nil || sessdata == "" {
res_error.Send(w, res_error.NotLogin)
return
}
client := bilibili.BiliClient{SESSDATA: sessdata}
playInfo, err := client.GetPlayInfo(bvid, cid)
if err != nil {
util.Res{Success: false, Message: fmt.Sprintf("client.GetPlayInfo: %v", err)}.Write(w)
return
}
util.Res{Success: true, Message: "获取成功", Data: playInfo}.Write(w)
}
func getPopularVideos(w http.ResponseWriter, r *http.Request) {
db := util.MustGetDB()
defer db.Close()
sessdata, err := bilibili.GetSessdata(db)
if err != nil || sessdata == "" {
res_error.Send(w, res_error.NotLogin)
return
}
client := bilibili.BiliClient{SESSDATA: sessdata}
videos, err := client.GetPopularVideos()
if err != nil {
util.Res{Success: false, Message: err.Error()}.Write(w)
return
}
bvidList := make([]string, 0)
for _, v := range videos {
bvidList = append(bvidList, v.Bvid)
}
util.Res{Success: true, Message: "获取成功", Data: bvidList}.Write(w)
}
var downloadVideo = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path")
if path == "" {
res_error.Send(w, res_error.ParamError)
return
}
// 获取下载目录作为白名单
db := util.MustGetDB()
defer db.Close()
downloadFolder, err := util.GetCurrentFolder(db)
if err != nil {
res_error.Send(w, fmt.Sprintf("获取下载目录失败: %v", err))
return
}
// 清理路径并转换为绝对路径
safePath := filepath.Clean(path)
absPath, err := filepath.Abs(safePath)
if err != nil {
res_error.Send(w, "无效的文件路径")
return
}
// 获取下载目录的绝对路径
absDownloadFolder, err := filepath.Abs(downloadFolder)
if err != nil {
res_error.Send(w, "无效的下载目录")
return
}
// 安全检查:确保请求的文件在下载目录内
if !strings.HasPrefix(absPath, absDownloadFolder) {
res_error.Send(w, "禁止访问该路径")
return
}
http.ServeFile(w, r, absPath)
})
var getSeasonsArchivesListFirstBvid = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var mid int
var seasonId int
var err error
if mid, err = strconv.Atoi(r.URL.Query().Get("mid")); err != nil {
res_error.Send(w, res_error.MidFormatError)
return
}
if seasonId, err = strconv.Atoi(r.URL.Query().Get("seasonId")); err != nil {
res_error.Send(w, res_error.SeasonIdFormatError)
return
}
client := bilibili.BiliClient{}
bvid, err := client.GetSeasonsArchivesListFirstBvid(mid, seasonId)
if err != nil {
res_error.Send(w, fmt.Sprintf("client.GetSeasonsArchivesList: %v", err))
return
}
util.Res{Success: true, Message: "获取成功", Data: bvid}.Write(w)
})
var getFavList = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mediaId, err := strconv.Atoi(r.URL.Query().Get("mediaId"))
if err != nil {
res_error.Send(w, res_error.ParamError)
return
}
db := util.MustGetDB()
defer db.Close()
sessdata, err := bilibili.GetSessdata(db)
if err != nil || sessdata == "" {
res_error.Send(w, res_error.NotLogin)
return
}
client := bilibili.BiliClient{SESSDATA: sessdata}
favList, err := client.GetFavlist(mediaId)
if err != nil {
res_error.Send(w, err.Error())
return
}
util.Res{Success: true, Message: "获取成功", Data: favList}.Write(w)
})