]> git.ipfire.org Git - thirdparty/squid.git/blame - src/gopher.cc
ANSIFY
[thirdparty/squid.git] / src / gopher.cc
CommitLineData
30a4f2a8 1/*
56fa4cad 2 * $Id: gopher.cc,v 1.42 1996/09/12 16:39:53 wessels Exp $
30a4f2a8 3 *
4 * DEBUG: section 10 Gopher
5 * AUTHOR: Harvest Derived
6 *
7 * SQUID Internet Object Cache http://www.nlanr.net/Squid/
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;
fd45fa07 154 char request[MAX_URL + 1];
090089c4 155 int data_in;
156 int cso_recno;
157 int len;
158 char *buf; /* pts to a 4k page */
56fa4cad 159} GopherStateData;
160
161static int gopherStateFree _PARAMS((int fd, GopherStateData *));
162static void gopher_mime_content _PARAMS((char *buf, char *name, char *def));
163static void gopherMimeCreate _PARAMS((GopherStateData *));
164static int gopher_url_parser _PARAMS((char *url,
165 char *host,
166 int *port,
167 char *type_id,
168 char *request));
169static void gopherEndHTML _PARAMS((GopherStateData *));
170static void gopherToHTML _PARAMS((GopherStateData *, char *inbuf, int len));
171static int gopherReadReplyTimeout _PARAMS((int fd, GopherStateData *));
172static void gopherLifetimeExpire _PARAMS((int fd, GopherStateData *));
173static void gopherReadReply _PARAMS((int fd, GopherStateData *));
174static void gopherSendComplete _PARAMS((int fd,
175 char *buf,
176 int size,
177 int errflag,
178 void *data));
179static void gopherSendRequest _PARAMS((int fd, GopherStateData *));
180static GopherStateData *CreateGopherStateData _PARAMS((void));
181
182static char def_gopher_bin[] = "www/unknown";
183static char def_gopher_text[] = "text/plain";
090089c4 184
51fa90db 185static int gopherStateFree(fd, gopherState)
ba718c8f 186 int fd;
56fa4cad 187 GopherStateData *gopherState;
ba718c8f 188{
51fa90db 189 if (gopherState == NULL)
190 return 1;
30a4f2a8 191 if (gopherState->entry)
192 storeUnlockObject(gopherState->entry);
51fa90db 193 put_free_4k_page(gopherState->buf);
194 xfree(gopherState);
195 return 0;
ba718c8f 196}
197
198
090089c4 199/* figure out content type from file extension */
200static void gopher_mime_content(buf, name, def)
201 char *buf;
202 char *name;
203 char *def;
204{
95d659f0 205 LOCAL_ARRAY(char, temp, MAX_URL + 1);
090089c4 206 char *ext1 = NULL;
207 char *ext2 = NULL;
208 char *str = NULL;
209 ext_table_entry *e = NULL;
210
211 ext2 = NULL;
212 strcpy(temp, name);
213 for (ext1 = temp; *ext1; ext1++)
214 if (isupper(*ext1))
215 *ext1 = tolower(*ext1);
216 if ((ext1 = strrchr(temp, '.')) == NULL) {
217 /* use default */
218 sprintf(buf + strlen(buf), "Content-Type: %s\r\n", def);
219 return;
220 }
221 /* try extension table */
222 *ext1++ = 0;
223 if (strcmp("gz", ext1) == 0 || strcmp("z", ext1) == 0) {
224 ext2 = ext1;
225 if ((ext1 = strrchr(temp, '.')) == NULL) {
226 ext1 = ext2;
227 ext2 = NULL;
228 } else
229 ext1++;
230 }
231 if ((e = mime_ext_to_type(ext1)) == NULL) {
232 /* mime_ext_to_type() can return a NULL */
233 if (ext2 && (e = mime_ext_to_type(ext2))) {
234 str = e->mime_type;
235 ext2 = NULL;
236 } else {
237 str = def;
238 }
239 } else {
240 str = e->mime_type;
241 }
242 sprintf(buf + strlen(buf), "Content-Type: %s\r\n", str);
243 if (ext2 && (e = mime_ext_to_type(ext2))) {
244 sprintf(buf + strlen(buf), "Content-Encoding: %s\r\n",
245 e->mime_encoding);
246 }
247}
248
249
250
251/* create MIME Header for Gopher Data */
56fa4cad 252static void gopherMimeCreate(data)
253 GopherStateData *data;
090089c4 254{
95d659f0 255 LOCAL_ARRAY(char, tempMIME, MAX_MIME);
090089c4 256
257 sprintf(tempMIME, "\
258HTTP/1.0 200 OK Gatewaying\r\n\
c5c666ab 259Server: Squid/%s\r\n\
8213067d 260MIME-version: 1.0\r\n", version_string);
090089c4 261
262 switch (data->type_id) {
263
264 case GOPHER_DIRECTORY:
265 case GOPHER_INDEX:
266 case GOPHER_HTML:
267 case GOPHER_WWW:
268 case GOPHER_CSO:
269 strcat(tempMIME, "Content-Type: text/html\r\n");
270 break;
271 case GOPHER_GIF:
272 case GOPHER_IMAGE:
273 case GOPHER_PLUS_IMAGE:
274 strcat(tempMIME, "Content-Type: image/gif\r\n");
275 break;
276 case GOPHER_SOUND:
277 case GOPHER_PLUS_SOUND:
278 strcat(tempMIME, "Content-Type: audio/basic\r\n");
279 break;
280 case GOPHER_PLUS_MOVIE:
281 strcat(tempMIME, "Content-Type: video/mpeg\r\n");
282 break;
283 case GOPHER_MACBINHEX:
284 case GOPHER_DOSBIN:
285 case GOPHER_UUENCODED:
286 case GOPHER_BIN:
287 /* Rightnow We have no idea what it is. */
288 gopher_mime_content(tempMIME, data->request, def_gopher_bin);
289 break;
290
291 case GOPHER_FILE:
292 default:
293 gopher_mime_content(tempMIME, data->request, def_gopher_text);
294 break;
295
296 }
297
298 strcat(tempMIME, "\r\n");
299 storeAppend(data->entry, tempMIME, strlen(tempMIME));
300}
301
302/* Parse a gopher url into components. By Anawat. */
56fa4cad 303static int gopher_url_parser(url, host, port, type_id, request)
090089c4 304 char *url;
305 char *host;
306 int *port;
307 char *type_id;
308 char *request;
309{
95d659f0 310 LOCAL_ARRAY(char, proto, MAX_URL);
311 LOCAL_ARRAY(char, hostbuf, MAX_URL);
090089c4 312 int t;
313
de06a228 314 proto[0] = hostbuf[0] = '\0';
090089c4 315 host[0] = request[0] = '\0';
316 (*port) = 0;
317 (*type_id) = 0;
318
de06a228 319 t = sscanf(url, "%[a-zA-Z]://%[^/]/%c%s", proto, hostbuf,
090089c4 320 type_id, request);
de06a228 321 if ((t < 2) || strcasecmp(proto, "gopher")) {
090089c4 322 return -1;
323 } else if (t == 2) {
324 (*type_id) = GOPHER_DIRECTORY;
325 request[0] = '\0';
326 } else if (t == 3) {
327 request[0] = '\0';
328 } else {
329 /* convert %xx to char */
de06a228 330 (void) url_convert_hex(request, 0);
090089c4 331 }
332
333 host[0] = '\0';
334 if (sscanf(hostbuf, "%[^:]:%d", host, port) < 2)
335 (*port) = GOPHER_PORT;
336
337 return 0;
338}
339
6eb42cae 340int gopherCachable(url)
090089c4 341 char *url;
090089c4 342{
56fa4cad 343 GopherStateData *data = NULL;
090089c4 344 int cachable = 1;
090089c4 345 /* use as temp data structure to parse gopher URL */
56fa4cad 346 data = CreateGopherStateData();
090089c4 347 /* parse to see type */
56fa4cad 348 gopher_url_parser(url,
349 data->host,
350 &data->port,
351 &data->type_id,
352 data->request);
090089c4 353 switch (data->type_id) {
354 case GOPHER_INDEX:
355 case GOPHER_CSO:
356 case GOPHER_TELNET:
357 case GOPHER_3270:
358 cachable = 0;
359 break;
360 default:
361 cachable = 1;
362 }
51fa90db 363 gopherStateFree(-1, data);
090089c4 364 return cachable;
365}
366
56fa4cad 367static void gopherEndHTML(data)
368 GopherStateData *data;
090089c4 369{
95d659f0 370 LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
090089c4 371
372 if (!data->data_in) {
a8f7d3ee 373 sprintf(tmpbuf, "<HTML><HEAD><TITLE>Server Return Nothing.</TITLE>\n\
374 </HEAD><BODY><HR><H1>Server Return Nothing.</H1></BODY></HTML>\n");
090089c4 375 storeAppend(data->entry, tmpbuf, strlen(tmpbuf));
376 return;
377 }
378}
379
380
381/* Convert Gopher to HTML */
382/* Borrow part of code from libwww2 came with Mosaic distribution */
56fa4cad 383static void gopherToHTML(data, inbuf, len)
384 GopherStateData *data;
090089c4 385 char *inbuf;
386 int len;
387{
388 char *pos = inbuf;
389 char *lpos = NULL;
390 char *tline = NULL;
95d659f0 391 LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
392 LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
393 LOCAL_ARRAY(char, outbuf, TEMP_BUF_SIZE << 4);
090089c4 394 char *name = NULL;
395 char *selector = NULL;
396 char *host = NULL;
397 char *port = NULL;
398 char *escaped_selector = NULL;
399 char *icon_type = NULL;
400 char gtype;
401 StoreEntry *entry = NULL;
402
53d02438 403 memset(outbuf, '\0', TEMP_BUF_SIZE << 4);
404 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
405 memset(line, '\0', TEMP_BUF_SIZE);
090089c4 406
407 entry = data->entry;
408
409 if (data->conversion == HTML_INDEX_PAGE) {
a8f7d3ee 410 sprintf(outbuf, "<HTML><HEAD><TITLE>Gopher Index %s</TITLE></HEAD>\n\
411 <BODY><H1>%s<BR>Gopher Search</H1>\n\
412 <p>This is a searchable Gopher index. Use the search\n\
413 function of your browser to enter search terms.\n\
414 <ISINDEX></BODY></HTML>\n", entry->url, entry->url);
090089c4 415 storeAppend(entry, outbuf, strlen(outbuf));
416 /* now let start sending stuff to client */
417 BIT_RESET(entry->flag, DELAY_SENDING);
418 data->data_in = 1;
419
420 return;
421 }
422 if (data->conversion == HTML_CSO_PAGE) {
a8f7d3ee 423 sprintf(outbuf, "<HTML><HEAD><TITLE>CSO Search of %s</TITLE></HEAD>\n\
424 <BODY><H1>%s<BR>CSO Search</H1>\n\
425 <P>A CSO database usually contains a phonebook or\n\
426 directory. Use the search function of your browser to enter\n\
427 search terms.</P><ISINDEX></BODY></HTML>\n",
090089c4 428 entry->url, entry->url);
429
430 storeAppend(entry, outbuf, strlen(outbuf));
431 /* now let start sending stuff to client */
432 BIT_RESET(entry->flag, DELAY_SENDING);
433 data->data_in = 1;
434
435 return;
436 }
437 inbuf[len] = '\0';
438
439 if (!data->HTML_header_added) {
440 if (data->conversion == HTML_CSO_RESULT)
a8f7d3ee 441 strcat(outbuf, "<HTML><HEAD><TITLE>CSO Searchs Result</TITLE></HEAD>\n\
442 <BODY><H1>CSO Searchs Result</H1>\n<PRE>\n");
090089c4 443 else
a8f7d3ee 444 strcat(outbuf, "<HTML><HEAD><TITLE>Gopher Menu</TITLE></HEAD>\n\
445 <BODY><H1>Gopher Menu</H1>\n<PRE>\n");
090089c4 446 data->HTML_header_added = 1;
447 }
448 while ((pos != NULL) && (pos < inbuf + len)) {
449
450 if (data->len != 0) {
451 /* there is something left from last tx. */
452 strncpy(line, data->buf, data->len);
453 lpos = (char *) memccpy(line + data->len, inbuf, '\n', len);
454 if (lpos)
455 *lpos = '\0';
456 else {
457 /* there is no complete line in inbuf */
458 /* copy it to temp buffer */
459 if (data->len + len > TEMP_BUF_SIZE) {
019dd986 460 debug(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
090089c4 461 entry->url);
462 len = TEMP_BUF_SIZE - data->len;
463 }
30a4f2a8 464 xmemcpy(data->buf + data->len, inbuf, len);
090089c4 465 data->len += len;
466 return;
467 }
468
469 /* skip one line */
470 pos = (char *) memchr(pos, '\n', 256);
471 if (pos)
472 pos++;
473
474 /* we're done with the remain from last tx. */
475 data->len = 0;
476 *(data->buf) = '\0';
477 } else {
478
479 lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf));
480 if (lpos)
481 *lpos = '\0';
482 else {
483 /* there is no complete line in inbuf */
484 /* copy it to temp buffer */
485 if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) {
019dd986 486 debug(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
090089c4 487 entry->url);
488 len = TEMP_BUF_SIZE;
489 }
490 if (len > (pos - inbuf)) {
30a4f2a8 491 xmemcpy(data->buf, pos, len - (pos - inbuf));
090089c4 492 data->len = len - (pos - inbuf);
493 }
494 break;
495 }
496
497 /* skip one line */
498 pos = (char *) memchr(pos, '\n', 256);
499 if (pos)
500 pos++;
501
502 }
503
504 /* at this point. We should have one line in buffer to process */
505
506 if (*line == '.') {
507 /* skip it */
508 memset(line, '\0', TEMP_BUF_SIZE);
509 continue;
510 }
511 switch (data->conversion) {
512
513 case HTML_INDEX_RESULT:
514 case HTML_DIR:{
515 tline = line;
516 gtype = *tline++;
517 name = tline;
518 selector = strchr(tline, TAB);
519 if (selector) {
520 *selector++ = '\0';
521 host = strchr(selector, TAB);
522 if (host) {
523 *host++ = '\0';
524 port = strchr(host, TAB);
525 if (port) {
526 char *junk;
527 port[0] = ':';
528 junk = strchr(host, TAB);
529 if (junk)
530 *junk++ = 0; /* Chop port */
531 else {
532 junk = strchr(host, '\r');
533 if (junk)
534 *junk++ = 0; /* Chop port */
535 else {
536 junk = strchr(host, '\n');
537 if (junk)
538 *junk++ = 0; /* Chop port */
539 }
540 }
541 if ((port[1] == '0') && (!port[2]))
542 port[0] = 0; /* 0 means none */
543 }
544 /* escape a selector here */
545 escaped_selector = url_escape(selector);
546
547 switch (gtype) {
548 case GOPHER_DIRECTORY:
549 icon_type = "internal-gopher-menu";
550 break;
551 case GOPHER_FILE:
552 icon_type = "internal-gopher-text";
553 break;
554 case GOPHER_INDEX:
555 case GOPHER_CSO:
556 icon_type = "internal-gopher-index";
557 break;
558 case GOPHER_IMAGE:
559 case GOPHER_GIF:
560 case GOPHER_PLUS_IMAGE:
561 icon_type = "internal-gopher-image";
562 break;
563 case GOPHER_SOUND:
564 case GOPHER_PLUS_SOUND:
565 icon_type = "internal-gopher-sound";
566 break;
567 case GOPHER_PLUS_MOVIE:
568 icon_type = "internal-gopher-movie";
569 break;
570 case GOPHER_TELNET:
571 case GOPHER_3270:
572 icon_type = "internal-gopher-telnet";
573 break;
574 case GOPHER_BIN:
575 case GOPHER_MACBINHEX:
576 case GOPHER_DOSBIN:
577 case GOPHER_UUENCODED:
578 icon_type = "internal-gopher-binary";
579 break;
580 default:
581 icon_type = "internal-gopher-unknown";
582 break;
583 }
584
585
586 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
587 if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
588 if (strlen(escaped_selector) != 0)
589 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s@%s/\">%s</A>\n",
590 icon_type, escaped_selector, host, name);
591 else
592 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s/\">%s</A>\n",
593 icon_type, host, name);
594
595 } else {
596 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
597 icon_type, host, gtype, escaped_selector, name);
598 }
599 safe_free(escaped_selector);
600 strcat(outbuf, tmpbuf);
601 data->data_in = 1;
602 } else {
603 memset(line, '\0', TEMP_BUF_SIZE);
604 continue;
605 }
606 } else {
607 memset(line, '\0', TEMP_BUF_SIZE);
608 continue;
609 }
610 break;
611 } /* HTML_DIR, HTML_INDEX_RESULT */
612
613
614 case HTML_CSO_RESULT:{
615 int t;
616 int code;
617 int recno;
95d659f0 618 LOCAL_ARRAY(char, result, MAX_CSO_RESULT);
090089c4 619
620 tline = line;
621
622 if (tline[0] == '-') {
623 t = sscanf(tline, "-%d:%d:%[^\n]", &code, &recno, result);
624 if (t < 3)
625 break;
626
627 if (code != 200)
628 break;
629
630 if (data->cso_recno != recno) {
631 sprintf(tmpbuf, "</PRE><HR><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, result);
632 data->cso_recno = recno;
633 } else {
634 sprintf(tmpbuf, "%s\n", result);
635 }
636 strcat(outbuf, tmpbuf);
637 data->data_in = 1;
638 break;
639 } else {
640 /* handle some error codes */
641 t = sscanf(tline, "%d:%[^\n]", &code, result);
642
643 if (t < 2)
644 break;
645
646 switch (code) {
647
648 case 200:{
649 /* OK */
650 /* Do nothing here */
651 break;
652 }
653
654 case 102: /* Number of matches */
655 case 501: /* No Match */
656 case 502: /* Too Many Matches */
657 {
658 /* Print the message the server returns */
a8f7d3ee 659 sprintf(tmpbuf, "</PRE><HR><H2>%s</H2>\n<PRE>", result);
090089c4 660 strcat(outbuf, tmpbuf);
661 data->data_in = 1;
662 break;
663 }
664
665
666 }
667 }
668
669 } /* HTML_CSO_RESULT */
670 default:
671 break; /* do nothing */
672
673 } /* switch */
674
675 } /* while loop */
676
677 if ((int) strlen(outbuf) > 0) {
678 storeAppend(entry, outbuf, strlen(outbuf));
679 /* now let start sending stuff to client */
680 BIT_RESET(entry->flag, DELAY_SENDING);
681 }
682 return;
683}
684
56fa4cad 685static int gopherReadReplyTimeout(fd, data)
090089c4 686 int fd;
56fa4cad 687 GopherStateData *data;
090089c4 688{
689 StoreEntry *entry = NULL;
690 entry = data->entry;
019dd986 691 debug(10, 4, "GopherReadReplyTimeout: Timeout on %d\n url: %s\n", fd, entry->url);
b8de7ebe 692 squid_error_entry(entry, ERR_READ_TIMEOUT, NULL);
51fa90db 693 comm_close(fd);
090089c4 694 return 0;
695}
696
697/* This will be called when socket lifetime is expired. */
56fa4cad 698static void gopherLifetimeExpire(fd, data)
090089c4 699 int fd;
56fa4cad 700 GopherStateData *data;
090089c4 701{
702 StoreEntry *entry = NULL;
703 entry = data->entry;
019dd986 704 debug(10, 4, "gopherLifeTimeExpire: FD %d: <URL:%s>\n", fd, entry->url);
b8de7ebe 705 squid_error_entry(entry, ERR_LIFETIME_EXP, NULL);
d1cfbef7 706 comm_set_select_handler(fd,
707 COMM_SELECT_READ | COMM_SELECT_WRITE,
708 0,
709 0);
51fa90db 710 comm_close(fd);
090089c4 711}
712
713
714
715
716/* This will be called when data is ready to be read from fd. Read until
717 * error or connection closed. */
56fa4cad 718static void gopherReadReply(fd, data)
090089c4 719 int fd;
56fa4cad 720 GopherStateData *data;
090089c4 721{
722 char *buf = NULL;
723 int len;
724 int clen;
725 int off;
726 StoreEntry *entry = NULL;
56fa4cad 727 int bin;
090089c4 728
729 entry = data->entry;
56fa4cad 730 if (entry->flag & DELETE_BEHIND && !storeClientWaiting(entry)) {
731 /* we can terminate connection right now */
732 squid_error_entry(entry, ERR_NO_CLIENTS_BIG_OBJ, NULL);
733 comm_close(fd);
734 return;
735 }
736 /* check if we want to defer reading */
737 clen = entry->mem_obj->e_current_len;
738 off = storeGetLowestReaderOffset(entry);
739 if ((clen - off) > GOPHER_DELETE_GAP) {
740 if (entry->flag & CLIENT_ABORT_REQUEST) {
741 squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
51fa90db 742 comm_close(fd);
56fa4cad 743 return;
744 }
745 IOStats.Gopher.reads_deferred++;
746 debug(10, 3, "gopherReadReply: Read deferred for Object: %s\n",
747 entry->url);
748 debug(10, 3, " Current Gap: %d bytes\n", clen - off);
749 /* reschedule, so it will automatically reactivated when
750 * Gap is big enough. */
751 comm_set_select_handler(fd,
752 COMM_SELECT_READ,
753 (PF) gopherReadReply,
754 (void *) data);
755 /* don't install read timeout until we are below the GAP */
756 comm_set_select_handler_plus_timeout(fd,
757 COMM_SELECT_TIMEOUT,
758 (PF) NULL,
759 (void *) NULL,
760 (time_t) 0);
761 if (!BIT_TEST(entry->flag, READ_DEFERRED)) {
762 comm_set_fd_lifetime(fd, 3600); /* limit during deferring */
763 BIT_SET(entry->flag, READ_DEFERRED);
090089c4 764 }
56fa4cad 765 /* dont try reading again for a while */
766 comm_set_stall(fd, Config.stallDelay);
767 return;
768 } else {
769 BIT_RESET(entry->flag, READ_DEFERRED);
090089c4 770 }
2daae136 771 buf = get_free_4k_page();
6fe6313d 772 errno = 0;
56fa4cad 773 /* leave one space for \0 in gopherToHTML */
774 len = read(fd, buf, TEMP_BUF_SIZE - 1);
019dd986 775 debug(10, 5, "gopherReadReply: FD %d read len=%d\n", fd, len);
56fa4cad 776 if (len > 0) {
777 IOStats.Gopher.reads++;
778 for (clen = len - 1, bin = 0; clen; bin++)
779 clen >>= 1;
780 IOStats.Gopher.read_hist[bin]++;
781 }
ba718c8f 782 if (len < 0) {
783 debug(10, 1, "gopherReadReply: error reading: %s\n", xstrerror());
784 if (errno == EAGAIN || errno == EWOULDBLOCK) {
6fe6313d 785 /* reinstall handlers */
786 /* XXX This may loop forever */
56fa4cad 787 comm_set_select_handler(fd,
788 COMM_SELECT_READ,
789 (PF) gopherReadReply,
790 (void *) data);
791 comm_set_select_handler_plus_timeout(fd,
792 COMM_SELECT_TIMEOUT,
793 (PF) gopherReadReplyTimeout,
794 (void *) data,
795 Config.readTimeout);
6fe6313d 796 } else {
1c481e00 797 BIT_RESET(entry->flag, ENTRY_CACHABLE);
2daae136 798 storeReleaseRequest(entry);
b8de7ebe 799 squid_error_entry(entry, ERR_READ_ERROR, xstrerror());
51fa90db 800 comm_close(fd);
6fe6313d 801 }
ba718c8f 802 } else if (len == 0 && entry->mem_obj->e_current_len == 0) {
b8de7ebe 803 squid_error_entry(entry,
ba718c8f 804 ERR_ZERO_SIZE_OBJECT,
805 errno ? xstrerror() : NULL);
51fa90db 806 comm_close(fd);
090089c4 807 } else if (len == 0) {
808 /* Connection closed; retrieval done. */
809 /* flush the rest of data in temp buf if there is one. */
810 if (data->conversion != NORMAL)
811 gopherEndHTML(data);
812 if (!(entry->flag & DELETE_BEHIND))
dd44ede3 813 ttlSet(entry);
090089c4 814 BIT_RESET(entry->flag, DELAY_SENDING);
815 storeComplete(entry);
51fa90db 816 comm_close(fd);
b6f794d6 817 } else if (((entry->mem_obj->e_current_len + len) > Config.Gopher.maxObjSize) &&
090089c4 818 !(entry->flag & DELETE_BEHIND)) {
819 /* accept data, but start to delete behind it */
820 storeStartDeleteBehind(entry);
090089c4 821 if (data->conversion != NORMAL) {
822 gopherToHTML(data, buf, len);
823 } else {
824 storeAppend(entry, buf, len);
825 }
ba718c8f 826 comm_set_select_handler(fd,
827 COMM_SELECT_READ,
828 (PF) gopherReadReply,
51496678 829 (void *) data);
ba718c8f 830 comm_set_select_handler_plus_timeout(fd,
831 COMM_SELECT_TIMEOUT,
832 (PF) gopherReadReplyTimeout,
51496678 833 (void *) data,
b6f794d6 834 Config.readTimeout);
090089c4 835 } else if (entry->flag & CLIENT_ABORT_REQUEST) {
836 /* append the last bit of info we got */
837 if (data->conversion != NORMAL) {
838 gopherToHTML(data, buf, len);
839 } else {
840 storeAppend(entry, buf, len);
841 }
b8de7ebe 842 squid_error_entry(entry, ERR_CLIENT_ABORT, NULL);
090089c4 843 if (data->conversion != NORMAL)
844 gopherEndHTML(data);
845 BIT_RESET(entry->flag, DELAY_SENDING);
51fa90db 846 comm_close(fd);
090089c4 847 } else {
848 if (data->conversion != NORMAL) {
849 gopherToHTML(data, buf, len);
850 } else {
851 storeAppend(entry, buf, len);
852 }
ba718c8f 853 comm_set_select_handler(fd,
854 COMM_SELECT_READ,
855 (PF) gopherReadReply,
51496678 856 (void *) data);
ba718c8f 857 comm_set_select_handler_plus_timeout(fd,
858 COMM_SELECT_TIMEOUT,
859 (PF) gopherReadReplyTimeout,
51496678 860 (void *) data,
b6f794d6 861 Config.readTimeout);
090089c4 862 }
2daae136 863 put_free_4k_page(buf);
56fa4cad 864 return;
090089c4 865}
866
867/* This will be called when request write is complete. Schedule read of
868 * reply. */
56fa4cad 869static void gopherSendComplete(fd, buf, size, errflag, data)
090089c4 870 int fd;
871 char *buf;
872 int size;
873 int errflag;
30a4f2a8 874 void *data;
090089c4 875{
56fa4cad 876 GopherStateData *gopherState = (GopherStateData *) data;
090089c4 877 StoreEntry *entry = NULL;
30a4f2a8 878 entry = gopherState->entry;
019dd986 879 debug(10, 5, "gopherSendComplete: FD %d size: %d errflag: %d\n",
090089c4 880 fd, size, errflag);
881 if (errflag) {
b8de7ebe 882 squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
51fa90db 883 comm_close(fd);
090089c4 884 if (buf)
2daae136 885 put_free_4k_page(buf); /* Allocated by gopherSendRequest. */
44a47c6e 886 return;
090089c4 887 }
888 /*
889 * OK. We successfully reach remote site. Start MIME typing
890 * stuff. Do it anyway even though request is not HTML type.
891 */
30a4f2a8 892 gopherMimeCreate(gopherState);
090089c4 893
6eb42cae 894 if (!BIT_TEST(entry->flag, ENTRY_HTML))
30a4f2a8 895 gopherState->conversion = NORMAL;
090089c4 896 else
30a4f2a8 897 switch (gopherState->type_id) {
090089c4 898
899 case GOPHER_DIRECTORY:
900 /* we got to convert it first */
901 BIT_SET(entry->flag, DELAY_SENDING);
30a4f2a8 902 gopherState->conversion = HTML_DIR;
903 gopherState->HTML_header_added = 0;
090089c4 904 break;
905
906 case GOPHER_INDEX:
907 /* we got to convert it first */
908 BIT_SET(entry->flag, DELAY_SENDING);
30a4f2a8 909 gopherState->conversion = HTML_INDEX_RESULT;
910 gopherState->HTML_header_added = 0;
090089c4 911 break;
912
913 case GOPHER_CSO:
914 /* we got to convert it first */
915 BIT_SET(entry->flag, DELAY_SENDING);
30a4f2a8 916 gopherState->conversion = HTML_CSO_RESULT;
917 gopherState->cso_recno = 0;
918 gopherState->HTML_header_added = 0;
090089c4 919 break;
920
921 default:
30a4f2a8 922 gopherState->conversion = NORMAL;
090089c4 923
924 }
925 /* Schedule read reply. */
926 comm_set_select_handler(fd,
927 COMM_SELECT_READ,
928 (PF) gopherReadReply,
30a4f2a8 929 (void *) gopherState);
090089c4 930 comm_set_select_handler_plus_timeout(fd,
931 COMM_SELECT_TIMEOUT,
932 (PF) gopherReadReplyTimeout,
30a4f2a8 933 (void *) gopherState,
b6f794d6 934 Config.readTimeout);
30a4f2a8 935 comm_set_fd_lifetime(fd, 86400); /* extend lifetime */
090089c4 936
937 if (buf)
2daae136 938 put_free_4k_page(buf); /* Allocated by gopherSendRequest. */
090089c4 939}
940
941/* This will be called when connect completes. Write request. */
56fa4cad 942static void gopherSendRequest(fd, data)
090089c4 943 int fd;
56fa4cad 944 GopherStateData *data;
090089c4 945{
090089c4 946 int len;
95d659f0 947 LOCAL_ARRAY(char, query, MAX_URL);
2daae136 948 char *buf = get_free_4k_page();
090089c4 949
090089c4 950 if (data->type_id == GOPHER_CSO) {
951 sscanf(data->request, "?%s", query);
952 len = strlen(query) + 15;
2285407f 953 sprintf(buf, "query %s\r\nquit\r\n", query);
090089c4 954 } else if (data->type_id == GOPHER_INDEX) {
955 char *c_ptr = strchr(data->request, '?');
956 if (c_ptr) {
957 *c_ptr = '\t';
958 }
959 len = strlen(data->request) + 3;
2285407f 960 sprintf(buf, "%s\r\n", data->request);
090089c4 961 } else {
962 len = strlen(data->request) + 3;
2285407f 963 sprintf(buf, "%s\r\n", data->request);
090089c4 964 }
965
019dd986 966 debug(10, 5, "gopherSendRequest: FD %d\n", fd);
30a4f2a8 967 comm_write(fd,
44a47c6e 968 buf,
969 len,
970 30,
971 gopherSendComplete,
9864ee44 972 (void *) data,
973 put_free_4k_page);
1c481e00 974 if (BIT_TEST(data->entry->flag, ENTRY_CACHABLE))
147d3115 975 storeSetPublicKey(data->entry); /* Make it public */
090089c4 976}
977
978int gopherStart(unusedfd, url, entry)
979 int unusedfd;
980 char *url;
981 StoreEntry *entry;
982{
983 /* Create state structure. */
984 int sock, status;
56fa4cad 985 GopherStateData *data = CreateGopherStateData();
090089c4 986
30a4f2a8 987 storeLockObject(data->entry = entry, NULL, NULL);
090089c4 988
019dd986 989 debug(10, 3, "gopherStart: url: %s\n", url);
090089c4 990
991 /* Parse url. */
992 if (gopher_url_parser(url, data->host, &data->port,
993 &data->type_id, data->request)) {
b8de7ebe 994 squid_error_entry(entry, ERR_INVALID_URL, NULL);
51fa90db 995 gopherStateFree(-1, data);
090089c4 996 return COMM_ERROR;
997 }
998 /* Create socket. */
b6f794d6 999 sock = comm_open(COMM_NONBLOCKING, Config.Addrs.tcp_outgoing, 0, url);
090089c4 1000 if (sock == COMM_ERROR) {
019dd986 1001 debug(10, 4, "gopherStart: Failed because we're out of sockets.\n");
b8de7ebe 1002 squid_error_entry(entry, ERR_NO_FDS, xstrerror());
51fa90db 1003 gopherStateFree(-1, data);
090089c4 1004 return COMM_ERROR;
1005 }
30a4f2a8 1006 comm_add_close_handler(sock,
983061ed 1007 (PF) gopherStateFree,
51fa90db 1008 (void *) data);
1009
090089c4 1010 /* check if IP is already in cache. It must be.
1011 * It should be done before this route is called.
1012 * Otherwise, we cannot check return code for connect. */
30a4f2a8 1013 if (!ipcache_gethostbyname(data->host, 0)) {
019dd986 1014 debug(10, 4, "gopherStart: Called without IP entry in ipcache. OR lookup failed.\n");
b8de7ebe 1015 squid_error_entry(entry, ERR_DNS_FAIL, dns_error_message);
51fa90db 1016 comm_close(sock);
090089c4 1017 return COMM_ERROR;
1018 }
1019 if (((data->type_id == GOPHER_INDEX) || (data->type_id == GOPHER_CSO))
1020 && (strchr(data->request, '?') == NULL)
6eb42cae 1021 && (BIT_TEST(entry->flag, ENTRY_HTML))) {
090089c4 1022 /* Index URL without query word */
1023 /* We have to generate search page back to client. No need for connection */
1024 gopherMimeCreate(data);
1025
1026 if (data->type_id == GOPHER_INDEX) {
1027 data->conversion = HTML_INDEX_PAGE;
1028 } else {
1029 if (data->type_id == GOPHER_CSO) {
1030 data->conversion = HTML_CSO_PAGE;
1031 } else {
1032 data->conversion = HTML_INDEX_PAGE;
1033 }
1034 }
1035 gopherToHTML(data, (char *) NULL, 0);
1036 storeComplete(entry);
51fa90db 1037 comm_close(sock);
090089c4 1038 return COMM_OK;
1039 }
1040 /* Open connection. */
1041 if ((status = comm_connect(sock, data->host, data->port)) != 0) {
1042 if (status != EINPROGRESS) {
b8de7ebe 1043 squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
51fa90db 1044 comm_close(sock);
090089c4 1045 return COMM_ERROR;
1046 } else {
019dd986 1047 debug(10, 5, "startGopher: conn %d EINPROGRESS\n", sock);
090089c4 1048 }
1049 }
1050 /* Install connection complete handler. */
f900607e 1051 if (opt_no_ipcache)
1052 ipcacheInvalidate(data->host);
d1cfbef7 1053 comm_set_select_handler(sock,
1054 COMM_SELECT_LIFETIME,
1055 (PF) gopherLifetimeExpire,
51496678 1056 (void *) data);
d1cfbef7 1057 comm_set_select_handler(sock,
1058 COMM_SELECT_WRITE,
1059 (PF) gopherSendRequest,
51496678 1060 (void *) data);
090089c4 1061 return COMM_OK;
1062}
1063
1064
56fa4cad 1065static GopherStateData *CreateGopherStateData()
090089c4 1066{
56fa4cad 1067 GopherStateData *gd = xcalloc(1, sizeof(GopherStateData));
2daae136 1068 gd->buf = get_free_4k_page();
090089c4 1069 return (gd);
1070}