]> git.ipfire.org Git - thirdparty/squid.git/blame - src/gopher.cc
remove ssl_proxy and passthrough_proxy
[thirdparty/squid.git] / src / gopher.cc
CommitLineData
30a4f2a8 1/*
86101e40 2 * $Id: gopher.cc,v 1.89 1997/07/15 03:29:02 wessels Exp $
30a4f2a8 3 *
4 * DEBUG: section 10 Gopher
5 * AUTHOR: Harvest Derived
6 *
42c04c16 7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
30a4f2a8 8 * --------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from the
11 * Internet community. Development is led by Duane Wessels of the
12 * National Laboratory for Applied Network Research and funded by
13 * the National Science Foundation.
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 *
29 */
019dd986 30
31/*
30a4f2a8 32 * Copyright (c) 1994, 1995. All rights reserved.
33 *
34 * The Harvest software was developed by the Internet Research Task
35 * Force Research Group on Resource Discovery (IRTF-RD):
36 *
37 * Mic Bowman of Transarc Corporation.
38 * Peter Danzig of the University of Southern California.
39 * Darren R. Hardy of the University of Colorado at Boulder.
40 * Udi Manber of the University of Arizona.
41 * Michael F. Schwartz of the University of Colorado at Boulder.
42 * Duane Wessels of the University of Colorado at Boulder.
43 *
44 * This copyright notice applies to software in the Harvest
45 * ``src/'' directory only. Users should consult the individual
46 * copyright notices in the ``components/'' subdirectories for
47 * copyright information about other software bundled with the
48 * Harvest source code distribution.
49 *
50 * TERMS OF USE
51 *
52 * The Harvest software may be used and re-distributed without
53 * charge, provided that the software origin and research team are
54 * cited in any use of the system. Most commonly this is
55 * accomplished by including a link to the Harvest Home Page
56 * (http://harvest.cs.colorado.edu/) from the query page of any
57 * Broker you deploy, as well as in the query result pages. These
58 * links are generated automatically by the standard Broker
59 * software distribution.
60 *
61 * The Harvest software is provided ``as is'', without express or
62 * implied warranty, and with no support nor obligation to assist
63 * in its use, correction, modification or enhancement. We assume
64 * no liability with respect to the infringement of copyrights,
65 * trade secrets, or any patents, and are not responsible for
66 * consequential damages. Proper use of the Harvest software is
67 * entirely the responsibility of the user.
68 *
69 * DERIVATIVE WORKS
70 *
71 * Users may make derivative works from the Harvest software, subject
72 * to the following constraints:
73 *
74 * - You must include the above copyright notice and these
75 * accompanying paragraphs in all forms of derivative works,
76 * and any documentation and other materials related to such
77 * distribution and use acknowledge that the software was
78 * developed at the above institutions.
79 *
80 * - You must notify IRTF-RD regarding your distribution of
81 * the derivative work.
82 *
83 * - You must clearly notify users that your are distributing
84 * a modified version and not the original Harvest software.
85 *
86 * - Any derivative product is also subject to these copyright
87 * and use restrictions.
88 *
89 * Note that the Harvest software is NOT in the public domain. We
90 * retain copyright, as specified above.
91 *
92 * HISTORY OF FREE SOFTWARE STATUS
93 *
94 * Originally we required sites to license the software in cases
95 * where they were going to build commercial products/services
96 * around Harvest. In June 1995 we changed this policy. We now
97 * allow people to use the core Harvest software (the code found in
98 * the Harvest ``src/'' directory) for free. We made this change
99 * in the interest of encouraging the widest possible deployment of
100 * the technology. The Harvest software is really a reference
101 * implementation of a set of protocols and formats, some of which
102 * we intend to standardize. We encourage commercial
103 * re-implementations of code complying to this set of standards.
019dd986 104 */
44a47c6e 105
106#include "squid.h"
090089c4 107
090089c4 108/* gopher type code from rfc. Anawat. */
109#define GOPHER_FILE '0'
110#define GOPHER_DIRECTORY '1'
111#define GOPHER_CSO '2'
112#define GOPHER_ERROR '3'
113#define GOPHER_MACBINHEX '4'
114#define GOPHER_DOSBIN '5'
115#define GOPHER_UUENCODED '6'
116#define GOPHER_INDEX '7'
117#define GOPHER_TELNET '8'
118#define GOPHER_BIN '9'
119#define GOPHER_REDUNT '+'
120#define GOPHER_3270 'T'
121#define GOPHER_GIF 'g'
122#define GOPHER_IMAGE 'I'
123
124#define GOPHER_HTML 'h' /* HTML */
125#define GOPHER_INFO 'i'
126#define GOPHER_WWW 'w' /* W3 address */
127#define GOPHER_SOUND 's'
128
129#define GOPHER_PLUS_IMAGE ':'
130#define GOPHER_PLUS_MOVIE ';'
131#define GOPHER_PLUS_SOUND '<'
132
133#define GOPHER_PORT 70
134#define GOPHER_DELETE_GAP (64*1024)
135
136#define TAB '\t'
137#define TEMP_BUF_SIZE SM_PAGE_SIZE
138#define MAX_CSO_RESULT 1024
139
140typedef struct gopher_ds {
141 StoreEntry *entry;
ed43818f 142 char host[SQUIDHOSTNAMELEN + 1];
090089c4 143 enum {
144 NORMAL,
145 HTML_DIR,
146 HTML_INDEX_RESULT,
147 HTML_CSO_RESULT,
148 HTML_INDEX_PAGE,
149 HTML_CSO_PAGE
150 } conversion;
151 int HTML_header_added;
152 int port;
090089c4 153 char type_id;
f2052513 154 char request[MAX_URL];
090089c4 155 int data_in;
156 int cso_recno;
157 int len;
158 char *buf; /* pts to a 4k page */
bfcaf585 159 int fd;
56fa4cad 160} GopherStateData;
161
582b6456 162static PF gopherStateFree;
0ee4272b 163static void gopher_mime_content _PARAMS((char *buf, const char *name, const char *def));
67508012 164static void gopherMimeCreate _PARAMS((GopherStateData *));
0ee4272b 165static int gopher_url_parser(const char *url,
684c2720 166 char *host,
167 int *port,
168 char *type_id,
169 char *request);
67508012 170static void gopherEndHTML _PARAMS((GopherStateData *));
171static void gopherToHTML _PARAMS((GopherStateData *, char *inbuf, int len));
5c5783a2 172static PF gopherTimeout;
582b6456 173static PF gopherReadReply;
684c2720 174static void gopherSendComplete(int fd,
175 char *buf,
176 int size,
177 int errflag,
178 void *data);
582b6456 179static PF gopherSendRequest;
67508012 180static GopherStateData *CreateGopherStateData _PARAMS((void));
4f92c80c 181static CNCB gopherConnectDone;
bfcaf585 182static STABH gopherAbort;
56fa4cad 183
184static char def_gopher_bin[] = "www/unknown";
185static char def_gopher_text[] = "text/plain";
090089c4 186
582b6456 187static void
188gopherStateFree(int fd, void *data)
ba718c8f 189{
582b6456 190 GopherStateData *gopherState = data;
51fa90db 191 if (gopherState == NULL)
582b6456 192 return;
bfcaf585 193 if (gopherState->entry) {
bfcaf585 194 storeUnregisterAbort(gopherState->entry);
f88211e8 195 storeUnlockObject(gopherState->entry);
bfcaf585 196 }
51fa90db 197 put_free_4k_page(gopherState->buf);
7dd44885 198 gopherState->buf = NULL;
199 cbdataFree(gopherState);
ba718c8f 200}
201
202
090089c4 203/* figure out content type from file extension */
b8d8561b 204static void
812ed90c 205gopher_mime_content(char *buf, const char *name, const char *def_ctype)
090089c4 206{
812ed90c 207 char *ctype = mimeGetContentType(name);
208 char *cenc = mimeGetContentEncoding(name);
209 if (cenc)
210 sprintf(buf + strlen(buf), "Content-Encoding: %s\r\n", cenc);
211 sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
212 ctype ? ctype : def_ctype);
090089c4 213}
214
215
216
217/* create MIME Header for Gopher Data */
b8d8561b 218static void
582b6456 219gopherMimeCreate(GopherStateData * gopherState)
090089c4 220{
95d659f0 221 LOCAL_ARRAY(char, tempMIME, MAX_MIME);
090089c4 222
0ee4272b 223 sprintf(tempMIME,
224 "HTTP/1.0 200 OK Gatewaying\r\n"
225 "Server: Squid/%s\r\n"
226 "Date: %s\r\n"
227 "MIME-version: 1.0\r\n",
cf49df4b 228 version_string, mkrfc1123(squid_curtime));
090089c4 229
582b6456 230 switch (gopherState->type_id) {
090089c4 231
232 case GOPHER_DIRECTORY:
233 case GOPHER_INDEX:
234 case GOPHER_HTML:
235 case GOPHER_WWW:
236 case GOPHER_CSO:
237 strcat(tempMIME, "Content-Type: text/html\r\n");
238 break;
239 case GOPHER_GIF:
240 case GOPHER_IMAGE:
241 case GOPHER_PLUS_IMAGE:
242 strcat(tempMIME, "Content-Type: image/gif\r\n");
243 break;
244 case GOPHER_SOUND:
245 case GOPHER_PLUS_SOUND:
246 strcat(tempMIME, "Content-Type: audio/basic\r\n");
247 break;
248 case GOPHER_PLUS_MOVIE:
249 strcat(tempMIME, "Content-Type: video/mpeg\r\n");
250 break;
251 case GOPHER_MACBINHEX:
252 case GOPHER_DOSBIN:
253 case GOPHER_UUENCODED:
254 case GOPHER_BIN:
255 /* Rightnow We have no idea what it is. */
582b6456 256 gopher_mime_content(tempMIME, gopherState->request, def_gopher_bin);
090089c4 257 break;
090089c4 258 case GOPHER_FILE:
259 default:
582b6456 260 gopher_mime_content(tempMIME, gopherState->request, def_gopher_text);
090089c4 261 break;
090089c4 262 }
090089c4 263 strcat(tempMIME, "\r\n");
582b6456 264 storeAppend(gopherState->entry, tempMIME, strlen(tempMIME));
090089c4 265}
266
267/* Parse a gopher url into components. By Anawat. */
b8d8561b 268static int
0ee4272b 269gopher_url_parser(const char *url, char *host, int *port, char *type_id, char *request)
090089c4 270{
95d659f0 271 LOCAL_ARRAY(char, proto, MAX_URL);
272 LOCAL_ARRAY(char, hostbuf, MAX_URL);
090089c4 273 int t;
274
de06a228 275 proto[0] = hostbuf[0] = '\0';
090089c4 276 host[0] = request[0] = '\0';
277 (*port) = 0;
278 (*type_id) = 0;
279
de06a228 280 t = sscanf(url, "%[a-zA-Z]://%[^/]/%c%s", proto, hostbuf,
090089c4 281 type_id, request);
de06a228 282 if ((t < 2) || strcasecmp(proto, "gopher")) {
090089c4 283 return -1;
284 } else if (t == 2) {
285 (*type_id) = GOPHER_DIRECTORY;
286 request[0] = '\0';
287 } else if (t == 3) {
288 request[0] = '\0';
289 } else {
290 /* convert %xx to char */
de06a228 291 (void) url_convert_hex(request, 0);
090089c4 292 }
293
294 host[0] = '\0';
295 if (sscanf(hostbuf, "%[^:]:%d", host, port) < 2)
296 (*port) = GOPHER_PORT;
297
298 return 0;
299}
300
b8d8561b 301int
0ee4272b 302gopherCachable(const char *url)
090089c4 303{
582b6456 304 GopherStateData *gopherState = NULL;
090089c4 305 int cachable = 1;
090089c4 306 /* use as temp data structure to parse gopher URL */
582b6456 307 gopherState = CreateGopherStateData();
090089c4 308 /* parse to see type */
56fa4cad 309 gopher_url_parser(url,
582b6456 310 gopherState->host,
311 &gopherState->port,
312 &gopherState->type_id,
313 gopherState->request);
314 switch (gopherState->type_id) {
090089c4 315 case GOPHER_INDEX:
316 case GOPHER_CSO:
317 case GOPHER_TELNET:
318 case GOPHER_3270:
319 cachable = 0;
320 break;
321 default:
322 cachable = 1;
323 }
582b6456 324 gopherStateFree(-1, gopherState);
090089c4 325 return cachable;
326}
327
b8d8561b 328static void
582b6456 329gopherEndHTML(GopherStateData * gopherState)
090089c4 330{
95d659f0 331 LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
090089c4 332
582b6456 333 if (!gopherState->data_in) {
0ee4272b 334 sprintf(tmpbuf, "<HTML><HEAD><TITLE>Server Return Nothing.</TITLE>\n"
335 "</HEAD><BODY><HR><H1>Server Return Nothing.</H1></BODY></HTML>\n");
582b6456 336 storeAppend(gopherState->entry, tmpbuf, strlen(tmpbuf));
090089c4 337 return;
338 }
339}
340
341
342/* Convert Gopher to HTML */
343/* Borrow part of code from libwww2 came with Mosaic distribution */
b8d8561b 344static void
582b6456 345gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
090089c4 346{
347 char *pos = inbuf;
348 char *lpos = NULL;
349 char *tline = NULL;
95d659f0 350 LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
351 LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
352 LOCAL_ARRAY(char, outbuf, TEMP_BUF_SIZE << 4);
090089c4 353 char *name = NULL;
354 char *selector = NULL;
355 char *host = NULL;
356 char *port = NULL;
357 char *escaped_selector = NULL;
358 char *icon_type = NULL;
359 char gtype;
360 StoreEntry *entry = NULL;
361
53d02438 362 memset(outbuf, '\0', TEMP_BUF_SIZE << 4);
363 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
364 memset(line, '\0', TEMP_BUF_SIZE);
090089c4 365
582b6456 366 entry = gopherState->entry;
090089c4 367
582b6456 368 if (gopherState->conversion == HTML_INDEX_PAGE) {
0ee4272b 369 sprintf(outbuf, "<HTML><HEAD><TITLE>Gopher Index %s</TITLE></HEAD>\n"
fe4e214f 370 "<BODY><H1>%s<BR>Gopher Search</H1>\n"
371 "<p>This is a searchable Gopher index. Use the search\n"
372 "function of your browser to enter search terms.\n"
373 "<ISINDEX></BODY></HTML>\n", entry->url, entry->url);
090089c4 374 storeAppend(entry, outbuf, strlen(outbuf));
375 /* now let start sending stuff to client */
376 BIT_RESET(entry->flag, DELAY_SENDING);
582b6456 377 gopherState->data_in = 1;
090089c4 378
379 return;
380 }
582b6456 381 if (gopherState->conversion == HTML_CSO_PAGE) {
0ee4272b 382 sprintf(outbuf, "<HTML><HEAD><TITLE>CSO Search of %s</TITLE></HEAD>\n"
fe4e214f 383 "<BODY><H1>%s<BR>CSO Search</H1>\n"
384 "<P>A CSO database usually contains a phonebook or\n"
385 "directory. Use the search function of your browser to enter\n"
386 "search terms.</P><ISINDEX></BODY></HTML>\n",
090089c4 387 entry->url, entry->url);
388
389 storeAppend(entry, outbuf, strlen(outbuf));
390 /* now let start sending stuff to client */
391 BIT_RESET(entry->flag, DELAY_SENDING);
582b6456 392 gopherState->data_in = 1;
090089c4 393
394 return;
395 }
396 inbuf[len] = '\0';
397
582b6456 398 if (!gopherState->HTML_header_added) {
399 if (gopherState->conversion == HTML_CSO_RESULT)
0ee4272b 400 strcat(outbuf, "<HTML><HEAD><TITLE>CSO Searchs Result</TITLE></HEAD>\n"
401 "<BODY><H1>CSO Searchs Result</H1>\n<PRE>\n");
090089c4 402 else
0ee4272b 403 strcat(outbuf, "<HTML><HEAD><TITLE>Gopher Menu</TITLE></HEAD>\n"
404 "<BODY><H1>Gopher Menu</H1>\n<PRE>\n");
582b6456 405 gopherState->HTML_header_added = 1;
090089c4 406 }
407 while ((pos != NULL) && (pos < inbuf + len)) {
408
582b6456 409 if (gopherState->len != 0) {
090089c4 410 /* there is something left from last tx. */
582b6456 411 xstrncpy(line, gopherState->buf, gopherState->len);
412 lpos = (char *) memccpy(line + gopherState->len, inbuf, '\n', len);
090089c4 413 if (lpos)
414 *lpos = '\0';
415 else {
416 /* there is no complete line in inbuf */
417 /* copy it to temp buffer */
582b6456 418 if (gopherState->len + len > TEMP_BUF_SIZE) {
a3d5953d 419 debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
090089c4 420 entry->url);
582b6456 421 len = TEMP_BUF_SIZE - gopherState->len;
090089c4 422 }
582b6456 423 xmemcpy(gopherState->buf + gopherState->len, inbuf, len);
424 gopherState->len += len;
090089c4 425 return;
426 }
427
428 /* skip one line */
429 pos = (char *) memchr(pos, '\n', 256);
430 if (pos)
431 pos++;
432
433 /* we're done with the remain from last tx. */
582b6456 434 gopherState->len = 0;
435 *(gopherState->buf) = '\0';
090089c4 436 } else {
437
438 lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf));
439 if (lpos)
440 *lpos = '\0';
441 else {
442 /* there is no complete line in inbuf */
443 /* copy it to temp buffer */
444 if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) {
a3d5953d 445 debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
090089c4 446 entry->url);
447 len = TEMP_BUF_SIZE;
448 }
449 if (len > (pos - inbuf)) {
582b6456 450 xmemcpy(gopherState->buf, pos, len - (pos - inbuf));
451 gopherState->len = len - (pos - inbuf);
090089c4 452 }
453 break;
454 }
455
456 /* skip one line */
457 pos = (char *) memchr(pos, '\n', 256);
458 if (pos)
459 pos++;
460
461 }
462
463 /* at this point. We should have one line in buffer to process */
464
465 if (*line == '.') {
466 /* skip it */
467 memset(line, '\0', TEMP_BUF_SIZE);
468 continue;
469 }
582b6456 470 switch (gopherState->conversion) {
090089c4 471
472 case HTML_INDEX_RESULT:
473 case HTML_DIR:{
474 tline = line;
475 gtype = *tline++;
476 name = tline;
477 selector = strchr(tline, TAB);
478 if (selector) {
479 *selector++ = '\0';
480 host = strchr(selector, TAB);
481 if (host) {
482 *host++ = '\0';
483 port = strchr(host, TAB);
484 if (port) {
485 char *junk;
486 port[0] = ':';
487 junk = strchr(host, TAB);
488 if (junk)
489 *junk++ = 0; /* Chop port */
490 else {
491 junk = strchr(host, '\r');
492 if (junk)
493 *junk++ = 0; /* Chop port */
494 else {
495 junk = strchr(host, '\n');
496 if (junk)
497 *junk++ = 0; /* Chop port */
498 }
499 }
500 if ((port[1] == '0') && (!port[2]))
501 port[0] = 0; /* 0 means none */
502 }
503 /* escape a selector here */
504 escaped_selector = url_escape(selector);
505
506 switch (gtype) {
507 case GOPHER_DIRECTORY:
508 icon_type = "internal-gopher-menu";
509 break;
510 case GOPHER_FILE:
511 icon_type = "internal-gopher-text";
512 break;
513 case GOPHER_INDEX:
514 case GOPHER_CSO:
515 icon_type = "internal-gopher-index";
516 break;
517 case GOPHER_IMAGE:
518 case GOPHER_GIF:
519 case GOPHER_PLUS_IMAGE:
520 icon_type = "internal-gopher-image";
521 break;
522 case GOPHER_SOUND:
523 case GOPHER_PLUS_SOUND:
524 icon_type = "internal-gopher-sound";
525 break;
526 case GOPHER_PLUS_MOVIE:
527 icon_type = "internal-gopher-movie";
528 break;
529 case GOPHER_TELNET:
530 case GOPHER_3270:
531 icon_type = "internal-gopher-telnet";
532 break;
533 case GOPHER_BIN:
534 case GOPHER_MACBINHEX:
535 case GOPHER_DOSBIN:
536 case GOPHER_UUENCODED:
537 icon_type = "internal-gopher-binary";
538 break;
539 default:
540 icon_type = "internal-gopher-unknown";
541 break;
542 }
543
544
545 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
546 if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
547 if (strlen(escaped_selector) != 0)
548 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s@%s/\">%s</A>\n",
549 icon_type, escaped_selector, host, name);
550 else
551 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s/\">%s</A>\n",
552 icon_type, host, name);
553
554 } else {
555 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
556 icon_type, host, gtype, escaped_selector, name);
557 }
558 safe_free(escaped_selector);
559 strcat(outbuf, tmpbuf);
582b6456 560 gopherState->data_in = 1;
090089c4 561 } else {
562 memset(line, '\0', TEMP_BUF_SIZE);
563 continue;
564 }
565 } else {
566 memset(line, '\0', TEMP_BUF_SIZE);
567 continue;
568 }
569 break;
570 } /* HTML_DIR, HTML_INDEX_RESULT */
571
572
573 case HTML_CSO_RESULT:{
574 int t;
575 int code;
576 int recno;
95d659f0 577 LOCAL_ARRAY(char, result, MAX_CSO_RESULT);
090089c4 578
579 tline = line;
580
581 if (tline[0] == '-') {
582 t = sscanf(tline, "-%d:%d:%[^\n]", &code, &recno, result);
583 if (t < 3)
584 break;
585
586 if (code != 200)
587 break;
588
582b6456 589 if (gopherState->cso_recno != recno) {
090089c4 590 sprintf(tmpbuf, "</PRE><HR><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, result);
582b6456 591 gopherState->cso_recno = recno;
090089c4 592 } else {
593 sprintf(tmpbuf, "%s\n", result);
594 }
595 strcat(outbuf, tmpbuf);
582b6456 596 gopherState->data_in = 1;
090089c4 597 break;
598 } else {
599 /* handle some error codes */
600 t = sscanf(tline, "%d:%[^\n]", &code, result);
601
602 if (t < 2)
603 break;
604
605 switch (code) {
606
607 case 200:{
608 /* OK */
609 /* Do nothing here */
610 break;
611 }
612
613 case 102: /* Number of matches */
614 case 501: /* No Match */
615 case 502: /* Too Many Matches */
616 {
617 /* Print the message the server returns */
a8f7d3ee 618 sprintf(tmpbuf, "</PRE><HR><H2>%s</H2>\n<PRE>", result);
090089c4 619 strcat(outbuf, tmpbuf);
582b6456 620 gopherState->data_in = 1;
090089c4 621 break;
622 }
623
624
625 }
626 }
627
628 } /* HTML_CSO_RESULT */
629 default:
630 break; /* do nothing */
631
632 } /* switch */
633
634 } /* while loop */
635
636 if ((int) strlen(outbuf) > 0) {
637 storeAppend(entry, outbuf, strlen(outbuf));
638 /* now let start sending stuff to client */
639 BIT_RESET(entry->flag, DELAY_SENDING);
640 }
641 return;
642}
643
582b6456 644static void
5c5783a2 645gopherTimeout(int fd, void *data)
090089c4 646{
582b6456 647 GopherStateData *gopherState = data;
648 StoreEntry *entry = gopherState->entry;
a3d5953d 649 debug(10, 4) ("gopherTimeout: FD %d: '%s'\n", fd, entry->url);
474cac1b 650 storeAbort(entry, ERR_READ_TIMEOUT, NULL, 0);
51fa90db 651 comm_close(fd);
090089c4 652}
653
090089c4 654/* This will be called when data is ready to be read from fd. Read until
655 * error or connection closed. */
b8d8561b 656static void
582b6456 657gopherReadReply(int fd, void *data)
090089c4 658{
582b6456 659 GopherStateData *gopherState = data;
bfcaf585 660 StoreEntry *entry = gopherState->entry;
090089c4 661 char *buf = NULL;
662 int len;
663 int clen;
664 int off;
56fa4cad 665 int bin;
d89d1fb6 666 if (protoAbortFetch(entry)) {
474cac1b 667 storeAbort(entry, ERR_CLIENT_ABORT, NULL, 0);
a3d5953d 668 comm_close(fd);
669 return;
56fa4cad 670 }
671 /* check if we want to defer reading */
672 clen = entry->mem_obj->e_current_len;
673 off = storeGetLowestReaderOffset(entry);
674 if ((clen - off) > GOPHER_DELETE_GAP) {
56fa4cad 675 IOStats.Gopher.reads_deferred++;
a3d5953d 676 debug(10, 3) ("gopherReadReply: Read deferred for Object: %s\n",
56fa4cad 677 entry->url);
a3d5953d 678 debug(10, 3) (" Current Gap: %d bytes\n", clen - off);
56fa4cad 679 /* reschedule, so it will automatically reactivated when
680 * Gap is big enough. */
b177367b 681 commSetSelect(fd,
56fa4cad 682 COMM_SELECT_READ,
cd1fb0eb 683 gopherReadReply,
684 data, 0);
56fa4cad 685 /* don't install read timeout until we are below the GAP */
56fa4cad 686 if (!BIT_TEST(entry->flag, READ_DEFERRED)) {
4f92c80c 687 commSetTimeout(fd, Config.Timeout.defer, NULL, NULL);
56fa4cad 688 BIT_SET(entry->flag, READ_DEFERRED);
090089c4 689 }
56fa4cad 690 /* dont try reading again for a while */
691 comm_set_stall(fd, Config.stallDelay);
692 return;
693 } else {
694 BIT_RESET(entry->flag, READ_DEFERRED);
090089c4 695 }
2daae136 696 buf = get_free_4k_page();
6fe6313d 697 errno = 0;
56fa4cad 698 /* leave one space for \0 in gopherToHTML */
699 len = read(fd, buf, TEMP_BUF_SIZE - 1);
4f92c80c 700 fd_bytes(fd, len, FD_READ);
a3d5953d 701 debug(10, 5) ("gopherReadReply: FD %d read len=%d\n", fd, len);
56fa4cad 702 if (len > 0) {
4f92c80c 703 commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
56fa4cad 704 IOStats.Gopher.reads++;
705 for (clen = len - 1, bin = 0; clen; bin++)
706 clen >>= 1;
707 IOStats.Gopher.read_hist[bin]++;
708 }
ba718c8f 709 if (len < 0) {
a3d5953d 710 debug(50, 1) ("gopherReadReply: error reading: %s\n", xstrerror());
0a0bf5db 711 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
6fe6313d 712 /* reinstall handlers */
713 /* XXX This may loop forever */
b177367b 714 commSetSelect(fd,
56fa4cad 715 COMM_SELECT_READ,
cd1fb0eb 716 gopherReadReply,
717 data, 0);
6fe6313d 718 } else {
1c481e00 719 BIT_RESET(entry->flag, ENTRY_CACHABLE);
2daae136 720 storeReleaseRequest(entry);
474cac1b 721 storeAbort(entry, ERR_READ_ERROR, xstrerror(), 0);
51fa90db 722 comm_close(fd);
6fe6313d 723 }
ba718c8f 724 } else if (len == 0 && entry->mem_obj->e_current_len == 0) {
474cac1b 725 storeAbort(entry, ERR_ZERO_SIZE_OBJECT, errno ? xstrerror() : NULL, 0);
51fa90db 726 comm_close(fd);
090089c4 727 } else if (len == 0) {
728 /* Connection closed; retrieval done. */
729 /* flush the rest of data in temp buf if there is one. */
582b6456 730 if (gopherState->conversion != NORMAL)
090089c4 731 gopherEndHTML(data);
d89d1fb6 732 storeTimestampsSet(entry);
090089c4 733 BIT_RESET(entry->flag, DELAY_SENDING);
734 storeComplete(entry);
51fa90db 735 comm_close(fd);
090089c4 736 } else {
582b6456 737 if (gopherState->conversion != NORMAL) {
090089c4 738 gopherToHTML(data, buf, len);
739 } else {
740 storeAppend(entry, buf, len);
741 }
b177367b 742 commSetSelect(fd,
ba718c8f 743 COMM_SELECT_READ,
cd1fb0eb 744 gopherReadReply,
745 data, 0);
090089c4 746 }
2daae136 747 put_free_4k_page(buf);
56fa4cad 748 return;
090089c4 749}
750
751/* This will be called when request write is complete. Schedule read of
752 * reply. */
b8d8561b 753static void
754gopherSendComplete(int fd, char *buf, int size, int errflag, void *data)
090089c4 755{
56fa4cad 756 GopherStateData *gopherState = (GopherStateData *) data;
090089c4 757 StoreEntry *entry = NULL;
30a4f2a8 758 entry = gopherState->entry;
a3d5953d 759 debug(10, 5) ("gopherSendComplete: FD %d size: %d errflag: %d\n",
090089c4 760 fd, size, errflag);
761 if (errflag) {
474cac1b 762 storeAbort(entry, ERR_CONNECT_FAIL, xstrerror(), 0);
51fa90db 763 comm_close(fd);
090089c4 764 if (buf)
2daae136 765 put_free_4k_page(buf); /* Allocated by gopherSendRequest. */
44a47c6e 766 return;
090089c4 767 }
768 /*
769 * OK. We successfully reach remote site. Start MIME typing
770 * stuff. Do it anyway even though request is not HTML type.
771 */
30a4f2a8 772 gopherMimeCreate(gopherState);
30a4f2a8 773 switch (gopherState->type_id) {
090089c4 774 case GOPHER_DIRECTORY:
775 /* we got to convert it first */
776 BIT_SET(entry->flag, DELAY_SENDING);
30a4f2a8 777 gopherState->conversion = HTML_DIR;
778 gopherState->HTML_header_added = 0;
090089c4 779 break;
090089c4 780 case GOPHER_INDEX:
781 /* we got to convert it first */
782 BIT_SET(entry->flag, DELAY_SENDING);
30a4f2a8 783 gopherState->conversion = HTML_INDEX_RESULT;
784 gopherState->HTML_header_added = 0;
090089c4 785 break;
090089c4 786 case GOPHER_CSO:
787 /* we got to convert it first */
788 BIT_SET(entry->flag, DELAY_SENDING);
30a4f2a8 789 gopherState->conversion = HTML_CSO_RESULT;
790 gopherState->cso_recno = 0;
791 gopherState->HTML_header_added = 0;
090089c4 792 break;
090089c4 793 default:
30a4f2a8 794 gopherState->conversion = NORMAL;
86101e40 795 }
090089c4 796 /* Schedule read reply. */
86101e40 797 commSetSelect(fd, COMM_SELECT_READ, gopherReadReply, gopherState, 0);
090089c4 798 if (buf)
2daae136 799 put_free_4k_page(buf); /* Allocated by gopherSendRequest. */
090089c4 800}
801
802/* This will be called when connect completes. Write request. */
b8d8561b 803static void
582b6456 804gopherSendRequest(int fd, void *data)
090089c4 805{
582b6456 806 GopherStateData *gopherState = data;
95d659f0 807 LOCAL_ARRAY(char, query, MAX_URL);
2daae136 808 char *buf = get_free_4k_page();
7aa8e7af 809 char *t;
582b6456 810 if (gopherState->type_id == GOPHER_CSO) {
811 sscanf(gopherState->request, "?%s", query);
2285407f 812 sprintf(buf, "query %s\r\nquit\r\n", query);
582b6456 813 } else if (gopherState->type_id == GOPHER_INDEX) {
814 if ((t = strchr(gopherState->request, '?')))
7aa8e7af 815 *t = '\t';
582b6456 816 sprintf(buf, "%s\r\n", gopherState->request);
090089c4 817 } else {
582b6456 818 sprintf(buf, "%s\r\n", gopherState->request);
090089c4 819 }
a3d5953d 820 debug(10, 5) ("gopherSendRequest: FD %d\n", fd);
30a4f2a8 821 comm_write(fd,
44a47c6e 822 buf,
7aa8e7af 823 strlen(buf),
44a47c6e 824 gopherSendComplete,
cd1fb0eb 825 data,
9864ee44 826 put_free_4k_page);
582b6456 827 if (BIT_TEST(gopherState->entry->flag, ENTRY_CACHABLE))
828 storeSetPublicKey(gopherState->entry); /* Make it public */
090089c4 829}
830
770f051d 831void
75e88d56 832gopherStart(StoreEntry * entry)
090089c4 833{
75e88d56 834 char *url = entry->url;
582b6456 835 GopherStateData *gopherState = CreateGopherStateData();
9e4ad609 836 int fd;
770f051d 837 storeLockObject(entry);
582b6456 838 gopherState->entry = entry;
a3d5953d 839 debug(10, 3) ("gopherStart: url: %s\n", url);
090089c4 840 /* Parse url. */
582b6456 841 if (gopher_url_parser(url, gopherState->host, &gopherState->port,
842 &gopherState->type_id, gopherState->request)) {
474cac1b 843 storeAbort(entry, ERR_INVALID_URL, NULL, 0);
582b6456 844 gopherStateFree(-1, gopherState);
0a0bf5db 845 return;
090089c4 846 }
847 /* Create socket. */
9e4ad609 848 fd = comm_open(SOCK_STREAM,
16b204c4 849 0,
850 Config.Addrs.tcp_outgoing,
851 0,
852 COMM_NONBLOCKING,
853 url);
9e4ad609 854 if (fd == COMM_ERROR) {
a3d5953d 855 debug(10, 4) ("gopherStart: Failed because we're out of sockets.\n");
474cac1b 856 storeAbort(entry, ERR_NO_FDS, xstrerror(), 0);
582b6456 857 gopherStateFree(-1, gopherState);
0a0bf5db 858 return;
090089c4 859 }
bfcaf585 860 comm_add_close_handler(fd, gopherStateFree, gopherState);
861 storeRegisterAbort(entry, gopherAbort, gopherState);
090089c4 862 /* check if IP is already in cache. It must be.
863 * It should be done before this route is called.
864 * Otherwise, we cannot check return code for connect. */
582b6456 865 if (!ipcache_gethostbyname(gopherState->host, 0)) {
a3d5953d 866 debug(10, 4) ("gopherStart: Called without IP entry in ipcache. OR lookup failed.\n");
474cac1b 867 storeAbort(entry, ERR_DNS_FAIL, dns_error_message, 0);
9e4ad609 868 comm_close(fd);
0a0bf5db 869 return;
090089c4 870 }
582b6456 871 if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
86101e40 872 && (strchr(gopherState->request, '?') == NULL)) {
090089c4 873 /* Index URL without query word */
874 /* We have to generate search page back to client. No need for connection */
582b6456 875 gopherMimeCreate(gopherState);
876 if (gopherState->type_id == GOPHER_INDEX) {
877 gopherState->conversion = HTML_INDEX_PAGE;
090089c4 878 } else {
582b6456 879 if (gopherState->type_id == GOPHER_CSO) {
880 gopherState->conversion = HTML_CSO_PAGE;
090089c4 881 } else {
582b6456 882 gopherState->conversion = HTML_INDEX_PAGE;
090089c4 883 }
884 }
582b6456 885 gopherToHTML(gopherState, (char *) NULL, 0);
090089c4 886 storeComplete(entry);
9e4ad609 887 comm_close(fd);
0a0bf5db 888 return;
090089c4 889 }
9e4ad609 890 commSetTimeout(fd, Config.Timeout.connect, gopherTimeout, gopherState);
891 commConnectStart(fd,
582b6456 892 gopherState->host,
893 gopherState->port,
e924600d 894 gopherConnectDone,
582b6456 895 gopherState);
bfcaf585 896 gopherState->fd = fd;
e5f6c5c2 897}
898
899static void
900gopherConnectDone(int fd, int status, void *data)
901{
902 GopherStateData *gopherState = data;
9e975e4e 903 if (status == COMM_ERR_DNS) {
93f70dd9 904 debug(10, 4) ("gopherConnectDone: Unknown host: %s\n", gopherState->host);
9e975e4e 905 storeAbort(gopherState->entry, ERR_DNS_FAIL, dns_error_message, 0);
906 comm_close(fd);
907 } else if (status != COMM_OK) {
474cac1b 908 storeAbort(gopherState->entry, ERR_CONNECT_FAIL, xstrerror(), 0);
e5f6c5c2 909 comm_close(fd);
9e975e4e 910 } else {
911 commSetSelect(fd, COMM_SELECT_WRITE, gopherSendRequest, gopherState, 0);
090089c4 912 }
090089c4 913}
914
915
b8d8561b 916static GopherStateData *
0673c0ba 917CreateGopherStateData(void)
090089c4 918{
56fa4cad 919 GopherStateData *gd = xcalloc(1, sizeof(GopherStateData));
7dd44885 920 cbdataAdd(gd);
2daae136 921 gd->buf = get_free_4k_page();
090089c4 922 return (gd);
923}
bfcaf585 924
925static void
926gopherAbort(void *data)
927{
928 GopherStateData *gopherState = data;
a3d5953d 929 debug(10, 1) ("gopherAbort: %s\n", gopherState->entry->url);
bfcaf585 930 comm_close(gopherState->fd);
931}