diff --git a/server/internal/context/middleware.go b/server/internal/context/middleware.go index 4d0a8d3..a06e946 100644 --- a/server/internal/context/middleware.go +++ b/server/internal/context/middleware.go @@ -3,6 +3,7 @@ package context import ( "lemma/internal/db" "net/http" + "net/url" "github.com/go-chi/chi/v5" ) @@ -42,12 +43,25 @@ func WithWorkspaceContextMiddleware(db db.WorkspaceReader) func(http.Handler) ht } 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 { log.Error("failed to get workspace", "error", err, "userID", ctx.UserID, - "workspace", workspaceName, + "workspace", decodedWorkspaceName, + "encodedWorkspace", workspaceName, "path", r.URL.Path) http.Error(w, "Failed to get workspace", http.StatusNotFound) return diff --git a/server/internal/handlers/file_handlers.go b/server/internal/handlers/file_handlers.go index 4c2a4c7..538742e 100644 --- a/server/internal/handlers/file_handlers.go +++ b/server/internal/handlers/file_handlers.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "net/http" + "net/url" "os" "time" @@ -110,7 +111,18 @@ func (h *Handler) LookupFileByName() http.HandlerFunc { 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 !os.IsNotExist(err) { log.Error("failed to lookup file", @@ -159,11 +171,22 @@ func (h *Handler) GetFileContent() http.HandlerFunc { ) 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 storage.IsPathValidationError(err) { log.Error("invalid file path attempted", - "filePath", filePath, + "filePath", decodedPath, "error", err.Error(), ) respondError(w, "Invalid file path", http.StatusBadRequest) @@ -172,7 +195,7 @@ func (h *Handler) GetFileContent() http.HandlerFunc { if os.IsNotExist(err) { log.Debug("file not found", - "filePath", filePath, + "filePath", decodedPath, ) respondError(w, "File not found", http.StatusNotFound) return @@ -228,21 +251,32 @@ func (h *Handler) SaveFile() http.HandlerFunc { ) 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) if err != nil { log.Error("failed to read request body", - "filePath", filePath, + "filePath", decodedPath, "error", err.Error(), ) respondError(w, "Failed to read request body", http.StatusBadRequest) 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 storage.IsPathValidationError(err) { log.Error("invalid file path attempted", - "filePath", filePath, + "filePath", decodedPath, "error", err.Error(), ) respondError(w, "Invalid file path", http.StatusBadRequest) @@ -295,11 +329,22 @@ func (h *Handler) DeleteFile() http.HandlerFunc { ) 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 storage.IsPathValidationError(err) { log.Error("invalid file path attempted", - "filePath", filePath, + "filePath", decodedPath, "error", err.Error(), ) respondError(w, "Invalid file path", http.StatusBadRequest) @@ -308,7 +353,7 @@ func (h *Handler) DeleteFile() http.HandlerFunc { if os.IsNotExist(err) { log.Debug("file not found", - "filePath", filePath, + "filePath", decodedPath, ) respondError(w, "File not found", http.StatusNotFound) return @@ -413,7 +458,19 @@ func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc { // Validate the file path in the workspace 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 storage.IsPathValidationError(err) { log.Error("invalid file path attempted", diff --git a/server/internal/handlers/workspace_handlers.go b/server/internal/handlers/workspace_handlers.go index 3977a80..663c966 100644 --- a/server/internal/handlers/workspace_handlers.go +++ b/server/internal/handlers/workspace_handlers.go @@ -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 - if new.GitEnabled != old.GitEnabled { + if newWorkspace.GitEnabled != old.GitEnabled { return true } // If Git is enabled, check if any settings changed - if new.GitEnabled { - return new.GitURL != old.GitURL || - new.GitUser != old.GitUser || - new.GitToken != old.GitToken || - new.GitCommitName != old.GitCommitName || - new.GitCommitEmail != old.GitCommitEmail + if newWorkspace.GitEnabled { + return newWorkspace.GitURL != old.GitURL || + newWorkspace.GitUser != old.GitUser || + newWorkspace.GitToken != old.GitToken || + newWorkspace.GitCommitName != old.GitCommitName || + newWorkspace.GitCommitEmail != old.GitCommitEmail } return false