Files
ArmA-3-web-server/backend/internal/api/mods.go
T
MrFastwind 9f232b5225 fix(backend): add path validation to DeleteMod and improve GetLog security
- DeleteMod now validates path prefix against SERVERFILE_DIR and MODS_DIR
  before allowing deletion (prevents arbitrary directory deletion)
- GetLog uses strings.ContainsAny for cross-platform path traversal check
2026-07-23 20:15:06 +02:00

103 lines
2.4 KiB
Go

package api
import (
"net/http"
"path/filepath"
"strings"
"arma3-web-server/internal/services"
"github.com/gin-gonic/gin"
)
type deleteModInput struct {
Path string `json:"path" binding:"required"`
}
func (h *Handler) ListMods(c *gin.Context) {
workshopMods := services.ListWorkshopMods(h.serverfileDir)
localMods := services.ListLocalMods(h.modsDir)
usedWorkshop, usedLocal := services.BuildUsageMap(h.modlists)
for i := range workshopMods {
if lists, ok := usedWorkshop[workshopMods[i].ID]; ok {
workshopMods[i].InUse = true
workshopMods[i].Modlists = lists
// pull name from first modlist that has it
if workshopMods[i].Name == "" {
workshopMods[i].Name = lists[0]
}
}
}
for i := range localMods {
// check both the display name and the @name
if lists, ok := usedLocal[localMods[i].ID]; ok {
localMods[i].InUse = true
localMods[i].Modlists = lists
} else if lists, ok := usedLocal[localMods[i].Name]; ok {
localMods[i].InUse = true
localMods[i].Modlists = lists
}
}
all := append(workshopMods, localMods...)
if all == nil {
all = []services.ModInfo{}
}
c.JSON(http.StatusOK, all)
}
func (h *Handler) DeleteMod(c *gin.Context) {
var input deleteModInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
absPath, err := filepath.Abs(input.Path)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
return
}
if !strings.HasPrefix(absPath, h.serverfileDir) && !strings.HasPrefix(absPath, h.modsDir) {
c.JSON(http.StatusForbidden, gin.H{"error": "path outside allowed directories"})
return
}
if err := services.RemoveMod(absPath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusNoContent, nil)
}
func (h *Handler) CleanupMods(c *gin.Context) {
workshopMods := services.ListWorkshopMods(h.serverfileDir)
localMods := services.ListLocalMods(h.modsDir)
usedWorkshop, usedLocal := services.BuildUsageMap(h.modlists)
var deleted int
for _, m := range workshopMods {
if _, ok := usedWorkshop[m.ID]; !ok {
if err := services.RemoveMod(m.Path); err == nil {
deleted++
}
}
}
for _, m := range localMods {
if _, ok := usedLocal[m.ID]; !ok {
if _, ok2 := usedLocal[m.Name]; !ok2 {
if err := services.RemoveMod(m.Path); err == nil {
deleted++
}
}
}
}
c.JSON(http.StatusOK, gin.H{"deleted": deleted})
}