#line 26 "ag-gopher.c-nw" #include #include #include #include #include #include #include "gopher2mime.e" #define min(a, b) ((a) < (b) ? (a) : (b)) #ifndef howmany #define howmany(x,y) (((x)+((y)-1))/(y)) #endif #define NRBITS (sizeof(int) * 8) /* 8 bits per byte */ /* * FD_SETSIZE is the maximum number of file descriptors per process */ typedef struct { W3ADocumentInfo doc; char *request; /* The Gopher request */ int offset, len; /* Of request */ Bool is_menu; /* TRUE if Gopher directory */ FILE *f; /* The socket */ char buf[BUFSIZ]; /* For synthesized HTML */ int bufstart, buflen; /* Of buf */ Bool eod; /* End of data */ Bool non_blocking_io; /* O_NONBLOCK flag used? */ } ConnInfo; static ConnInfo *conn_info[FD_SETSIZE]; EXPORT Bool initGopher(char ***protocols, int *nrprotocols) { static char *protos[] = {"gopher"}; *protocols = protos; *nrprotocols = 1; return TRUE; } #line 89 "ag-gopher.c-nw" EXPORT int openGopher(const char *url, int method, int flags, const char *referer) { URI uri; char type; char *selector, *mime; char *port, *query; int s; char *request; size_t len; FILE *f; /* Analyze the URL */ if (! URL_parse(url, &uri) || uri.scheme != str2strip("gopher") || ! URL_parse_selector(strip2str(uri.path), &type, &selector)) { errno = EURL; return -1; } /* Connect; 70 is the default port */ port = uri.port ? strip2str(uri.port) : "70"; /* if ((s = connectTCP(strip2str(uri.host), port, flags & O_NONBLOCK)) == -1 */ if ((s = connectTCP(strip2str(uri.host), port, FALSE)) == -1 && errno != EINPROGRESS) return -1; debug("Connected by Gopher to \"%s.%s\"\n", strip2str(uri.host), port); /* Create request string into `request' and `len' */ if (! uri.search) { newarray(request, strlen(selector) + 3); len = sprintf(request, "%s\015\012", selector); } else if ((query = strchr(strip2str(uri.search), '='))) { query = URL_unescape(newstring(query + 1)); newarray(request, strlen(selector) + strlen(query) + 4); len = sprintf(request, "%s\t%s\015\012", selector, query); } else { query = URL_unescape(newstring(strip2str(uri.search))); newarray(request, strlen(selector) + strlen(query) + 4); len = sprintf(request, "%s\t%s\015\012", selector, query); } if ((flags & O_NONBLOCK) == 0) { /* Normal, blocking, I/O */ if (write(s, request, len) == -1) return -1; dispose(request); len = 0; #if 0 } else if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { /* Set non-blocking */ return -1; #endif } if (!(f = fdopen(s, "r+"))) return -1; /* Create document info and store in `conn_info[fd]' */ gopher2mime(type, selector, &mime); new(conn_info[s]); conn_info[s]->doc.url = newstring(url); conn_info[s]->doc.mime_type = newstring(mime); conn_info[s]->doc.mime_params = NULL; conn_info[s]->doc.title = NULL; conn_info[s]->doc.referer = newstring(referer); conn_info[s]->doc.status = NULL; conn_info[s]->doc.size = -1; conn_info[s]->is_menu = type == '1' || type == '7'; conn_info[s]->f = f; conn_info[s]->eod = FALSE; conn_info[s]->non_blocking_io = (flags & O_NONBLOCK) != 0; conn_info[s]->request = request; conn_info[s]->offset = 0; conn_info[s]->len = len; conn_info[s]->bufstart = 0; conn_info[s]->buflen = !conn_info[s]->is_menu ? 0 : sprintf(conn_info[s]->buf, "\n

Gopher menu

