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