5 * DEBUG: section 10 Gopher
6 * AUTHOR: Harvest Derived
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
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.
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.
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.
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
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 #include "comm/Write.h"
38 #include "errorpage.h"
40 #include "html_quote.h"
41 #include "HttpRequest.h"
42 #include "HttpReply.h"
45 #include "DelayPools.h"
46 #include "MemObject.h"
51 #include "SquidTime.h"
54 \defgroup ServerProtocolGopherInternal Server-Side Gopher Internals
55 \ingroup ServerProtocolGopherAPI
56 * Gopher is somewhat complex and gross because it must convert from
57 * the Gopher protocol to HTTP.
60 /* gopher type code from rfc. Anawat. */
61 /// \ingroup ServerProtocolGopherInternal
62 #define GOPHER_FILE '0'
63 /// \ingroup ServerProtocolGopherInternal
64 #define GOPHER_DIRECTORY '1'
65 /// \ingroup ServerProtocolGopherInternal
66 #define GOPHER_CSO '2'
67 /// \ingroup ServerProtocolGopherInternal
68 #define GOPHER_ERROR '3'
69 /// \ingroup ServerProtocolGopherInternal
70 #define GOPHER_MACBINHEX '4'
71 /// \ingroup ServerProtocolGopherInternal
72 #define GOPHER_DOSBIN '5'
73 /// \ingroup ServerProtocolGopherInternal
74 #define GOPHER_UUENCODED '6'
75 /// \ingroup ServerProtocolGopherInternal
76 #define GOPHER_INDEX '7'
77 /// \ingroup ServerProtocolGopherInternal
78 #define GOPHER_TELNET '8'
79 /// \ingroup ServerProtocolGopherInternal
80 #define GOPHER_BIN '9'
81 /// \ingroup ServerProtocolGopherInternal
82 #define GOPHER_REDUNT '+'
83 /// \ingroup ServerProtocolGopherInternal
84 #define GOPHER_3270 'T'
85 /// \ingroup ServerProtocolGopherInternal
86 #define GOPHER_GIF 'g'
87 /// \ingroup ServerProtocolGopherInternal
88 #define GOPHER_IMAGE 'I'
90 /// \ingroup ServerProtocolGopherInternal
91 #define GOPHER_HTML 'h' /* HTML */
92 /// \ingroup ServerProtocolGopherInternal
93 #define GOPHER_INFO 'i'
95 \ingroup ServerProtocolGopherInternal
98 #define GOPHER_WWW 'w'
99 /// \ingroup ServerProtocolGopherInternal
100 #define GOPHER_SOUND 's'
102 /// \ingroup ServerProtocolGopherInternal
103 #define GOPHER_PLUS_IMAGE ':'
104 /// \ingroup ServerProtocolGopherInternal
105 #define GOPHER_PLUS_MOVIE ';'
106 /// \ingroup ServerProtocolGopherInternal
107 #define GOPHER_PLUS_SOUND '<'
109 /// \ingroup ServerProtocolGopherInternal
110 #define GOPHER_PORT 70
112 /// \ingroup ServerProtocolGopherInternal
114 /// \ingroup ServerProtocolGopherInternal
115 /// \todo CODE: should this be a protocol-specific thing?
116 #define TEMP_BUF_SIZE 4096
117 /// \ingroup ServerProtocolGopherInternal
118 #define MAX_CSO_RESULT 1024
120 /// \ingroup ServerProtocolGopherInternal
121 typedef struct gopher_ds
{
131 int HTML_header_added
;
134 char request
[MAX_URL
];
137 char *buf
; /* pts to a 4k page */
140 FwdState::Pointer fwd
;
141 char replybuf
[BUFSIZ
];
144 static PF gopherStateFree
;
145 static void gopherMimeCreate(GopherStateData
*);
146 static void gopher_request_parse(const HttpRequest
* req
,
149 static void gopherEndHTML(GopherStateData
*);
150 static void gopherToHTML(GopherStateData
*, char *inbuf
, int len
);
151 static PF gopherTimeout
;
152 static IOCB gopherReadReply
;
153 static IOCB gopherSendComplete
;
154 static PF gopherSendRequest
;
156 /// \ingroup ServerProtocolGopherInternal
157 static char def_gopher_bin
[] = "www/unknown";
159 /// \ingroup ServerProtocolGopherInternal
160 static char def_gopher_text
[] = "text/plain";
162 /// \ingroup ServerProtocolGopherInternal
164 gopherStateFree(int fdnotused
, void *data
)
166 GopherStateData
*gopherState
= (GopherStateData
*)data
;
168 if (gopherState
== NULL
)
171 if (gopherState
->entry
) {
172 gopherState
->entry
->unlock();
175 HTTPMSGUNLOCK(gopherState
->req
);
177 gopherState
->fwd
= NULL
; // refcounted
179 memFree(gopherState
->buf
, MEM_4K_BUF
);
180 gopherState
->buf
= NULL
;
181 cbdataFree(gopherState
);
185 \ingroup ServerProtocolGopherInternal
186 * Create MIME Header for Gopher Data
189 gopherMimeCreate(GopherStateData
* gopherState
)
191 StoreEntry
*entry
= gopherState
->entry
;
192 const char *mime_type
= NULL
;
193 const char *mime_enc
= NULL
;
195 switch (gopherState
->type_id
) {
197 case GOPHER_DIRECTORY
:
206 mime_type
= "text/html";
213 case GOPHER_PLUS_IMAGE
:
214 mime_type
= "image/gif";
219 case GOPHER_PLUS_SOUND
:
220 mime_type
= "audio/basic";
223 case GOPHER_PLUS_MOVIE
:
224 mime_type
= "video/mpeg";
227 case GOPHER_MACBINHEX
:
231 case GOPHER_UUENCODED
:
234 /* Rightnow We have no idea what it is. */
235 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
236 mime_type
= mimeGetContentType(gopherState
->request
);
238 mime_type
= def_gopher_bin
;
244 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
245 mime_type
= mimeGetContentType(gopherState
->request
);
247 mime_type
= def_gopher_text
;
251 assert(entry
->isEmpty());
252 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
254 HttpReply
*reply
= new HttpReply
;
256 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, -1, -1, -2);
258 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
260 entry
->replaceHttpReply(reply
);
264 \ingroup ServerProtocolGopherInternal
265 * Parse a gopher request into components. By Anawat.
268 gopher_request_parse(const HttpRequest
* req
, char *type_id
, char *request
)
270 const char *path
= req
->urlpath
.termedBuf();
275 if (path
&& (*path
== '/'))
278 if (!path
|| !*path
) {
279 *type_id
= GOPHER_DIRECTORY
;
286 xstrncpy(request
, path
+ 1, MAX_URL
);
287 /* convert %xx to char */
288 rfc1738_unescape(request
);
293 \ingroup ServerProtocolGopherAPI
294 * Parse the request to determine whether it is cachable.
296 \param req Request data.
297 \retval 0 Not cachable.
301 gopherCachable(const HttpRequest
* req
)
305 /* parse to see type */
306 gopher_request_parse(req
,
329 /// \ingroup ServerProtocolGopherInternal
331 gopherHTMLHeader(StoreEntry
* e
, const char *title
, const char *substring
)
333 storeAppendPrintf(e
, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
334 storeAppendPrintf(e
, "<HTML><HEAD><TITLE>");
335 storeAppendPrintf(e
, title
, substring
);
336 storeAppendPrintf(e
, "</TITLE>");
337 storeAppendPrintf(e
, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
338 storeAppendPrintf(e
, "</HEAD>\n<BODY><H1>");
339 storeAppendPrintf(e
, title
, substring
);
340 storeAppendPrintf(e
, "</H1>\n");
343 /// \ingroup ServerProtocolGopherInternal
345 gopherHTMLFooter(StoreEntry
* e
)
347 storeAppendPrintf(e
, "<HR noshade size=\"1px\">\n");
348 storeAppendPrintf(e
, "<ADDRESS>\n");
349 storeAppendPrintf(e
, "Generated %s by %s (%s)\n",
350 mkrfc1123(squid_curtime
),
352 visible_appname_string
);
353 storeAppendPrintf(e
, "</ADDRESS></BODY></HTML>\n");
356 /// \ingroup ServerProtocolGopherInternal
358 gopherEndHTML(GopherStateData
* gopherState
)
360 StoreEntry
*e
= gopherState
->entry
;
362 if (!gopherState
->HTML_header_added
) {
363 gopherHTMLHeader(e
, "Server Return Nothing", NULL
);
364 storeAppendPrintf(e
, "<P>The Gopher query resulted in a blank response</P>");
365 } else if (gopherState
->HTML_pre
) {
366 storeAppendPrintf(e
, "</PRE>\n");
373 \ingroup ServerProtocolGopherInternal
374 * Convert Gopher to HTML.
376 * Borrow part of code from libwww2 came with Mosaic distribution.
379 gopherToHTML(GopherStateData
* gopherState
, char *inbuf
, int len
)
384 LOCAL_ARRAY(char, line
, TEMP_BUF_SIZE
);
385 LOCAL_ARRAY(char, tmpbuf
, TEMP_BUF_SIZE
);
387 char *selector
= NULL
;
390 char *escaped_selector
= NULL
;
391 const char *icon_url
= NULL
;
393 StoreEntry
*entry
= NULL
;
395 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
396 memset(line
, '\0', TEMP_BUF_SIZE
);
398 entry
= gopherState
->entry
;
400 if (gopherState
->conversion
== gopher_ds::HTML_INDEX_PAGE
) {
401 char *html_url
= html_quote(entry
->url());
402 gopherHTMLHeader(entry
, "Gopher Index %s", html_url
);
403 storeAppendPrintf(entry
,
404 "<p>This is a searchable Gopher index. Use the search\n"
405 "function of your browser to enter search terms.\n"
407 gopherHTMLFooter(entry
);
408 /* now let start sending stuff to client */
410 gopherState
->HTML_header_added
= 1;
415 if (gopherState
->conversion
== gopher_ds::HTML_CSO_PAGE
) {
416 char *html_url
= html_quote(entry
->url());
417 gopherHTMLHeader(entry
, "CSO Search of %s", html_url
);
418 storeAppendPrintf(entry
,
419 "<P>A CSO database usually contains a phonebook or\n"
420 "directory. Use the search function of your browser to enter\n"
421 "search terms.</P><ISINDEX>\n");
422 gopherHTMLFooter(entry
);
423 /* now let start sending stuff to client */
425 gopherState
->HTML_header_added
= 1;
433 if (!gopherState
->HTML_header_added
) {
434 if (gopherState
->conversion
== gopher_ds::HTML_CSO_RESULT
)
435 gopherHTMLHeader(entry
, "CSO Search Result", NULL
);
437 gopherHTMLHeader(entry
, "Gopher Menu", NULL
);
439 outbuf
.append ("<PRE>");
441 gopherState
->HTML_header_added
= 1;
443 gopherState
->HTML_pre
= 1;
446 while ((pos
!= NULL
) && (pos
< inbuf
+ len
)) {
448 if (gopherState
->len
!= 0) {
449 /* there is something left from last tx. */
450 xstrncpy(line
, gopherState
->buf
, gopherState
->len
+ 1);
452 if (gopherState
->len
+ len
> TEMP_BUF_SIZE
) {
453 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
454 len
= TEMP_BUF_SIZE
- gopherState
->len
;
457 lpos
= (char *) memccpy(line
+ gopherState
->len
, inbuf
, '\n', len
);
462 /* there is no complete line in inbuf */
463 /* copy it to temp buffer */
465 if (gopherState
->len
+ len
> TEMP_BUF_SIZE
) {
466 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
467 len
= TEMP_BUF_SIZE
- gopherState
->len
;
470 xmemcpy(gopherState
->buf
+ gopherState
->len
, inbuf
, len
);
471 gopherState
->len
+= len
;
476 pos
= (char *) memchr(pos
, '\n', len
);
481 /* we're done with the remain from last tx. */
482 gopherState
->len
= 0;
484 *(gopherState
->buf
) = '\0';
487 lpos
= (char *) memccpy(line
, pos
, '\n', len
- (pos
- inbuf
));
492 /* there is no complete line in inbuf */
493 /* copy it to temp buffer */
495 if ((len
- (pos
- inbuf
)) > TEMP_BUF_SIZE
) {
496 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
500 if (len
> (pos
- inbuf
)) {
501 xmemcpy(gopherState
->buf
, pos
, len
- (pos
- inbuf
));
502 gopherState
->len
= len
- (pos
- inbuf
);
509 pos
= (char *) memchr(pos
, '\n', len
);
516 /* at this point. We should have one line in buffer to process */
520 memset(line
, '\0', TEMP_BUF_SIZE
);
524 switch (gopherState
->conversion
) {
526 case gopher_ds::HTML_INDEX_RESULT
:
528 case gopher_ds::HTML_DIR
: {
532 selector
= strchr(tline
, TAB
);
536 host
= strchr(selector
, TAB
);
540 port
= strchr(host
, TAB
);
545 junk
= strchr(host
, TAB
);
548 *junk
++ = 0; /* Chop port */
550 junk
= strchr(host
, '\r');
553 *junk
++ = 0; /* Chop port */
555 junk
= strchr(host
, '\n');
558 *junk
++ = 0; /* Chop port */
562 if ((port
[1] == '0') && (!port
[2]))
563 port
[0] = 0; /* 0 means none */
566 /* escape a selector here */
567 escaped_selector
= xstrdup(rfc1738_escape_part(selector
));
571 case GOPHER_DIRECTORY
:
572 icon_url
= mimeGetIconURL("internal-menu");
578 icon_url
= mimeGetIconURL("internal-text");
584 icon_url
= mimeGetIconURL("internal-index");
591 case GOPHER_PLUS_IMAGE
:
592 icon_url
= mimeGetIconURL("internal-image");
597 case GOPHER_PLUS_SOUND
:
598 icon_url
= mimeGetIconURL("internal-sound");
601 case GOPHER_PLUS_MOVIE
:
602 icon_url
= mimeGetIconURL("internal-movie");
608 icon_url
= mimeGetIconURL("internal-telnet");
613 case GOPHER_MACBINHEX
:
617 case GOPHER_UUENCODED
:
618 icon_url
= mimeGetIconURL("internal-binary");
626 icon_url
= mimeGetIconURL("internal-unknown");
630 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
632 if ((gtype
== GOPHER_TELNET
) || (gtype
== GOPHER_3270
)) {
633 if (strlen(escaped_selector
) != 0)
634 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
635 icon_url
, escaped_selector
, rfc1738_escape_part(host
),
636 *port
? ":" : "", port
, html_quote(name
));
638 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
639 icon_url
, rfc1738_escape_part(host
), *port
? ":" : "",
640 port
, html_quote(name
));
642 } else if (gtype
== GOPHER_INFO
) {
643 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "\t%s\n", html_quote(name
));
645 if (strncmp(selector
, "GET /", 5) == 0) {
647 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
648 icon_url
, host
, rfc1738_escape_unescaped(selector
+ 5), html_quote(name
));
651 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
652 icon_url
, host
, gtype
, escaped_selector
, html_quote(name
));
656 safe_free(escaped_selector
);
657 outbuf
.append(tmpbuf
);
659 memset(line
, '\0', TEMP_BUF_SIZE
);
663 memset(line
, '\0', TEMP_BUF_SIZE
);
668 } /* HTML_DIR, HTML_INDEX_RESULT */
671 case gopher_ds::HTML_CSO_RESULT
: {
672 if (line
[0] == '-') {
674 char *s_code
, *s_recno
, *result
;
676 s_code
= strtok(line
+ 1, ":\n");
677 s_recno
= strtok(NULL
, ":\n");
678 result
= strtok(NULL
, "\n");
685 recno
= atoi(s_recno
);
690 if (gopherState
->cso_recno
!= recno
) {
691 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno
, html_quote(result
));
692 gopherState
->cso_recno
= recno
;
694 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "%s\n", html_quote(result
));
697 outbuf
.append(tmpbuf
);
701 char *s_code
, *result
;
703 s_code
= strtok(line
, ":");
704 result
= strtok(NULL
, "\n");
715 /* Do nothing here */
719 case 102: /* Number of matches */
721 case 501: /* No Match */
723 case 502: { /* Too Many Matches */
724 /* Print the message the server returns */
725 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result
));
726 outbuf
.append(tmpbuf
);
734 } /* HTML_CSO_RESULT */
737 break; /* do nothing */
743 if (outbuf
.size() > 0) {
744 entry
->append(outbuf
.rawBuf(), outbuf
.size());
745 /* now let start sending stuff to client */
753 /// \ingroup ServerProtocolGopherInternal
755 gopherTimeout(int fd
, void *data
)
757 GopherStateData
*gopherState
= (GopherStateData
*)data
;
758 StoreEntry
*entry
= gopherState
->entry
;
759 debugs(10, 4, "gopherTimeout: FD " << fd
<< ": '" << entry
->url() << "'" );
761 gopherState
->fwd
->fail(errorCon(ERR_READ_TIMEOUT
, HTTP_GATEWAY_TIMEOUT
, gopherState
->fwd
->request
));
767 \ingroup ServerProtocolGopherInternal
768 * This will be called when data is ready to be read from fd.
769 * Read until error or connection closed.
772 gopherReadReply(int fd
, char *buf
, size_t len
, comm_err_t flag
, int xerrno
, void *data
)
774 GopherStateData
*gopherState
= (GopherStateData
*)data
;
775 StoreEntry
*entry
= gopherState
->entry
;
778 size_t read_sz
= BUFSIZ
;
779 int do_next_read
= 0;
781 DelayId delayId
= entry
->mem_obj
->mostBytesAllowed();
784 /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
786 if (flag
== COMM_ERR_CLOSING
) {
790 assert(buf
== gopherState
->replybuf
);
792 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
799 read_sz
= delayId
.bytesWanted(1, read_sz
);
802 /* leave one space for \0 in gopherToHTML */
804 if (flag
== COMM_OK
&& len
> 0) {
806 delayId
.bytesIn(len
);
809 kb_incr(&statCounter
.server
.all
.kbytes_in
, len
);
810 kb_incr(&statCounter
.server
.other
.kbytes_in
, len
);
813 debugs(10, 5, "gopherReadReply: FD " << fd
<< " read len=" << len
);
815 if (flag
== COMM_OK
&& len
> 0) {
816 commSetTimeout(fd
, Config
.Timeout
.read
, NULL
, NULL
);
817 IOStats
.Gopher
.reads
++;
819 for (clen
= len
- 1, bin
= 0; clen
; bin
++)
822 IOStats
.Gopher
.read_hist
[bin
]++;
824 HttpRequest
*req
= gopherState
->fwd
->request
;
825 if (req
->hier
.bodyBytesRead
< 0)
826 req
->hier
.bodyBytesRead
= 0;
828 req
->hier
.bodyBytesRead
+= len
;
832 if (flag
!= COMM_OK
|| len
< 0) {
833 debugs(50, 1, "gopherReadReply: error reading: " << xstrerror());
835 if (ignoreErrno(errno
)) {
839 err
= errorCon(ERR_READ_ERROR
, HTTP_INTERNAL_SERVER_ERROR
, gopherState
->fwd
->request
);
841 gopherState
->fwd
->fail(err
);
845 } else if (len
== 0 && entry
->isEmpty()) {
846 gopherState
->fwd
->fail(errorCon(ERR_ZERO_SIZE_OBJECT
, HTTP_SERVICE_UNAVAILABLE
, gopherState
->fwd
->request
));
849 } else if (len
== 0) {
850 /* Connection closed; retrieval done. */
851 /* flush the rest of data in temp buf if there is one. */
853 if (gopherState
->conversion
!= gopher_ds::NORMAL
)
854 gopherEndHTML(gopherState
);
856 entry
->timestampsSet();
860 gopherState
->fwd
->complete();
866 if (gopherState
->conversion
!= gopher_ds::NORMAL
) {
867 gopherToHTML(gopherState
, buf
, len
);
869 entry
->append(buf
, len
);
876 comm_read(fd
, buf
, read_sz
, gopherReadReply
, gopherState
);
882 \ingroup ServerProtocolGopherInternal
883 * This will be called when request write is complete. Schedule read of reply.
886 gopherSendComplete(int fd
, char *buf
, size_t size
, comm_err_t errflag
, int xerrno
, void *data
)
888 GopherStateData
*gopherState
= (GopherStateData
*) data
;
889 StoreEntry
*entry
= gopherState
->entry
;
890 debugs(10, 5, "gopherSendComplete: FD " << fd
<< " size: " << size
<< " errflag: " << errflag
);
893 fd_bytes(fd
, size
, FD_WRITE
);
894 kb_incr(&statCounter
.server
.all
.kbytes_out
, size
);
895 kb_incr(&statCounter
.server
.other
.kbytes_out
, size
);
900 err
= errorCon(ERR_WRITE_ERROR
, HTTP_SERVICE_UNAVAILABLE
, gopherState
->fwd
->request
);
902 err
->port
= gopherState
->fwd
->request
->port
;
903 err
->url
= xstrdup(entry
->url());
904 gopherState
->fwd
->fail(err
);
908 memFree(buf
, MEM_4K_BUF
); /* Allocated by gopherSendRequest. */
914 * OK. We successfully reach remote site. Start MIME typing
915 * stuff. Do it anyway even though request is not HTML type.
919 gopherMimeCreate(gopherState
);
921 switch (gopherState
->type_id
) {
923 case GOPHER_DIRECTORY
:
924 /* we got to convert it first */
925 gopherState
->conversion
= gopher_ds::HTML_DIR
;
926 gopherState
->HTML_header_added
= 0;
930 /* we got to convert it first */
931 gopherState
->conversion
= gopher_ds::HTML_INDEX_RESULT
;
932 gopherState
->HTML_header_added
= 0;
936 /* we got to convert it first */
937 gopherState
->conversion
= gopher_ds::HTML_CSO_RESULT
;
938 gopherState
->cso_recno
= 0;
939 gopherState
->HTML_header_added
= 0;
943 gopherState
->conversion
= gopher_ds::NORMAL
;
947 /* Schedule read reply. */
948 AsyncCall::Pointer call
= commCbCall(10,5, "gopherReadReply",
949 CommIoCbPtrFun(gopherReadReply
, gopherState
));
950 entry
->delayAwareRead(fd
, gopherState
->replybuf
, BUFSIZ
, call
);
953 memFree(buf
, MEM_4K_BUF
); /* Allocated by gopherSendRequest. */
957 \ingroup ServerProtocolGopherInternal
958 * This will be called when connect completes. Write request.
961 gopherSendRequest(int fd
, void *data
)
963 GopherStateData
*gopherState
= (GopherStateData
*)data
;
964 char *buf
= (char *)memAllocate(MEM_4K_BUF
);
966 if (gopherState
->type_id
== GOPHER_CSO
) {
967 const char *t
= strchr(gopherState
->request
, '?');
970 t
++; /* skip the ? */
974 snprintf(buf
, 4096, "query %s\r\nquit\r\n", t
);
975 } else if (gopherState
->type_id
== GOPHER_INDEX
) {
976 char *t
= strchr(gopherState
->request
, '?');
981 snprintf(buf
, 4096, "%s\r\n", gopherState
->request
);
983 snprintf(buf
, 4096, "%s\r\n", gopherState
->request
);
986 debugs(10, 5, "gopherSendRequest: FD " << fd
);
987 AsyncCall::Pointer call
= commCbCall(5,5, "gopherSendComplete",
988 CommIoCbPtrFun(gopherSendComplete
, gopherState
));
989 Comm::Write(fd
, buf
, strlen(buf
), call
, NULL
);
991 if (EBIT_TEST(gopherState
->entry
->flags
, ENTRY_CACHABLE
))
992 gopherState
->entry
->setPublicKey(); /* Make it public */
995 /// \ingroup ServerProtocolGopherInternal
996 CBDATA_TYPE(GopherStateData
);
998 /// \ingroup ServerProtocolGopherAPI
1000 gopherStart(FwdState
* fwd
)
1002 int fd
= fwd
->server_fd
;
1003 StoreEntry
*entry
= fwd
->entry
;
1004 GopherStateData
*gopherState
;
1005 CBDATA_INIT_TYPE(GopherStateData
);
1006 gopherState
= cbdataAlloc(GopherStateData
);
1007 gopherState
->buf
= (char *)memAllocate(MEM_4K_BUF
);
1010 gopherState
->entry
= entry
;
1012 gopherState
->fwd
= fwd
;
1014 debugs(10, 3, "gopherStart: " << entry
->url() );
1016 statCounter
.server
.all
.requests
++;
1018 statCounter
.server
.other
.requests
++;
1021 gopher_request_parse(fwd
->request
,
1022 &gopherState
->type_id
, gopherState
->request
);
1024 comm_add_close_handler(fd
, gopherStateFree
, gopherState
);
1026 if (((gopherState
->type_id
== GOPHER_INDEX
) || (gopherState
->type_id
== GOPHER_CSO
))
1027 && (strchr(gopherState
->request
, '?') == NULL
)) {
1028 /* Index URL without query word */
1029 /* We have to generate search page back to client. No need for connection */
1030 gopherMimeCreate(gopherState
);
1032 if (gopherState
->type_id
== GOPHER_INDEX
) {
1033 gopherState
->conversion
= gopher_ds::HTML_INDEX_PAGE
;
1035 if (gopherState
->type_id
== GOPHER_CSO
) {
1036 gopherState
->conversion
= gopher_ds::HTML_CSO_PAGE
;
1038 gopherState
->conversion
= gopher_ds::HTML_INDEX_PAGE
;
1042 gopherToHTML(gopherState
, (char *) NULL
, 0);
1048 gopherState
->fd
= fd
;
1049 gopherState
->fwd
= fwd
;
1050 gopherSendRequest(fd
, gopherState
);
1051 commSetTimeout(fd
, Config
.Timeout
.read
, gopherTimeout
, gopherState
);