Upload files to "cmd/gemreader"
This commit is contained in:
parent
ace6fd5bf1
commit
9aba5e7902
1 changed files with 148 additions and 0 deletions
148
cmd/gemreader/main.go
Normal file
148
cmd/gemreader/main.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue