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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors