diff options
Diffstat (limited to 'bollux.sh')
-rwxr-xr-x | bollux.sh | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/bollux.sh b/bollux.sh new file mode 100755 index 0000000..84786b6 --- /dev/null +++ b/bollux.sh | |||
@@ -0,0 +1,264 @@ | |||
1 | #!/usr/bin/env bash | ||
2 | # bollux: bash gemini client | ||
3 | # Author: Case Duckworth <acdw@acdw.net> | ||
4 | # License: MIT | ||
5 | # Version: -0.9 | ||
6 | |||
7 | PRGN="${0##*/}" | ||
8 | PORT=1965 | ||
9 | LOG_LEVEL=3 # higher=more important. | ||
10 | |||
11 | clean() { | ||
12 | # '\e[?7h': re-enable line wrapping | ||
13 | # '\e[2J': clear the screen | ||
14 | # '\e[;r': reset the scroll area | ||
15 | # '\e[?1049l': swap back to primary screen | ||
16 | printf '\e[?7h\e[2J\e[;r\e[?1049l' | ||
17 | exit | ||
18 | } | ||
19 | |||
20 | refresh() { | ||
21 | # grab the terminal size | ||
22 | shopt -s checkwinsize | ||
23 | ( | ||
24 | : | ||
25 | : | ||
26 | ) | ||
27 | # '\e[?1049h': Swap to the alternate buffer. | ||
28 | # '\e[?7l': Disable line wrapping. | ||
29 | # '\e[2J': Clear the screen. | ||
30 | # '\e[3;%sr': Set the scroll area. | ||
31 | # '\e[999H': Move the cursor to the bottom. | ||
32 | printf '\e[?1049h\e[?7l\e[2J\e[3;%sr\e[999H' "$((LINES - 1))" | ||
33 | } | ||
34 | |||
35 | resize() { | ||
36 | refresh | ||
37 | # '\e7': Save the cursor position. | ||
38 | # '\e[?25l': Hide the cursor. | ||
39 | # '\r': Move the cursor to column 0. | ||
40 | # '\e[999B': Move the cursor to the bottom. | ||
41 | # '\e[A': Move the cursor up a line. | ||
42 | printf '\e7\e[?25l\r\e[999B\e[A' | ||
43 | } | ||
44 | |||
45 | bollux() { | ||
46 | if [[ -n "$1" ]]; then | ||
47 | loc="$1" | ||
48 | else | ||
49 | read -rp "GO> " loc | ||
50 | fi | ||
51 | |||
52 | log 2 "location: $loc" | ||
53 | log 2 "address: $(address "$loc")" | ||
54 | log 2 "server: $(server "$loc")" | ||
55 | |||
56 | address "$loc" | | ||
57 | download "$(server "$loc")" | | ||
58 | handle | ||
59 | } | ||
60 | |||
61 | log() { | ||
62 | case "$1" in | ||
63 | [0-9]*) | ||
64 | lvl="$1" | ||
65 | shift | ||
66 | ;; | ||
67 | *) lvl=5 ;; | ||
68 | esac | ||
69 | if ((lvl >= LOG_LEVEL)); then | ||
70 | if [[ "$2" == - ]]; then | ||
71 | while IFS= read -r line; do | ||
72 | printf '\e[33m%s\e[0m:\t%s\n' "$PRGN" "$line" >&2 | ||
73 | done | ||
74 | else | ||
75 | printf '\e[34m%s\e[0m:\t%s\n' "bollux" "$*" >&2 | ||
76 | fi | ||
77 | fi | ||
78 | } | ||
79 | |||
80 | download() { | ||
81 | # usage: | ||
82 | # echo REQUEST | download SERVER | ||
83 | # download SERVER REQUEST | ||
84 | serv="$1" | ||
85 | req= | ||
86 | if (($# == 2)); then | ||
87 | req="$2" | ||
88 | else | ||
89 | req="$(cat)" | ||
90 | fi | ||
91 | t="$(mktemp)" | ||
92 | openssl s_client -crlf -ign_eof -quiet -connect "$serv" <<<"$req" 2>"$t" | ||
93 | log 1 <"$t" | ||
94 | rm "$t" | ||
95 | } | ||
96 | |||
97 | address() { | ||
98 | addr="$1" | ||
99 | if [[ "$addr" != gemini://* ]]; then | ||
100 | addr="gemini://$addr" | ||
101 | fi | ||
102 | echo "$addr" | trim | ||
103 | } | ||
104 | |||
105 | server() { | ||
106 | serv="${1#*://}" | ||
107 | serv="${serv%%/*}" | ||
108 | if [[ "$serv" != *:* ]]; then | ||
109 | serv="$serv:$PORT" | ||
110 | fi | ||
111 | echo "$serv" | trim | ||
112 | } | ||
113 | |||
114 | trim() { | ||
115 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | ||
116 | } | ||
117 | |||
118 | display() { | ||
119 | echo | ||
120 | cat | ||
121 | echo | ||
122 | } | ||
123 | |||
124 | handle() { | ||
125 | # cf. gemini://gemini.circumlunar.space/docs/spec-spec.txt | ||
126 | resp="$(cat)" | ||
127 | head="$(head -n1 <<<"$resp")" | ||
128 | body="$(tail -n+2 <<<"$resp")" | ||
129 | stat="$(awk '{print $1}' <<<"$head")" | ||
130 | smsg="$( | ||
131 | awk '{for(i=2;i<=NF;i++)printf "%s ",$i;printf "\n";}' <<<"$head" | ||
132 | )" | ||
133 | |||
134 | log "[$stat] $smsg" | ||
135 | |||
136 | case "$stat" in | ||
137 | 10) # INPUT | ||
138 | # As per definition of single-digit code 1 in 1.3.2. | ||
139 | NOT_IMPLEMENTED | ||
140 | ;; | ||
141 | 20) # SUCCESS | ||
142 | # As per definition of single-digit code 2 in 1.3.2. | ||
143 | display <<<"$body" | ||
144 | ;; | ||
145 | 21) # SUCCESS - END OF CLIENT CERTIFICATE SESSION | ||
146 | # The request was handled successfully and a response body will | ||
147 | # follow the response header. The <META> line is a MIME media | ||
148 | # type which applies to the response body. In addition, the | ||
149 | # server is signalling the end of a transient client certificate | ||
150 | # session which was previously initiated with a status 61 | ||
151 | # response. The client should immediately and permanently | ||
152 | # delete the certificate and accompanying private key which was | ||
153 | # used in this request. | ||
154 | display <<<"$body" | ||
155 | NOT_FULLY_IMPLEMENTED | ||
156 | ;; | ||
157 | 30) # REDIRECT - TEMPORARY | ||
158 | # As per definition of single-digit code 3 in 1.3.2. | ||
159 | exec "$0" "$smsg" | ||
160 | ;; | ||
161 | 31) # REDIRECT - PERMANENT | ||
162 | # The requested resource should be consistently requested from | ||
163 | # the new URL provided in future. Tools like search engine | ||
164 | # indexers or content aggregators should update their | ||
165 | # configurations to avoid requesting the old URL, and end-user | ||
166 | # clients may automatically update bookmarks, etc. Note that | ||
167 | # clients which only pay attention to the initial digit of | ||
168 | # status codes will treat this as a temporary redirect. They | ||
169 | # will still end up at the right place, they just won't be able | ||
170 | # to make use of the knowledge that this redirect is permanent, | ||
171 | # so they'll pay a small performance penalty by having to follow | ||
172 | # the redirect each time. | ||
173 | exec "$0" "$smsg" | ||
174 | NOT_FULLY_IMPLEMENTED | ||
175 | ;; | ||
176 | 4*) # 40 - TEMPORARY FAILURE | ||
177 | # As per definition of single-digit code 4 in 1.3.2. | ||
178 | # 41 - SERVER UNAVAILABLE | ||
179 | # The server is unavailable due to overload or maintenance. | ||
180 | # (cf HTTP 503) | ||
181 | # 42 - CGI ERROR | ||
182 | # A CGI process, or similar system for generating dynamic | ||
183 | # content, died unexpectedly or timed out. | ||
184 | # 43 - PROXY ERROR | ||
185 | # A proxy request failed because the server was unable to | ||
186 | # successfully complete a transaction with the remote host. | ||
187 | # (cf HTTP 502, 504) | ||
188 | # 44 - SLOW DOWN | ||
189 | # Rate limiting is in effect. <META> is an integer number of | ||
190 | # seconds which the client must wait before another request is | ||
191 | # made to this server. | ||
192 | # (cf HTTP 429) | ||
193 | printf 'OH SHIT!\n%s\t%s\n' "$stat" "$smsg" >&2 | ||
194 | NOT_IMPLEMENTED | ||
195 | ;; | ||
196 | 5*) # 50 - PERMANENT FAILURE | ||
197 | # As per definition of single-digit code 5 in 1.3.2. | ||
198 | # 51 - NOT FOUND | ||
199 | # The requested resource could not be found but may be available | ||
200 | # in the future. | ||
201 | # (cf HTTP 404) | ||
202 | # (struggling to remember this important status code? Easy: | ||
203 | # you can't find things hidden at Area 51!) | ||
204 | # 52 - GONE | ||
205 | # The resource requested is no longer available and will not be | ||
206 | # available again. Search engines and similar tools should | ||
207 | # remove this resource from their indices. Content aggregators | ||
208 | # should stop requesting the resource and convey to their human | ||
209 | # users that the subscribed resource is gone. | ||
210 | # (cf HTTP 410) | ||
211 | # 53 - PROXY REQUEST REFUSED | ||
212 | # The request was for a resource at a domain not served by the | ||
213 | # server and the server does not accept proxy requests. | ||
214 | # 59 - BAD REQUEST | ||
215 | # The server was unable to parse the client's request, | ||
216 | # presumably due to a malformed request. | ||
217 | # (cf HTTP 400) | ||
218 | printf 'OH SHIT!\n%s\t%s\n' "$stat" "$smsg" >&2 | ||
219 | NOT_IMPLEMENTED | ||
220 | ;; | ||
221 | 6*) # 60 - CLIENT CERTIFICATE REQUIRED | ||
222 | # As per definition of single-digit code 6 in 1.3.2. | ||
223 | # 61 - TRANSIENT CERTIFICATE REQUESTED | ||
224 | # The server is requesting the initiation of a transient client | ||
225 | # certificate session, as described in 1.4.3. The client should | ||
226 | # ask the user if they want to accept this and, if so, generate | ||
227 | # a disposable key/cert pair and re-request the resource using it. | ||
228 | # The key/cert pair should be destroyed when the client quits, | ||
229 | # or some reasonable time after it was last used (24 hours? | ||
230 | # Less?) | ||
231 | # 62 - AUTHORISED CERTIFICATE REQUIRED | ||
232 | # This resource is protected and a client certificate which the | ||
233 | # server accepts as valid must be used - a disposable key/cert | ||
234 | # generated on the fly in response to this status is not | ||
235 | # appropriate as the server will do something like compare the | ||
236 | # certificate fingerprint against a white-list of allowed | ||
237 | # certificates. The client should ask the user if they want to | ||
238 | # use a pre-existing certificate from a stored "key chain". | ||
239 | # 63 - CERTIFICATE NOT ACCEPTED | ||
240 | # The supplied client certificate is not valid for accessing the | ||
241 | # requested resource. | ||
242 | # 64 - FUTURE CERTIFICATE REJECTED | ||
243 | # The supplied client certificate was not accepted because its | ||
244 | # validity start date is in the future. | ||
245 | # 65 - EXPIRED CERTIFICTE REJECTED | ||
246 | # The supplied client certificate was not accepted because its | ||
247 | # expiry date has passed. | ||
248 | printf 'OH SHIT!\n%s\t%s\n' "$stat" "$smsg" >&2 | ||
249 | NOT_IMPLEMENTED | ||
250 | ;; | ||
251 | esac | ||
252 | } | ||
253 | |||
254 | NOT_IMPLEMENTED() { | ||
255 | log "NOT IMPLEMENTED!!!" >&2 | ||
256 | exit 127 | ||
257 | } | ||
258 | NOT_FULLY_IMPLEMENTED() { | ||
259 | log "NOT FULLY IMPLEMENTED!!!" >&2 | ||
260 | } | ||
261 | |||
262 | if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then | ||
263 | bollux "$@" | ||
264 | fi | ||