about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xbollux207
1 files 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() {
99# SIGINT is C-c, and I want to make sure bollux quits when it's typed. 99# SIGINT is C-c, and I want to make sure bollux quits when it's typed.
100trap bollux_quit SIGINT 100trap bollux_quit SIGINT
101 101
102# Bash built-in replacement for `sleep`
103#
104# [1]: #use-read-as-an-alternative-to-the-sleep-command
105sleep() { # sleep SECONDS
106 read -rt "$1" <> <(:) || :
107}
108
109# Trim leading and trailing whitespace from a string. 102# Trim leading and trailing whitespace from a string.
110# 103#
111# [1]: #trim-leading-and-trailing-white-space-from-string 104# [1]: #trim-leading-and-trailing-white-space-from-string
@@ -177,6 +170,29 @@ prompt() { # prompt [-u] PROMPT [READ_ARGS...]
177 "${read_cmd[@]}" </dev/tty "$@" 170 "${read_cmd[@]}" </dev/tty "$@"
178} 171}
179 172
173
174# Bash built-in replacement for `cat'
175#
176# One of the more pedantic bits of bollux (is 'pedantic' the right word?) --
177# `cat' is more than likely installed on any system with bash, so this function
178# is really just here so I can say that bollux is written as purely in bash as
179# possible.
180passthru() {
181 while IFS= read -r; do
182 printf '%s\n' "$REPLY"
183 done
184}
185
186# Bash built-in replacement for `sleep'
187#
188# The commentary for `passthru' applies here as well, though I didn't write this
189# function -- Dylan Araps did.
190#
191# [1]: #use-read-as-an-alternative-to-the-sleep-command
192sleep() { # sleep SECONDS
193 read -rt "$1" <> <(:) || :
194}
195
180# MAIN BOLLUX DISPATCH FUNCTIONS ############################################### 196# MAIN BOLLUX DISPATCH FUNCTIONS ###############################################
181 197
182# Main entry point into `bollux'. 198# Main entry point into `bollux'.
@@ -772,71 +788,140 @@ gemini_response() { # gemini_response URL
772 run blastoff "$meta" # TODO: confirm redirect 788 run blastoff "$meta" # TODO: confirm redirect
773 ;; 789 ;;
774 (4*) # TEMPORARY ERROR 790 (4*) # TEMPORARY ERROR
791 # Since the 4* codes ([3], Appendix 1) are all server issues,
792 # bollux can treat them all basically the same. This is an area
793 # that could use some expansion.
794 local desc="Temporary error"
795 case "$code" in
796 (41) desc+=" (server unavailable)" ;;
797 (42) desc+=" (CGI error)" ;;
798 (43) desc+=" (proxy error)" ;;
799 (44) desc+=" (slow down)" ;; # could be particularly improved
800 esac
775 REDIRECTS=0 801 REDIRECTS=0
776 die "$((100 + code))" "Temporary error [$code]: $meta" 802 die "$((100 + code))" "$desc [$code]: $meta"
777 ;; 803 ;;
778 (5*) # PERMANENT ERROR 804 (5*) # PERMANENT ERROR
805 # The situation with the 5* codes is basically similar to the 4*
806 # codes. It could maybe use more thought as to what behavior to
807 # implement. Maybe adding the (bad) requests to history,
808 # subject to configuration?
809 local desc="Permanent failure"
810 case "$code" in
811 (51) desc+=" (not found)" ;;
812 (52) desc+=" (gone)" ;;
813 (53) desc+=" (proxy request refused)" ;;
814 # For some reason, codes 54--58 inclusive aren't used.
815 (59) desc+=" (bad request)" ;;
816 esac
779 REDIRECTS=0 817 REDIRECTS=0
780 die "$((100 + code))" "Permanent error [$code]: $meta" 818 die "$((100 + code))" "$desc [$code]: $meta"
781 ;; 819 ;;
782 (6*) # CERTIFICATE ERROR 820 (6*) # CERTIFICATE ERROR (TODO)
821 # Dealing with certificates is honestly the most important
822 # feature missing from bollux to get it to 1.0. Right now,
823 # bollux deals with 6* status codes identically to 4* and 5*
824 # codes. This is not ideal, in the slightest.
825 local desc="Client certificate required"
826 case "$code" in
827 (61) desc+=" (certificate not authorized)" ;;
828 (62) desc+=" (certificate not valid)" ;;
829 esac
783 REDIRECTS=0 830 REDIRECTS=0
784 log d "Not implemented: Client certificates" 831 log d "Not implemented: Client certificates"
785 die "$((100 + code))" "[$code] $meta" 832 die "$((100 + code))" "[$code] $meta"
786 ;; 833 ;;
787 (*) 834 (*) # UNKNOWN
835 # Just in case we get a weird, un-spec-compliant status code.
788 [[ -z "${code-}" ]] && die 100 "Empty response code." 836 [[ -z "${code-}" ]] && die 100 "Empty response code."
789 die "$((100 + code))" "Unknown response code: $code." 837 die "$((100 + code))" "Unknown response code: $code."
790 ;; 838 ;;
791 esac 839 esac
792} 840}
793 841
794# GOPHER 842# GOPHER #######################################################################
795# https://tools.ietf.org/html/rfc1436 protocol 843# https://tools.ietf.org/html/rfc1436 protocol
796# https://tools.ietf.org/html/rfc4266 url 844# https://tools.ietf.org/html/rfc4266 url
845#
846# Gopher is the grand-daddy of gemini (or maybe just weird uncle? hm..),
847# invented in 1991 as a fancier FTP. There's been a sort of resurgence in it as
848# a consequence of the shittifying of the WWW, but it's shown its age (which is
849# why Gemini was born). But why am I telling you this? You're reading the
850# source code of a Gemini browser! You're a meganerd just like me. Welcome to
851# the club, kid.
852#
853# Since gopher is so old, it actually has two RFCs: RFC 1436 [6] for the
854# protocol itself, and RFC 4266 [7] for the URL format (gopher predates the
855# URL!). However, requesting and handling responses is still fundamentally the
856# same to gemini, so it was pretty easy to implement this. I don't think bollux
857# handles all the possible item types, but it should get the main ones.
858#
859################################################################################
860
861# Request a resource.
797gopher_request() { # gopher_request URL 862gopher_request() { # gopher_request URL
798 local url server port type path 863 local url="$1"
799 url="$1"
800 port=70
801 864
802 # RFC 4266 865 # [7] § 2.1
803 [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]] 866 [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]]
804 server="${BASH_REMATCH[1]}" 867 local server="${BASH_REMATCH[1]}" \
805 port="${BASH_REMATCH[3]:-70}" 868 port="${BASH_REMATCH[3]:-$BOLLUX_GOPHER_PORT}" \
806 type="${BASH_REMATCH[6]:-1}" 869 type="${BASH_REMATCH[6]:-1}" \
807 path="${BASH_REMATCH[7]}" 870 path="${BASH_REMATCH[7]}"
808
809 log d "URL='$url' SERVER='$server' TYPE='$type' PATH='$path'" 871 log d "URL='$url' SERVER='$server' TYPE='$type' PATH='$path'"
810 872
873 # Bash has this really neat feature where it can open a TCP socket
874 # directly. bollux uses that feature here to ask the server for the
875 # resource and then `passthru' it to the next thing.
811 exec 9<>"/dev/tcp/$server/$port" 876 exec 9<>"/dev/tcp/$server/$port"
812 printf '%s\r\n' "$path" >&9 877 printf '%s\r\n' "$path" >&9
813 passthru <&9 878 passthru <&9
814} 879}
815 880
881# Handle a server response.
816gopher_response() { # gopher_response URL 882gopher_response() { # gopher_response URL
817 local url pre type cur_server 883 local url="$1" pre=false
818 pre=false 884 # [7] § 2.1
819 url="$1" 885 #
820 # RFC 4266 886 # Note that this duplicates the code in `gopher_request'. There might
887 # be a good way to thread this data through so that it's not computed
888 # twice.
821 [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]] 889 [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]]
822 cur_server="${BASH_REMATCH[1]}" 890 local cur_server="${BASH_REMATCH[1]}"
823 type="${BASH_REMATCH[6]:-1}" 891 local type="${BASH_REMATCH[6]:-1}"
824 892
825 run history_append "$url" "" # gopher doesn't really have titles, huh 893 run history_append "$url" "" # gopher doesn't really have titles, huh
826 894
827 log d "TYPE='$type'" 895 # Gopher has a concept of 'line types', or maybe 'item types' --
828 896 # basically, each line in a gophermap starts with a character, its type,
897 # and then is followed by a series of tab-separated fields describing
898 # where that type is and how to display it. The full list of original
899 # line types can be found in [6] § 3.8, though the types have also been
900 # extended over the years. Since bollux can only display types that are
901 # text-ish, it only concerns itself with those in this case statement.
902 # All the others are simply downloaded.
829 case "$type" in 903 case "$type" in
830 (0) # text 904 (0) # Item is a file
905 # Since gopher doesn't send MIME-type information in-band, we
906 # just assume it's text/plain, and try to convert it later to
907 # UTF-8 with `iconv'.
831 run display text/plain 908 run display text/plain
832 ;; 909 ;;
833 (1) # menu 910 (1) # Item is a directory [gophermap]
911 # Since I've already written all the code to typeset gemini
912 # well, it's easy to convert a gophermap to text/gemini and
913 # display it than to write a whole new gophermap typesetter.
834 run gopher_convert | run display text/gemini 914 run gopher_convert | run display text/gemini
835 ;; 915 ;;
836 (3) # failure 916 (3) # Error
917 # I don't know all the gopher error cases, and the spec is
918 # pretty quiet on them. So bollux just signals failure and
919 # bails.
837 die 203 "GOPHER: failed" 920 die 203 "GOPHER: failed"
838 ;; 921 ;;
839 (7) # search 922 (7) # Item is an Index-Search server
923 # Gopher search queries are separated from their resources by a
924 # TAB. It's wild.
840 if [[ "$url" =~ $'\t' ]]; then 925 if [[ "$url" =~ $'\t' ]]; then
841 run gopher_convert | run display text/gemini 926 run gopher_convert | run display text/gemini
842 else 927 else
@@ -844,16 +929,42 @@ gopher_response() { # gopher_response URL
844 run blastoff "$url $REPLY" 929 run blastoff "$url $REPLY"
845 fi 930 fi
846 ;; 931 ;;
847 (*) # something else 932 (*) # Anything else
933 # The list at [6] § 3.8 includes the following (noted where it
934 # might be good to differently handle them in the future):
935 #
936 # 2. Item is a CSO phone-book server *****
937 # 4. Item is a BinHexed Macintosh file
938 # 5. Item is DOS binary archive of some sort
939 # 6. Item is a UNIX uuencoded file
940 # 8. Item points to a text-based telnet session *****
941 # 9. Item is a binary file! [exclamation point sic. -- ed.]
942 # +. Item is a redundant server *****
943 # T. Item points to a text-based tn3270 session
944 # g. Item is a GIF format graphics file
945 # I. Item is some kind of image file
946 #
947 # As mentioned, there are other line types floating around as
948 # well. Since I don't browse gopher much, there's not much
949 # personal motivation to extend `gopher_response'; however pull
950 # requests are always welcome.
848 run download "$url" 951 run download "$url"
849 ;; 952 ;;
850 esac 953 esac
851} 954}
852 955
853# convert gophermap to text/gemini (probably naive) 956# Convert a gophermap naively to a gemini page.
957#
958# Based strongly on [8], but bash-ified. Due to the properties of link lines in
959# gemini, many of the item types in `gemini_reponse' can be linked to the proper
960# protocol handlers here -- so if a user is trying to reach a TCP link through
961# gopher, bollux won't have to handle it, for example.*
962#
963# * Ideally -- right now, bollux simply errors out on all unknown protocols.
964# More research needs to be done into how to farm out to `xdg-open' or a
965# similar generic opener.
854gopher_convert() { 966gopher_convert() {
855 local type label path server port regex 967 local type label path server port regex
856 # [GOPHER_GEMINI]
857 while IFS= read -r; do 968 while IFS= read -r; do
858 printf -v regex '(.)([^\t]*)(\t([^\t]*)\t([^\t]*)\t([^\t]*))?' 969 printf -v regex '(.)([^\t]*)(\t([^\t]*)\t([^\t]*)\t([^\t]*))?'
859 if [[ "$REPLY" =~ $regex ]]; then 970 if [[ "$REPLY" =~ $regex ]]; then
@@ -922,13 +1033,12 @@ gopher_convert() {
922 exec 9>&- 1033 exec 9>&-
923} 1034}
924 1035
925 1036# HANDLING CONTENT #############################################################
926# 'cat' but in pure bash 1037#
927passthru() { 1038# After fetching the resource requested by the user, bollux needs to display or
928 while IFS= read -r; do 1039# otherwise 'give' the resource to the user for consumption.
929 printf '%s\n' "$REPLY" 1040#
930 done 1041################################################################################
931}
932 1042
933# display the fetched content 1043# display the fetched content
934display() { # display METADATA [TITLE] 1044display() { # display METADATA [TITLE]
@@ -959,8 +1069,15 @@ display() { # display METADATA [TITLE]
959 case "$mime" in 1069 case "$mime" in
960 (text/*) 1070 (text/*)
961 set_title "$title${title:+ - }bollux" 1071 set_title "$title${title:+ - }bollux"
962 # render ANSI color escapes and don't wrap pre-formatted blocks 1072 # Build the `less' command
963 less_cmd=(less -RS) 1073 less_cmd=(less)
1074 # Render ANSI color escapes ONLY (as opposed to `-r', which
1075 # renders all escapes)
1076 less_cmd+=(-R)
1077 # Don't wrap text. `fold_line' takes care of wrapping normal
1078 # text, and pre-formatted text shouldn't wrap.
1079 less_cmd+=(-S)
1080 # Load the keybindings (see `lesskey').
964 mklesskey && less_cmd+=(-k "$BOLLUX_LESSKEY") 1081 mklesskey && less_cmd+=(-k "$BOLLUX_LESSKEY")
965 local helpline="${KEY_OPEN}:open, " 1082 local helpline="${KEY_OPEN}:open, "
966 helpline+="${KEY_GOTO}/" 1083 helpline+="${KEY_GOTO}/"