]>
Commit | Line | Data |
---|---|---|
a47b9029 | 1 | |
30a4f2a8 | 2 | /* |
86a2f789 | 3 | * $Id: gopher.cc,v 1.183 2003/06/20 01:01:00 robertc Exp $ |
30a4f2a8 | 4 | * |
5 | * DEBUG: section 10 Gopher | |
6 | * AUTHOR: Harvest Derived | |
7 | * | |
2b6662ba | 8 | * SQUID Web Proxy Cache http://www.squid-cache.org/ |
e25c139f | 9 | * ---------------------------------------------------------- |
30a4f2a8 | 10 | * |
2b6662ba | 11 | * Squid is the result of efforts by numerous individuals from |
12 | * the Internet community; see the CONTRIBUTORS file for full | |
13 | * details. Many organizations have provided support for Squid's | |
14 | * development; see the SPONSORS file for full details. Squid is | |
15 | * Copyrighted (C) 2001 by the Regents of the University of | |
16 | * California; see the COPYRIGHT file for full details. Squid | |
17 | * incorporates software developed and/or copyrighted by other | |
18 | * sources; see the CREDITS file for full details. | |
30a4f2a8 | 19 | * |
20 | * This program is free software; you can redistribute it and/or modify | |
21 | * it under the terms of the GNU General Public License as published by | |
22 | * the Free Software Foundation; either version 2 of the License, or | |
23 | * (at your option) any later version. | |
24 | * | |
25 | * This program is distributed in the hope that it will be useful, | |
26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
28 | * GNU General Public License for more details. | |
29 | * | |
30 | * You should have received a copy of the GNU General Public License | |
31 | * along with this program; if not, write to the Free Software | |
cbdec147 | 32 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. |
e25c139f | 33 | * |
019dd986 | 34 | */ |
44a47c6e | 35 | |
36 | #include "squid.h" | |
e6ccf245 | 37 | #include "Store.h" |
528b2c61 | 38 | #include "HttpRequest.h" |
13de3699 | 39 | #include "comm.h" |
b67e2c8c | 40 | #if DELAY_POOLS |
41 | #include "DelayPools.h" | |
86a2f789 | 42 | #include "MemObject.h" |
b67e2c8c | 43 | #endif |
090089c4 | 44 | |
090089c4 | 45 | /* gopher type code from rfc. Anawat. */ |
46 | #define GOPHER_FILE '0' | |
47 | #define GOPHER_DIRECTORY '1' | |
48 | #define GOPHER_CSO '2' | |
49 | #define GOPHER_ERROR '3' | |
50 | #define GOPHER_MACBINHEX '4' | |
51 | #define GOPHER_DOSBIN '5' | |
52 | #define GOPHER_UUENCODED '6' | |
53 | #define GOPHER_INDEX '7' | |
54 | #define GOPHER_TELNET '8' | |
55 | #define GOPHER_BIN '9' | |
56 | #define GOPHER_REDUNT '+' | |
57 | #define GOPHER_3270 'T' | |
58 | #define GOPHER_GIF 'g' | |
59 | #define GOPHER_IMAGE 'I' | |
60 | ||
61 | #define GOPHER_HTML 'h' /* HTML */ | |
62 | #define GOPHER_INFO 'i' | |
63 | #define GOPHER_WWW 'w' /* W3 address */ | |
64 | #define GOPHER_SOUND 's' | |
65 | ||
66 | #define GOPHER_PLUS_IMAGE ':' | |
67 | #define GOPHER_PLUS_MOVIE ';' | |
68 | #define GOPHER_PLUS_SOUND '<' | |
69 | ||
70 | #define GOPHER_PORT 70 | |
090089c4 | 71 | |
72 | #define TAB '\t' | |
447e176b | 73 | #define TEMP_BUF_SIZE 4096 |
090089c4 | 74 | #define MAX_CSO_RESULT 1024 |
75 | ||
62e76326 | 76 | typedef struct gopher_ds |
77 | { | |
090089c4 | 78 | StoreEntry *entry; |
090089c4 | 79 | enum { |
62e76326 | 80 | NORMAL, |
81 | HTML_DIR, | |
82 | HTML_INDEX_RESULT, | |
83 | HTML_CSO_RESULT, | |
84 | HTML_INDEX_PAGE, | |
85 | HTML_CSO_PAGE | |
090089c4 | 86 | } conversion; |
87 | int HTML_header_added; | |
090089c4 | 88 | char type_id; |
f2052513 | 89 | char request[MAX_URL]; |
090089c4 | 90 | int data_in; |
91 | int cso_recno; | |
92 | int len; | |
93 | char *buf; /* pts to a 4k page */ | |
bfcaf585 | 94 | int fd; |
09fb5b61 | 95 | request_t *req; |
db1cd23c | 96 | FwdState *fwdState; |
c4b7a5a9 | 97 | char replybuf[BUFSIZ]; |
62e76326 | 98 | } |
99 | ||
100 | GopherStateData; | |
56fa4cad | 101 | |
582b6456 | 102 | static PF gopherStateFree; |
eeb423fb | 103 | static void gopher_mime_content(MemBuf * mb, const char *name, const char *def); |
f5b8bbc4 | 104 | static void gopherMimeCreate(GopherStateData *); |
09fb5b61 | 105 | static void gopher_request_parse(const request_t * req, |
62e76326 | 106 | char *type_id, |
107 | char *request); | |
f5b8bbc4 | 108 | static void gopherEndHTML(GopherStateData *); |
109 | static void gopherToHTML(GopherStateData *, char *inbuf, int len); | |
5c5783a2 | 110 | static PF gopherTimeout; |
c4b7a5a9 | 111 | static IOCB gopherReadReply; |
13de3699 | 112 | static IOWCB gopherSendComplete; |
582b6456 | 113 | static PF gopherSendRequest; |
56fa4cad | 114 | |
115 | static char def_gopher_bin[] = "www/unknown"; | |
116 | static char def_gopher_text[] = "text/plain"; | |
090089c4 | 117 | |
582b6456 | 118 | static void |
79d39a72 | 119 | gopherStateFree(int fdnotused, void *data) |
ba718c8f | 120 | { |
e6ccf245 | 121 | GopherStateData *gopherState = (GopherStateData *)data; |
62e76326 | 122 | |
51fa90db | 123 | if (gopherState == NULL) |
62e76326 | 124 | return; |
125 | ||
bfcaf585 | 126 | if (gopherState->entry) { |
62e76326 | 127 | storeUnlockObject(gopherState->entry); |
bfcaf585 | 128 | } |
62e76326 | 129 | |
09fb5b61 | 130 | if (gopherState->req) { |
62e76326 | 131 | requestUnlink(gopherState->req); |
09fb5b61 | 132 | } |
62e76326 | 133 | |
db1cd23c | 134 | memFree(gopherState->buf, MEM_4K_BUF); |
7dd44885 | 135 | gopherState->buf = NULL; |
136 | cbdataFree(gopherState); | |
ba718c8f | 137 | } |
138 | ||
139 | ||
090089c4 | 140 | /* figure out content type from file extension */ |
b8d8561b | 141 | static void |
eeb423fb | 142 | gopher_mime_content(MemBuf * mb, const char *name, const char *def_ctype) |
090089c4 | 143 | { |
812ed90c | 144 | char *ctype = mimeGetContentType(name); |
145 | char *cenc = mimeGetContentEncoding(name); | |
62e76326 | 146 | |
812ed90c | 147 | if (cenc) |
62e76326 | 148 | memBufPrintf(mb, "Content-Encoding: %s\r\n", cenc); |
149 | ||
137ee196 | 150 | memBufPrintf(mb, "Content-Type: %s\r\n", |
62e76326 | 151 | ctype ? ctype : def_ctype); |
090089c4 | 152 | } |
153 | ||
154 | ||
155 | ||
156 | /* create MIME Header for Gopher Data */ | |
b8d8561b | 157 | static void |
582b6456 | 158 | gopherMimeCreate(GopherStateData * gopherState) |
090089c4 | 159 | { |
137ee196 | 160 | MemBuf mb; |
090089c4 | 161 | |
137ee196 | 162 | memBufDefInit(&mb); |
163 | ||
eeb423fb | 164 | memBufPrintf(&mb, |
62e76326 | 165 | "HTTP/1.0 200 OK Gatewaying\r\n" |
166 | "Server: Squid/%s\r\n" | |
167 | "Date: %s\r\n", | |
168 | version_string, mkrfc1123(squid_curtime)); | |
090089c4 | 169 | |
582b6456 | 170 | switch (gopherState->type_id) { |
090089c4 | 171 | |
172 | case GOPHER_DIRECTORY: | |
62e76326 | 173 | |
090089c4 | 174 | case GOPHER_INDEX: |
62e76326 | 175 | |
090089c4 | 176 | case GOPHER_HTML: |
62e76326 | 177 | |
090089c4 | 178 | case GOPHER_WWW: |
62e76326 | 179 | |
090089c4 | 180 | case GOPHER_CSO: |
62e76326 | 181 | memBufPrintf(&mb, "Content-Type: text/html\r\n"); |
182 | break; | |
183 | ||
090089c4 | 184 | case GOPHER_GIF: |
62e76326 | 185 | |
090089c4 | 186 | case GOPHER_IMAGE: |
62e76326 | 187 | |
090089c4 | 188 | case GOPHER_PLUS_IMAGE: |
62e76326 | 189 | memBufPrintf(&mb, "Content-Type: image/gif\r\n"); |
190 | break; | |
191 | ||
090089c4 | 192 | case GOPHER_SOUND: |
62e76326 | 193 | |
090089c4 | 194 | case GOPHER_PLUS_SOUND: |
62e76326 | 195 | memBufPrintf(&mb, "Content-Type: audio/basic\r\n"); |
196 | break; | |
197 | ||
090089c4 | 198 | case GOPHER_PLUS_MOVIE: |
62e76326 | 199 | memBufPrintf(&mb, "Content-Type: video/mpeg\r\n"); |
200 | break; | |
201 | ||
090089c4 | 202 | case GOPHER_MACBINHEX: |
62e76326 | 203 | |
090089c4 | 204 | case GOPHER_DOSBIN: |
62e76326 | 205 | |
090089c4 | 206 | case GOPHER_UUENCODED: |
62e76326 | 207 | |
090089c4 | 208 | case GOPHER_BIN: |
62e76326 | 209 | /* Rightnow We have no idea what it is. */ |
210 | gopher_mime_content(&mb, gopherState->request, def_gopher_bin); | |
211 | break; | |
212 | ||
090089c4 | 213 | case GOPHER_FILE: |
62e76326 | 214 | |
090089c4 | 215 | default: |
62e76326 | 216 | gopher_mime_content(&mb, gopherState->request, def_gopher_text); |
217 | break; | |
090089c4 | 218 | } |
62e76326 | 219 | |
137ee196 | 220 | memBufPrintf(&mb, "\r\n"); |
ec250dfd | 221 | EBIT_CLR(gopherState->entry->flags, ENTRY_FWD_HDR_WAIT); |
137ee196 | 222 | storeAppend(gopherState->entry, mb.buf, mb.size); |
223 | memBufClean(&mb); | |
090089c4 | 224 | } |
225 | ||
09fb5b61 | 226 | /* Parse a gopher request into components. By Anawat. */ |
227 | static void | |
228 | gopher_request_parse(const request_t * req, char *type_id, char *request) | |
090089c4 | 229 | { |
528b2c61 | 230 | const char *path = req->urlpath.buf(); |
09fb5b61 | 231 | |
232 | if (request) | |
62e76326 | 233 | request[0] = '\0'; |
09fb5b61 | 234 | |
235 | if (path && (*path == '/')) | |
62e76326 | 236 | path++; |
09fb5b61 | 237 | |
238 | if (!path || !*path) { | |
62e76326 | 239 | *type_id = GOPHER_DIRECTORY; |
240 | return; | |
09fb5b61 | 241 | } |
62e76326 | 242 | |
09fb5b61 | 243 | *type_id = path[0]; |
244 | ||
245 | if (request) { | |
62e76326 | 246 | xstrncpy(request, path + 1, MAX_URL); |
247 | /* convert %xx to char */ | |
248 | url_convert_hex(request, 0); | |
090089c4 | 249 | } |
090089c4 | 250 | } |
251 | ||
b8d8561b | 252 | int |
09fb5b61 | 253 | gopherCachable(const request_t * req) |
090089c4 | 254 | { |
090089c4 | 255 | int cachable = 1; |
09fb5b61 | 256 | char type_id; |
090089c4 | 257 | /* parse to see type */ |
09fb5b61 | 258 | gopher_request_parse(req, |
62e76326 | 259 | &type_id, |
260 | NULL); | |
261 | ||
09fb5b61 | 262 | switch (type_id) { |
62e76326 | 263 | |
090089c4 | 264 | case GOPHER_INDEX: |
62e76326 | 265 | |
090089c4 | 266 | case GOPHER_CSO: |
62e76326 | 267 | |
090089c4 | 268 | case GOPHER_TELNET: |
62e76326 | 269 | |
090089c4 | 270 | case GOPHER_3270: |
62e76326 | 271 | cachable = 0; |
272 | break; | |
273 | ||
090089c4 | 274 | default: |
62e76326 | 275 | cachable = 1; |
090089c4 | 276 | } |
62e76326 | 277 | |
090089c4 | 278 | return cachable; |
279 | } | |
280 | ||
eb7d6bd6 | 281 | static void |
282 | gopherHTMLHeader(StoreEntry * e, const char *title, const char *substring) | |
283 | { | |
df339671 | 284 | storeAppendPrintf(e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"); |
eb7d6bd6 | 285 | storeAppendPrintf(e, "<HTML><HEAD><TITLE>"); |
286 | storeAppendPrintf(e, title, substring); | |
df339671 | 287 | storeAppendPrintf(e, "</TITLE>"); |
d8c0128e | 288 | storeAppendPrintf(e, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n"); |
df339671 | 289 | storeAppendPrintf(e, "</HEAD>\n<BODY><H1>"); |
eb7d6bd6 | 290 | storeAppendPrintf(e, title, substring); |
291 | storeAppendPrintf(e, "</H1>\n"); | |
292 | } | |
293 | ||
294 | static void | |
295 | gopherHTMLFooter(StoreEntry * e) | |
296 | { | |
df339671 | 297 | storeAppendPrintf(e, "<HR noshade size=\"1px\">\n"); |
eb7d6bd6 | 298 | storeAppendPrintf(e, "<ADDRESS>\n"); |
299 | storeAppendPrintf(e, "Generated %s by %s (%s)\n", | |
62e76326 | 300 | mkrfc1123(squid_curtime), |
301 | getMyHostname(), | |
302 | full_appname_string); | |
eb7d6bd6 | 303 | storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n"); |
304 | } | |
305 | ||
b8d8561b | 306 | static void |
582b6456 | 307 | gopherEndHTML(GopherStateData * gopherState) |
090089c4 | 308 | { |
eb7d6bd6 | 309 | StoreEntry *e = gopherState->entry; |
62e76326 | 310 | |
eb7d6bd6 | 311 | if (!gopherState->data_in) { |
62e76326 | 312 | gopherHTMLHeader(e, "Server Return Nothing", NULL); |
313 | storeAppendPrintf(e, "<P>The Gopher query resulted in a blank response</P>"); | |
eb7d6bd6 | 314 | } else { |
62e76326 | 315 | storeAppendPrintf(e, "</PRE>\n"); |
eb7d6bd6 | 316 | } |
62e76326 | 317 | |
eb7d6bd6 | 318 | gopherHTMLFooter(e); |
090089c4 | 319 | } |
320 | ||
321 | ||
322 | /* Convert Gopher to HTML */ | |
323 | /* Borrow part of code from libwww2 came with Mosaic distribution */ | |
b8d8561b | 324 | static void |
582b6456 | 325 | gopherToHTML(GopherStateData * gopherState, char *inbuf, int len) |
090089c4 | 326 | { |
327 | char *pos = inbuf; | |
328 | char *lpos = NULL; | |
329 | char *tline = NULL; | |
95d659f0 | 330 | LOCAL_ARRAY(char, line, TEMP_BUF_SIZE); |
331 | LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE); | |
090089c4 | 332 | char *name = NULL; |
333 | char *selector = NULL; | |
334 | char *host = NULL; | |
335 | char *port = NULL; | |
336 | char *escaped_selector = NULL; | |
d20b1cd0 | 337 | const char *icon_url = NULL; |
090089c4 | 338 | char gtype; |
339 | StoreEntry *entry = NULL; | |
340 | ||
53d02438 | 341 | memset(tmpbuf, '\0', TEMP_BUF_SIZE); |
342 | memset(line, '\0', TEMP_BUF_SIZE); | |
090089c4 | 343 | |
582b6456 | 344 | entry = gopherState->entry; |
090089c4 | 345 | |
e6ccf245 | 346 | if (gopherState->conversion == gopher_ds::HTML_INDEX_PAGE) { |
62e76326 | 347 | char *html_url = html_quote(storeUrl(entry)); |
348 | gopherHTMLHeader(entry, "Gopher Index %s", html_url); | |
349 | storeAppendPrintf(entry, | |
350 | "<p>This is a searchable Gopher index. Use the search\n" | |
351 | "function of your browser to enter search terms.\n" | |
352 | "<ISINDEX>\n"); | |
353 | gopherHTMLFooter(entry); | |
354 | /* now let start sending stuff to client */ | |
355 | storeBufferFlush(entry); | |
356 | gopherState->data_in = 1; | |
357 | ||
358 | return; | |
090089c4 | 359 | } |
62e76326 | 360 | |
e6ccf245 | 361 | if (gopherState->conversion == gopher_ds::HTML_CSO_PAGE) { |
62e76326 | 362 | char *html_url = html_quote(storeUrl(entry)); |
363 | gopherHTMLHeader(entry, "CSO Search of %s", html_url); | |
364 | storeAppendPrintf(entry, | |
365 | "<P>A CSO database usually contains a phonebook or\n" | |
366 | "directory. Use the search function of your browser to enter\n" | |
367 | "search terms.</P><ISINDEX>\n"); | |
368 | gopherHTMLFooter(entry); | |
369 | /* now let start sending stuff to client */ | |
370 | storeBufferFlush(entry); | |
371 | gopherState->data_in = 1; | |
372 | ||
373 | return; | |
090089c4 | 374 | } |
62e76326 | 375 | |
090089c4 | 376 | inbuf[len] = '\0'; |
650c4b88 | 377 | String outbuf; |
090089c4 | 378 | |
582b6456 | 379 | if (!gopherState->HTML_header_added) { |
62e76326 | 380 | if (gopherState->conversion == gopher_ds::HTML_CSO_RESULT) |
381 | gopherHTMLHeader(entry, "CSO Search Result", NULL); | |
382 | else | |
383 | gopherHTMLHeader(entry, "Gopher Menu", NULL); | |
384 | ||
385 | outbuf.append ("<PRE>"); | |
386 | ||
387 | gopherState->HTML_header_added = 1; | |
090089c4 | 388 | } |
62e76326 | 389 | |
090089c4 | 390 | while ((pos != NULL) && (pos < inbuf + len)) { |
391 | ||
62e76326 | 392 | if (gopherState->len != 0) { |
393 | /* there is something left from last tx. */ | |
394 | xstrncpy(line, gopherState->buf, gopherState->len + 1); | |
395 | lpos = (char *) memccpy(line + gopherState->len, inbuf, '\n', len); | |
396 | ||
397 | if (lpos) | |
398 | *lpos = '\0'; | |
399 | else { | |
400 | /* there is no complete line in inbuf */ | |
401 | /* copy it to temp buffer */ | |
402 | ||
403 | if (gopherState->len + len > TEMP_BUF_SIZE) { | |
404 | debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n", | |
405 | storeUrl(entry)); | |
406 | len = TEMP_BUF_SIZE - gopherState->len; | |
407 | } | |
408 | ||
409 | xmemcpy(gopherState->buf + gopherState->len, inbuf, len); | |
410 | gopherState->len += len; | |
411 | return; | |
412 | } | |
413 | ||
414 | /* skip one line */ | |
415 | pos = (char *) memchr(pos, '\n', len); | |
416 | ||
417 | if (pos) | |
418 | pos++; | |
419 | ||
420 | /* we're done with the remain from last tx. */ | |
421 | gopherState->len = 0; | |
422 | ||
423 | *(gopherState->buf) = '\0'; | |
424 | } else { | |
425 | ||
426 | lpos = (char *) memccpy(line, pos, '\n', len - (pos - inbuf)); | |
427 | ||
428 | if (lpos) | |
429 | *lpos = '\0'; | |
430 | else { | |
431 | /* there is no complete line in inbuf */ | |
432 | /* copy it to temp buffer */ | |
433 | ||
434 | if ((len - (pos - inbuf)) > TEMP_BUF_SIZE) { | |
435 | debug(10, 1) ("GopherHTML: Buffer overflow. Lost some data on URL: %s\n", | |
436 | storeUrl(entry)); | |
437 | len = TEMP_BUF_SIZE; | |
438 | } | |
439 | ||
440 | if (len > (pos - inbuf)) { | |
441 | xmemcpy(gopherState->buf, pos, len - (pos - inbuf)); | |
442 | gopherState->len = len - (pos - inbuf); | |
443 | } | |
444 | ||
445 | break; | |
446 | } | |
447 | ||
448 | /* skip one line */ | |
449 | pos = (char *) memchr(pos, '\n', len); | |
450 | ||
451 | if (pos) | |
452 | pos++; | |
453 | ||
454 | } | |
455 | ||
456 | /* at this point. We should have one line in buffer to process */ | |
457 | ||
458 | if (*line == '.') { | |
459 | /* skip it */ | |
460 | memset(line, '\0', TEMP_BUF_SIZE); | |
461 | continue; | |
462 | } | |
463 | ||
464 | switch (gopherState->conversion) { | |
465 | ||
466 | case gopher_ds::HTML_INDEX_RESULT: | |
467 | ||
468 | case gopher_ds::HTML_DIR: { | |
469 | tline = line; | |
470 | gtype = *tline++; | |
471 | name = tline; | |
472 | selector = strchr(tline, TAB); | |
473 | ||
474 | if (selector) { | |
475 | *selector++ = '\0'; | |
476 | host = strchr(selector, TAB); | |
477 | ||
478 | if (host) { | |
479 | *host++ = '\0'; | |
480 | port = strchr(host, TAB); | |
481 | ||
482 | if (port) { | |
483 | char *junk; | |
484 | port[0] = ':'; | |
485 | junk = strchr(host, TAB); | |
486 | ||
487 | if (junk) | |
488 | *junk++ = 0; /* Chop port */ | |
489 | else { | |
490 | junk = strchr(host, '\r'); | |
491 | ||
492 | if (junk) | |
493 | *junk++ = 0; /* Chop port */ | |
494 | else { | |
495 | junk = strchr(host, '\n'); | |
496 | ||
497 | if (junk) | |
498 | *junk++ = 0; /* Chop port */ | |
499 | } | |
500 | } | |
501 | ||
502 | if ((port[1] == '0') && (!port[2])) | |
503 | port[0] = 0; /* 0 means none */ | |
504 | } | |
505 | ||
506 | /* escape a selector here */ | |
507 | escaped_selector = xstrdup(rfc1738_escape_part(selector)); | |
508 | ||
509 | switch (gtype) { | |
510 | ||
511 | case GOPHER_DIRECTORY: | |
512 | icon_url = mimeGetIconURL("internal-menu"); | |
513 | break; | |
514 | ||
515 | case GOPHER_HTML: | |
516 | ||
517 | case GOPHER_FILE: | |
518 | icon_url = mimeGetIconURL("internal-text"); | |
519 | break; | |
520 | ||
521 | case GOPHER_INDEX: | |
522 | ||
523 | case GOPHER_CSO: | |
524 | icon_url = mimeGetIconURL("internal-index"); | |
525 | break; | |
526 | ||
527 | case GOPHER_IMAGE: | |
528 | ||
529 | case GOPHER_GIF: | |
530 | ||
531 | case GOPHER_PLUS_IMAGE: | |
532 | icon_url = mimeGetIconURL("internal-image"); | |
533 | break; | |
534 | ||
535 | case GOPHER_SOUND: | |
536 | ||
537 | case GOPHER_PLUS_SOUND: | |
538 | icon_url = mimeGetIconURL("internal-sound"); | |
539 | break; | |
540 | ||
541 | case GOPHER_PLUS_MOVIE: | |
542 | icon_url = mimeGetIconURL("internal-movie"); | |
543 | break; | |
544 | ||
545 | case GOPHER_TELNET: | |
546 | ||
547 | case GOPHER_3270: | |
548 | icon_url = mimeGetIconURL("internal-telnet"); | |
549 | break; | |
550 | ||
551 | case GOPHER_BIN: | |
552 | ||
553 | case GOPHER_MACBINHEX: | |
554 | ||
555 | case GOPHER_DOSBIN: | |
556 | ||
557 | case GOPHER_UUENCODED: | |
558 | icon_url = mimeGetIconURL("internal-binary"); | |
559 | break; | |
560 | ||
561 | case GOPHER_INFO: | |
562 | icon_url = NULL; | |
563 | break; | |
564 | ||
565 | default: | |
566 | icon_url = mimeGetIconURL("internal-unknown"); | |
567 | break; | |
568 | } | |
569 | ||
570 | memset(tmpbuf, '\0', TEMP_BUF_SIZE); | |
571 | ||
572 | if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) { | |
573 | if (strlen(escaped_selector) != 0) | |
574 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n", | |
575 | icon_url, escaped_selector, rfc1738_escape_part(host), | |
576 | *port ? ":" : "", port, html_quote(name)); | |
577 | else | |
578 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n", | |
579 | icon_url, rfc1738_escape_part(host), *port ? ":" : "", | |
580 | port, html_quote(name)); | |
581 | ||
582 | } else if (gtype == GOPHER_INFO) { | |
583 | snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name)); | |
584 | } else { | |
585 | if (strncmp(selector, "GET /", 5) == 0) { | |
586 | /* WWW link */ | |
587 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n", | |
588 | icon_url, host, rfc1738_escape_unescaped(selector + 5), html_quote(name)); | |
589 | } else { | |
590 | /* Standard link */ | |
591 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n", | |
592 | icon_url, host, gtype, escaped_selector, html_quote(name)); | |
593 | } | |
594 | } | |
595 | ||
596 | safe_free(escaped_selector); | |
597 | outbuf.append(tmpbuf); | |
598 | gopherState->data_in = 1; | |
599 | } else { | |
600 | memset(line, '\0', TEMP_BUF_SIZE); | |
601 | continue; | |
602 | } | |
603 | } else { | |
604 | memset(line, '\0', TEMP_BUF_SIZE); | |
605 | continue; | |
606 | } | |
607 | ||
608 | break; | |
609 | } /* HTML_DIR, HTML_INDEX_RESULT */ | |
610 | ||
611 | ||
612 | case gopher_ds::HTML_CSO_RESULT: { | |
613 | if (line[0] == '-') { | |
614 | int code, recno; | |
615 | char *s_code, *s_recno, *result; | |
616 | ||
617 | s_code = strtok(line + 1, ":\n"); | |
618 | s_recno = strtok(NULL, ":\n"); | |
619 | result = strtok(NULL, "\n"); | |
620 | ||
621 | if (!result) | |
622 | break; | |
623 | ||
624 | code = atoi(s_code); | |
625 | ||
626 | recno = atoi(s_recno); | |
627 | ||
628 | if (code != 200) | |
629 | break; | |
630 | ||
631 | if (gopherState->cso_recno != recno) { | |
632 | snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result)); | |
633 | gopherState->cso_recno = recno; | |
634 | } else { | |
635 | snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result)); | |
636 | } | |
637 | ||
638 | outbuf.append(tmpbuf); | |
639 | gopherState->data_in = 1; | |
640 | break; | |
641 | } else { | |
642 | int code; | |
643 | char *s_code, *result; | |
644 | ||
645 | s_code = strtok(line, ":"); | |
646 | result = strtok(NULL, "\n"); | |
647 | ||
648 | if (!result) | |
649 | break; | |
650 | ||
651 | code = atoi(s_code); | |
652 | ||
653 | switch (code) { | |
654 | ||
655 | case 200: { | |
656 | /* OK */ | |
657 | /* Do nothing here */ | |
658 | break; | |
659 | } | |
660 | ||
661 | case 102: /* Number of matches */ | |
662 | ||
663 | case 501: /* No Match */ | |
664 | ||
665 | case 502: /* Too Many Matches */ | |
666 | { | |
667 | /* Print the message the server returns */ | |
668 | snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result)); | |
669 | outbuf.append(tmpbuf); | |
670 | gopherState->data_in = 1; | |
671 | break; | |
672 | } | |
673 | ||
674 | ||
675 | } | |
676 | } | |
677 | ||
678 | } /* HTML_CSO_RESULT */ | |
679 | ||
680 | default: | |
681 | break; /* do nothing */ | |
682 | ||
683 | } /* switch */ | |
090089c4 | 684 | |
685 | } /* while loop */ | |
686 | ||
528b2c61 | 687 | if (outbuf.size() > 0) { |
62e76326 | 688 | storeAppend(entry, outbuf.buf(), outbuf.size()); |
689 | /* now let start sending stuff to client */ | |
690 | storeBufferFlush(entry); | |
090089c4 | 691 | } |
62e76326 | 692 | |
528b2c61 | 693 | outbuf.clean(); |
090089c4 | 694 | return; |
695 | } | |
696 | ||
582b6456 | 697 | static void |
5c5783a2 | 698 | gopherTimeout(int fd, void *data) |
090089c4 | 699 | { |
e6ccf245 | 700 | GopherStateData *gopherState = (GopherStateData *)data; |
582b6456 | 701 | StoreEntry *entry = gopherState->entry; |
9fb13bb6 | 702 | debug(10, 4) ("gopherTimeout: FD %d: '%s'\n", fd, storeUrl(entry)); |
62e76326 | 703 | |
e3dd531e | 704 | if (entry->store_status == STORE_PENDING) { |
62e76326 | 705 | if (entry->isEmpty()) { |
706 | fwdFail(gopherState->fwdState, | |
707 | errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT)); | |
708 | } | |
e3dd531e | 709 | } |
62e76326 | 710 | |
51fa90db | 711 | comm_close(fd); |
090089c4 | 712 | } |
713 | ||
090089c4 | 714 | /* This will be called when data is ready to be read from fd. Read until |
715 | * error or connection closed. */ | |
b8d8561b | 716 | static void |
c4b7a5a9 | 717 | gopherReadReply(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, void *data) |
090089c4 | 718 | { |
e6ccf245 | 719 | GopherStateData *gopherState = (GopherStateData *)data; |
bfcaf585 | 720 | StoreEntry *entry = gopherState->entry; |
090089c4 | 721 | int clen; |
56fa4cad | 722 | int bin; |
c4b7a5a9 | 723 | size_t read_sz = BUFSIZ; |
724 | int do_next_read = 0; | |
447e176b | 725 | #if DELAY_POOLS |
62e76326 | 726 | |
b67e2c8c | 727 | DelayId delayId = entry->mem_obj->mostBytesAllowed(); |
447e176b | 728 | #endif |
c4b7a5a9 | 729 | |
730 | /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */ | |
62e76326 | 731 | |
c4b7a5a9 | 732 | if (flag == COMM_ERR_CLOSING) { |
733 | return; | |
734 | } | |
735 | ||
736 | assert(buf == gopherState->replybuf); | |
62e76326 | 737 | |
e92e4e44 | 738 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { |
62e76326 | 739 | comm_close(fd); |
740 | return; | |
e92e4e44 | 741 | } |
c4b7a5a9 | 742 | |
6fe6313d | 743 | errno = 0; |
447e176b | 744 | #if DELAY_POOLS |
62e76326 | 745 | |
b67e2c8c | 746 | read_sz = delayId.bytesWanted(1, read_sz); |
447e176b | 747 | #endif |
c4b7a5a9 | 748 | |
56fa4cad | 749 | /* leave one space for \0 in gopherToHTML */ |
62e76326 | 750 | |
c4b7a5a9 | 751 | if (flag == COMM_OK && len > 0) { |
447e176b | 752 | #if DELAY_POOLS |
62e76326 | 753 | delayId.bytesIn(len); |
447e176b | 754 | #endif |
62e76326 | 755 | |
756 | kb_incr(&statCounter.server.all.kbytes_in, len); | |
757 | kb_incr(&statCounter.server.other.kbytes_in, len); | |
ee1679df | 758 | } |
62e76326 | 759 | |
c4b7a5a9 | 760 | debug(10, 5) ("gopherReadReply: FD %d read len=%d\n", fd, (int)len); |
62e76326 | 761 | |
c4b7a5a9 | 762 | if (flag == COMM_OK && len > 0) { |
62e76326 | 763 | commSetTimeout(fd, Config.Timeout.read, NULL, NULL); |
764 | IOStats.Gopher.reads++; | |
765 | ||
766 | for (clen = len - 1, bin = 0; clen; bin++) | |
767 | clen >>= 1; | |
768 | ||
769 | IOStats.Gopher.read_hist[bin]++; | |
56fa4cad | 770 | } |
62e76326 | 771 | |
c4b7a5a9 | 772 | if (flag != COMM_OK || len < 0) { |
62e76326 | 773 | debug(50, 1) ("gopherReadReply: error reading: %s\n", xstrerror()); |
774 | ||
775 | if (ignoreErrno(errno)) { | |
c4b7a5a9 | 776 | do_next_read = 1; |
62e76326 | 777 | } else if (entry->isEmpty()) { |
778 | ErrorState *err; | |
779 | err = errorCon(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR); | |
780 | err->xerrno = errno; | |
781 | err->url = xstrdup(storeUrl(entry)); | |
782 | errorAppendEntry(entry, err); | |
783 | comm_close(fd); | |
c4b7a5a9 | 784 | do_next_read = 0; |
62e76326 | 785 | } else { |
786 | comm_close(fd); | |
c4b7a5a9 | 787 | do_next_read = 0; |
62e76326 | 788 | } |
528b2c61 | 789 | } else if (len == 0 && entry->isEmpty()) { |
62e76326 | 790 | ErrorState *err; |
791 | err = errorCon(ERR_ZERO_SIZE_OBJECT, HTTP_SERVICE_UNAVAILABLE); | |
792 | err->xerrno = errno; | |
793 | err->url = xstrdup(gopherState->request); | |
794 | errorAppendEntry(entry, err); | |
795 | comm_close(fd); | |
c4b7a5a9 | 796 | do_next_read = 0; |
090089c4 | 797 | } else if (len == 0) { |
62e76326 | 798 | /* Connection closed; retrieval done. */ |
799 | /* flush the rest of data in temp buf if there is one. */ | |
800 | ||
801 | if (gopherState->conversion != gopher_ds::NORMAL) | |
802 | gopherEndHTML(gopherState); | |
803 | ||
804 | storeTimestampsSet(entry); | |
805 | ||
806 | storeBufferFlush(entry); | |
807 | ||
808 | fwdComplete(gopherState->fwdState); | |
809 | ||
810 | comm_close(fd); | |
811 | ||
c4b7a5a9 | 812 | do_next_read = 0; |
090089c4 | 813 | } else { |
62e76326 | 814 | if (gopherState->conversion != gopher_ds::NORMAL) { |
815 | gopherToHTML(gopherState, buf, len); | |
816 | } else { | |
817 | storeAppend(entry, buf, len); | |
818 | } | |
819 | ||
c4b7a5a9 | 820 | do_next_read = 1; |
090089c4 | 821 | } |
62e76326 | 822 | |
c4b7a5a9 | 823 | if (do_next_read) |
824 | comm_read(fd, buf, read_sz, gopherReadReply, gopherState); | |
62e76326 | 825 | |
56fa4cad | 826 | return; |
090089c4 | 827 | } |
828 | ||
829 | /* This will be called when request write is complete. Schedule read of | |
830 | * reply. */ | |
b8d8561b | 831 | static void |
13de3699 | 832 | gopherSendComplete(int fd, char *buf, size_t size, comm_err_t errflag, int xerrno, void *data) |
090089c4 | 833 | { |
56fa4cad | 834 | GopherStateData *gopherState = (GopherStateData *) data; |
70a9dab4 | 835 | StoreEntry *entry = gopherState->entry; |
a3d5953d | 836 | debug(10, 5) ("gopherSendComplete: FD %d size: %d errflag: %d\n", |
62e76326 | 837 | fd, (int) size, errflag); |
838 | ||
ee1679df | 839 | if (size > 0) { |
62e76326 | 840 | fd_bytes(fd, size, FD_WRITE); |
841 | kb_incr(&statCounter.server.all.kbytes_out, size); | |
842 | kb_incr(&statCounter.server.other.kbytes_out, size); | |
ee1679df | 843 | } |
62e76326 | 844 | |
090089c4 | 845 | if (errflag) { |
62e76326 | 846 | ErrorState *err; |
847 | err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE); | |
848 | err->xerrno = errno; | |
849 | err->host = xstrdup(gopherState->req->host); | |
850 | err->port = gopherState->req->port; | |
851 | err->url = xstrdup(storeUrl(entry)); | |
852 | errorAppendEntry(entry, err); | |
853 | comm_close(fd); | |
854 | ||
855 | if (buf) | |
856 | memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */ | |
857 | ||
858 | return; | |
090089c4 | 859 | } |
62e76326 | 860 | |
861 | /* | |
090089c4 | 862 | * OK. We successfully reach remote site. Start MIME typing |
863 | * stuff. Do it anyway even though request is not HTML type. | |
864 | */ | |
30a4f2a8 | 865 | gopherMimeCreate(gopherState); |
62e76326 | 866 | |
a47b9029 | 867 | switch (gopherState->type_id) { |
62e76326 | 868 | |
a47b9029 | 869 | case GOPHER_DIRECTORY: |
62e76326 | 870 | /* we got to convert it first */ |
871 | storeBuffer(entry); | |
872 | gopherState->conversion = gopher_ds::HTML_DIR; | |
873 | gopherState->HTML_header_added = 0; | |
874 | break; | |
875 | ||
a47b9029 | 876 | case GOPHER_INDEX: |
62e76326 | 877 | /* we got to convert it first */ |
878 | storeBuffer(entry); | |
879 | gopherState->conversion = gopher_ds::HTML_INDEX_RESULT; | |
880 | gopherState->HTML_header_added = 0; | |
881 | break; | |
882 | ||
a47b9029 | 883 | case GOPHER_CSO: |
62e76326 | 884 | /* we got to convert it first */ |
885 | storeBuffer(entry); | |
886 | gopherState->conversion = gopher_ds::HTML_CSO_RESULT; | |
887 | gopherState->cso_recno = 0; | |
888 | gopherState->HTML_header_added = 0; | |
889 | break; | |
890 | ||
a47b9029 | 891 | default: |
62e76326 | 892 | gopherState->conversion = gopher_ds::NORMAL; |
86101e40 | 893 | } |
62e76326 | 894 | |
090089c4 | 895 | /* Schedule read reply. */ |
a46d2c0e | 896 | entry->delayAwareRead(fd, gopherState->replybuf, BUFSIZ, gopherReadReply, gopherState); |
62e76326 | 897 | |
090089c4 | 898 | if (buf) |
62e76326 | 899 | memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */ |
090089c4 | 900 | } |
901 | ||
902 | /* This will be called when connect completes. Write request. */ | |
b8d8561b | 903 | static void |
582b6456 | 904 | gopherSendRequest(int fd, void *data) |
090089c4 | 905 | { |
e6ccf245 | 906 | GopherStateData *gopherState = (GopherStateData *)data; |
907 | char *buf = (char *)memAllocate(MEM_4K_BUF); | |
62e76326 | 908 | |
582b6456 | 909 | if (gopherState->type_id == GOPHER_CSO) { |
62e76326 | 910 | const char *t = strchr(gopherState->request, '?'); |
911 | ||
912 | if (t != NULL) | |
913 | t++; /* skip the ? */ | |
914 | else | |
915 | t = ""; | |
916 | ||
917 | snprintf(buf, 4096, "query %s\r\nquit\r\n", t); | |
582b6456 | 918 | } else if (gopherState->type_id == GOPHER_INDEX) { |
62e76326 | 919 | char *t = strchr(gopherState->request, '?'); |
920 | ||
921 | if (t != NULL) | |
922 | *t = '\t'; | |
923 | ||
924 | snprintf(buf, 4096, "%s\r\n", gopherState->request); | |
090089c4 | 925 | } else { |
62e76326 | 926 | snprintf(buf, 4096, "%s\r\n", gopherState->request); |
090089c4 | 927 | } |
62e76326 | 928 | |
a3d5953d | 929 | debug(10, 5) ("gopherSendRequest: FD %d\n", fd); |
13de3699 | 930 | comm_write(fd, |
62e76326 | 931 | buf, |
932 | strlen(buf), | |
933 | gopherSendComplete, | |
934 | gopherState); | |
935 | ||
d46a87a8 | 936 | if (EBIT_TEST(gopherState->entry->flags, ENTRY_CACHABLE)) |
62e76326 | 937 | storeSetPublicKey(gopherState->entry); /* Make it public */ |
090089c4 | 938 | } |
939 | ||
09fb5b61 | 940 | CBDATA_TYPE(GopherStateData); |
941 | ||
770f051d | 942 | void |
db1cd23c | 943 | gopherStart(FwdState * fwdState) |
090089c4 | 944 | { |
db1cd23c | 945 | int fd = fwdState->server_fd; |
946 | StoreEntry *entry = fwdState->entry; | |
09fb5b61 | 947 | GopherStateData *gopherState; |
948 | CBDATA_INIT_TYPE(GopherStateData); | |
949 | gopherState = cbdataAlloc(GopherStateData); | |
e6ccf245 | 950 | gopherState->buf = (char *)memAllocate(MEM_4K_BUF); |
770f051d | 951 | storeLockObject(entry); |
582b6456 | 952 | gopherState->entry = entry; |
09fb5b61 | 953 | gopherState->fwdState = fwdState; |
9fb13bb6 | 954 | debug(10, 3) ("gopherStart: %s\n", storeUrl(entry)); |
83704487 | 955 | statCounter.server.all.requests++; |
956 | statCounter.server.other.requests++; | |
090089c4 | 957 | /* Parse url. */ |
09fb5b61 | 958 | gopher_request_parse(fwdState->request, |
62e76326 | 959 | &gopherState->type_id, gopherState->request); |
09fb5b61 | 960 | #if OLD_PARSE_ERROR_CODE |
62e76326 | 961 | |
09fb5b61 | 962 | if (...) { |
62e76326 | 963 | ErrorState *err; |
964 | err = errorCon(ERR_INVALID_URL, HTTP_BAD_REQUEST); | |
965 | err->url = xstrdup(storeUrl(entry)); | |
966 | errorAppendEntry(entry, err); | |
967 | gopherStateFree(-1, gopherState); | |
968 | return; | |
090089c4 | 969 | } |
62e76326 | 970 | |
09fb5b61 | 971 | #endif |
bfcaf585 | 972 | comm_add_close_handler(fd, gopherStateFree, gopherState); |
62e76326 | 973 | |
582b6456 | 974 | if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO)) |
62e76326 | 975 | && (strchr(gopherState->request, '?') == NULL)) { |
976 | /* Index URL without query word */ | |
977 | /* We have to generate search page back to client. No need for connection */ | |
978 | gopherMimeCreate(gopherState); | |
979 | ||
980 | if (gopherState->type_id == GOPHER_INDEX) { | |
981 | gopherState->conversion = gopher_ds::HTML_INDEX_PAGE; | |
982 | } else { | |
983 | if (gopherState->type_id == GOPHER_CSO) { | |
984 | gopherState->conversion = gopher_ds::HTML_CSO_PAGE; | |
985 | } else { | |
986 | gopherState->conversion = gopher_ds::HTML_INDEX_PAGE; | |
987 | } | |
988 | } | |
989 | ||
990 | gopherToHTML(gopherState, (char *) NULL, 0); | |
991 | fwdComplete(fwdState); | |
992 | comm_close(fd); | |
993 | return; | |
090089c4 | 994 | } |
62e76326 | 995 | |
bfcaf585 | 996 | gopherState->fd = fd; |
db1cd23c | 997 | gopherState->fwdState = fwdState; |
c4b7a5a9 | 998 | gopherSendRequest(fd, gopherState); |
41462d93 | 999 | commSetTimeout(fd, Config.Timeout.read, gopherTimeout, gopherState); |
e5f6c5c2 | 1000 | } |