dillo-gemini/gemini.filter.dpi

321 lines
6.7 KiB
Bash
Executable File

#!/bin/bash
# dillo-gemini
# © 2020 Charles E. Lehner
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
# Note: "read -d" is a Bashism
read -d '>' auth
read -d '>' cmd
case "$cmd" in
"<cmd='open_url' url='"*);;
*) echo $cmd; exit;;
esac
url=${cmd#"<cmd='open_url' url='"}
url=${url%"' '"}
serve_404() {
printf "<cmd='start_send_page' url='' '>\n"
printf "Content-type: text/plain\r\n\r\n"
echo Not found
}
render_gemini() {
printf "Content-type: text/html\r\n\r\n"
if command -v ansi2html >/dev/null
then ansi2html
else cat
fi | awk '
function escape_html(str) {
gsub(/&/, "\\&amp;", str)
gsub(/</, "\\&lt;", str)
gsub(/>/, "\\&gt;", str)
return str
}
BEGIN {
print "<!doctype html><body><style>"\
"h1, h2, h3 { margin: 0; }\n"\
"</style>"
}
{
sub(/\r$/, "")
}
/^```/ {
if (!in_literal) {
in_literal = 1
print "<pre>"
} else {
in_literal = 0
print "</pre>"
}
next
}
in_literal {
print escape_html($0)
next
}
in_list && !/^\*/ {
in_list = 0
print "</ul>"
}
/^\*/ {
if (!in_list) {
in_list = 1
printf "<ul>"
}
match($0, /^\*+[ \t]*/)
text = substr($0, RLENGTH+1)
printf "<li>%s</li>\n", escape_html(text)
next
}
/^#+/ {
match($0, /^#+/)
tag = "h" RLENGTH
match($0, /^(#+[ \t]*)/)
text = substr($0, RLENGTH+1)
html = escape_html(text)
printf "<%s style=\"font:sans-serif\">%s</%s>\n", tag, html, tag
next
}
/^>/ {
match($0, /^>+[ \t]*/)
text = substr($0, RLENGTH+1)
printf "<blockquote>%s</blockquote>\n", escape_html(text)
next
}
/^=>/ {
match($0, /^=>[ \t]*/)
href = substr($0, RLENGTH+1)
if (match(href, /^[^ \t]+/)) {
text = substr(href, RLENGTH+2)
href = substr(href, 0, RLENGTH)
}
match($0, /^=>[ \t]+/)
prefix = substr($0, 3, RLENGTH-3)
match(text, /^[ \t]+/)
text = substr(text, RLENGTH+1)
sub(/:1965/, "", href)
if (!text) {
text = href
}
if (match(href, /^gemini:\/\/[^/]+$/)) {
href = href "/"
}
sub(/^\t+/, "", prefix)
html = escape_html(text)
printf "<div>%s<a href=\"%s\">%s</a></div>\n", prefix, href, html
next
}
/^$/ {
print "<br>"
next
}
{
printf "<div>%s</div>\n", escape_html($0)
}
END {
print "</body>"
}
'
}
send_status_msg() {
printf "<cmd='send_status_message' msg='%s' '>" "$*"
}
serve_status_not_supported() {
printf "<cmd='start_send_page' url='' '>\n"
printf "Content-type: text/plain\r\n\r\n"
echo Status not implemented: $1
echo $2
}
serve_missing_status() {
printf "<cmd='start_send_page' url='' '>\n"
printf "Content-type: text/plain\r\n\r\n"
echo Empty status response. $2
}
serve_input() {
printf "<cmd='start_send_page' url='' '>\n"
url=$1
prompt=$2
# TODO: html-escape prompt
printf "Content-type: text/html\r\n\r\n"
cat <<-EOF
<!doctype html>
<html>
<head>
<title>Input</title>
</head>
<body>
<form action="gemini:input:$url" method=get>
<h3>$prompt</h3>
<input name=q style='width:100%'>
<input type=submit>
</form>
</body>
</html>
EOF
}
serve_success() {
printf "<cmd='start_send_page' url='' '>\n"
type=$1
case "$type" in
text/gemini*) render_gemini;;
application/xml|application/*+xml) printf "Content-type: text/xml\r\n\r\n"; cat;;
*) printf "Content-type: %s\r\n\r\n" "$type"; cat;;
esac
}
serve_redirect() {
url=$1
send_status_msg "Redirected"
printf "<cmd='start_send_page' url='' '>\n"
printf "Content-type: text/html\r\n\r\n"
# TODO: html-escape url
cat <<-EOF
<!doctype html>
<html>
<head>
<title>Redirect to $url</title>
</head>
<body>
<h3>Redirect to <a href="$url">$url</a></3>
</body>
</html>
EOF
}
serve_real_redirect() {
send_status_msg "Redirected"
printf "<cmd='start_send_page' url='' '>\n"
printf "Content-type: text/html\r\n\r\n"
# TODO: html-escape url
cat <<-EOF
<!doctype html>
<html>
<head>
<title>Redirecting to $url</title>
<meta http-equiv="Refresh" content="0; url=$url" />
</head>
<body>
</body>
</html>
EOF
}
serve_error() {
status=$1
meta=$2
send_status_msg "Request failed"
printf "<cmd='start_send_page' url='' '>\n"
printf "Content-type: text/html\r\n\r\n"
cat <<-EOF
<!doctype html>
<html>
<head>
<title>Request failed</title>
</head>
<body>
<h3>Request failed: $status</h3>
<code>$meta</code>
</body>
</html>
EOF
}
serve_fail() {
meta="$1"
send_status_msg "Client certificate required"
printf "<cmd='start_send_page' url='' '>\n"
printf "Content-type: text/html\r\n\r\n"
cat <<-EOF
<!doctype html>
<html>
<head>
<title>Client certificate required</title>
</head>
<body>
<h3>Client certificate required</h3>
<p>Not implemented!</p>
<code>$meta</code>
</body>
</html>
EOF
}
serve_gemini_input() {
url=${1#gemini:input:}
# gemini:input://REQUEST?q=INPUT -> gemini://REQUEST?INPUT
url_no_query=${url%%?q=*}
if [ "$url_no_query" != "$url" ]; then
url="${url_no_query}?${url#*?q=}"
fi
url=$(printf "%s" "$url" | sed 's/\+/%20/g')
serve_real_redirect "$url"
}
blobs_dir=${ssb_path:-~/.${ssb_appname:-ssb}}/blobs
serve_gemini() {
url=${1%%#*}
url_noquery=${url%%\?*}
query=${url##*\?}
if [ "$query" = "$url_noquery" ]
then query=
else query="?$query"
fi
url_scheme_relative=${url_noquery#gemini://}
hostname=${url_scheme_relative%%/*}
path=${url_scheme_relative#*/}
if [ "$path" = "$hostname" ]; then path=; fi
host=${hostname%%:*}
port=${hostname##*:}
url="gemini://$hostname/$path$query"
if [ "$host" = "$port" ]; then port=1965; fi
send_status_msg "Sending request..."
printf "%s\r\n" "$url" | openssl s_client -quiet -connect "$host:$port" | serve_gemini_response "$url"
}
serve_gemini_response() {
url=$1
read status meta
send_status_msg "Status: $status"
meta=$(echo "$meta" | sed 's/\s*$//')
mkdir -p $blobs_dir/tmp ~/.dillo/gemini
tmp=$(mktemp $blobs_dir/tmp/XXXXXXXXXXXX)
tee "$tmp" | case "$status" in
1*) serve_input "$url" "$meta";;
2*) serve_success "$meta";;
3*) serve_redirect "$meta";;
4*) serve_error "$status" "$meta";;
5*) serve_error "$meta";;
#6*) serve_client_cert_required "$meta";;
'') serve_missing_status "$meta";;
*) serve_status_not_supported "$status" "$meta";;
esac
dest=$blobs_dir/sha256/$(sha256sum "$tmp" | awk '{print substr($1, 1, 2) "/" substr($1, 3)}')
if test -e "$dest" && cmp -s "$tmp" "$dest"
then
# unchanged resource
rm "$tmp"
else
mkdir -p "${dest%/*}"
mv "$tmp" "$dest"
fi
printf "%s %s %s %s\n" "$(date -u +%FT%TZ)" "$dest" "$url" "$status $meta" >>~/.dillo/gemini/history.txt
}
case "$url" in
gemini:input:*) serve_gemini_input "$url";;
gemini:*) serve_gemini "$url";;
*) serve_404;;
esac