mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
fix: add filename validation, error handling, and safety backup visibility
This commit is contained in:
parent
5b2946a46d
commit
7f3855eb9a
1 changed files with 35 additions and 5 deletions
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -18,6 +19,8 @@ import (
|
||||||
"github.com/mhsanaei/3x-ui/v2/logger"
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var backupFilenameRegex = regexp.MustCompile(`^(backup|pre-restore)-\d{4}-\d{2}-\d{2}-\d{6}\.tar\.gz$`)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
backupDir = "/etc/x-ui/backups"
|
backupDir = "/etc/x-ui/backups"
|
||||||
)
|
)
|
||||||
|
|
@ -46,6 +49,15 @@ func ensureBackupDir() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateBackupFilename checks that the filename matches the expected backup naming pattern
|
||||||
|
// and does not contain path traversal sequences.
|
||||||
|
func validateBackupFilename(filename string) error {
|
||||||
|
if !backupFilenameRegex.MatchString(filename) {
|
||||||
|
return fmt.Errorf("invalid backup filename: %s", filename)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// checkNodeRole verifies the current node is a master node.
|
// checkNodeRole verifies the current node is a master node.
|
||||||
func checkNodeRole() error {
|
func checkNodeRole() error {
|
||||||
nodeCfg := config.GetNodeConfigFromJSON()
|
nodeCfg := config.GetNodeConfigFromJSON()
|
||||||
|
|
@ -109,16 +121,23 @@ func (s *BackupService) ListBackups() ([]BackupEntry, error) {
|
||||||
|
|
||||||
var result []BackupEntry
|
var result []BackupEntry
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() || !strings.HasPrefix(entry.Name(), "backup-") || !strings.HasSuffix(entry.Name(), ".tar.gz") {
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := entry.Name()
|
||||||
|
if !strings.HasSuffix(name, ".tar.gz") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(name, "backup-") && !strings.HasPrefix(name, "pre-restore-") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info, err := entry.Info()
|
info, err := entry.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ts := extractTimestamp(entry.Name())
|
ts := extractTimestamp(name)
|
||||||
result = append(result, BackupEntry{
|
result = append(result, BackupEntry{
|
||||||
Filename: entry.Name(),
|
Filename: name,
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
Size: info.Size(),
|
Size: info.Size(),
|
||||||
})
|
})
|
||||||
|
|
@ -137,6 +156,10 @@ func (s *BackupService) RestoreBackup(filename string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validateBackupFilename(filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(backupDir, filename)
|
filePath := filepath.Join(backupDir, filename)
|
||||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("backup file not found: %s", filePath)
|
return fmt.Errorf("backup file not found: %s", filePath)
|
||||||
|
|
@ -171,6 +194,9 @@ func (s *BackupService) RestoreBackup(filename string) error {
|
||||||
|
|
||||||
// DeleteBackup deletes a backup file.
|
// DeleteBackup deletes a backup file.
|
||||||
func (s *BackupService) DeleteBackup(filename string) error {
|
func (s *BackupService) DeleteBackup(filename string) error {
|
||||||
|
if err := validateBackupFilename(filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
filePath := filepath.Join(backupDir, filename)
|
filePath := filepath.Join(backupDir, filename)
|
||||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("backup file not found: %s", filePath)
|
return fmt.Errorf("backup file not found: %s", filePath)
|
||||||
|
|
@ -271,7 +297,8 @@ func defaultDBType(t string) string {
|
||||||
|
|
||||||
// extractTimestamp extracts the timestamp string from backup filename.
|
// extractTimestamp extracts the timestamp string from backup filename.
|
||||||
func extractTimestamp(filename string) string {
|
func extractTimestamp(filename string) string {
|
||||||
name := strings.TrimPrefix(filename, "backup-")
|
name := strings.TrimPrefix(filename, "pre-restore-")
|
||||||
|
name = strings.TrimPrefix(name, "backup-")
|
||||||
name = strings.TrimSuffix(name, ".tar.gz")
|
name = strings.TrimSuffix(name, ".tar.gz")
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
@ -371,7 +398,10 @@ func createTarGz(filePath string, meta BackupMeta, dumpSQL string) error {
|
||||||
defer tw.Close()
|
defer tw.Close()
|
||||||
|
|
||||||
// metadata.json
|
// metadata.json
|
||||||
metaBytes, _ := json.MarshalIndent(meta, "", " ")
|
metaBytes, err := json.MarshalIndent(meta, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal metadata: %w", err)
|
||||||
|
}
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
if err := tw.WriteHeader(&tar.Header{
|
||||||
Name: "metadata.json",
|
Name: "metadata.json",
|
||||||
Size: int64(len(metaBytes)),
|
Size: int64(len(metaBytes)),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue