mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 22:36:37 +08:00
fix: security hardening and HTTP status code corrections
- importSTRMHandler: add URL scheme validation (blocks file://, ftp://, etc.) - backup Delete/Restore: harden path traversal check (block backslash, require .db extension) - HTTP 201 for create endpoints: register, subscription, download client, notify channel, library, STRM import - Error handling: return 500 for service/infra errors in download client and notify channel handlers Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -65,7 +65,7 @@ func registerHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
"user": u,
|
"user": u,
|
||||||
"tokens": tokens,
|
"tokens": tokens,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ func createDownloadClientHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
row, err := svc.DownloadClients.Create(c.Request.Context(), in)
|
row, err := svc.DownloadClients.Create(c.Request.Context(), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 让真正发起下载的 DownloadService 立刻读到新的 qb 配置,
|
// 让真正发起下载的 DownloadService 立刻读到新的 qb 配置,
|
||||||
// 避免保存后还要重启进程才能生效。
|
// 避免保存后还要重启进程才能生效。
|
||||||
_ = svc.Downloads.ReloadConfig(c.Request.Context())
|
_ = svc.Downloads.ReloadConfig(c.Request.Context())
|
||||||
c.JSON(http.StatusOK, row)
|
c.JSON(http.StatusCreated, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ func updateDownloadClientHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
row, err := svc.DownloadClients.Update(c.Request.Context(), c.Param("id"), in)
|
row, err := svc.DownloadClients.Update(c.Request.Context(), c.Param("id"), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = svc.Downloads.ReloadConfig(c.Request.Context())
|
_ = svc.Downloads.ReloadConfig(c.Request.Context())
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func createLibraryHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
svc.Audit.Record(c.Request.Context(), toString(uid), "library.create", l.ID, c.ClientIP(), l.Path)
|
svc.Audit.Record(c.Request.Context(), toString(uid), "library.create", l.ID, c.ClientIP(), l.Path)
|
||||||
// Refresh fsnotify watcher to pick up the new library root.
|
// Refresh fsnotify watcher to pick up the new library root.
|
||||||
go func() { _ = svc.Watcher.Refresh(context.Background()) }()
|
go func() { _ = svc.Watcher.Refresh(context.Background()) }()
|
||||||
c.JSON(http.StatusOK, l)
|
c.JSON(http.StatusCreated, l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ func createNotifyChannelHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
row, err := svc.NotifyChannels.Create(c.Request.Context(), in)
|
row, err := svc.NotifyChannels.Create(c.Request.Context(), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, row)
|
c.JSON(http.StatusCreated, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ func updateNotifyChannelHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
row, err := svc.NotifyChannels.Update(c.Request.Context(), c.Param("id"), in)
|
row, err := svc.NotifyChannels.Update(c.Request.Context(), c.Param("id"), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, row)
|
c.JSON(http.StatusOK, row)
|
||||||
|
|||||||
@@ -77,17 +77,22 @@ func importSTRMHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
url := strings.TrimSpace(req.URL)
|
||||||
|
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "url must start with http:// or https://"})
|
||||||
|
return
|
||||||
|
}
|
||||||
m := &model.Media{
|
m := &model.Media{
|
||||||
LibraryID: req.LibraryID,
|
LibraryID: req.LibraryID,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Path: req.URL, // unique-index target — keep it identical to the URL
|
Path: url,
|
||||||
STRMURL: req.URL,
|
STRMURL: url,
|
||||||
Container: "strm",
|
Container: "strm",
|
||||||
}
|
}
|
||||||
if err := svc.Repo.Media.Upsert(c.Request.Context(), m); err != nil {
|
if err := svc.Repo.Media.Upsert(c.Request.Context(), m); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusCreated, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func createSubscriptionHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
enriched := []model.Subscription{*s}
|
enriched := []model.Subscription{*s}
|
||||||
service.EnrichSubscriptionProgress(c.Request.Context(), svc.Repo, enriched)
|
service.EnrichSubscriptionProgress(c.Request.Context(), svc.Repo, enriched)
|
||||||
*s = enriched[0]
|
*s = enriched[0]
|
||||||
c.JSON(http.StatusOK, s)
|
c.JSON(http.StatusCreated, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (b *BackupService) List() ([]BackupInfo, error) {
|
|||||||
|
|
||||||
// Delete removes a single backup file.
|
// Delete removes a single backup file.
|
||||||
func (b *BackupService) Delete(filename string) error {
|
func (b *BackupService) Delete(filename string) error {
|
||||||
if strings.Contains(filename, "/") || strings.Contains(filename, "..") {
|
if !isValidBackupFilename(filename) {
|
||||||
return errors.New("invalid filename")
|
return errors.New("invalid filename")
|
||||||
}
|
}
|
||||||
path := filepath.Join(b.backupDir(), filename)
|
path := filepath.Join(b.backupDir(), filename)
|
||||||
@@ -117,7 +117,7 @@ func (b *BackupService) Delete(filename string) error {
|
|||||||
// reverse. WARNING: this is destructive — the live DB will be replaced.
|
// reverse. WARNING: this is destructive — the live DB will be replaced.
|
||||||
// Callers should shut down the server after this call.
|
// Callers should shut down the server after this call.
|
||||||
func (b *BackupService) Restore(ctx context.Context, filename string) error {
|
func (b *BackupService) Restore(ctx context.Context, filename string) error {
|
||||||
if strings.Contains(filename, "/") || strings.Contains(filename, "..") {
|
if !isValidBackupFilename(filename) {
|
||||||
return errors.New("invalid filename")
|
return errors.New("invalid filename")
|
||||||
}
|
}
|
||||||
src := filepath.Join(b.backupDir(), filename)
|
src := filepath.Join(b.backupDir(), filename)
|
||||||
@@ -148,3 +148,17 @@ func (b *BackupService) Restore(ctx context.Context, filename string) error {
|
|||||||
zap.String("backup", filename))
|
zap.String("backup", filename))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidBackupFilename rejects path traversal attempts and non-.db files.
|
||||||
|
func isValidBackupFilename(name string) bool {
|
||||||
|
if name == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(name, "/\\") || strings.Contains(name, "..") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(name, ".db") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user