💾 Archived View for gmn.clttr.info › sources › astro.git › tree › astro.txt captured on 2023-03-20 at 18:22:08.

View Raw

More Information

⬅️ Previous capture (2023-01-29)

➡️ Next capture (2023-09-08)

🚧 View Differences

-=-=-=-=-=-=-

#!/bin/sh

version="0.20.0"

usage() {
	echo "astro v$version: Browse the gemini web on the terminal."
	echo ""
	echo "Usage: astro [url]|[option]"
	echo ""
	echo "Options:"
	echo "  -h, --help		show this help"
	echo "  -v, --version		show version info"
	echo ""
	echo "Commands available inside the browser:"
	echo "  q	quit"
	echo "  g	go to a link"
	echo "  r	reload current page"
	echo "  b	go back one page"
	echo "  u	jump one path segment up"
	echo "  o	open an address"
	echo "  s	save current page"
	echo "  H	go to homepage"
	echo "  m	add bookmark"
	echo "  M	go to a bookmark"
	echo "  K	remove bookmark for current url"
	echo ""
	echo "Examples:"
	echo "  astro		Start browsing the default webpage"
	echo "  astro url	Start browsing url"
	echo "  astro --help	Show help"
	echo ""
	echo "Debugging:"
	echo "  debug=1 astro	Will start astro in debug mode"
	echo ""
	echo "Report bugs to: bleemayer@gmail.com"
	echo "Home page: <https://www.github.com/blmayer/astro/>"
	echo "General help: <https://www.github.com/blmayer/astro/wiki>"
}

version() {
	echo "astro $version"
	echo "Copyright (C) 2021-2023 Brian Mayer."
	echo "License MIT: MIT License <https://opensource.org/licenses/MIT>"
	echo "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,"
	echo "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF"
	echo "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT."
	echo ""
	echo "Written by Brian Lee Mayer."
}

debug() {
	[ "$debug" ] && echo "DEBUG: $*" >&2 && sleep 2
}

# Parse arguments
args="$*"
case "$args" in
	'--help'* | '-h'*)
		usage
		exit
		;;
	'--version'* | '-v'*)
		version
		exit
		;;
esac

# Save terminal
tput smcup

# Configuration
confighome=${XDG_CONFIG_HOME:-$HOME/.config}
mkdir -p "$confighome/astro"
configfile="$confighome/astro/astro.conf"
bookmarkfile="$confighome/astro/bookmarks"
LESSKEY="$confighome/astro/less.keys"
certdir="$confighome/astro/certs"
mkdir -p "$certdir"

[ -z "$cachehome" ] && cachehome=${XDG_CACHE_HOME:-$HOME/.cache}
cachedir="$cachehome/astro"

# This is the final binary form, to save space, it corresponds to:
# o (49): go to a URL
# r (50): reload page
# g (51): go to a link
# b (52): go back
# H (53): go to homepage
# m (54): add page to bookmarks
# M (55): go to a bookmark
# K (56): remove bookmark for current url
# u (57): jump one path element up
[ -n "$LESSKEY" ] && echo "AE0rR2MtAG8AmDEAcgCYMgBnAJgzAGIAmDQASACYNQBtAJg2AE0AmDcASwCYOAB1AJg5AGUAAHYAAHhFbmQ=" | \
	base64 -d  > "$LESSKEY"

margin=8
if [ ! -s "$configfile" ]
then
	# Default values
	cat << EOF > "$configfile"
cachedir="$cachehome"
margin=8
homepage="gemini.circumlunar.space/"
sty_header1='35;7;1'
sty_header2='35;4;1'
sty_header3='35;4'
sty_quote='2;3'
sty_linkb='35'
sty_linkt='36;3'
sty_listb='35;1'
sty_listt='0'
EOF
fi


# shellcheck disable=SC1090
. "$configfile"

mkdir -p "$cachedir"
histfile="$cachedir/history"
linksfile="$cachedir/links"

# Restore terminal
trap 'tput rmcup && rm -f $histfile $linksfile > /dev/null 2>&1; exit' EXIT INT HUP

# ANSI style code
sty_header1="\\033[${sty_header1}m"
sty_header2="\\033[${sty_header2}m"
sty_header3="\\033[${sty_header3}m"
sty_quote="\\033[${sty_quote}m"
sty_linkb="\\033[${sty_linkb}m"
sty_linkt="\\033[${sty_linkt}m"
sty_listb="\\033[${sty_listb}m"
sty_listt="\\033[${sty_listt}m"

getprevious() {
	sed -i '$d' "$histfile"
	prev="$(tail -n 1 "$histfile")"
	sed -i '$d' "$histfile"
	echo "$prev"
}

