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