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