package handlers import ( "net/http" "os" "path/filepath" "strings" "github.com/gin-gonic/gin" "gobsidian/internal/utils" ) func (h *Handlers) CreateNotePageHandler(c *gin.Context) { folderPath := c.Query("folder") if folderPath == "" { folderPath = "" } notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config) if err != nil { c.HTML(http.StatusInternalServerError, "error", gin.H{ "error": "Failed to build tree structure", "app_name": h.config.AppName, "message": err.Error(), "ContentTemplate": "error_content", "ScriptsTemplate": "error_scripts", "Page": "error", }) return } c.HTML(http.StatusOK, "create", gin.H{ "app_name": h.config.AppName, "folder_path": folderPath, "notes_tree": notesTree, "active_path": utils.GetActivePath(folderPath), "current_note": nil, "breadcrumbs": utils.GenerateBreadcrumbs(folderPath), "image_storage_mode": h.config.ImageStorageMode, "image_subfolder_name": h.config.ImageSubfolderName, "ContentTemplate": "create_content", "ScriptsTemplate": "create_scripts", "Page": "create", }) } func (h *Handlers) CreateNoteHandler(c *gin.Context) { folderPath := strings.TrimSpace(c.PostForm("folder_path")) title := strings.TrimSpace(c.PostForm("title")) content := c.PostForm("content") if title == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Title is required"}) return } // Normalize slashes: treat backslashes as folder separators folderPath = strings.ReplaceAll(folderPath, "\\", "/") title = strings.ReplaceAll(title, "\\", "/") // Merge any subfolder segments included in title into folderPath if strings.Contains(title, "/") { dirPart := filepath.Dir(title) base := filepath.Base(title) if dirPart != "." && dirPart != "" { if folderPath == "" { folderPath = dirPart } else { folderPath = filepath.Join(folderPath, dirPart) } } title = base } // Strip any leading separators that might imply absolute path folderPath = strings.TrimPrefix(folderPath, "/") title = strings.TrimPrefix(title, "/") // Security check if strings.Contains(folderPath, "..") || strings.Contains(title, "..") { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path or title"}) return } // Check if path is in skipped directories (after merging title path) if utils.IsPathInSkippedDirs(folderPath, h.config.NotesDirSkip) { c.JSON(http.StatusForbidden, gin.H{"error": "Cannot create notes in this directory"}) return } // Determine extension logic ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(title)), ".") if ext == "" { // No extension provided: default to markdown title += ".md" ext = "md" } else { // Has extension: allow if md or in allowed file extensions allowed := ext == "md" if !allowed { for _, a := range h.config.AllowedFileExtensions { if strings.EqualFold(a, ext) { allowed = true break } } } if !allowed { c.JSON(http.StatusBadRequest, gin.H{"error": "File extension not allowed"}) return } } // Create full path var notePath string if folderPath == "" { notePath = title } else { notePath = filepath.Join(folderPath, title) } fullPath := filepath.Join(h.config.NotesDir, notePath) // Check if file already exists if _, err := os.Stat(fullPath); !os.IsNotExist(err) { c.JSON(http.StatusConflict, gin.H{"error": "A note with this title already exists"}) return } // Ensure directory exists dir := filepath.Dir(fullPath) if err := utils.EnsureDir(dir); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create directory"}) return } // Write file if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create note"}) return } // Redirect based on extension redirect := h.config.URLPrefix + "/note/" + notePath if strings.ToLower(ext) != "md" { redirect = h.config.URLPrefix + "/view_text/" + notePath } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Note created successfully", "note_path": notePath, "redirect": redirect, }) } func (h *Handlers) EditNotePageHandler(c *gin.Context) { notePath := strings.TrimPrefix(c.Param("path"), "/") if !strings.HasSuffix(notePath, ".md") { c.HTML(http.StatusBadRequest, "error", gin.H{ "error": "Invalid note path", "app_name": h.config.AppName, "message": "Note path must end with .md", "ContentTemplate": "error_content", "ScriptsTemplate": "error_scripts", "Page": "error", }) return } // Security check if strings.Contains(notePath, "..") { c.HTML(http.StatusBadRequest, "error", gin.H{ "error": "Invalid path", "app_name": h.config.AppName, "message": "Path traversal is not allowed", "ContentTemplate": "error_content", "ScriptsTemplate": "error_scripts", "Page": "error", }) return } // Check if path is in skipped directories if utils.IsPathInSkippedDirs(notePath, h.config.NotesDirSkip) { c.HTML(http.StatusForbidden, "error", gin.H{ "error": "Access denied", "app_name": h.config.AppName, "message": "This note cannot be edited", "ContentTemplate": "error_content", "ScriptsTemplate": "error_scripts", "Page": "error", }) return } fullPath := filepath.Join(h.config.NotesDir, notePath) if _, err := os.Stat(fullPath); os.IsNotExist(err) { c.HTML(http.StatusNotFound, "error", gin.H{ "error": "Note not found", "app_name": h.config.AppName, "message": "The requested note does not exist", "ContentTemplate": "error_content", "ScriptsTemplate": "error_scripts", "Page": "error", }) return } content, err := os.ReadFile(fullPath) if err != nil { c.HTML(http.StatusInternalServerError, "error", gin.H{ "error": "Failed to read note", "app_name": h.config.AppName, "message": err.Error(), "ContentTemplate": "error_content", "ScriptsTemplate": "error_scripts", "Page": "error", }) return } notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config) if err != nil { c.HTML(http.StatusInternalServerError, "error", gin.H{ "error": "Failed to build tree structure", "app_name": h.config.AppName, "message": err.Error(), "ContentTemplate": "error_content", "ScriptsTemplate": "error_scripts", "Page": "error", }) return } title := strings.TrimSuffix(filepath.Base(notePath), ".md") folderPath := filepath.Dir(notePath) if folderPath == "." { folderPath = "" } c.HTML(http.StatusOK, "edit", gin.H{ "app_name": h.config.AppName, "title": title, "content": string(content), "note_path": notePath, "folder_path": folderPath, "notes_tree": notesTree, "active_path": utils.GetActivePath(folderPath), "current_note": notePath, "breadcrumbs": utils.GenerateBreadcrumbs(folderPath), "image_storage_mode": h.config.ImageStorageMode, "image_subfolder_name": h.config.ImageSubfolderName, "ContentTemplate": "edit_content", "ScriptsTemplate": "edit_scripts", "Page": "edit", }) } func (h *Handlers) EditNoteHandler(c *gin.Context) { notePath := strings.TrimPrefix(c.Param("path"), "/") content := c.PostForm("content") if !strings.HasSuffix(notePath, ".md") { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid note path"}) return } // Security check if strings.Contains(notePath, "..") { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path"}) return } // Check if path is in skipped directories if utils.IsPathInSkippedDirs(notePath, h.config.NotesDirSkip) { c.JSON(http.StatusForbidden, gin.H{"error": "This note cannot be edited"}) return } fullPath := filepath.Join(h.config.NotesDir, notePath) if _, err := os.Stat(fullPath); os.IsNotExist(err) { c.JSON(http.StatusNotFound, gin.H{"error": "Note not found"}) return } // Write updated content if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save note"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Note saved successfully", "redirect": h.config.URLPrefix + "/note/" + notePath, }) } func (h *Handlers) DeleteHandler(c *gin.Context) { filePath := strings.TrimPrefix(c.Param("path"), "/") // Security check if strings.Contains(filePath, "..") { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path"}) return } // Check if path is in skipped directories if utils.IsPathInSkippedDirs(filePath, h.config.NotesDirSkip) { c.JSON(http.StatusForbidden, gin.H{"error": "Cannot delete files in this directory"}) return } fullPath := filepath.Join(h.config.NotesDir, filePath) if _, err := os.Stat(fullPath); os.IsNotExist(err) { c.JSON(http.StatusNotFound, gin.H{"error": "File not found"}) return } // Delete file or directory if err := os.RemoveAll(fullPath); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete file"}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "File deleted successfully", }) }