2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 10 Gopher */
13 #include "comm/Read.h"
14 #include "comm/Write.h"
15 #include "errorpage.h"
19 #include "html_quote.h"
20 #include "HttpReply.h"
21 #include "HttpRequest.h"
24 #include "parser/Tokenizer.h"
26 #include "SquidConfig.h"
27 #include "SquidTime.h"
28 #include "StatCounters.h"
33 #include "DelayPools.h"
34 #include "MemObject.h"
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'
53 #define GOPHER_HTML 'h'
54 #define GOPHER_INFO 'i'
57 #define GOPHER_WWW 'w'
58 #define GOPHER_SOUND 's'
60 #define GOPHER_PLUS_IMAGE ':'
61 #define GOPHER_PLUS_MOVIE ';'
62 #define GOPHER_PLUS_SOUND '<'
64 #define GOPHER_PORT 70
68 // TODO CODE: should this be a protocol-specific thing?
69 #define TEMP_BUF_SIZE 4096
71 #define MAX_CSO_RESULT 1024
74 * Gopher Gateway Internals
76 * Gopher is somewhat complex and gross because it must convert from
77 * the Gopher protocol to HTTP.
81 CBDATA_CLASS(GopherStateData
);
84 GopherStateData(FwdState
*aFwd
) :
89 type_id(GOPHER_FILE
/* '0' */),
96 buf
= (char *)memAllocate(MEM_4K_BUF
);
97 entry
->lock("gopherState");
100 ~GopherStateData() {if(buf
) swanSong();}
102 /* AsyncJob API emulated */
103 void deleteThis(const char *aReason
);
116 int HTML_header_added
;
119 char request
[MAX_URL
];
122 char *buf
; /* pts to a 4k page */
123 Comm::ConnectionPointer serverConn
;
124 FwdState::Pointer fwd
;
125 HttpReply::Pointer reply_
;
126 char replybuf
[BUFSIZ
];
129 CBDATA_CLASS_INIT(GopherStateData
);
131 static CLCB gopherStateFree
;
132 static void gopherMimeCreate(GopherStateData
*);
133 static void gopher_request_parse(const HttpRequest
* req
,
136 static void gopherEndHTML(GopherStateData
*);
137 static void gopherToHTML(GopherStateData
*, char *inbuf
, int len
);
138 static CTCB gopherTimeout
;
139 static IOCB gopherReadReply
;
140 static IOCB gopherSendComplete
;
141 static PF gopherSendRequest
;
143 static char def_gopher_bin
[] = "www/unknown";
145 static char def_gopher_text
[] = "text/plain";
148 gopherStateFree(const CommCloseCbParams
¶ms
)
150 GopherStateData
*gopherState
= (GopherStateData
*)params
.data
;
152 if (gopherState
== NULL
)
155 gopherState
->deleteThis("gopherStateFree");
159 GopherStateData::deleteThis(const char *)
166 GopherStateData::swanSong()
169 entry
->unlock("gopherState");
172 memFree(buf
, MEM_4K_BUF
);
178 * Create MIME Header for Gopher Data
181 gopherMimeCreate(GopherStateData
* gopherState
)
183 StoreEntry
*entry
= gopherState
->entry
;
184 const char *mime_type
= NULL
;
185 const char *mime_enc
= NULL
;
187 switch (gopherState
->type_id
) {
189 case GOPHER_DIRECTORY
:
198 mime_type
= "text/html";
205 case GOPHER_PLUS_IMAGE
:
206 mime_type
= "image/gif";
211 case GOPHER_PLUS_SOUND
:
212 mime_type
= "audio/basic";
215 case GOPHER_PLUS_MOVIE
:
216 mime_type
= "video/mpeg";
219 case GOPHER_MACBINHEX
:
223 case GOPHER_UUENCODED
:
226 /* Rightnow We have no idea what it is. */
227 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
228 mime_type
= mimeGetContentType(gopherState
->request
);
230 mime_type
= def_gopher_bin
;
236 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
237 mime_type
= mimeGetContentType(gopherState
->request
);
239 mime_type
= def_gopher_text
;
243 assert(entry
->isEmpty());
245 HttpReply
*reply
= new HttpReply
;
247 reply
->setHeaders(Http::scOkay
, "Gatewaying", mime_type
, -1, -1, -2);
249 reply
->header
.putStr(Http::HdrType::CONTENT_ENCODING
, mime_enc
);
251 entry
->replaceHttpReply(reply
);
252 gopherState
->reply_
= reply
;
256 * Parse a gopher request into components. By Anawat.
259 gopher_request_parse(const HttpRequest
* req
, char *type_id
, char *request
)
261 ::Parser::Tokenizer
tok(req
->url
.path());
266 tok
.skip('/'); // ignore failures? path could be ab-empty
269 *type_id
= GOPHER_DIRECTORY
;
273 static const CharacterSet
anyByte("UTF-8",0x00, 0xFF);
276 (void)tok
.prefix(typeId
, anyByte
, 1); // never fails since !atEnd()
277 *type_id
= typeId
[0];
280 SBufToCstring(request
, tok
.remaining().substr(0, MAX_URL
-1));
281 /* convert %xx to char */
282 rfc1738_unescape(request
);
287 * Parse the request to determine whether it is cachable.
289 * \param req Request data.
290 * \retval 0 Not cachable.
291 * \retval 1 Cachable.
294 gopherCachable(const HttpRequest
* req
)
298 /* parse to see type */
299 gopher_request_parse(req
,
323 gopherHTMLHeader(StoreEntry
* e
, const char *title
, const char *substring
)
325 storeAppendPrintf(e
, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
326 storeAppendPrintf(e
, "<HTML><HEAD><TITLE>");
327 storeAppendPrintf(e
, title
, substring
);
328 storeAppendPrintf(e
, "</TITLE>");
329 storeAppendPrintf(e
, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
330 storeAppendPrintf(e
, "</HEAD>\n<BODY><H1>");
331 storeAppendPrintf(e
, title
, substring
);
332 storeAppendPrintf(e
, "</H1>\n");
336 gopherHTMLFooter(StoreEntry
* e
)
338 storeAppendPrintf(e
, "<HR noshade size=\"1px\">\n");
339 storeAppendPrintf(e
, "<ADDRESS>\n");
340 storeAppendPrintf(e
, "Generated %s by %s (%s)\n",
341 mkrfc1123(squid_curtime
),
343 visible_appname_string
);
344 storeAppendPrintf(e
, "</ADDRESS></BODY></HTML>\n");
348 gopherEndHTML(GopherStateData
* gopherState
)
350 StoreEntry
*e
= gopherState
->entry
;
352 if (!gopherState
->HTML_header_added
) {
353 gopherHTMLHeader(e
, "Server Return Nothing", NULL
);
354 storeAppendPrintf(e
, "<P>The Gopher query resulted in a blank response</P>");
355 } else if (gopherState
->HTML_pre
) {
356 storeAppendPrintf(e
, "</PRE>\n");
363 * Convert Gopher to HTML.
365 * Borrow part of code from libwww2 came with Mosaic distribution.
368 gopherToHTML(GopherStateData
* gopherState
, char *inbuf
, int len
)
373 LOCAL_ARRAY(char, line
, TEMP_BUF_SIZE
);
374 LOCAL_ARRAY(char, tmpbuf
, TEMP_BUF_SIZE
);
376 char *selector
= NULL
;
379 char *escaped_selector
= NULL
;
380 const char *icon_url
= NULL
;
382 StoreEntry
*entry
= NULL
;
384 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
385 memset(line
, '\0', TEMP_BUF_SIZE
);
387 entry
= gopherState
->entry
;
389 if (gopherState
->conversion
== GopherStateData::HTML_INDEX_PAGE
) {
390 char *html_url
= html_quote(entry
->url());
391 gopherHTMLHeader(entry
, "Gopher Index %s", html_url
);
392 storeAppendPrintf(entry
,
393 "<p>This is a searchable Gopher index. Use the search\n"
394 "function of your browser to enter search terms.\n"
396 gopherHTMLFooter(entry
);
397 /* now let start sending stuff to client */
399 gopherState
->HTML_header_added
= 1;
404 if (gopherState
->conversion
== GopherStateData::HTML_CSO_PAGE
) {
405 char *html_url
= html_quote(entry
->url());
406 gopherHTMLHeader(entry
, "CSO Search of %s", html_url
);
407 storeAppendPrintf(entry
,
408 "<P>A CSO database usually contains a phonebook or\n"
409 "directory. Use the search function of your browser to enter\n"
410 "search terms.</P><ISINDEX>\n");
411 gopherHTMLFooter(entry
);
412 /* now let start sending stuff to client */
414 gopherState
->HTML_header_added
= 1;
421 if (!gopherState
->HTML_header_added
) {
422 if (gopherState
->conversion
== GopherStateData::HTML_CSO_RESULT
)
423 gopherHTMLHeader(entry
, "CSO Search Result", NULL
);
425 gopherHTMLHeader(entry
, "Gopher Menu", NULL
);
427 outbuf
.append ("<PRE>");
429 gopherState
->HTML_header_added
= 1;
431 gopherState
->HTML_pre
= 1;
434 while (pos
< inbuf
+ len
) {
436 int left
= len
- (pos
- inbuf
);
437 lpos
= (char *)memchr(pos
, '\n', left
);
439 ++lpos
; /* Next line is after \n */
444 if (gopherState
->len
+ llen
>= TEMP_BUF_SIZE
) {
445 debugs(10, DBG_IMPORTANT
, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
446 llen
= TEMP_BUF_SIZE
- gopherState
->len
- 1;
449 /* there is no complete line in inbuf */
450 /* copy it to temp buffer */
451 /* note: llen is adjusted above */
452 memcpy(gopherState
->buf
+ gopherState
->len
, pos
, llen
);
453 gopherState
->len
+= llen
;
456 if (gopherState
->len
!= 0) {
457 /* there is something left from last tx. */
458 memcpy(line
, gopherState
->buf
, gopherState
->len
);
459 memcpy(line
+ gopherState
->len
, pos
, llen
);
460 llen
+= gopherState
->len
;
461 gopherState
->len
= 0;
463 memcpy(line
, pos
, llen
);
465 line
[llen
+ 1] = '\0';
466 /* move input to next line */
469 /* at this point. We should have one line in buffer to process */
473 memset(line
, '\0', TEMP_BUF_SIZE
);
477 switch (gopherState
->conversion
) {
479 case GopherStateData::HTML_INDEX_RESULT
:
481 case GopherStateData::HTML_DIR
: {
486 selector
= strchr(tline
, TAB
);
491 host
= strchr(selector
, TAB
);
496 port
= strchr(host
, TAB
);
501 junk
= strchr(host
, TAB
);
504 *junk
++ = 0; /* Chop port */
506 junk
= strchr(host
, '\r');
509 *junk
++ = 0; /* Chop port */
511 junk
= strchr(host
, '\n');
514 *junk
++ = 0; /* Chop port */
518 if ((port
[1] == '0') && (!port
[2]))
519 port
[0] = 0; /* 0 means none */
522 /* escape a selector here */
523 escaped_selector
= xstrdup(rfc1738_escape_part(selector
));
527 case GOPHER_DIRECTORY
:
528 icon_url
= mimeGetIconURL("internal-menu");
534 icon_url
= mimeGetIconURL("internal-text");
540 icon_url
= mimeGetIconURL("internal-index");
547 case GOPHER_PLUS_IMAGE
:
548 icon_url
= mimeGetIconURL("internal-image");
553 case GOPHER_PLUS_SOUND
:
554 icon_url
= mimeGetIconURL("internal-sound");
557 case GOPHER_PLUS_MOVIE
:
558 icon_url
= mimeGetIconURL("internal-movie");
564 icon_url
= mimeGetIconURL("internal-telnet");
569 case GOPHER_MACBINHEX
:
573 case GOPHER_UUENCODED
:
574 icon_url
= mimeGetIconURL("internal-binary");
582 icon_url
= mimeGetIconURL("internal-unknown");
586 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
588 if ((gtype
== GOPHER_TELNET
) || (gtype
== GOPHER_3270
)) {
589 if (strlen(escaped_selector
) != 0)
590 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
591 icon_url
, escaped_selector
, rfc1738_escape_part(host
),
592 *port
? ":" : "", port
, html_quote(name
));
594 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
595 icon_url
, rfc1738_escape_part(host
), *port
? ":" : "",
596 port
, html_quote(name
));
598 } else if (gtype
== GOPHER_INFO
) {
599 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "\t%s\n", html_quote(name
));
601 if (strncmp(selector
, "GET /", 5) == 0) {
603 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
604 icon_url
, host
, rfc1738_escape_unescaped(selector
+ 5), html_quote(name
));
607 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
608 icon_url
, host
, gtype
, escaped_selector
, html_quote(name
));
612 safe_free(escaped_selector
);
613 outbuf
.append(tmpbuf
);
615 memset(line
, '\0', TEMP_BUF_SIZE
);
619 memset(line
, '\0', TEMP_BUF_SIZE
);
624 } /* HTML_DIR, HTML_INDEX_RESULT */
626 case GopherStateData::HTML_CSO_RESULT
: {
627 if (line
[0] == '-') {
629 char *s_code
, *s_recno
, *result
;
631 s_code
= strtok(line
+ 1, ":\n");
632 s_recno
= strtok(NULL
, ":\n");
633 result
= strtok(NULL
, "\n");
640 recno
= atoi(s_recno
);
645 if (gopherState
->cso_recno
!= recno
) {
646 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno
, html_quote(result
));
647 gopherState
->cso_recno
= recno
;
649 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "%s\n", html_quote(result
));
652 outbuf
.append(tmpbuf
);
656 char *s_code
, *result
;
658 s_code
= strtok(line
, ":");
659 result
= strtok(NULL
, "\n");
670 /* Do nothing here */
674 case 102: /* Number of matches */
676 case 501: /* No Match */
678 case 502: { /* Too Many Matches */
679 /* Print the message the server returns */
680 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result
));
681 outbuf
.append(tmpbuf
);
688 } /* HTML_CSO_RESULT */
691 break; /* do nothing */
697 if (outbuf
.size() > 0) {
698 entry
->append(outbuf
.rawBuf(), outbuf
.size());
699 /* now let start sending stuff to client */
708 gopherTimeout(const CommTimeoutCbParams
&io
)
710 GopherStateData
*gopherState
= static_cast<GopherStateData
*>(io
.data
);
711 debugs(10, 4, HERE
<< io
.conn
<< ": '" << gopherState
->entry
->url() << "'" );
713 gopherState
->fwd
->fail(new ErrorState(ERR_READ_TIMEOUT
, Http::scGatewayTimeout
, gopherState
->fwd
->request
, gopherState
->fwd
->al
));
715 if (Comm::IsConnOpen(io
.conn
))
720 * This will be called when data is ready to be read from fd.
721 * Read until error or connection closed.
724 gopherReadReply(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, Comm::Flag flag
, int xerrno
, void *data
)
726 GopherStateData
*gopherState
= (GopherStateData
*)data
;
727 StoreEntry
*entry
= gopherState
->entry
;
730 size_t read_sz
= BUFSIZ
;
732 DelayId delayId
= entry
->mem_obj
->mostBytesAllowed();
735 /* Bail out early on Comm::ERR_CLOSING - close handlers will tidy up for us */
737 if (flag
== Comm::ERR_CLOSING
) {
741 assert(buf
== gopherState
->replybuf
);
743 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
744 gopherState
->serverConn
->close();
749 read_sz
= delayId
.bytesWanted(1, read_sz
);
752 /* leave one space for \0 in gopherToHTML */
754 if (flag
== Comm::OK
&& len
> 0) {
756 delayId
.bytesIn(len
);
759 statCounter
.server
.all
.kbytes_in
+= len
;
760 statCounter
.server
.other
.kbytes_in
+= len
;
763 debugs(10, 5, HERE
<< conn
<< " read len=" << len
);
765 if (flag
== Comm::OK
&& len
> 0) {
766 AsyncCall::Pointer nil
;
767 commSetConnTimeout(conn
, Config
.Timeout
.read
, nil
);
768 ++IOStats
.Gopher
.reads
;
770 for (clen
= len
- 1, bin
= 0; clen
; ++bin
)
773 ++IOStats
.Gopher
.read_hist
[bin
];
775 HttpRequest
*req
= gopherState
->fwd
->request
;
776 if (req
->hier
.bodyBytesRead
< 0) {
777 req
->hier
.bodyBytesRead
= 0;
778 // first bytes read, update Reply flags:
779 gopherState
->reply_
->sources
|= Http::Message::srcGopher
;
782 req
->hier
.bodyBytesRead
+= len
;
785 if (flag
!= Comm::OK
) {
786 debugs(50, DBG_IMPORTANT
, MYNAME
<< "error reading: " << xstrerr(xerrno
));
788 if (ignoreErrno(xerrno
)) {
789 AsyncCall::Pointer call
= commCbCall(5,4, "gopherReadReply",
790 CommIoCbPtrFun(gopherReadReply
, gopherState
));
791 comm_read(conn
, buf
, read_sz
, call
);
793 const auto err
= new ErrorState(ERR_READ_ERROR
, Http::scInternalServerError
, gopherState
->fwd
->request
, gopherState
->fwd
->al
);
794 err
->xerrno
= xerrno
;
795 gopherState
->fwd
->fail(err
);
796 gopherState
->serverConn
->close();
798 } else if (len
== 0 && entry
->isEmpty()) {
799 gopherState
->fwd
->fail(new ErrorState(ERR_ZERO_SIZE_OBJECT
, Http::scServiceUnavailable
, gopherState
->fwd
->request
, gopherState
->fwd
->al
));
800 gopherState
->serverConn
->close();
801 } else if (len
== 0) {
802 /* Connection closed; retrieval done. */
803 /* flush the rest of data in temp buf if there is one. */
805 if (gopherState
->conversion
!= GopherStateData::NORMAL
)
806 gopherEndHTML(gopherState
);
808 entry
->timestampsSet();
810 gopherState
->fwd
->complete();
811 gopherState
->serverConn
->close();
813 if (gopherState
->conversion
!= GopherStateData::NORMAL
) {
814 gopherToHTML(gopherState
, buf
, len
);
816 entry
->append(buf
, len
);
818 AsyncCall::Pointer call
= commCbCall(5,4, "gopherReadReply",
819 CommIoCbPtrFun(gopherReadReply
, gopherState
));
820 comm_read(conn
, buf
, read_sz
, call
);
825 * This will be called when request write is complete. Schedule read of reply.
828 gopherSendComplete(const Comm::ConnectionPointer
&conn
, char *, size_t size
, Comm::Flag errflag
, int xerrno
, void *data
)
830 GopherStateData
*gopherState
= (GopherStateData
*) data
;
831 StoreEntry
*entry
= gopherState
->entry
;
832 debugs(10, 5, HERE
<< conn
<< " size: " << size
<< " errflag: " << errflag
);
835 fd_bytes(conn
->fd
, size
, FD_WRITE
);
836 statCounter
.server
.all
.kbytes_out
+= size
;
837 statCounter
.server
.other
.kbytes_out
+= size
;
841 const auto err
= new ErrorState(ERR_WRITE_ERROR
, Http::scServiceUnavailable
, gopherState
->fwd
->request
, gopherState
->fwd
->al
);
842 err
->xerrno
= xerrno
;
843 err
->port
= gopherState
->fwd
->request
->url
.port();
844 err
->url
= xstrdup(entry
->url());
845 gopherState
->fwd
->fail(err
);
846 gopherState
->serverConn
->close();
851 * OK. We successfully reach remote site. Start MIME typing
852 * stuff. Do it anyway even though request is not HTML type.
856 gopherMimeCreate(gopherState
);
858 switch (gopherState
->type_id
) {
860 case GOPHER_DIRECTORY
:
861 /* we got to convert it first */
862 gopherState
->conversion
= GopherStateData::HTML_DIR
;
863 gopherState
->HTML_header_added
= 0;
867 /* we got to convert it first */
868 gopherState
->conversion
= GopherStateData::HTML_INDEX_RESULT
;
869 gopherState
->HTML_header_added
= 0;
873 /* we got to convert it first */
874 gopherState
->conversion
= GopherStateData::HTML_CSO_RESULT
;
875 gopherState
->cso_recno
= 0;
876 gopherState
->HTML_header_added
= 0;
880 gopherState
->conversion
= GopherStateData::NORMAL
;
884 /* Schedule read reply. */
885 AsyncCall::Pointer call
= commCbCall(5,5, "gopherReadReply",
886 CommIoCbPtrFun(gopherReadReply
, gopherState
));
887 entry
->delayAwareRead(conn
, gopherState
->replybuf
, BUFSIZ
, call
);
891 * This will be called when connect completes. Write request.
894 gopherSendRequest(int, void *data
)
896 GopherStateData
*gopherState
= (GopherStateData
*)data
;
900 if (gopherState
->type_id
== GOPHER_CSO
) {
901 const char *t
= strchr(gopherState
->request
, '?');
904 ++t
; /* skip the ? */
908 mb
.appendf("query %s\r\nquit", t
);
910 if (gopherState
->type_id
== GOPHER_INDEX
) {
911 if (char *t
= strchr(gopherState
->request
, '?'))
914 mb
.append(gopherState
->request
, strlen(gopherState
->request
));
916 mb
.append("\r\n", 2);
918 debugs(10, 5, gopherState
->serverConn
);
919 AsyncCall::Pointer call
= commCbCall(5,5, "gopherSendComplete",
920 CommIoCbPtrFun(gopherSendComplete
, gopherState
));
921 Comm::Write(gopherState
->serverConn
, &mb
, call
);
923 if (!gopherState
->entry
->makePublic())
924 gopherState
->entry
->makePrivate(true);
928 gopherStart(FwdState
* fwd
)
930 GopherStateData
*gopherState
= new GopherStateData(fwd
);
932 debugs(10, 3, gopherState
->entry
->url());
934 ++ statCounter
.server
.all
.requests
;
936 ++ statCounter
.server
.other
.requests
;
939 gopher_request_parse(fwd
->request
,
940 &gopherState
->type_id
, gopherState
->request
);
942 comm_add_close_handler(fwd
->serverConnection()->fd
, gopherStateFree
, gopherState
);
944 if (((gopherState
->type_id
== GOPHER_INDEX
) || (gopherState
->type_id
== GOPHER_CSO
))
945 && (strchr(gopherState
->request
, '?') == NULL
)) {
946 /* Index URL without query word */
947 /* We have to generate search page back to client. No need for connection */
948 gopherMimeCreate(gopherState
);
950 if (gopherState
->type_id
== GOPHER_INDEX
) {
951 gopherState
->conversion
= GopherStateData::HTML_INDEX_PAGE
;
953 if (gopherState
->type_id
== GOPHER_CSO
) {
954 gopherState
->conversion
= GopherStateData::HTML_CSO_PAGE
;
956 gopherState
->conversion
= GopherStateData::HTML_INDEX_PAGE
;
960 gopherToHTML(gopherState
, (char *) NULL
, 0);
965 gopherState
->serverConn
= fwd
->serverConnection();
966 gopherSendRequest(fwd
->serverConnection()->fd
, gopherState
);
967 AsyncCall::Pointer timeoutCall
= commCbCall(5, 4, "gopherTimeout",
968 CommTimeoutCbPtrFun(gopherTimeout
, gopherState
));
969 commSetConnTimeout(fwd
->serverConnection(), Config
.Timeout
.read
, timeoutCall
);