Add URL decoding for workspace and file paths in handlers

This commit is contained in:
2025-05-25 15:33:07 +02:00
parent d2c4a84c32
commit fe2a466a4f
3 changed files with 92 additions and 21 deletions

View File

@@ -3,6 +3,7 @@ package context
import ( import (
"lemma/internal/db" "lemma/internal/db"
"net/http" "net/http"
"net/url"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
@@ -42,12 +43,25 @@ func WithWorkspaceContextMiddleware(db db.WorkspaceReader) func(http.Handler) ht
} }
workspaceName := chi.URLParam(r, "workspaceName") workspaceName := chi.URLParam(r, "workspaceName")
workspace, err := db.GetWorkspaceByName(ctx.UserID, workspaceName) // URL-decode the workspace name
decodedWorkspaceName, err := url.PathUnescape(workspaceName)
if err != nil {
log.Error("failed to decode workspace name",
"error", err,
"userID", ctx.UserID,
"workspace", workspaceName,
"path", r.URL.Path)
http.Error(w, "Invalid workspace name", http.StatusBadRequest)
return
}
workspace, err := db.GetWorkspaceByName(ctx.UserID, decodedWorkspaceName)
if err != nil { if err != nil {
log.Error("failed to get workspace", log.Error("failed to get workspace",
"error", err, "error", err,
"userID", ctx.UserID, "userID", ctx.UserID,
"workspace", workspaceName, "workspace", decodedWorkspaceName,
"encodedWorkspace", workspaceName,
"path", r.URL.Path) "path", r.URL.Path)
http.Error(w, "Failed to get workspace", http.StatusNotFound) http.Error(w, "Failed to get workspace", http.StatusNotFound)
return return

View File

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"io" "io"
"net/http" "net/http"
"net/url"
"os" "os"
"time" "time"
@@ -110,7 +111,18 @@ func (h *Handler) LookupFileByName() http.HandlerFunc {
return return
} }
filePaths, err := h.Storage.FindFileByName(ctx.UserID, ctx.Workspace.ID, filename) // URL-decode the filename
decodedFilename, err := url.PathUnescape(filename)
if err != nil {
log.Error("failed to decode filename",
"filename", filename,
"error", err.Error(),
)
respondError(w, "Invalid filename", http.StatusBadRequest)
return
}
filePaths, err := h.Storage.FindFileByName(ctx.UserID, ctx.Workspace.ID, decodedFilename)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
log.Error("failed to lookup file", log.Error("failed to lookup file",
@@ -159,11 +171,22 @@ func (h *Handler) GetFileContent() http.HandlerFunc {
) )
filePath := chi.URLParam(r, "*") filePath := chi.URLParam(r, "*")
content, err := h.Storage.GetFileContent(ctx.UserID, ctx.Workspace.ID, filePath) // URL-decode the file path
decodedPath, err := url.PathUnescape(filePath)
if err != nil {
log.Error("failed to decode file path",
"filePath", filePath,
"error", err.Error(),
)
respondError(w, "Invalid file path", http.StatusBadRequest)
return
}
content, err := h.Storage.GetFileContent(ctx.UserID, ctx.Workspace.ID, decodedPath)
if err != nil { if err != nil {
if storage.IsPathValidationError(err) { if storage.IsPathValidationError(err) {
log.Error("invalid file path attempted", log.Error("invalid file path attempted",
"filePath", filePath, "filePath", decodedPath,
"error", err.Error(), "error", err.Error(),
) )
respondError(w, "Invalid file path", http.StatusBadRequest) respondError(w, "Invalid file path", http.StatusBadRequest)
@@ -172,7 +195,7 @@ func (h *Handler) GetFileContent() http.HandlerFunc {
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Debug("file not found", log.Debug("file not found",
"filePath", filePath, "filePath", decodedPath,
) )
respondError(w, "File not found", http.StatusNotFound) respondError(w, "File not found", http.StatusNotFound)
return return
@@ -228,21 +251,32 @@ func (h *Handler) SaveFile() http.HandlerFunc {
) )
filePath := chi.URLParam(r, "*") filePath := chi.URLParam(r, "*")
// URL-decode the file path
decodedPath, err := url.PathUnescape(filePath)
if err != nil {
log.Error("failed to decode file path",
"filePath", filePath,
"error", err.Error(),
)
respondError(w, "Invalid file path", http.StatusBadRequest)
return
}
content, err := io.ReadAll(r.Body) content, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
log.Error("failed to read request body", log.Error("failed to read request body",
"filePath", filePath, "filePath", decodedPath,
"error", err.Error(), "error", err.Error(),
) )
respondError(w, "Failed to read request body", http.StatusBadRequest) respondError(w, "Failed to read request body", http.StatusBadRequest)
return return
} }
err = h.Storage.SaveFile(ctx.UserID, ctx.Workspace.ID, filePath, content) err = h.Storage.SaveFile(ctx.UserID, ctx.Workspace.ID, decodedPath, content)
if err != nil { if err != nil {
if storage.IsPathValidationError(err) { if storage.IsPathValidationError(err) {
log.Error("invalid file path attempted", log.Error("invalid file path attempted",
"filePath", filePath, "filePath", decodedPath,
"error", err.Error(), "error", err.Error(),
) )
respondError(w, "Invalid file path", http.StatusBadRequest) respondError(w, "Invalid file path", http.StatusBadRequest)
@@ -295,11 +329,22 @@ func (h *Handler) DeleteFile() http.HandlerFunc {
) )
filePath := chi.URLParam(r, "*") filePath := chi.URLParam(r, "*")
err := h.Storage.DeleteFile(ctx.UserID, ctx.Workspace.ID, filePath) // URL-decode the file path
decodedPath, err := url.PathUnescape(filePath)
if err != nil {
log.Error("failed to decode file path",
"filePath", filePath,
"error", err.Error(),
)
respondError(w, "Invalid file path", http.StatusBadRequest)
return
}
err = h.Storage.DeleteFile(ctx.UserID, ctx.Workspace.ID, decodedPath)
if err != nil { if err != nil {
if storage.IsPathValidationError(err) { if storage.IsPathValidationError(err) {
log.Error("invalid file path attempted", log.Error("invalid file path attempted",
"filePath", filePath, "filePath", decodedPath,
"error", err.Error(), "error", err.Error(),
) )
respondError(w, "Invalid file path", http.StatusBadRequest) respondError(w, "Invalid file path", http.StatusBadRequest)
@@ -308,7 +353,7 @@ func (h *Handler) DeleteFile() http.HandlerFunc {
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Debug("file not found", log.Debug("file not found",
"filePath", filePath, "filePath", decodedPath,
) )
respondError(w, "File not found", http.StatusNotFound) respondError(w, "File not found", http.StatusNotFound)
return return
@@ -413,7 +458,19 @@ func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc {
// Validate the file path in the workspace // Validate the file path in the workspace
if requestBody.FilePath != "" { if requestBody.FilePath != "" {
_, err := h.Storage.GetFileContent(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath) // URL-decode the file path
decodedPath, err := url.PathUnescape(requestBody.FilePath)
if err != nil {
log.Error("failed to decode file path",
"filePath", requestBody.FilePath,
"error", err.Error(),
)
respondError(w, "Invalid file path", http.StatusBadRequest)
return
}
requestBody.FilePath = decodedPath
_, err = h.Storage.GetFileContent(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath)
if err != nil { if err != nil {
if storage.IsPathValidationError(err) { if storage.IsPathValidationError(err) {
log.Error("invalid file path attempted", log.Error("invalid file path attempted",

View File

@@ -173,19 +173,19 @@ func (h *Handler) GetWorkspace() http.HandlerFunc {
} }
} }
func gitSettingsChanged(new, old *models.Workspace) bool { func gitSettingsChanged(newWorkspace, old *models.Workspace) bool {
// Check if Git was enabled/disabled // Check if Git was enabled/disabled
if new.GitEnabled != old.GitEnabled { if newWorkspace.GitEnabled != old.GitEnabled {
return true return true
} }
// If Git is enabled, check if any settings changed // If Git is enabled, check if any settings changed
if new.GitEnabled { if newWorkspace.GitEnabled {
return new.GitURL != old.GitURL || return newWorkspace.GitURL != old.GitURL ||
new.GitUser != old.GitUser || newWorkspace.GitUser != old.GitUser ||
new.GitToken != old.GitToken || newWorkspace.GitToken != old.GitToken ||
new.GitCommitName != old.GitCommitName || newWorkspace.GitCommitName != old.GitCommitName ||
new.GitCommitEmail != old.GitCommitEmail newWorkspace.GitCommitEmail != old.GitCommitEmail
} }
return false return false