mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
Update path validation error handling
This commit is contained in:
@@ -4,8 +4,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"novamd/internal/context"
|
"novamd/internal/context"
|
||||||
|
"novamd/internal/storage"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
@@ -63,10 +65,21 @@ 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)
|
content, err := h.Storage.GetFileContent(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
|
if storage.IsPathValidationError(err) {
|
||||||
|
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
http.Error(w, "Failed to read file", http.StatusNotFound)
|
http.Error(w, "Failed to read file", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
http.Error(w, "Failed to read file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
_, err = w.Write(content)
|
_, err = w.Write(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,6 +106,11 @@ func (h *Handler) SaveFile() http.HandlerFunc {
|
|||||||
|
|
||||||
err = h.Storage.SaveFile(ctx.UserID, ctx.Workspace.ID, filePath, content)
|
err = h.Storage.SaveFile(ctx.UserID, ctx.Workspace.ID, filePath, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if storage.IsPathValidationError(err) {
|
||||||
|
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -112,6 +130,16 @@ func (h *Handler) DeleteFile() http.HandlerFunc {
|
|||||||
filePath := chi.URLParam(r, "*")
|
filePath := chi.URLParam(r, "*")
|
||||||
err := h.Storage.DeleteFile(ctx.UserID, ctx.Workspace.ID, filePath)
|
err := h.Storage.DeleteFile(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if storage.IsPathValidationError(err) {
|
||||||
|
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
http.Error(w, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
|
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -165,12 +193,23 @@ func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the file path exists in the workspace
|
// Validate the file path in the workspace
|
||||||
if requestBody.FilePath != "" {
|
if requestBody.FilePath != "" {
|
||||||
if _, err := h.Storage.ValidatePath(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
_, err := h.Storage.GetFileContent(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath)
|
||||||
|
if err != nil {
|
||||||
|
if storage.IsPathValidationError(err) {
|
||||||
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
http.Error(w, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, "Failed to update file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.DB.UpdateLastOpenedFile(ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
if err := h.DB.UpdateLastOpenedFile(ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
||||||
|
|||||||
24
server/internal/storage/errors.go
Normal file
24
server/internal/storage/errors.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// storage/errors.go
|
||||||
|
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PathValidationError represents a path validation error (e.g., path traversal attempt)
|
||||||
|
type PathValidationError struct {
|
||||||
|
Path string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PathValidationError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.Message, e.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPathValidationError checks if the error is a PathValidationError
|
||||||
|
func IsPathValidationError(err error) bool {
|
||||||
|
var pathErr *PathValidationError
|
||||||
|
return err != nil && errors.As(err, &pathErr)
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ func (s *Service) ValidatePath(userID, workspaceID int, path string) (string, er
|
|||||||
|
|
||||||
// First check if the path is absolute
|
// First check if the path is absolute
|
||||||
if filepath.IsAbs(path) {
|
if filepath.IsAbs(path) {
|
||||||
return "", fmt.Errorf("invalid path: absolute paths not allowed")
|
return "", &PathValidationError{Path: path, Message: "absolute paths not allowed"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join and clean the path
|
// Join and clean the path
|
||||||
@@ -36,7 +36,7 @@ func (s *Service) ValidatePath(userID, workspaceID int, path string) (string, er
|
|||||||
|
|
||||||
// Verify the path is still within the workspace
|
// Verify the path is still within the workspace
|
||||||
if !strings.HasPrefix(cleanPath, workspacePath) {
|
if !strings.HasPrefix(cleanPath, workspacePath) {
|
||||||
return "", fmt.Errorf("invalid path: outside of workspace")
|
return "", &PathValidationError{Path: path, Message: "path traversal attempt"}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanPath, nil
|
return cleanPath, nil
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func TestValidatePath(t *testing.T) {
|
|||||||
path: "../../../etc/passwd",
|
path: "../../../etc/passwd",
|
||||||
want: "",
|
want: "",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errContains: "outside of workspace",
|
errContains: "path traversal attempt",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "absolute path attempt",
|
name: "absolute path attempt",
|
||||||
|
|||||||
Reference in New Issue
Block a user