]>
Commit | Line | Data |
---|---|---|
30a4f2a8 | 1 | /* |
9864ee44 | 2 | * $Id: gopher.cc,v 1.35 1996/07/20 03:16:50 wessels Exp $ |
30a4f2a8 | 3 | * |
4 | * DEBUG: section 10 Gopher | |
5 | * AUTHOR: Harvest Derived | |
6 | * | |
7 | * SQUID Internet Object Cache http://www.nlanr.net/Squid/ | |
8 | * -------------------------------------------------------- | |
9 | * | |
10 | * Squid is the result of efforts by numerous individuals from the | |
11 | * Internet community. Development is led by Duane Wessels of the | |
12 | * National Laboratory for Applied Network Research and funded by | |
13 | * the National Science Foundation. | |
14 | * | |
15 | * This program is free software; you can redistribute it and/or modify | |
16 | * it under the terms of the GNU General Public License as published by | |
17 | * the Free Software Foundation; either version 2 of the License, or | |
18 | * (at your option) any later version. | |
19 | * | |
20 | * This program is distributed in the hope that it will be useful, | |
21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
23 | * GNU General Public License for more details. | |
24 | * | |
25 | * You should have received a copy of the GNU General Public License | |
26 | * along with this program; if not, write to the Free Software | |
27 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
28 | * | |
29 | */ | |
019dd986 | 30 | |
31 | /* | |
30a4f2a8 | 32 | * Copyright (c) 1994, 1995. All rights reserved. |
33 | * | |
34 | * The Harvest software was developed by the Internet Research Task | |
35 | * Force Research Group on Resource Discovery (IRTF-RD): | |
36 | * | |
37 | * Mic Bowman of Transarc Corporation. | |
38 | * Peter Danzig of the University of Southern California. | |
39 | * Darren R. Hardy of the University of Colorado at Boulder. | |
40 | * Udi Manber of the University of Arizona. | |
41 | * Michael F. Schwartz of the University of Colorado at Boulder. | |
42 | * Duane Wessels of the University of Colorado at Boulder. | |
43 | * | |
44 | * This copyright notice applies to software in the Harvest | |
45 | * ``src/'' directory only. Users should consult the individual | |
46 | * copyright notices in the ``components/'' subdirectories for | |
47 | * copyright information about other software bundled with the | |
48 | * Harvest source code distribution. | |
49 | * | |
50 | * TERMS OF USE | |
51 | * | |
52 | * The Harvest software may be used and re-distributed without | |
53 | * charge, provided that the software origin and research team are | |
54 | * cited in any use of the system. Most commonly this is | |
55 | * accomplished by including a link to the Harvest Home Page | |
56 | * (http://harvest.cs.colorado.edu/) from the query page of any | |
57 | * Broker you deploy, as well as in the query result pages. These | |
58 | * links are generated automatically by the standard Broker | |
59 | * software distribution. | |
60 | * | |
61 | * The Harvest software is provided ``as is'', without express or | |
62 | * implied warranty, and with no support nor obligation to assist | |
63 | * in its use, correction, modification or enhancement. We assume | |
64 | * no liability with respect to the infringement of copyrights, | |
65 | * trade secrets, or any patents, and are not responsible for | |
66 | * consequential damages. Proper use of the Harvest software is | |
67 | * entirely the responsibility of the user. | |
68 | * | |
69 | * DERIVATIVE WORKS | |
70 | * | |
71 | * Users may make derivative works from the Harvest software, subject | |
72 | * to the following constraints: | |
73 | * | |
74 | * - You must include the above copyright notice and these | |
75 | * accompanying paragraphs in all forms of derivative works, | |
76 | * and any documentation and other materials related to such | |
77 | * distribution and use acknowledge that the software was | |
78 | * developed at the above institutions. | |
79 | * | |
80 | * - You must notify IRTF-RD regarding your distribution of | |
81 | * the derivative work. | |
82 | * | |
83 | * - You must clearly notify users that your are distributing | |
84 | * a modified version and not the original Harvest software. | |
85 | * | |
86 | * - Any derivative product is also subject to these copyright | |
87 | * and use restrictions. | |
88 | * | |
89 | * Note that the Harvest software is NOT in the public domain. We | |
90 | * retain copyright, as specified above. | |
91 | * | |
92 | * HISTORY OF FREE SOFTWARE STATUS | |
93 | * | |
94 | * Originally we required sites to license the software in cases | |
95 | * where they were going to build commercial products/services | |
96 | * around Harvest. In June 1995 we changed this policy. We now | |
97 | * allow people to use the core Harvest software (the code found in | |
98 | * the Harvest ``src/'' directory) for free. We made this change | |
99 | * in the interest of encouraging the widest possible deployment of | |
100 | * the technology. The Harvest software is really a reference | |
101 | * implementation of a set of protocols and formats, some of which | |
102 | * we intend to standardize. We encourage commercial | |
103 | * re-implementations of code complying to this set of standards. | |
019dd986 | 104 | */ |
44a47c6e | 105 | |
106 | #include "squid.h" | |
090089c4 | 107 | |
090089c4 | 108 | /* gopher type code from rfc. Anawat. */ |
109 | #define GOPHER_FILE '0' | |
110 | #define GOPHER_DIRECTORY '1' | |
111 | #define GOPHER_CSO '2' | |
112 | #define GOPHER_ERROR '3' | |
113 | #define GOPHER_MACBINHEX '4' | |
114 | #define GOPHER_DOSBIN '5' | |
115 | #define GOPHER_UUENCODED '6' | |
116 | #define GOPHER_INDEX '7' | |
117 | #define GOPHER_TELNET '8' | |
118 | #define GOPHER_BIN '9' | |
119 | #define GOPHER_REDUNT '+' | |
120 | #define GOPHER_3270 'T' | |
121 | #define GOPHER_GIF 'g' | |
122 | #define GOPHER_IMAGE 'I' | |
123 | ||
124 | #define GOPHER_HTML 'h' /* HTML */ | |
125 | #define GOPHER_INFO 'i' | |
126 | #define GOPHER_WWW 'w' /* W3 address */ | |
127 | #define GOPHER_SOUND 's' | |
128 | ||
129 | #define GOPHER_PLUS_IMAGE ':' | |
130 | #define GOPHER_PLUS_MOVIE ';' | |
131 | #define GOPHER_PLUS_SOUND '<' | |
132 | ||
133 | #define GOPHER_PORT 70 | |
134 | #define GOPHER_DELETE_GAP (64*1024) | |
135 | ||
136 | #define TAB '\t' | |
137 | #define TEMP_BUF_SIZE SM_PAGE_SIZE | |
138 | #define MAX_CSO_RESULT 1024 | |
139 | ||
140 | typedef struct gopher_ds { | |
141 | StoreEntry *entry; | |
ed43818f | 142 | char host[SQUIDHOSTNAMELEN + 1]; |
090089c4 | 143 | enum { |
144 | NORMAL, | |
145 | HTML_DIR, | |
146 | HTML_INDEX_RESULT, | |
147 | HTML_CSO_RESULT, | |
148 | HTML_INDEX_PAGE, | |
149 | HTML_CSO_PAGE | |
150 | } conversion; | |
151 | int HTML_header_added; | |
152 | int port; | |
090089c4 | 153 | char type_id; |
fd45fa07 | 154 | char request[MAX_URL + 1]; |
090089c4 | 155 | int data_in; |
156 | int cso_recno; | |
157 | int len; | |
158 | char *buf; /* pts to a 4k page */ | |
090089c4 | 159 | } GopherData; |
160 | ||
161 | GopherData *CreateGopherData(); | |
162 | ||
090089c4 | 163 | char def_gopher_bin[] = "www/unknown"; |
164 | char def_gopher_text[] = "text/plain"; | |
165 | ||
51fa90db | 166 | static int gopherStateFree(fd, gopherState) |
ba718c8f | 167 | int fd; |
51fa90db | 168 | GopherData *gopherState; |
ba718c8f | 169 | { |
51fa90db | 170 | if (gopherState == NULL) |
171 | return 1; | |
30a4f2a8 | 172 | if (gopherState->entry) |
173 | storeUnlockObject(gopherState->entry); | |
51fa90db | 174 | put_free_4k_page(gopherState->buf); |
175 | xfree(gopherState); | |
176 | return 0; | |
ba718c8f | 177 | } |
178 | ||
179 | ||
090089c4 | 180 | /* figure out content type from file extension */ |
181 | static void gopher_mime_content(buf, name, def) | |
182 | char *buf; | |
183 | char *name; | |
184 | char *def; | |
185 | { | |
95d659f0 | 186 | LOCAL_ARRAY(char, temp, MAX_URL + 1); |
090089c4 | 187 | char *ext1 = NULL; |
188 | char *ext2 = NULL; | |
189 | char *str = NULL; | |
190 | ext_table_entry *e = NULL; | |
191 | ||
192 | ext2 = NULL; | |
193 | strcpy(temp, name); | |
194 | for (ext1 = temp; *ext1; ext1++) | |
195 | if (isupper(*ext1)) | |
196 | *ext1 = tolower(*ext1); | |
197 | if ((ext1 = strrchr(temp, '.')) == NULL) { | |
198 | /* use default */ | |
199 | sprintf(buf + strlen(buf), "Content-Type: %s\r\n", def); | |
200 | return; | |
201 | } | |
202 | /* try extension table */ | |
203 | *ext1++ = 0; | |
204 | if (strcmp("gz", ext1) == 0 || strcmp("z", ext1) == 0) { | |
205 | ext2 = ext1; | |
206 | if ((ext1 = strrchr(temp, '.')) == NULL) { | |
207 | ext1 = ext2; | |
208 | ext2 = NULL; | |
209 | } else | |
210 | ext1++; | |
211 | } | |
212 | if ((e = mime_ext_to_type(ext1)) == NULL) { | |
213 | /* mime_ext_to_type() can return a NULL */ | |
214 | if (ext2 && (e = mime_ext_to_type(ext2))) { | |
215 | str = e->mime_type; | |
216 | ext2 = NULL; | |
217 | } else { | |
218 | str = def; | |
219 | } | |
220 | } else { | |
221 | str = e->mime_type; | |
222 | } | |
223 | sprintf(buf + strlen(buf), "Content-Type: %s\r\n", str); | |
224 | if (ext2 && (e = mime_ext_to_type(ext2))) { | |
225 | sprintf(buf + strlen(buf), "Content-Encoding: %s\r\n", | |
226 | e->mime_encoding); | |
227 | } | |
228 | } | |
229 | ||
230 | ||
231 | ||
232 | /* create MIME Header for Gopher Data */ | |
233 | void gopherMimeCreate(data) | |
234 | GopherData *data; | |
235 | { | |
95d659f0 | 236 | LOCAL_ARRAY(char, tempMIME, MAX_MIME); |
090089c4 | 237 | |
238 | sprintf(tempMIME, "\ | |
239 | HTTP/1.0 200 OK Gatewaying\r\n\ | |
c5c666ab | 240 | Server: Squid/%s\r\n\ |
8213067d | 241 | MIME-version: 1.0\r\n", version_string); |
090089c4 | 242 | |
243 | switch (data->type_id) { | |
244 | ||
245 | case GOPHER_DIRECTORY: | |
246 | case GOPHER_INDEX: | |
247 | case GOPHER_HTML: | |
248 | case GOPHER_WWW: | |
249 | case GOPHER_CSO: | |
250 | strcat(tempMIME, "Content-Type: text/html\r\n"); | |
251 | break; | |
252 | case GOPHER_GIF: | |
253 | case GOPHER_IMAGE: | |
254 | case GOPHER_PLUS_IMAGE: | |
255 | strcat(tempMIME, "Content-Type: image/gif\r\n"); | |
256 | break; | |
257 | case GOPHER_SOUND: | |
258 | case GOPHER_PLUS_SOUND: | |
259 | strcat(tempMIME, "Content-Type: audio/basic\r\n"); | |
260 | break; | |
261 | case GOPHER_PLUS_MOVIE: | |
262 | strcat(tempMIME, "Content-Type: video/mpeg\r\n"); | |
263 | break; | |
264 | case GOPHER_MACBINHEX: | |
265 | case GOPHER_DOSBIN: | |
266 | case GOPHER_UUENCODED: | |
267 | case GOPHER_BIN: | |
268 | /* Rightnow We have no idea what it is. */ | |
269 | gopher_mime_content(tempMIME, data->request, def_gopher_bin); | |
270 | break; | |
271 | ||
272 | case GOPHER_FILE: | |
273 | default: | |
274 | gopher_mime_content(tempMIME, data->request, def_gopher_text); | |
275 | break; | |
276 | ||
277 | } | |
278 | ||
279 | strcat(tempMIME, "\r\n"); | |
280 | storeAppend(data->entry, tempMIME, strlen(tempMIME)); | |
281 | } | |
282 | ||
283 | /* Parse a gopher url into components. By Anawat. */ | |
284 | int gopher_url_parser(url, host, port, type_id, request) | |
285 | char *url; | |
286 | char *host; | |
287 | int *port; | |
288 | char *type_id; | |
289 | char *request; | |
290 | { | |
95d659f0 | 291 | LOCAL_ARRAY(char, proto, MAX_URL); |
292 | LOCAL_ARRAY(char, hostbuf, MAX_URL); | |
090089c4 | 293 | int t; |
294 | ||
de06a228 | 295 | proto[0] = hostbuf[0] = '\0'; |
090089c4 | 296 | host[0] = request[0] = '\0'; |
297 | (*port) = 0; | |
298 | (*type_id) = 0; | |
299 | ||
de06a228 | 300 | t = sscanf(url, "%[a-zA-Z]://%[^/]/%c%s", proto, hostbuf, |
090089c4 | 301 | type_id, request); |
de06a228 | 302 | if ((t < 2) || strcasecmp(proto, "gopher")) { |
090089c4 | 303 | return -1; |
304 | } else if (t == 2) { | |
305 | (*type_id) = GOPHER_DIRECTORY; | |
306 | request[0] = '\0'; | |
307 | } else if (t == 3) { | |
308 | request[0] = '\0'; | |
309 | } else { | |
310 | /* convert %xx to char */ | |
de06a228 | 311 | (void) url_convert_hex(request, 0); |
090089c4 | 312 | } |
313 | ||
314 | host[0] = '\0'; | |
315 | if (sscanf(hostbuf, "%[^:]:%d", host, port) < 2) | |
316 | (*port) = GOPHER_PORT; | |
317 | ||
318 | return 0; | |
319 | } | |
320 | ||
6eb42cae | 321 | int gopherCachable(url) |
090089c4 | 322 | char *url; |
090089c4 | 323 | { |
0ffd22bc | 324 | wordlist *p = NULL; |
090089c4 | 325 | GopherData *data = NULL; |
326 | int cachable = 1; | |
327 | ||
328 | /* scan stop list */ | |
0ffd22bc | 329 | for (p = getGopherStoplist(); p; p = p->next) |
090089c4 | 330 | if (strstr(url, p->key)) |
331 | return 0; | |
332 | ||
333 | /* use as temp data structure to parse gopher URL */ | |
334 | data = CreateGopherData(); | |
335 | ||
336 | /* parse to see type */ | |
337 | gopher_url_parser(url, data->host, &data->port, &data->type_id, data->request); | |
338 | ||
339 | switch (data->type_id) { | |
340 | case GOPHER_INDEX: | |
341 | case GOPHER_CSO: | |
342 | case GOPHER_TELNET: | |
343 | case GOPHER_3270: | |
344 | cachable = 0; | |
345 | break; | |
346 | default: | |
347 | cachable = 1; | |
348 | } | |
51fa90db | 349 | gopherStateFree(-1, data); |
090089c4 | 350 | |
351 | return cachable; | |
352 | } | |
353 | ||
354 | void gopherEndHTML(data) | |
355 | GopherData *data; | |
356 | { | |
95d659f0 | 357 | LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE); |
090089c4 | 358 | |
359 | if (!data->data_in) { | |
360 | sprintf(tmpbuf, "<HR><H2><i>Server Return Nothing.</i></H2>\n"); | |
361 | storeAppend(data->entry, tmpbuf, strlen(tmpbuf)); | |
362 | return; | |
363 | } | |
364 | } | |
365 | ||
366 | ||
367 | /* Convert Gopher to HTML */ | |
368 | /* Borrow part of code from libwww2 came with Mosaic distribution */ | |
369 | void gopherToHTML(data, inbuf, len) | |
370 | GopherData *data; | |
371 | char *inbuf; | |
372 | int len; | |
373 | { | |
374 | char *pos = inbuf; | |
375 | char *lpos = NULL; | |
376 | char *tline = NULL; | |
95d659f0 | 377 | LOCAL_ARRAY(char, line, TEMP_BUF_SIZE); |
378 | LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE); | |
379 | LOCAL_ARRAY(char, outbuf, TEMP_BUF_SIZE << 4); | |
090089c4 | 380 | char *name = NULL; |
381 | char *selector = NULL; | |
382 | char *host = NULL; | |
383 | char *port = NULL; | |
384 | char *escaped_selector = NULL; | |
385 | char *icon_type = NULL; | |
386 | char gtype; | |
387 | StoreEntry *entry = NULL; | |
388 | ||
389 | memset(outbuf, '\0', sizeof(outbuf)); | |
390 | memset(tmpbuf, '\0', sizeof(outbuf)); | |
391 | memset(line, '\0', sizeof(outbuf)); | |
392 | ||
393 | entry = data->entry; | |
394 | ||
395 | if (data->conversion == HTML_INDEX_PAGE) { | |
396 | sprintf(outbuf, "<TITLE>Gopher Index %s</TITLE><H1>%s<BR>Gopher Search</H1> This is a searchable Gopher index.Use the search function of your browser to enter search terms. <ISINDEX>\n", entry->url, entry->url); | |
397 | ||
398 | storeAppend(entry, outbuf, strlen(outbuf)); | |
399 | /* now let start sending stuff to client */ | |
400 | BIT_RESET(entry->flag, DELAY_SENDING); | |
401 | data->data_in = 1; | |
402 | ||
403 | return; | |
404 | } | |
405 | if (data->conversion == HTML_CSO_PAGE) { | |
406 | sprintf(outbuf, "<TITLE>CSO Search of %s</TITLE><H1>%s<BR>CSO Search</H1>A CSO database usually contains a phonebook or directory. Use the search function of your browser to enter search terms.<ISINDEX>\n", | |
407 | entry->url, entry->url); | |
408 | ||
409 | storeAppend(entry, outbuf, strlen(outbuf)); | |
410 | /* now let start sending stuff to client */ | |
411 | BIT_RESET(entry->flag, DELAY_SENDING); | |
412 | data->data_in = 1; | |
413 | ||
414 | return; | |
415 | } | |
416 | inbuf[len] = '\0'; | |
417 | ||
418 | if (!data->HTML_header_added) { | |
419 | if (data->conversion == HTML_CSO_RESULT) | |
420 | strcat(outbuf, "<H1>CSO Searchs Result</H1>\n<PRE>\n"); | |
421 | else | |
422 | strcat(outbuf, "<H1>Gopher Menu</H1>\n<PRE>\n"); | |
423 | data->HTML_header_added = 1; | |
424 | } | |
425 | while ((pos != NULL) && (pos < inbuf + len)) { | |
426 | ||
427 | if (data->len != 0) { | |
428 | /* there is something left from last tx. */ | |
429 | strncpy(line, data->buf, data->len); | |
430 | lpos = (char *) memccpy(line + data->len, inbuf, '\n', len); | |
431 | if (lpos) | |
432 | *lpos = '\0'; | |
433 | else { | |
434 | /* there is no complete line in inbuf */ | |
435 | /* copy it to temp buffer */ | |
436 | if (data->len + len > TEMP_BUF_SIZE) { | |
019dd986 | 437 | debug(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: %s\n", |
090089c4 | 438 | entry->url); |
439 | len = TEMP_BUF_SIZE - data->len; | |
440 | } | |
30a4f2a8 | 441 | xmemcpy(data->buf + data->len, inbuf, len); |
090089c4 | 442 | data->len += len; |
443 | return; | |
444 | } | |
445 | ||
446 | /* skip one line */ | |
447 | pos = (char *) memchr(pos, '\n', 256); | |
448 | if (pos) | |
449 | pos++; | |
450 | ||
451 | /* we're done with the remain from last tx. */ | |
452 | data->len = 0; | |
453 | *(data->buf) = '\0'; | |
454 | } else { | |
455 | ||
456 | lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf)); | |
457 | if (lpos) | |
458 | *lpos = '\0'; | |
459 | else { | |
460 | /* there is no complete line in inbuf */ | |
461 | /* copy it to temp buffer */ | |
462 | if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) { | |
019dd986 | 463 | debug(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: %s\n", |
090089c4 | 464 | entry->url); |
465 | len = TEMP_BUF_SIZE; | |
466 | } | |
467 | if (len > (pos - inbuf)) { | |
30a4f2a8 | 468 | xmemcpy(data->buf, pos, len - (pos - inbuf)); |
090089c4 | 469 | data->len = len - (pos - inbuf); |
470 | } | |
471 | break; | |
472 | } | |
473 | ||
474 | /* skip one line */ | |
475 | pos = (char *) memchr(pos, '\n', 256); | |
476 | if (pos) | |
477 | pos++; | |
478 | ||
479 | } | |
480 | ||
481 | /* at this point. We should have one line in buffer to process */ | |
482 | ||
483 | if (*line == '.') { | |
484 | /* skip it */ | |
485 | memset(line, '\0', TEMP_BUF_SIZE); | |
486 | continue; | |
487 | } | |
488 | switch (data->conversion) { | |
489 | ||
490 | case HTML_INDEX_RESULT: | |
491 | case HTML_DIR:{ | |
492 | tline = line; | |
493 | gtype = *tline++; | |
494 | name = tline; | |
495 | selector = strchr(tline, TAB); | |
496 | if (selector) { | |
497 | *selector++ = '\0'; | |
498 | host = strchr(selector, TAB); | |
499 | if (host) { | |
500 | *host++ = '\0'; | |
501 | port = strchr(host, TAB); | |
502 | if (port) { | |
503 | char *junk; | |
504 | port[0] = ':'; | |
505 | junk = strchr(host, TAB); | |
506 | if (junk) | |
507 | *junk++ = 0; /* Chop port */ | |
508 | else { | |
509 | junk = strchr(host, '\r'); | |
510 | if (junk) | |
511 | *junk++ = 0; /* Chop port */ | |
512 | else { | |
513 | junk = strchr(host, '\n'); | |
514 | if (junk) | |
515 | *junk++ = 0; /* Chop port */ | |
516 | } | |
517 | } | |
518 | if ((port[1] == '0') && (!port[2])) | |
519 | port[0] = 0; /* 0 means none */ | |
520 | } | |
521 | /* escape a selector here */ | |
522 | escaped_selector = url_escape(selector); | |
523 | ||
524 | switch (gtype) { | |
525 | case GOPHER_DIRECTORY: | |
526 | icon_type = "internal-gopher-menu"; | |
527 | break; | |
528 | case GOPHER_FILE: | |
529 | icon_type = "internal-gopher-text"; | |
530 | break; | |
531 | case GOPHER_INDEX: | |
532 | case GOPHER_CSO: | |
533 | icon_type = "internal-gopher-index"; | |
534 | break; | |
535 | case GOPHER_IMAGE: | |
536 | case GOPHER_GIF: | |
537 | case GOPHER_PLUS_IMAGE: | |
538 | icon_type = "internal-gopher-image"; | |
539 | break; | |
540 | case GOPHER_SOUND: | |
541 | case GOPHER_PLUS_SOUND: | |
542 | icon_type = "internal-gopher-sound"; | |
543 | break; | |
544 | case GOPHER_PLUS_MOVIE: | |
545 | icon_type = "internal-gopher-movie"; | |
546 | break; | |
547 | case GOPHER_TELNET: | |
548 | case GOPHER_3270: | |
549 | icon_type = "internal-gopher-telnet"; | |
550 | break; | |
551 | case GOPHER_BIN: | |
552 | case GOPHER_MACBINHEX: | |
553 | case GOPHER_DOSBIN: | |
554 | case GOPHER_UUENCODED: | |
555 | icon_type = "internal-gopher-binary"; | |
556 | break; | |
557 | default: | |
558 | icon_type = "internal-gopher-unknown"; | |
559 | break; | |
560 | } | |
561 | ||
562 | ||
563 | memset(tmpbuf, '\0', TEMP_BUF_SIZE); | |
564 | if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) { | |
565 | if (strlen(escaped_selector) != 0) | |
566 | sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s@%s/\">%s</A>\n", | |
567 | icon_type, escaped_selector, host, name); | |
568 | else | |
569 | sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"telnet://%s/\">%s</A>\n", | |
570 | icon_type, host, name); | |
571 | ||
572 | } else { | |
573 | sprintf(tmpbuf, "<IMG BORDER=0 SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n", | |
574 | icon_type, host, gtype, escaped_selector, name); | |
575 | } | |
576 | safe_free(escaped_selector); | |
577 | strcat(outbuf, tmpbuf); | |
578 | data->data_in = 1; | |
579 | } else { | |
580 | memset(line, '\0', TEMP_BUF_SIZE); | |
581 | continue; | |
582 | } | |
583 | } else { | |
584 | memset(line, '\0', TEMP_BUF_SIZE); | |
585 | continue; | |
586 | } | |
587 | break; | |
588 | } /* HTML_DIR, HTML_INDEX_RESULT */ | |
589 | ||
590 | ||
591 | case HTML_CSO_RESULT:{ | |
592 | int t; | |
593 | int code; | |
594 | int recno; | |
95d659f0 | 595 | LOCAL_ARRAY(char, result, MAX_CSO_RESULT); |
090089c4 | 596 | |
597 | tline = line; | |
598 | ||
599 | if (tline[0] == '-') { | |
600 | t = sscanf(tline, "-%d:%d:%[^\n]", &code, &recno, result); | |
601 | if (t < 3) | |
602 | break; | |
603 | ||
604 | if (code != 200) | |
605 | break; | |
606 | ||
607 | if (data->cso_recno != recno) { | |
608 | sprintf(tmpbuf, "</PRE><HR><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, result); | |
609 | data->cso_recno = recno; | |
610 | } else { | |
611 | sprintf(tmpbuf, "%s\n", result); | |
612 | } | |
613 | strcat(outbuf, tmpbuf); | |
614 | data->data_in = 1; | |
615 | break; | |
616 | } else { | |
617 | /* handle some error codes */ | |
618 | t = sscanf(tline, "%d:%[^\n]", &code, result); | |
619 | ||
620 | if (t < 2) | |
621 | break; | |
622 | ||
623 | switch (code) { | |
624 | ||
625 | case 200:{ | |
626 | /* OK */ | |
627 | /* Do nothing here */ | |
628 | break; | |
629 | } | |
630 | ||
631 | case 102: /* Number of matches */ | |
632 | case 501: /* No Match */ | |
633 | case 502: /* Too Many Matches */ | |
634 | { | |
635 | /* Print the message the server returns */ | |
636 | sprintf(tmpbuf, "</PRE><HR><H2><i>%s</i></H2>\n<PRE>", result); | |
637 | strcat(outbuf, tmpbuf); | |
638 | data->data_in = 1; | |
639 | break; | |
640 | } | |
641 | ||
642 | ||
643 | } | |
644 | } | |
645 | ||
646 | } /* HTML_CSO_RESULT */ | |
647 | default: | |
648 | break; /* do nothing */ | |
649 | ||
650 | } /* switch */ | |
651 | ||
652 | } /* while loop */ | |
653 | ||
654 | if ((int) strlen(outbuf) > 0) { | |
655 | storeAppend(entry, outbuf, strlen(outbuf)); | |
656 | /* now let start sending stuff to client */ | |
657 | BIT_RESET(entry->flag, DELAY_SENDING); | |
658 | } | |
659 | return; | |
660 | } | |
661 | ||
662 | ||
663 | int gopherReadReplyTimeout(fd, data) | |
664 | int fd; | |
665 | GopherData *data; | |
666 | { | |
667 | StoreEntry *entry = NULL; | |
668 | entry = data->entry; | |
019dd986 | 669 | debug(10, 4, "GopherReadReplyTimeout: Timeout on %d\n url: %s\n", fd, entry->url); |
b8de7ebe | 670 | squid_error_entry(entry, ERR_READ_TIMEOUT, NULL); |
51fa90db | 671 | comm_close(fd); |
090089c4 | 672 | return 0; |
673 | } | |
674 | ||
675 | /* This will be called when socket lifetime is expired. */ | |
676 | void gopherLifetimeExpire(fd, data) | |
677 | int fd; | |
678 | GopherData *data; | |
679 | { | |
680 | StoreEntry *entry = NULL; | |
681 | entry = data->entry; | |
019dd986 | 682 | debug(10, 4, "gopherLifeTimeExpire: FD %d: <URL:%s>\n", fd, entry->url); |
b8de7ebe | 683 | squid_error_entry(entry, ERR_LIFETIME_EXP, NULL); |
d1cfbef7 | 684 | comm_set_select_handler(fd, |
685 | COMM_SELECT_READ | COMM_SELECT_WRITE, | |
686 | 0, | |
687 | 0); | |
51fa90db | 688 | comm_close(fd); |
090089c4 | 689 | } |
690 | ||
691 | ||
692 | ||
693 | ||
694 | /* This will be called when data is ready to be read from fd. Read until | |
695 | * error or connection closed. */ | |
696 | int gopherReadReply(fd, data) | |
697 | int fd; | |
698 | GopherData *data; | |
699 | { | |
700 | char *buf = NULL; | |
701 | int len; | |
702 | int clen; | |
703 | int off; | |
704 | StoreEntry *entry = NULL; | |
705 | ||
706 | entry = data->entry; | |
707 | if (entry->flag & DELETE_BEHIND) { | |
708 | if (storeClientWaiting(entry)) { | |
22e4fa85 | 709 | clen = entry->mem_obj->e_current_len; |
710 | off = entry->mem_obj->e_lowest_offset; | |
090089c4 | 711 | if ((clen - off) > GOPHER_DELETE_GAP) { |
019dd986 | 712 | debug(10, 3, "gopherReadReply: Read deferred for Object: %s\n", |
ba718c8f | 713 | entry->url); |
019dd986 | 714 | debug(10, 3, " Current Gap: %d bytes\n", |
090089c4 | 715 | clen - off); |
716 | ||
717 | /* reschedule, so it will automatically reactivated when | |
718 | * Gap is big enough. */ | |
719 | comm_set_select_handler(fd, | |
720 | COMM_SELECT_READ, | |
721 | (PF) gopherReadReply, | |
51496678 | 722 | (void *) data); |
14e59844 | 723 | /* don't install read timeout until we are below the GAP */ |
090089c4 | 724 | comm_set_select_handler_plus_timeout(fd, |
725 | COMM_SELECT_TIMEOUT, | |
726 | (PF) NULL, | |
51496678 | 727 | (void *) NULL, |
090089c4 | 728 | (time_t) 0); |
45cd3339 | 729 | comm_set_stall(fd, getStallDelay()); /* dont try reading again for a while */ |
090089c4 | 730 | return 0; |
731 | } | |
732 | } else { | |
733 | /* we can terminate connection right now */ | |
b8de7ebe | 734 | squid_error_entry(entry, ERR_NO_CLIENTS_BIG_OBJ, NULL); |
51fa90db | 735 | comm_close(fd); |
090089c4 | 736 | return 0; |
737 | } | |
738 | } | |
2daae136 | 739 | buf = get_free_4k_page(); |
6fe6313d | 740 | errno = 0; |
090089c4 | 741 | len = read(fd, buf, TEMP_BUF_SIZE - 1); /* leave one space for \0 in gopherToHTML */ |
019dd986 | 742 | debug(10, 5, "gopherReadReply: FD %d read len=%d\n", fd, len); |
090089c4 | 743 | |
ba718c8f | 744 | if (len < 0) { |
745 | debug(10, 1, "gopherReadReply: error reading: %s\n", xstrerror()); | |
746 | if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
6fe6313d | 747 | /* reinstall handlers */ |
748 | /* XXX This may loop forever */ | |
749 | comm_set_select_handler(fd, COMM_SELECT_READ, | |
51496678 | 750 | (PF) gopherReadReply, (void *) data); |
6fe6313d | 751 | comm_set_select_handler_plus_timeout(fd, COMM_SELECT_TIMEOUT, |
51496678 | 752 | (PF) gopherReadReplyTimeout, (void *) data, getReadTimeout()); |
6fe6313d | 753 | } else { |
ba718c8f | 754 | BIT_RESET(entry->flag, CACHABLE); |
2daae136 | 755 | storeReleaseRequest(entry); |
b8de7ebe | 756 | squid_error_entry(entry, ERR_READ_ERROR, xstrerror()); |
51fa90db | 757 | comm_close(fd); |
6fe6313d | 758 | } |
ba718c8f | 759 | } else if (len == 0 && entry->mem_obj->e_current_len == 0) { |
b8de7ebe | 760 | squid_error_entry(entry, |
ba718c8f | 761 | ERR_ZERO_SIZE_OBJECT, |
762 | errno ? xstrerror() : NULL); | |
51fa90db | 763 | comm_close(fd); |
090089c4 | 764 | } else if (len == 0) { |
765 | /* Connection closed; retrieval done. */ | |
766 | /* flush the rest of data in temp buf if there is one. */ | |
767 | if (data->conversion != NORMAL) | |
768 | gopherEndHTML(data); | |
769 | if (!(entry->flag & DELETE_BEHIND)) | |
dd44ede3 | 770 | ttlSet(entry); |
090089c4 | 771 | BIT_RESET(entry->flag, DELAY_SENDING); |
772 | storeComplete(entry); | |
51fa90db | 773 | comm_close(fd); |
22e4fa85 | 774 | } else if (((entry->mem_obj->e_current_len + len) > getGopherMax()) && |
090089c4 | 775 | !(entry->flag & DELETE_BEHIND)) { |
776 | /* accept data, but start to delete behind it */ | |
777 | storeStartDeleteBehind(entry); | |
778 | ||
779 | if (data->conversion != NORMAL) { | |
780 | gopherToHTML(data, buf, len); | |
781 | } else { | |
782 | storeAppend(entry, buf, len); | |
783 | } | |
ba718c8f | 784 | comm_set_select_handler(fd, |
785 | COMM_SELECT_READ, | |
786 | (PF) gopherReadReply, | |
51496678 | 787 | (void *) data); |
ba718c8f | 788 | comm_set_select_handler_plus_timeout(fd, |
789 | COMM_SELECT_TIMEOUT, | |
790 | (PF) gopherReadReplyTimeout, | |
51496678 | 791 | (void *) data, |
ba718c8f | 792 | getReadTimeout()); |
090089c4 | 793 | } else if (entry->flag & CLIENT_ABORT_REQUEST) { |
794 | /* append the last bit of info we got */ | |
795 | if (data->conversion != NORMAL) { | |
796 | gopherToHTML(data, buf, len); | |
797 | } else { | |
798 | storeAppend(entry, buf, len); | |
799 | } | |
b8de7ebe | 800 | squid_error_entry(entry, ERR_CLIENT_ABORT, NULL); |
090089c4 | 801 | if (data->conversion != NORMAL) |
802 | gopherEndHTML(data); | |
803 | BIT_RESET(entry->flag, DELAY_SENDING); | |
51fa90db | 804 | comm_close(fd); |
090089c4 | 805 | } else { |
806 | if (data->conversion != NORMAL) { | |
807 | gopherToHTML(data, buf, len); | |
808 | } else { | |
809 | storeAppend(entry, buf, len); | |
810 | } | |
ba718c8f | 811 | comm_set_select_handler(fd, |
812 | COMM_SELECT_READ, | |
813 | (PF) gopherReadReply, | |
51496678 | 814 | (void *) data); |
ba718c8f | 815 | comm_set_select_handler_plus_timeout(fd, |
816 | COMM_SELECT_TIMEOUT, | |
817 | (PF) gopherReadReplyTimeout, | |
51496678 | 818 | (void *) data, |
ba718c8f | 819 | getReadTimeout()); |
090089c4 | 820 | } |
2daae136 | 821 | put_free_4k_page(buf); |
090089c4 | 822 | return 0; |
823 | } | |
824 | ||
825 | /* This will be called when request write is complete. Schedule read of | |
826 | * reply. */ | |
44a47c6e | 827 | void gopherSendComplete(fd, buf, size, errflag, data) |
090089c4 | 828 | int fd; |
829 | char *buf; | |
830 | int size; | |
831 | int errflag; | |
30a4f2a8 | 832 | void *data; |
090089c4 | 833 | { |
30a4f2a8 | 834 | GopherData *gopherState = (GopherData *) data; |
090089c4 | 835 | StoreEntry *entry = NULL; |
30a4f2a8 | 836 | entry = gopherState->entry; |
019dd986 | 837 | debug(10, 5, "gopherSendComplete: FD %d size: %d errflag: %d\n", |
090089c4 | 838 | fd, size, errflag); |
839 | if (errflag) { | |
b8de7ebe | 840 | squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); |
51fa90db | 841 | comm_close(fd); |
090089c4 | 842 | if (buf) |
2daae136 | 843 | put_free_4k_page(buf); /* Allocated by gopherSendRequest. */ |
44a47c6e | 844 | return; |
090089c4 | 845 | } |
846 | /* | |
847 | * OK. We successfully reach remote site. Start MIME typing | |
848 | * stuff. Do it anyway even though request is not HTML type. | |
849 | */ | |
30a4f2a8 | 850 | gopherMimeCreate(gopherState); |
090089c4 | 851 | |
6eb42cae | 852 | if (!BIT_TEST(entry->flag, ENTRY_HTML)) |
30a4f2a8 | 853 | gopherState->conversion = NORMAL; |
090089c4 | 854 | else |
30a4f2a8 | 855 | switch (gopherState->type_id) { |
090089c4 | 856 | |
857 | case GOPHER_DIRECTORY: | |
858 | /* we got to convert it first */ | |
859 | BIT_SET(entry->flag, DELAY_SENDING); | |
30a4f2a8 | 860 | gopherState->conversion = HTML_DIR; |
861 | gopherState->HTML_header_added = 0; | |
090089c4 | 862 | break; |
863 | ||
864 | case GOPHER_INDEX: | |
865 | /* we got to convert it first */ | |
866 | BIT_SET(entry->flag, DELAY_SENDING); | |
30a4f2a8 | 867 | gopherState->conversion = HTML_INDEX_RESULT; |
868 | gopherState->HTML_header_added = 0; | |
090089c4 | 869 | break; |
870 | ||
871 | case GOPHER_CSO: | |
872 | /* we got to convert it first */ | |
873 | BIT_SET(entry->flag, DELAY_SENDING); | |
30a4f2a8 | 874 | gopherState->conversion = HTML_CSO_RESULT; |
875 | gopherState->cso_recno = 0; | |
876 | gopherState->HTML_header_added = 0; | |
090089c4 | 877 | break; |
878 | ||
879 | default: | |
30a4f2a8 | 880 | gopherState->conversion = NORMAL; |
090089c4 | 881 | |
882 | } | |
883 | /* Schedule read reply. */ | |
884 | comm_set_select_handler(fd, | |
885 | COMM_SELECT_READ, | |
886 | (PF) gopherReadReply, | |
30a4f2a8 | 887 | (void *) gopherState); |
090089c4 | 888 | comm_set_select_handler_plus_timeout(fd, |
889 | COMM_SELECT_TIMEOUT, | |
890 | (PF) gopherReadReplyTimeout, | |
30a4f2a8 | 891 | (void *) gopherState, |
090089c4 | 892 | getReadTimeout()); |
30a4f2a8 | 893 | comm_set_fd_lifetime(fd, 86400); /* extend lifetime */ |
090089c4 | 894 | |
895 | if (buf) | |
2daae136 | 896 | put_free_4k_page(buf); /* Allocated by gopherSendRequest. */ |
090089c4 | 897 | } |
898 | ||
899 | /* This will be called when connect completes. Write request. */ | |
2285407f | 900 | void gopherSendRequest(fd, data) |
090089c4 | 901 | int fd; |
902 | GopherData *data; | |
903 | { | |
090089c4 | 904 | int len; |
95d659f0 | 905 | LOCAL_ARRAY(char, query, MAX_URL); |
2daae136 | 906 | char *buf = get_free_4k_page(); |
090089c4 | 907 | |
090089c4 | 908 | if (data->type_id == GOPHER_CSO) { |
909 | sscanf(data->request, "?%s", query); | |
910 | len = strlen(query) + 15; | |
2285407f | 911 | sprintf(buf, "query %s\r\nquit\r\n", query); |
090089c4 | 912 | } else if (data->type_id == GOPHER_INDEX) { |
913 | char *c_ptr = strchr(data->request, '?'); | |
914 | if (c_ptr) { | |
915 | *c_ptr = '\t'; | |
916 | } | |
917 | len = strlen(data->request) + 3; | |
2285407f | 918 | sprintf(buf, "%s\r\n", data->request); |
090089c4 | 919 | } else { |
920 | len = strlen(data->request) + 3; | |
2285407f | 921 | sprintf(buf, "%s\r\n", data->request); |
090089c4 | 922 | } |
923 | ||
019dd986 | 924 | debug(10, 5, "gopherSendRequest: FD %d\n", fd); |
30a4f2a8 | 925 | comm_write(fd, |
44a47c6e | 926 | buf, |
927 | len, | |
928 | 30, | |
929 | gopherSendComplete, | |
9864ee44 | 930 | (void *) data, |
931 | put_free_4k_page); | |
234967c9 | 932 | if (BIT_TEST(data->entry->flag, CACHABLE)) |
147d3115 | 933 | storeSetPublicKey(data->entry); /* Make it public */ |
090089c4 | 934 | } |
935 | ||
936 | int gopherStart(unusedfd, url, entry) | |
937 | int unusedfd; | |
938 | char *url; | |
939 | StoreEntry *entry; | |
940 | { | |
941 | /* Create state structure. */ | |
942 | int sock, status; | |
943 | GopherData *data = CreateGopherData(); | |
944 | ||
30a4f2a8 | 945 | storeLockObject(data->entry = entry, NULL, NULL); |
090089c4 | 946 | |
019dd986 | 947 | debug(10, 3, "gopherStart: url: %s\n", url); |
090089c4 | 948 | |
949 | /* Parse url. */ | |
950 | if (gopher_url_parser(url, data->host, &data->port, | |
951 | &data->type_id, data->request)) { | |
b8de7ebe | 952 | squid_error_entry(entry, ERR_INVALID_URL, NULL); |
51fa90db | 953 | gopherStateFree(-1, data); |
090089c4 | 954 | return COMM_ERROR; |
955 | } | |
956 | /* Create socket. */ | |
30a4f2a8 | 957 | sock = comm_open(COMM_NONBLOCKING, getTcpOutgoingAddr(), 0, url); |
090089c4 | 958 | if (sock == COMM_ERROR) { |
019dd986 | 959 | debug(10, 4, "gopherStart: Failed because we're out of sockets.\n"); |
b8de7ebe | 960 | squid_error_entry(entry, ERR_NO_FDS, xstrerror()); |
51fa90db | 961 | gopherStateFree(-1, data); |
090089c4 | 962 | return COMM_ERROR; |
963 | } | |
30a4f2a8 | 964 | comm_add_close_handler(sock, |
983061ed | 965 | (PF) gopherStateFree, |
51fa90db | 966 | (void *) data); |
967 | ||
090089c4 | 968 | /* check if IP is already in cache. It must be. |
969 | * It should be done before this route is called. | |
970 | * Otherwise, we cannot check return code for connect. */ | |
30a4f2a8 | 971 | if (!ipcache_gethostbyname(data->host, 0)) { |
019dd986 | 972 | debug(10, 4, "gopherStart: Called without IP entry in ipcache. OR lookup failed.\n"); |
b8de7ebe | 973 | squid_error_entry(entry, ERR_DNS_FAIL, dns_error_message); |
51fa90db | 974 | comm_close(sock); |
090089c4 | 975 | return COMM_ERROR; |
976 | } | |
977 | if (((data->type_id == GOPHER_INDEX) || (data->type_id == GOPHER_CSO)) | |
978 | && (strchr(data->request, '?') == NULL) | |
6eb42cae | 979 | && (BIT_TEST(entry->flag, ENTRY_HTML))) { |
090089c4 | 980 | /* Index URL without query word */ |
981 | /* We have to generate search page back to client. No need for connection */ | |
982 | gopherMimeCreate(data); | |
983 | ||
984 | if (data->type_id == GOPHER_INDEX) { | |
985 | data->conversion = HTML_INDEX_PAGE; | |
986 | } else { | |
987 | if (data->type_id == GOPHER_CSO) { | |
988 | data->conversion = HTML_CSO_PAGE; | |
989 | } else { | |
990 | data->conversion = HTML_INDEX_PAGE; | |
991 | } | |
992 | } | |
993 | gopherToHTML(data, (char *) NULL, 0); | |
994 | storeComplete(entry); | |
51fa90db | 995 | comm_close(sock); |
090089c4 | 996 | return COMM_OK; |
997 | } | |
998 | /* Open connection. */ | |
999 | if ((status = comm_connect(sock, data->host, data->port)) != 0) { | |
1000 | if (status != EINPROGRESS) { | |
b8de7ebe | 1001 | squid_error_entry(entry, ERR_CONNECT_FAIL, xstrerror()); |
51fa90db | 1002 | comm_close(sock); |
090089c4 | 1003 | return COMM_ERROR; |
1004 | } else { | |
019dd986 | 1005 | debug(10, 5, "startGopher: conn %d EINPROGRESS\n", sock); |
090089c4 | 1006 | } |
1007 | } | |
1008 | /* Install connection complete handler. */ | |
d1cfbef7 | 1009 | comm_set_select_handler(sock, |
1010 | COMM_SELECT_LIFETIME, | |
1011 | (PF) gopherLifetimeExpire, | |
51496678 | 1012 | (void *) data); |
d1cfbef7 | 1013 | comm_set_select_handler(sock, |
1014 | COMM_SELECT_WRITE, | |
1015 | (PF) gopherSendRequest, | |
51496678 | 1016 | (void *) data); |
090089c4 | 1017 | return COMM_OK; |
1018 | } | |
1019 | ||
1020 | ||
1021 | GopherData *CreateGopherData() | |
1022 | { | |
30a4f2a8 | 1023 | GopherData *gd = xcalloc(1, sizeof(GopherData)); |
2daae136 | 1024 | gd->buf = get_free_4k_page(); |
090089c4 | 1025 | return (gd); |
1026 | } |