]> git.ipfire.org Git - thirdparty/squid.git/blob - src/gopher.cc
Fixed up *Timeout() functions. We might get fooTimeout() called
[thirdparty/squid.git] / src / gopher.cc
1
2
3 /*
4 * $Id: gopher.cc,v 1.148 1999/01/20 19:27:11 wessels Exp $
5 *
6 * DEBUG: section 10 Gopher
7 * AUTHOR: Harvest Derived
8 *
9 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
10 * ----------------------------------------------------------
11 *
12 * Squid is the result of efforts by numerous individuals from the
13 * Internet community. Development is led by Duane Wessels of the
14 * National Laboratory for Applied Network Research and funded by the
15 * National Science Foundation. Squid is Copyrighted (C) 1998 by
16 * Duane Wessels and the University of California San Diego. Please
17 * see the COPYRIGHT file for full details. Squid incorporates
18 * software developed and/or copyrighted by other sources. Please see
19 * the CREDITS file for full details.
20 *
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 *
35 */
36
37 #include "squid.h"
38
39 /* gopher type code from rfc. Anawat. */
40 #define GOPHER_FILE '0'
41 #define GOPHER_DIRECTORY '1'
42 #define GOPHER_CSO '2'
43 #define GOPHER_ERROR '3'
44 #define GOPHER_MACBINHEX '4'
45 #define GOPHER_DOSBIN '5'
46 #define GOPHER_UUENCODED '6'
47 #define GOPHER_INDEX '7'
48 #define GOPHER_TELNET '8'
49 #define GOPHER_BIN '9'
50 #define GOPHER_REDUNT '+'
51 #define GOPHER_3270 'T'
52 #define GOPHER_GIF 'g'
53 #define GOPHER_IMAGE 'I'
54
55 #define GOPHER_HTML 'h' /* HTML */
56 #define GOPHER_INFO 'i'
57 #define GOPHER_WWW 'w' /* W3 address */
58 #define GOPHER_SOUND 's'
59
60 #define GOPHER_PLUS_IMAGE ':'
61 #define GOPHER_PLUS_MOVIE ';'
62 #define GOPHER_PLUS_SOUND '<'
63
64 #define GOPHER_PORT 70
65
66 #define TAB '\t'
67 #define TEMP_BUF_SIZE 4096
68 #define MAX_CSO_RESULT 1024
69
70 typedef struct gopher_ds {
71 StoreEntry *entry;
72 char host[SQUIDHOSTNAMELEN + 1];
73 enum {
74 NORMAL,
75 HTML_DIR,
76 HTML_INDEX_RESULT,
77 HTML_CSO_RESULT,
78 HTML_INDEX_PAGE,
79 HTML_CSO_PAGE
80 } conversion;
81 int HTML_header_added;
82 int port;
83 char type_id;
84 char request[MAX_URL];
85 int data_in;
86 int cso_recno;
87 int len;
88 char *buf; /* pts to a 4k page */
89 int fd;
90 FwdState *fwdState;
91 } GopherStateData;
92
93 static PF gopherStateFree;
94 static void gopher_mime_content(MemBuf * mb, const char *name, const char *def);
95 static void gopherMimeCreate(GopherStateData *);
96 static int gopher_url_parser(const char *url,
97 char *host,
98 int *port,
99 char *type_id,
100 char *request);
101 static void gopherEndHTML(GopherStateData *);
102 static void gopherToHTML(GopherStateData *, char *inbuf, int len);
103 static PF gopherTimeout;
104 static PF gopherReadReply;
105 static CWCB gopherSendComplete;
106 static PF gopherSendRequest;
107 static GopherStateData *CreateGopherStateData(void);
108
109 static char def_gopher_bin[] = "www/unknown";
110 static char def_gopher_text[] = "text/plain";
111
112 static void
113 gopherStateFree(int fdnotused, void *data)
114 {
115 GopherStateData *gopherState = data;
116 if (gopherState == NULL)
117 return;
118 if (gopherState->entry) {
119 storeUnlockObject(gopherState->entry);
120 }
121 memFree(gopherState->buf, MEM_4K_BUF);
122 gopherState->buf = NULL;
123 cbdataFree(gopherState);
124 }
125
126
127 /* figure out content type from file extension */
128 static void
129 gopher_mime_content(MemBuf * mb, const char *name, const char *def_ctype)
130 {
131 char *ctype = mimeGetContentType(name);
132 char *cenc = mimeGetContentEncoding(name);
133 if (cenc)
134 memBufPrintf(mb, "Content-Encoding: %s\r\n", cenc);
135 memBufPrintf(mb, "Content-Type: %s\r\n",
136 ctype ? ctype : def_ctype);
137 }
138
139
140
141 /* create MIME Header for Gopher Data */
142 static void
143 gopherMimeCreate(GopherStateData * gopherState)
144 {
145 MemBuf mb;
146
147 memBufDefInit(&mb);
148
149 memBufPrintf(&mb,
150 "HTTP/1.0 200 OK Gatewaying\r\n"
151 "Server: Squid/%s\r\n"
152 "Date: %s\r\n"
153 "MIME-version: 1.0\r\n",
154 version_string, mkrfc1123(squid_curtime));
155
156 switch (gopherState->type_id) {
157
158 case GOPHER_DIRECTORY:
159 case GOPHER_INDEX:
160 case GOPHER_HTML:
161 case GOPHER_WWW:
162 case GOPHER_CSO:
163 memBufPrintf(&mb, "Content-Type: text/html\r\n");
164 break;
165 case GOPHER_GIF:
166 case GOPHER_IMAGE:
167 case GOPHER_PLUS_IMAGE:
168 memBufPrintf(&mb, "Content-Type: image/gif\r\n");
169 break;
170 case GOPHER_SOUND:
171 case GOPHER_PLUS_SOUND:
172 memBufPrintf(&mb, "Content-Type: audio/basic\r\n");
173 break;
174 case GOPHER_PLUS_MOVIE:
175 memBufPrintf(&mb, "Content-Type: video/mpeg\r\n");
176 break;
177 case GOPHER_MACBINHEX:
178 case GOPHER_DOSBIN:
179 case GOPHER_UUENCODED:
180 case GOPHER_BIN:
181 /* Rightnow We have no idea what it is. */
182 gopher_mime_content(&mb, gopherState->request, def_gopher_bin);
183 break;
184 case GOPHER_FILE:
185 default:
186 gopher_mime_content(&mb, gopherState->request, def_gopher_text);
187 break;
188 }
189 memBufPrintf(&mb, "\r\n");
190 EBIT_CLR(gopherState->entry->flags, ENTRY_FWD_HDR_WAIT);
191 storeAppend(gopherState->entry, mb.buf, mb.size);
192 memBufClean(&mb);
193 }
194
195 /* Parse a gopher url into components. By Anawat. */
196 static int
197 gopher_url_parser(const char *url, char *host, int *port, char *type_id, char *request)
198 {
199 LOCAL_ARRAY(char, proto, MAX_URL);
200 LOCAL_ARRAY(char, hostbuf, MAX_URL);
201 int t;
202
203 proto[0] = hostbuf[0] = '\0';
204 host[0] = request[0] = '\0';
205 (*port) = 0;
206 (*type_id) = 0;
207
208 t = sscanf(url,
209 #if defined(__QNX__)
210 "%[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]://%[^/]/%c%s",
211 #else
212 "%[a-zA-Z]://%[^/]/%c%s",
213 #endif
214 proto, hostbuf, type_id, request);
215 if ((t < 2) || strcasecmp(proto, "gopher")) {
216 return -1;
217 } else if (t == 2) {
218 (*type_id) = GOPHER_DIRECTORY;
219 request[0] = '\0';
220 } else if (t == 3) {
221 request[0] = '\0';
222 } else {
223 /* convert %xx to char */
224 url_convert_hex(request, 0);
225 }
226
227 host[0] = '\0';
228 if (sscanf(hostbuf, "%[^:]:%d", host, port) < 2)
229 (*port) = GOPHER_PORT;
230
231 return 0;
232 }
233
234 int
235 gopherCachable(const char *url)
236 {
237 GopherStateData *gopherState = NULL;
238 int cachable = 1;
239 /* use as temp data structure to parse gopher URL */
240 gopherState = CreateGopherStateData();
241 /* parse to see type */
242 gopher_url_parser(url,
243 gopherState->host,
244 &gopherState->port,
245 &gopherState->type_id,
246 gopherState->request);
247 switch (gopherState->type_id) {
248 case GOPHER_INDEX:
249 case GOPHER_CSO:
250 case GOPHER_TELNET:
251 case GOPHER_3270:
252 cachable = 0;
253 break;
254 default:
255 cachable = 1;
256 }
257 gopherStateFree(-1, gopherState);
258 return cachable;
259 }
260
261 static void
262 gopherEndHTML(GopherStateData * gopherState)
263 {
264 if (!gopherState->data_in)
265 storeAppendPrintf(gopherState->entry,
266 "<HTML><HEAD><TITLE>Server Return Nothing.</TITLE>\n"
267 "</HEAD><BODY><HR><H1>Server Return Nothing.</H1></BODY></HTML>\n");
268 }
269
270
271 /* Convert Gopher to HTML */
272 /* Borrow part of code from libwww2 came with Mosaic distribution */
273 static void
274 gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
275 {
276 char *pos = inbuf;
277 char *lpos = NULL;
278 char *tline = NULL;
279 LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
280 LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
281 LOCAL_ARRAY(char, outbuf, TEMP_BUF_SIZE << 4);
282 char *name = NULL;
283 char *selector = NULL;
284 char *host = NULL;
285 char *port = NULL;
286 char *escaped_selector = NULL;
287 char *icon_url = NULL;
288 char gtype;
289 StoreEntry *entry = NULL;
290
291 memset(outbuf, '\0', TEMP_BUF_SIZE << 4);
292 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
293 memset(line, '\0', TEMP_BUF_SIZE);
294
295 entry = gopherState->entry;
296
297 if (gopherState->conversion == HTML_INDEX_PAGE) {
298 storeAppendPrintf(entry,
299 "<HTML><HEAD><TITLE>Gopher Index %s</TITLE></HEAD>\n"
300 "<BODY><H1>%s<BR>Gopher Search</H1>\n"
301 "<p>This is a searchable Gopher index. Use the search\n"
302 "function of your browser to enter search terms.\n"
303 "<ISINDEX></BODY></HTML>\n",
304 storeUrl(entry), storeUrl(entry));
305 /* now let start sending stuff to client */
306 storeBufferFlush(entry);
307 gopherState->data_in = 1;
308
309 return;
310 }
311 if (gopherState->conversion == HTML_CSO_PAGE) {
312 storeAppendPrintf(entry,
313 "<HTML><HEAD><TITLE>CSO Search of %s</TITLE></HEAD>\n"
314 "<BODY><H1>%s<BR>CSO Search</H1>\n"
315 "<P>A CSO database usually contains a phonebook or\n"
316 "directory. Use the search function of your browser to enter\n"
317 "search terms.</P><ISINDEX></BODY></HTML>\n",
318 storeUrl(entry), storeUrl(entry));
319 /* now let start sending stuff to client */
320 storeBufferFlush(entry);
321 gopherState->data_in = 1;
322
323 return;
324 }
325 inbuf[len] = '\0';
326
327 if (!gopherState->HTML_header_added) {
328 if (gopherState->conversion == HTML_CSO_RESULT)
329 strcat(outbuf, "<HTML><HEAD><TITLE>CSO Searchs Result</TITLE></HEAD>\n"
330 "<BODY><H1>CSO Searchs Result</H1>\n<PRE>\n");
331 else
332 strcat(outbuf, "<HTML><HEAD><TITLE>Gopher Menu</TITLE></HEAD>\n"
333 "<BODY><H1>Gopher Menu</H1>\n<PRE>\n");
334 gopherState->HTML_header_added = 1;
335 }
336 while ((pos != NULL) && (pos < inbuf + len)) {
337
338 if (gopherState->len != 0) {
339 /* there is something left from last tx. */
340 xstrncpy(line, gopherState->buf, gopherState->len);
341 lpos = (char *) memccpy(line + gopherState->len, inbuf, '\n', len);
342 if (lpos)
343 *lpos = '\0';
344 else {
345 /* there is no complete line in inbuf */
346 /* copy it to temp buffer */
347 if (gopherState->len + len > TEMP_BUF_SIZE) {
348 debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
349 storeUrl(entry));
350 len = TEMP_BUF_SIZE - gopherState->len;
351 }
352 xmemcpy(gopherState->buf + gopherState->len, inbuf, len);
353 gopherState->len += len;
354 return;
355 }
356
357 /* skip one line */
358 pos = (char *) memchr(pos, '\n', len);
359 if (pos)
360 pos++;
361
362 /* we're done with the remain from last tx. */
363 gopherState->len = 0;
364 *(gopherState->buf) = '\0';
365 } else {
366
367 lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf));
368 if (lpos)
369 *lpos = '\0';
370 else {
371 /* there is no complete line in inbuf */
372 /* copy it to temp buffer */
373 if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) {
374 debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n",
375 storeUrl(entry));
376 len = TEMP_BUF_SIZE;
377 }
378 if (len > (pos - inbuf)) {
379 xmemcpy(gopherState->buf, pos, len - (pos - inbuf));
380 gopherState->len = len - (pos - inbuf);
381 }
382 break;
383 }
384
385 /* skip one line */
386 pos = (char *) memchr(pos, '\n', len);
387 if (pos)
388 pos++;
389
390 }
391
392 /* at this point. We should have one line in buffer to process */
393
394 if (*line == '.') {
395 /* skip it */
396 memset(line, '\0', TEMP_BUF_SIZE);
397 continue;
398 }
399 switch (gopherState->conversion) {
400
401 case HTML_INDEX_RESULT:
402 case HTML_DIR:{
403 tline = line;
404 gtype = *tline++;
405 name = tline;
406 selector = strchr(tline, TAB);
407 if (selector) {
408 *selector++ = '\0';
409 host = strchr(selector, TAB);
410 if (host) {
411 *host++ = '\0';
412 port = strchr(host, TAB);
413 if (port) {
414 char *junk;
415 port[0] = ':';
416 junk = strchr(host, TAB);
417 if (junk)
418 *junk++ = 0; /* Chop port */
419 else {
420 junk = strchr(host, '\r');
421 if (junk)
422 *junk++ = 0; /* Chop port */
423 else {
424 junk = strchr(host, '\n');
425 if (junk)
426 *junk++ = 0; /* Chop port */
427 }
428 }
429 if ((port[1] == '0') && (!port[2]))
430 port[0] = 0; /* 0 means none */
431 }
432 /* escape a selector here */
433 escaped_selector = xstrdup(rfc1738_escape(selector));
434
435 switch (gtype) {
436 case GOPHER_DIRECTORY:
437 icon_url = mimeGetIconURL("internal-menu");
438 break;
439 case GOPHER_FILE:
440 icon_url = mimeGetIconURL("internal-text");
441 break;
442 case GOPHER_INDEX:
443 case GOPHER_CSO:
444 icon_url = mimeGetIconURL("internal-index");
445 break;
446 case GOPHER_IMAGE:
447 case GOPHER_GIF:
448 case GOPHER_PLUS_IMAGE:
449 icon_url = mimeGetIconURL("internal-image");
450 break;
451 case GOPHER_SOUND:
452 case GOPHER_PLUS_SOUND:
453 icon_url = mimeGetIconURL("internal-sound");
454 break;
455 case GOPHER_PLUS_MOVIE:
456 icon_url = mimeGetIconURL("internal-movie");
457 break;
458 case GOPHER_TELNET:
459 case GOPHER_3270:
460 icon_url = mimeGetIconURL("internal-telnet");
461 break;
462 case GOPHER_BIN:
463 case GOPHER_MACBINHEX:
464 case GOPHER_DOSBIN:
465 case GOPHER_UUENCODED:
466 icon_url = mimeGetIconURL("internal-binary");
467 break;
468 default:
469 icon_url = mimeGetIconURL("internal-unknown");
470 break;
471 }
472
473
474 memset(tmpbuf, '\0', TEMP_BUF_SIZE);
475 if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
476 if (strlen(escaped_selector) != 0)
477 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s@%s/\">%s</A>\n",
478 icon_url, escaped_selector, host, name);
479 else
480 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s/\">%s</A>\n",
481 icon_url, host, name);
482
483 } else {
484 snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
485 icon_url, host, gtype, escaped_selector, name);
486 }
487 safe_free(escaped_selector);
488 strcat(outbuf, tmpbuf);
489 gopherState->data_in = 1;
490 } else {
491 memset(line, '\0', TEMP_BUF_SIZE);
492 continue;
493 }
494 } else {
495 memset(line, '\0', TEMP_BUF_SIZE);
496 continue;
497 }
498 break;
499 } /* HTML_DIR, HTML_INDEX_RESULT */
500
501
502 case HTML_CSO_RESULT:{
503 int t;
504 int code;
505 int recno;
506 LOCAL_ARRAY(char, result, MAX_CSO_RESULT);
507
508 tline = line;
509
510 if (tline[0] == '-') {
511 t = sscanf(tline, "-%d:%d:%[^\n]", &code, &recno, result);
512 if (t < 3)
513 break;
514
515 if (code != 200)
516 break;
517
518 if (gopherState->cso_recno != recno) {
519 snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, result);
520 gopherState->cso_recno = recno;
521 } else {
522 snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", result);
523 }
524 strcat(outbuf, tmpbuf);
525 gopherState->data_in = 1;
526 break;
527 } else {
528 /* handle some error codes */
529 t = sscanf(tline, "%d:%[^\n]", &code, result);
530
531 if (t < 2)
532 break;
533
534 switch (code) {
535
536 case 200:{
537 /* OK */
538 /* Do nothing here */
539 break;
540 }
541
542 case 102: /* Number of matches */
543 case 501: /* No Match */
544 case 502: /* Too Many Matches */
545 {
546 /* Print the message the server returns */
547 snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR><H2>%s</H2>\n<PRE>", result);
548 strcat(outbuf, tmpbuf);
549 gopherState->data_in = 1;
550 break;
551 }
552
553
554 }
555 }
556
557 } /* HTML_CSO_RESULT */
558 default:
559 break; /* do nothing */
560
561 } /* switch */
562
563 } /* while loop */
564
565 if ((int) strlen(outbuf) > 0) {
566 storeAppend(entry, outbuf, strlen(outbuf));
567 /* now let start sending stuff to client */
568 storeBufferFlush(entry);
569 }
570 return;
571 }
572
573 static void
574 gopherTimeout(int fd, void *data)
575 {
576 GopherStateData *gopherState = data;
577 StoreEntry *entry = gopherState->entry;
578 debug(10, 4) ("gopherTimeout: FD %d: '%s'\n", fd, storeUrl(entry));
579 if (entry->store_status == STORE_PENDING) {
580 if (entry->mem_obj->inmem_hi == 0) {
581 fwdFail(gopherState->fwdState,
582 errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT));
583 }
584 }
585 comm_close(fd);
586 }
587
588 /* This will be called when data is ready to be read from fd. Read until
589 * error or connection closed. */
590 static void
591 gopherReadReply(int fd, void *data)
592 {
593 GopherStateData *gopherState = data;
594 StoreEntry *entry = gopherState->entry;
595 char *buf = NULL;
596 int len;
597 int clen;
598 int bin;
599 size_t read_sz;
600 #if DELAY_POOLS
601 delay_id delay_id = delayMostBytesAllowed(entry->mem_obj);
602 #endif
603 if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
604 comm_close(fd);
605 return;
606 }
607 errno = 0;
608 buf = memAllocate(MEM_4K_BUF);
609 read_sz = 4096 - 1; /* leave room for termination */
610 #if DELAY_POOLS
611 read_sz = delayBytesWanted(delay_id, 1, read_sz);
612 #endif
613 /* leave one space for \0 in gopherToHTML */
614 Counter.syscalls.sock.reads++;
615 len = read(fd, buf, read_sz);
616 if (len > 0) {
617 fd_bytes(fd, len, FD_READ);
618 #if DELAY_POOLS
619 delayBytesIn(delay_id, len);
620 #endif
621 kb_incr(&Counter.server.all.kbytes_in, len);
622 kb_incr(&Counter.server.other.kbytes_in, len);
623 }
624 debug(10, 5) ("gopherReadReply: FD %d read len=%d\n", fd, len);
625 if (len > 0) {
626 commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
627 IOStats.Gopher.reads++;
628 for (clen = len - 1, bin = 0; clen; bin++)
629 clen >>= 1;
630 IOStats.Gopher.read_hist[bin]++;
631 }
632 if (len < 0) {
633 debug(50, 1) ("gopherReadReply: error reading: %s\n", xstrerror());
634 if (ignoreErrno(errno)) {
635 commSetSelect(fd, COMM_SELECT_READ, gopherReadReply, data, 0);
636 } else if (entry->mem_obj->inmem_hi == 0) {
637 ErrorState *err;
638 err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR);
639 err->xerrno = errno;
640 err->url = xstrdup(storeUrl(entry));
641 errorAppendEntry(entry, err);
642 comm_close(fd);
643 } else {
644 comm_close(fd);
645 }
646 } else if (len == 0 && entry->mem_obj->inmem_hi == 0) {
647 ErrorState *err;
648 err = errorCon(ERR_ZERO_SIZE_OBJECT, HTTP_SERVICE_UNAVAILABLE);
649 err->xerrno = errno;
650 err->url = xstrdup(gopherState->request);
651 errorAppendEntry(entry, err);
652 comm_close(fd);
653 } else if (len == 0) {
654 /* Connection closed; retrieval done. */
655 /* flush the rest of data in temp buf if there is one. */
656 if (gopherState->conversion != NORMAL)
657 gopherEndHTML(data);
658 storeTimestampsSet(entry);
659 storeBufferFlush(entry);
660 fwdComplete(gopherState->fwdState);
661 comm_close(fd);
662 } else {
663 if (gopherState->conversion != NORMAL) {
664 gopherToHTML(data, buf, len);
665 } else {
666 storeAppend(entry, buf, len);
667 }
668 commSetSelect(fd,
669 COMM_SELECT_READ,
670 gopherReadReply,
671 data, 0);
672 }
673 memFree(buf, MEM_4K_BUF);
674 return;
675 }
676
677 /* This will be called when request write is complete. Schedule read of
678 * reply. */
679 static void
680 gopherSendComplete(int fd, char *buf, size_t size, int errflag, void *data)
681 {
682 GopherStateData *gopherState = (GopherStateData *) data;
683 StoreEntry *entry = gopherState->entry;
684 debug(10, 5) ("gopherSendComplete: FD %d size: %d errflag: %d\n",
685 fd, size, errflag);
686 if (size > 0) {
687 fd_bytes(fd, size, FD_WRITE);
688 kb_incr(&Counter.server.all.kbytes_out, size);
689 kb_incr(&Counter.server.other.kbytes_out, size);
690 }
691 if (errflag) {
692 ErrorState *err;
693 err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
694 err->xerrno = errno;
695 err->host = xstrdup(gopherState->host);
696 err->port = gopherState->port;
697 err->url = xstrdup(storeUrl(entry));
698 errorAppendEntry(entry, err);
699 comm_close(fd);
700 if (buf)
701 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
702 return;
703 }
704 /*
705 * OK. We successfully reach remote site. Start MIME typing
706 * stuff. Do it anyway even though request is not HTML type.
707 */
708 gopherMimeCreate(gopherState);
709 switch (gopherState->type_id) {
710 case GOPHER_DIRECTORY:
711 /* we got to convert it first */
712 storeBuffer(entry);
713 gopherState->conversion = HTML_DIR;
714 gopherState->HTML_header_added = 0;
715 break;
716 case GOPHER_INDEX:
717 /* we got to convert it first */
718 storeBuffer(entry);
719 gopherState->conversion = HTML_INDEX_RESULT;
720 gopherState->HTML_header_added = 0;
721 break;
722 case GOPHER_CSO:
723 /* we got to convert it first */
724 storeBuffer(entry);
725 gopherState->conversion = HTML_CSO_RESULT;
726 gopherState->cso_recno = 0;
727 gopherState->HTML_header_added = 0;
728 break;
729 default:
730 gopherState->conversion = NORMAL;
731 }
732 /* Schedule read reply. */
733 commSetSelect(fd, COMM_SELECT_READ, gopherReadReply, gopherState, 0);
734 commSetDefer(fd, fwdCheckDeferRead, entry);
735 if (buf)
736 memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */
737 }
738
739 /* This will be called when connect completes. Write request. */
740 static void
741 gopherSendRequest(int fd, void *data)
742 {
743 GopherStateData *gopherState = data;
744 LOCAL_ARRAY(char, query, MAX_URL);
745 char *buf = memAllocate(MEM_4K_BUF);
746 char *t;
747 if (gopherState->type_id == GOPHER_CSO) {
748 sscanf(gopherState->request, "?%s", query);
749 snprintf(buf, 4096, "query %s\r\nquit\r\n", query);
750 } else if (gopherState->type_id == GOPHER_INDEX) {
751 if ((t = strchr(gopherState->request, '?')))
752 *t = '\t';
753 snprintf(buf, 4096, "%s\r\n", gopherState->request);
754 } else {
755 snprintf(buf, 4096, "%s\r\n", gopherState->request);
756 }
757 debug(10, 5) ("gopherSendRequest: FD %d\n", fd);
758 comm_write(fd,
759 buf,
760 strlen(buf),
761 gopherSendComplete,
762 data,
763 memFree4K);
764 if (EBIT_TEST(gopherState->entry->flags, ENTRY_CACHABLE))
765 storeSetPublicKey(gopherState->entry); /* Make it public */
766 }
767
768 void
769 gopherStart(FwdState * fwdState)
770 {
771 int fd = fwdState->server_fd;
772 StoreEntry *entry = fwdState->entry;
773 GopherStateData *gopherState = CreateGopherStateData();
774 storeLockObject(entry);
775 gopherState->entry = entry;
776 debug(10, 3) ("gopherStart: %s\n", storeUrl(entry));
777 Counter.server.all.requests++;
778 Counter.server.other.requests++;
779 /* Parse url. */
780 if (gopher_url_parser(storeUrl(entry), gopherState->host, &gopherState->port,
781 &gopherState->type_id, gopherState->request)) {
782 ErrorState *err;
783 err = errorCon(ERR_INVALID_URL, HTTP_BAD_REQUEST);
784 err->url = xstrdup(storeUrl(entry));
785 errorAppendEntry(entry, err);
786 gopherStateFree(-1, gopherState);
787 return;
788 }
789 comm_add_close_handler(fd, gopherStateFree, gopherState);
790 if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
791 && (strchr(gopherState->request, '?') == NULL)) {
792 /* Index URL without query word */
793 /* We have to generate search page back to client. No need for connection */
794 gopherMimeCreate(gopherState);
795 if (gopherState->type_id == GOPHER_INDEX) {
796 gopherState->conversion = HTML_INDEX_PAGE;
797 } else {
798 if (gopherState->type_id == GOPHER_CSO) {
799 gopherState->conversion = HTML_CSO_PAGE;
800 } else {
801 gopherState->conversion = HTML_INDEX_PAGE;
802 }
803 }
804 gopherToHTML(gopherState, (char *) NULL, 0);
805 fwdComplete(gopherState->fwdState);
806 comm_close(fd);
807 return;
808 }
809 gopherState->fd = fd;
810 gopherState->fwdState = fwdState;
811 commSetSelect(fd, COMM_SELECT_WRITE, gopherSendRequest, gopherState, 0);
812 commSetTimeout(fd, Config.Timeout.read, gopherTimeout, gopherState);
813 }
814
815 static GopherStateData *
816 CreateGopherStateData(void)
817 {
818 GopherStateData *gd = xcalloc(1, sizeof(GopherStateData));
819 cbdataAdd(gd, cbdataXfree, 0);
820 gd->buf = memAllocate(MEM_4K_BUF);
821 return (gd);
822 }