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