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