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