]> git.ipfire.org Git - thirdparty/squid.git/blame - src/gopher.cc
Send HTTP/1.1 on CONNECT responses
[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 */
bfcaf585 137 int fd;
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
79d39a72 163gopherStateFree(int fdnotused, 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;
582b6456 757 StoreEntry *entry = gopherState->entry;
bf8fe701 758 debugs(10, 4, "gopherTimeout: FD " << fd << ": '" << entry->url() << "'" );
62e76326 759
2cc81f1f 760 gopherState->fwd->fail(errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT, gopherState->fwd->request));
62e76326 761
51fa90db 762 comm_close(fd);
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
c4b7a5a9 771gopherReadReply(int fd, 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)) {
62e76326 793 comm_close(fd);
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
c4b7a5a9 805 if (flag == COMM_OK && len > 0) {
447e176b 806#if DELAY_POOLS
62e76326 807 delayId.bytesIn(len);
447e176b 808#endif
62e76326 809
810 kb_incr(&statCounter.server.all.kbytes_in, len);
811 kb_incr(&statCounter.server.other.kbytes_in, len);
ee1679df 812 }
62e76326 813
4a7a3d56 814 debugs(10, 5, "gopherReadReply: FD " << fd << " read len=" << len);
62e76326 815
c4b7a5a9 816 if (flag == COMM_OK && len > 0) {
62e76326 817 commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
818 IOStats.Gopher.reads++;
819
820 for (clen = len - 1, bin = 0; clen; bin++)
821 clen >>= 1;
822
823 IOStats.Gopher.read_hist[bin]++;
bae917ac
CT
824
825 HttpRequest *req = gopherState->fwd->request;
dcaab393 826 if (req->hier.bodyBytesRead < 0)
bae917ac 827 req->hier.bodyBytesRead = 0;
dcaab393 828
bae917ac
CT
829 req->hier.bodyBytesRead += len;
830
56fa4cad 831 }
62e76326 832
c4b7a5a9 833 if (flag != COMM_OK || len < 0) {
bf8fe701 834 debugs(50, 1, "gopherReadReply: error reading: " << xstrerror());
62e76326 835
836 if (ignoreErrno(errno)) {
c4b7a5a9 837 do_next_read = 1;
6cae5db1 838 } else {
62e76326 839 ErrorState *err;
2cc81f1f 840 err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR, gopherState->fwd->request);
62e76326 841 err->xerrno = errno;
b6b6f466 842 gopherState->fwd->fail(err);
62e76326 843 comm_close(fd);
c4b7a5a9 844 do_next_read = 0;
62e76326 845 }
528b2c61 846 } else if (len == 0 && entry->isEmpty()) {
2cc81f1f 847 gopherState->fwd->fail(errorCon(ERR_ZERO_SIZE_OBJECT, HTTP_SERVICE_UNAVAILABLE, gopherState->fwd->request));
62e76326 848 comm_close(fd);
c4b7a5a9 849 do_next_read = 0;
090089c4 850 } else if (len == 0) {
62e76326 851 /* Connection closed; retrieval done. */
bb790702 852 /* flush the rest of data in temp buf if there is one. */
62e76326 853
854 if (gopherState->conversion != gopher_ds::NORMAL)
855 gopherEndHTML(gopherState);
856
3900307b 857 entry->timestampsSet();
62e76326 858
3900307b 859 entry->flush();
62e76326 860
b6b6f466 861 gopherState->fwd->complete();
62e76326 862
863 comm_close(fd);
864
c4b7a5a9 865 do_next_read = 0;
090089c4 866 } else {
62e76326 867 if (gopherState->conversion != gopher_ds::NORMAL) {
868 gopherToHTML(gopherState, buf, len);
869 } else {
3900307b 870 entry->append(buf, len);
62e76326 871 }
872
c4b7a5a9 873 do_next_read = 1;
090089c4 874 }
62e76326 875
c4b7a5a9 876 if (do_next_read)
877 comm_read(fd, buf, read_sz, gopherReadReply, gopherState);
26ac0430 878
56fa4cad 879 return;
090089c4 880}
881
63be0a78 882/**
883 \ingroup ServerProtocolGopherInternal
884 * This will be called when request write is complete. Schedule read of reply.
885 */
b8d8561b 886static void
13de3699 887gopherSendComplete(int fd, char *buf, size_t size, comm_err_t errflag, int xerrno, void *data)
090089c4 888{
56fa4cad 889 GopherStateData *gopherState = (GopherStateData *) data;
70a9dab4 890 StoreEntry *entry = gopherState->entry;
4a7a3d56 891 debugs(10, 5, "gopherSendComplete: FD " << fd << " size: " << size << " errflag: " << errflag);
62e76326 892
ee1679df 893 if (size > 0) {
62e76326 894 fd_bytes(fd, size, FD_WRITE);
895 kb_incr(&statCounter.server.all.kbytes_out, size);
896 kb_incr(&statCounter.server.other.kbytes_out, size);
ee1679df 897 }
62e76326 898
090089c4 899 if (errflag) {
62e76326 900 ErrorState *err;
2cc81f1f 901 err = errorCon(ERR_WRITE_ERROR, HTTP_SERVICE_UNAVAILABLE, gopherState->fwd->request);
62e76326 902 err->xerrno = errno;
d2b3e30e 903 err->port = gopherState->fwd->request->port;
3900307b 904 err->url = xstrdup(entry->url());
b6b6f466 905 gopherState->fwd->fail(err);
62e76326 906 comm_close(fd);
907
908 if (buf)
909 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
910
911 return;
090089c4 912 }
62e76326 913
914 /*
090089c4 915 * OK. We successfully reach remote site. Start MIME typing
916 * stuff. Do it anyway even though request is not HTML type.
917 */
3900307b 918 entry->buffer();
b66315e4 919
30a4f2a8 920 gopherMimeCreate(gopherState);
62e76326 921
a47b9029 922 switch (gopherState->type_id) {
62e76326 923
a47b9029 924 case GOPHER_DIRECTORY:
62e76326 925 /* we got to convert it first */
62e76326 926 gopherState->conversion = gopher_ds::HTML_DIR;
927 gopherState->HTML_header_added = 0;
928 break;
929
a47b9029 930 case GOPHER_INDEX:
62e76326 931 /* we got to convert it first */
62e76326 932 gopherState->conversion = gopher_ds::HTML_INDEX_RESULT;
933 gopherState->HTML_header_added = 0;
934 break;
935
a47b9029 936 case GOPHER_CSO:
62e76326 937 /* we got to convert it first */
62e76326 938 gopherState->conversion = gopher_ds::HTML_CSO_RESULT;
939 gopherState->cso_recno = 0;
940 gopherState->HTML_header_added = 0;
941 break;
942
a47b9029 943 default:
62e76326 944 gopherState->conversion = gopher_ds::NORMAL;
3900307b 945 entry->flush();
86101e40 946 }
62e76326 947
090089c4 948 /* Schedule read reply. */
65517dc8 949 AsyncCall::Pointer call = commCbCall(10,5, "gopherReadReply",
26ac0430 950 CommIoCbPtrFun(gopherReadReply, gopherState));
65517dc8 951 entry->delayAwareRead(fd, gopherState->replybuf, BUFSIZ, call);
62e76326 952
090089c4 953 if (buf)
62e76326 954 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
090089c4 955}
956
63be0a78 957/**
958 \ingroup ServerProtocolGopherInternal
959 * This will be called when connect completes. Write request.
960 */
b8d8561b 961static void
582b6456 962gopherSendRequest(int fd, void *data)
090089c4 963{
e6ccf245 964 GopherStateData *gopherState = (GopherStateData *)data;
965 char *buf = (char *)memAllocate(MEM_4K_BUF);
62e76326 966
582b6456 967 if (gopherState->type_id == GOPHER_CSO) {
62e76326 968 const char *t = strchr(gopherState->request, '?');
969
970 if (t != NULL)
971 t++; /* skip the ? */
972 else
973 t = "";
974
975 snprintf(buf, 4096, "query %s\r\nquit\r\n", t);
582b6456 976 } else if (gopherState->type_id == GOPHER_INDEX) {
62e76326 977 char *t = strchr(gopherState->request, '?');
978
979 if (t != NULL)
980 *t = '\t';
981
982 snprintf(buf, 4096, "%s\r\n", gopherState->request);
090089c4 983 } else {
62e76326 984 snprintf(buf, 4096, "%s\r\n", gopherState->request);
090089c4 985 }
62e76326 986
bf8fe701 987 debugs(10, 5, "gopherSendRequest: FD " << fd);
2b663917 988 comm_write(fd, buf, strlen(buf), gopherSendComplete, gopherState, NULL);
62e76326 989
d46a87a8 990 if (EBIT_TEST(gopherState->entry->flags, ENTRY_CACHABLE))
d88e3c49 991 gopherState->entry->setPublicKey(); /* Make it public */
090089c4 992}
993
63be0a78 994/// \ingroup ServerProtocolGopherInternal
09fb5b61 995CBDATA_TYPE(GopherStateData);
996
63be0a78 997/// \ingroup ServerProtocolGopherAPI
770f051d 998void
b6b6f466 999gopherStart(FwdState * fwd)
090089c4 1000{
b6b6f466 1001 int fd = fwd->server_fd;
1002 StoreEntry *entry = fwd->entry;
09fb5b61 1003 GopherStateData *gopherState;
1004 CBDATA_INIT_TYPE(GopherStateData);
1005 gopherState = cbdataAlloc(GopherStateData);
e6ccf245 1006 gopherState->buf = (char *)memAllocate(MEM_4K_BUF);
34266cde 1007
3d0ac046 1008 entry->lock();
582b6456 1009 gopherState->entry = entry;
34266cde 1010
b6b6f466 1011 gopherState->fwd = fwd;
34266cde 1012
bf8fe701 1013 debugs(10, 3, "gopherStart: " << entry->url() );
34266cde 1014
83704487 1015 statCounter.server.all.requests++;
34266cde 1016
83704487 1017 statCounter.server.other.requests++;
34266cde 1018
090089c4 1019 /* Parse url. */
b6b6f466 1020 gopher_request_parse(fwd->request,
62e76326 1021 &gopherState->type_id, gopherState->request);
34266cde 1022
bfcaf585 1023 comm_add_close_handler(fd, gopherStateFree, gopherState);
62e76326 1024
582b6456 1025 if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
62e76326 1026 && (strchr(gopherState->request, '?') == NULL)) {
1027 /* Index URL without query word */
1028 /* We have to generate search page back to client. No need for connection */
1029 gopherMimeCreate(gopherState);
1030
1031 if (gopherState->type_id == GOPHER_INDEX) {
1032 gopherState->conversion = gopher_ds::HTML_INDEX_PAGE;
1033 } else {
1034 if (gopherState->type_id == GOPHER_CSO) {
1035 gopherState->conversion = gopher_ds::HTML_CSO_PAGE;
1036 } else {
1037 gopherState->conversion = gopher_ds::HTML_INDEX_PAGE;
1038 }
1039 }
1040
1041 gopherToHTML(gopherState, (char *) NULL, 0);
b6b6f466 1042 fwd->complete();
62e76326 1043 comm_close(fd);
1044 return;
090089c4 1045 }
62e76326 1046
bfcaf585 1047 gopherState->fd = fd;
b6b6f466 1048 gopherState->fwd = fwd;
c4b7a5a9 1049 gopherSendRequest(fd, gopherState);
41462d93 1050 commSetTimeout(fd, Config.Timeout.read, gopherTimeout, gopherState);
e5f6c5c2 1051}