]> git.ipfire.org Git - thirdparty/squid.git/blame - src/gopher.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / gopher.cc
CommitLineData
30a4f2a8 1/*
4ac4a490 2 * Copyright (C) 1996-2017 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
AJ
243 assert(entry->isEmpty());
244 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
245
246 HttpReply *reply = new HttpReply;
247 entry->buffer();
955394ce 248 reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, -1, -1, -2);
9969d2a8 249 if (mime_enc)
789217a2 250 reply->header.putStr(Http::HdrType::CONTENT_ENCODING, mime_enc);
9969d2a8
AJ
251
252 entry->replaceHttpReply(reply);
88df846b 253 gopherState->reply_ = reply;
090089c4 254}
255
63be0a78 256/**
63be0a78 257 * Parse a gopher request into components. By Anawat.
258 */
09fb5b61 259static void
190154cf 260gopher_request_parse(const HttpRequest * req, char *type_id, char *request)
090089c4 261{
51b5dcf5 262 ::Parser::Tokenizer tok(req->url.path());
09fb5b61 263
264 if (request)
51b5dcf5 265 *request = 0;
09fb5b61 266
51b5dcf5 267 tok.skip('/'); // ignore failures? path could be ab-empty
09fb5b61 268
51b5dcf5 269 if (tok.atEnd()) {
62e76326 270 *type_id = GOPHER_DIRECTORY;
271 return;
09fb5b61 272 }
62e76326 273
51b5dcf5
AJ
274 static const CharacterSet anyByte("UTF-8",0x00, 0xFF);
275
276 SBuf typeId;
277 (void)tok.prefix(typeId, anyByte, 1); // never fails since !atEnd()
278 *type_id = typeId[0];
09fb5b61 279
280 if (request) {
3f0e38d6 281 SBufToCstring(request, tok.remaining().substr(0, MAX_URL-1));
62e76326 282 /* convert %xx to char */
2c5d57e0 283 rfc1738_unescape(request);
090089c4 284 }
090089c4 285}
286
63be0a78 287/**
63be0a78 288 * Parse the request to determine whether it is cachable.
289 *
f53969cc
SM
290 * \param req Request data.
291 * \retval 0 Not cachable.
292 * \retval 1 Cachable.
63be0a78 293 */
b8d8561b 294int
190154cf 295gopherCachable(const HttpRequest * req)
090089c4 296{
090089c4 297 int cachable = 1;
09fb5b61 298 char type_id;
090089c4 299 /* parse to see type */
09fb5b61 300 gopher_request_parse(req,
62e76326 301 &type_id,
302 NULL);
303
09fb5b61 304 switch (type_id) {
62e76326 305
090089c4 306 case GOPHER_INDEX:
62e76326 307
090089c4 308 case GOPHER_CSO:
62e76326 309
090089c4 310 case GOPHER_TELNET:
62e76326 311
090089c4 312 case GOPHER_3270:
62e76326 313 cachable = 0;
314 break;
315
090089c4 316 default:
62e76326 317 cachable = 1;
090089c4 318 }
62e76326 319
090089c4 320 return cachable;
321}
322
eb7d6bd6 323static void
324gopherHTMLHeader(StoreEntry * e, const char *title, const char *substring)
325{
df339671 326 storeAppendPrintf(e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
eb7d6bd6 327 storeAppendPrintf(e, "<HTML><HEAD><TITLE>");
328 storeAppendPrintf(e, title, substring);
df339671 329 storeAppendPrintf(e, "</TITLE>");
d8c0128e 330 storeAppendPrintf(e, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
df339671 331 storeAppendPrintf(e, "</HEAD>\n<BODY><H1>");
eb7d6bd6 332 storeAppendPrintf(e, title, substring);
333 storeAppendPrintf(e, "</H1>\n");
334}
335
336static void
337gopherHTMLFooter(StoreEntry * e)
338{
df339671 339 storeAppendPrintf(e, "<HR noshade size=\"1px\">\n");
eb7d6bd6 340 storeAppendPrintf(e, "<ADDRESS>\n");
341 storeAppendPrintf(e, "Generated %s by %s (%s)\n",
62e76326 342 mkrfc1123(squid_curtime),
343 getMyHostname(),
d3caee79 344 visible_appname_string);
eb7d6bd6 345 storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
346}
347
b8d8561b 348static void
582b6456 349gopherEndHTML(GopherStateData * gopherState)
090089c4 350{
eb7d6bd6 351 StoreEntry *e = gopherState->entry;
62e76326 352
d7443679 353 if (!gopherState->HTML_header_added) {
62e76326 354 gopherHTMLHeader(e, "Server Return Nothing", NULL);
355 storeAppendPrintf(e, "<P>The Gopher query resulted in a blank response</P>");
d7443679 356 } else if (gopherState->HTML_pre) {
62e76326 357 storeAppendPrintf(e, "</PRE>\n");
eb7d6bd6 358 }
62e76326 359
eb7d6bd6 360 gopherHTMLFooter(e);
090089c4 361}
362
63be0a78 363/**
63be0a78 364 * Convert Gopher to HTML.
2327e7c0 365 *
63be0a78 366 * Borrow part of code from libwww2 came with Mosaic distribution.
367 */
b8d8561b 368static void
582b6456 369gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
090089c4 370{
371 char *pos = inbuf;
372 char *lpos = NULL;
373 char *tline = NULL;
95d659f0 374 LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
375 LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
090089c4 376 char *name = NULL;
377 char *selector = NULL;
378 char *host = NULL;
379 char *port = NULL;
380 char *escaped_selector = NULL;
d20b1cd0 381 const char *icon_url = NULL;
090089c4 382 char gtype;
383 StoreEntry *entry = NULL;
384
53d02438 385 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
386 memset(line, '\0', TEMP_BUF_SIZE);
090089c4 387
582b6456 388 entry = gopherState->entry;
090089c4 389
2327e7c0 390 if (gopherState->conversion == GopherStateData::HTML_INDEX_PAGE) {
3900307b 391 char *html_url = html_quote(entry->url());
62e76326 392 gopherHTMLHeader(entry, "Gopher Index %s", html_url);
393 storeAppendPrintf(entry,
394 "<p>This is a searchable Gopher index. Use the search\n"
395 "function of your browser to enter search terms.\n"
396 "<ISINDEX>\n");
397 gopherHTMLFooter(entry);
398 /* now let start sending stuff to client */
3900307b 399 entry->flush();
d7443679 400 gopherState->HTML_header_added = 1;
62e76326 401
402 return;
090089c4 403 }
62e76326 404
2327e7c0 405 if (gopherState->conversion == GopherStateData::HTML_CSO_PAGE) {
3900307b 406 char *html_url = html_quote(entry->url());
62e76326 407 gopherHTMLHeader(entry, "CSO Search of %s", html_url);
408 storeAppendPrintf(entry,
409 "<P>A CSO database usually contains a phonebook or\n"
410 "directory. Use the search function of your browser to enter\n"
411 "search terms.</P><ISINDEX>\n");
412 gopherHTMLFooter(entry);
413 /* now let start sending stuff to client */
3900307b 414 entry->flush();
d7443679 415 gopherState->HTML_header_added = 1;
62e76326 416
417 return;
090089c4 418 }
62e76326 419
30abd221 420 String outbuf;
090089c4 421
582b6456 422 if (!gopherState->HTML_header_added) {
2327e7c0 423 if (gopherState->conversion == GopherStateData::HTML_CSO_RESULT)
62e76326 424 gopherHTMLHeader(entry, "CSO Search Result", NULL);
425 else
426 gopherHTMLHeader(entry, "Gopher Menu", NULL);
427
428 outbuf.append ("<PRE>");
429
430 gopherState->HTML_header_added = 1;
d7443679 431
432 gopherState->HTML_pre = 1;
090089c4 433 }
62e76326 434
7592589a
HN
435 while (pos < inbuf + len) {
436 int llen;
437 int left = len - (pos - inbuf);
438 lpos = (char *)memchr(pos, '\n', left);
439 if (lpos) {
95dc7ff4 440 ++lpos; /* Next line is after \n */
7592589a
HN
441 llen = lpos - pos;
442 } else {
443 llen = left;
444 }
445 if (gopherState->len + llen >= TEMP_BUF_SIZE) {
e0236918 446 debugs(10, DBG_IMPORTANT, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry->url() );
7592589a
HN
447 llen = TEMP_BUF_SIZE - gopherState->len - 1;
448 }
449 if (!lpos) {
450 /* there is no complete line in inbuf */
451 /* copy it to temp buffer */
452 /* note: llen is adjusted above */
453 memcpy(gopherState->buf + gopherState->len, pos, llen);
454 gopherState->len += llen;
455 break;
456 }
62e76326 457 if (gopherState->len != 0) {
458 /* there is something left from last tx. */
7592589a
HN
459 memcpy(line, gopherState->buf, gopherState->len);
460 memcpy(line + gopherState->len, pos, llen);
461 llen += gopherState->len;
62e76326 462 gopherState->len = 0;
62e76326 463 } else {
7592589a 464 memcpy(line, pos, llen);
62e76326 465 }
7592589a
HN
466 line[llen + 1] = '\0';
467 /* move input to next line */
468 pos = lpos;
62e76326 469
470 /* at this point. We should have one line in buffer to process */
471
472 if (*line == '.') {
473 /* skip it */
474 memset(line, '\0', TEMP_BUF_SIZE);
475 continue;
476 }
477
478 switch (gopherState->conversion) {
479
2327e7c0 480 case GopherStateData::HTML_INDEX_RESULT:
62e76326 481
2327e7c0 482 case GopherStateData::HTML_DIR: {
26ac0430 483 tline = line;
aec55359
FC
484 gtype = *tline;
485 ++tline;
26ac0430
AJ
486 name = tline;
487 selector = strchr(tline, TAB);
488
489 if (selector) {
a38ec4b1
FC
490 *selector = '\0';
491 ++selector;
26ac0430 492 host = strchr(selector, TAB);
62e76326 493
26ac0430 494 if (host) {
a38ec4b1
FC
495 *host = '\0';
496 ++host;
26ac0430 497 port = strchr(host, TAB);
62e76326 498
26ac0430
AJ
499 if (port) {
500 char *junk;
501 port[0] = ':';
502 junk = strchr(host, TAB);
62e76326 503
26ac0430 504 if (junk)
f53969cc 505 *junk++ = 0; /* Chop port */
26ac0430
AJ
506 else {
507 junk = strchr(host, '\r');
62e76326 508
509 if (junk)
f53969cc 510 *junk++ = 0; /* Chop port */
62e76326 511 else {
26ac0430 512 junk = strchr(host, '\n');
62e76326 513
514 if (junk)
f53969cc 515 *junk++ = 0; /* Chop port */
62e76326 516 }
62e76326 517 }
518
26ac0430 519 if ((port[1] == '0') && (!port[2]))
f53969cc 520 port[0] = 0; /* 0 means none */
26ac0430 521 }
62e76326 522
26ac0430
AJ
523 /* escape a selector here */
524 escaped_selector = xstrdup(rfc1738_escape_part(selector));
62e76326 525
26ac0430 526 switch (gtype) {
62e76326 527
26ac0430
AJ
528 case GOPHER_DIRECTORY:
529 icon_url = mimeGetIconURL("internal-menu");
530 break;
531
532 case GOPHER_HTML:
62e76326 533
26ac0430
AJ
534 case GOPHER_FILE:
535 icon_url = mimeGetIconURL("internal-text");
536 break;
62e76326 537
26ac0430 538 case GOPHER_INDEX:
62e76326 539
26ac0430
AJ
540 case GOPHER_CSO:
541 icon_url = mimeGetIconURL("internal-index");
542 break;
62e76326 543
26ac0430 544 case GOPHER_IMAGE:
62e76326 545
26ac0430 546 case GOPHER_GIF:
62e76326 547
26ac0430
AJ
548 case GOPHER_PLUS_IMAGE:
549 icon_url = mimeGetIconURL("internal-image");
550 break;
62e76326 551
26ac0430 552 case GOPHER_SOUND:
62e76326 553
26ac0430
AJ
554 case GOPHER_PLUS_SOUND:
555 icon_url = mimeGetIconURL("internal-sound");
556 break;
62e76326 557
26ac0430
AJ
558 case GOPHER_PLUS_MOVIE:
559 icon_url = mimeGetIconURL("internal-movie");
560 break;
62e76326 561
26ac0430 562 case GOPHER_TELNET:
62e76326 563
26ac0430
AJ
564 case GOPHER_3270:
565 icon_url = mimeGetIconURL("internal-telnet");
566 break;
62e76326 567
26ac0430 568 case GOPHER_BIN:
62e76326 569
26ac0430 570 case GOPHER_MACBINHEX:
62e76326 571
26ac0430 572 case GOPHER_DOSBIN:
62e76326 573
26ac0430
AJ
574 case GOPHER_UUENCODED:
575 icon_url = mimeGetIconURL("internal-binary");
576 break;
62e76326 577
26ac0430
AJ
578 case GOPHER_INFO:
579 icon_url = NULL;
580 break;
62e76326 581
26ac0430
AJ
582 default:
583 icon_url = mimeGetIconURL("internal-unknown");
584 break;
585 }
62e76326 586
26ac0430 587 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
62e76326 588
26ac0430
AJ
589 if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
590 if (strlen(escaped_selector) != 0)
591 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
592 icon_url, escaped_selector, rfc1738_escape_part(host),
593 *port ? ":" : "", port, html_quote(name));
594 else
595 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
596 icon_url, rfc1738_escape_part(host), *port ? ":" : "",
597 port, html_quote(name));
62e76326 598
26ac0430
AJ
599 } else if (gtype == GOPHER_INFO) {
600 snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name));
601 } else {
602 if (strncmp(selector, "GET /", 5) == 0) {
603 /* WWW link */
604 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
605 icon_url, host, rfc1738_escape_unescaped(selector + 5), html_quote(name));
62e76326 606 } else {
26ac0430
AJ
607 /* Standard link */
608 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
609 icon_url, host, gtype, escaped_selector, html_quote(name));
62e76326 610 }
62e76326 611 }
26ac0430
AJ
612
613 safe_free(escaped_selector);
614 outbuf.append(tmpbuf);
62e76326 615 } else {
616 memset(line, '\0', TEMP_BUF_SIZE);
617 continue;
618 }
26ac0430
AJ
619 } else {
620 memset(line, '\0', TEMP_BUF_SIZE);
621 continue;
622 }
62e76326 623
26ac0430 624 break;
f53969cc 625 } /* HTML_DIR, HTML_INDEX_RESULT */
62e76326 626
2327e7c0 627 case GopherStateData::HTML_CSO_RESULT: {
26ac0430
AJ
628 if (line[0] == '-') {
629 int code, recno;
630 char *s_code, *s_recno, *result;
62e76326 631
26ac0430
AJ
632 s_code = strtok(line + 1, ":\n");
633 s_recno = strtok(NULL, ":\n");
634 result = strtok(NULL, "\n");
62e76326 635
26ac0430
AJ
636 if (!result)
637 break;
62e76326 638
26ac0430 639 code = atoi(s_code);
62e76326 640
26ac0430 641 recno = atoi(s_recno);
62e76326 642
26ac0430 643 if (code != 200)
62e76326 644 break;
26ac0430
AJ
645
646 if (gopherState->cso_recno != recno) {
647 snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
648 gopherState->cso_recno = recno;
62e76326 649 } else {
26ac0430
AJ
650 snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result));
651 }
652
653 outbuf.append(tmpbuf);
654 break;
655 } else {
656 int code;
657 char *s_code, *result;
62e76326 658
26ac0430
AJ
659 s_code = strtok(line, ":");
660 result = strtok(NULL, "\n");
62e76326 661
26ac0430
AJ
662 if (!result)
663 break;
62e76326 664
26ac0430 665 code = atoi(s_code);
62e76326 666
26ac0430 667 switch (code) {
62e76326 668
26ac0430
AJ
669 case 200: {
670 /* OK */
671 /* Do nothing here */
672 break;
673 }
62e76326 674
f53969cc 675 case 102: /* Number of matches */
62e76326 676
f53969cc 677 case 501: /* No Match */
62e76326 678
f53969cc 679 case 502: { /* Too Many Matches */
26ac0430
AJ
680 /* Print the message the server returns */
681 snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
682 outbuf.append(tmpbuf);
683 break;
684 }
62e76326 685
62e76326 686 }
26ac0430 687 }
62e76326 688
f53969cc 689 } /* HTML_CSO_RESULT */
62e76326 690
691 default:
f53969cc 692 break; /* do nothing */
62e76326 693
f53969cc 694 } /* switch */
090089c4 695
f53969cc 696 } /* while loop */
090089c4 697
528b2c61 698 if (outbuf.size() > 0) {
d53b3f6d 699 entry->append(outbuf.rawBuf(), outbuf.size());
62e76326 700 /* now let start sending stuff to client */
3900307b 701 entry->flush();
090089c4 702 }
62e76326 703
30abd221 704 outbuf.clean();
090089c4 705 return;
706}
707
582b6456 708static void
8d77a37c 709gopherTimeout(const CommTimeoutCbParams &io)
090089c4 710{
8d77a37c
AJ
711 GopherStateData *gopherState = static_cast<GopherStateData *>(io.data);
712 debugs(10, 4, HERE << io.conn << ": '" << gopherState->entry->url() << "'" );
62e76326 713
f11c8e2f 714 gopherState->fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGatewayTimeout, gopherState->fwd->request));
62e76326 715
8d77a37c
AJ
716 if (Comm::IsConnOpen(io.conn))
717 io.conn->close();
090089c4 718}
719
63be0a78 720/**
63be0a78 721 * This will be called when data is ready to be read from fd.
722 * Read until error or connection closed.
723 */
b8d8561b 724static void
c8407295 725gopherReadReply(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm::Flag flag, int xerrno, void *data)
090089c4 726{
e6ccf245 727 GopherStateData *gopherState = (GopherStateData *)data;
bfcaf585 728 StoreEntry *entry = gopherState->entry;
090089c4 729 int clen;
56fa4cad 730 int bin;
c4b7a5a9 731 size_t read_sz = BUFSIZ;
9a0a18de 732#if USE_DELAY_POOLS
b67e2c8c 733 DelayId delayId = entry->mem_obj->mostBytesAllowed();
447e176b 734#endif
c4b7a5a9 735
c8407295 736 /* Bail out early on Comm::ERR_CLOSING - close handlers will tidy up for us */
62e76326 737
c8407295 738 if (flag == Comm::ERR_CLOSING) {
c4b7a5a9 739 return;
740 }
741
742 assert(buf == gopherState->replybuf);
62e76326 743
e92e4e44 744 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
e0d28505 745 gopherState->serverConn->close();
62e76326 746 return;
e92e4e44 747 }
c4b7a5a9 748
9a0a18de 749#if USE_DELAY_POOLS
b67e2c8c 750 read_sz = delayId.bytesWanted(1, read_sz);
447e176b 751#endif
c4b7a5a9 752
56fa4cad 753 /* leave one space for \0 in gopherToHTML */
62e76326 754
c8407295 755 if (flag == Comm::OK && len > 0) {
9a0a18de 756#if USE_DELAY_POOLS
62e76326 757 delayId.bytesIn(len);
447e176b 758#endif
62e76326 759
a0864754
AJ
760 statCounter.server.all.kbytes_in += len;
761 statCounter.server.other.kbytes_in += len;
ee1679df 762 }
62e76326 763
1b76e6c1 764 debugs(10, 5, HERE << conn << " read len=" << len);
62e76326 765
c8407295 766 if (flag == Comm::OK && len > 0) {
8d77a37c
AJ
767 AsyncCall::Pointer nil;
768 commSetConnTimeout(conn, Config.Timeout.read, nil);
95dc7ff4 769 ++IOStats.Gopher.reads;
62e76326 770
95dc7ff4 771 for (clen = len - 1, bin = 0; clen; ++bin)
62e76326 772 clen >>= 1;
773
95dc7ff4 774 ++IOStats.Gopher.read_hist[bin];
bae917ac
CT
775
776 HttpRequest *req = gopherState->fwd->request;
88df846b 777 if (req->hier.bodyBytesRead < 0) {
bae917ac 778 req->hier.bodyBytesRead = 0;
88df846b
CT
779 // first bytes read, update Reply flags:
780 gopherState->reply_->sources |= HttpMsg::srcGopher;
781 }
dcaab393 782
bae917ac 783 req->hier.bodyBytesRead += len;
56fa4cad 784 }
62e76326 785
c8407295 786 if (flag != Comm::OK) {
b69e9ffa 787 debugs(50, DBG_IMPORTANT, MYNAME << "error reading: " << xstrerr(xerrno));
62e76326 788
129fe2a1 789 if (ignoreErrno(xerrno)) {
1b76e6c1
AJ
790 AsyncCall::Pointer call = commCbCall(5,4, "gopherReadReply",
791 CommIoCbPtrFun(gopherReadReply, gopherState));
792 comm_read(conn, buf, read_sz, call);
6cae5db1 793 } else {
955394ce 794 ErrorState *err = new ErrorState(ERR_READ_ERROR, Http::scInternalServerError, gopherState->fwd->request);
129fe2a1 795 err->xerrno = xerrno;
b6b6f466 796 gopherState->fwd->fail(err);
e0d28505 797 gopherState->serverConn->close();
62e76326 798 }
528b2c61 799 } else if (len == 0 && entry->isEmpty()) {
955394ce 800 gopherState->fwd->fail(new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, gopherState->fwd->request));
e0d28505 801 gopherState->serverConn->close();
090089c4 802 } else if (len == 0) {
62e76326 803 /* Connection closed; retrieval done. */
bb790702 804 /* flush the rest of data in temp buf if there is one. */
62e76326 805
2327e7c0 806 if (gopherState->conversion != GopherStateData::NORMAL)
62e76326 807 gopherEndHTML(gopherState);
808
3900307b 809 entry->timestampsSet();
3900307b 810 entry->flush();
b6b6f466 811 gopherState->fwd->complete();
e0d28505 812 gopherState->serverConn->close();
090089c4 813 } else {
2327e7c0 814 if (gopherState->conversion != GopherStateData::NORMAL) {
62e76326 815 gopherToHTML(gopherState, buf, len);
816 } else {
3900307b 817 entry->append(buf, len);
62e76326 818 }
abd8f140
AJ
819 AsyncCall::Pointer call = commCbCall(5,4, "gopherReadReply",
820 CommIoCbPtrFun(gopherReadReply, gopherState));
821 comm_read(conn, buf, read_sz, call);
822 }
090089c4 823}
824
63be0a78 825/**
63be0a78 826 * This will be called when request write is complete. Schedule read of reply.
827 */
b8d8561b 828static void
c8407295 829gopherSendComplete(const Comm::ConnectionPointer &conn, char *buf, size_t size, Comm::Flag errflag, int xerrno, void *data)
090089c4 830{
56fa4cad 831 GopherStateData *gopherState = (GopherStateData *) data;
70a9dab4 832 StoreEntry *entry = gopherState->entry;
e0d28505 833 debugs(10, 5, HERE << conn << " size: " << size << " errflag: " << errflag);
62e76326 834
ee1679df 835 if (size > 0) {
e0d28505 836 fd_bytes(conn->fd, size, FD_WRITE);
a0864754
AJ
837 statCounter.server.all.kbytes_out += size;
838 statCounter.server.other.kbytes_out += size;
ee1679df 839 }
62e76326 840
090089c4 841 if (errflag) {
62e76326 842 ErrorState *err;
955394ce 843 err = new ErrorState(ERR_WRITE_ERROR, Http::scServiceUnavailable, gopherState->fwd->request);
129fe2a1 844 err->xerrno = xerrno;
5c51bffb 845 err->port = gopherState->fwd->request->url.port();
3900307b 846 err->url = xstrdup(entry->url());
b6b6f466 847 gopherState->fwd->fail(err);
e0d28505 848 gopherState->serverConn->close();
62e76326 849
850 if (buf)
f53969cc 851 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
62e76326 852
853 return;
090089c4 854 }
62e76326 855
856 /*
090089c4 857 * OK. We successfully reach remote site. Start MIME typing
858 * stuff. Do it anyway even though request is not HTML type.
859 */
3900307b 860 entry->buffer();
b66315e4 861
30a4f2a8 862 gopherMimeCreate(gopherState);
62e76326 863
a47b9029 864 switch (gopherState->type_id) {
62e76326 865
a47b9029 866 case GOPHER_DIRECTORY:
62e76326 867 /* we got to convert it first */
2327e7c0 868 gopherState->conversion = GopherStateData::HTML_DIR;
62e76326 869 gopherState->HTML_header_added = 0;
870 break;
871
a47b9029 872 case GOPHER_INDEX:
62e76326 873 /* we got to convert it first */
2327e7c0 874 gopherState->conversion = GopherStateData::HTML_INDEX_RESULT;
62e76326 875 gopherState->HTML_header_added = 0;
876 break;
877
a47b9029 878 case GOPHER_CSO:
62e76326 879 /* we got to convert it first */
2327e7c0 880 gopherState->conversion = GopherStateData::HTML_CSO_RESULT;
62e76326 881 gopherState->cso_recno = 0;
882 gopherState->HTML_header_added = 0;
883 break;
884
a47b9029 885 default:
2327e7c0 886 gopherState->conversion = GopherStateData::NORMAL;
3900307b 887 entry->flush();
86101e40 888 }
62e76326 889
090089c4 890 /* Schedule read reply. */
8d77a37c 891 AsyncCall::Pointer call = commCbCall(5,5, "gopherReadReply",
26ac0430 892 CommIoCbPtrFun(gopherReadReply, gopherState));
3e4bebf8 893 entry->delayAwareRead(conn, gopherState->replybuf, BUFSIZ, call);
62e76326 894
090089c4 895 if (buf)
f53969cc 896 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
090089c4 897}
898
63be0a78 899/**
63be0a78 900 * This will be called when connect completes. Write request.
901 */
b8d8561b 902static void
ced8def3 903gopherSendRequest(int, void *data)
090089c4 904{
e6ccf245 905 GopherStateData *gopherState = (GopherStateData *)data;
906 char *buf = (char *)memAllocate(MEM_4K_BUF);
62e76326 907
582b6456 908 if (gopherState->type_id == GOPHER_CSO) {
62e76326 909 const char *t = strchr(gopherState->request, '?');
910
911 if (t != NULL)
f53969cc 912 ++t; /* skip the ? */
62e76326 913 else
914 t = "";
915
916 snprintf(buf, 4096, "query %s\r\nquit\r\n", t);
582b6456 917 } else if (gopherState->type_id == GOPHER_INDEX) {
62e76326 918 char *t = strchr(gopherState->request, '?');
919
920 if (t != NULL)
921 *t = '\t';
922
923 snprintf(buf, 4096, "%s\r\n", gopherState->request);
090089c4 924 } else {
62e76326 925 snprintf(buf, 4096, "%s\r\n", gopherState->request);
090089c4 926 }
62e76326 927
e0d28505 928 debugs(10, 5, HERE << gopherState->serverConn);
ec41b64c
AJ
929 AsyncCall::Pointer call = commCbCall(5,5, "gopherSendComplete",
930 CommIoCbPtrFun(gopherSendComplete, gopherState));
b0388924 931 Comm::Write(gopherState->serverConn, buf, strlen(buf), call, NULL);
62e76326 932
6919be24 933 gopherState->entry->makePublic();
090089c4 934}
935
770f051d 936void
b6b6f466 937gopherStart(FwdState * fwd)
090089c4 938{
2327e7c0 939 GopherStateData *gopherState = new GopherStateData(fwd);
34266cde 940
2327e7c0 941 debugs(10, 3, gopherState->entry->url());
34266cde 942
95dc7ff4 943 ++ statCounter.server.all.requests;
34266cde 944
95dc7ff4 945 ++ statCounter.server.other.requests;
34266cde 946
090089c4 947 /* Parse url. */
b6b6f466 948 gopher_request_parse(fwd->request,
62e76326 949 &gopherState->type_id, gopherState->request);
34266cde 950
5229395c 951 comm_add_close_handler(fwd->serverConnection()->fd, gopherStateFree, gopherState);
62e76326 952
582b6456 953 if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
62e76326 954 && (strchr(gopherState->request, '?') == NULL)) {
955 /* Index URL without query word */
956 /* We have to generate search page back to client. No need for connection */
957 gopherMimeCreate(gopherState);
958
959 if (gopherState->type_id == GOPHER_INDEX) {
2327e7c0 960 gopherState->conversion = GopherStateData::HTML_INDEX_PAGE;
62e76326 961 } else {
962 if (gopherState->type_id == GOPHER_CSO) {
2327e7c0 963 gopherState->conversion = GopherStateData::HTML_CSO_PAGE;
62e76326 964 } else {
2327e7c0 965 gopherState->conversion = GopherStateData::HTML_INDEX_PAGE;
62e76326 966 }
967 }
968
969 gopherToHTML(gopherState, (char *) NULL, 0);
b6b6f466 970 fwd->complete();
62e76326 971 return;
090089c4 972 }
62e76326 973
e0d28505 974 gopherState->serverConn = fwd->serverConnection();
5229395c 975 gopherSendRequest(fwd->serverConnection()->fd, gopherState);
8d77a37c 976 AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "gopherTimeout",
dc49061a 977 CommTimeoutCbPtrFun(gopherTimeout, gopherState));
8d77a37c 978 commSetConnTimeout(fwd->serverConnection(), Config.Timeout.read, timeoutCall);
e5f6c5c2 979}
f53969cc 980