]> git.ipfire.org Git - thirdparty/squid.git/blame - src/gopher.cc
assign store.c to debugging section 20
[thirdparty/squid.git] / src / gopher.cc
CommitLineData
6fe6313d 1/* $Id: gopher.cc,v 1.10 1996/03/28 20:42:47 wessels Exp $ */
44a47c6e 2
3#include "squid.h"
090089c4 4
5extern char *dns_error_message;
090089c4 6
7/* gopher type code from rfc. Anawat. */
8#define GOPHER_FILE '0'
9#define GOPHER_DIRECTORY '1'
10#define GOPHER_CSO '2'
11#define GOPHER_ERROR '3'
12#define GOPHER_MACBINHEX '4'
13#define GOPHER_DOSBIN '5'
14#define GOPHER_UUENCODED '6'
15#define GOPHER_INDEX '7'
16#define GOPHER_TELNET '8'
17#define GOPHER_BIN '9'
18#define GOPHER_REDUNT '+'
19#define GOPHER_3270 'T'
20#define GOPHER_GIF 'g'
21#define GOPHER_IMAGE 'I'
22
23#define GOPHER_HTML 'h' /* HTML */
24#define GOPHER_INFO 'i'
25#define GOPHER_WWW 'w' /* W3 address */
26#define GOPHER_SOUND 's'
27
28#define GOPHER_PLUS_IMAGE ':'
29#define GOPHER_PLUS_MOVIE ';'
30#define GOPHER_PLUS_SOUND '<'
31
32#define GOPHER_PORT 70
33#define GOPHER_DELETE_GAP (64*1024)
34
35#define TAB '\t'
36#define TEMP_BUF_SIZE SM_PAGE_SIZE
37#define MAX_CSO_RESULT 1024
38
39typedef struct gopher_ds {
40 StoreEntry *entry;
ed43818f 41 char host[SQUIDHOSTNAMELEN + 1];
090089c4 42 enum {
43 NORMAL,
44 HTML_DIR,
45 HTML_INDEX_RESULT,
46 HTML_CSO_RESULT,
47 HTML_INDEX_PAGE,
48 HTML_CSO_PAGE
49 } conversion;
50 int HTML_header_added;
51 int port;
52 char *mime_hdr;
53 char type_id;
54 char request[MAX_URL];
55 int data_in;
56 int cso_recno;
57 int len;
58 char *buf; /* pts to a 4k page */
59 char *icp_page_ptr; /* Pts to gopherStart buffer that needs to be freed */
60 char *icp_rwd_ptr; /* Pts to icp rw structure that needs to be freed */
61} GopherData;
62
63GopherData *CreateGopherData();
64
65static void freeGopherData _PARAMS((GopherData *));
66
67char def_gopher_bin[] = "www/unknown";
68char def_gopher_text[] = "text/plain";
69
70/* figure out content type from file extension */
71static void gopher_mime_content(buf, name, def)
72 char *buf;
73 char *name;
74 char *def;
75{
76 static char temp[MAX_URL];
77 char *ext1 = NULL;
78 char *ext2 = NULL;
79 char *str = NULL;
80 ext_table_entry *e = NULL;
81
82 ext2 = NULL;
83 strcpy(temp, name);
84 for (ext1 = temp; *ext1; ext1++)
85 if (isupper(*ext1))
86 *ext1 = tolower(*ext1);
87 if ((ext1 = strrchr(temp, '.')) == NULL) {
88 /* use default */
89 sprintf(buf + strlen(buf), "Content-Type: %s\r\n", def);
90 return;
91 }
92 /* try extension table */
93 *ext1++ = 0;
94 if (strcmp("gz", ext1) == 0 || strcmp("z", ext1) == 0) {
95 ext2 = ext1;
96 if ((ext1 = strrchr(temp, '.')) == NULL) {
97 ext1 = ext2;
98 ext2 = NULL;
99 } else
100 ext1++;
101 }
102 if ((e = mime_ext_to_type(ext1)) == NULL) {
103 /* mime_ext_to_type() can return a NULL */
104 if (ext2 && (e = mime_ext_to_type(ext2))) {
105 str = e->mime_type;
106 ext2 = NULL;
107 } else {
108 str = def;
109 }
110 } else {
111 str = e->mime_type;
112 }
113 sprintf(buf + strlen(buf), "Content-Type: %s\r\n", str);
114 if (ext2 && (e = mime_ext_to_type(ext2))) {
115 sprintf(buf + strlen(buf), "Content-Encoding: %s\r\n",
116 e->mime_encoding);
117 }
118}
119
120
121
122/* create MIME Header for Gopher Data */
123void gopherMimeCreate(data)
124 GopherData *data;
125{
126 static char tempMIME[MAX_MIME];
127
128 sprintf(tempMIME, "\
129HTTP/1.0 200 OK Gatewaying\r\n\
130Server: HarvestCache/%s\r\n\
73a5465f 131MIME-version: 1.0\r\n", SQUID_VERSION);
090089c4 132
133 switch (data->type_id) {
134
135 case GOPHER_DIRECTORY:
136 case GOPHER_INDEX:
137 case GOPHER_HTML:
138 case GOPHER_WWW:
139 case GOPHER_CSO:
140 strcat(tempMIME, "Content-Type: text/html\r\n");
141 break;
142 case GOPHER_GIF:
143 case GOPHER_IMAGE:
144 case GOPHER_PLUS_IMAGE:
145 strcat(tempMIME, "Content-Type: image/gif\r\n");
146 break;
147 case GOPHER_SOUND:
148 case GOPHER_PLUS_SOUND:
149 strcat(tempMIME, "Content-Type: audio/basic\r\n");
150 break;
151 case GOPHER_PLUS_MOVIE:
152 strcat(tempMIME, "Content-Type: video/mpeg\r\n");
153 break;
154 case GOPHER_MACBINHEX:
155 case GOPHER_DOSBIN:
156 case GOPHER_UUENCODED:
157 case GOPHER_BIN:
158 /* Rightnow We have no idea what it is. */
159 gopher_mime_content(tempMIME, data->request, def_gopher_bin);
160 break;
161
162 case GOPHER_FILE:
163 default:
164 gopher_mime_content(tempMIME, data->request, def_gopher_text);
165 break;
166
167 }
168
169 strcat(tempMIME, "\r\n");
170 storeAppend(data->entry, tempMIME, strlen(tempMIME));
171}
172
173/* Parse a gopher url into components. By Anawat. */
174int gopher_url_parser(url, host, port, type_id, request)
175 char *url;
176 char *host;
177 int *port;
178 char *type_id;
179 char *request;
180{
181 static char atypebuf[MAX_URL];
182 static char hostbuf[MAX_URL];
183 char *tmp = NULL;
184 int t;
185
186 atypebuf[0] = hostbuf[0] = '\0';
187 host[0] = request[0] = '\0';
188 (*port) = 0;
189 (*type_id) = 0;
190
191 t = sscanf(url, "%[a-zA-Z]://%[^/]/%c%s", atypebuf, hostbuf,
192 type_id, request);
193 if ((t < 2) || strcasecmp(atypebuf, "gopher")) {
194 return -1;
195 } else if (t == 2) {
196 (*type_id) = GOPHER_DIRECTORY;
197 request[0] = '\0';
198 } else if (t == 3) {
199 request[0] = '\0';
200 } else {
201 /* convert %xx to char */
202 tmp = url_convert_hex(request);
203 strncpy(request, tmp, MAX_URL);
204 safe_free(tmp);
205 }
206
207 host[0] = '\0';
208 if (sscanf(hostbuf, "%[^:]:%d", host, port) < 2)
209 (*port) = GOPHER_PORT;
210
211 return 0;
212}
213
214int gopherCachable(url, type, mime_hdr)
215 char *url;
216 char *type;
217 char *mime_hdr;
218{
219 stoplist *p = NULL;
220 GopherData *data = NULL;
221 int cachable = 1;
222
223 /* scan stop list */
224 for (p = gopher_stoplist; p; p = p->next)
225 if (strstr(url, p->key))
226 return 0;
227
228 /* use as temp data structure to parse gopher URL */
229 data = CreateGopherData();
230
231 /* parse to see type */
232 gopher_url_parser(url, data->host, &data->port, &data->type_id, data->request);
233
234 switch (data->type_id) {
235 case GOPHER_INDEX:
236 case GOPHER_CSO:
237 case GOPHER_TELNET:
238 case GOPHER_3270:
239 cachable = 0;
240 break;
241 default:
242 cachable = 1;
243 }
244 freeGopherData(data);
245
246 return cachable;
247}
248
249void gopherEndHTML(data)
250 GopherData *data;
251{
252 static char tmpbuf[TEMP_BUF_SIZE];
253
254 if (!data->data_in) {
255 sprintf(tmpbuf, "<HR><H2><i>Server Return Nothing.</i></H2>\n");
256 storeAppend(data->entry, tmpbuf, strlen(tmpbuf));
257 return;
258 }
259}
260
261
262/* Convert Gopher to HTML */
263/* Borrow part of code from libwww2 came with Mosaic distribution */
264void gopherToHTML(data, inbuf, len)
265 GopherData *data;
266 char *inbuf;
267 int len;
268{
269 char *pos = inbuf;
270 char *lpos = NULL;
271 char *tline = NULL;
272 static char line[TEMP_BUF_SIZE];
273 static char tmpbuf[TEMP_BUF_SIZE];
274 static char outbuf[TEMP_BUF_SIZE << 4];
275 char *name = NULL;
276 char *selector = NULL;
277 char *host = NULL;
278 char *port = NULL;
279 char *escaped_selector = NULL;
280 char *icon_type = NULL;
281 char gtype;
282 StoreEntry *entry = NULL;
283
284 memset(outbuf, '\0', sizeof(outbuf));
285 memset(tmpbuf, '\0', sizeof(outbuf));
286 memset(line, '\0', sizeof(outbuf));
287
288 entry = data->entry;
289
290 if (data->conversion == HTML_INDEX_PAGE) {
291 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);
292
293 storeAppend(entry, outbuf, strlen(outbuf));
294 /* now let start sending stuff to client */
295 BIT_RESET(entry->flag, DELAY_SENDING);
296 data->data_in = 1;
297
298 return;
299 }
300 if (data->conversion == HTML_CSO_PAGE) {
301 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",
302 entry->url, entry->url);
303
304 storeAppend(entry, outbuf, strlen(outbuf));
305 /* now let start sending stuff to client */
306 BIT_RESET(entry->flag, DELAY_SENDING);
307 data->data_in = 1;
308
309 return;
310 }
311 inbuf[len] = '\0';
312
313 if (!data->HTML_header_added) {
314 if (data->conversion == HTML_CSO_RESULT)
315 strcat(outbuf, "<H1>CSO Searchs Result</H1>\n<PRE>\n");
316 else
317 strcat(outbuf, "<H1>Gopher Menu</H1>\n<PRE>\n");
318 data->HTML_header_added = 1;
319 }
320 while ((pos != NULL) && (pos < inbuf + len)) {
321
322 if (data->len != 0) {
323 /* there is something left from last tx. */
324 strncpy(line, data->buf, data->len);
325 lpos = (char *) memccpy(line + data->len, inbuf, '\n', len);
326 if (lpos)
327 *lpos = '\0';
328 else {
329 /* there is no complete line in inbuf */
330 /* copy it to temp buffer */
331 if (data->len + len > TEMP_BUF_SIZE) {
12b9e9b1 332 debug(0, 1, "GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
090089c4 333 entry->url);
334 len = TEMP_BUF_SIZE - data->len;
335 }
336 memcpy(data->buf + data->len, inbuf, len);
337 data->len += len;
338 return;
339 }
340
341 /* skip one line */
342 pos = (char *) memchr(pos, '\n', 256);
343 if (pos)
344 pos++;
345
346 /* we're done with the remain from last tx. */
347 data->len = 0;
348 *(data->buf) = '\0';
349 } else {
350
351 lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf));
352 if (lpos)
353 *lpos = '\0';
354 else {
355 /* there is no complete line in inbuf */
356 /* copy it to temp buffer */
357 if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) {
12b9e9b1 358 debug(0, 1, "GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
090089c4 359 entry->url);
360 len = TEMP_BUF_SIZE;
361 }
362 if (len > (pos - inbuf)) {
363 memcpy(data->buf, pos, len - (pos - inbuf));
364 data->len = len - (pos - inbuf);
365 }
366 break;
367 }
368
369 /* skip one line */
370 pos = (char *) memchr(pos, '\n', 256);
371 if (pos)
372 pos++;
373
374 }
375
376 /* at this point. We should have one line in buffer to process */
377
378 if (*line == '.') {
379 /* skip it */
380 memset(line, '\0', TEMP_BUF_SIZE);
381 continue;
382 }
383 switch (data->conversion) {
384
385 case HTML_INDEX_RESULT:
386 case HTML_DIR:{
387 tline = line;
388 gtype = *tline++;
389 name = tline;
390 selector = strchr(tline, TAB);
391 if (selector) {
392 *selector++ = '\0';
393 host = strchr(selector, TAB);
394 if (host) {
395 *host++ = '\0';
396 port = strchr(host, TAB);
397 if (port) {
398 char *junk;
399 port[0] = ':';
400 junk = strchr(host, TAB);
401 if (junk)
402 *junk++ = 0; /* Chop port */
403 else {
404 junk = strchr(host, '\r');
405 if (junk)
406 *junk++ = 0; /* Chop port */
407 else {
408 junk = strchr(host, '\n');
409 if (junk)
410 *junk++ = 0; /* Chop port */
411 }
412 }
413 if ((port[1] == '0') && (!port[2]))
414 port[0] = 0; /* 0 means none */
415 }
416 /* escape a selector here */
417 escaped_selector = url_escape(selector);
418
419 switch (gtype) {
420 case GOPHER_DIRECTORY:
421 icon_type = "internal-gopher-menu";
422 break;
423 case GOPHER_FILE:
424 icon_type = "internal-gopher-text";
425 break;
426 case GOPHER_INDEX:
427 case GOPHER_CSO:
428 icon_type = "internal-gopher-index";
429 break;
430 case GOPHER_IMAGE:
431 case GOPHER_GIF:
432 case GOPHER_PLUS_IMAGE:
433 icon_type = "internal-gopher-image";
434 break;
435 case GOPHER_SOUND:
436 case GOPHER_PLUS_SOUND:
437 icon_type = "internal-gopher-sound";
438 break;
439 case GOPHER_PLUS_MOVIE:
440 icon_type = "internal-gopher-movie";
441 break;
442 case GOPHER_TELNET:
443 case GOPHER_3270:
444 icon_type = "internal-gopher-telnet";
445 break;
446 case GOPHER_BIN:
447 case GOPHER_MACBINHEX:
448 case GOPHER_DOSBIN:
449 case GOPHER_UUENCODED:
450 icon_type = "internal-gopher-binary";
451 break;
452 default:
453 icon_type = "internal-gopher-unknown";
454 break;
455 }
456
457
458 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
459 if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
460 if (strlen(escaped_selector) != 0)
461 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s@%s/\">%s</A>\n",
462 icon_type, escaped_selector, host, name);
463 else
464 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s/\">%s</A>\n",
465 icon_type, host, name);
466
467 } else {
468 sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
469 icon_type, host, gtype, escaped_selector, name);
470 }
471 safe_free(escaped_selector);
472 strcat(outbuf, tmpbuf);
473 data->data_in = 1;
474 } else {
475 memset(line, '\0', TEMP_BUF_SIZE);
476 continue;
477 }
478 } else {
479 memset(line, '\0', TEMP_BUF_SIZE);
480 continue;
481 }
482 break;
483 } /* HTML_DIR, HTML_INDEX_RESULT */
484
485
486 case HTML_CSO_RESULT:{
487 int t;
488 int code;
489 int recno;
490 char result[MAX_CSO_RESULT];
491
492 tline = line;
493
494 if (tline[0] == '-') {
495 t = sscanf(tline, "-%d:%d:%[^\n]", &code, &recno, result);
496 if (t < 3)
497 break;
498
499 if (code != 200)
500 break;
501
502 if (data->cso_recno != recno) {
503 sprintf(tmpbuf, "</PRE><HR><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, result);
504 data->cso_recno = recno;
505 } else {
506 sprintf(tmpbuf, "%s\n", result);
507 }
508 strcat(outbuf, tmpbuf);
509 data->data_in = 1;
510 break;
511 } else {
512 /* handle some error codes */
513 t = sscanf(tline, "%d:%[^\n]", &code, result);
514
515 if (t < 2)
516 break;
517
518 switch (code) {
519
520 case 200:{
521 /* OK */
522 /* Do nothing here */
523 break;
524 }
525
526 case 102: /* Number of matches */
527 case 501: /* No Match */
528 case 502: /* Too Many Matches */
529 {
530 /* Print the message the server returns */
531 sprintf(tmpbuf, "</PRE><HR><H2><i>%s</i></H2>\n<PRE>", result);
532 strcat(outbuf, tmpbuf);
533 data->data_in = 1;
534 break;
535 }
536
537
538 }
539 }
540
541 } /* HTML_CSO_RESULT */
542 default:
543 break; /* do nothing */
544
545 } /* switch */
546
547 } /* while loop */
548
549 if ((int) strlen(outbuf) > 0) {
550 storeAppend(entry, outbuf, strlen(outbuf));
551 /* now let start sending stuff to client */
552 BIT_RESET(entry->flag, DELAY_SENDING);
553 }
554 return;
555}
556
557
558int gopherReadReplyTimeout(fd, data)
559 int fd;
560 GopherData *data;
561{
562 StoreEntry *entry = NULL;
563 entry = data->entry;
12b9e9b1 564 debug(0, 4, "GopherReadReplyTimeout: Timeout on %d\n url: %s\n", fd, entry->url);
b367f261 565 cached_error_entry(entry, ERR_READ_TIMEOUT, NULL);
090089c4 566 if (data->icp_page_ptr)
567 put_free_4k_page(data->icp_page_ptr);
568 if (data->icp_rwd_ptr)
569 safe_free(data->icp_rwd_ptr);
570 comm_close(fd);
090089c4 571 freeGopherData(data);
572 return 0;
573}
574
575/* This will be called when socket lifetime is expired. */
576void gopherLifetimeExpire(fd, data)
577 int fd;
578 GopherData *data;
579{
580 StoreEntry *entry = NULL;
581 entry = data->entry;
12b9e9b1 582 debug(0, 4, "gopherLifeTimeExpire: FD %d: <URL:%s>\n", fd, entry->url);
b367f261 583 cached_error_entry(entry, ERR_LIFETIME_EXP, NULL);
090089c4 584 if (data->icp_page_ptr)
585 put_free_4k_page(data->icp_page_ptr);
586 if (data->icp_rwd_ptr)
587 safe_free(data->icp_rwd_ptr);
d1cfbef7 588 comm_set_select_handler(fd,
589 COMM_SELECT_READ | COMM_SELECT_WRITE,
590 0,
591 0);
090089c4 592 comm_close(fd);
090089c4 593 freeGopherData(data);
594}
595
596
597
598
599/* This will be called when data is ready to be read from fd. Read until
600 * error or connection closed. */
601int gopherReadReply(fd, data)
602 int fd;
603 GopherData *data;
604{
605 char *buf = NULL;
606 int len;
607 int clen;
608 int off;
609 StoreEntry *entry = NULL;
610
611 entry = data->entry;
612 if (entry->flag & DELETE_BEHIND) {
613 if (storeClientWaiting(entry)) {
22e4fa85 614 clen = entry->mem_obj->e_current_len;
615 off = entry->mem_obj->e_lowest_offset;
090089c4 616 if ((clen - off) > GOPHER_DELETE_GAP) {
12b9e9b1 617 debug(0, 3, "gopherReadReply: Read deferred for Object: %s\n",
090089c4 618 entry->key);
12b9e9b1 619 debug(0, 3, " Current Gap: %d bytes\n",
090089c4 620 clen - off);
621
622 /* reschedule, so it will automatically reactivated when
623 * Gap is big enough. */
624 comm_set_select_handler(fd,
625 COMM_SELECT_READ,
626 (PF) gopherReadReply,
627 (caddr_t) data);
628/* don't install read timeout until we are below the GAP */
629#ifdef INSTALL_READ_TIMEOUT_ABOVE_GAP
630 comm_set_select_handler_plus_timeout(fd,
631 COMM_SELECT_TIMEOUT,
632 (PF) gopherReadReplyTimeout,
633 (caddr_t) data,
634 getReadTimeout());
635#else
636 comm_set_select_handler_plus_timeout(fd,
637 COMM_SELECT_TIMEOUT,
638 (PF) NULL,
639 (caddr_t) NULL,
640 (time_t) 0);
641#endif
45cd3339 642 comm_set_stall(fd, getStallDelay()); /* dont try reading again for a while */
090089c4 643 return 0;
644 }
645 } else {
646 /* we can terminate connection right now */
b367f261 647 cached_error_entry(entry, ERR_NO_CLIENTS_BIG_OBJ, NULL);
090089c4 648 comm_close(fd);
090089c4 649 freeGopherData(data);
650 return 0;
651 }
652 }
653 buf = get_free_4k_page();
6fe6313d 654 errno = 0;
090089c4 655 len = read(fd, buf, TEMP_BUF_SIZE - 1); /* leave one space for \0 in gopherToHTML */
6fe6313d 656 debug(0, 5, "gopherReadReply: FD %d read len=%d\n", fd, len);
090089c4 657
22e4fa85 658 if (len < 0 || ((len == 0) && (entry->mem_obj->e_current_len == 0))) {
6fe6313d 659 debug(0, 1, "gopherReadReply: error reading: %s\n",
090089c4 660 xstrerror());
6fe6313d 661 if (errno == ECONNRESET) {
662 /* Connection reset by peer */
663 /* consider it as a EOF */
664 if (!(entry->flag & DELETE_BEHIND))
665 entry->expires = cached_curtime + ttlSet(entry);
666 sprintf(tmp_error_buf, "\nWarning: The Remote Server sent RESET at the end of transmission.\n");
667 storeAppend(entry, tmp_error_buf, strlen(tmp_error_buf));
668 storeComplete(entry);
669 comm_close(fd);
670 freeGopherData(data);
671 } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
672 /* reinstall handlers */
673 /* XXX This may loop forever */
674 comm_set_select_handler(fd, COMM_SELECT_READ,
675 (PF) gopherReadReply, (caddr_t) data);
676 comm_set_select_handler_plus_timeout(fd, COMM_SELECT_TIMEOUT,
677 (PF) gopherReadReplyTimeout, (caddr_t) data, getReadTimeout());
678 } else {
679 cached_error_entry(entry, ERR_READ_ERROR, xstrerror());
680 comm_close(fd);
681 freeGopherData(data);
682 }
090089c4 683 } else if (len == 0) {
684 /* Connection closed; retrieval done. */
685 /* flush the rest of data in temp buf if there is one. */
686 if (data->conversion != NORMAL)
687 gopherEndHTML(data);
688 if (!(entry->flag & DELETE_BEHIND))
689 entry->expires = cached_curtime + ttlSet(entry);
690 BIT_RESET(entry->flag, DELAY_SENDING);
691 storeComplete(entry);
692 comm_close(fd);
693 freeGopherData(data);
22e4fa85 694 } else if (((entry->mem_obj->e_current_len + len) > getGopherMax()) &&
090089c4 695 !(entry->flag & DELETE_BEHIND)) {
696 /* accept data, but start to delete behind it */
697 storeStartDeleteBehind(entry);
698
699 if (data->conversion != NORMAL) {
700 gopherToHTML(data, buf, len);
701 } else {
702 storeAppend(entry, buf, len);
703 }
704 comm_set_select_handler(fd, COMM_SELECT_READ, (PF) gopherReadReply, (caddr_t) data);
705 comm_set_select_handler_plus_timeout(fd, COMM_SELECT_TIMEOUT, (PF) gopherReadReplyTimeout,
706 (caddr_t) data, getReadTimeout());
707
708 } else if (entry->flag & CLIENT_ABORT_REQUEST) {
709 /* append the last bit of info we got */
710 if (data->conversion != NORMAL) {
711 gopherToHTML(data, buf, len);
712 } else {
713 storeAppend(entry, buf, len);
714 }
b367f261 715 cached_error_entry(entry, ERR_CLIENT_ABORT, NULL);
090089c4 716 if (data->conversion != NORMAL)
717 gopherEndHTML(data);
718 BIT_RESET(entry->flag, DELAY_SENDING);
090089c4 719 comm_close(fd);
090089c4 720 freeGopherData(data);
090089c4 721 } else {
722 if (data->conversion != NORMAL) {
723 gopherToHTML(data, buf, len);
724 } else {
725 storeAppend(entry, buf, len);
726 }
727 comm_set_select_handler(fd, COMM_SELECT_READ, (PF) gopherReadReply, (caddr_t) data);
728 comm_set_select_handler_plus_timeout(fd, COMM_SELECT_TIMEOUT, (PF) gopherReadReplyTimeout,
729 (caddr_t) data, getReadTimeout());
730 }
731 put_free_4k_page(buf);
732 return 0;
733}
734
735/* This will be called when request write is complete. Schedule read of
736 * reply. */
44a47c6e 737void gopherSendComplete(fd, buf, size, errflag, data)
090089c4 738 int fd;
739 char *buf;
740 int size;
741 int errflag;
742 GopherData *data;
743{
744 StoreEntry *entry = NULL;
745 entry = data->entry;
6fe6313d 746 debug(0, 5, "gopherSendComplete: FD %d size: %d errflag: %d\n",
090089c4 747 fd, size, errflag);
748 if (errflag) {
b367f261 749 cached_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
090089c4 750 comm_close(fd);
090089c4 751 freeGopherData(data);
752 if (buf)
753 put_free_4k_page(buf); /* Allocated by gopherSendRequest. */
44a47c6e 754 return;
090089c4 755 }
756 /*
757 * OK. We successfully reach remote site. Start MIME typing
758 * stuff. Do it anyway even though request is not HTML type.
759 */
760 gopherMimeCreate(data);
761
762 if (!BIT_TEST(entry->flag, REQ_HTML))
763 data->conversion = NORMAL;
764 else
765 switch (data->type_id) {
766
767 case GOPHER_DIRECTORY:
768 /* we got to convert it first */
769 BIT_SET(entry->flag, DELAY_SENDING);
770 data->conversion = HTML_DIR;
771 data->HTML_header_added = 0;
772 break;
773
774 case GOPHER_INDEX:
775 /* we got to convert it first */
776 BIT_SET(entry->flag, DELAY_SENDING);
777 data->conversion = HTML_INDEX_RESULT;
778 data->HTML_header_added = 0;
779 break;
780
781 case GOPHER_CSO:
782 /* we got to convert it first */
783 BIT_SET(entry->flag, DELAY_SENDING);
784 data->conversion = HTML_CSO_RESULT;
785 data->cso_recno = 0;
786 data->HTML_header_added = 0;
787 break;
788
789 default:
790 data->conversion = NORMAL;
791
792 }
793 /* Schedule read reply. */
794 comm_set_select_handler(fd,
795 COMM_SELECT_READ,
796 (PF) gopherReadReply,
797 (caddr_t) data);
798 comm_set_select_handler_plus_timeout(fd,
799 COMM_SELECT_TIMEOUT,
800 (PF) gopherReadReplyTimeout,
801 (caddr_t) data,
802 getReadTimeout());
803 comm_set_fd_lifetime(fd, -1); /* disable */
804
805 if (buf)
806 put_free_4k_page(buf); /* Allocated by gopherSendRequest. */
807 data->icp_page_ptr = NULL;
808 data->icp_rwd_ptr = NULL;
090089c4 809}
810
811/* This will be called when connect completes. Write request. */
812int gopherSendRequest(fd, data)
813 int fd;
814 GopherData *data;
815{
816#define CR '\015'
817#define LF '\012'
818 int len;
819 static char query[MAX_URL];
820 char *buf = get_free_4k_page();
821
822 data->icp_page_ptr = buf;
823
824 if (data->type_id == GOPHER_CSO) {
825 sscanf(data->request, "?%s", query);
826 len = strlen(query) + 15;
827 sprintf(buf, "query %s%c%cquit%c%c", query, CR, LF, CR, LF);
828 } else if (data->type_id == GOPHER_INDEX) {
829 char *c_ptr = strchr(data->request, '?');
830 if (c_ptr) {
831 *c_ptr = '\t';
832 }
833 len = strlen(data->request) + 3;
834 sprintf(buf, "%s%c%c", data->request, CR, LF);
835 } else {
836 len = strlen(data->request) + 3;
837 sprintf(buf, "%s%c%c", data->request, CR, LF);
838 }
839
6fe6313d 840 debug(0, 5, "gopherSendRequest: FD %d\n", fd);
44a47c6e 841 data->icp_rwd_ptr = icpWrite(fd,
842 buf,
843 len,
844 30,
845 gopherSendComplete,
846 (caddr_t) data);
090089c4 847 return 0;
848}
849
850int gopherStart(unusedfd, url, entry)
851 int unusedfd;
852 char *url;
853 StoreEntry *entry;
854{
855 /* Create state structure. */
856 int sock, status;
857 GopherData *data = CreateGopherData();
858
859 data->entry = entry;
860
6fe6313d 861 debug(0, 3, "gopherStart: url: %s\n", url);
090089c4 862
863 /* Parse url. */
864 if (gopher_url_parser(url, data->host, &data->port,
865 &data->type_id, data->request)) {
b367f261 866 cached_error_entry(entry, ERR_INVALID_URL, NULL);
090089c4 867 freeGopherData(data);
868 return COMM_ERROR;
869 }
870 /* Create socket. */
871 sock = comm_open(COMM_NONBLOCKING, 0, 0, url);
090089c4 872 if (sock == COMM_ERROR) {
12b9e9b1 873 debug(0, 4, "gopherStart: Failed because we're out of sockets.\n");
b367f261 874 cached_error_entry(entry, ERR_NO_FDS, xstrerror());
090089c4 875 freeGopherData(data);
876 return COMM_ERROR;
877 }
878 /* check if IP is already in cache. It must be.
879 * It should be done before this route is called.
880 * Otherwise, we cannot check return code for connect. */
881 if (!ipcache_gethostbyname(data->host)) {
12b9e9b1 882 debug(0, 4, "gopherStart: Called without IP entry in ipcache. OR lookup failed.\n");
090089c4 883 comm_close(sock);
b367f261 884 cached_error_entry(entry, ERR_DNS_FAIL, dns_error_message);
090089c4 885 freeGopherData(data);
886 return COMM_ERROR;
887 }
888 if (((data->type_id == GOPHER_INDEX) || (data->type_id == GOPHER_CSO))
889 && (strchr(data->request, '?') == NULL)
890 && (BIT_TEST(entry->flag, REQ_HTML))) {
891 /* Index URL without query word */
892 /* We have to generate search page back to client. No need for connection */
893 gopherMimeCreate(data);
894
895 if (data->type_id == GOPHER_INDEX) {
896 data->conversion = HTML_INDEX_PAGE;
897 } else {
898 if (data->type_id == GOPHER_CSO) {
899 data->conversion = HTML_CSO_PAGE;
900 } else {
901 data->conversion = HTML_INDEX_PAGE;
902 }
903 }
904 gopherToHTML(data, (char *) NULL, 0);
905 storeComplete(entry);
906 freeGopherData(data);
907 comm_close(sock);
908 return COMM_OK;
909 }
910 /* Open connection. */
911 if ((status = comm_connect(sock, data->host, data->port)) != 0) {
912 if (status != EINPROGRESS) {
913 comm_close(sock);
b367f261 914 cached_error_entry(entry, ERR_CONNECT_FAIL, xstrerror());
090089c4 915 freeGopherData(data);
916 return COMM_ERROR;
917 } else {
6fe6313d 918 debug(0, 5, "startGopher: conn %d EINPROGRESS\n", sock);
090089c4 919 }
920 }
921 /* Install connection complete handler. */
d1cfbef7 922 comm_set_select_handler(sock,
923 COMM_SELECT_LIFETIME,
924 (PF) gopherLifetimeExpire,
925 (caddr_t) data);
926 comm_set_select_handler(sock,
927 COMM_SELECT_WRITE,
928 (PF) gopherSendRequest,
929 (caddr_t) data);
090089c4 930
931 return COMM_OK;
932}
933
934
935GopherData *CreateGopherData()
936{
937 GopherData *gd = (GopherData *) xcalloc(1, sizeof(GopherData));
938 gd->buf = get_free_4k_page();
939 return (gd);
940}
941
942static void freeGopherData(gd)
943 GopherData *gd;
944{
945 put_free_4k_page(gd->buf);
946 safe_free(gd);
947}