This commit is contained in:
2025-05-23 12:43:26 +01:00
commit 0facfa1398
3 changed files with 194 additions and 0 deletions

104
main.go Normal file
View File

@ -0,0 +1,104 @@
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
// maxRedirects defines the maximum number of redirects to follow.
const maxRedirects = 10
// isValidURLAndFetchContent checks if the URL is valid and fetches its content.
// It handles redirects and checks for the .sh extension.
// It returns the content, a boolean indicating validity, and an error if any.
func isValidURLAndFetchContent(url string, redirectCount int) ([]byte, bool, error) {
if redirectCount > maxRedirects {
return nil, false, fmt.Errorf("too many redirects")
}
// Check if the URL ends with .sh
if strings.HasSuffix(strings.ToLower(url), ".sh") {
// If it ends with .sh, try to download it directly
fmt.Printf("Attempting to download: %s\n", url)
resp, err := http.Get(url)
if err != nil {
return nil, false, fmt.Errorf("failed to download from %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, false, fmt.Errorf("failed to read content from %s: %w", url, err)
}
return content, true, nil
}
// If status is not OK, but it ended with .sh, we treat it as a failed download for a .sh file.
return nil, false, fmt.Errorf("failed to download %s: status code %d", url, resp.StatusCode)
}
// If it doesn't end with .sh, check for redirect
fmt.Printf("URL %s does not end with .sh, checking for redirect...\n", url)
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // Do not follow redirects automatically
},
}
resp, err := client.Get(url) // Using Get to be able to read body if needed, though Head is often used for just status/headers
if err != nil {
return nil, false, fmt.Errorf("failed to request %s: %w", url, err)
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
location, err := resp.Location()
if err != nil {
return nil, false, fmt.Errorf("failed to get redirect location from %s: %w", url, err)
}
fmt.Printf("Redirected to: %s\n", location.String())
return isValidURLAndFetchContent(location.String(), redirectCount+1)
default:
// Not a .sh and not a redirect status code
return nil, false, fmt.Errorf("URL %s does not end in .sh and did not redirect (status: %d)", url, resp.StatusCode)
}
}
// hasShebang checks if the content starts with "#!".
func hasShebang(content []byte) bool {
return len(content) >= 2 && content[0] == '#' && content[1] == '!'
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run script_downloader.go <URL>")
os.Exit(1)
}
initialURL := os.Args[1]
content, isValid, err := isValidURLAndFetchContent(initialURL, 0)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if !isValid {
// This case should ideally be covered by errors from isValidURLAndFetchContent
fmt.Fprintf(os.Stderr, "The provided URL is invalid or does not point to a valid .sh file after redirects.\n")
os.Exit(1)
}
if hasShebang(content) {
fmt.Println("Valid script found. Content:")
fmt.Println("--- SCRIPT START ---")
fmt.Print(string(content))
fmt.Println("--- SCRIPT END ---")
} else {
fmt.Fprintf(os.Stderr, "Error: The file from URL %s does not start with a shebang '#!'.\n", initialURL)
os.Exit(1)
}
}