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