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