94 lines
2.6 KiB
Go
94 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// sanitizePath encodes invalid filesystem characters using URL encoding.
|
|
// Example:
|
|
// /example/path/to/page?query=param&another=value
|
|
// would become
|
|
// example/path/to/page%3Fquery%3Dparam%26another%3Dvalue
|
|
func sanitizePath(p string) string {
|
|
// Split the path into its components
|
|
components := strings.Split(p, "/")
|
|
|
|
// Encode each component separately
|
|
for i, component := range components {
|
|
// Decode any existing percent-encoded characters
|
|
decodedComponent, err := url.PathUnescape(component)
|
|
if err != nil {
|
|
decodedComponent = component // Fallback to original if unescape fails
|
|
}
|
|
|
|
// Encode the component to escape invalid filesystem characters
|
|
encodedComponent := url.QueryEscape(decodedComponent)
|
|
|
|
// Replace '+' (from QueryEscape) with '%20' to handle spaces correctly
|
|
encodedComponent = strings.ReplaceAll(encodedComponent, "+", "%20")
|
|
|
|
components[i] = encodedComponent
|
|
}
|
|
|
|
// Rejoin the components into a sanitized path
|
|
safe := filepath.Join(components...)
|
|
|
|
return safe
|
|
}
|
|
|
|
// getFilePath constructs a safe file path from the root path and URL path.
|
|
// It URL-encodes invalid filesystem characters to ensure the path is valid.
|
|
func calcFilePath(rootPath, urlPath string) (string, error) {
|
|
// Normalize the URL path
|
|
cleanPath := filepath.Clean(urlPath)
|
|
|
|
// Safe check to prevent directory traversal
|
|
if strings.Contains(cleanPath, "..") {
|
|
return "", fmt.Errorf("Invalid URL path: contains directory traversal")
|
|
}
|
|
|
|
// Sanitize the path by encoding invalid characters
|
|
safePath := sanitizePath(cleanPath)
|
|
|
|
// Join the root path and the sanitized URL path
|
|
finalPath := filepath.Join(rootPath, safePath)
|
|
|
|
return finalPath, nil
|
|
}
|
|
|
|
func SaveResult(rootPath string, s *Snapshot) {
|
|
parentPath := path.Join(rootPath, s.Url.Hostname)
|
|
urlPath := s.Url.Path
|
|
// If path is empty, add `index.gmi` as the file to save
|
|
if urlPath == "" || urlPath == "." {
|
|
urlPath = fmt.Sprintf("index.gmi")
|
|
}
|
|
// If path ends with '/' then add index.gmi for the
|
|
// directory to be created.
|
|
if strings.HasSuffix(urlPath, "/") {
|
|
urlPath = strings.Join([]string{urlPath, "index.gmi"}, "")
|
|
}
|
|
|
|
finalPath, err := calcFilePath(parentPath, urlPath)
|
|
if err != nil {
|
|
LogError("Error saving %s: %w", s.Url, err)
|
|
return
|
|
}
|
|
// Ensure the directory exists
|
|
dir := filepath.Dir(finalPath)
|
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
|
LogError("Failed to create directory: %w", err)
|
|
return
|
|
}
|
|
err = os.WriteFile(finalPath, []byte((*s).Data), 0666)
|
|
if err != nil {
|
|
LogError("Error saving %s: %w", s.Url.Full, err)
|
|
}
|
|
LogInfo("[%s] Saved to %s", s.Url.Full, finalPath)
|
|
}
|