149 lines
3.5 KiB
Bash
Executable File
149 lines
3.5 KiB
Bash
Executable File
#!/bin/sh
|
|
usage() {
|
|
printf 'usage: geminawk [-R] [-h hostname ] ' >&2
|
|
printf '[-p default-port] [-r max-redirects]\n' >&2
|
|
exit 64
|
|
}
|
|
Rflag=
|
|
PORT="${PORT:-1965}"
|
|
REDIRECTS="${REDIRECTS:-5}"
|
|
while getopts Rh:p:r: opt; do
|
|
case "$opt" in
|
|
R) Rflag=1;;
|
|
h) HOST="$OPTARG";;
|
|
p) PORT="$OPTARG";;
|
|
r) REDIRECTS="$OPTARG";;
|
|
*) usage;;
|
|
esac
|
|
done
|
|
shift $((OPTIND - 1))
|
|
# Try the default nc(1) command
|
|
if ! [ "$NC" ] && command -v nc >/dev/null &&
|
|
nc -c 2>/dev/null | head -n1 | grep -q '^usage:'; then
|
|
NC=nc
|
|
fi
|
|
if ! [ "$NC" ]; then
|
|
# Try to find a nc(1) that supports TLS
|
|
IFS=:
|
|
for dir in ${PATH-/usr/local/bin:/usr/bin:/bin}; do
|
|
if [ -e "$dir/nc" ]; then
|
|
case "$("$dir/nc" -c 2>&1 | head -n1)" in
|
|
usage:*)NC="${NC:-$dir/nc}";;
|
|
esac
|
|
fi
|
|
done
|
|
unset IFS
|
|
fi
|
|
# Default to openssl(1)
|
|
NC="${NC:-openssl s_client}"
|
|
# openssl(1) needs the `-quiet' option, otherwise
|
|
# it mixes its output with the server's response.
|
|
case "$NC" in
|
|
*s_client*)
|
|
NCFLAGS="${NCFLAGS:--quiet -no_check_time}";;
|
|
esac
|
|
NCFLAGS="${NCFLAGS:--c -Tnoverify -Tnoname}"
|
|
geminawk() {
|
|
awk -vu="$1" -vR="$Rflag" -vh="$HOST" -vp="$PORT" \
|
|
-vr="$REDIRECTS" -vnc="$NC $NCFLAGS" '
|
|
function escape(s) {
|
|
gsub(/'\''/, "'\''\\'\'''\''", s)
|
|
return ("'\''" s "'\''")
|
|
}
|
|
function uriescape(s, c) {
|
|
split(s, uriescape_a, "")
|
|
uriescape_cmd = "printf %s " escape(s) \
|
|
" | od -An -tx1 | tr a-z A-Z"
|
|
uriescape_hex = ""
|
|
while (uriescape_cmd | getline uriescape_line)
|
|
uriescape_hex = uriescape_hex uriescape_line
|
|
close(uriescape_cmd)
|
|
sub(/^[[:space:]]+/, "", uriescape_hex)
|
|
split(uriescape_hex, uriescape_x, /[[:space:]]+/)
|
|
s = ""
|
|
for (uriescape_i = 1; uriescape_i in uriescape_a;
|
|
uriescape_i++) {
|
|
uriescape_c = uriescape_a[uriescape_i]
|
|
if (uriescape_c ~ /[-.0-9A-Z_a-z~]/ || \
|
|
index(c, uriescape_c) > 0)
|
|
s = s uriescape_c
|
|
else
|
|
s = s "%" uriescape_x[uriescape_i]
|
|
}
|
|
delete uriescape_a
|
|
delete uriescape_x
|
|
return (s);
|
|
}
|
|
function command(u) {
|
|
url = u
|
|
sub(/^gemini:\/\//, "", url)
|
|
sub(/^\/+/, "", url)
|
|
host = url
|
|
sub(/^[^\/]*@/, "", host) # userinfo
|
|
port = host
|
|
if (host ~ /^\[/) { # IPv6
|
|
sub(/^\[/, "", host)
|
|
sub(/^\].*/, "", host)
|
|
} else # IPv4/reg-name
|
|
sub(/[\/:].*/, "", host)
|
|
if (h !~ /./ && host == "")
|
|
exit(1)
|
|
sub(/^(\[[^\]]*\]|[^:\/]*)/, "", port) # hostname
|
|
if (port ~ /^:/) { # port
|
|
sub(/^:/, "", port)
|
|
sub(/\/.*/, "", port)
|
|
if (h ~ /./ && (port + 0 != port ||
|
|
port + 0 < 0))
|
|
exit 1
|
|
} else
|
|
port = p
|
|
u = R ? u : "gemini://" url
|
|
ehost = escape(h ~ /./ ? h : host)
|
|
eport = escape(h ~ /./ ? p : port)
|
|
cmd0 = "printf '\''%s\\r\\n'\'' " escape(u)
|
|
if (nc ~ /s_client/)
|
|
cmd1 = nc " -connect " ehost ":" eport
|
|
else
|
|
cmd1 = nc " -- " ehost " " eport
|
|
cmd = cmd0 " | " cmd1 " 2>/dev/null"
|
|
return (cmd)
|
|
}
|
|
BEGIN {
|
|
r += 0
|
|
for (redir = 0; redir < r; redir++) {
|
|
cmd = command(u)
|
|
cmd | getline
|
|
sub(/\r$/, "")
|
|
if ($1 ~ /^1/) { # INPUT
|
|
prompt = ""
|
|
for (i = 2; i <= NF; i++)
|
|
prompt = prompt \
|
|
(i > 2 ? " " : "") $i
|
|
print(i > 2 ? prompt : "?")
|
|
getline
|
|
sub(/\?.*/, "", u)
|
|
u = u "?" uriescape($0, \
|
|
"!$&'\''()*+,/:;=?@")
|
|
redir = -1;
|
|
} else if ($1 ~ /^2/) { # OK
|
|
while (cmd | getline)
|
|
print
|
|
exit
|
|
} else if ($1 ~ /^3/) { # REDIRECT
|
|
if (NF > 2)
|
|
exit 1
|
|
u = $2;
|
|
if ($2 ~ /^\/[^\/]/ && h !~ /./)
|
|
u = host $2
|
|
} else { # Error or unsupported
|
|
print
|
|
exit 1
|
|
}
|
|
close(cmd)
|
|
}
|
|
}'
|
|
}
|
|
for url in "$@"; do
|
|
geminawk "$url"
|
|
done
|