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