\n\n", url); return s; } #line 184 "ag-gopher.c-nw" static Bool ready_for_write(int fd) { #if USE_POLL struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLOUT; /* return: -1 = err; 0 = timed out; 1 = input available */ return poll(fds, 1, 0) > 0; #else /* USE_POLL */ int mask[howmany(FD_SETSIZE, NRBITS)]; struct timeval timeout; int n, i; timeout.tv_sec = 0; timeout.tv_usec = 0; for (i = 0; i < XtNumber(mask); i++) mask[i] = 0; mask[fd/NRBITS] |= 1 << (fd % NRBITS); return select(fd + 1, NULL, mask, NULL, &timeout) > 0; #endif /* USE_POLL */ } EXPORT Bool doneGopher(int fd) { int n; /* Try to send the request, if it hasn't been sent already */ if (conn_info[fd]->len != 0 && (! conn_info[fd]->non_blocking_io || ready_for_write(fd))) { debug("Write gopher request: %s", conn_info[fd]->request + conn_info[fd]->offset); n = fwrite(conn_info[fd]->request + conn_info[fd]->offset, 1, conn_info[fd]->len, conn_info[fd]->f); if (errno == EINPROGRESS) errno = EAGAIN; if (n == -1) return FALSE; /* Error or `EAGAIN' */ if (n > 0) { conn_info[fd]->offset += n; conn_info[fd]->len -= n; } fflush(conn_info[fd]->f); /* Make ready for reading */ } if (conn_info[fd]->len != 0) { errno = EAGAIN; /* Not ready yet */ return FALSE; } else { return TRUE; /* Everything's been sent */ } } EXPORT int peekGopher(int fd) { int mask[howmany(FD_SETSIZE, NRBITS)]; struct timeval timeout; int n, i; /* Check if there's anything to read */ timeout.tv_sec = 0; timeout.tv_usec = 0; for (i = 0; i < XtNumber(mask); i++) mask[i] = 0; mask[fd/NRBITS] |= 1 << (fd % NRBITS); if ((n = select(fd + 1, mask, NULL, NULL, &timeout)) == -1) return -1; return n; /* 0 or 1 */ } /* tp_to_icon -- select HTML icon entity based on Gopher type */ static char *tp_to_icon(char tp) { switch (tp) { case '0': return "&text.document;"; case '1': return "&folder;"; case '2': return "&telephone;"; case '4': return "&binhex.document;"; case '7': return "&index;"; case '8': return "&telnet;"; case '9': return "&binary.document;"; case 's': return "&audio;"; case 'I': return "ℑ"; case 'g': return "ℑ"; case 'G': return "ℑ"; case 'h': return "&text.document;"; default: return "&unknown.document;"; } } /* read_menu -- read available Gopher menu lines and convert them to HTML */ static int read_menu(int fd, char *buf, int nbytes) { int n; char line[BUFSIZ], tp, *title, *selector, *host, *port, *p; /* Read something, only if there is nothing left in the buffer */ if (conn_info[fd]->buflen == 0) { if (conn_info[fd]->eod) { /* End of data has been seen */ /* skip */; } else if (!fgets(line, sizeof(line), conn_info[fd]->f)) { return -1; /* Error or EAGAIN */ } else if (line[0] != '.') { /* Not end of menu */ tp = line[0]; title = tokenize(line + 1, "\t", &p); selector = tokenize(p, "\t", &p); host = tokenize(p, "\t", &p); port = tokenize(p, "\t\012\015", &p); conn_info[fd]->buflen = sprintf (conn_info[fd]->buf, "
  • %s %s\n", tp_to_icon(tp), host, port, tp, selector, title); conn_info[fd]->bufstart = 0; debug("Read Gopher menu line: %s", conn_info[fd]->buf); } else { /* End of menu */ conn_info[fd]->buflen = sprintf(conn_info[fd]->buf, "
  • \n"); conn_info[fd]->bufstart = 0; conn_info[fd]->eod = TRUE; } } /* Copy from buffer to caller's buffer */ n = min(nbytes, conn_info[fd]->buflen); debug("read_menu: %d bytes: %s\n", n, conn_info[fd]->doc.url); strncpy(buf, conn_info[fd]->buf + conn_info[fd]->bufstart, n); conn_info[fd]->buflen -= n; conn_info[fd]->bufstart += n; return n; } EXPORT int readGopher(int fd, char *buf, size_t nbytes) { if (conn_info[fd]->len > 0) if (peekGopher(fd) == -1) return -1; if (conn_info[fd]->is_menu) return read_menu(fd, buf, nbytes); else return fread(buf, 1, nbytes, conn_info[fd]->f); } EXPORT int writeGopher(int fd, const char *buf, size_t nbytes) { errno = EMETHOD; return -1; /* Impossible in Gopher */ } EXPORT Bool infoGopher(int fd, W3ADocumentInfo *buf) { buf->url = newstring(conn_info[fd]->doc.url); buf->mime_type = newstring(conn_info[fd]->doc.mime_type); buf->mime_params = newstring(conn_info[fd]->doc.mime_params); buf->title = newstring(conn_info[fd]->doc.title); buf->referer = newstring(conn_info[fd]->doc.referer); buf->status = newstring(conn_info[fd]->doc.status); debug("infoGopher: %s --> %s\n", buf->url, buf->mime_type); return TRUE; } EXPORT Bool closeGopher(int fd) { int status; status = fclose(conn_info[fd]->f); dispose(conn_info[fd]->doc.url); dispose(conn_info[fd]->doc.mime_type); dispose(conn_info[fd]->doc.mime_params); dispose(conn_info[fd]->doc.title); dispose(conn_info[fd]->doc.referer); dispose(conn_info[fd]->doc.status); dispose(conn_info[fd]->request); dispose(conn_info[fd]); return status; } EXPORT Bool deleteGopher(const char *url) { errno = EMETHOD; return -1; /* Impossible in Gopher */ }