phroxy.c - phroxy - Gopher to HTTP proxy
HTML git clone git://git.z3bra.org/phroxy.git
DIR Log
DIR Files
DIR Refs
DIR LICENSE
---
phroxy.c (9594B)
---
1 #include <err.h>
2 #include <errno.h>
3 #include <limits.h>
4 #include <netdb.h>
5 #include <signal.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <time.h>
10 #include <unistd.h>
11
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <sys/socket.h>
15
16 /* supported items */
17 enum {
18 ITEM_0,
19 ITEM_1,
20 ITEM_2,
21 ITEM_3,
22 ITEM_4,
23 ITEM_5,
24 ITEM_6,
25 ITEM_7,
26 ITEM_8,
27 ITEM_9,
28 ITEM_I,
29 ITEM_g,
30 ITEM_h,
31 ITEM_s,
32 ITEM_P,
33 ITEM_d,
34 };
35
36 #include "config.h"
37
38 void *
39 xreallocarray(void *m, const size_t n, const size_t s)
40 {
41 void *nm;
42
43 if (n == 0 || s == 0) {
44 free(m);
45 return NULL;
46 }
47 if (s && n > (size_t)-1/s)
48 errx(1, "realloc: overflow");
49 if (!(nm = realloc(m, n * s)))
50 errx(1, "realloc: %s", strerror(errno));
51
52 return nm;
53 }
54
55
56 static int
57 connectto(const char *host, const char *port)
58 {
59 static const struct addrinfo hints = {
60 .ai_family = AF_UNSPEC,
61 .ai_socktype = SOCK_STREAM,
62 .ai_protocol = IPPROTO_TCP,
63 };
64 struct addrinfo *addrs, *addr;
65 struct timeval tv;
66 int r, sock = -1;
67
68 if ((r = getaddrinfo(host, port, &hints, &addrs)))
69 return -1;
70
71 /* connection timeout */
72 tv.tv_sec = 5;
73 tv.tv_usec = 0;
74
75 for (addr = addrs; addr; addr = addr->ai_next) {
76 if ((sock = socket(addr->ai_family, addr->ai_socktype,
77 addr->ai_protocol)) < 0)
78 continue;
79 if ((r = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0) {
80 close(sock);
81 continue;
82 }
83 break;
84 }
85
86 freeaddrinfo(addrs);
87
88 if (sock < 0 || r < 0)
89 return -1;
90
91 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (void *)&tv, sizeof(tv));
92 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv));
93
94 return sock;
95 }
96
97 int
98 sendselector(int sock, const char *selector, const char *search)
99 {
100 char *msg, *p;
101 char *fmt = "%s\r\n";
102 size_t ln;
103 ssize_t n;
104
105 ln = strlen(selector) + 3;
106 if (search) {
107 fmt = "%s\t%s\r\n";
108 ln += strlen(search) + 1;
109 }
110
111 msg = p = malloc(ln);
112 snprintf(msg, ln--, fmt, selector, search);
113
114 while ((n = write(sock, p, ln)) > 0) {
115 ln -= n;
116 p += n;
117 }
118
119 free(msg);
120 return n;
121 }
122
123 static char
124 hex2bin(const unsigned char *in)
125 {
126 int out;
127
128 if (*in == '%')
129 in++;
130
131 if ('A' <= in[0] && in[0] <= 'F') out = 16 * (in[0] - 'A' + 10);
132 if ('0' <= in[0] && in[0] <= '9') out = 16 * (in[0] - '0');
133
134 if ('A' <= in[1] && in[1] <= 'F') out += (in[1] - 'A' + 10);
135 if ('0' <= in[1] && in[1] <= '9') out += (in[1] - '0');
136
137 return out;
138 }
139
140 char *
141 urldec(char *search)
142 {
143 char *msg, *p;
144
145 if (!search)
146 return NULL;
147
148 msg = p = search;
149 for (p = msg; *p != '\0'; msg++, p++) {
150 switch(*p) {
151 case '+':
152 *msg = ' ';
153 break;
154 case '%':
155 *msg = hex2bin((unsigned char *)p);
156 p += 2;
157 break;
158 default:
159 *msg = *p;
160 }
161 }
162 *msg = '\0';
163
164 return search;
165 }
166
167
168 char *
169 getrawitem(int sock, size_t *sz)
170 {
171 char *raw, *buf;
172 size_t bn, bs;
173 ssize_t n;
174
175 raw = buf = NULL;
176 bn = bs = n = 0;
177
178 do {
179 bs -= n;
180 buf += n;
181 if (bs < 1) {
182 raw = xreallocarray(raw, ++bn, BUFSIZ);
183 buf = raw + (bn-1) * BUFSIZ;
184 bs = BUFSIZ;
185 }
186 } while ((n = read(sock, buf, bs)) > 0);
187
188 *buf = '\0';
189
190 if (sz)
191 *sz = buf - raw;
192
193 if (n < 0) {
194 free(raw);
195 return NULL;
196 }
197
198 return raw;
199 }
200
201 void
202 printhttp(int code)
203 {
204 switch (code) {
205 case 400: printf("HTTP/1.1 400 That's Illegal\r\n"); break;
206 case 404: printf("HTTP/1.1 404 Google Broke The Web\r\n"); break;
207 case 405: printf("HTTP/1.1 405 Don't Do That\r\n"); break;
208 case 415: printf("HTTP/1.1 415 Gopher Type Not Handled\r\n"); break;
209 case 444: printf("HTTP/1.1 444 You're Alone\r\n"); break;
210 case 500: printf("HTTP/1.1 500 You Broke The Web\r\n"); break;
211 }
212 printf("\r\n");
213 }
214
215 char *
216 contenttype(char i, char *path)
217 {
218 static char *ext, type[32];
219
220 /* isolate file extention, if any */
221 ext = strrchr(path, '.');
222 ext = ext ? ext + 1 : "*";
223
224 switch(i) {
225 case '0':
226 case '1':
227 case '7':
228 case 'h':
229 snprintf(type, sizeof(type)-1, "text/html; charset=utf-8");
230 break;
231 case '6':
232 snprintf(type, sizeof(type)-1, "text/x-uuencode");
233 break;
234 case '4':
235 case '5':
236 case '9':
237 snprintf(type, sizeof(type)-1, "application/octet-stream");
238 break;
239 case 'I':
240 /* assume 4 chars max for extension */
241 snprintf(type, sizeof(type)-1, "image/%s", ext);
242 break;
243 case 's':
244 snprintf(type, sizeof(type)-1, "audio/%s", ext);
245 break;
246 case 'g':
247 snprintf(type, sizeof(type)-1, "image/gif");
248 break;
249 case 'P':
250 case 'd':
251 snprintf(type, sizeof(type)-1, "application/%s", ext);
252 break;
253 default:
254 return NULL;
255 break; /* NOTREACHED */
256 }
257
258 return type;
259 }
260
261 const char *
262 itemname(char i)
263 {
264 switch(i) {
265 case '0': return items[ITEM_0];
266 case '1': return items[ITEM_1];
267 case '2': return items[ITEM_2];
268 case '3': return items[ITEM_3];
269 case '4': return items[ITEM_4];
270 case '5': return items[ITEM_5];
271 case '6': return items[ITEM_6];
272 case '7': return items[ITEM_7];
273 case '8': return items[ITEM_8];
274 case '9': return items[ITEM_9];
275 case 'I': return items[ITEM_I];
276 case 'g': return items[ITEM_g];
277 case 'h': return items[ITEM_h];
278 case 's': return items[ITEM_s];
279 case 'd': return items[ITEM_d];
280 case 'P': return items[ITEM_P];
281 }
282
283 return NULL;
284 }
285
286
287 void
288 printheaders(char *ctype)
289 {
290 time_t t;
291
292 t = time(NULL);
293 if (t > 0)
294 printf("Date: %s", asctime(gmtime(&t)));
295 if (ctype)
296 printf("Content-Type: %s\r\n", ctype);
297 printf("Server: phroxy\r\n");
298 printf("Host: %s\r\n", http_host);
299 printf("Connection: close\r\n");
300 }
301
302 int
303 printmenu(int fd, char *data)
304 {
305 char i, *p, a[LINE_MAX], *f[4];
306 char *ifmt = "<div class='item'><span> </span><code>%s</code></div>\n";
307 char *afmt = "<div class='item'><span>%s</span><a href='/%s:%s/%c%s'>%s</a></div>\n";
308 char *sfmt = "<div class='item'><span>%s</span><details><summary>%s</summary><form method='get' action='/%s:%s/%c%s'><input type='text' name='q'></form></details></div>\n";
309
310 p = data;
311
312 while((p = strsep(&data, "\n"))) {
313 i = *p++;
314 if (i == '.')
315 break;
316
317 f[0] = strsep(&p, "\t");
318 f[1] = strsep(&p, "\t");
319 f[2] = strsep(&p, "\t");
320 f[3] = strsep(&p, "\r");
321 if (!f[1])
322 continue;
323
324 switch(i) {
325 case 'i':
326 snprintf(a, sizeof(a), ifmt, f[0]);
327 break;
328 case '7':
329 snprintf(a, sizeof(a), sfmt, itemname(i), f[0], f[2], f[3], i, f[1]);
330 break;
331 default:
332 snprintf(a, sizeof(a), afmt, itemname(i), f[2], f[3], i, f[1], f[0]);
333 }
334
335 write(fd, a, strlen(a));
336 }
337
338 return 0;
339 }
340
341 int
342 printhtml(int fd, const char *data, size_t len)
343 {
344 size_t r, n;
345 const char *s, *e, *x;
346
347 write(fd, "<pre>", 5);
348
349 for (n = 0; n < len; n++) {
350
351 s = data + n;
352
353 /* escape XML characters */
354 x = NULL;
355 switch (*s) {
356 case '&': x = x ? x : "&"; /* FALLTHROUGH */
357 case '<': x = x ? x : "<"; /* FALLTHROUGH */
358 case '>': x = x ? x : ">"; /* FALLTHROUGH */
359 write(fd, x, strlen(x));
360 break;
361 default:
362 e = strpbrk(s, "&<>");
363 r = e ? (size_t)(e - s) : len - n;
364 if (r) {
365 write(fd, s, r);
366 n += r - 1;
367 }
368 }
369 }
370 write(fd, "</pre>\n", 7);
371 return 0;
372 }
373
374 int
375 servebots()
376 {
377 printf("HTTP/1.1 200 OK\r\n");
378 printheaders("text/plain");
379 printf("Content-Length: %ld\r\n", strlen(robotstxt));
380 printf("\r\n");
381 fflush(stdout);
382 write(1, robotstxt, strlen(robotstxt));
383 fflush(stdout);
384
385 return 0;
386 }
387
388 int
389 serveitem(char item, char *path, char *data, size_t len)
390 {
391 char *send;
392 int sent;
393
394
395 if (!contenttype(item, path)) {
396 printhttp(415);
397 return 1;
398 }
399
400 printf("HTTP/1.1 200 OK\r\n");
401 printheaders(contenttype(item, path));
402
403 switch(item) {
404 case '7': // search
405 case '1': // menu
406 case '0': // text
407 printf("\r\n");
408 fflush(stdout);
409 write(1, head, strlen(head));
410 if (item == '1' || item == '7') printmenu(1, data);
411 if (item == '0') printhtml(1, data, len);
412 write(1, foot, strlen(foot));
413 break;
414
415 case '4': // BinHexed Macintosh file
416 case '5': // DOS binary archive of some sort
417 case '6': // uuencoded
418 case '9': // binary
419 case 'g': // gif
420 case 'I': // image
421 case 's': // sound
422 case 'd': // document
423 case 'P': // pdf (~document)
424 case 'h': // http redirect
425 printf("Content-Length: %ld\r\n", len);
426 printf("\r\n");
427 fflush(stdout);
428 send = data;
429 while (len > 0) {
430 if ((sent = write(1, send, len)) < 0)
431 return 1;
432 len -= sent;
433 send += sent;
434 }
435 break;
436
437 case '2': // CSO phone-book server
438 case '3': // Error
439 case '8': // telnet session.
440 case 'T': // tn3270 session.
441 case '+': // mirror link
442 default:
443 /* IGNORE */
444 break;
445 }
446
447 free(data);
448 fflush(stdout);
449
450 return 0;
451 }
452
453 int
454 phroxy(char *url)
455 {
456 int sock;
457 size_t len;
458 char item = 0;
459 char *hole, *path, *host, *port;
460 char *data = NULL, *srch = NULL;
461
462 if (!strncmp(url, "/robots.txt", 11))
463 return servebots();
464
465 url++;
466 hole = strsep(&url, "/");
467 if (!hole || !strnlen(hole, 1))
468 hole = default_hole;
469
470 host = strsep(&hole, ":");
471 port = strsep(&hole, "\0");
472 if (!port)
473 port = "70";
474
475 if (url)
476 item = *url++;
477
478 if (!item)
479 item = '1';
480
481 path = strsep(&url, "\0");
482 if (!path || *path == '\0')
483 path = "/";
484
485 if((srch = strchr(path, '?'))) {
486 *srch = '\0';
487 srch += 3; /* go past "?q=" in URL, to fetch actual query */
488 }
489
490 if ((sock = connectto(host, port)) < 0) {
491 printhttp(500);
492 return 1;
493 }
494
495 if (!sendselector(sock, path, urldec(srch)))
496 data = getrawitem(sock, &len);
497
498 close(sock);
499
500 if (!data) {
501 printhttp(444);
502 return 1;
503 }
504
505 serveitem(item, path, data, len);
506
507 return 0;
508 }
509
510 int
511 main(void)
512 {
513 ssize_t rlen;
514 char request[512], *url;
515
516 rlen = read(0, request, sizeof(request) - 1);
517 if (rlen < 0)
518 return 1;
519
520 request[rlen] = '\0';
521
522 if (strncmp(request, "GET ", 4)) {
523 printhttp(405);
524 return 1;
525 }
526
527 url = strtok(request + 4, " ");
528
529 return phroxy(url);
530 }