]>
Commit | Line | Data |
---|---|---|
30a4f2a8 | 1 | /* |
4ac4a490 | 2 | * Copyright (C) 1996-2017 The Squid Software Foundation and contributors |
30a4f2a8 | 3 | * |
bbc27441 AJ |
4 | * Squid software is distributed under GPLv2+ license and includes |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
019dd986 | 7 | */ |
44a47c6e | 8 | |
bbc27441 AJ |
9 | /* DEBUG: section 10 Gopher */ |
10 | ||
582c2af2 FC |
11 | #include "squid.h" |
12 | #include "comm.h" | |
7e66d5e2 | 13 | #include "comm/Read.h" |
ec41b64c | 14 | #include "comm/Write.h" |
aa839030 | 15 | #include "errorpage.h" |
c4ad1349 | 16 | #include "fd.h" |
eb13c21e | 17 | #include "FwdState.h" |
67679543 | 18 | #include "globals.h" |
25f98340 | 19 | #include "html_quote.h" |
9969d2a8 | 20 | #include "HttpReply.h" |
582c2af2 FC |
21 | #include "HttpRequest.h" |
22 | #include "MemBuf.h" | |
b65ce00c | 23 | #include "mime.h" |
51b5dcf5 | 24 | #include "parser/Tokenizer.h" |
582c2af2 | 25 | #include "rfc1738.h" |
4d5904f7 | 26 | #include "SquidConfig.h" |
582c2af2 FC |
27 | #include "SquidTime.h" |
28 | #include "StatCounters.h" | |
29 | #include "Store.h" | |
4e540555 | 30 | #include "tools.h" |
582c2af2 | 31 | |
9a0a18de | 32 | #if USE_DELAY_POOLS |
b67e2c8c | 33 | #include "DelayPools.h" |
86a2f789 | 34 | #include "MemObject.h" |
b67e2c8c | 35 | #endif |
090089c4 | 36 | |
090089c4 | 37 | /* gopher type code from rfc. Anawat. */ |
38 | #define GOPHER_FILE '0' | |
39 | #define GOPHER_DIRECTORY '1' | |
40 | #define GOPHER_CSO '2' | |
41 | #define GOPHER_ERROR '3' | |
42 | #define GOPHER_MACBINHEX '4' | |
43 | #define GOPHER_DOSBIN '5' | |
44 | #define GOPHER_UUENCODED '6' | |
45 | #define GOPHER_INDEX '7' | |
46 | #define GOPHER_TELNET '8' | |
47 | #define GOPHER_BIN '9' | |
48 | #define GOPHER_REDUNT '+' | |
49 | #define GOPHER_3270 'T' | |
50 | #define GOPHER_GIF 'g' | |
51 | #define GOPHER_IMAGE 'I' | |
52 | ||
2327e7c0 | 53 | #define GOPHER_HTML 'h' |
090089c4 | 54 | #define GOPHER_INFO 'i' |
2327e7c0 AJ |
55 | |
56 | /// W3 address | |
63be0a78 | 57 | #define GOPHER_WWW 'w' |
090089c4 | 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' | |
2327e7c0 AJ |
67 | |
68 | // TODO CODE: should this be a protocol-specific thing? | |
447e176b | 69 | #define TEMP_BUF_SIZE 4096 |
2327e7c0 | 70 | |
090089c4 | 71 | #define MAX_CSO_RESULT 1024 |
72 | ||
2327e7c0 AJ |
73 | /** |
74 | * Gopher Gateway Internals | |
75 | * | |
76 | * Gopher is somewhat complex and gross because it must convert from | |
77 | * the Gopher protocol to HTTP. | |
78 | */ | |
79 | class GopherStateData | |
80 | { | |
5c2f68b7 AJ |
81 | CBDATA_CLASS(GopherStateData); |
82 | ||
2327e7c0 AJ |
83 | public: |
84 | GopherStateData(FwdState *aFwd) : | |
85 | entry(aFwd->entry), | |
86 | conversion(NORMAL), | |
87 | HTML_header_added(0), | |
88 | HTML_pre(0), | |
89 | type_id(GOPHER_FILE /* '0' */), | |
90 | cso_recno(0), | |
91 | len(0), | |
92 | buf(NULL), | |
93 | fwd(aFwd) | |
94 | { | |
95 | *request = 0; | |
96 | buf = (char *)memAllocate(MEM_4K_BUF); | |
97 | entry->lock("gopherState"); | |
98 | *replybuf = 0; | |
99 | } | |
58d27a90 | 100 | ~GopherStateData() {if(buf) swanSong();} |
2327e7c0 AJ |
101 | |
102 | /* AsyncJob API emulated */ | |
103 | void deleteThis(const char *aReason); | |
104 | void swanSong(); | |
105 | ||
106 | public: | |
090089c4 | 107 | StoreEntry *entry; |
090089c4 | 108 | enum { |
62e76326 | 109 | NORMAL, |
110 | HTML_DIR, | |
111 | HTML_INDEX_RESULT, | |
112 | HTML_CSO_RESULT, | |
113 | HTML_INDEX_PAGE, | |
114 | HTML_CSO_PAGE | |
090089c4 | 115 | } conversion; |
116 | int HTML_header_added; | |
d7443679 | 117 | int HTML_pre; |
090089c4 | 118 | char type_id; |
f2052513 | 119 | char request[MAX_URL]; |
090089c4 | 120 | int cso_recno; |
121 | int len; | |
f53969cc | 122 | char *buf; /* pts to a 4k page */ |
e0d28505 | 123 | Comm::ConnectionPointer serverConn; |
b6b6f466 | 124 | FwdState::Pointer fwd; |
88df846b | 125 | HttpReply::Pointer reply_; |
c4b7a5a9 | 126 | char replybuf[BUFSIZ]; |
2327e7c0 AJ |
127 | }; |
128 | ||
129 | CBDATA_CLASS_INIT(GopherStateData); | |
56fa4cad | 130 | |
575d05c4 | 131 | static CLCB gopherStateFree; |
f5b8bbc4 | 132 | static void gopherMimeCreate(GopherStateData *); |
190154cf | 133 | static void gopher_request_parse(const HttpRequest * req, |
62e76326 | 134 | char *type_id, |
135 | char *request); | |
f5b8bbc4 | 136 | static void gopherEndHTML(GopherStateData *); |
137 | static void gopherToHTML(GopherStateData *, char *inbuf, int len); | |
8d77a37c | 138 | static CTCB gopherTimeout; |
c4b7a5a9 | 139 | static IOCB gopherReadReply; |
2b663917 | 140 | static IOCB gopherSendComplete; |
582b6456 | 141 | static PF gopherSendRequest; |
56fa4cad | 142 | |
143 | static char def_gopher_bin[] = "www/unknown"; | |
63be0a78 | 144 | |
56fa4cad | 145 | static char def_gopher_text[] = "text/plain"; |
090089c4 | 146 | |
582b6456 | 147 | static void |
575d05c4 | 148 | gopherStateFree(const CommCloseCbParams ¶ms) |
ba718c8f | 149 | { |
575d05c4 | 150 | GopherStateData *gopherState = (GopherStateData *)params.data; |
62e76326 | 151 | |
51fa90db | 152 | if (gopherState == NULL) |
62e76326 | 153 | return; |
154 | ||
2327e7c0 AJ |
155 | gopherState->deleteThis("gopherStateFree"); |
156 | } | |
62e76326 | 157 | |
2327e7c0 | 158 | void |
ced8def3 | 159 | GopherStateData::deleteThis(const char *) |
2327e7c0 AJ |
160 | { |
161 | swanSong(); | |
162 | delete this; | |
163 | } | |
62e76326 | 164 | |
2327e7c0 AJ |
165 | void |
166 | GopherStateData::swanSong() | |
167 | { | |
168 | if (entry) | |
169 | entry->unlock("gopherState"); | |
b6b6f466 | 170 | |
58d27a90 | 171 | if (buf) { |
2327e7c0 | 172 | memFree(buf, MEM_4K_BUF); |
58d27a90 AJ |
173 | buf = nullptr; |
174 | } | |
ba718c8f | 175 | } |
176 | ||
63be0a78 | 177 | /** |
63be0a78 | 178 | * Create MIME Header for Gopher Data |
179 | */ | |
b8d8561b | 180 | static void |
582b6456 | 181 | gopherMimeCreate(GopherStateData * gopherState) |
090089c4 | 182 | { |
9969d2a8 AJ |
183 | StoreEntry *entry = gopherState->entry; |
184 | const char *mime_type = NULL; | |
185 | const char *mime_enc = NULL; | |
090089c4 | 186 | |
582b6456 | 187 | switch (gopherState->type_id) { |
090089c4 | 188 | |
189 | case GOPHER_DIRECTORY: | |
62e76326 | 190 | |
090089c4 | 191 | case GOPHER_INDEX: |
62e76326 | 192 | |
090089c4 | 193 | case GOPHER_HTML: |
62e76326 | 194 | |
090089c4 | 195 | case GOPHER_WWW: |
62e76326 | 196 | |
090089c4 | 197 | case GOPHER_CSO: |
9969d2a8 | 198 | mime_type = "text/html"; |
62e76326 | 199 | break; |
200 | ||
090089c4 | 201 | case GOPHER_GIF: |
62e76326 | 202 | |
090089c4 | 203 | case GOPHER_IMAGE: |
62e76326 | 204 | |
090089c4 | 205 | case GOPHER_PLUS_IMAGE: |
9969d2a8 | 206 | mime_type = "image/gif"; |
62e76326 | 207 | break; |
208 | ||
090089c4 | 209 | case GOPHER_SOUND: |
62e76326 | 210 | |
090089c4 | 211 | case GOPHER_PLUS_SOUND: |
9969d2a8 | 212 | mime_type = "audio/basic"; |
62e76326 | 213 | break; |
214 | ||
090089c4 | 215 | case GOPHER_PLUS_MOVIE: |
9969d2a8 | 216 | mime_type = "video/mpeg"; |
62e76326 | 217 | break; |
218 | ||
090089c4 | 219 | case GOPHER_MACBINHEX: |
62e76326 | 220 | |
090089c4 | 221 | case GOPHER_DOSBIN: |
62e76326 | 222 | |
090089c4 | 223 | case GOPHER_UUENCODED: |
62e76326 | 224 | |
090089c4 | 225 | case GOPHER_BIN: |
62e76326 | 226 | /* Rightnow We have no idea what it is. */ |
9969d2a8 AJ |
227 | mime_enc = mimeGetContentEncoding(gopherState->request); |
228 | mime_type = mimeGetContentType(gopherState->request); | |
229 | if (!mime_type) | |
230 | mime_type = def_gopher_bin; | |
62e76326 | 231 | break; |
232 | ||
090089c4 | 233 | case GOPHER_FILE: |
62e76326 | 234 | |
090089c4 | 235 | default: |
9969d2a8 AJ |
236 | mime_enc = mimeGetContentEncoding(gopherState->request); |
237 | mime_type = mimeGetContentType(gopherState->request); | |
238 | if (!mime_type) | |
239 | mime_type = def_gopher_text; | |
62e76326 | 240 | break; |
090089c4 | 241 | } |
62e76326 | 242 | |
9969d2a8 AJ |
243 | assert(entry->isEmpty()); |
244 | EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT); | |
245 | ||
246 | HttpReply *reply = new HttpReply; | |
247 | entry->buffer(); | |
955394ce | 248 | reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, -1, -1, -2); |
9969d2a8 | 249 | if (mime_enc) |
789217a2 | 250 | reply->header.putStr(Http::HdrType::CONTENT_ENCODING, mime_enc); |
9969d2a8 AJ |
251 | |
252 | entry->replaceHttpReply(reply); | |
88df846b | 253 | gopherState->reply_ = reply; |
090089c4 | 254 | } |
255 | ||
63be0a78 | 256 | /** |
63be0a78 | 257 | * Parse a gopher request into components. By Anawat. |
258 | */ | |
09fb5b61 | 259 | static void |
190154cf | 260 | gopher_request_parse(const HttpRequest * req, char *type_id, char *request) |
090089c4 | 261 | { |
51b5dcf5 | 262 | ::Parser::Tokenizer tok(req->url.path()); |
09fb5b61 | 263 | |
264 | if (request) | |
51b5dcf5 | 265 | *request = 0; |
09fb5b61 | 266 | |
51b5dcf5 | 267 | tok.skip('/'); // ignore failures? path could be ab-empty |
09fb5b61 | 268 | |
51b5dcf5 | 269 | if (tok.atEnd()) { |
62e76326 | 270 | *type_id = GOPHER_DIRECTORY; |
271 | return; | |
09fb5b61 | 272 | } |
62e76326 | 273 | |
51b5dcf5 AJ |
274 | static const CharacterSet anyByte("UTF-8",0x00, 0xFF); |
275 | ||
276 | SBuf typeId; | |
277 | (void)tok.prefix(typeId, anyByte, 1); // never fails since !atEnd() | |
278 | *type_id = typeId[0]; | |
09fb5b61 | 279 | |
280 | if (request) { | |
3f0e38d6 | 281 | SBufToCstring(request, tok.remaining().substr(0, MAX_URL-1)); |
62e76326 | 282 | /* convert %xx to char */ |
2c5d57e0 | 283 | rfc1738_unescape(request); |
090089c4 | 284 | } |
090089c4 | 285 | } |
286 | ||
63be0a78 | 287 | /** |
63be0a78 | 288 | * Parse the request to determine whether it is cachable. |
289 | * | |
f53969cc SM |
290 | * \param req Request data. |
291 | * \retval 0 Not cachable. | |
292 | * \retval 1 Cachable. | |
63be0a78 | 293 | */ |
b8d8561b | 294 | int |
190154cf | 295 | gopherCachable(const HttpRequest * req) |
090089c4 | 296 | { |
090089c4 | 297 | int cachable = 1; |
09fb5b61 | 298 | char type_id; |
090089c4 | 299 | /* parse to see type */ |
09fb5b61 | 300 | gopher_request_parse(req, |
62e76326 | 301 | &type_id, |
302 | NULL); | |
303 | ||
09fb5b61 | 304 | switch (type_id) { |
62e76326 | 305 | |
090089c4 | 306 | case GOPHER_INDEX: |
62e76326 | 307 | |
090089c4 | 308 | case GOPHER_CSO: |
62e76326 | 309 | |
090089c4 | 310 | case GOPHER_TELNET: |
62e76326 | 311 | |
090089c4 | 312 | case GOPHER_3270: |
62e76326 | 313 | cachable = 0; |
314 | break; | |
315 | ||
090089c4 | 316 | default: |
62e76326 | 317 | cachable = 1; |
090089c4 | 318 | } |
62e76326 | 319 | |
090089c4 | 320 | return cachable; |
321 | } | |
322 | ||
eb7d6bd6 | 323 | static void |
324 | gopherHTMLHeader(StoreEntry * e, const char *title, const char *substring) | |
325 | { | |
df339671 | 326 | storeAppendPrintf(e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"); |
eb7d6bd6 | 327 | storeAppendPrintf(e, "<HTML><HEAD><TITLE>"); |
328 | storeAppendPrintf(e, title, substring); | |
df339671 | 329 | storeAppendPrintf(e, "</TITLE>"); |
d8c0128e | 330 | storeAppendPrintf(e, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n"); |
df339671 | 331 | storeAppendPrintf(e, "</HEAD>\n<BODY><H1>"); |
eb7d6bd6 | 332 | storeAppendPrintf(e, title, substring); |
333 | storeAppendPrintf(e, "</H1>\n"); | |
334 | } | |
335 | ||
336 | static void | |
337 | gopherHTMLFooter(StoreEntry * e) | |
338 | { | |
df339671 | 339 | storeAppendPrintf(e, "<HR noshade size=\"1px\">\n"); |
eb7d6bd6 | 340 | storeAppendPrintf(e, "<ADDRESS>\n"); |
341 | storeAppendPrintf(e, "Generated %s by %s (%s)\n", | |
62e76326 | 342 | mkrfc1123(squid_curtime), |
343 | getMyHostname(), | |
d3caee79 | 344 | visible_appname_string); |
eb7d6bd6 | 345 | storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n"); |
346 | } | |
347 | ||
b8d8561b | 348 | static void |
582b6456 | 349 | gopherEndHTML(GopherStateData * gopherState) |
090089c4 | 350 | { |
eb7d6bd6 | 351 | StoreEntry *e = gopherState->entry; |
62e76326 | 352 | |
d7443679 | 353 | if (!gopherState->HTML_header_added) { |
62e76326 | 354 | gopherHTMLHeader(e, "Server Return Nothing", NULL); |
355 | storeAppendPrintf(e, "<P>The Gopher query resulted in a blank response</P>"); | |
d7443679 | 356 | } else if (gopherState->HTML_pre) { |
62e76326 | 357 | storeAppendPrintf(e, "</PRE>\n"); |
eb7d6bd6 | 358 | } |
62e76326 | 359 | |
eb7d6bd6 | 360 | gopherHTMLFooter(e); |
090089c4 | 361 | } |
362 | ||
63be0a78 | 363 | /** |
63be0a78 | 364 | * Convert Gopher to HTML. |
2327e7c0 | 365 | * |
63be0a78 | 366 | * Borrow part of code from libwww2 came with Mosaic distribution. |
367 | */ | |
b8d8561b | 368 | static void |
582b6456 | 369 | gopherToHTML(GopherStateData * gopherState, char *inbuf, int len) |
090089c4 | 370 | { |
371 | char *pos = inbuf; | |
372 | char *lpos = NULL; | |
373 | char *tline = NULL; | |
95d659f0 | 374 | LOCAL_ARRAY(char, line, TEMP_BUF_SIZE); |
375 | LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE); | |
090089c4 | 376 | char *name = NULL; |
377 | char *selector = NULL; | |
378 | char *host = NULL; | |
379 | char *port = NULL; | |
380 | char *escaped_selector = NULL; | |
d20b1cd0 | 381 | const char *icon_url = NULL; |
090089c4 | 382 | char gtype; |
383 | StoreEntry *entry = NULL; | |
384 | ||
53d02438 | 385 | memset(tmpbuf, '\0', TEMP_BUF_SIZE); |
386 | memset(line, '\0', TEMP_BUF_SIZE); | |
090089c4 | 387 | |
582b6456 | 388 | entry = gopherState->entry; |
090089c4 | 389 | |
2327e7c0 | 390 | if (gopherState->conversion == GopherStateData::HTML_INDEX_PAGE) { |
3900307b | 391 | char *html_url = html_quote(entry->url()); |
62e76326 | 392 | gopherHTMLHeader(entry, "Gopher Index %s", html_url); |
393 | storeAppendPrintf(entry, | |
394 | "<p>This is a searchable Gopher index. Use the search\n" | |
395 | "function of your browser to enter search terms.\n" | |
396 | "<ISINDEX>\n"); | |
397 | gopherHTMLFooter(entry); | |
398 | /* now let start sending stuff to client */ | |
3900307b | 399 | entry->flush(); |
d7443679 | 400 | gopherState->HTML_header_added = 1; |
62e76326 | 401 | |
402 | return; | |
090089c4 | 403 | } |
62e76326 | 404 | |
2327e7c0 | 405 | if (gopherState->conversion == GopherStateData::HTML_CSO_PAGE) { |
3900307b | 406 | char *html_url = html_quote(entry->url()); |
62e76326 | 407 | gopherHTMLHeader(entry, "CSO Search of %s", html_url); |
408 | storeAppendPrintf(entry, | |
409 | "<P>A CSO database usually contains a phonebook or\n" | |
410 | "directory. Use the search function of your browser to enter\n" | |
411 | "search terms.</P><ISINDEX>\n"); | |
412 | gopherHTMLFooter(entry); | |
413 | /* now let start sending stuff to client */ | |
3900307b | 414 | entry->flush(); |
d7443679 | 415 | gopherState->HTML_header_added = 1; |
62e76326 | 416 | |
417 | return; | |
090089c4 | 418 | } |
62e76326 | 419 | |
30abd221 | 420 | String outbuf; |
090089c4 | 421 | |
582b6456 | 422 | if (!gopherState->HTML_header_added) { |
2327e7c0 | 423 | if (gopherState->conversion == GopherStateData::HTML_CSO_RESULT) |
62e76326 | 424 | gopherHTMLHeader(entry, "CSO Search Result", NULL); |
425 | else | |
426 | gopherHTMLHeader(entry, "Gopher Menu", NULL); | |
427 | ||
428 | outbuf.append ("<PRE>"); | |
429 | ||
430 | gopherState->HTML_header_added = 1; | |
d7443679 | 431 | |
432 | gopherState->HTML_pre = 1; | |
090089c4 | 433 | } |
62e76326 | 434 | |
7592589a HN |
435 | while (pos < inbuf + len) { |
436 | int llen; | |
437 | int left = len - (pos - inbuf); | |
438 | lpos = (char *)memchr(pos, '\n', left); | |
439 | if (lpos) { | |
95dc7ff4 | 440 | ++lpos; /* Next line is after \n */ |
7592589a HN |
441 | llen = lpos - pos; |
442 | } else { | |
443 | llen = left; | |
444 | } | |
445 | if (gopherState->len + llen >= TEMP_BUF_SIZE) { | |
e0236918 | 446 | debugs(10, DBG_IMPORTANT, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry->url() ); |
7592589a HN |
447 | llen = TEMP_BUF_SIZE - gopherState->len - 1; |
448 | } | |
449 | if (!lpos) { | |
450 | /* there is no complete line in inbuf */ | |
451 | /* copy it to temp buffer */ | |
452 | /* note: llen is adjusted above */ | |
453 | memcpy(gopherState->buf + gopherState->len, pos, llen); | |
454 | gopherState->len += llen; | |
455 | break; | |
456 | } | |
62e76326 | 457 | if (gopherState->len != 0) { |
458 | /* there is something left from last tx. */ | |
7592589a HN |
459 | memcpy(line, gopherState->buf, gopherState->len); |
460 | memcpy(line + gopherState->len, pos, llen); | |
461 | llen += gopherState->len; | |
62e76326 | 462 | gopherState->len = 0; |
62e76326 | 463 | } else { |
7592589a | 464 | memcpy(line, pos, llen); |
62e76326 | 465 | } |
7592589a HN |
466 | line[llen + 1] = '\0'; |
467 | /* move input to next line */ | |
468 | pos = lpos; | |
62e76326 | 469 | |
470 | /* at this point. We should have one line in buffer to process */ | |
471 | ||
472 | if (*line == '.') { | |
473 | /* skip it */ | |
474 | memset(line, '\0', TEMP_BUF_SIZE); | |
475 | continue; | |
476 | } | |
477 | ||
478 | switch (gopherState->conversion) { | |
479 | ||
2327e7c0 | 480 | case GopherStateData::HTML_INDEX_RESULT: |
62e76326 | 481 | |
2327e7c0 | 482 | case GopherStateData::HTML_DIR: { |
26ac0430 | 483 | tline = line; |
aec55359 FC |
484 | gtype = *tline; |
485 | ++tline; | |
26ac0430 AJ |
486 | name = tline; |
487 | selector = strchr(tline, TAB); | |
488 | ||
489 | if (selector) { | |
a38ec4b1 FC |
490 | *selector = '\0'; |
491 | ++selector; | |
26ac0430 | 492 | host = strchr(selector, TAB); |
62e76326 | 493 | |
26ac0430 | 494 | if (host) { |
a38ec4b1 FC |
495 | *host = '\0'; |
496 | ++host; | |
26ac0430 | 497 | port = strchr(host, TAB); |
62e76326 | 498 | |
26ac0430 AJ |
499 | if (port) { |
500 | char *junk; | |
501 | port[0] = ':'; | |
502 | junk = strchr(host, TAB); | |
62e76326 | 503 | |
26ac0430 | 504 | if (junk) |
f53969cc | 505 | *junk++ = 0; /* Chop port */ |
26ac0430 AJ |
506 | else { |
507 | junk = strchr(host, '\r'); | |
62e76326 | 508 | |
509 | if (junk) | |
f53969cc | 510 | *junk++ = 0; /* Chop port */ |
62e76326 | 511 | else { |
26ac0430 | 512 | junk = strchr(host, '\n'); |
62e76326 | 513 | |
514 | if (junk) | |
f53969cc | 515 | *junk++ = 0; /* Chop port */ |
62e76326 | 516 | } |
62e76326 | 517 | } |
518 | ||
26ac0430 | 519 | if ((port[1] == '0') && (!port[2])) |
f53969cc | 520 | port[0] = 0; /* 0 means none */ |
26ac0430 | 521 | } |
62e76326 | 522 | |
26ac0430 AJ |
523 | /* escape a selector here */ |
524 | escaped_selector = xstrdup(rfc1738_escape_part(selector)); | |
62e76326 | 525 | |
26ac0430 | 526 | switch (gtype) { |
62e76326 | 527 | |
26ac0430 AJ |
528 | case GOPHER_DIRECTORY: |
529 | icon_url = mimeGetIconURL("internal-menu"); | |
530 | break; | |
531 | ||
532 | case GOPHER_HTML: | |
62e76326 | 533 | |
26ac0430 AJ |
534 | case GOPHER_FILE: |
535 | icon_url = mimeGetIconURL("internal-text"); | |
536 | break; | |
62e76326 | 537 | |
26ac0430 | 538 | case GOPHER_INDEX: |
62e76326 | 539 | |
26ac0430 AJ |
540 | case GOPHER_CSO: |
541 | icon_url = mimeGetIconURL("internal-index"); | |
542 | break; | |
62e76326 | 543 | |
26ac0430 | 544 | case GOPHER_IMAGE: |
62e76326 | 545 | |
26ac0430 | 546 | case GOPHER_GIF: |
62e76326 | 547 | |
26ac0430 AJ |
548 | case GOPHER_PLUS_IMAGE: |
549 | icon_url = mimeGetIconURL("internal-image"); | |
550 | break; | |
62e76326 | 551 | |
26ac0430 | 552 | case GOPHER_SOUND: |
62e76326 | 553 | |
26ac0430 AJ |
554 | case GOPHER_PLUS_SOUND: |
555 | icon_url = mimeGetIconURL("internal-sound"); | |
556 | break; | |
62e76326 | 557 | |
26ac0430 AJ |
558 | case GOPHER_PLUS_MOVIE: |
559 | icon_url = mimeGetIconURL("internal-movie"); | |
560 | break; | |
62e76326 | 561 | |
26ac0430 | 562 | case GOPHER_TELNET: |
62e76326 | 563 | |
26ac0430 AJ |
564 | case GOPHER_3270: |
565 | icon_url = mimeGetIconURL("internal-telnet"); | |
566 | break; | |
62e76326 | 567 | |
26ac0430 | 568 | case GOPHER_BIN: |
62e76326 | 569 | |
26ac0430 | 570 | case GOPHER_MACBINHEX: |
62e76326 | 571 | |
26ac0430 | 572 | case GOPHER_DOSBIN: |
62e76326 | 573 | |
26ac0430 AJ |
574 | case GOPHER_UUENCODED: |
575 | icon_url = mimeGetIconURL("internal-binary"); | |
576 | break; | |
62e76326 | 577 | |
26ac0430 AJ |
578 | case GOPHER_INFO: |
579 | icon_url = NULL; | |
580 | break; | |
62e76326 | 581 | |
26ac0430 AJ |
582 | default: |
583 | icon_url = mimeGetIconURL("internal-unknown"); | |
584 | break; | |
585 | } | |
62e76326 | 586 | |
26ac0430 | 587 | memset(tmpbuf, '\0', TEMP_BUF_SIZE); |
62e76326 | 588 | |
26ac0430 AJ |
589 | if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) { |
590 | if (strlen(escaped_selector) != 0) | |
591 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n", | |
592 | icon_url, escaped_selector, rfc1738_escape_part(host), | |
593 | *port ? ":" : "", port, html_quote(name)); | |
594 | else | |
595 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n", | |
596 | icon_url, rfc1738_escape_part(host), *port ? ":" : "", | |
597 | port, html_quote(name)); | |
62e76326 | 598 | |
26ac0430 AJ |
599 | } else if (gtype == GOPHER_INFO) { |
600 | snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name)); | |
601 | } else { | |
602 | if (strncmp(selector, "GET /", 5) == 0) { | |
603 | /* WWW link */ | |
604 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n", | |
605 | icon_url, host, rfc1738_escape_unescaped(selector + 5), html_quote(name)); | |
62e76326 | 606 | } else { |
26ac0430 AJ |
607 | /* Standard link */ |
608 | snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n", | |
609 | icon_url, host, gtype, escaped_selector, html_quote(name)); | |
62e76326 | 610 | } |
62e76326 | 611 | } |
26ac0430 AJ |
612 | |
613 | safe_free(escaped_selector); | |
614 | outbuf.append(tmpbuf); | |
62e76326 | 615 | } else { |
616 | memset(line, '\0', TEMP_BUF_SIZE); | |
617 | continue; | |
618 | } | |
26ac0430 AJ |
619 | } else { |
620 | memset(line, '\0', TEMP_BUF_SIZE); | |
621 | continue; | |
622 | } | |
62e76326 | 623 | |
26ac0430 | 624 | break; |
f53969cc | 625 | } /* HTML_DIR, HTML_INDEX_RESULT */ |
62e76326 | 626 | |
2327e7c0 | 627 | case GopherStateData::HTML_CSO_RESULT: { |
26ac0430 AJ |
628 | if (line[0] == '-') { |
629 | int code, recno; | |
630 | char *s_code, *s_recno, *result; | |
62e76326 | 631 | |
26ac0430 AJ |
632 | s_code = strtok(line + 1, ":\n"); |
633 | s_recno = strtok(NULL, ":\n"); | |
634 | result = strtok(NULL, "\n"); | |
62e76326 | 635 | |
26ac0430 AJ |
636 | if (!result) |
637 | break; | |
62e76326 | 638 | |
26ac0430 | 639 | code = atoi(s_code); |
62e76326 | 640 | |
26ac0430 | 641 | recno = atoi(s_recno); |
62e76326 | 642 | |
26ac0430 | 643 | if (code != 200) |
62e76326 | 644 | break; |
26ac0430 AJ |
645 | |
646 | if (gopherState->cso_recno != recno) { | |
647 | snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result)); | |
648 | gopherState->cso_recno = recno; | |
62e76326 | 649 | } else { |
26ac0430 AJ |
650 | snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result)); |
651 | } | |
652 | ||
653 | outbuf.append(tmpbuf); | |
654 | break; | |
655 | } else { | |
656 | int code; | |
657 | char *s_code, *result; | |
62e76326 | 658 | |
26ac0430 AJ |
659 | s_code = strtok(line, ":"); |
660 | result = strtok(NULL, "\n"); | |
62e76326 | 661 | |
26ac0430 AJ |
662 | if (!result) |
663 | break; | |
62e76326 | 664 | |
26ac0430 | 665 | code = atoi(s_code); |
62e76326 | 666 | |
26ac0430 | 667 | switch (code) { |
62e76326 | 668 | |
26ac0430 AJ |
669 | case 200: { |
670 | /* OK */ | |
671 | /* Do nothing here */ | |
672 | break; | |
673 | } | |
62e76326 | 674 | |
f53969cc | 675 | case 102: /* Number of matches */ |
62e76326 | 676 | |
f53969cc | 677 | case 501: /* No Match */ |
62e76326 | 678 | |
f53969cc | 679 | case 502: { /* Too Many Matches */ |
26ac0430 AJ |
680 | /* Print the message the server returns */ |
681 | snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result)); | |
682 | outbuf.append(tmpbuf); | |
683 | break; | |
684 | } | |
62e76326 | 685 | |
62e76326 | 686 | } |
26ac0430 | 687 | } |
62e76326 | 688 | |
f53969cc | 689 | } /* HTML_CSO_RESULT */ |
62e76326 | 690 | |
691 | default: | |
f53969cc | 692 | break; /* do nothing */ |
62e76326 | 693 | |
f53969cc | 694 | } /* switch */ |
090089c4 | 695 | |
f53969cc | 696 | } /* while loop */ |
090089c4 | 697 | |
528b2c61 | 698 | if (outbuf.size() > 0) { |
d53b3f6d | 699 | entry->append(outbuf.rawBuf(), outbuf.size()); |
62e76326 | 700 | /* now let start sending stuff to client */ |
3900307b | 701 | entry->flush(); |
090089c4 | 702 | } |
62e76326 | 703 | |
30abd221 | 704 | outbuf.clean(); |
090089c4 | 705 | return; |
706 | } | |
707 | ||
582b6456 | 708 | static void |
8d77a37c | 709 | gopherTimeout(const CommTimeoutCbParams &io) |
090089c4 | 710 | { |
8d77a37c AJ |
711 | GopherStateData *gopherState = static_cast<GopherStateData *>(io.data); |
712 | debugs(10, 4, HERE << io.conn << ": '" << gopherState->entry->url() << "'" ); | |
62e76326 | 713 | |
f11c8e2f | 714 | gopherState->fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGatewayTimeout, gopherState->fwd->request)); |
62e76326 | 715 | |
8d77a37c AJ |
716 | if (Comm::IsConnOpen(io.conn)) |
717 | io.conn->close(); | |
090089c4 | 718 | } |
719 | ||
63be0a78 | 720 | /** |
63be0a78 | 721 | * This will be called when data is ready to be read from fd. |
722 | * Read until error or connection closed. | |
723 | */ | |
b8d8561b | 724 | static void |
c8407295 | 725 | gopherReadReply(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm::Flag flag, int xerrno, void *data) |
090089c4 | 726 | { |
e6ccf245 | 727 | GopherStateData *gopherState = (GopherStateData *)data; |
bfcaf585 | 728 | StoreEntry *entry = gopherState->entry; |
090089c4 | 729 | int clen; |
56fa4cad | 730 | int bin; |
c4b7a5a9 | 731 | size_t read_sz = BUFSIZ; |
9a0a18de | 732 | #if USE_DELAY_POOLS |
b67e2c8c | 733 | DelayId delayId = entry->mem_obj->mostBytesAllowed(); |
447e176b | 734 | #endif |
c4b7a5a9 | 735 | |
c8407295 | 736 | /* Bail out early on Comm::ERR_CLOSING - close handlers will tidy up for us */ |
62e76326 | 737 | |
c8407295 | 738 | if (flag == Comm::ERR_CLOSING) { |
c4b7a5a9 | 739 | return; |
740 | } | |
741 | ||
742 | assert(buf == gopherState->replybuf); | |
62e76326 | 743 | |
e92e4e44 | 744 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { |
e0d28505 | 745 | gopherState->serverConn->close(); |
62e76326 | 746 | return; |
e92e4e44 | 747 | } |
c4b7a5a9 | 748 | |
9a0a18de | 749 | #if USE_DELAY_POOLS |
b67e2c8c | 750 | read_sz = delayId.bytesWanted(1, read_sz); |
447e176b | 751 | #endif |
c4b7a5a9 | 752 | |
56fa4cad | 753 | /* leave one space for \0 in gopherToHTML */ |
62e76326 | 754 | |
c8407295 | 755 | if (flag == Comm::OK && len > 0) { |
9a0a18de | 756 | #if USE_DELAY_POOLS |
62e76326 | 757 | delayId.bytesIn(len); |
447e176b | 758 | #endif |
62e76326 | 759 | |
a0864754 AJ |
760 | statCounter.server.all.kbytes_in += len; |
761 | statCounter.server.other.kbytes_in += len; | |
ee1679df | 762 | } |
62e76326 | 763 | |
1b76e6c1 | 764 | debugs(10, 5, HERE << conn << " read len=" << len); |
62e76326 | 765 | |
c8407295 | 766 | if (flag == Comm::OK && len > 0) { |
8d77a37c AJ |
767 | AsyncCall::Pointer nil; |
768 | commSetConnTimeout(conn, Config.Timeout.read, nil); | |
95dc7ff4 | 769 | ++IOStats.Gopher.reads; |
62e76326 | 770 | |
95dc7ff4 | 771 | for (clen = len - 1, bin = 0; clen; ++bin) |
62e76326 | 772 | clen >>= 1; |
773 | ||
95dc7ff4 | 774 | ++IOStats.Gopher.read_hist[bin]; |
bae917ac CT |
775 | |
776 | HttpRequest *req = gopherState->fwd->request; | |
88df846b | 777 | if (req->hier.bodyBytesRead < 0) { |
bae917ac | 778 | req->hier.bodyBytesRead = 0; |
88df846b CT |
779 | // first bytes read, update Reply flags: |
780 | gopherState->reply_->sources |= HttpMsg::srcGopher; | |
781 | } | |
dcaab393 | 782 | |
bae917ac | 783 | req->hier.bodyBytesRead += len; |
56fa4cad | 784 | } |
62e76326 | 785 | |
c8407295 | 786 | if (flag != Comm::OK) { |
b69e9ffa | 787 | debugs(50, DBG_IMPORTANT, MYNAME << "error reading: " << xstrerr(xerrno)); |
62e76326 | 788 | |
129fe2a1 | 789 | if (ignoreErrno(xerrno)) { |
1b76e6c1 AJ |
790 | AsyncCall::Pointer call = commCbCall(5,4, "gopherReadReply", |
791 | CommIoCbPtrFun(gopherReadReply, gopherState)); | |
792 | comm_read(conn, buf, read_sz, call); | |
6cae5db1 | 793 | } else { |
955394ce | 794 | ErrorState *err = new ErrorState(ERR_READ_ERROR, Http::scInternalServerError, gopherState->fwd->request); |
129fe2a1 | 795 | err->xerrno = xerrno; |
b6b6f466 | 796 | gopherState->fwd->fail(err); |
e0d28505 | 797 | gopherState->serverConn->close(); |
62e76326 | 798 | } |
528b2c61 | 799 | } else if (len == 0 && entry->isEmpty()) { |
955394ce | 800 | gopherState->fwd->fail(new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, gopherState->fwd->request)); |
e0d28505 | 801 | gopherState->serverConn->close(); |
090089c4 | 802 | } else if (len == 0) { |
62e76326 | 803 | /* Connection closed; retrieval done. */ |
bb790702 | 804 | /* flush the rest of data in temp buf if there is one. */ |
62e76326 | 805 | |
2327e7c0 | 806 | if (gopherState->conversion != GopherStateData::NORMAL) |
62e76326 | 807 | gopherEndHTML(gopherState); |
808 | ||
3900307b | 809 | entry->timestampsSet(); |
3900307b | 810 | entry->flush(); |
b6b6f466 | 811 | gopherState->fwd->complete(); |
e0d28505 | 812 | gopherState->serverConn->close(); |
090089c4 | 813 | } else { |
2327e7c0 | 814 | if (gopherState->conversion != GopherStateData::NORMAL) { |
62e76326 | 815 | gopherToHTML(gopherState, buf, len); |
816 | } else { | |
3900307b | 817 | entry->append(buf, len); |
62e76326 | 818 | } |
abd8f140 AJ |
819 | AsyncCall::Pointer call = commCbCall(5,4, "gopherReadReply", |
820 | CommIoCbPtrFun(gopherReadReply, gopherState)); | |
821 | comm_read(conn, buf, read_sz, call); | |
822 | } | |
090089c4 | 823 | } |
824 | ||
63be0a78 | 825 | /** |
63be0a78 | 826 | * This will be called when request write is complete. Schedule read of reply. |
827 | */ | |
b8d8561b | 828 | static void |
c8407295 | 829 | gopherSendComplete(const Comm::ConnectionPointer &conn, char *buf, size_t size, Comm::Flag errflag, int xerrno, void *data) |
090089c4 | 830 | { |
56fa4cad | 831 | GopherStateData *gopherState = (GopherStateData *) data; |
70a9dab4 | 832 | StoreEntry *entry = gopherState->entry; |
e0d28505 | 833 | debugs(10, 5, HERE << conn << " size: " << size << " errflag: " << errflag); |
62e76326 | 834 | |
ee1679df | 835 | if (size > 0) { |
e0d28505 | 836 | fd_bytes(conn->fd, size, FD_WRITE); |
a0864754 AJ |
837 | statCounter.server.all.kbytes_out += size; |
838 | statCounter.server.other.kbytes_out += size; | |
ee1679df | 839 | } |
62e76326 | 840 | |
090089c4 | 841 | if (errflag) { |
62e76326 | 842 | ErrorState *err; |
955394ce | 843 | err = new ErrorState(ERR_WRITE_ERROR, Http::scServiceUnavailable, gopherState->fwd->request); |
129fe2a1 | 844 | err->xerrno = xerrno; |
5c51bffb | 845 | err->port = gopherState->fwd->request->url.port(); |
3900307b | 846 | err->url = xstrdup(entry->url()); |
b6b6f466 | 847 | gopherState->fwd->fail(err); |
e0d28505 | 848 | gopherState->serverConn->close(); |
62e76326 | 849 | |
850 | if (buf) | |
f53969cc | 851 | memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */ |
62e76326 | 852 | |
853 | return; | |
090089c4 | 854 | } |
62e76326 | 855 | |
856 | /* | |
090089c4 | 857 | * OK. We successfully reach remote site. Start MIME typing |
858 | * stuff. Do it anyway even though request is not HTML type. | |
859 | */ | |
3900307b | 860 | entry->buffer(); |
b66315e4 | 861 | |
30a4f2a8 | 862 | gopherMimeCreate(gopherState); |
62e76326 | 863 | |
a47b9029 | 864 | switch (gopherState->type_id) { |
62e76326 | 865 | |
a47b9029 | 866 | case GOPHER_DIRECTORY: |
62e76326 | 867 | /* we got to convert it first */ |
2327e7c0 | 868 | gopherState->conversion = GopherStateData::HTML_DIR; |
62e76326 | 869 | gopherState->HTML_header_added = 0; |
870 | break; | |
871 | ||
a47b9029 | 872 | case GOPHER_INDEX: |
62e76326 | 873 | /* we got to convert it first */ |
2327e7c0 | 874 | gopherState->conversion = GopherStateData::HTML_INDEX_RESULT; |
62e76326 | 875 | gopherState->HTML_header_added = 0; |
876 | break; | |
877 | ||
a47b9029 | 878 | case GOPHER_CSO: |
62e76326 | 879 | /* we got to convert it first */ |
2327e7c0 | 880 | gopherState->conversion = GopherStateData::HTML_CSO_RESULT; |
62e76326 | 881 | gopherState->cso_recno = 0; |
882 | gopherState->HTML_header_added = 0; | |
883 | break; | |
884 | ||
a47b9029 | 885 | default: |
2327e7c0 | 886 | gopherState->conversion = GopherStateData::NORMAL; |
3900307b | 887 | entry->flush(); |
86101e40 | 888 | } |
62e76326 | 889 | |
090089c4 | 890 | /* Schedule read reply. */ |
8d77a37c | 891 | AsyncCall::Pointer call = commCbCall(5,5, "gopherReadReply", |
26ac0430 | 892 | CommIoCbPtrFun(gopherReadReply, gopherState)); |
3e4bebf8 | 893 | entry->delayAwareRead(conn, gopherState->replybuf, BUFSIZ, call); |
62e76326 | 894 | |
090089c4 | 895 | if (buf) |
f53969cc | 896 | memFree(buf, MEM_4K_BUF); /* Allocated by gopherSendRequest. */ |
090089c4 | 897 | } |
898 | ||
63be0a78 | 899 | /** |
63be0a78 | 900 | * This will be called when connect completes. Write request. |
901 | */ | |
b8d8561b | 902 | static void |
ced8def3 | 903 | gopherSendRequest(int, void *data) |
090089c4 | 904 | { |
e6ccf245 | 905 | GopherStateData *gopherState = (GopherStateData *)data; |
906 | char *buf = (char *)memAllocate(MEM_4K_BUF); | |
62e76326 | 907 | |
582b6456 | 908 | if (gopherState->type_id == GOPHER_CSO) { |
62e76326 | 909 | const char *t = strchr(gopherState->request, '?'); |
910 | ||
911 | if (t != NULL) | |
f53969cc | 912 | ++t; /* skip the ? */ |
62e76326 | 913 | else |
914 | t = ""; | |
915 | ||
916 | snprintf(buf, 4096, "query %s\r\nquit\r\n", t); | |
582b6456 | 917 | } else if (gopherState->type_id == GOPHER_INDEX) { |
62e76326 | 918 | char *t = strchr(gopherState->request, '?'); |
919 | ||
920 | if (t != NULL) | |
921 | *t = '\t'; | |
922 | ||
923 | snprintf(buf, 4096, "%s\r\n", gopherState->request); | |
090089c4 | 924 | } else { |
62e76326 | 925 | snprintf(buf, 4096, "%s\r\n", gopherState->request); |
090089c4 | 926 | } |
62e76326 | 927 | |
e0d28505 | 928 | debugs(10, 5, HERE << gopherState->serverConn); |
ec41b64c AJ |
929 | AsyncCall::Pointer call = commCbCall(5,5, "gopherSendComplete", |
930 | CommIoCbPtrFun(gopherSendComplete, gopherState)); | |
b0388924 | 931 | Comm::Write(gopherState->serverConn, buf, strlen(buf), call, NULL); |
62e76326 | 932 | |
6919be24 | 933 | gopherState->entry->makePublic(); |
090089c4 | 934 | } |
935 | ||
770f051d | 936 | void |
b6b6f466 | 937 | gopherStart(FwdState * fwd) |
090089c4 | 938 | { |
2327e7c0 | 939 | GopherStateData *gopherState = new GopherStateData(fwd); |
34266cde | 940 | |
2327e7c0 | 941 | debugs(10, 3, gopherState->entry->url()); |
34266cde | 942 | |
95dc7ff4 | 943 | ++ statCounter.server.all.requests; |
34266cde | 944 | |
95dc7ff4 | 945 | ++ statCounter.server.other.requests; |
34266cde | 946 | |
090089c4 | 947 | /* Parse url. */ |
b6b6f466 | 948 | gopher_request_parse(fwd->request, |
62e76326 | 949 | &gopherState->type_id, gopherState->request); |
34266cde | 950 | |
5229395c | 951 | comm_add_close_handler(fwd->serverConnection()->fd, gopherStateFree, gopherState); |
62e76326 | 952 | |
582b6456 | 953 | if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO)) |
62e76326 | 954 | && (strchr(gopherState->request, '?') == NULL)) { |
955 | /* Index URL without query word */ | |
956 | /* We have to generate search page back to client. No need for connection */ | |
957 | gopherMimeCreate(gopherState); | |
958 | ||
959 | if (gopherState->type_id == GOPHER_INDEX) { | |
2327e7c0 | 960 | gopherState->conversion = GopherStateData::HTML_INDEX_PAGE; |
62e76326 | 961 | } else { |
962 | if (gopherState->type_id == GOPHER_CSO) { | |
2327e7c0 | 963 | gopherState->conversion = GopherStateData::HTML_CSO_PAGE; |
62e76326 | 964 | } else { |
2327e7c0 | 965 | gopherState->conversion = GopherStateData::HTML_INDEX_PAGE; |
62e76326 | 966 | } |
967 | } | |
968 | ||
969 | gopherToHTML(gopherState, (char *) NULL, 0); | |
b6b6f466 | 970 | fwd->complete(); |
62e76326 | 971 | return; |
090089c4 | 972 | } |
62e76326 | 973 | |
e0d28505 | 974 | gopherState->serverConn = fwd->serverConnection(); |
5229395c | 975 | gopherSendRequest(fwd->serverConnection()->fd, gopherState); |
8d77a37c | 976 | AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "gopherTimeout", |
dc49061a | 977 | CommTimeoutCbPtrFun(gopherTimeout, gopherState)); |
8d77a37c | 978 | commSetConnTimeout(fwd->serverConnection(), Config.Timeout.read, timeoutCall); |
e5f6c5c2 | 979 | } |
f53969cc | 980 |