]> git.ipfire.org Git - thirdparty/squid.git/blob - src/gopher.cc
Policy: use USE_* from code wrappers and ENABLE_* for conditionals.
[thirdparty/squid.git] / src / gopher.cc
1
2 /*
3 * $Id$
4 *
5 * DEBUG: section 10 Gopher
6 * AUTHOR: Harvest Derived
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 *
34 */
35
36 #include "squid.h"
37 #include "comm/Write.h"
38 #include "errorpage.h"
39 #include "Store.h"
40 #include "html_quote.h"
41 #include "HttpRequest.h"
42 #include "HttpReply.h"
43 #include "comm.h"
44 #if USE_DELAY_POOLS
45 #include "DelayPools.h"
46 #include "MemObject.h"
47 #endif
48 #include "MemBuf.h"
49 #include "forward.h"
50 #include "rfc1738.h"
51 #include "SquidTime.h"
52
53 /**
54 \defgroup ServerProtocolGopherInternal Server-Side Gopher Internals
55 \ingroup ServerProtocolGopherAPI
56 * Gopher is somewhat complex and gross because it must convert from
57 * the Gopher protocol to HTTP.
58 */
59
60 /* gopher type code from rfc. Anawat. */
61 /// \ingroup ServerProtocolGopherInternal
62 #define GOPHER_FILE '0'
63 /// \ingroup ServerProtocolGopherInternal
64 #define GOPHER_DIRECTORY '1'
65 /// \ingroup ServerProtocolGopherInternal
66 #define GOPHER_CSO '2'
67 /// \ingroup ServerProtocolGopherInternal
68 #define GOPHER_ERROR '3'
69 /// \ingroup ServerProtocolGopherInternal
70 #define GOPHER_MACBINHEX '4'
71 /// \ingroup ServerProtocolGopherInternal
72 #define GOPHER_DOSBIN '5'
73 /// \ingroup ServerProtocolGopherInternal
74 #define GOPHER_UUENCODED '6'
75 /// \ingroup ServerProtocolGopherInternal
76 #define GOPHER_INDEX '7'
77 /// \ingroup ServerProtocolGopherInternal
78 #define GOPHER_TELNET '8'
79 /// \ingroup ServerProtocolGopherInternal
80 #define GOPHER_BIN '9'
81 /// \ingroup ServerProtocolGopherInternal
82 #define GOPHER_REDUNT '+'
83 /// \ingroup ServerProtocolGopherInternal
84 #define GOPHER_3270 'T'
85 /// \ingroup ServerProtocolGopherInternal
86 #define GOPHER_GIF 'g'
87 /// \ingroup ServerProtocolGopherInternal
88 #define GOPHER_IMAGE 'I'
89
90 /// \ingroup ServerProtocolGopherInternal
91 #define GOPHER_HTML 'h' /* HTML */
92 /// \ingroup ServerProtocolGopherInternal
93 #define GOPHER_INFO 'i'
94 /**
95 \ingroup ServerProtocolGopherInternal
96 W3 address
97 */
98 #define GOPHER_WWW 'w'
99 /// \ingroup ServerProtocolGopherInternal
100 #define GOPHER_SOUND 's'
101
102 /// \ingroup ServerProtocolGopherInternal
103 #define GOPHER_PLUS_IMAGE ':'
104 /// \ingroup ServerProtocolGopherInternal
105 #define GOPHER_PLUS_MOVIE ';'
106 /// \ingroup ServerProtocolGopherInternal
107 #define GOPHER_PLUS_SOUND '<'
108
109 /// \ingroup ServerProtocolGopherInternal
110 #define GOPHER_PORT 70
111
112 /// \ingroup ServerProtocolGopherInternal
113 #define TAB '\t'
114 /// \ingroup ServerProtocolGopherInternal
115 /// \todo CODE: should this be a protocol-specific thing?
116 #define TEMP_BUF_SIZE 4096
117 /// \ingroup ServerProtocolGopherInternal
118 #define MAX_CSO_RESULT 1024
119
120 /// \ingroup ServerProtocolGopherInternal
121 typedef struct gopher_ds {
122 StoreEntry *entry;
123 enum {
124 NORMAL,
125 HTML_DIR,
126 HTML_INDEX_RESULT,
127 HTML_CSO_RESULT,
128 HTML_INDEX_PAGE,
129 HTML_CSO_PAGE
130 } conversion;
131 int HTML_header_added;
132 int HTML_pre;
133 char type_id;
134 char request[MAX_URL];
135 int cso_recno;
136 int len;
137 char *buf; /* pts to a 4k page */
138 int fd;
139 HttpRequest *req;
140 FwdState::Pointer fwd;
141 char replybuf[BUFSIZ];
142 } GopherStateData;
143
144 static PF gopherStateFree;
145 static void gopherMimeCreate(GopherStateData *);
146 static void gopher_request_parse(const HttpRequest * req,
147 char *type_id,
148 char *request);
149 static void gopherEndHTML(GopherStateData *);
150 static void gopherToHTML(GopherStateData *, char *inbuf, int len);
151 static PF gopherTimeout;
152 static IOCB gopherReadReply;
153 static IOCB gopherSendComplete;
154 static PF gopherSendRequest;
155
156 /// \ingroup ServerProtocolGopherInternal
157 static char def_gopher_bin[] = "www/unknown";
158
159 /// \ingroup ServerProtocolGopherInternal
160 static char def_gopher_text[] = "text/plain";
161
162 /// \ingroup ServerProtocolGopherInternal
163 static void
164 gopherStateFree(int fdnotused, void *data)
165 {
166 GopherStateData *gopherState = (GopherStateData *)data;
167
168 if (gopherState == NULL)
169 return;
170
171 if (gopherState->entry) {
172 gopherState->entry->unlock();
173 }
174
175 HTTPMSGUNLOCK(gopherState->req);
176
177 gopherState->fwd = NULL; // refcounted
178
179 memFree(gopherState->buf, MEM_4K_BUF);
180 gopherState->buf = NULL;
181 cbdataFree(gopherState);
182 }
183
184 /**
185 \ingroup ServerProtocolGopherInternal
186 * Create MIME Header for Gopher Data
187 */
188 static void
189 gopherMimeCreate(GopherStateData * gopherState)
190 {
191 StoreEntry *entry = gopherState->entry;
192 const char *mime_type = NULL;
193 const char *mime_enc = NULL;
194
195 switch (gopherState->type_id) {
196
197 case GOPHER_DIRECTORY:
198
199 case GOPHER_INDEX:
200
201 case GOPHER_HTML:
202
203 case GOPHER_WWW:
204
205 case GOPHER_CSO:
206 mime_type = "text/html";
207 break;
208
209 case GOPHER_GIF:
210
211 case GOPHER_IMAGE:
212
213 case GOPHER_PLUS_IMAGE:
214 mime_type = "image/gif";
215 break;
216
217 case GOPHER_SOUND:
218
219 case GOPHER_PLUS_SOUND:
220 mime_type = "audio/basic";
221 break;
222
223 case GOPHER_PLUS_MOVIE:
224 mime_type = "video/mpeg";
225 break;
226
227 case GOPHER_MACBINHEX:
228
229 case GOPHER_DOSBIN:
230
231 case GOPHER_UUENCODED:
232
233 case GOPHER_BIN:
234 /* Rightnow We have no idea what it is. */
235 mime_enc = mimeGetContentEncoding(gopherState->request);
236 mime_type = mimeGetContentType(gopherState->request);
237 if (!mime_type)
238 mime_type = def_gopher_bin;
239 break;
240
241 case GOPHER_FILE:
242
243 default:
244 mime_enc = mimeGetContentEncoding(gopherState->request);
245 mime_type = mimeGetContentType(gopherState->request);
246 if (!mime_type)
247 mime_type = def_gopher_text;
248 break;
249 }
250
251 assert(entry->isEmpty());
252 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
253
254 HttpReply *reply = new HttpReply;
255 entry->buffer();
256 reply->setHeaders(HTTP_OK, "Gatewaying", mime_type, -1, -1, -2);
257 if (mime_enc)
258 reply->header.putStr(HDR_CONTENT_ENCODING, mime_enc);
259
260 entry->replaceHttpReply(reply);
261 }
262
263 /**
264 \ingroup ServerProtocolGopherInternal
265 * Parse a gopher request into components. By Anawat.
266 */
267 static void
268 gopher_request_parse(const HttpRequest * req, char *type_id, char *request)
269 {
270 const char *path = req->urlpath.termedBuf();
271
272 if (request)
273 request[0] = '\0';
274
275 if (path && (*path == '/'))
276 path++;
277
278 if (!path || !*path) {
279 *type_id = GOPHER_DIRECTORY;
280 return;
281 }
282
283 *type_id = path[0];
284
285 if (request) {
286 xstrncpy(request, path + 1, MAX_URL);
287 /* convert %xx to char */
288 rfc1738_unescape(request);
289 }
290 }
291
292 /**
293 \ingroup ServerProtocolGopherAPI
294 * Parse the request to determine whether it is cachable.
295 *
296 \param req Request data.
297 \retval 0 Not cachable.
298 \retval 1 Cachable.
299 */
300 int
301 gopherCachable(const HttpRequest * req)
302 {
303 int cachable = 1;
304 char type_id;
305 /* parse to see type */
306 gopher_request_parse(req,
307 &type_id,
308 NULL);
309
310 switch (type_id) {
311
312 case GOPHER_INDEX:
313
314 case GOPHER_CSO:
315
316 case GOPHER_TELNET:
317
318 case GOPHER_3270:
319 cachable = 0;
320 break;
321
322 default:
323 cachable = 1;
324 }
325
326 return cachable;
327 }
328
329 /// \ingroup ServerProtocolGopherInternal
330 static void
331 gopherHTMLHeader(StoreEntry * e, const char *title, const char *substring)
332 {
333 storeAppendPrintf(e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
334 storeAppendPrintf(e, "<HTML><HEAD><TITLE>");
335 storeAppendPrintf(e, title, substring);
336 storeAppendPrintf(e, "</TITLE>");
337 storeAppendPrintf(e, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
338 storeAppendPrintf(e, "</HEAD>\n<BODY><H1>");
339 storeAppendPrintf(e, title, substring);
340 storeAppendPrintf(e, "</H1>\n");
341 }
342
343 /// \ingroup ServerProtocolGopherInternal
344 static void
345 gopherHTMLFooter(StoreEntry * e)
346 {
347 storeAppendPrintf(e, "<HR noshade size=\"1px\">\n");
348 storeAppendPrintf(e, "<ADDRESS>\n");
349 storeAppendPrintf(e, "Generated %s by %s (%s)\n",
350 mkrfc1123(squid_curtime),
351 getMyHostname(),
352 visible_appname_string);
353 storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
354 }
355
356 /// \ingroup ServerProtocolGopherInternal
357 static void
358 gopherEndHTML(GopherStateData * gopherState)
359 {
360 StoreEntry *e = gopherState->entry;
361
362 if (!gopherState->HTML_header_added) {
363 gopherHTMLHeader(e, "Server Return Nothing", NULL);
364 storeAppendPrintf(e, "<P>The Gopher query resulted in a blank response</P>");
365 } else if (gopherState->HTML_pre) {
366 storeAppendPrintf(e, "</PRE>\n");
367 }
368
369 gopherHTMLFooter(e);
370 }
371
372 /**
373 \ingroup ServerProtocolGopherInternal
374 * Convert Gopher to HTML.
375 \par
376 * Borrow part of code from libwww2 came with Mosaic distribution.
377 */
378 static void
379 gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
380 {
381 char *pos = inbuf;
382 char *lpos = NULL;
383 char *tline = NULL;
384 LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
385 LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
386 char *name = NULL;
387 char *selector = NULL;
388 char *host = NULL;
389 char *port = NULL;
390 char *escaped_selector = NULL;
391 const char *icon_url = NULL;
392 char gtype;
393 StoreEntry *entry = NULL;
394
395 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
396 memset(line, '\0', TEMP_BUF_SIZE);
397
398 entry = gopherState->entry;
399
400 if (gopherState->conversion == gopher_ds::HTML_INDEX_PAGE) {
401 char *html_url = html_quote(entry->url());
402 gopherHTMLHeader(entry, "Gopher Index %s", html_url);
403 storeAppendPrintf(entry,
404 "<p>This is a searchable Gopher index. Use the search\n"
405 "function of your browser to enter search terms.\n"
406 "<ISINDEX>\n");
407 gopherHTMLFooter(entry);
408 /* now let start sending stuff to client */
409 entry->flush();
410 gopherState->HTML_header_added = 1;
411
412 return;
413 }
414
415 if (gopherState->conversion == gopher_ds::HTML_CSO_PAGE) {
416 char *html_url = html_quote(entry->url());
417 gopherHTMLHeader(entry, "CSO Search of %s", html_url);
418 storeAppendPrintf(entry,
419 "<P>A CSO database usually contains a phonebook or\n"
420 "directory. Use the search function of your browser to enter\n"
421 "search terms.</P><ISINDEX>\n");
422 gopherHTMLFooter(entry);
423 /* now let start sending stuff to client */
424 entry->flush();
425 gopherState->HTML_header_added = 1;
426
427 return;
428 }
429
430 inbuf[len] = '\0';
431 String outbuf;
432
433 if (!gopherState->HTML_header_added) {
434 if (gopherState->conversion == gopher_ds::HTML_CSO_RESULT)
435 gopherHTMLHeader(entry, "CSO Search Result", NULL);
436 else
437 gopherHTMLHeader(entry, "Gopher Menu", NULL);
438
439 outbuf.append ("<PRE>");
440
441 gopherState->HTML_header_added = 1;
442
443 gopherState->HTML_pre = 1;
444 }
445
446 while ((pos != NULL) && (pos < inbuf + len)) {
447
448 if (gopherState->len != 0) {
449 /* there is something left from last tx. */
450 xstrncpy(line, gopherState->buf, gopherState->len + 1);
451
452 if (gopherState->len + len > TEMP_BUF_SIZE) {
453 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry->url() );
454 len = TEMP_BUF_SIZE - gopherState->len;
455 }
456
457 lpos = (char *) memccpy(line + gopherState->len, inbuf, '\n', len);
458
459 if (lpos)
460 *lpos = '\0';
461 else {
462 /* there is no complete line in inbuf */
463 /* copy it to temp buffer */
464
465 if (gopherState->len + len > TEMP_BUF_SIZE) {
466 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry->url() );
467 len = TEMP_BUF_SIZE - gopherState->len;
468 }
469
470 xmemcpy(gopherState->buf + gopherState->len, inbuf, len);
471 gopherState->len += len;
472 return;
473 }
474
475 /* skip one line */
476 pos = (char *) memchr(pos, '\n', len);
477
478 if (pos)
479 pos++;
480
481 /* we're done with the remain from last tx. */
482 gopherState->len = 0;
483
484 *(gopherState->buf) = '\0';
485 } else {
486
487 lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf));
488
489 if (lpos)
490 *lpos = '\0';
491 else {
492 /* there is no complete line in inbuf */
493 /* copy it to temp buffer */
494
495 if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) {
496 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry->url() );
497 len = TEMP_BUF_SIZE;
498 }
499
500 if (len > (pos - inbuf)) {
501 xmemcpy(gopherState->buf, pos, len - (pos - inbuf));
502 gopherState->len = len - (pos - inbuf);
503 }
504
505 break;
506 }
507
508 /* skip one line */
509 pos = (char *) memchr(pos, '\n', len);
510
511 if (pos)
512 pos++;
513
514 }
515
516 /* at this point. We should have one line in buffer to process */
517
518 if (*line == '.') {
519 /* skip it */
520 memset(line, '\0', TEMP_BUF_SIZE);
521 continue;
522 }
523
524 switch (gopherState->conversion) {
525
526 case gopher_ds::HTML_INDEX_RESULT:
527
528 case gopher_ds::HTML_DIR: {
529 tline = line;
530 gtype = *tline++;
531 name = tline;
532 selector = strchr(tline, TAB);
533
534 if (selector) {
535 *selector++ = '\0';
536 host = strchr(selector, TAB);
537
538 if (host) {
539 *host++ = '\0';
540 port = strchr(host, TAB);
541
542 if (port) {
543 char *junk;
544 port[0] = ':';
545 junk = strchr(host, TAB);
546
547 if (junk)
548 *junk++ = 0; /* Chop port */
549 else {
550 junk = strchr(host, '\r');
551
552 if (junk)
553 *junk++ = 0; /* Chop port */
554 else {
555 junk = strchr(host, '\n');
556
557 if (junk)
558 *junk++ = 0; /* Chop port */
559 }
560 }
561
562 if ((port[1] == '0') && (!port[2]))
563 port[0] = 0; /* 0 means none */
564 }
565
566 /* escape a selector here */
567 escaped_selector = xstrdup(rfc1738_escape_part(selector));
568
569 switch (gtype) {
570
571 case GOPHER_DIRECTORY:
572 icon_url = mimeGetIconURL("internal-menu");
573 break;
574
575 case GOPHER_HTML:
576
577 case GOPHER_FILE:
578 icon_url = mimeGetIconURL("internal-text");
579 break;
580
581 case GOPHER_INDEX:
582
583 case GOPHER_CSO:
584 icon_url = mimeGetIconURL("internal-index");
585 break;
586
587 case GOPHER_IMAGE:
588
589 case GOPHER_GIF:
590
591 case GOPHER_PLUS_IMAGE:
592 icon_url = mimeGetIconURL("internal-image");
593 break;
594
595 case GOPHER_SOUND:
596
597 case GOPHER_PLUS_SOUND:
598 icon_url = mimeGetIconURL("internal-sound");
599 break;
600
601 case GOPHER_PLUS_MOVIE:
602 icon_url = mimeGetIconURL("internal-movie");
603 break;
604
605 case GOPHER_TELNET:
606
607 case GOPHER_3270:
608 icon_url = mimeGetIconURL("internal-telnet");
609 break;
610
611 case GOPHER_BIN:
612
613 case GOPHER_MACBINHEX:
614
615 case GOPHER_DOSBIN:
616
617 case GOPHER_UUENCODED:
618 icon_url = mimeGetIconURL("internal-binary");
619 break;
620
621 case GOPHER_INFO:
622 icon_url = NULL;
623 break;
624
625 default:
626 icon_url = mimeGetIconURL("internal-unknown");
627 break;
628 }
629
630 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
631
632 if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
633 if (strlen(escaped_selector) != 0)
634 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
635 icon_url, escaped_selector, rfc1738_escape_part(host),
636 *port ? ":" : "", port, html_quote(name));
637 else
638 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
639 icon_url, rfc1738_escape_part(host), *port ? ":" : "",
640 port, html_quote(name));
641
642 } else if (gtype == GOPHER_INFO) {
643 snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name));
644 } else {
645 if (strncmp(selector, "GET /", 5) == 0) {
646 /* WWW link */
647 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
648 icon_url, host, rfc1738_escape_unescaped(selector + 5), html_quote(name));
649 } else {
650 /* Standard link */
651 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
652 icon_url, host, gtype, escaped_selector, html_quote(name));
653 }
654 }
655
656 safe_free(escaped_selector);
657 outbuf.append(tmpbuf);
658 } else {
659 memset(line, '\0', TEMP_BUF_SIZE);
660 continue;
661 }
662 } else {
663 memset(line, '\0', TEMP_BUF_SIZE);
664 continue;
665 }
666
667 break;
668 } /* HTML_DIR, HTML_INDEX_RESULT */
669
670
671 case gopher_ds::HTML_CSO_RESULT: {
672 if (line[0] == '-') {
673 int code, recno;
674 char *s_code, *s_recno, *result;
675
676 s_code = strtok(line + 1, ":\n");
677 s_recno = strtok(NULL, ":\n");
678 result = strtok(NULL, "\n");
679
680 if (!result)
681 break;
682
683 code = atoi(s_code);
684
685 recno = atoi(s_recno);
686
687 if (code != 200)
688 break;
689
690 if (gopherState->cso_recno != recno) {
691 snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
692 gopherState->cso_recno = recno;
693 } else {
694 snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result));
695 }
696
697 outbuf.append(tmpbuf);
698 break;
699 } else {
700 int code;
701 char *s_code, *result;
702
703 s_code = strtok(line, ":");
704 result = strtok(NULL, "\n");
705
706 if (!result)
707 break;
708
709 code = atoi(s_code);
710
711 switch (code) {
712
713 case 200: {
714 /* OK */
715 /* Do nothing here */
716 break;
717 }
718
719 case 102: /* Number of matches */
720
721 case 501: /* No Match */
722
723 case 502: { /* Too Many Matches */
724 /* Print the message the server returns */
725 snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
726 outbuf.append(tmpbuf);
727 break;
728 }
729
730
731 }
732 }
733
734 } /* HTML_CSO_RESULT */
735
736 default:
737 break; /* do nothing */
738
739 } /* switch */
740
741 } /* while loop */
742
743 if (outbuf.size() > 0) {
744 entry->append(outbuf.rawBuf(), outbuf.size());
745 /* now let start sending stuff to client */
746 entry->flush();
747 }
748
749 outbuf.clean();
750 return;
751 }
752
753 /// \ingroup ServerProtocolGopherInternal
754 static void
755 gopherTimeout(int fd, void *data)
756 {
757 GopherStateData *gopherState = (GopherStateData *)data;
758 StoreEntry *entry = gopherState->entry;
759 debugs(10, 4, "gopherTimeout: FD " << fd << ": '" << entry->url() << "'" );
760
761 gopherState->fwd->fail(errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT, gopherState->fwd->request));
762
763 comm_close(fd);
764 }
765
766 /**
767 \ingroup ServerProtocolGopherInternal
768 * This will be called when data is ready to be read from fd.
769 * Read until error or connection closed.
770 */
771 static void
772 gopherReadReply(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
773 {
774 GopherStateData *gopherState = (GopherStateData *)data;
775 StoreEntry *entry = gopherState->entry;
776 int clen;
777 int bin;
778 size_t read_sz = BUFSIZ;
779 int do_next_read = 0;
780 #if USE_DELAY_POOLS
781 DelayId delayId = entry->mem_obj->mostBytesAllowed();
782 #endif
783
784 /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
785
786 if (flag == COMM_ERR_CLOSING) {
787 return;
788 }
789
790 assert(buf == gopherState->replybuf);
791
792 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
793 comm_close(fd);
794 return;
795 }
796
797 errno = 0;
798 #if USE_DELAY_POOLS
799 read_sz = delayId.bytesWanted(1, read_sz);
800 #endif
801
802 /* leave one space for \0 in gopherToHTML */
803
804 if (flag == COMM_OK && len > 0) {
805 #if USE_DELAY_POOLS
806 delayId.bytesIn(len);
807 #endif
808
809 kb_incr(&statCounter.server.all.kbytes_in, len);
810 kb_incr(&statCounter.server.other.kbytes_in, len);
811 }
812
813 debugs(10, 5, "gopherReadReply: FD " << fd << " read len=" << len);
814
815 if (flag == COMM_OK && len > 0) {
816 commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
817 IOStats.Gopher.reads++;
818
819 for (clen = len - 1, bin = 0; clen; bin++)
820 clen >>= 1;
821
822 IOStats.Gopher.read_hist[bin]++;
823
824 HttpRequest *req = gopherState->fwd->request;
825 if (req->hier.bodyBytesRead < 0)
826 req->hier.bodyBytesRead = 0;
827
828 req->hier.bodyBytesRead += len;
829
830 }
831
832 if (flag != COMM_OK || len < 0) {
833 debugs(50, 1, "gopherReadReply: error reading: " << xstrerror());
834
835 if (ignoreErrno(errno)) {
836 do_next_read = 1;
837 } else {
838 ErrorState *err;
839 err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR, gopherState->fwd->request);
840 err->xerrno = errno;
841 gopherState->fwd->fail(err);
842 comm_close(fd);
843 do_next_read = 0;
844 }
845 } else if (len == 0 && entry->isEmpty()) {
846 gopherState->fwd->fail(errorCon(ERR_ZERO_SIZE_OBJECT, HTTP_SERVICE_UNAVAILABLE, gopherState->fwd->request));
847 comm_close(fd);
848 do_next_read = 0;
849 } else if (len == 0) {
850 /* Connection closed; retrieval done. */
851 /* flush the rest of data in temp buf if there is one. */
852
853 if (gopherState->conversion != gopher_ds::NORMAL)
854 gopherEndHTML(gopherState);
855
856 entry->timestampsSet();
857
858 entry->flush();
859
860 gopherState->fwd->complete();
861
862 comm_close(fd);
863
864 do_next_read = 0;
865 } else {
866 if (gopherState->conversion != gopher_ds::NORMAL) {
867 gopherToHTML(gopherState, buf, len);
868 } else {
869 entry->append(buf, len);
870 }
871
872 do_next_read = 1;
873 }
874
875 if (do_next_read)
876 comm_read(fd, buf, read_sz, gopherReadReply, gopherState);
877
878 return;
879 }
880
881 /**
882 \ingroup ServerProtocolGopherInternal
883 * This will be called when request write is complete. Schedule read of reply.
884 */
885 static void
886 gopherSendComplete(int fd, char *buf, size_t size, comm_err_t errflag, int xerrno, void *data)
887 {
888 GopherStateData *gopherState = (GopherStateData *) data;
889 StoreEntry *entry = gopherState->entry;
890 debugs(10, 5, "gopherSendComplete: FD " << fd << " size: " << size << " errflag: " << errflag);
891
892 if (size > 0) {
893 fd_bytes(fd, size, FD_WRITE);
894 kb_incr(&statCounter.server.all.kbytes_out, size);
895 kb_incr(&statCounter.server.other.kbytes_out, size);
896 }
897
898 if (errflag) {
899 ErrorState *err;
900 err = errorCon(ERR_WRITE_ERROR, HTTP_SERVICE_UNAVAILABLE, gopherState->fwd->request);
901 err->xerrno = errno;
902 err->port = gopherState->fwd->request->port;
903 err->url = xstrdup(entry->url());
904 gopherState->fwd->fail(err);
905 comm_close(fd);
906
907 if (buf)
908 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
909
910 return;
911 }
912
913 /*
914 * OK. We successfully reach remote site. Start MIME typing
915 * stuff. Do it anyway even though request is not HTML type.
916 */
917 entry->buffer();
918
919 gopherMimeCreate(gopherState);
920
921 switch (gopherState->type_id) {
922
923 case GOPHER_DIRECTORY:
924 /* we got to convert it first */
925 gopherState->conversion = gopher_ds::HTML_DIR;
926 gopherState->HTML_header_added = 0;
927 break;
928
929 case GOPHER_INDEX:
930 /* we got to convert it first */
931 gopherState->conversion = gopher_ds::HTML_INDEX_RESULT;
932 gopherState->HTML_header_added = 0;
933 break;
934
935 case GOPHER_CSO:
936 /* we got to convert it first */
937 gopherState->conversion = gopher_ds::HTML_CSO_RESULT;
938 gopherState->cso_recno = 0;
939 gopherState->HTML_header_added = 0;
940 break;
941
942 default:
943 gopherState->conversion = gopher_ds::NORMAL;
944 entry->flush();
945 }
946
947 /* Schedule read reply. */
948 AsyncCall::Pointer call = commCbCall(10,5, "gopherReadReply",
949 CommIoCbPtrFun(gopherReadReply, gopherState));
950 entry->delayAwareRead(fd, gopherState->replybuf, BUFSIZ, call);
951
952 if (buf)
953 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
954 }
955
956 /**
957 \ingroup ServerProtocolGopherInternal
958 * This will be called when connect completes. Write request.
959 */
960 static void
961 gopherSendRequest(int fd, void *data)
962 {
963 GopherStateData *gopherState = (GopherStateData *)data;
964 char *buf = (char *)memAllocate(MEM_4K_BUF);
965
966 if (gopherState->type_id == GOPHER_CSO) {
967 const char *t = strchr(gopherState->request, '?');
968
969 if (t != NULL)
970 t++; /* skip the ? */
971 else
972 t = "";
973
974 snprintf(buf, 4096, "query %s\r\nquit\r\n", t);
975 } else if (gopherState->type_id == GOPHER_INDEX) {
976 char *t = strchr(gopherState->request, '?');
977
978 if (t != NULL)
979 *t = '\t';
980
981 snprintf(buf, 4096, "%s\r\n", gopherState->request);
982 } else {
983 snprintf(buf, 4096, "%s\r\n", gopherState->request);
984 }
985
986 debugs(10, 5, "gopherSendRequest: FD " << fd);
987 AsyncCall::Pointer call = commCbCall(5,5, "gopherSendComplete",
988 CommIoCbPtrFun(gopherSendComplete, gopherState));
989 Comm::Write(fd, buf, strlen(buf), call, NULL);
990
991 if (EBIT_TEST(gopherState->entry->flags, ENTRY_CACHABLE))
992 gopherState->entry->setPublicKey(); /* Make it public */
993 }
994
995 /// \ingroup ServerProtocolGopherInternal
996 CBDATA_TYPE(GopherStateData);
997
998 /// \ingroup ServerProtocolGopherAPI
999 void
1000 gopherStart(FwdState * fwd)
1001 {
1002 int fd = fwd->server_fd;
1003 StoreEntry *entry = fwd->entry;
1004 GopherStateData *gopherState;
1005 CBDATA_INIT_TYPE(GopherStateData);
1006 gopherState = cbdataAlloc(GopherStateData);
1007 gopherState->buf = (char *)memAllocate(MEM_4K_BUF);
1008
1009 entry->lock();
1010 gopherState->entry = entry;
1011
1012 gopherState->fwd = fwd;
1013
1014 debugs(10, 3, "gopherStart: " << entry->url() );
1015
1016 statCounter.server.all.requests++;
1017
1018 statCounter.server.other.requests++;
1019
1020 /* Parse url. */
1021 gopher_request_parse(fwd->request,
1022 &gopherState->type_id, gopherState->request);
1023
1024 comm_add_close_handler(fd, gopherStateFree, gopherState);
1025
1026 if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
1027 && (strchr(gopherState->request, '?') == NULL)) {
1028 /* Index URL without query word */
1029 /* We have to generate search page back to client. No need for connection */
1030 gopherMimeCreate(gopherState);
1031
1032 if (gopherState->type_id == GOPHER_INDEX) {
1033 gopherState->conversion = gopher_ds::HTML_INDEX_PAGE;
1034 } else {
1035 if (gopherState->type_id == GOPHER_CSO) {
1036 gopherState->conversion = gopher_ds::HTML_CSO_PAGE;
1037 } else {
1038 gopherState->conversion = gopher_ds::HTML_INDEX_PAGE;
1039 }
1040 }
1041
1042 gopherToHTML(gopherState, (char *) NULL, 0);
1043 fwd->complete();
1044 comm_close(fd);
1045 return;
1046 }
1047
1048 gopherState->fd = fd;
1049 gopherState->fwd = fwd;
1050 gopherSendRequest(fd, gopherState);
1051 commSetTimeout(fd, Config.Timeout.read, gopherTimeout, gopherState);
1052 }