]>
Commit | Line | Data |
---|---|---|
6fe6313d | 1 | /* $Id: gopher.cc,v 1.10 1996/03/28 20:42:47 wessels Exp $ */ |
44a47c6e | 2 | |
3 | #include "squid.h" | |
090089c4 | 4 | |
5 | extern 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 | ||
39 | typedef 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 | ||
63 | GopherData *CreateGopherData(); | |
64 | ||
65 | static void freeGopherData _PARAMS((GopherData *)); | |
66 | ||
67 | char def_gopher_bin[] = "www/unknown"; | |
68 | char def_gopher_text[] = "text/plain"; | |
69 | ||
70 | /* figure out content type from file extension */ | |
71 | static 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 */ | |
123 | void gopherMimeCreate(data) | |
124 | GopherData *data; | |
125 | { | |
126 | static char tempMIME[MAX_MIME]; | |
127 | ||
128 | sprintf(tempMIME, "\ | |
129 | HTTP/1.0 200 OK Gatewaying\r\n\ | |
130 | Server: HarvestCache/%s\r\n\ | |
73a5465f | 131 | MIME-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. */ | |
174 | int 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 | ||
214 | int 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 | ||
249 | void 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 */ | |
264 | void 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 | ||
558 | int 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. */ | |
576 | void 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. */ | |
601 | int 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 | 737 | void 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. */ | |
812 | int 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 | ||
850 | int 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 | ||
935 | GopherData *CreateGopherData() | |
936 | { | |
937 | GopherData *gd = (GopherData *) xcalloc(1, sizeof(GopherData)); | |
938 | gd->buf = get_free_4k_page(); | |
939 | return (gd); | |
940 | } | |
941 | ||
942 | static void freeGopherData(gd) | |
943 | GopherData *gd; | |
944 | { | |
945 | put_free_4k_page(gd->buf); | |
946 | safe_free(gd); | |
947 | } |