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 "errorpage.h"
39 #include "HttpRequest.h"
40 #include "HttpReply.h"
43 #include "DelayPools.h"
44 #include "MemObject.h"
49 #include "SquidTime.h"
52 \defgroup ServerProtocolGopherInternal Server-Side Gopher Internals
53 \ingroup ServerProtocolGopherAPI
54 * Gopher is somewhat complex and gross because it must convert from
55 * the Gopher protocol to HTTP.
58 /* gopher type code from rfc. Anawat. */
59 /// \ingroup ServerProtocolGopherInternal
60 #define GOPHER_FILE '0'
61 /// \ingroup ServerProtocolGopherInternal
62 #define GOPHER_DIRECTORY '1'
63 /// \ingroup ServerProtocolGopherInternal
64 #define GOPHER_CSO '2'
65 /// \ingroup ServerProtocolGopherInternal
66 #define GOPHER_ERROR '3'
67 /// \ingroup ServerProtocolGopherInternal
68 #define GOPHER_MACBINHEX '4'
69 /// \ingroup ServerProtocolGopherInternal
70 #define GOPHER_DOSBIN '5'
71 /// \ingroup ServerProtocolGopherInternal
72 #define GOPHER_UUENCODED '6'
73 /// \ingroup ServerProtocolGopherInternal
74 #define GOPHER_INDEX '7'
75 /// \ingroup ServerProtocolGopherInternal
76 #define GOPHER_TELNET '8'
77 /// \ingroup ServerProtocolGopherInternal
78 #define GOPHER_BIN '9'
79 /// \ingroup ServerProtocolGopherInternal
80 #define GOPHER_REDUNT '+'
81 /// \ingroup ServerProtocolGopherInternal
82 #define GOPHER_3270 'T'
83 /// \ingroup ServerProtocolGopherInternal
84 #define GOPHER_GIF 'g'
85 /// \ingroup ServerProtocolGopherInternal
86 #define GOPHER_IMAGE 'I'
88 /// \ingroup ServerProtocolGopherInternal
89 #define GOPHER_HTML 'h' /* HTML */
90 /// \ingroup ServerProtocolGopherInternal
91 #define GOPHER_INFO 'i'
93 \ingroup ServerProtocolGopherInternal
96 #define GOPHER_WWW 'w'
97 /// \ingroup ServerProtocolGopherInternal
98 #define GOPHER_SOUND 's'
100 /// \ingroup ServerProtocolGopherInternal
101 #define GOPHER_PLUS_IMAGE ':'
102 /// \ingroup ServerProtocolGopherInternal
103 #define GOPHER_PLUS_MOVIE ';'
104 /// \ingroup ServerProtocolGopherInternal
105 #define GOPHER_PLUS_SOUND '<'
107 /// \ingroup ServerProtocolGopherInternal
108 #define GOPHER_PORT 70
110 /// \ingroup ServerProtocolGopherInternal
112 /// \ingroup ServerProtocolGopherInternal
113 /// \todo CODE: should this be a protocol-specific thing?
114 #define TEMP_BUF_SIZE 4096
115 /// \ingroup ServerProtocolGopherInternal
116 #define MAX_CSO_RESULT 1024
118 /// \ingroup ServerProtocolGopherInternal
119 typedef struct gopher_ds
{
129 int HTML_header_added
;
132 char request
[MAX_URL
];
135 char *buf
; /* pts to a 4k page */
138 FwdState::Pointer fwd
;
139 char replybuf
[BUFSIZ
];
142 static PF gopherStateFree
;
143 static void gopherMimeCreate(GopherStateData
*);
144 static void gopher_request_parse(const HttpRequest
* req
,
147 static void gopherEndHTML(GopherStateData
*);
148 static void gopherToHTML(GopherStateData
*, char *inbuf
, int len
);
149 static PF gopherTimeout
;
150 static IOCB gopherReadReply
;
151 static IOCB gopherSendComplete
;
152 static PF gopherSendRequest
;
154 /// \ingroup ServerProtocolGopherInternal
155 static char def_gopher_bin
[] = "www/unknown";
157 /// \ingroup ServerProtocolGopherInternal
158 static char def_gopher_text
[] = "text/plain";
160 /// \ingroup ServerProtocolGopherInternal
162 gopherStateFree(int fdnotused
, void *data
)
164 GopherStateData
*gopherState
= (GopherStateData
*)data
;
166 if (gopherState
== NULL
)
169 if (gopherState
->entry
) {
170 gopherState
->entry
->unlock();
173 HTTPMSGUNLOCK(gopherState
->req
);
175 gopherState
->fwd
= NULL
; // refcounted
177 memFree(gopherState
->buf
, MEM_4K_BUF
);
178 gopherState
->buf
= NULL
;
179 cbdataFree(gopherState
);
183 \ingroup ServerProtocolGopherInternal
184 * Create MIME Header for Gopher Data
187 gopherMimeCreate(GopherStateData
* gopherState
)
189 StoreEntry
*entry
= gopherState
->entry
;
190 const char *mime_type
= NULL
;
191 const char *mime_enc
= NULL
;
193 switch (gopherState
->type_id
) {
195 case GOPHER_DIRECTORY
:
204 mime_type
= "text/html";
211 case GOPHER_PLUS_IMAGE
:
212 mime_type
= "image/gif";
217 case GOPHER_PLUS_SOUND
:
218 mime_type
= "audio/basic";
221 case GOPHER_PLUS_MOVIE
:
222 mime_type
= "video/mpeg";
225 case GOPHER_MACBINHEX
:
229 case GOPHER_UUENCODED
:
232 /* Rightnow We have no idea what it is. */
233 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
234 mime_type
= mimeGetContentType(gopherState
->request
);
236 mime_type
= def_gopher_bin
;
242 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
243 mime_type
= mimeGetContentType(gopherState
->request
);
245 mime_type
= def_gopher_text
;
249 assert(entry
->isEmpty());
250 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
252 HttpReply
*reply
= new HttpReply
;
254 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, -1, -1, -2);
256 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
258 entry
->replaceHttpReply(reply
);
262 \ingroup ServerProtocolGopherInternal
263 * Parse a gopher request into components. By Anawat.
266 gopher_request_parse(const HttpRequest
* req
, char *type_id
, char *request
)
268 const char *path
= req
->urlpath
.termedBuf();
273 if (path
&& (*path
== '/'))
276 if (!path
|| !*path
) {
277 *type_id
= GOPHER_DIRECTORY
;
284 xstrncpy(request
, path
+ 1, MAX_URL
);
285 /* convert %xx to char */
286 rfc1738_unescape(request
);
291 \ingroup ServerProtocolGopherAPI
292 * Parse the request to determine whether it is cachable.
294 \param req Request data.
295 \retval 0 Not cachable.
299 gopherCachable(const HttpRequest
* req
)
303 /* parse to see type */
304 gopher_request_parse(req
,
327 /// \ingroup ServerProtocolGopherInternal
329 gopherHTMLHeader(StoreEntry
* e
, const char *title
, const char *substring
)
331 storeAppendPrintf(e
, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
332 storeAppendPrintf(e
, "<HTML><HEAD><TITLE>");
333 storeAppendPrintf(e
, title
, substring
);
334 storeAppendPrintf(e
, "</TITLE>");
335 storeAppendPrintf(e
, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
336 storeAppendPrintf(e
, "</HEAD>\n<BODY><H1>");
337 storeAppendPrintf(e
, title
, substring
);
338 storeAppendPrintf(e
, "</H1>\n");
341 /// \ingroup ServerProtocolGopherInternal
343 gopherHTMLFooter(StoreEntry
* e
)
345 storeAppendPrintf(e
, "<HR noshade size=\"1px\">\n");
346 storeAppendPrintf(e
, "<ADDRESS>\n");
347 storeAppendPrintf(e
, "Generated %s by %s (%s)\n",
348 mkrfc1123(squid_curtime
),
350 visible_appname_string
);
351 storeAppendPrintf(e
, "</ADDRESS></BODY></HTML>\n");
354 /// \ingroup ServerProtocolGopherInternal
356 gopherEndHTML(GopherStateData
* gopherState
)
358 StoreEntry
*e
= gopherState
->entry
;
360 if (!gopherState
->HTML_header_added
) {
361 gopherHTMLHeader(e
, "Server Return Nothing", NULL
);
362 storeAppendPrintf(e
, "<P>The Gopher query resulted in a blank response</P>");
363 } else if (gopherState
->HTML_pre
) {
364 storeAppendPrintf(e
, "</PRE>\n");
371 \ingroup ServerProtocolGopherInternal
372 * Convert Gopher to HTML.
374 * Borrow part of code from libwww2 came with Mosaic distribution.
377 gopherToHTML(GopherStateData
* gopherState
, char *inbuf
, int len
)
382 LOCAL_ARRAY(char, line
, TEMP_BUF_SIZE
);
383 LOCAL_ARRAY(char, tmpbuf
, TEMP_BUF_SIZE
);
385 char *selector
= NULL
;
388 char *escaped_selector
= NULL
;
389 const char *icon_url
= NULL
;
391 StoreEntry
*entry
= NULL
;
393 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
394 memset(line
, '\0', TEMP_BUF_SIZE
);
396 entry
= gopherState
->entry
;
398 if (gopherState
->conversion
== gopher_ds::HTML_INDEX_PAGE
) {
399 char *html_url
= html_quote(entry
->url());
400 gopherHTMLHeader(entry
, "Gopher Index %s", html_url
);
401 storeAppendPrintf(entry
,
402 "<p>This is a searchable Gopher index. Use the search\n"
403 "function of your browser to enter search terms.\n"
405 gopherHTMLFooter(entry
);
406 /* now let start sending stuff to client */
408 gopherState
->HTML_header_added
= 1;
413 if (gopherState
->conversion
== gopher_ds::HTML_CSO_PAGE
) {
414 char *html_url
= html_quote(entry
->url());
415 gopherHTMLHeader(entry
, "CSO Search of %s", html_url
);
416 storeAppendPrintf(entry
,
417 "<P>A CSO database usually contains a phonebook or\n"
418 "directory. Use the search function of your browser to enter\n"
419 "search terms.</P><ISINDEX>\n");
420 gopherHTMLFooter(entry
);
421 /* now let start sending stuff to client */
423 gopherState
->HTML_header_added
= 1;
430 if (!gopherState
->HTML_header_added
) {
431 if (gopherState
->conversion
== gopher_ds::HTML_CSO_RESULT
)
432 gopherHTMLHeader(entry
, "CSO Search Result", NULL
);
434 gopherHTMLHeader(entry
, "Gopher Menu", NULL
);
436 outbuf
.append ("<PRE>");
438 gopherState
->HTML_header_added
= 1;
440 gopherState
->HTML_pre
= 1;
443 while (pos
< inbuf
+ len
) {
445 int left
= len
- (pos
- inbuf
);
446 lpos
= (char *)memchr(pos
, '\n', left
);
448 lpos
++; /* Next line is after \n */
453 if (gopherState
->len
+ llen
>= TEMP_BUF_SIZE
) {
454 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
455 llen
= TEMP_BUF_SIZE
- gopherState
->len
- 1;
458 /* there is no complete line in inbuf */
459 /* copy it to temp buffer */
460 /* note: llen is adjusted above */
461 xmemcpy(gopherState
->buf
+ gopherState
->len
, pos
, llen
);
462 gopherState
->len
+= llen
;
466 /* there is no complete line in inbuf */
467 /* copy it to temp buffer */
468 /* note: llen is adjusted above */
469 xmemcpy(gopherState
->buf
+ gopherState
->len
, pos
, llen
);
470 gopherState
->len
+= llen
;
473 if (gopherState
->len
!= 0) {
474 /* there is something left from last tx. */
475 xmemcpy(line
, gopherState
->buf
, gopherState
->len
);
476 xmemcpy(line
+ gopherState
->len
, pos
, llen
);
477 llen
+= gopherState
->len
;
478 gopherState
->len
= 0;
480 xmemcpy(line
, pos
, llen
);
482 line
[llen
+ 1] = '\0';
483 /* move input to next line */
486 /* at this point. We should have one line in buffer to process */
490 memset(line
, '\0', TEMP_BUF_SIZE
);
494 switch (gopherState
->conversion
) {
496 case gopher_ds::HTML_INDEX_RESULT
:
498 case gopher_ds::HTML_DIR
: {
502 selector
= strchr(tline
, TAB
);
506 host
= strchr(selector
, TAB
);
510 port
= strchr(host
, TAB
);
515 junk
= strchr(host
, TAB
);
518 *junk
++ = 0; /* Chop port */
520 junk
= strchr(host
, '\r');
523 *junk
++ = 0; /* Chop port */
525 junk
= strchr(host
, '\n');
528 *junk
++ = 0; /* Chop port */
532 if ((port
[1] == '0') && (!port
[2]))
533 port
[0] = 0; /* 0 means none */
536 /* escape a selector here */
537 escaped_selector
= xstrdup(rfc1738_escape_part(selector
));
541 case GOPHER_DIRECTORY
:
542 icon_url
= mimeGetIconURL("internal-menu");
548 icon_url
= mimeGetIconURL("internal-text");
554 icon_url
= mimeGetIconURL("internal-index");
561 case GOPHER_PLUS_IMAGE
:
562 icon_url
= mimeGetIconURL("internal-image");
567 case GOPHER_PLUS_SOUND
:
568 icon_url
= mimeGetIconURL("internal-sound");
571 case GOPHER_PLUS_MOVIE
:
572 icon_url
= mimeGetIconURL("internal-movie");
578 icon_url
= mimeGetIconURL("internal-telnet");
583 case GOPHER_MACBINHEX
:
587 case GOPHER_UUENCODED
:
588 icon_url
= mimeGetIconURL("internal-binary");
596 icon_url
= mimeGetIconURL("internal-unknown");
600 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
602 if ((gtype
== GOPHER_TELNET
) || (gtype
== GOPHER_3270
)) {
603 if (strlen(escaped_selector
) != 0)
604 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
605 icon_url
, escaped_selector
, rfc1738_escape_part(host
),
606 *port
? ":" : "", port
, html_quote(name
));
608 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
609 icon_url
, rfc1738_escape_part(host
), *port
? ":" : "",
610 port
, html_quote(name
));
612 } else if (gtype
== GOPHER_INFO
) {
613 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "\t%s\n", html_quote(name
));
615 if (strncmp(selector
, "GET /", 5) == 0) {
617 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
618 icon_url
, host
, rfc1738_escape_unescaped(selector
+ 5), html_quote(name
));
621 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
622 icon_url
, host
, gtype
, escaped_selector
, html_quote(name
));
626 safe_free(escaped_selector
);
627 outbuf
.append(tmpbuf
);
629 memset(line
, '\0', TEMP_BUF_SIZE
);
633 memset(line
, '\0', TEMP_BUF_SIZE
);
638 } /* HTML_DIR, HTML_INDEX_RESULT */
641 case gopher_ds::HTML_CSO_RESULT
: {
642 if (line
[0] == '-') {
644 char *s_code
, *s_recno
, *result
;
646 s_code
= strtok(line
+ 1, ":\n");
647 s_recno
= strtok(NULL
, ":\n");
648 result
= strtok(NULL
, "\n");
655 recno
= atoi(s_recno
);
660 if (gopherState
->cso_recno
!= recno
) {
661 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno
, html_quote(result
));
662 gopherState
->cso_recno
= recno
;
664 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "%s\n", html_quote(result
));
667 outbuf
.append(tmpbuf
);
671 char *s_code
, *result
;
673 s_code
= strtok(line
, ":");
674 result
= strtok(NULL
, "\n");
685 /* Do nothing here */
689 case 102: /* Number of matches */
691 case 501: /* No Match */
693 case 502: { /* Too Many Matches */
694 /* Print the message the server returns */
695 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result
));
696 outbuf
.append(tmpbuf
);
704 } /* HTML_CSO_RESULT */
707 break; /* do nothing */
713 if (outbuf
.size() > 0) {
714 entry
->append(outbuf
.rawBuf(), outbuf
.size());
715 /* now let start sending stuff to client */
723 /// \ingroup ServerProtocolGopherInternal
725 gopherTimeout(int fd
, void *data
)
727 GopherStateData
*gopherState
= (GopherStateData
*)data
;
728 StoreEntry
*entry
= gopherState
->entry
;
729 debugs(10, 4, "gopherTimeout: FD " << fd
<< ": '" << entry
->url() << "'" );
731 gopherState
->fwd
->fail(errorCon(ERR_READ_TIMEOUT
, HTTP_GATEWAY_TIMEOUT
, gopherState
->fwd
->request
));
737 \ingroup ServerProtocolGopherInternal
738 * This will be called when data is ready to be read from fd.
739 * Read until error or connection closed.
742 gopherReadReply(int fd
, char *buf
, size_t len
, comm_err_t flag
, int xerrno
, void *data
)
744 GopherStateData
*gopherState
= (GopherStateData
*)data
;
745 StoreEntry
*entry
= gopherState
->entry
;
748 size_t read_sz
= BUFSIZ
;
749 int do_next_read
= 0;
752 DelayId delayId
= entry
->mem_obj
->mostBytesAllowed();
755 /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
757 if (flag
== COMM_ERR_CLOSING
) {
761 assert(buf
== gopherState
->replybuf
);
763 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
771 read_sz
= delayId
.bytesWanted(1, read_sz
);
774 /* leave one space for \0 in gopherToHTML */
776 if (flag
== COMM_OK
&& len
> 0) {
778 delayId
.bytesIn(len
);
781 kb_incr(&statCounter
.server
.all
.kbytes_in
, len
);
782 kb_incr(&statCounter
.server
.other
.kbytes_in
, len
);
785 debugs(10, 5, "gopherReadReply: FD " << fd
<< " read len=" << len
);
787 if (flag
== COMM_OK
&& len
> 0) {
788 commSetTimeout(fd
, Config
.Timeout
.read
, NULL
, NULL
);
789 IOStats
.Gopher
.reads
++;
791 for (clen
= len
- 1, bin
= 0; clen
; bin
++)
794 IOStats
.Gopher
.read_hist
[bin
]++;
797 if (flag
!= COMM_OK
|| len
< 0) {
798 debugs(50, 1, "gopherReadReply: error reading: " << xstrerror());
800 if (ignoreErrno(errno
)) {
804 err
= errorCon(ERR_READ_ERROR
, HTTP_INTERNAL_SERVER_ERROR
, gopherState
->fwd
->request
);
806 gopherState
->fwd
->fail(err
);
810 } else if (len
== 0 && entry
->isEmpty()) {
811 gopherState
->fwd
->fail(errorCon(ERR_ZERO_SIZE_OBJECT
, HTTP_SERVICE_UNAVAILABLE
, gopherState
->fwd
->request
));
814 } else if (len
== 0) {
815 /* Connection closed; retrieval done. */
816 /* flush the rest of data in temp buf if there is one. */
818 if (gopherState
->conversion
!= gopher_ds::NORMAL
)
819 gopherEndHTML(gopherState
);
821 entry
->timestampsSet();
825 gopherState
->fwd
->complete();
831 if (gopherState
->conversion
!= gopher_ds::NORMAL
) {
832 gopherToHTML(gopherState
, buf
, len
);
834 entry
->append(buf
, len
);
841 comm_read(fd
, buf
, read_sz
, gopherReadReply
, gopherState
);
847 \ingroup ServerProtocolGopherInternal
848 * This will be called when request write is complete. Schedule read of reply.
851 gopherSendComplete(int fd
, char *buf
, size_t size
, comm_err_t errflag
, int xerrno
, void *data
)
853 GopherStateData
*gopherState
= (GopherStateData
*) data
;
854 StoreEntry
*entry
= gopherState
->entry
;
855 debugs(10, 5, "gopherSendComplete: FD " << fd
<< " size: " << size
<< " errflag: " << errflag
);
858 fd_bytes(fd
, size
, FD_WRITE
);
859 kb_incr(&statCounter
.server
.all
.kbytes_out
, size
);
860 kb_incr(&statCounter
.server
.other
.kbytes_out
, size
);
865 err
= errorCon(ERR_WRITE_ERROR
, HTTP_SERVICE_UNAVAILABLE
, gopherState
->fwd
->request
);
867 err
->port
= gopherState
->fwd
->request
->port
;
868 err
->url
= xstrdup(entry
->url());
869 gopherState
->fwd
->fail(err
);
873 memFree(buf
, MEM_4K_BUF
); /* Allocated by gopherSendRequest. */
879 * OK. We successfully reach remote site. Start MIME typing
880 * stuff. Do it anyway even though request is not HTML type.
884 gopherMimeCreate(gopherState
);
886 switch (gopherState
->type_id
) {
888 case GOPHER_DIRECTORY
:
889 /* we got to convert it first */
890 gopherState
->conversion
= gopher_ds::HTML_DIR
;
891 gopherState
->HTML_header_added
= 0;
895 /* we got to convert it first */
896 gopherState
->conversion
= gopher_ds::HTML_INDEX_RESULT
;
897 gopherState
->HTML_header_added
= 0;
901 /* we got to convert it first */
902 gopherState
->conversion
= gopher_ds::HTML_CSO_RESULT
;
903 gopherState
->cso_recno
= 0;
904 gopherState
->HTML_header_added
= 0;
908 gopherState
->conversion
= gopher_ds::NORMAL
;
912 /* Schedule read reply. */
913 AsyncCall::Pointer call
= commCbCall(10,5, "gopherReadReply",
914 CommIoCbPtrFun(gopherReadReply
, gopherState
));
915 entry
->delayAwareRead(fd
, gopherState
->replybuf
, BUFSIZ
, call
);
918 memFree(buf
, MEM_4K_BUF
); /* Allocated by gopherSendRequest. */
922 \ingroup ServerProtocolGopherInternal
923 * This will be called when connect completes. Write request.
926 gopherSendRequest(int fd
, void *data
)
928 GopherStateData
*gopherState
= (GopherStateData
*)data
;
929 char *buf
= (char *)memAllocate(MEM_4K_BUF
);
931 if (gopherState
->type_id
== GOPHER_CSO
) {
932 const char *t
= strchr(gopherState
->request
, '?');
935 t
++; /* skip the ? */
939 snprintf(buf
, 4096, "query %s\r\nquit\r\n", t
);
940 } else if (gopherState
->type_id
== GOPHER_INDEX
) {
941 char *t
= strchr(gopherState
->request
, '?');
946 snprintf(buf
, 4096, "%s\r\n", gopherState
->request
);
948 snprintf(buf
, 4096, "%s\r\n", gopherState
->request
);
951 debugs(10, 5, "gopherSendRequest: FD " << fd
);
952 comm_write(fd
, buf
, strlen(buf
), gopherSendComplete
, gopherState
, NULL
);
954 if (EBIT_TEST(gopherState
->entry
->flags
, ENTRY_CACHABLE
))
955 gopherState
->entry
->setPublicKey(); /* Make it public */
958 /// \ingroup ServerProtocolGopherInternal
959 CBDATA_TYPE(GopherStateData
);
961 /// \ingroup ServerProtocolGopherAPI
963 gopherStart(FwdState
* fwd
)
965 int fd
= fwd
->server_fd
;
966 StoreEntry
*entry
= fwd
->entry
;
967 GopherStateData
*gopherState
;
968 CBDATA_INIT_TYPE(GopherStateData
);
969 gopherState
= cbdataAlloc(GopherStateData
);
970 gopherState
->buf
= (char *)memAllocate(MEM_4K_BUF
);
973 gopherState
->entry
= entry
;
975 gopherState
->fwd
= fwd
;
977 debugs(10, 3, "gopherStart: " << entry
->url() );
979 statCounter
.server
.all
.requests
++;
981 statCounter
.server
.other
.requests
++;
984 gopher_request_parse(fwd
->request
,
985 &gopherState
->type_id
, gopherState
->request
);
987 comm_add_close_handler(fd
, gopherStateFree
, gopherState
);
989 if (((gopherState
->type_id
== GOPHER_INDEX
) || (gopherState
->type_id
== GOPHER_CSO
))
990 && (strchr(gopherState
->request
, '?') == NULL
)) {
991 /* Index URL without query word */
992 /* We have to generate search page back to client. No need for connection */
993 gopherMimeCreate(gopherState
);
995 if (gopherState
->type_id
== GOPHER_INDEX
) {
996 gopherState
->conversion
= gopher_ds::HTML_INDEX_PAGE
;
998 if (gopherState
->type_id
== GOPHER_CSO
) {
999 gopherState
->conversion
= gopher_ds::HTML_CSO_PAGE
;
1001 gopherState
->conversion
= gopher_ds::HTML_INDEX_PAGE
;
1005 gopherToHTML(gopherState
, (char *) NULL
, 0);
1011 gopherState
->fd
= fd
;
1012 gopherState
->fwd
= fwd
;
1013 gopherSendRequest(fd
, gopherState
);
1014 commSetTimeout(fd
, Config
.Timeout
.read
, gopherTimeout
, gopherState
);