# Returns the complete url scheme with gemini defaults
# Parameters: url
parseurl() {
	# Credits: https://stackoverflow.com/a/6174447/7618649
	debug "parsing: $url oldhost: $oldhost oldpath: $oldpath"
	proto="$(echo "$url" | grep :// | sed -e 's,^\(.*://\).*,\1,g')"
	if [ "$proto" ]
	then 
		url="$(echo "$url" | sed -e "s@$proto@@g")"
	else
		if [ "$oldhost" ]
		then
			case "$url" in
				"/"*) url="$oldhost$url" ;;
				*) oldpath="/${oldpath#/*}"; url="$oldhost${oldpath%/*}/$url" ;;
			esac
		fi
	fi
	debug "url: $url"

	proto="$(echo "$proto" | sed -e 's,:\?//,,g')"
	user="$(echo "$url" | grep @ | cut -d@ -f1)"
	hostport="$(echo "$url" | sed -e "s/$user@//g" | cut -d/ -f1)"
	host="$(echo "$hostport" | sed -e 's,:.*,,g')"
	port="$(echo "$hostport" | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')"
	path="$(echo "${url#/*}" | sed "s@/\?$hostport@@")"

	debug "parsed: proto: ${proto:-gemini} host: $host port: ${port:-1965} path: ${path#/*}"
	echo "${proto:-gemini}" "$host" "${port:-1965}" "${path#/*}" "$rest"
}

typesetgmi() {
	linkcount=1
	while IFS='' read -r line
	do
		line="$(echo "$line" | tr -d '\r')"
		# shellcheck disable=SC2016
		echo "$line" | grep -q '^```' && pre=$((1 - pre)) && line=""

		# Add margins and fold
		if [ "$pre" = 1 ]
		then
			printf '%*s%s\n' "$margin" "" "$line"
			continue
		fi

		case "$line" in
			"### "*) sty="$sty_header3" && line="$(echo "$line" | cut -c 5- )" ;;
			"## "*) sty="$sty_header2" && line="$(echo "$line" | cut -c 4-)" ;;
			"# "*) sty="$sty_header1" && line="$(echo "$line" | cut -c 3-)" ;;
			"> "*) sty="  $sty_quote" && line="$(echo "$line" | cut -c 3-)" ;;
			"=>"*)
				link="$(echo "$line" | sed -e 's/^=> *\(\S\+\)\(\s*.*\)/\1 \2/g')"
				echo "$link" >> "$linksfile"

				# shellcheck disable=SC2086
				line="$(echo $link | cut -d' ' -f2-)"
				[ -z "$line" ] && line="$link"
				sty="$sty_linkt"
				line="$sty_linkb$linkcount>$sty_linkt $line"
				linkcount=$((linkcount+1))
				;;
			'* '*) sty="" && line=" $sty_listb•$sty_listt$(echo "$line" | cut -c 2-)";;
			*) sty="";;
		esac
		echo "$line" | sed 's/%/%%/g' | fold -w "$width" -s | {
			while IFS='' read -r txt
			do
				printf "%*s" "$margin" ""
				# shellcheck disable=SC2059
				printf -- "$sty$txt\\n"
			done
		}
	done
}

# borrowed from https://gist.github.com/cdown/1163649
urlencode() {
	old_lang=$LANG
	LANG=C

	old_lc_collate=$LC_COLLATE
	LC_COLLATE=C

	length="${#1}"
	i=1
	while [ "$i" -le "$length" ]
	do
		c=$(printf '%s' "$1" | cut -c $i)
		case $c in
			[a-zA-Z0-9.~_-]) printf '%s' "$c" ;;
			*) printf '%%%02X' "'$c" ;;
		esac
		i=$((i+1))
	done

	LC_COLLATE=$old_lc_collate
	LANG=$old_lang
}

# Fetches the gemini response from server
# Parameters: proto, host, port and path
# Spec draft is here: https://gemini.circumlunar.space/docs/specification.html
fetch() {
	if [ ! "$1" = "gemini" ]
	then
		echo "Only gemini links are supported."
		echo "Type a key to continue."
		read -r i <&1
		read -r proto host port path << EOF
			$(getprevious)
EOF
		url="$proto://$host:$port/$path"
		url="${url:-$homepage}"
		debug "previous page: $url"
		return
	fi

	# Some setup first
	cols=$(tput cols)
	width=$((cols - (2*margin)))
	debug "text width: $width"

	debug "requesting $1://$2:$3/$4$5"
	printf '\033]2;%s\007' "astro: $2/$4"

	echo "$1 $2 $3 $4 $5" >> "$histfile"

	clear
	certfile=""
	if [ -f "$certdir/$2.crt" ] && [ -f "$certdir/$2.key" ]
	then
		certfile="-cert \"$certdir/$2.crt\" -key \"$certdir/$2.key\""
		debug "using client cert for domain: $certfile"
	fi

	echo "$1://$2:$3/$4$5" | eval openssl s_client \
		-connect "$2:$3" "$certfile" -crlf -quiet \
		-ign_eof 2> /dev/null > "$cachedir/curpage"

	# First line is status and meta information
	read -r status meta < "$cachedir/curpage"
	status="$(echo "$status" | tr -d '\r\n')"
	meta="$(echo "$meta" | tr -d '\r\n')"
	sed -i '1d' "$cachedir/curpage"
	debug "response header: $status $meta"

	# Validate
	case "$status" in
		10)
			echo "Input needed: $meta" >&2
			echo "Please provide the input:" >&2
			read -r input <&1
			url="$1://$2:$3/$4?$(urlencode "$input")"
			return 0
			;;
		11)
			echo "Sensitive input needed: $meta" >&2
			read -r input <&1
			url="$1://$2:$3/$4?$(urlencode "$input")"
			return 0
			;;
		31|32)
			# Redirect
			debug "redirecting to: $meta"

			# shellcheck disable=SC2046
			read -r proto host port path << EOF
				$(oldhost="$2" oldpath="$4" url="$meta" parseurl)
