From 8629f74d16c48d3b6ada1a702154cded23eadcee Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Wed, 3 Mar 2021 17:57:07 -0600 Subject: Still. More. Documentation. --- bollux | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 162 insertions(+), 45 deletions(-) diff --git a/bollux b/bollux index 46c381b..2de37ab 100755 --- a/bollux +++ b/bollux @@ -99,13 +99,6 @@ bollux_quit() { # SIGINT is C-c, and I want to make sure bollux quits when it's typed. trap bollux_quit SIGINT -# Bash built-in replacement for `sleep` -# -# [1]: #use-read-as-an-alternative-to-the-sleep-command -sleep() { # sleep SECONDS - read -rt "$1" <> <(:) || : -} - # Trim leading and trailing whitespace from a string. # # [1]: #trim-leading-and-trailing-white-space-from-string @@ -177,6 +170,29 @@ prompt() { # prompt [-u] PROMPT [READ_ARGS...] "${read_cmd[@]}" <(:) || : +} + # MAIN BOLLUX DISPATCH FUNCTIONS ############################################### # Main entry point into `bollux'. @@ -772,71 +788,140 @@ gemini_response() { # gemini_response URL run blastoff "$meta" # TODO: confirm redirect ;; (4*) # TEMPORARY ERROR + # Since the 4* codes ([3], Appendix 1) are all server issues, + # bollux can treat them all basically the same. This is an area + # that could use some expansion. + local desc="Temporary error" + case "$code" in + (41) desc+=" (server unavailable)" ;; + (42) desc+=" (CGI error)" ;; + (43) desc+=" (proxy error)" ;; + (44) desc+=" (slow down)" ;; # could be particularly improved + esac REDIRECTS=0 - die "$((100 + code))" "Temporary error [$code]: $meta" + die "$((100 + code))" "$desc [$code]: $meta" ;; (5*) # PERMANENT ERROR + # The situation with the 5* codes is basically similar to the 4* + # codes. It could maybe use more thought as to what behavior to + # implement. Maybe adding the (bad) requests to history, + # subject to configuration? + local desc="Permanent failure" + case "$code" in + (51) desc+=" (not found)" ;; + (52) desc+=" (gone)" ;; + (53) desc+=" (proxy request refused)" ;; + # For some reason, codes 54--58 inclusive aren't used. + (59) desc+=" (bad request)" ;; + esac REDIRECTS=0 - die "$((100 + code))" "Permanent error [$code]: $meta" + die "$((100 + code))" "$desc [$code]: $meta" ;; - (6*) # CERTIFICATE ERROR + (6*) # CERTIFICATE ERROR (TODO) + # Dealing with certificates is honestly the most important + # feature missing from bollux to get it to 1.0. Right now, + # bollux deals with 6* status codes identically to 4* and 5* + # codes. This is not ideal, in the slightest. + local desc="Client certificate required" + case "$code" in + (61) desc+=" (certificate not authorized)" ;; + (62) desc+=" (certificate not valid)" ;; + esac REDIRECTS=0 log d "Not implemented: Client certificates" die "$((100 + code))" "[$code] $meta" ;; - (*) + (*) # UNKNOWN + # Just in case we get a weird, un-spec-compliant status code. [[ -z "${code-}" ]] && die 100 "Empty response code." die "$((100 + code))" "Unknown response code: $code." ;; esac } -# GOPHER +# GOPHER ####################################################################### # https://tools.ietf.org/html/rfc1436 protocol # https://tools.ietf.org/html/rfc4266 url +# +# Gopher is the grand-daddy of gemini (or maybe just weird uncle? hm..), +# invented in 1991 as a fancier FTP. There's been a sort of resurgence in it as +# a consequence of the shittifying of the WWW, but it's shown its age (which is +# why Gemini was born). But why am I telling you this? You're reading the +# source code of a Gemini browser! You're a meganerd just like me. Welcome to +# the club, kid. +# +# Since gopher is so old, it actually has two RFCs: RFC 1436 [6] for the +# protocol itself, and RFC 4266 [7] for the URL format (gopher predates the +# URL!). However, requesting and handling responses is still fundamentally the +# same to gemini, so it was pretty easy to implement this. I don't think bollux +# handles all the possible item types, but it should get the main ones. +# +################################################################################ + +# Request a resource. gopher_request() { # gopher_request URL - local url server port type path - url="$1" - port=70 + local url="$1" - # RFC 4266 + # [7] § 2.1 [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]] - server="${BASH_REMATCH[1]}" - port="${BASH_REMATCH[3]:-70}" - type="${BASH_REMATCH[6]:-1}" - path="${BASH_REMATCH[7]}" - + local server="${BASH_REMATCH[1]}" \ + port="${BASH_REMATCH[3]:-$BOLLUX_GOPHER_PORT}" \ + type="${BASH_REMATCH[6]:-1}" \ + path="${BASH_REMATCH[7]}" log d "URL='$url' SERVER='$server' TYPE='$type' PATH='$path'" + # Bash has this really neat feature where it can open a TCP socket + # directly. bollux uses that feature here to ask the server for the + # resource and then `passthru' it to the next thing. exec 9<>"/dev/tcp/$server/$port" printf '%s\r\n' "$path" >&9 passthru <&9 } +# Handle a server response. gopher_response() { # gopher_response URL - local url pre type cur_server - pre=false - url="$1" - # RFC 4266 + local url="$1" pre=false + # [7] § 2.1 + # + # Note that this duplicates the code in `gopher_request'. There might + # be a good way to thread this data through so that it's not computed + # twice. [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]] - cur_server="${BASH_REMATCH[1]}" - type="${BASH_REMATCH[6]:-1}" + local cur_server="${BASH_REMATCH[1]}" + local type="${BASH_REMATCH[6]:-1}" run history_append "$url" "" # gopher doesn't really have titles, huh - log d "TYPE='$type'" - + # Gopher has a concept of 'line types', or maybe 'item types' -- + # basically, each line in a gophermap starts with a character, its type, + # and then is followed by a series of tab-separated fields describing + # where that type is and how to display it. The full list of original + # line types can be found in [6] § 3.8, though the types have also been + # extended over the years. Since bollux can only display types that are + # text-ish, it only concerns itself with those in this case statement. + # All the others are simply downloaded. case "$type" in - (0) # text + (0) # Item is a file + # Since gopher doesn't send MIME-type information in-band, we + # just assume it's text/plain, and try to convert it later to + # UTF-8 with `iconv'. run display text/plain ;; - (1) # menu + (1) # Item is a directory [gophermap] + # Since I've already written all the code to typeset gemini + # well, it's easy to convert a gophermap to text/gemini and + # display it than to write a whole new gophermap typesetter. run gopher_convert | run display text/gemini ;; - (3) # failure + (3) # Error + # I don't know all the gopher error cases, and the spec is + # pretty quiet on them. So bollux just signals failure and + # bails. die 203 "GOPHER: failed" ;; - (7) # search + (7) # Item is an Index-Search server + # Gopher search queries are separated from their resources by a + # TAB. It's wild. if [[ "$url" =~ $'\t' ]]; then run gopher_convert | run display text/gemini else @@ -844,16 +929,42 @@ gopher_response() { # gopher_response URL run blastoff "$url $REPLY" fi ;; - (*) # something else + (*) # Anything else + # The list at [6] § 3.8 includes the following (noted where it + # might be good to differently handle them in the future): + # + # 2. Item is a CSO phone-book server ***** + # 4. Item is a BinHexed Macintosh file + # 5. Item is DOS binary archive of some sort + # 6. Item is a UNIX uuencoded file + # 8. Item points to a text-based telnet session ***** + # 9. Item is a binary file! [exclamation point sic. -- ed.] + # +. Item is a redundant server ***** + # T. Item points to a text-based tn3270 session + # g. Item is a GIF format graphics file + # I. Item is some kind of image file + # + # As mentioned, there are other line types floating around as + # well. Since I don't browse gopher much, there's not much + # personal motivation to extend `gopher_response'; however pull + # requests are always welcome. run download "$url" ;; esac } -# convert gophermap to text/gemini (probably naive) +# Convert a gophermap naively to a gemini page. +# +# Based strongly on [8], but bash-ified. Due to the properties of link lines in +# gemini, many of the item types in `gemini_reponse' can be linked to the proper +# protocol handlers here -- so if a user is trying to reach a TCP link through +# gopher, bollux won't have to handle it, for example.* +# +# * Ideally -- right now, bollux simply errors out on all unknown protocols. +# More research needs to be done into how to farm out to `xdg-open' or a +# similar generic opener. gopher_convert() { local type label path server port regex - # [GOPHER_GEMINI] while IFS= read -r; do printf -v regex '(.)([^\t]*)(\t([^\t]*)\t([^\t]*)\t([^\t]*))?' if [[ "$REPLY" =~ $regex ]]; then @@ -922,13 +1033,12 @@ gopher_convert() { exec 9>&- } - -# 'cat' but in pure bash -passthru() { - while IFS= read -r; do - printf '%s\n' "$REPLY" - done -} +# HANDLING CONTENT ############################################################# +# +# After fetching the resource requested by the user, bollux needs to display or +# otherwise 'give' the resource to the user for consumption. +# +################################################################################ # display the fetched content display() { # display METADATA [TITLE] @@ -959,8 +1069,15 @@ display() { # display METADATA [TITLE] case "$mime" in (text/*) set_title "$title${title:+ - }bollux" - # render ANSI color escapes and don't wrap pre-formatted blocks - less_cmd=(less -RS) + # Build the `less' command + less_cmd=(less) + # Render ANSI color escapes ONLY (as opposed to `-r', which + # renders all escapes) + less_cmd+=(-R) + # Don't wrap text. `fold_line' takes care of wrapping normal + # text, and pre-formatted text shouldn't wrap. + less_cmd+=(-S) + # Load the keybindings (see `lesskey'). mklesskey && less_cmd+=(-k "$BOLLUX_LESSKEY") local helpline="${KEY_OPEN}:open, " helpline+="${KEY_GOTO}/" -- cgit 1.4.1-21-gabe81