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