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 "html_quote.h"
40 #include "HttpRequest.h"
41 #include "HttpReply.h"
44 #include "DelayPools.h"
45 #include "MemObject.h"
50 #include "SquidTime.h"
53 \defgroup ServerProtocolGopherInternal Server-Side Gopher Internals
54 \ingroup ServerProtocolGopherAPI
55 * Gopher is somewhat complex and gross because it must convert from
56 * the Gopher protocol to HTTP.
59 /* gopher type code from rfc. Anawat. */
60 /// \ingroup ServerProtocolGopherInternal
61 #define GOPHER_FILE '0'
62 /// \ingroup ServerProtocolGopherInternal
63 #define GOPHER_DIRECTORY '1'
64 /// \ingroup ServerProtocolGopherInternal
65 #define GOPHER_CSO '2'
66 /// \ingroup ServerProtocolGopherInternal
67 #define GOPHER_ERROR '3'
68 /// \ingroup ServerProtocolGopherInternal
69 #define GOPHER_MACBINHEX '4'
70 /// \ingroup ServerProtocolGopherInternal
71 #define GOPHER_DOSBIN '5'
72 /// \ingroup ServerProtocolGopherInternal
73 #define GOPHER_UUENCODED '6'
74 /// \ingroup ServerProtocolGopherInternal
75 #define GOPHER_INDEX '7'
76 /// \ingroup ServerProtocolGopherInternal
77 #define GOPHER_TELNET '8'
78 /// \ingroup ServerProtocolGopherInternal
79 #define GOPHER_BIN '9'
80 /// \ingroup ServerProtocolGopherInternal
81 #define GOPHER_REDUNT '+'
82 /// \ingroup ServerProtocolGopherInternal
83 #define GOPHER_3270 'T'
84 /// \ingroup ServerProtocolGopherInternal
85 #define GOPHER_GIF 'g'
86 /// \ingroup ServerProtocolGopherInternal
87 #define GOPHER_IMAGE 'I'
89 /// \ingroup ServerProtocolGopherInternal
90 #define GOPHER_HTML 'h' /* HTML */
91 /// \ingroup ServerProtocolGopherInternal
92 #define GOPHER_INFO 'i'
94 \ingroup ServerProtocolGopherInternal
97 #define GOPHER_WWW 'w'
98 /// \ingroup ServerProtocolGopherInternal
99 #define GOPHER_SOUND 's'
101 /// \ingroup ServerProtocolGopherInternal
102 #define GOPHER_PLUS_IMAGE ':'
103 /// \ingroup ServerProtocolGopherInternal
104 #define GOPHER_PLUS_MOVIE ';'
105 /// \ingroup ServerProtocolGopherInternal
106 #define GOPHER_PLUS_SOUND '<'
108 /// \ingroup ServerProtocolGopherInternal
109 #define GOPHER_PORT 70
111 /// \ingroup ServerProtocolGopherInternal
113 /// \ingroup ServerProtocolGopherInternal
114 /// \todo CODE: should this be a protocol-specific thing?
115 #define TEMP_BUF_SIZE 4096
116 /// \ingroup ServerProtocolGopherInternal
117 #define MAX_CSO_RESULT 1024
119 /// \ingroup ServerProtocolGopherInternal
120 typedef struct gopher_ds
{
130 int HTML_header_added
;
133 char request
[MAX_URL
];
136 char *buf
; /* pts to a 4k page */
137 Comm::ConnectionPointer serverConn
;
139 FwdState::Pointer fwd
;
140 char replybuf
[BUFSIZ
];
143 static PF gopherStateFree
;
144 static void gopherMimeCreate(GopherStateData
*);
145 static void gopher_request_parse(const HttpRequest
* req
,
148 static void gopherEndHTML(GopherStateData
*);
149 static void gopherToHTML(GopherStateData
*, char *inbuf
, int len
);
150 static PF gopherTimeout
;
151 static IOCB gopherReadReply
;
152 static IOCB gopherSendComplete
;
153 static PF gopherSendRequest
;
155 /// \ingroup ServerProtocolGopherInternal
156 static char def_gopher_bin
[] = "www/unknown";
158 /// \ingroup ServerProtocolGopherInternal
159 static char def_gopher_text
[] = "text/plain";
161 /// \ingroup ServerProtocolGopherInternal
163 gopherStateFree(int, void *data
)
165 GopherStateData
*gopherState
= (GopherStateData
*)data
;
167 if (gopherState
== NULL
)
170 if (gopherState
->entry
) {
171 gopherState
->entry
->unlock();
174 HTTPMSGUNLOCK(gopherState
->req
);
176 gopherState
->fwd
= NULL
; // refcounted
178 memFree(gopherState
->buf
, MEM_4K_BUF
);
179 gopherState
->buf
= NULL
;
180 cbdataFree(gopherState
);
184 \ingroup ServerProtocolGopherInternal
185 * Create MIME Header for Gopher Data
188 gopherMimeCreate(GopherStateData
* gopherState
)
190 StoreEntry
*entry
= gopherState
->entry
;
191 const char *mime_type
= NULL
;
192 const char *mime_enc
= NULL
;
194 switch (gopherState
->type_id
) {
196 case GOPHER_DIRECTORY
:
205 mime_type
= "text/html";
212 case GOPHER_PLUS_IMAGE
:
213 mime_type
= "image/gif";
218 case GOPHER_PLUS_SOUND
:
219 mime_type
= "audio/basic";
222 case GOPHER_PLUS_MOVIE
:
223 mime_type
= "video/mpeg";
226 case GOPHER_MACBINHEX
:
230 case GOPHER_UUENCODED
:
233 /* Rightnow We have no idea what it is. */
234 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
235 mime_type
= mimeGetContentType(gopherState
->request
);
237 mime_type
= def_gopher_bin
;
243 mime_enc
= mimeGetContentEncoding(gopherState
->request
);
244 mime_type
= mimeGetContentType(gopherState
->request
);
246 mime_type
= def_gopher_text
;
250 assert(entry
->isEmpty());
251 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
253 HttpReply
*reply
= new HttpReply
;
255 reply
->setHeaders(HTTP_OK
, "Gatewaying", mime_type
, -1, -1, -2);
257 reply
->header
.putStr(HDR_CONTENT_ENCODING
, mime_enc
);
259 entry
->replaceHttpReply(reply
);
263 \ingroup ServerProtocolGopherInternal
264 * Parse a gopher request into components. By Anawat.
267 gopher_request_parse(const HttpRequest
* req
, char *type_id
, char *request
)
269 const char *path
= req
->urlpath
.termedBuf();
274 if (path
&& (*path
== '/'))
277 if (!path
|| !*path
) {
278 *type_id
= GOPHER_DIRECTORY
;
285 xstrncpy(request
, path
+ 1, MAX_URL
);
286 /* convert %xx to char */
287 rfc1738_unescape(request
);
292 \ingroup ServerProtocolGopherAPI
293 * Parse the request to determine whether it is cachable.
295 \param req Request data.
296 \retval 0 Not cachable.
300 gopherCachable(const HttpRequest
* req
)
304 /* parse to see type */
305 gopher_request_parse(req
,
328 /// \ingroup ServerProtocolGopherInternal
330 gopherHTMLHeader(StoreEntry
* e
, const char *title
, const char *substring
)
332 storeAppendPrintf(e
, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
333 storeAppendPrintf(e
, "<HTML><HEAD><TITLE>");
334 storeAppendPrintf(e
, title
, substring
);
335 storeAppendPrintf(e
, "</TITLE>");
336 storeAppendPrintf(e
, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
337 storeAppendPrintf(e
, "</HEAD>\n<BODY><H1>");
338 storeAppendPrintf(e
, title
, substring
);
339 storeAppendPrintf(e
, "</H1>\n");
342 /// \ingroup ServerProtocolGopherInternal
344 gopherHTMLFooter(StoreEntry
* e
)
346 storeAppendPrintf(e
, "<HR noshade size=\"1px\">\n");
347 storeAppendPrintf(e
, "<ADDRESS>\n");
348 storeAppendPrintf(e
, "Generated %s by %s (%s)\n",
349 mkrfc1123(squid_curtime
),
351 visible_appname_string
);
352 storeAppendPrintf(e
, "</ADDRESS></BODY></HTML>\n");
355 /// \ingroup ServerProtocolGopherInternal
357 gopherEndHTML(GopherStateData
* gopherState
)
359 StoreEntry
*e
= gopherState
->entry
;
361 if (!gopherState
->HTML_header_added
) {
362 gopherHTMLHeader(e
, "Server Return Nothing", NULL
);
363 storeAppendPrintf(e
, "<P>The Gopher query resulted in a blank response</P>");
364 } else if (gopherState
->HTML_pre
) {
365 storeAppendPrintf(e
, "</PRE>\n");
372 \ingroup ServerProtocolGopherInternal
373 * Convert Gopher to HTML.
375 * Borrow part of code from libwww2 came with Mosaic distribution.
378 gopherToHTML(GopherStateData
* gopherState
, char *inbuf
, int len
)
383 LOCAL_ARRAY(char, line
, TEMP_BUF_SIZE
);
384 LOCAL_ARRAY(char, tmpbuf
, TEMP_BUF_SIZE
);
386 char *selector
= NULL
;
389 char *escaped_selector
= NULL
;
390 const char *icon_url
= NULL
;
392 StoreEntry
*entry
= NULL
;
394 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
395 memset(line
, '\0', TEMP_BUF_SIZE
);
397 entry
= gopherState
->entry
;
399 if (gopherState
->conversion
== gopher_ds::HTML_INDEX_PAGE
) {
400 char *html_url
= html_quote(entry
->url());
401 gopherHTMLHeader(entry
, "Gopher Index %s", html_url
);
402 storeAppendPrintf(entry
,
403 "<p>This is a searchable Gopher index. Use the search\n"
404 "function of your browser to enter search terms.\n"
406 gopherHTMLFooter(entry
);
407 /* now let start sending stuff to client */
409 gopherState
->HTML_header_added
= 1;
414 if (gopherState
->conversion
== gopher_ds::HTML_CSO_PAGE
) {
415 char *html_url
= html_quote(entry
->url());
416 gopherHTMLHeader(entry
, "CSO Search of %s", html_url
);
417 storeAppendPrintf(entry
,
418 "<P>A CSO database usually contains a phonebook or\n"
419 "directory. Use the search function of your browser to enter\n"
420 "search terms.</P><ISINDEX>\n");
421 gopherHTMLFooter(entry
);
422 /* now let start sending stuff to client */
424 gopherState
->HTML_header_added
= 1;
432 if (!gopherState
->HTML_header_added
) {
433 if (gopherState
->conversion
== gopher_ds::HTML_CSO_RESULT
)
434 gopherHTMLHeader(entry
, "CSO Search Result", NULL
);
436 gopherHTMLHeader(entry
, "Gopher Menu", NULL
);
438 outbuf
.append ("<PRE>");
440 gopherState
->HTML_header_added
= 1;
442 gopherState
->HTML_pre
= 1;
445 while ((pos
!= NULL
) && (pos
< inbuf
+ len
)) {
447 if (gopherState
->len
!= 0) {
448 /* there is something left from last tx. */
449 xstrncpy(line
, gopherState
->buf
, gopherState
->len
+ 1);
451 if (gopherState
->len
+ len
> TEMP_BUF_SIZE
) {
452 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
453 len
= TEMP_BUF_SIZE
- gopherState
->len
;
456 lpos
= (char *) memccpy(line
+ gopherState
->len
, inbuf
, '\n', len
);
461 /* there is no complete line in inbuf */
462 /* copy it to temp buffer */
464 if (gopherState
->len
+ len
> TEMP_BUF_SIZE
) {
465 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
466 len
= TEMP_BUF_SIZE
- gopherState
->len
;
469 xmemcpy(gopherState
->buf
+ gopherState
->len
, inbuf
, len
);
470 gopherState
->len
+= len
;
475 pos
= (char *) memchr(pos
, '\n', len
);
480 /* we're done with the remain from last tx. */
481 gopherState
->len
= 0;
483 *(gopherState
->buf
) = '\0';
486 lpos
= (char *) memccpy(line
, pos
, '\n', len
- (pos
- inbuf
));
491 /* there is no complete line in inbuf */
492 /* copy it to temp buffer */
494 if ((len
- (pos
- inbuf
)) > TEMP_BUF_SIZE
) {
495 debugs(10, 1, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry
->url() );
499 if (len
> (pos
- inbuf
)) {
500 xmemcpy(gopherState
->buf
, pos
, len
- (pos
- inbuf
));
501 gopherState
->len
= len
- (pos
- inbuf
);
508 pos
= (char *) memchr(pos
, '\n', len
);
515 /* at this point. We should have one line in buffer to process */
519 memset(line
, '\0', TEMP_BUF_SIZE
);
523 switch (gopherState
->conversion
) {
525 case gopher_ds::HTML_INDEX_RESULT
:
527 case gopher_ds::HTML_DIR
: {
531 selector
= strchr(tline
, TAB
);
535 host
= strchr(selector
, TAB
);
539 port
= strchr(host
, TAB
);
544 junk
= strchr(host
, TAB
);
547 *junk
++ = 0; /* Chop port */
549 junk
= strchr(host
, '\r');
552 *junk
++ = 0; /* Chop port */
554 junk
= strchr(host
, '\n');
557 *junk
++ = 0; /* Chop port */
561 if ((port
[1] == '0') && (!port
[2]))
562 port
[0] = 0; /* 0 means none */
565 /* escape a selector here */
566 escaped_selector
= xstrdup(rfc1738_escape_part(selector
));
570 case GOPHER_DIRECTORY
:
571 icon_url
= mimeGetIconURL("internal-menu");
577 icon_url
= mimeGetIconURL("internal-text");
583 icon_url
= mimeGetIconURL("internal-index");
590 case GOPHER_PLUS_IMAGE
:
591 icon_url
= mimeGetIconURL("internal-image");
596 case GOPHER_PLUS_SOUND
:
597 icon_url
= mimeGetIconURL("internal-sound");
600 case GOPHER_PLUS_MOVIE
:
601 icon_url
= mimeGetIconURL("internal-movie");
607 icon_url
= mimeGetIconURL("internal-telnet");
612 case GOPHER_MACBINHEX
:
616 case GOPHER_UUENCODED
:
617 icon_url
= mimeGetIconURL("internal-binary");
625 icon_url
= mimeGetIconURL("internal-unknown");
629 memset(tmpbuf
, '\0', TEMP_BUF_SIZE
);
631 if ((gtype
== GOPHER_TELNET
) || (gtype
== GOPHER_3270
)) {
632 if (strlen(escaped_selector
) != 0)
633 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
634 icon_url
, escaped_selector
, rfc1738_escape_part(host
),
635 *port
? ":" : "", port
, html_quote(name
));
637 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
638 icon_url
, rfc1738_escape_part(host
), *port
? ":" : "",
639 port
, html_quote(name
));
641 } else if (gtype
== GOPHER_INFO
) {
642 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "\t%s\n", html_quote(name
));
644 if (strncmp(selector
, "GET /", 5) == 0) {
646 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
647 icon_url
, host
, rfc1738_escape_unescaped(selector
+ 5), html_quote(name
));
650 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
651 icon_url
, host
, gtype
, escaped_selector
, html_quote(name
));
655 safe_free(escaped_selector
);
656 outbuf
.append(tmpbuf
);
658 memset(line
, '\0', TEMP_BUF_SIZE
);
662 memset(line
, '\0', TEMP_BUF_SIZE
);
667 } /* HTML_DIR, HTML_INDEX_RESULT */
670 case gopher_ds::HTML_CSO_RESULT
: {
671 if (line
[0] == '-') {
673 char *s_code
, *s_recno
, *result
;
675 s_code
= strtok(line
+ 1, ":\n");
676 s_recno
= strtok(NULL
, ":\n");
677 result
= strtok(NULL
, "\n");
684 recno
= atoi(s_recno
);
689 if (gopherState
->cso_recno
!= recno
) {
690 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno
, html_quote(result
));
691 gopherState
->cso_recno
= recno
;
693 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "%s\n", html_quote(result
));
696 outbuf
.append(tmpbuf
);
700 char *s_code
, *result
;
702 s_code
= strtok(line
, ":");
703 result
= strtok(NULL
, "\n");
714 /* Do nothing here */
718 case 102: /* Number of matches */
720 case 501: /* No Match */
722 case 502: { /* Too Many Matches */
723 /* Print the message the server returns */
724 snprintf(tmpbuf
, TEMP_BUF_SIZE
, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result
));
725 outbuf
.append(tmpbuf
);
733 } /* HTML_CSO_RESULT */
736 break; /* do nothing */
742 if (outbuf
.size() > 0) {
743 entry
->append(outbuf
.rawBuf(), outbuf
.size());
744 /* now let start sending stuff to client */
752 /// \ingroup ServerProtocolGopherInternal
754 gopherTimeout(int fd
, void *data
)
756 GopherStateData
*gopherState
= (GopherStateData
*)data
;
757 debugs(10, 4, HERE
<< gopherState
->serverConn
<< ": '" << gopherState
->entry
->url() << "'" );
759 gopherState
->fwd
->fail(errorCon(ERR_READ_TIMEOUT
, HTTP_GATEWAY_TIMEOUT
, gopherState
->fwd
->request
));
761 if (Comm::IsConnOpen(gopherState
->serverConn
))
762 gopherState
->serverConn
->close();
766 \ingroup ServerProtocolGopherInternal
767 * This will be called when data is ready to be read from fd.
768 * Read until error or connection closed.
771 gopherReadReply(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, comm_err_t flag
, int xerrno
, void *data
)
773 GopherStateData
*gopherState
= (GopherStateData
*)data
;
774 StoreEntry
*entry
= gopherState
->entry
;
777 size_t read_sz
= BUFSIZ
;
778 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
)) {
793 gopherState
->serverConn
->close();
800 read_sz
= delayId
.bytesWanted(1, read_sz
);
803 /* leave one space for \0 in gopherToHTML */
805 debugs(10, 5, HERE
<< conn
<< " read len=" << len
);
807 if (flag
== COMM_OK
&& len
> 0) {
809 delayId
.bytesIn(len
);
812 kb_incr(&statCounter
.server
.all
.kbytes_in
, len
);
813 kb_incr(&statCounter
.server
.other
.kbytes_in
, len
);
815 commSetTimeout(conn
->fd
, Config
.Timeout
.read
, NULL
, NULL
);
816 IOStats
.Gopher
.reads
++;
818 for (clen
= len
- 1, bin
= 0; clen
; bin
++)
821 IOStats
.Gopher
.read_hist
[bin
]++;
823 HttpRequest
*req
= gopherState
->fwd
->request
;
824 if (req
->hier
.bodyBytesRead
< 0)
825 req
->hier
.bodyBytesRead
= 0;
827 req
->hier
.bodyBytesRead
+= len
;
831 if (flag
!= COMM_OK
|| len
< 0) {
832 debugs(50, 1, "gopherReadReply: error reading: " << xstrerror());
834 if (ignoreErrno(errno
)) {
837 ErrorState
*err
= errorCon(ERR_READ_ERROR
, HTTP_INTERNAL_SERVER_ERROR
, gopherState
->fwd
->request
);
839 gopherState
->fwd
->fail(err
);
840 gopherState
->serverConn
->close();
843 } else if (len
== 0 && entry
->isEmpty()) {
844 gopherState
->fwd
->fail(errorCon(ERR_ZERO_SIZE_OBJECT
, HTTP_SERVICE_UNAVAILABLE
, gopherState
->fwd
->request
));
845 gopherState
->serverConn
->close();
847 } else if (len
== 0) {
848 /* Connection closed; retrieval done. */
849 /* flush the rest of data in temp buf if there is one. */
851 if (gopherState
->conversion
!= gopher_ds::NORMAL
)
852 gopherEndHTML(gopherState
);
854 entry
->timestampsSet();
856 gopherState
->fwd
->complete();
857 gopherState
->serverConn
->close();
860 if (gopherState
->conversion
!= gopher_ds::NORMAL
) {
861 gopherToHTML(gopherState
, buf
, len
);
863 entry
->append(buf
, len
);
870 comm_read(conn
, buf
, read_sz
, gopherReadReply
, gopherState
);
876 \ingroup ServerProtocolGopherInternal
877 * This will be called when request write is complete. Schedule read of reply.
880 gopherSendComplete(const Comm::ConnectionPointer
&conn
, char *buf
, size_t size
, comm_err_t errflag
, int xerrno
, void *data
)
882 GopherStateData
*gopherState
= (GopherStateData
*) data
;
883 StoreEntry
*entry
= gopherState
->entry
;
884 debugs(10, 5, HERE
<< conn
<< " size: " << size
<< " errflag: " << errflag
);
887 fd_bytes(conn
->fd
, size
, FD_WRITE
);
888 kb_incr(&statCounter
.server
.all
.kbytes_out
, size
);
889 kb_incr(&statCounter
.server
.other
.kbytes_out
, size
);
894 err
= errorCon(ERR_WRITE_ERROR
, HTTP_SERVICE_UNAVAILABLE
, gopherState
->fwd
->request
);
896 err
->port
= gopherState
->fwd
->request
->port
;
897 err
->url
= xstrdup(entry
->url());
898 gopherState
->fwd
->fail(err
);
899 gopherState
->serverConn
->close();
902 memFree(buf
, MEM_4K_BUF
); /* Allocated by gopherSendRequest. */
908 * OK. We successfully reach remote site. Start MIME typing
909 * stuff. Do it anyway even though request is not HTML type.
913 gopherMimeCreate(gopherState
);
915 switch (gopherState
->type_id
) {
917 case GOPHER_DIRECTORY
:
918 /* we got to convert it first */
919 gopherState
->conversion
= gopher_ds::HTML_DIR
;
920 gopherState
->HTML_header_added
= 0;
924 /* we got to convert it first */
925 gopherState
->conversion
= gopher_ds::HTML_INDEX_RESULT
;
926 gopherState
->HTML_header_added
= 0;
930 /* we got to convert it first */
931 gopherState
->conversion
= gopher_ds::HTML_CSO_RESULT
;
932 gopherState
->cso_recno
= 0;
933 gopherState
->HTML_header_added
= 0;
937 gopherState
->conversion
= gopher_ds::NORMAL
;
941 /* Schedule read reply. */
942 AsyncCall::Pointer call
= commCbCall(10,5, "gopherReadReply",
943 CommIoCbPtrFun(gopherReadReply
, gopherState
));
944 entry
->delayAwareRead(conn
->fd
, gopherState
->replybuf
, BUFSIZ
, call
);
947 memFree(buf
, MEM_4K_BUF
); /* Allocated by gopherSendRequest. */
951 \ingroup ServerProtocolGopherInternal
952 * This will be called when connect completes. Write request.
955 gopherSendRequest(int fd
, void *data
)
957 GopherStateData
*gopherState
= (GopherStateData
*)data
;
958 char *buf
= (char *)memAllocate(MEM_4K_BUF
);
960 if (gopherState
->type_id
== GOPHER_CSO
) {
961 const char *t
= strchr(gopherState
->request
, '?');
964 t
++; /* skip the ? */
968 snprintf(buf
, 4096, "query %s\r\nquit\r\n", t
);
969 } else if (gopherState
->type_id
== GOPHER_INDEX
) {
970 char *t
= strchr(gopherState
->request
, '?');
975 snprintf(buf
, 4096, "%s\r\n", gopherState
->request
);
977 snprintf(buf
, 4096, "%s\r\n", gopherState
->request
);
980 debugs(10, 5, HERE
<< gopherState
->serverConn
);
981 comm_write(gopherState
->serverConn
, buf
, strlen(buf
), gopherSendComplete
, gopherState
, NULL
);
983 if (EBIT_TEST(gopherState
->entry
->flags
, ENTRY_CACHABLE
))
984 gopherState
->entry
->setPublicKey(); /* Make it public */
987 /// \ingroup ServerProtocolGopherInternal
988 CBDATA_TYPE(GopherStateData
);
990 /// \ingroup ServerProtocolGopherAPI
992 gopherStart(FwdState
* fwd
)
994 StoreEntry
*entry
= fwd
->entry
;
995 GopherStateData
*gopherState
;
996 CBDATA_INIT_TYPE(GopherStateData
);
997 gopherState
= cbdataAlloc(GopherStateData
);
998 gopherState
->buf
= (char *)memAllocate(MEM_4K_BUF
);
1001 gopherState
->entry
= entry
;
1003 gopherState
->fwd
= fwd
;
1005 debugs(10, 3, "gopherStart: " << entry
->url() );
1007 statCounter
.server
.all
.requests
++;
1009 statCounter
.server
.other
.requests
++;
1012 gopher_request_parse(fwd
->request
,
1013 &gopherState
->type_id
, gopherState
->request
);
1015 comm_add_close_handler(fwd
->serverConnection()->fd
, gopherStateFree
, gopherState
);
1017 if (((gopherState
->type_id
== GOPHER_INDEX
) || (gopherState
->type_id
== GOPHER_CSO
))
1018 && (strchr(gopherState
->request
, '?') == NULL
)) {
1019 /* Index URL without query word */
1020 /* We have to generate search page back to client. No need for connection */
1021 gopherMimeCreate(gopherState
);
1023 if (gopherState
->type_id
== GOPHER_INDEX
) {
1024 gopherState
->conversion
= gopher_ds::HTML_INDEX_PAGE
;
1026 if (gopherState
->type_id
== GOPHER_CSO
) {
1027 gopherState
->conversion
= gopher_ds::HTML_CSO_PAGE
;
1029 gopherState
->conversion
= gopher_ds::HTML_INDEX_PAGE
;
1033 gopherToHTML(gopherState
, (char *) NULL
, 0);
1038 gopherState
->serverConn
= fwd
->serverConnection();
1039 gopherSendRequest(fwd
->serverConnection()->fd
, gopherState
);
1040 commSetTimeout(fwd
->serverConnection()->fd
, Config
.Timeout
.read
, gopherTimeout
, gopherState
);