AmendHub

Download

jcs

/

detritus

/

gopher.c

 

(View History)

jcs   gopher: Zero leading Latest amendment: 54 on 2024-12-12

1 /*
2 * Copyright (c) 2024 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <string.h>
20
21 #include "detritus.h"
22
23 #define GOPHER_ICON_SICN_ID 1024
24 #define GOPHER_ICON_TEXT_ID 0
25 #define GOPHER_ICON_FOLDER_ID 1
26 #define GOPHER_ICON_MACARCHIVE_ID 2
27 #define GOPHER_ICON_DOSARCHIVE_ID 3
28 #define GOPHER_ICON_UNKNOWN_ID 4
29 #define GOPHER_ICON_SEARCH_ID 5
30 #define GOPHER_ICON_SOUND_ID 6
31 #define GOPHER_ICON_IMAGE_ID 7
32 #define GOPHER_ICON_DOC_ID 8
33 #define GOPHER_ICON_GOPHER_ID 10
34
35 static const char showable_types[] = "013i";
36
37 struct gopher_page {
38 Handle sicn;
39 };
40
41 bool gopher_accept_uri(struct URI *uri);
42 bool gopher_request_init(page_handle pageh);
43 bool gopher_process(page_handle pageh);
44 void gopher_free(page_handle pageh);
45
46 static void gopher_print_menu(struct page *page, char *line, size_t len);
47
48 struct page_handler gopher_handler = {
49 gopher_accept_uri,
50 gopher_request_init,
51 page_queue_output,
52 page_consume_data,
53 page_request_cleanup,
54 gopher_process,
55 NULL,
56 gopher_free,
57 };
58
59 bool
60 gopher_accept_uri(struct URI *uri)
61 {
62 return (strcasecmp(uri->scheme, "gopher") == 0);
63 }
64
65 bool
66 gopher_request_init(page_handle pageh)
67 {
68 struct page *page = *pageh;
69 struct gopher_page *gopher;
70 size_t selector_len, output_len;
71 char *filename, *output;
72
73 if (page->uri->port == 0)
74 page->uri->port = GOPHER_PORT;
75
76 /*
77 * If we have a path other than /, assume the first character is the
78 * type and should not be sent, per RFC 4266.
79 */
80 if (page->uri->path[0] == '\0' ||
81 (page->uri->path[0] == '/' && page->uri->path[1] == '\0')) {
82 page->content_type[0] = '1';
83 selector_len = 0;
84 } else {
85 /* /0/blah -> blah */
86 page->content_type[0] = page->uri->path[1];
87 selector_len = strlen(page->uri->path + 2);
88 }
89
90 output_len = selector_len + 2;
91 output = xmalloc(output_len + 1);
92 if (output == NULL) {
93 warn("Out of memory");
94 return false;
95 }
96 snprintf(output, output_len + 1, "%s\r\n",
97 selector_len ? page->uri->path + 2 : "");
98
99 gopher = xmalloc(sizeof(struct gopher_page));
100 if (gopher == NULL) {
101 xfree(&output);
102 warn("Out of memory");
103 return false;
104 }
105 gopher->sicn = Get1Resource('SICN', GOPHER_ICON_SICN_ID);
106 if (gopher->sicn == NULL) {
107 xfree(&output);
108 xfree(&gopher);
109 warn("Can't find Gopher SICN");
110 return false;
111 }
112
113 page->request = request_connect(page->browser, page->uri->hostname,
114 page->uri->port, false, 0);
115 if (page->request == NULL) {
116 ReleaseResource(gopher->sicn);
117 xfree(&gopher);
118 xfree(&output);
119 return false;
120 }
121 page->request->output_len = output_len;
122 page->request->output = output;
123 page->handler_cookie = gopher;
124
125 /* XXX: try to detect server responding with "3" for error? */
126
127 if (strchr(showable_types, page->content_type[0]) == NULL) {
128 if (selector_len == 2)
129 filename = NULL;
130 else {
131 filename = strrchr(page->uri->path + 2, '/');
132 if (filename && filename[0] == '/')
133 filename++;
134 }
135 if (!browser_start_download(page->browser, filename, NULL, 0))
136 return false;
137 } else
138 browser_commit_to_loading_page(page->browser);
139
140 return true;
141 }
142
143 bool
144 gopher_process(page_handle pageh)
145 {
146 struct page *page = *pageh;
147 long n, plines = 0;
148 bool ret = true;
149
150 if (strchr(showable_types, page->content_type[0]) &&
151 page->content_len >= 3 &&
152 (page->content_len == 3 ||
153 page->content[page->content_len - 4] == '\n') &&
154 page->content[page->content_len - 3] == '.' &&
155 page->content[page->content_len - 2] == '\r' &&
156 page->content[page->content_len - 1] == '\n') {
157 /* ".\r\n" is end of transfer */
158 page->content_len -= 3;
159 ret = false;
160 }
161
162 if (page->content_pos == page->content_len)
163 return PAGE_CAN_READ_MORE(page);
164
165 if (page->content_type[0] == '1') {
166 /* gopher menu */
167 for (n = page->content_pos; n < page->content_len; n++) {
168 if (n > 0 && page->content[n - 1] == '\r' &&
169 page->content[n] == '\n') {
170 /* print line */
171 gopher_print_menu(page, page->content +
172 page->content_pos, n - page->content_pos);
173 page->content_pos = n + 1;
174 }
175 }
176
177 if (!PAGE_CAN_READ_MORE(page) &&
178 page->content_pos < page->content_len) {
179 gopher_print_menu(page, page->content + page->content_pos,
180 page->content_len - page->content_pos);
181 page->content_pos = page->content_len;
182 ret = false;
183 }
184 } else if (page->content_type[0] == '0') {
185 /* text file, convert newlines and display */
186 ret = page_print_plaintext(pageh);
187 } else {
188 /* anything else, just show it as-is */
189 browser_print(page->browser, page->content + page->content_pos,
190 page->content_len - page->content_pos, false);
191 page->content_pos = page->content_len;
192 ret = false;
193 }
194
195 return ret;
196 }
197
198 void
199 gopher_free(page_handle pageh)
200 {
201 struct page *page = *pageh;
202 struct gopher_page *gopher;
203
204 gopher = (struct gopher_page *)page->handler_cookie;
205
206 ReleaseResource(gopher->sicn);
207 xfree(&page->handler_cookie);
208 }
209
210 static void
211 gopher_print_menu(struct page *page, char *line, size_t len)
212 {
213 static char uri[URI_MAX_STR_LEN + 1];
214 struct gopher_page *gopher;
215 BitMap icon;
216 FontInfo sizing;
217 char type, *label, *selector = NULL, *hostname = NULL;
218 size_t tlen;
219 unsigned short port = 0;
220 short icon_off = -1;
221 long n;
222
223 type = line[0];
224 label = line + 1;
225
226 gopher = (struct gopher_page *)page->handler_cookie;
227
228 for (n = 1; n < len; n++) {
229 if (line[n] != '\t')
230 continue;
231
232 line[n] = '\0';
233 if (selector == NULL)
234 selector = line + n + 1;
235 else if (hostname == NULL)
236 hostname = line + n + 1;
237 else {
238 port = atoi(line + n + 1);
239 break;
240 }
241 }
242
243 page->browser->style = STYLE_PRE;
244
245 switch (type) {
246 case 'i':
247 /* informational */
248 browser_print(page->browser, label, strlen(label), true);
249 break;
250 default:
251 if (type == 'h' && strncmp(selector, "URL:", 4) == 0)
252 tlen = strlcpy(uri, selector + 4, sizeof(uri));
253 else
254 tlen = snprintf(uri, sizeof(uri), "gopher://%s/%c%s", hostname,
255 type, selector);
256
257 switch (type) {
258 case '0':
259 /* text file */
260 icon_off = GOPHER_ICON_TEXT_ID;
261 break;
262 case '1':
263 /* submenu */
264 if (strcasecmp(hostname, page->uri->hostname) == 0)
265 icon_off = GOPHER_ICON_FOLDER_ID;
266 else
267 icon_off = GOPHER_ICON_GOPHER_ID;
268 break;
269 case '3':
270 /* error page */
271 icon_off = GOPHER_ICON_UNKNOWN_ID;
272 break;
273 case '5':
274 /* dos */
275 icon_off = GOPHER_ICON_DOSARCHIVE_ID;
276 break;
277 case '4':
278 /* mac binhex */
279 icon_off = GOPHER_ICON_MACARCHIVE_ID;
280 break;
281 case '7':
282 /* search */
283 icon_off = GOPHER_ICON_SEARCH_ID;
284 break;
285 case '2':
286 /* ccso nameserver? */
287 case '+':
288 /* mirror? */
289 case '6':
290 /* uuencoded */
291 case '8':
292 /* telnet */
293 case '9':
294 /* binary file */
295 case 'T':
296 /* telnet 3270 */
297 case 'd':
298 /* doc */
299 case 'h':
300 /* html */
301 case 'r':
302 /* rtf */
303 case 'P':
304 /* pdf */
305 case 'X':
306 /* xml */
307 icon_off = GOPHER_ICON_DOC_ID;
308 break;
309 case 'g':
310 /* gif */
311 case 'I':
312 /* image */
313 case 'p':
314 /* png */
315 case ':':
316 /* bitmap */
317 case ';':
318 /* movie */
319 icon_off = GOPHER_ICON_IMAGE_ID;
320 break;
321 case '<':
322 /* sound */
323 icon_off = GOPHER_ICON_SOUND_ID;
324 break;
325 default:
326 icon_off = GOPHER_ICON_UNKNOWN_ID;
327 }
328
329 HLock(gopher->sicn);
330 icon.baseAddr = (char *)*(gopher->sicn) + (icon_off * 32);
331 icon.rowBytes = 2;
332 SetRect(&icon.bounds, 0, 0, 12, 12);
333
334 sizing.ascent = 10;
335 sizing.descent = 2;
336 sizing.widMax = 18;
337 sizing.leading = 0;
338 browser_print_bitmap(page->browser, &icon, &sizing);
339
340 HUnlock(gopher->sicn);
341
342 page->browser->style |= STYLE_BOLD;
343 browser_print_link(page->browser, uri, tlen, label, strlen(label),
344 true);
345 page->browser->style &= ~(STYLE_BOLD);
346 break;
347 }
348
349 for (n = 1; n < len; n++) {
350 if (line[n] == '\0')
351 line[n] = '\t';
352 }
353 }