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