gopher.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
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.
7  */
8 
9 /* DEBUG: section 10 Gopher */
10 
11 #include "squid.h"
12 #include "comm.h"
13 #include "comm/Read.h"
14 #include "comm/Write.h"
15 #include "errorpage.h"
16 #include "fd.h"
17 #include "FwdState.h"
18 #include "globals.h"
19 #include "gopher.h"
20 #include "html_quote.h"
21 #include "HttpReply.h"
22 #include "HttpRequest.h"
23 #include "MemBuf.h"
24 #include "mime.h"
25 #include "parser/Tokenizer.h"
26 #include "rfc1738.h"
27 #include "SquidConfig.h"
28 #include "SquidTime.h"
29 #include "StatCounters.h"
30 #include "Store.h"
31 #include "tools.h"
32 
33 #if USE_DELAY_POOLS
34 #include "DelayPools.h"
35 #include "MemObject.h"
36 #endif
37 
38 // RFC 1436 section 3.8 gopher item-type codes
39 #define GOPHER_FILE '0'
40 #define GOPHER_DIRECTORY '1'
41 #define GOPHER_CSO '2'
42 #define GOPHER_ERROR '3'
43 #define GOPHER_MACBINHEX '4'
44 #define GOPHER_DOSBIN '5'
45 #define GOPHER_UUENCODED '6'
46 #define GOPHER_INDEX '7'
47 #define GOPHER_TELNET '8'
48 #define GOPHER_BIN '9'
49 #define GOPHER_REDUNT '+'
50 #define GOPHER_3270 'T'
51 #define GOPHER_GIF 'g'
52 #define GOPHER_IMAGE 'I'
53 
54 // Gopher+ section 2.9 extension types
55 // https://github.com/jgoerzen/pygopherd/blob/master/doc/standards/Gopher%2B.txt
56 #define GOPHER_PLUS_IMAGE ':'
57 #define GOPHER_PLUS_MOVIE ';'
58 #define GOPHER_PLUS_SOUND '<'
59 
60 // non-standard item-type codes
61 #define GOPHER_HTML 'h'
62 #define GOPHER_INFO 'i'
63 #define GOPHER_WWW 'w'
64 #define GOPHER_SOUND 's'
65 
66 #define GOPHER_PORT 70
67 
68 #define TAB '\t'
69 
70 // TODO CODE: should this be a protocol-specific thing?
71 #define TEMP_BUF_SIZE 4096
72 
73 #define MAX_CSO_RESULT 1024
74 
82 {
84 
85 public:
87  entry(aFwd->entry),
90  HTML_pre(0),
91  type_id(GOPHER_FILE /* '0' */),
93  cso_recno(0),
94  len(0),
95  buf(NULL),
96  fwd(aFwd)
97  {
98  *request = 0;
99  buf = (char *)memAllocate(MEM_4K_BUF);
100  entry->lock("gopherState");
101  *replybuf = 0;
102  }
103 
105 
106 public:
108  enum {
117  int HTML_pre;
118  char type_id;
120 
123 
125 
127  int len;
128 
129  char *buf; /* pts to a 4k page */
134 };
135 
137 
139 static void gopherMimeCreate(GopherStateData *);
140 static void gopher_request_parse(const HttpRequest * req,
141  char *type_id,
142  char *request);
143 static void gopherEndHTML(GopherStateData *);
144 static void gopherToHTML(GopherStateData *, char *inbuf, int len);
149 
150 static char def_gopher_bin[] = "www/unknown";
151 
152 static char def_gopher_text[] = "text/plain";
153 
154 static void
156 {
157  GopherStateData *gopherState = (GopherStateData *)params.data;
158  // Assume that FwdState is monitoring and calls noteClosure(). See XXX about
159  // Connection sharing with FwdState in gopherStart().
160  delete gopherState;
161 }
162 
164 {
165  if (entry)
166  entry->unlock("gopherState");
167 
168  if (buf)
170 }
171 
175 static void
177 {
178  StoreEntry *entry = gopherState->entry;
179  const char *mime_type = NULL;
180  const char *mime_enc = NULL;
181 
182  switch (gopherState->type_id) {
183 
184  case GOPHER_DIRECTORY:
185 
186  case GOPHER_INDEX:
187 
188  case GOPHER_HTML:
189 
190  case GOPHER_WWW:
191 
192  case GOPHER_CSO:
193  mime_type = "text/html";
194  break;
195 
196  case GOPHER_GIF:
197 
198  case GOPHER_IMAGE:
199 
200  case GOPHER_PLUS_IMAGE:
201  mime_type = "image/gif";
202  break;
203 
204  case GOPHER_SOUND:
205 
206  case GOPHER_PLUS_SOUND:
207  mime_type = "audio/basic";
208  break;
209 
210  case GOPHER_PLUS_MOVIE:
211  mime_type = "video/mpeg";
212  break;
213 
214  case GOPHER_MACBINHEX:
215 
216  case GOPHER_DOSBIN:
217 
218  case GOPHER_UUENCODED:
219 
220  case GOPHER_BIN:
221  /* Rightnow We have no idea what it is. */
222  mime_enc = mimeGetContentEncoding(gopherState->request);
223  mime_type = mimeGetContentType(gopherState->request);
224  if (!mime_type)
225  mime_type = def_gopher_bin;
226  break;
227 
228  case GOPHER_FILE:
229 
230  default:
231  mime_enc = mimeGetContentEncoding(gopherState->request);
232  mime_type = mimeGetContentType(gopherState->request);
233  if (!mime_type)
234  mime_type = def_gopher_text;
235  break;
236  }
237 
238  assert(entry->isEmpty());
239 
240  HttpReply *reply = new HttpReply;
241  entry->buffer();
242  reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, -1, -1, -2);
243  if (mime_enc)
245 
246  entry->replaceHttpReply(reply);
247  gopherState->reply_ = reply;
248 }
249 
253 static void
254 gopher_request_parse(const HttpRequest * req, char *type_id, char *request)
255 {
257 
258  if (request)
259  *request = 0;
260 
261  tok.skip('/'); // ignore failures? path could be ab-empty
262 
263  if (tok.atEnd()) {
264  *type_id = GOPHER_DIRECTORY;
265  return;
266  }
267 
268  static const CharacterSet anyByte("UTF-8",0x00, 0xFF);
269 
270  SBuf typeId;
271  (void)tok.prefix(typeId, anyByte, 1); // never fails since !atEnd()
272  *type_id = typeId[0];
273 
274  if (request) {
275  SBufToCstring(request, tok.remaining().substr(0, MAX_URL-1));
276  /* convert %xx to char */
278  }
279 }
280 
288 int
290 {
291  int cachable = 1;
292  char type_id;
293  /* parse to see type */
295  &type_id,
296  NULL);
297 
298  switch (type_id) {
299 
300  case GOPHER_INDEX:
301 
302  case GOPHER_CSO:
303 
304  case GOPHER_TELNET:
305 
306  case GOPHER_3270:
307  cachable = 0;
308  break;
309 
310  default:
311  cachable = 1;
312  }
313 
314  return cachable;
315 }
316 
317 static void
318 gopherHTMLHeader(StoreEntry * e, const char *title, const char *substring)
319 {
320  storeAppendPrintf(e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
321  storeAppendPrintf(e, "<HTML><HEAD><TITLE>");
322  storeAppendPrintf(e, title, substring);
323  storeAppendPrintf(e, "</TITLE>");
324  storeAppendPrintf(e, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
325  storeAppendPrintf(e, "</HEAD>\n<BODY><H1>");
326  storeAppendPrintf(e, title, substring);
327  storeAppendPrintf(e, "</H1>\n");
328 }
329 
330 static void
332 {
333  storeAppendPrintf(e, "<HR noshade size=\"1px\">\n");
334  storeAppendPrintf(e, "<ADDRESS>\n");
335  storeAppendPrintf(e, "Generated %s by %s (%s)\n",
337  getMyHostname(),
339  storeAppendPrintf(e, "</ADDRESS></BODY></HTML>\n");
340 }
341 
342 static void
344 {
345  StoreEntry *e = gopherState->entry;
346 
347  if (!gopherState->HTML_header_added) {
348  gopherHTMLHeader(e, "Server Return Nothing", NULL);
349  storeAppendPrintf(e, "<P>The Gopher query resulted in a blank response</P>");
350  } else if (gopherState->HTML_pre) {
351  storeAppendPrintf(e, "</PRE>\n");
352  }
353 
354  gopherHTMLFooter(e);
355 }
356 
362 static void
363 gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
364 {
365  char *pos = inbuf;
366  char *lpos = NULL;
367  char *tline = NULL;
368  LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
369  LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
370  char *name = NULL;
371  char *selector = NULL;
372  char *host = NULL;
373  char *port = NULL;
374  char *escaped_selector = NULL;
375  const char *icon_url = NULL;
376  char gtype;
377  StoreEntry *entry = NULL;
378 
379  memset(tmpbuf, '\0', TEMP_BUF_SIZE);
380  memset(line, '\0', TEMP_BUF_SIZE);
381 
382  entry = gopherState->entry;
383 
384  if (gopherState->conversion == GopherStateData::HTML_INDEX_PAGE) {
385  char *html_url = html_quote(entry->url());
386  gopherHTMLHeader(entry, "Gopher Index %s", html_url);
387  storeAppendPrintf(entry,
388  "<p>This is a searchable Gopher index. Use the search\n"
389  "function of your browser to enter search terms.\n"
390  "<ISINDEX>\n");
391  gopherHTMLFooter(entry);
392  /* now let start sending stuff to client */
393  entry->flush();
394  gopherState->HTML_header_added = 1;
395 
396  return;
397  }
398 
399  if (gopherState->conversion == GopherStateData::HTML_CSO_PAGE) {
400  char *html_url = html_quote(entry->url());
401  gopherHTMLHeader(entry, "CSO Search of %s", html_url);
402  storeAppendPrintf(entry,
403  "<P>A CSO database usually contains a phonebook or\n"
404  "directory. Use the search function of your browser to enter\n"
405  "search terms.</P><ISINDEX>\n");
406  gopherHTMLFooter(entry);
407  /* now let start sending stuff to client */
408  entry->flush();
409  gopherState->HTML_header_added = 1;
410 
411  return;
412  }
413 
414  String outbuf;
415 
416  if (!gopherState->HTML_header_added) {
417  if (gopherState->conversion == GopherStateData::HTML_CSO_RESULT)
418  gopherHTMLHeader(entry, "CSO Search Result", NULL);
419  else
420  gopherHTMLHeader(entry, "Gopher Menu", NULL);
421 
422  outbuf.append ("<PRE>");
423 
424  gopherState->HTML_header_added = 1;
425 
426  gopherState->HTML_pre = 1;
427  }
428 
429  while (pos < inbuf + len) {
430  int llen;
431  int left = len - (pos - inbuf);
432  lpos = (char *)memchr(pos, '\n', left);
433  if (lpos) {
434  ++lpos; /* Next line is after \n */
435  llen = lpos - pos;
436  } else {
437  llen = left;
438  }
439  if (gopherState->len + llen >= TEMP_BUF_SIZE) {
440  debugs(10, DBG_IMPORTANT, "GopherHTML: Buffer overflow. Lost some data on URL: " << entry->url() );
441  llen = TEMP_BUF_SIZE - gopherState->len - 1;
442  gopherState->overflowed = true; // may already be true
443  }
444  if (!lpos) {
445  /* there is no complete line in inbuf */
446  /* copy it to temp buffer */
447  /* note: llen is adjusted above */
448  memcpy(gopherState->buf + gopherState->len, pos, llen);
449  gopherState->len += llen;
450  break;
451  }
452  if (gopherState->len != 0) {
453  /* there is something left from last tx. */
454  memcpy(line, gopherState->buf, gopherState->len);
455  memcpy(line + gopherState->len, pos, llen);
456  llen += gopherState->len;
457  gopherState->len = 0;
458  } else {
459  memcpy(line, pos, llen);
460  }
461  line[llen + 1] = '\0';
462  /* move input to next line */
463  pos = lpos;
464 
465  /* at this point. We should have one line in buffer to process */
466 
467  if (*line == '.') {
468  /* skip it */
469  memset(line, '\0', TEMP_BUF_SIZE);
470  continue;
471  }
472 
473  switch (gopherState->conversion) {
474 
476 
478  tline = line;
479  gtype = *tline;
480  ++tline;
481  name = tline;
482  selector = strchr(tline, TAB);
483 
484  if (selector) {
485  *selector = '\0';
486  ++selector;
487  host = strchr(selector, TAB);
488 
489  if (host) {
490  *host = '\0';
491  ++host;
492  port = strchr(host, TAB);
493 
494  if (port) {
495  char *junk;
496  port[0] = ':';
497  junk = strchr(host, TAB);
498 
499  if (junk)
500  *junk++ = 0; /* Chop port */
501  else {
502  junk = strchr(host, '\r');
503 
504  if (junk)
505  *junk++ = 0; /* Chop port */
506  else {
507  junk = strchr(host, '\n');
508 
509  if (junk)
510  *junk++ = 0; /* Chop port */
511  }
512  }
513 
514  if ((port[1] == '0') && (!port[2]))
515  port[0] = 0; /* 0 means none */
516  }
517 
518  /* escape a selector here */
519  escaped_selector = xstrdup(rfc1738_escape_part(selector));
520 
521  switch (gtype) {
522 
523  case GOPHER_DIRECTORY:
524  icon_url = mimeGetIconURL("internal-menu");
525  break;
526 
527  case GOPHER_HTML:
528 
529  case GOPHER_FILE:
530  icon_url = mimeGetIconURL("internal-text");
531  break;
532 
533  case GOPHER_INDEX:
534 
535  case GOPHER_CSO:
536  icon_url = mimeGetIconURL("internal-index");
537  break;
538 
539  case GOPHER_IMAGE:
540 
541  case GOPHER_GIF:
542 
543  case GOPHER_PLUS_IMAGE:
544  icon_url = mimeGetIconURL("internal-image");
545  break;
546 
547  case GOPHER_SOUND:
548 
549  case GOPHER_PLUS_SOUND:
550  icon_url = mimeGetIconURL("internal-sound");
551  break;
552 
553  case GOPHER_PLUS_MOVIE:
554  icon_url = mimeGetIconURL("internal-movie");
555  break;
556 
557  case GOPHER_TELNET:
558 
559  case GOPHER_3270:
560  icon_url = mimeGetIconURL("internal-telnet");
561  break;
562 
563  case GOPHER_BIN:
564 
565  case GOPHER_MACBINHEX:
566 
567  case GOPHER_DOSBIN:
568 
569  case GOPHER_UUENCODED:
570  icon_url = mimeGetIconURL("internal-binary");
571  break;
572 
573  case GOPHER_INFO:
574  icon_url = NULL;
575  break;
576 
577  case GOPHER_WWW:
578  icon_url = mimeGetIconURL("internal-link");
579  break;
580 
581  default:
582  icon_url = mimeGetIconURL("internal-unknown");
583  break;
584  }
585 
586  memset(tmpbuf, '\0', TEMP_BUF_SIZE);
587 
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));
593  else
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));
597 
598  } else if (gtype == GOPHER_INFO) {
599  snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name));
600  } else {
601  if (strncmp(selector, "GET /", 5) == 0) {
602  /* WWW link */
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));
605  } else if (gtype == GOPHER_WWW) {
606  snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"%s\">%s</A>\n",
607  icon_url, rfc1738_escape_unescaped(selector), html_quote(name));
608  } else {
609  /* Standard link */
610  snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
611  icon_url, host, gtype, escaped_selector, html_quote(name));
612  }
613  }
614 
615  safe_free(escaped_selector);
616  outbuf.append(tmpbuf);
617  } else {
618  memset(line, '\0', TEMP_BUF_SIZE);
619  continue;
620  }
621  } else {
622  memset(line, '\0', TEMP_BUF_SIZE);
623  continue;
624  }
625 
626  break;
627  } /* HTML_DIR, HTML_INDEX_RESULT */
628 
630  if (line[0] == '-') {
631  int code, recno;
632  char *s_code, *s_recno, *result;
633 
634  s_code = strtok(line + 1, ":\n");
635  s_recno = strtok(NULL, ":\n");
636  result = strtok(NULL, "\n");
637 
638  if (!result)
639  break;
640 
641  code = atoi(s_code);
642 
643  recno = atoi(s_recno);
644 
645  if (code != 200)
646  break;
647 
648  if (gopherState->cso_recno != recno) {
649  snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
650  gopherState->cso_recno = recno;
651  } else {
652  snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result));
653  }
654 
655  outbuf.append(tmpbuf);
656  break;
657  } else {
658  int code;
659  char *s_code, *result;
660 
661  s_code = strtok(line, ":");
662  result = strtok(NULL, "\n");
663 
664  if (!result)
665  break;
666 
667  code = atoi(s_code);
668 
669  switch (code) {
670 
671  case 200: {
672  /* OK */
673  /* Do nothing here */
674  break;
675  }
676 
677  case 102: /* Number of matches */
678 
679  case 501: /* No Match */
680 
681  case 502: { /* Too Many Matches */
682  /* Print the message the server returns */
683  snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
684  outbuf.append(tmpbuf);
685  break;
686  }
687 
688  }
689  }
690 
691  break;
692  } /* HTML_CSO_RESULT */
693  default:
694  break; /* do nothing */
695 
696  } /* switch */
697 
698  } /* while loop */
699 
700  if (outbuf.size() > 0) {
701  entry->append(outbuf.rawBuf(), outbuf.size());
702  /* now let start sending stuff to client */
703  entry->flush();
704  }
705 
706  outbuf.clean();
707  return;
708 }
709 
710 static void
712 {
713  GopherStateData *gopherState = static_cast<GopherStateData *>(io.data);
714  debugs(10, 4, HERE << io.conn << ": '" << gopherState->entry->url() << "'" );
715 
716  gopherState->fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGatewayTimeout, gopherState->fwd->request, gopherState->fwd->al));
717 
718  if (Comm::IsConnOpen(io.conn))
719  io.conn->close();
720 }
721 
726 static void
727 gopherReadReply(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm::Flag flag, int xerrno, void *data)
728 {
729  GopherStateData *gopherState = (GopherStateData *)data;
730  StoreEntry *entry = gopherState->entry;
731  int clen;
732  int bin;
733  size_t read_sz = BUFSIZ;
734 #if USE_DELAY_POOLS
735  DelayId delayId = entry->mem_obj->mostBytesAllowed();
736 #endif
737 
738  /* Bail out early on Comm::ERR_CLOSING - close handlers will tidy up for us */
739 
740  if (flag == Comm::ERR_CLOSING) {
741  return;
742  }
743 
744  assert(buf == gopherState->replybuf);
745 
746  // XXX: Should update delayId, statCounter, etc. before bailing
747  if (!entry->isAccepting()) {
748  debugs(10, 3, "terminating due to bad " << *entry);
749  // TODO: Do not abuse connection for triggering cleanup.
750  gopherState->serverConn->close();
751  return;
752  }
753 
754 #if USE_DELAY_POOLS
755  read_sz = delayId.bytesWanted(1, read_sz);
756 #endif
757 
758  /* leave one space for \0 in gopherToHTML */
759 
760  if (flag == Comm::OK && len > 0) {
761 #if USE_DELAY_POOLS
762  delayId.bytesIn(len);
763 #endif
764 
765  statCounter.server.all.kbytes_in += len;
766  statCounter.server.other.kbytes_in += len;
767  }
768 
769  debugs(10, 5, HERE << conn << " read len=" << len);
770 
771  if (flag == Comm::OK && len > 0) {
774  ++IOStats.Gopher.reads;
775 
776  for (clen = len - 1, bin = 0; clen; ++bin)
777  clen >>= 1;
778 
779  ++IOStats.Gopher.read_hist[bin];
780 
781  HttpRequest *req = gopherState->fwd->request;
782  if (req->hier.bodyBytesRead < 0) {
783  req->hier.bodyBytesRead = 0;
784  // first bytes read, update Reply flags:
785  gopherState->reply_->sources |= Http::Message::srcGopher;
786  }
787 
788  req->hier.bodyBytesRead += len;
789  }
790 
791  if (flag != Comm::OK) {
792  debugs(50, DBG_IMPORTANT, MYNAME << "error reading: " << xstrerr(xerrno));
793 
794  if (ignoreErrno(xerrno)) {
795  AsyncCall::Pointer call = commCbCall(5,4, "gopherReadReply",
796  CommIoCbPtrFun(gopherReadReply, gopherState));
797  comm_read(conn, buf, read_sz, call);
798  } else {
799  const auto err = new ErrorState(ERR_READ_ERROR, Http::scInternalServerError, gopherState->fwd->request, gopherState->fwd->al);
800  err->xerrno = xerrno;
801  gopherState->fwd->fail(err);
802  gopherState->serverConn->close();
803  }
804  } else if (len == 0 && entry->isEmpty()) {
805  gopherState->fwd->fail(new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, gopherState->fwd->request, gopherState->fwd->al));
806  gopherState->serverConn->close();
807  } else if (len == 0) {
808  /* Connection closed; retrieval done. */
809  /* flush the rest of data in temp buf if there is one. */
810 
811  if (gopherState->conversion != GopherStateData::NORMAL)
812  gopherEndHTML(gopherState);
813 
814  entry->timestampsSet();
815  entry->flush();
816 
817  if (!gopherState->len && !gopherState->overflowed)
818  gopherState->fwd->markStoredReplyAsWhole("gopher EOF after receiving/storing some bytes");
819 
820  gopherState->fwd->complete();
821  gopherState->serverConn->close();
822  } else {
823  if (gopherState->conversion != GopherStateData::NORMAL) {
824  gopherToHTML(gopherState, buf, len);
825  } else {
826  entry->append(buf, len);
827  }
828  AsyncCall::Pointer call = commCbCall(5,4, "gopherReadReply",
829  CommIoCbPtrFun(gopherReadReply, gopherState));
830  comm_read(conn, buf, read_sz, call);
831  }
832 }
833 
837 static void
838 gopherSendComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int xerrno, void *data)
839 {
840  GopherStateData *gopherState = (GopherStateData *) data;
841  StoreEntry *entry = gopherState->entry;
842  debugs(10, 5, HERE << conn << " size: " << size << " errflag: " << errflag);
843 
844  if (size > 0) {
845  fd_bytes(conn->fd, size, FD_WRITE);
846  statCounter.server.all.kbytes_out += size;
847  statCounter.server.other.kbytes_out += size;
848  }
849 
850  if (!entry->isAccepting()) {
851  debugs(10, 3, "terminating due to bad " << *entry);
852  // TODO: Do not abuse connection for triggering cleanup.
853  gopherState->serverConn->close();
854  return;
855  }
856 
857  if (errflag) {
858  const auto err = new ErrorState(ERR_WRITE_ERROR, Http::scServiceUnavailable, gopherState->fwd->request, gopherState->fwd->al);
859  err->xerrno = xerrno;
860  err->port = gopherState->fwd->request->url.port();
861  err->url = xstrdup(entry->url());
862  gopherState->fwd->fail(err);
863  gopherState->serverConn->close();
864  return;
865  }
866 
867  /*
868  * OK. We successfully reach remote site. Start MIME typing
869  * stuff. Do it anyway even though request is not HTML type.
870  */
871  entry->buffer();
872 
873  gopherMimeCreate(gopherState);
874 
875  switch (gopherState->type_id) {
876 
877  case GOPHER_DIRECTORY:
878  /* we got to convert it first */
879  gopherState->conversion = GopherStateData::HTML_DIR;
880  gopherState->HTML_header_added = 0;
881  break;
882 
883  case GOPHER_INDEX:
884  /* we got to convert it first */
886  gopherState->HTML_header_added = 0;
887  break;
888 
889  case GOPHER_CSO:
890  /* we got to convert it first */
892  gopherState->cso_recno = 0;
893  gopherState->HTML_header_added = 0;
894  break;
895 
896  default:
897  gopherState->conversion = GopherStateData::NORMAL;
898  entry->flush();
899  }
900 
901  /* Schedule read reply. */
902  AsyncCall::Pointer call = commCbCall(5,5, "gopherReadReply",
903  CommIoCbPtrFun(gopherReadReply, gopherState));
904  entry->delayAwareRead(conn, gopherState->replybuf, BUFSIZ, call);
905 }
906 
910 static void
911 gopherSendRequest(int, void *data)
912 {
913  GopherStateData *gopherState = (GopherStateData *)data;
914  MemBuf mb;
915  mb.init();
916 
917  if (gopherState->type_id == GOPHER_CSO) {
918  const char *t = strchr(gopherState->request, '?');
919 
920  if (t)
921  ++t; /* skip the ? */
922  else
923  t = "";
924 
925  mb.appendf("query %s\r\nquit", t);
926  } else {
927  if (gopherState->type_id == GOPHER_INDEX) {
928  if (char *t = strchr(gopherState->request, '?'))
929  *t = '\t';
930  }
931  mb.append(gopherState->request, strlen(gopherState->request));
932  }
933  mb.append("\r\n", 2);
934 
935  debugs(10, 5, gopherState->serverConn);
936  AsyncCall::Pointer call = commCbCall(5,5, "gopherSendComplete",
937  CommIoCbPtrFun(gopherSendComplete, gopherState));
938  Comm::Write(gopherState->serverConn, &mb, call);
939 
940  if (!gopherState->entry->makePublic())
941  gopherState->entry->makePrivate(true);
942 }
943 
944 void
946 {
947  GopherStateData *gopherState = new GopherStateData(fwd);
948 
949  debugs(10, 3, gopherState->entry->url());
950 
951  ++ statCounter.server.all.requests;
952 
953  ++ statCounter.server.other.requests;
954 
955  /* Parse url. */
957  &gopherState->type_id, gopherState->request);
958 
960 
961  if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
962  && (strchr(gopherState->request, '?') == NULL)) {
963  /* Index URL without query word */
964  /* We have to generate search page back to client. No need for connection */
965  gopherMimeCreate(gopherState);
966 
967  if (gopherState->type_id == GOPHER_INDEX) {
969  } else {
970  if (gopherState->type_id == GOPHER_CSO) {
972  } else {
974  }
975  }
976 
977  gopherToHTML(gopherState, (char *) NULL, 0);
978  fwd->markStoredReplyAsWhole("gopher instant internal request satisfaction");
979  fwd->complete();
980  return;
981  }
982 
983  // XXX: Sharing open Connection with FwdState that has its own handlers/etc.
984  gopherState->serverConn = fwd->serverConnection();
985  gopherSendRequest(fwd->serverConnection()->fd, gopherState);
986  AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "gopherTimeout",
987  CommTimeoutCbPtrFun(gopherTimeout, gopherState));
988  commSetConnTimeout(fwd->serverConnection(), Config.Timeout.read, timeoutCall);
989 }
990 
const char * xstrerr(int error)
Definition: xstrerror.cc:83
void makePrivate(const bool shareable)
Definition: store.cc:172
static void gopher_request_parse(const HttpRequest *req, char *type_id, char *request)
Definition: gopher.cc:254
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:922
#define GOPHER_TELNET
Definition: gopher.cc:47
#define MYNAME
Definition: Debug.h:165
virtual void buffer()
Definition: store.cc:1618
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
struct SquidConfig::@97 Timeout
#define BUFSIZ
Definition: defines.h:20
void port(unsigned short p)
Definition: Uri.h:94
char replybuf[BUFSIZ]
Definition: gopher.cc:133
AnyP::Uri url
the request URI
Definition: HttpRequest.h:115
@ ERR_READ_ERROR
Definition: forward.h:28
#define GOPHER_PLUS_MOVIE
Definition: gopher.cc:57
#define GOPHER_MACBINHEX
Definition: gopher.cc:43
static IOCB gopherSendComplete
Definition: gopher.cc:147
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:75
int reads
Definition: IoStats.h:19
bool makePublic(const KeyScope keyScope=ksDefault)
Definition: store.cc:165
HttpHeader header
Definition: Message.h:75
HttpReply::Pointer reply_
Definition: gopher.cc:132
virtual void flush()
Definition: store.cc:1629
MemObject * mem_obj
Definition: Store.h:222
const char * url() const
Definition: store.cc:1583
int bytesWanted(int min, int max) const
Definition: DelayId.cc:130
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition: store.cc:869
void lock(const char *context)
Definition: store.cc:459
StoreEntry * entry
Definition: gopher.cc:107
enum GopherStateData::@72 conversion
AccessLogEntryPointer al
info for the future access.log entry
Definition: FwdState.h:171
FwdState::Pointer fwd
Definition: gopher.cc:131
#define GOPHER_INDEX
Definition: gopher.cc:46
#define CBDATA_CLASS(type)
Definition: cbdata.h:302
bool overflowed
some received bytes ignored due to internal buffer capacity limits
Definition: gopher.cc:122
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:95
Definition: SBuf.h:87
int commSetConnTimeout(const Comm::ConnectionPointer &conn, int timeout, AsyncCall::Pointer &callback)
Definition: comm.cc:564
void SBufToCstring(char *d, const SBuf &s)
Definition: SBuf.h:745
char request[MAX_URL]
Definition: gopher.cc:119
#define xstrdup
DelayId mostBytesAllowed() const
Definition: MemObject.cc:462
static CLCB gopherStateFree
Definition: gopher.cc:138
char * html_quote(const char *)
Definition: html_quote.c:53
#define TEMP_BUF_SIZE
Definition: gopher.cc:71
@ CONTENT_ENCODING
#define TAB
Definition: gopher.cc:68
static void gopherToHTML(GopherStateData *, char *inbuf, int len)
Definition: gopher.cc:363
virtual void append(const char *c, int sz)
Definition: MemBuf.cc:211
#define GOPHER_HTML
Definition: gopher.cc:61
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
IoStats IOStats
struct StatCounters::@129 server
@ OK
Definition: Flag.h:16
static void gopherEndHTML(GopherStateData *)
Definition: gopher.cc:343
bool isAccepting() const
Definition: store.cc:1990
void replaceHttpReply(const HttpReplyPointer &, const bool andStartWriting=true)
Definition: store.cc:1722
void * memAllocate(mem_type)
Allocate one element from the typed pool.
Definition: old_api.cc:214
void CLCB(const CommCloseCbParams &params)
Definition: CommCalls.h:42
void fail(ErrorState *err)
Definition: FwdState.cc:490
@ ERR_CLOSING
Definition: Flag.h:25
#define DBG_IMPORTANT
Definition: Debug.h:41
static IOCB gopherReadReply
Definition: gopher.cc:146
static int port
Definition: ldap_backend.cc:69
static char def_gopher_text[]
Definition: gopher.cc:152
#define GOPHER_PLUS_SOUND
Definition: gopher.cc:58
#define GOPHER_PLUS_IMAGE
Definition: gopher.cc:56
#define GOPHER_DIRECTORY
Definition: gopher.cc:40
@ scGatewayTimeout
Definition: StatusCode.h:75
#define MAX_URL
Definition: defines.h:78
void comm_read(const Comm::ConnectionPointer &conn, char *buf, int len, AsyncCall::Pointer &callback)
Definition: Read.h:59
static PF gopherSendRequest
Definition: gopher.cc:148
void bytesIn(int qty)
Definition: DelayId.cc:152
#define GOPHER_SOUND
Definition: gopher.cc:64
static char def_gopher_bin[]
Definition: gopher.cc:150
int size
Definition: ModDevPoll.cc:76
#define NULL
Definition: types.h:166
#define GOPHER_CSO
Definition: gopher.cc:41
void rfc1738_unescape(char *url)
Definition: rfc1738.c:146
time_t read
Definition: SquidConfig.h:110
void CTCB(const CommTimeoutCbParams &params)
Definition: CommCalls.h:39
#define GOPHER_BIN
Definition: gopher.cc:48
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
struct StatCounters::@129::@139 all
char const * visible_appname_string
int len
the number of not-yet-parsed Gopher line bytes in this->buf
Definition: gopher.cc:127
GopherStateData(FwdState *aFwd)
Definition: gopher.cc:86
void append(char const *buf, int len)
Definition: String.cc:151
Definition: MemBuf.h:24
static void gopherHTMLFooter(StoreEntry *e)
Definition: gopher.cc:331
int gopherCachable(const HttpRequest *req)
Definition: gopher.cc:289
unsigned char code
Definition: html_quote.c:20
std::ostream & HERE(std::ostream &s)
Definition: Debug.h:152
void IOCB(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag flag, int xerrno, void *data)
Definition: CommCalls.h:36
int unlock(const char *context)
Definition: store.cc:483
Comm::ConnectionPointer conn
Definition: CommCalls.h:85
#define safe_free(x)
Definition: xalloc.h:73
CommCbFunPtrCallT< Dialer > * commCbCall(int debugSection, int debugLevel, const char *callName, const Dialer &dialer)
Definition: CommCalls.h:342
@ ERR_ZERO_SIZE_OBJECT
Definition: forward.h:46
int conn
the current server connection FD
Definition: Transport.cc:26
#define assert(EX)
Definition: assert.h:19
HierarchyLogEntry hier
Definition: HttpRequest.h:157
@ scServiceUnavailable
Definition: StatusCode.h:74
static void gopherMimeCreate(GopherStateData *)
Definition: gopher.cc:176
@ scInternalServerError
Definition: StatusCode.h:71
void Write(const Comm::ConnectionPointer &conn, const char *buf, int size, AsyncCall::Pointer &callback, FREE *free_func)
Definition: Write.cc:34
#define CBDATA_CLASS_INIT(type)
Definition: cbdata.h:318
time_t squid_curtime
Definition: stub_time.cc:17
const char * mkrfc1123(time_t)
Definition: rfc1123.c:202
void fd_bytes(int fd, int len, unsigned int type)
Definition: fd.cc:227
uint32_t sources
The message sources.
Definition: Message.h:100
Flag
Definition: Flag.h:15
int ignoreErrno(int ierrno)
Definition: comm.cc:1411
@ ERR_READ_TIMEOUT
Definition: forward.h:26
#define rfc1738_escape_unescaped(x)
Definition: rfc1738.h:59
int64_t bodyBytesRead
number of body bytes received from the next hop or -1
#define GOPHER_UUENCODED
Definition: gopher.cc:45
@ MEM_4K_BUF
Definition: forward.h:42
void path(const char *p)
Definition: Uri.h:99
const char * mimeGetContentType(const char *fn)
Definition: mime.cc:177
static void gopherHTMLHeader(StoreEntry *e, const char *title, const char *substring)
Definition: gopher.cc:318
Definition: parse.c:160
void complete()
Definition: FwdState.cc:558
char * buf
Definition: gopher.cc:129
bool timestampsSet()
Definition: store.cc:1410
static CTCB gopherTimeout
Definition: gopher.cc:145
void putStr(Http::HdrType id, const char *str)
Definition: HttpHeader.cc:1051
@ ERR_WRITE_ERROR
Definition: forward.h:29
size_type size() const
Definition: SquidString.h:73
const char * getMyHostname(void)
Definition: tools.cc:414
void memFree(void *, int type)
Free a element allocated by memAllocate()
Definition: old_api.cc:222
bool isEmpty() const
Definition: Store.h:67
const char * mimeGetContentEncoding(const char *fn)
Definition: mime.cc:191
void delayAwareRead(const Comm::ConnectionPointer &conn, char *buf, int len, AsyncCall::Pointer callback)
Definition: store.cc:223
#define GOPHER_WWW
Definition: gopher.cc:63
void Controller::create() STUB void Controller Controller nil
#define GOPHER_3270
Definition: gopher.cc:50
#define GOPHER_GIF
Definition: gopher.cc:51
#define GOPHER_DOSBIN
Definition: gopher.cc:44
@ FD_WRITE
Definition: enums.h:24
virtual void append(char const *, int)
Appends a c-string to existing packed data.
Definition: store.cc:817
struct StatCounters::@129::@139 other
optimized set of C chars, with quick membership test and merge support
Definition: CharacterSet.h:18
struct IoStats::@79 Gopher
const char * mimeGetIconURL(const char *fn)
Definition: mime.cc:158
void setHeaders(Http::StatusCode status, const char *reason, const char *ctype, int64_t clen, time_t lmt, time_t expires)
Definition: HttpReply.cc:168
void markStoredReplyAsWhole(const char *whyWeAreSure)
Definition: FwdState.cc:607
#define GOPHER_IMAGE
Definition: gopher.cc:52
@ scOkay
Definition: StatusCode.h:26
#define rfc1738_escape_part(x)
Definition: rfc1738.h:55
Comm::ConnectionPointer serverConn
Definition: gopher.cc:130
#define false
Definition: GnuRegex.c:233
void gopherStart(FwdState *fwd)
Definition: gopher.cc:945
HttpRequest * request
Definition: FwdState.h:170
int read_hist[histSize]
Definition: IoStats.h:21
struct _request * request(char *urlin)
Definition: tcp-banger2.c:291
#define GOPHER_INFO
Definition: gopher.cc:62
char const * rawBuf() const
Definition: SquidString.h:86
Comm::ConnectionPointer const & serverConnection() const
Definition: FwdState.h:106
#define GOPHER_FILE
Definition: gopher.cc:39
int HTML_header_added
Definition: gopher.cc:116
void PF(int, void *)
Definition: forward.h:18
class SquidConfig Config
Definition: SquidConfig.cc:12
@ srcGopher
Gopher server.
Definition: Message.h:43
StatCounters statCounter
Definition: StatCounters.cc:12
void clean()
Definition: String.cc:120

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors