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