]>
Commit | Line | Data |
---|---|---|
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 | ||
70 | typedef 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 | 92 | static PF gopherStateFree; |
eeb423fb | 93 | static void gopher_mime_content(MemBuf * mb, const char *name, const char *def); |
f5b8bbc4 | 94 | static void gopherMimeCreate(GopherStateData *); |
0ee4272b | 95 | static int gopher_url_parser(const char *url, |
684c2720 | 96 | char *host, |
97 | int *port, | |
98 | char *type_id, | |
99 | char *request); | |
f5b8bbc4 | 100 | static void gopherEndHTML(GopherStateData *); |
101 | static void gopherToHTML(GopherStateData *, char *inbuf, int len); | |
5c5783a2 | 102 | static PF gopherTimeout; |
582b6456 | 103 | static PF gopherReadReply; |
79a15e0a | 104 | static CWCB gopherSendComplete; |
582b6456 | 105 | static PF gopherSendRequest; |
f5b8bbc4 | 106 | static GopherStateData *CreateGopherStateData(void); |
56fa4cad | 107 | |
108 | static char def_gopher_bin[] = "www/unknown"; | |
109 | static char def_gopher_text[] = "text/plain"; | |
090089c4 | 110 | |
582b6456 | 111 | static void |
79d39a72 | 112 | gopherStateFree(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 | 127 | static void |
eeb423fb | 128 | gopher_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 | 141 | static void |
582b6456 | 142 | gopherMimeCreate(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 | 194 | static int |
0ee4272b | 195 | gopher_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 | 232 | int |
0ee4272b | 233 | gopherCachable(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 | 259 | static void |
582b6456 | 260 | gopherEndHTML(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 | 271 | static void |
582b6456 | 272 | gopherToHTML(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 | 571 | static void |
5c5783a2 | 572 | gopherTimeout(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 | 590 | static void |
582b6456 | 591 | gopherReadReply(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 | 681 | static void |
79a15e0a | 682 | gopherSendComplete(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 | 742 | static void |
582b6456 | 743 | gopherSendRequest(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 | 770 | void |
41462d93 | 771 | gopherStart(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 | 814 | static GopherStateData * |
0673c0ba | 815 | CreateGopherStateData(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 | } |