EOF
			url="$proto://$host:$port/$path"
			return 0
			;;
		40)
			echo "Temporary failure" >&2
			return 3
			;;
		41)
			return 4
			;;
		42)
			return 5
			;;
		43)
			return 6
			;;
		44)
			return 7
			;;
		51*)
			echo "Page not found!" >&2
			read -r proto host port path << EOF
				$(getprevious)
EOF
			url="$proto://$host:$port/$path"
			debug "previous page: $url"
			return 0
			;;
		52)
			return 10
			;;
		53)
			return 11
			;;
		59)
			echo "Bad request: $meta" >&2
			return 12
			;;
		60)
			printf "client certificate required, to create a client cert use the following command:\n\n"
			printf "\topenssl req -x509 -newkey rsa:4096 -keyout %s/%s.key -out %s/%s.crt -days 36500 -nodes\n\n" "$certdir" "$2" "$certdir" "$2"
			printf "press 'return' to reload the page or 'b' to go back to the previous page:\n"
			read -r in <&1
			if [ "$in" = "b" ]
			then
				read -r proto host port path << EOF
					$(getprevious)
EOF
				url="$proto://$host:$port/$path"
			else
				url="$1://$2:$3/$4?$5"
			fi
			return 0
			;;
		61)
			return 14
			;;
		62)
			return 15
			;;
	esac

	# Success
	[ -f "$linksfile" ] && rm "$linksfile"

	# Set charset
	charset="$(echo "$meta" | grep -i "charset=" | sed -e 's/.*charset=\([^;]\+\).*/\1/Ig')"
	case "$charset" in
		"iso-8859-1" | "ISO-8859-1") charset="iso8859" ;;
		"utf-8" | "UTF-8" | "") charset="utf8" ;;
		"us-ascii" | "US-ASCII") charset="ascii" ;;
	esac
	debug "charset: $charset"

	case $4 in
		*".gmi"|"") typesetgmi ;;
		*.*) cat ;;
		*) typesetgmi ;;
	esac < "$cachedir/curpage" | LESSCHARSET="$charset" less -k "$LESSKEY" +k -R
	code="$?"
	rm "$cachedir/curpage"

	# Choose what to do next
	debug "pager exit code: $code"
	case "$code" in
		0) exit 0 ;;
		49)
			# Open url
			printf "Type url: "
			read -r url <&1
			;;
		50) url="$1://$2:$3/$4" ;;
		51)
			# Follow link
			cat -n "$linksfile"
			printf "Enter link number: "
			read -r i <&1
			debug "selected $i"
			url="$(sed "${i}q;d" "$linksfile" | cut -d' ' -f1)"
			oldhost="$2"; oldpath="$4"
			;;
		52) 
			read -r proto host port path << EOF
				$(getprevious)
EOF
			url="$proto://$host:$port/$path"
			;;
		53) url="$homepage"; shift $# ;;
		54) 
			echo "Enter description: (optional)"
			read -r desc <&1
			url="$1://$2:$3/$4"
			echo "$url $desc" >> "$bookmarkfile"
			;;
		55)
			cat -n "$bookmarkfile"
			printf "Enter link number: "
			read -r i <&1
			url="$(sed "${i}q;d" "$bookmarkfile" | cut -d' ' -f1)"
			;;
		56)
			url="$1://$2:$3/$4"
			grep -iv "^$url " "$bookmarkfile" > "$cachedir/bookmarks"
			mv "$cachedir/bookmarks" "$bookmarkfile"
			;;
		57)
			newpath=$(echo "/$4" | tr -d '/' | rev | cut -d'/' -f2- | rev)
			url="$1://$2:$3$newpath"
			;;
	esac

	debug "new url: $url"
}

# Execution
export LESS='-P Keys\: qgrubosHmMK, to see a description run astro -h'

# First request
url="${args:-$homepage}"

while :
do
	# shellcheck disable=SC2046
	fetch $(parseurl)
done