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