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