package files import ( "fmt" "io" "log" "net/http" "os" "path/filepath" "time" ) // FileInfo represents metadata about a file or directory type FileInfo struct { Name string `json:"name"` Path string `json:"path"` Size int64 `json:"size"` IsDir bool `json:"is_dir"` ModTime string `json:"mod_time"` } // Operations handles file system operations type Operations struct{} // NewOperations creates a new file operations handler func NewOperations() *Operations { return &Operations{} } // Read reads a file and returns its contents func (o *Operations) Read(path string) (string, error) { log.Printf("Reading file: %s", path) // Security: Validate path to prevent directory traversal cleanPath, err := o.validatePath(path) if err != nil { return "", err } data, err := os.ReadFile(cleanPath) if err != nil { return "", fmt.Errorf("failed to read file: %w", err) } log.Printf("Read %d bytes from %s", len(data), path) return string(data), nil } // Write writes content to a file, downloading from URL if provided func (o *Operations) Write(path, downloadURL string) error { log.Printf("Writing file: %s", path) // Security: Validate path cleanPath, err := o.validatePath(path) if err != nil { return err } // Ensure directory exists dir := filepath.Dir(cleanPath) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } // If download URL is provided, fetch content from URL if downloadURL != "" { return o.downloadAndWrite(cleanPath, downloadURL) } return fmt.Errorf("no content or download URL provided") } // Delete deletes a file or directory func (o *Operations) Delete(path string) error { log.Printf("Deleting: %s", path) // Security: Validate path cleanPath, err := o.validatePath(path) if err != nil { return err } // Check if path exists if _, err := os.Stat(cleanPath); os.IsNotExist(err) { return fmt.Errorf("path does not exist: %s", path) } // Remove file or directory if err := os.RemoveAll(cleanPath); err != nil { return fmt.Errorf("failed to delete: %w", err) } log.Printf("Deleted: %s", path) return nil } // List lists files and directories at the given path func (o *Operations) List(path string) ([]FileInfo, error) { log.Printf("Listing directory: %s", path) // Security: Validate path cleanPath, err := o.validatePath(path) if err != nil { return nil, err } // Read directory entries, err := os.ReadDir(cleanPath) if err != nil { return nil, fmt.Errorf("failed to read directory: %w", err) } var files []FileInfo for _, entry := range entries { info, err := entry.Info() if err != nil { log.Printf("Warning: failed to get info for %s: %v", entry.Name(), err) continue } files = append(files, FileInfo{ Name: entry.Name(), Path: filepath.Join(path, entry.Name()), Size: info.Size(), IsDir: entry.IsDir(), ModTime: info.ModTime().Format(time.RFC3339), }) } log.Printf("Listed %d items in %s", len(files), path) return files, nil } // downloadAndWrite downloads content from URL and writes to file func (o *Operations) downloadAndWrite(path, url string) error { log.Printf("Downloading from %s to %s", url, path) // Create HTTP client with timeout client := &http.Client{ Timeout: 5 * time.Minute, } // Download file resp, err := client.Get(url) if err != nil { return fmt.Errorf("failed to download: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("download failed with status: %d", resp.StatusCode) } // Create destination file file, err := os.Create(path) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer file.Close() // Copy content written, err := io.Copy(file, resp.Body) if err != nil { return fmt.Errorf("failed to write file: %w", err) } log.Printf("Downloaded and wrote %d bytes to %s", written, path) return nil } // validatePath validates and cleans a file path to prevent directory traversal func (o *Operations) validatePath(path string) (string, error) { // Get absolute path absPath, err := filepath.Abs(path) if err != nil { return "", fmt.Errorf("invalid path: %w", err) } // Clean the path (removes .. and . elements) cleanPath := filepath.Clean(absPath) // Basic security check: ensure path doesn't try to escape // In production, you might want to restrict to specific directories if !filepath.IsAbs(cleanPath) { return "", fmt.Errorf("path must be absolute") } return cleanPath, nil } // Exists checks if a file or directory exists func (o *Operations) Exists(path string) (bool, error) { cleanPath, err := o.validatePath(path) if err != nil { return false, err } _, err = os.Stat(cleanPath) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err }