From 9aba5e7902f2fdfe7e376508fb64d63e5bbb1ae4 Mon Sep 17 00:00:00 2001 From: Moto Date: Mon, 29 Sep 2025 22:45:20 -0500 Subject: [PATCH] Upload files to "cmd/gemreader" --- cmd/gemreader/main.go | 148 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 cmd/gemreader/main.go diff --git a/cmd/gemreader/main.go b/cmd/gemreader/main.go new file mode 100644 index 0000000..c76261b --- /dev/null +++ b/cmd/gemreader/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "gemreader/internal/config" + "gemreader/internal/tui" + "github.com/charmbracelet/bubbletea" + "github.com/spf13/cobra" +) + +const ( + MaxFileSize = 10 * 1024 * 1024 // 10MB limit +) + +// validateFilePath ensures the file path is safe by resolving it relative to the current working directory +func validateFilePath(inputPath string) (string, error) { + // Clean the path to remove any .. elements + cleanPath := filepath.Clean(inputPath) + + // Get the current working directory + currentDir, err := os.Getwd() + if err != nil { + return "", err + } + + // Join the current working directory with the clean path + absPath := filepath.Join(currentDir, cleanPath) + + // Resolve the absolute path to remove any .. elements + resolvedPath, err := filepath.Abs(absPath) + if err != nil { + return "", err + } + + // Ensure the resolved path is within the current directory + relPath, err := filepath.Rel(currentDir, resolvedPath) + if err != nil { + return "", err + } + + // If the relative path starts with .. or is an absolute path, it's trying to go outside the current directory + if strings.HasPrefix(relPath, "..") || filepath.IsAbs(relPath) { + return "", fmt.Errorf("path traversal detected: %s", inputPath) + } + + return resolvedPath, nil +} + +// validateFile performs additional security checks on the file +func validateFile(filePath string) error { + // Check file size to prevent loading extremely large files + fileInfo, err := os.Stat(filePath) + if err != nil { + return err + } + + if fileInfo.Size() > MaxFileSize { + return fmt.Errorf("file size too large: %d bytes (max: %d bytes)", fileInfo.Size(), MaxFileSize) + } + + // Check file extension to ensure it's a text/markdown file + ext := strings.ToLower(filepath.Ext(filePath)) + if ext != ".md" && ext != ".markdown" && ext != ".txt" && ext != "" { + // If there's no extension, we'll allow it since some markdown files don't have extensions + // If there's an extension, it should be markdown-related or text + if ext != "" { + return fmt.Errorf("unsupported file type: %s (only .md, .markdown, .txt files are allowed)", ext) + } + } + + return nil +} + +var rootCmd = &cobra.Command{ + Use: "gemreader [file]", + Short: "A markdown viewer for your terminal.", + Args: cobra.MaximumNArgs(1), // Allow 0 or 1 arguments + Run: func(cmd *cobra.Command, args []string) { + // Load configuration + cfg, err := config.LoadConfig() + if err != nil { + fmt.Printf("could not load config: %v\n", err) + // Continue with default behavior if config loading fails + cfg = config.Config{Title: "GemReader", DefaultFile: ""} // default values + } + + var filePath string + if len(args) > 0 { + // Use the provided file path from command line arguments + filePath = args[0] + // Validate the file path to prevent directory traversal attacks + securePath, err := validateFilePath(args[0]) + if err != nil { + fmt.Printf("invalid file path: %v\n", err) + os.Exit(1) + } + filePath = securePath + } else if cfg.DefaultFile != "" { + // Use the default file from configuration + securePath, err := validateFilePath(cfg.DefaultFile) + if err != nil { + fmt.Printf("invalid default file path in config: %v\n", err) + os.Exit(1) + } + filePath = securePath + } else { + // No file provided and no default file configured + fmt.Println("No file provided and no default file configured. Please provide a file or set default_file in config.toml") + os.Exit(1) + } + + // Perform additional security validation on the file + if err := validateFile(filePath); err != nil { + fmt.Printf("file validation failed: %v\n", err) + os.Exit(1) + } + + content, err := os.ReadFile(filePath) + if err != nil { + fmt.Printf("could not read file: %v\n", err) + os.Exit(1) + } + + // Pass raw content to TUI so it can generate TOC from the original markdown + m := tui.NewModel(string(content), cfg) + + p := tea.NewProgram(m) + if err := p.Start(); err != nil { + fmt.Printf("Alas, there's been an error: %v\n", err) + os.Exit(1) + } + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func main() { + Execute() +} \ No newline at end of file