Veruse / Redirect Checker

Last active 1 month ago

Like 0

bash <(curl -fsSL https://gist.veru.se/Veruse/87653b8402024210986ecec3c826e1b9/raw/HEAD/redirect-checker.sh)

Veruse revised this gist 1 month ago · 1266ec4

1 file changed, 1 insertion, 1 deletion

redirect-checker.sh
@@ -6,7 +6,7 @@
6 6 # bash <(curl -fsSL https://gist.veru.se/Veruse/87653b8402024210986ecec3c826e1b9/raw/HEAD/redirect-checker.sh)
7 7 # curl -fsSL https://gist.veru.se/Veruse/87653b8402024210986ecec3c826e1b9/raw/HEAD/redirect-checker.sh | bash
8 8 #
9 - # Arguments/flags still work if you want to skip the prompts:
9 + # Arguments/flags work if you want to skip the prompts:
10 10 # bash <(curl -fsSL URL) https://a.com https://b.com
11 11 # bash <(curl -fsSL URL) -f urls.txt
12 12

Veruse revised this gist 1 month ago · a3eecbc

1 file changed, 2 insertions, 2 deletions

redirect-checker.sh
@@ -3,8 +3,8 @@
3 3 # check_redirects.sh - Follow and report HTTP redirect chains (max 10 hops).
4 4 #
5 5 # Run straight from a gist (it will ask for the URLs):
6 - # bash <(curl -fsSL https://gist.githubusercontent.com/.../raw/check_redirects.sh)
7 - # curl -fsSL https://gist.githubusercontent.com/.../raw/check_redirects.sh | bash
6 + # bash <(curl -fsSL https://gist.veru.se/Veruse/87653b8402024210986ecec3c826e1b9/raw/HEAD/redirect-checker.sh)
7 + # curl -fsSL https://gist.veru.se/Veruse/87653b8402024210986ecec3c826e1b9/raw/HEAD/redirect-checker.sh | bash
8 8 #
9 9 # Arguments/flags still work if you want to skip the prompts:
10 10 # bash <(curl -fsSL URL) https://a.com https://b.com

Veruse revised this gist 1 month ago · c043962

1 file changed, 195 insertions

redirect-checker.sh (file created)
@@ -0,0 +1,195 @@
1 + #!/usr/bin/env bash
2 + #
3 + # check_redirects.sh - Follow and report HTTP redirect chains (max 10 hops).
4 + #
5 + # Run straight from a gist (it will ask for the URLs):
6 + # bash <(curl -fsSL https://gist.githubusercontent.com/.../raw/check_redirects.sh)
7 + # curl -fsSL https://gist.githubusercontent.com/.../raw/check_redirects.sh | bash
8 + #
9 + # Arguments/flags still work if you want to skip the prompts:
10 + # bash <(curl -fsSL URL) https://a.com https://b.com
11 + # bash <(curl -fsSL URL) -f urls.txt
12 +
13 + set -uo pipefail
14 +
15 + MAX_REDIRECTS=10
16 + TIMEOUT=10
17 + CSV=0
18 + URL_FILE=""
19 + USER_AGENT="Mozilla/5.0 (redirect-checker)"
20 +
21 + if [[ -t 1 ]]; then
22 + RED=$'\e[31m'; GREEN=$'\e[32m'; YELLOW=$'\e[33m'; CYAN=$'\e[36m'; BOLD=$'\e[1m'; RESET=$'\e[0m'
23 + else
24 + RED=""; GREEN=""; YELLOW=""; CYAN=""; BOLD=""; RESET=""
25 + fi
26 +
27 + usage() {
28 + cat <<'EOF'
29 + Usage: check_redirects.sh [options] [url ...]
30 + (no URLs given = interactive prompts)
31 +
32 + Options:
33 + -f FILE Read URLs from FILE (one per line, # comments and blank lines ignored)
34 + -m N Max redirects to follow (default: 10)
35 + -t SECS Timeout per request in seconds (default: 10)
36 + -c Output CSV (url,final_url,final_status,hops,result)
37 + -h Show help
38 + EOF
39 + exit "${1:-0}"
40 + }
41 +
42 + while getopts ":f:m:t:ch" opt; do
43 + case "$opt" in
44 + f) URL_FILE="$OPTARG" ;;
45 + m) MAX_REDIRECTS="$OPTARG" ;;
46 + t) TIMEOUT="$OPTARG" ;;
47 + c) CSV=1 ;;
48 + h) usage 0 ;;
49 + :) echo "Option -$OPTARG requires an argument." >&2; usage 1 ;;
50 + \?) echo "Unknown option: -$OPTARG" >&2; usage 1 ;;
51 + esac
52 + done
53 + shift $((OPTIND - 1))
54 +
55 + command -v curl >/dev/null 2>&1 || { echo "Error: curl is required." >&2; exit 1; }
56 +
57 + # ---- Collect URLs ----
58 + URLS=()
59 + if [[ -n "$URL_FILE" ]]; then
60 + [[ -r "$URL_FILE" ]] || { echo "Error: cannot read $URL_FILE" >&2; exit 1; }
61 + while IFS= read -r line || [[ -n "$line" ]]; do URLS+=("$line"); done < "$URL_FILE"
62 + fi
63 + URLS+=("$@")
64 +
65 + # ---- Interactive prompts (read from the terminal, so it works with curl | bash) ----
66 + TTY="${TTY_DEVICE:-/dev/tty}"
67 + if [[ ${#URLS[@]} -eq 0 ]]; then
68 + if ! { exec 3<"$TTY"; } 2>/dev/null; then
69 + echo "Error: no URLs given and no terminal available to ask for them." >&2
70 + usage 1
71 + fi
72 +
73 + echo "${BOLD}Redirect checker${RESET} (max $MAX_REDIRECTS redirects per URL)"
74 + echo " 1) Check a single URL"
75 + echo " 2) Paste a list of URLs"
76 + echo " 3) Load URLs from a file"
77 + read -r -u 3 -p "Choose [1-3]: " choice
78 +
79 + case "$choice" in
80 + 1)
81 + read -r -u 3 -p "URL: " line
82 + URLS+=("$line")
83 + ;;
84 + 2)
85 + echo "Paste URLs, one per line. Press Enter on an empty line when done:"
86 + while IFS= read -r -u 3 line; do
87 + line="${line//$'\r'/}"
88 + [[ -z "${line// /}" ]] && break
89 + URLS+=("$line")
90 + done
91 + ;;
92 + 3)
93 + read -r -u 3 -p "File path: " URL_FILE
94 + URL_FILE="${URL_FILE/#\~/$HOME}"
95 + [[ -r "$URL_FILE" ]] || { echo "Error: cannot read $URL_FILE" >&2; exit 1; }
96 + while IFS= read -r line || [[ -n "$line" ]]; do URLS+=("$line"); done < "$URL_FILE"
97 + ;;
98 + *)
99 + echo "Invalid choice." >&2; exit 1 ;;
100 + esac
101 +
102 + read -r -u 3 -p "Save results as CSV instead of on-screen output? [y/N]: " ans
103 + if [[ "$ans" =~ ^[Yy] ]]; then
104 + read -r -u 3 -p "CSV file name [redirects.csv]: " csvfile
105 + csvfile="${csvfile:-redirects.csv}"
106 + CSV=1
107 + echo "Writing results to $csvfile ..."
108 + exec >"$csvfile"
109 + fi
110 + exec 3<&-
111 + fi
112 +
113 + [[ ${#URLS[@]} -eq 0 ]] && { echo "No URLs provided." >&2; exit 1; }
114 +
115 + csv_escape() { local s="${1//\"/\"\"}"; printf '"%s"' "$s"; }
116 +
117 + # ---- Check a single URL ----
118 + check_url() {
119 + local start="$1" current="$1" hops=0 result="" code="" next=""
120 + declare -A seen=()
121 +
122 + [[ "$current" =~ ^https?:// ]] || current="http://$current"
123 + [[ $CSV -eq 0 ]] && printf '\n%s%s%s\n' "$BOLD" "$start" "$RESET"
124 +
125 + while :; do
126 + local out
127 + out=$(curl -s -o /dev/null --max-time "$TIMEOUT" -A "$USER_AGENT" \
128 + -w '%{http_code} %{redirect_url}' "$current" 2>/dev/null)
129 + code="${out%% *}"
130 + next="${out#* }"; [[ "$next" == "$out" ]] && next=""
131 +
132 + if [[ -z "$code" || "$code" == "000" ]]; then
133 + result="ERROR (connection failed/timeout)"
134 + [[ $CSV -eq 0 ]] && printf ' [%2d] %sERR%s %s\n' "$hops" "$RED" "$RESET" "$current"
135 + break
136 + fi
137 +
138 + local colour="$GREEN"
139 + [[ "$code" =~ ^3 ]] && colour="$YELLOW"
140 + [[ "$code" =~ ^[45] ]] && colour="$RED"
141 + [[ $CSV -eq 0 ]] && printf ' [%2d] %s%s%s %s\n' "$hops" "$colour" "$code" "$RESET" "$current"
142 +
143 + seen["$current"]=1
144 +
145 + # Not a redirect (or redirect without Location) -> done
146 + if [[ ! "$code" =~ ^3 || -z "$next" ]]; then
147 + if [[ "$code" =~ ^2 ]]; then result="OK"
148 + elif [[ "$code" =~ ^3 ]]; then result="REDIRECT WITHOUT LOCATION"
149 + else result="HTTP $code"; fi
150 + break
151 + fi
152 +
153 + if [[ -n "${seen[$next]:-}" ]]; then
154 + result="LOOP DETECTED"
155 + [[ $CSV -eq 0 ]] && printf ' %s↻ loops back to %s%s\n' "$RED" "$next" "$RESET"
156 + current="$next"; hops=$((hops + 1))
157 + break
158 + fi
159 +
160 + hops=$((hops + 1))
161 + current="$next"
162 +
163 + if (( hops > MAX_REDIRECTS )); then
164 + result="TOO MANY REDIRECTS (>$MAX_REDIRECTS)"
165 + break
166 + fi
167 + done
168 +
169 + if [[ $CSV -eq 1 ]]; then
170 + printf '%s,%s,%s,%s,%s\n' "$(csv_escape "$start")" "$(csv_escape "$current")" \
171 + "$code" "$hops" "$(csv_escape "$result")"
172 + else
173 + local rc="$GREEN"; [[ "$result" != "OK" ]] && rc="$RED"
174 + printf ' %s→ %s%s | hops: %d | final: %s\n' "$rc" "$result" "$RESET" "$hops" "$current"
175 + fi
176 +
177 + [[ "$result" == "OK" ]]
178 + }
179 +
180 + # ---- Main ----
181 + [[ $CSV -eq 1 ]] && echo "url,final_url,final_status,hops,result"
182 +
183 + total=0; failed=0
184 + for raw in "${URLS[@]}"; do
185 + url="$(echo "$raw" | tr -d '\r' | xargs)" # trim whitespace / CRLF
186 + [[ -z "$url" || "$url" == \#* ]] && continue
187 + total=$((total + 1))
188 + check_url "$url" || failed=$((failed + 1))
189 + done
190 +
191 + if [[ $CSV -eq 0 ]]; then
192 + printf '\n%sChecked %d URL(s): %d OK, %d with issues%s\n' "$CYAN" "$total" "$((total - failed))" "$failed" "$RESET"
193 + fi
194 +
195 + (( failed == 0 ))