Blob


1 /*
2 * Copyright (c) 2021, 2024 Omar Polo <op@omarpolo.com>
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 */
17 #include "compat.h"
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
23 #include "iri.h"
24 #include "parser.h"
25 #include "telescope.h"
26 #include "utils.h"
27 #include "xwrapper.h"
29 #ifndef LINE_MAX
30 #define LINE_MAX 2048
31 #endif
33 struct gm_selector {
34 char type;
35 const char *ds;
36 const char *selector;
37 const char *addr;
38 const char *port;
39 };
41 static void gm_parse_selector(char *, struct gm_selector *);
43 static int gm_parse_line(struct buffer *, const char *, size_t);
44 static int gm_serialize(struct buffer *, FILE *);
46 const struct parser gophermap_parser = {
47 .name = "gophermap",
48 .parseline = &gm_parse_line,
49 .serialize = &gm_serialize,
50 };
52 static void
53 gm_parse_selector(char *line, struct gm_selector *s)
54 {
55 s->type = *line++;
56 s->ds = line;
57 s->selector = "";
58 s->addr = "";
59 s->port = "";
61 if ((line = strchr(line, '\t')) == NULL)
62 return;
63 *line++ = '\0';
64 s->selector = line;
66 if ((line = strchr(line, '\t')) == NULL)
67 return;
68 *line++ = '\0';
69 s->addr = line;
71 if ((line = strchr(line, '\t')) == NULL)
72 return;
73 *line++ = '\0';
74 s->port = line;
75 }
77 static int
78 selector2uri(struct gm_selector *s, char *buf, size_t len)
79 {
80 int r;
82 r = snprintf(buf, len, "gopher://%s:%s/%c%s",
83 s->addr, s->port, s->type, *s->selector != '/' ? "/" : "");
84 if (r < 0 || (size_t)r >= len)
85 return (-1);
87 buf += r;
88 len -= r;
89 return (iri_urlescape(s->selector, buf, len));
90 }
92 static inline int
93 emit_line(struct buffer *b, enum line_type type, struct gm_selector *s)
94 {
95 struct line *l;
96 char buf[LINE_MAX];
98 l = xcalloc(1, sizeof(*l));
100 l->line = xstrdup(s->ds);
102 switch (l->type = type) {
103 case LINE_LINK:
104 if (s->type == 'h' && !strncmp(s->selector, "URL:", 4)) {
105 strlcpy(buf, s->selector+4, sizeof(buf));
106 } else if (selector2uri(s, buf, sizeof(buf)) == -1)
107 goto err;
109 l->alt = xstrdup(buf);
110 break;
112 default:
113 break;
116 TAILQ_INSERT_TAIL(&b->head, l, lines);
118 return 1;
120 err:
121 if (l != NULL) {
122 free(l->line);
123 free(l->alt);
124 free(l);
126 return 0;
129 static int
130 gm_parse_line(struct buffer *b, const char *line, size_t linelen)
132 char buf[LINE_MAX] = {0};
133 struct gm_selector s = {0};
135 memcpy(buf, line, MIN(sizeof(buf)-1, linelen));
136 gm_parse_selector(buf, &s);
138 switch (s.type) {
139 case '0': /* text file */
140 case '1': /* gopher submenu */
141 case '2': /* CCSO nameserver */
142 case '4': /* binhex-encoded file */
143 case '5': /* DOS file */
144 case '6': /* uuencoded file */
145 case '7': /* full-text search */
146 case '8': /* telnet */
147 case '9': /* binary file */
148 case '+': /* mirror or alternate server */
149 case 'g': /* gif */
150 case 'I': /* image */
151 case 'T': /* telnet 3270 */
152 case ':': /* gopher+: bitmap image */
153 case ';': /* gopher+: movie file */
154 case 'd': /* non-canonical: doc */
155 case 'h': /* non-canonical: html file */
156 case 's': /* non-canonical: sound file */
157 if (!emit_line(b, LINE_LINK, &s))
158 return 0;
159 break;
161 case 'i': /* non-canonical: message */
162 if (!emit_line(b, LINE_TEXT, &s))
163 return 0;
164 break;
166 case '3': /* error code */
167 if (!emit_line(b, LINE_QUOTE, &s))
168 return 0;
169 break;
172 return 1;
175 static inline const char *
176 gopher_skip_selector(const char *path, int *ret_type)
178 *ret_type = 0;
180 if (!strcmp(path, "/") || *path == '\0') {
181 *ret_type = '1';
182 return path;
185 if (*path != '/')
186 return path;
187 path++;
189 switch (*ret_type = *path) {
190 case '0':
191 case '1':
192 case '7':
193 break;
195 default:
196 *ret_type = 0;
197 path -= 1;
198 return path;
201 return ++path;
204 static int
205 serialize_link(struct line *line, const char *text, FILE *fp)
207 size_t portlen = 0;
208 int type;
209 const char *uri, *endhost, *port, *path, *colon;
211 if ((uri = line->alt) == NULL)
212 return -1;
214 if (strncmp(uri, "gopher://", 9) != 0)
215 return fprintf(fp, "h%s\tURL:%s\terror.host\t1\n",
216 text, line->alt);
218 uri += 9; /* skip gopher:// */
220 path = strchr(uri, '/');
221 colon = strchr(uri, ':');
223 if (path != NULL && colon > path)
224 colon = NULL;
226 if ((endhost = colon) == NULL &&
227 (endhost = path) == NULL)
228 endhost = strchr(uri, '\0');
230 if (colon != NULL) {
231 for (port = colon+1; *port && *port != '/'; ++port)
232 ++portlen;
233 port = colon+1;
234 } else {
235 port = "70";
236 portlen = 2;
239 if (path == NULL) {
240 type = '1';
241 path = "";
242 } else
243 path = gopher_skip_selector(path, &type);
245 return fprintf(fp, "%c%s\t%s\t%.*s\t%.*s\n", type, text,
246 path, (int)(endhost - uri), uri, (int)portlen, port);
249 static int
250 gm_serialize(struct buffer *b, FILE *fp)
252 struct line *line;
253 const char *text;
254 int r;
256 TAILQ_FOREACH(line, &b->head, lines) {
257 if ((text = line->line) == NULL)
258 text = "";
260 switch (line->type) {
261 case LINE_LINK:
262 r = serialize_link(line, text, fp);
263 break;
265 case LINE_TEXT:
266 r = fprintf(fp, "i%s\t\terror.host\t1\n", text);
267 break;
269 case LINE_QUOTE:
270 r = fprintf(fp, "3%s\t\terror.host\t1\n", text);
271 break;
273 default:
274 /* unreachable */
275 abort();
278 if (r == -1)
279 return 0;
282 return 1;