Precis:
Gopher browsing is now mostly functional.
Due to having to wait for the full content before converting to html, large pages like
search results might take some seconds to load, but otherwise it works not too bad.
Supported item-types:
- 0 (plain text)
- 1 (gopher dir) converted to html for display
- 3 (error, when first item should behave as an HTTP 404, not much tested)
- 7 (search) converted to html for display
- 8 generates a link to a telnet: URI
- g (GIF) adds a link (or optionally inlines the image as <img>)
- h (html) selectors begining with URL: are automatically converted to direct links to the
specified URL, which then is not limited to http: either.
- i displays a text line
- I (image) links to the item but tries to display as text.
- 9 (binary) links to the item but tries to display as text.
- d (PDF, unofficial)
- p (seems to be PNG, unofficial)
- all other unknown types are just linked as is, and will probably work when the mime type
is correctly sniffed.
Unsupported item-types:
- 2 (CSO search) I failed to find a sample link, it requires a complex and separate
protocol.
- T (TN3270) Failed to find a sample link, it's an antique competitor to telnet.
Missing stuff, which will be considered in a second iteration:
- document the feature.
- document requirements for ports (like, OSX requires libcurl from MacPort to have the
fetcher working).
- better error handling on non-existing files (will require an heuristic since gopher
doesn't have out-of-band signaling).
- make the parser more robust.
- fix url escaping in generated html code, gopher selectors can include any character
except tab, though no tested servers ever tried using reserved html chars yet.
- merge custom generated CSS with internal.css.
- improve the used CSS (reuse the blueish dirlist one ?).
- add text/dir/search icons like other usual clients (optional ?).
- links to ambiguous items (like type 'I' which can be JPEG or PNG or another
image type, and binary types like '9') will attempt to load the target as a text
file. Handling them correctly probably requires implementing LLCACHE_RETRIEVE_SNIFF_TYPE.
Archive types could probably get a fake Content-disposition header forcing a download
though.
- make gopher: URI scheme handling explicit to OS front-ends (done for BeOS).
- fix search field sending an extra = in the url (add a "GOPHER" form method
maybe ?), though all tested search engines just skip it.
- maybe Gopher+ support but it seems most clients just ignore it.
Added files
Index: render/gopher.c
===================================================================
--- /dev/null 2011-05-04 14:01:53.000000000 +0200
+++ render/gopher.c 2011-05-04 12:18:42.000000000 +0200
@@ -0,0 +1,662 @@
+/*
+ * Copyright 2006 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2006 Adrian Lees <adrianl(a)users.sourceforge.net>
+ * Copyright 2011 François Revol <mmu_man(a)users.sourceforge.net>
+ *
+ * This file is part of NetSurf,
http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <
http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Content for text/x-gopher-directory (implementation).
+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+#include <strings.h>
+#include <math.h>
+
+#include "content/content_protected.h"
+#include "desktop/gui.h"
+#include "desktop/options.h"
+#include "render/gopher.h"
+#include "utils/http.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/utils.h"
+
+static char *gen_nice_title(const char *path);
+static bool gopher_generate_top(char *buffer, int buffer_length);
+static bool gopher_generate_title(const char *title, char *buffer, int buffer_length);
+static bool gopher_generate_row(const char **data, size_t *size,
+ char *buffer, int buffer_length);
+static bool gopher_generate_bottom(char *buffer, int buffer_length);
+
+
+static struct {
+ char type;
+ const char *mime;
+} gopher_type_map[] = {
+ /* these come from
http://tools.ietf.org/html/rfc1436 */
+ { '0', "text/plain" },
+ { '1', "text/x-gopher-directory;charset=UTF-8" }, /* gopher directory
*/
+ /* 2 CSO search */
+ /* 3 error message */
+ /* 4 binhex encoded text */
+ /* 5 binary archive file */
+ /* 6 uuencoded text */
+ { '7', "text/x-gopher-directory;charset=UTF-8" }, /* search query */
+ /* 8 telnet: */
+ /* 9 binary */
+ { 'g', "image/gif" },
+ { 'h', "text/html" },
+ /* i information text */
+ /* I image (depends, usually jpeg) */
+ /* s audio (wav?) */
+ /* T tn3270 session */
+
+ /* those are not standardized */
+ { 'd', "application/pdf" }, /* display?? seems to be only for PDF
files so far */
+ { 'p', "image/png"}, /* at least on
gopher://namcub.accelera-labs.com/1/pics */
+ { 0, NULL }
+};
+
+
+/**
+ * Create a CONTENT_GOPHER.
+ */
+
+bool gopher_create(struct content *c, const http_parameter *params)
+{
+ bool ok;
+ ok = html_create(c, params);
+ return ok;
+}
+
+
+/**
+ * Convert a CONTENT_GOPHER for display.
+ */
+
+bool gopher_convert(struct content *c)
+{
+ char *title;
+ char buffer[1024];
+ const char *data;
+ unsigned long size;
+ const char *p;
+ unsigned long left;
+ bool ok;
+
+ data = content__get_source_data(c, &size);
+
+ p = data;
+ left = size;
+ if (data == NULL || size == 0)
+ return false;
+
+ if (gopher_generate_bottom(buffer, sizeof(buffer))) {
+ ok = html_process_data(c, buffer, strlen(buffer));
+ if (!ok)
+ return false;
+ }
+ if (gopher_generate_top(buffer, sizeof(buffer))) {
+ ok = html_process_data(c, buffer, strlen(buffer));
+ if (!ok)
+ return false;
+ }
+ title = gen_nice_title(content__get_url(c));
+ if (gopher_generate_title(title, buffer, sizeof(buffer))) {
+ ok = html_process_data(c, buffer, strlen(buffer));
+ if (!ok)
+ return false;
+ }
+ free(title);
+
+ while (gopher_generate_row(&p, &left, buffer, sizeof(buffer))) {
+ ok = html_process_data(c, buffer, strlen(buffer));
+ if (!ok)
+ return false;
+ gui_multitask();
+ }
+
+ /* finally make it HTML so we don't have to bother for other calls */
+ c->type = CONTENT_HTML;
+ return html_convert(c);
+}
+
+
+static char *html_escape_string(char *str)
+{
+ char *nice_str, *cnv, *tmp;
+
+ if (str == NULL) {
+ return NULL;
+ }
+
+ /* Convert str for display */
+ nice_str = malloc(strlen(str) * SLEN("&") + 1);
+ if (nice_str == NULL) {
+ return NULL;
+ }
+
+ /* Escape special HTML characters */
+ for (cnv = nice_str, tmp = str; *tmp != '\0'; tmp++) {
+ if (*tmp == '<') {
+ *cnv++ = '&';
+ *cnv++ = 'l';
+ *cnv++ = 't';
+ *cnv++ = ';';
+ } else if (*tmp == '>') {
+ *cnv++ = '&';
+ *cnv++ = 'g';
+ *cnv++ = 't';
+ *cnv++ = ';';
+ } else if (*tmp == '&') {
+ *cnv++ = '&';
+ *cnv++ = 'a';
+ *cnv++ = 'm';
+ *cnv++ = 'p';
+ *cnv++ = ';';
+ } else {
+ *cnv++ = *tmp;
+ }
+ }
+ *cnv = '\0';
+
+ return nice_str;
+}
+
+
+static char *gen_nice_title(const char *path)
+{
+ const char *tmp;
+ char *nice_path, *cnv;
+ char *title;
+ int title_length;
+
+ /* Convert path for display */
+ nice_path = malloc(strlen(path) * SLEN("&") + 1);
+ if (nice_path == NULL) {
+ return NULL;
+ }
+
+ /* Escape special HTML characters */
+ for (cnv = nice_path, tmp = path; *tmp != '\0'; tmp++) {
+ if (*tmp == '<') {
+ *cnv++ = '&';
+ *cnv++ = 'l';
+ *cnv++ = 't';
+ *cnv++ = ';';
+ } else if (*tmp == '>') {
+ *cnv++ = '&';
+ *cnv++ = 'g';
+ *cnv++ = 't';
+ *cnv++ = ';';
+ } else if (*tmp == '&') {
+ *cnv++ = '&';
+ *cnv++ = 'a';
+ *cnv++ = 'm';
+ *cnv++ = 'p';
+ *cnv++ = ';';
+ } else {
+ *cnv++ = *tmp;
+ }
+ }
+ *cnv = '\0';
+
+ /* Construct a localised title string */
+ title_length = (cnv - nice_path) + strlen(messages_get("FileIndex"));
+ title = malloc(title_length + 1);
+
+ if (title == NULL) {
+ free(nice_path);
+ return NULL;
+ }
+
+ /* Set title to localised "Index of <nice_path>" */
+ snprintf(title, title_length, messages_get("FileIndex"), nice_path);
+
+ free(nice_path);
+
+ return title;
+}
+
+
+/**
+ * Convert the gopher item type to mime type
+ *
+ * \return MIME type string
+ *
+ */
+
+const char *gopher_type_to_mime(char type)
+{
+ int i;
+
+ for (i = 0; gopher_type_map[i].type; i++)
+ if (gopher_type_map[i].type == type)
+ return gopher_type_map[i].mime;
+ return NULL;
+}
+
+
+/**
+ * Tells if the gopher item type needs to be converted to html
+ *
+ * \return true iff the item must be converted
+ *
+ */
+
+bool gopher_need_generate(char type)
+{
+ switch (type) {
+ case '1':
+ case '7':
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+/**
+ * Generates the top part of an HTML directory listing page
+ *
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * gopher_generate_top()
+ * gopher_generate_title()
+ * gopher_generate_row() -- call 'n' times for 'n' rows
+ * gopher_generate_bottom()
+ */
+
+static bool gopher_generate_top(char *buffer, int buffer_length)
+{
+ int error = snprintf(buffer, buffer_length,
+ "<html>\n"
+ "<head>\n"
+ /*"<!-- base href=\"%s\" -->\n"*//* XXX: needs the content
url */
+ /* Don't do that:
+ * seems to trigger a reparsing of the gopher data itself as html...
+ * "<meta http-equiv=\"Content-Type\" content=\"text/html;
charset=UTF-8\" />\n"
+ */
+ /* TODO: move this to clean CSS in internal.css */
+ "<link rel=\"stylesheet\" title=\"Standard\" "
+ "type=\"text/css\"
href=\"resource:internal.css\">\n"
+ "<style>\n"
+ /* XXX: white-space: pre-wrap would be better but is currently buggy */
+ "span { font-family: Courier, monospace; white-space: pre; }\n"
+ "body { margin: 10px; }\n");
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+
+}
+
+
+/**
+ * Generates the part of an HTML directory listing page that contains the title
+ *
+ * \param title title to use
+ * \param buffer buffer to fill with generated HTML
+ * \param buffer_length maximum size of buffer
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * gopher_generate_top()
+ * gopher_generate_title()
+ * gopher_generate_row() -- call 'n' times for 'n' rows
+ * gopher_generate_bottom()
+ */
+
+static bool gopher_generate_title(const char *title, char *buffer, int buffer_length)
+{
+ int error;
+
+ if (title == NULL)
+ title = "";
+
+ error = snprintf(buffer, buffer_length,
+ "</style>\n"
+ "<title>%s</title>\n"
+ "</head>\n"
+ "<body id=\"gopher\">\n"
+ "<h1>%s</h1>\n",
+ title, title);
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+/**
+ * Internal worker called by gopher_generate_row().
+ */
+
+static bool gopher_generate_row_internal(char type, char *fields[5],
+ char *buffer, int buffer_length)
+{
+ char *nice_text;
+ char *redirect_url = NULL;
+ int error;
+ bool alt_port = false;
+ char *username = NULL;
+
+ if (fields[3] && strcmp(fields[3], "70"))
+ alt_port = true;
+
+ /* escape html special characters */
+ nice_text = html_escape_string(fields[0]);
+
+ /* XXX: outputting \n generates better looking html code,
+ * but currently screws up indentation due to a bug.
+ */
+#define HTML_LF
+/*#define HTML_LF "\n"*/
+
+ switch (type) {
+ case '.':
+ /* end of the page */
+ *buffer = '\0';
+ break;
+ case '0': /* text/plain link */
+ case '9': /* binary */
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"gopher://%s%s%s/%c%s\">"HTML_LF
+ "<span class=\"dir\">%s</span><br/>"HTML_LF
+ "</a>"HTML_LF,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1], nice_text);
+ break;
+ case '1':
+ /*
+ * directory link
+ */
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"gopher://%s%s%s/%c%s\">"HTML_LF
+ "<span class=\"text\">%s</span><br/>"HTML_LF
+ "</a>"HTML_LF,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1], nice_text);
+ break;
+ case '3':
+ /* Error
+ */
+ error = snprintf(buffer, buffer_length,
+ "<span class=\"error\">%s</span><br/>"HTML_LF,
+ nice_text);
+ break;
+ case '7':
+ /* TODO: handle search better.
+ * For now we use an unnamed input field and accept sending ?=foo
+ * as it seems at least Veronica-2 ignores the = but it's unclean.
+ */
+ error = snprintf(buffer, buffer_length,
+ "<form method=\"get\"
action=\"gopher://%s%s%s/%c%s\">"HTML_LF
+ "<span class=\"query\"><label>%s <input
name=\"\" type=\"text\" align=\"right\"
/></label></span><br/>"HTML_LF
+ "</form>"HTML_LF,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1], nice_text);
+ break;
+ case '8':
+ /* telnet: links
+ * cf. gopher://78.80.30.202/1/ps3
+ * -> gopher://78.80.30.202:23/8/ps3/new -> new(a)78.80.30.202
+ */
+ alt_port = false;
+ if (fields[3] && strcmp(fields[3], "23"))
+ alt_port = true;
+ username = strrchr(fields[1], '/');
+ if (username)
+ username++;
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"telnet://%s%s%s%s%s\">"HTML_LF
+ "<span class=\"dir\">%s</span><br/>"HTML_LF
+ "</a>"HTML_LF,
+ username ? username : "",
+ username ? "@" : "",
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ nice_text);
+ break;
+ case 'g':
+ case 'I':
+ case 'p':
+ /* quite dangerous, cf.
gopher://namcub.accela-labs.com/1/pics */
+ if (option_gopher_inline_images) {
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"gopher://%s%s%s/%c%s\">"HTML_LF
+ "<span class=\"img\">%s "HTML_LF /*
</span><br/> */
+ //"<span class=\"img\" >"HTML_LF
+ "<img src=\"gopher://%s%s%s/%c%s\"
alt=\"%s\"/>"HTML_LF
+ "</span>"
+ "<br/>"
+ "</a>"HTML_LF,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1],
+ nice_text,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1],
+ nice_text);
+ break;
+ }
+ /* fallback to default, link them */
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"gopher://%s%s%s/%c%s\">"HTML_LF
+ "<span class=\"dir\">%s</span><br/>"HTML_LF
+ "</a>"HTML_LF,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1], nice_text);
+ break;
+ case 'h':
+ if (fields[1] && strncmp(fields[1], "URL:", 4) == 0)
+ redirect_url = fields[1] + 4;
+ /* cf. gopher://pineapple.vg/1 */
+ if (fields[1] && strncmp(fields[1], "/URL:", 5) == 0)
+ redirect_url = fields[1] + 5;
+ if (redirect_url) {
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"%s\">"HTML_LF
+ "<span class=\"link\">%s</span><br/>"HTML_LF
+ "</a>"HTML_LF,
+ redirect_url,
+ nice_text);
+ } else {
+ /* cf.
gopher://sdf.org/1/sdf/classes/ */
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"gopher://%s%s%s/%c%s\">"HTML_LF
+ "<span class=\"dir\">%s</span><br/>"HTML_LF
+ "</a>"HTML_LF,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1], nice_text);
+ }
+ break;
+ case 'i':
+ error = snprintf(buffer, buffer_length,
+ "<span class=\"info\">%s</span><br/>"HTML_LF,
+ nice_text);
+ break;
+ default:
+ LOG(("warning: unknown gopher item type 0x%02x '%c'\n", type,
type));
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"gopher://%s%s%s/%c%s\">"HTML_LF
+ "<span class=\"dir\">%s</span><br/>"HTML_LF
+ "</a>"HTML_LF,
+ fields[2],
+ alt_port ? ":" : "",
+ alt_port ? fields[3] : "",
+ type, fields[1], nice_text);
+ break;
+ }
+
+ free(nice_text);
+
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
+/**
+ * Generates the part of an HTML directory listing page that displays a row
+ * of the gopher data
+ *
+ * \param size pointer to the data buffer pointer
+ * \param size pointer to the remaining data size
+ * \param buffer buffer to fill with generated HTML
+ * \param buffer_length maximum size of buffer
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * gopher_generate_top()
+ * gopher_generate_title()
+ * gopher_generate_row() -- call 'n' times for 'n' rows
+ * gopher_generate_bottom()
+ */
+
+static bool gopher_generate_row(const char **data, size_t *size,
+ char *buffer, int buffer_length)
+{
+ bool ok = false;
+ char type = 0;
+ int field = 0;
+ /* name, selector, host, port, gopher+ flag */
+ char *fields[5] = { NULL, NULL, NULL, NULL, NULL };
+ const char *s = *data;
+ const char *p = *data;
+ int i;
+
+ for (; *size && *p; p++, (*size)--) {
+ if (!type) {
+ type = *p;
+ if (!type || type == '\n' || type == '\r') {
+ LOG(("warning: invalid gopher item type 0x%02x\n", type));
+ }
+ s++;
+ continue;
+ }
+ switch (*p) {
+ case '\n':
+ if (field > 0) {
+ LOG(("warning: unterminated gopher item '%c'\n", type));
+ }
+ //FALLTHROUGH
+ case '\r':
+ if (*size < 1 || p[1] != '\n') {
+ LOG(("warning: CR without LF in gopher item '%c'\n", type));
+ }
+ if (field < 3 && type != '.') {
+ LOG(("warning: unterminated gopher item '%c'\n", type));
+ }
+ fields[field] = malloc(p - s + 1);
+ memcpy(fields[field], s, p - s);
+ fields[field][p - s] = '\0';
+ if (type == '.' && field == 0 && p == s) {
+ ;/* XXX: signal end of page? For now we just ignore it. */
+ }
+ ok = gopher_generate_row_internal(type, fields, buffer, buffer_length);
+ for (i = 0; i < 5; i++) {
+ free(fields[i]);
+ fields[i] = NULL;
+ }
+ (*size)--;
+ p++;
+ if (*size && *p == '\n') {
+ p++;
+ (*size)--;
+ }
+ *data = p;
+ field = 0;
+ return ok;
+ case '\x09':
+ if (field >= 4) {
+ LOG(("warning: extra tab in gopher item '%c'\n", type));
+ break;
+ }
+ fields[field] = malloc(p - s + 1);
+ memcpy(fields[field], s, p - s);
+ fields[field][p - s] = '\0';
+ field++;
+ s = p + 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Generates the bottom part of an HTML directory listing page
+ *
+ * \return Bottom of directory listing HTML
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * gopher_generate_top()
+ * gopher_generate_title()
+ * gopher_generate_row() -- call 'n' times for 'n' rows
+ * gopher_generate_bottom()
+ */
+
+static bool gopher_generate_bottom(char *buffer, int buffer_length)
+{
+ int error = snprintf(buffer, buffer_length,
+ "</div>\n"
+ "</body>\n"
+ "</html>\n");
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
Index: render/gopher.h
===================================================================
--- /dev/null 2011-05-04 14:01:53.000000000 +0200
+++ render/gopher.h 2011-05-04 12:18:42.000000000 +0200
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2006 James Bursa <bursa(a)users.sourceforge.net>
+ * Copyright 2006 Adrian Lees <adrianl(a)users.sourceforge.net>
+ * Copyright 2011 François Revol <mmu_man(a)users.sourceforge.net>
+ *
+ * This file is part of NetSurf,
http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <
http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Content for text/x-gopher-directory (interface).
+ */
+
+#ifndef _NETSURF_RENDER_GOPHER_H_
+#define _NETSURF_RENDER_GOPHER_H_
+
+#include <stddef.h>
+
+struct content;
+struct http_parameter;
+
+bool gopher_create(struct content *c, const struct http_parameter *params);
+bool gopher_convert(struct content *c);
+
+
+const char *gopher_type_to_mime(char type);
+bool gopher_need_generate(char type);
+
+#endif
Changed files
Makefile.sources | 3 +-
beos/beos_res.rdef | 2 +
content/content.c | 6 ++++
content/content_type.h | 1
content/fetchers/curl.c | 71 +++++++++++++++++++++++++++++++++++++++++++++---
desktop/options.c | 3 ++
desktop/options.h | 1
utils/url.c | 32 +++++++++++++++++++++
utils/url.h | 1
9 files changed, 116 insertions(+), 4 deletions(-)
Index: beos/beos_res.rdef
===================================================================
--- beos/beos_res.rdef (revision 12271)
+++ beos/beos_res.rdef (working copy)
@@ -108,9 +108,11 @@
"types" = "image/jpeg",
"types" = "application/x-vnd.Be-bookmark",
"types" = "text",
+ "types" = "text/x-gopher-directory",
"types" = "application/x-vnd.Be-doc_bookmark",
"types" = "application/x-vnd.Be.URL.file",
"types" = "application/x-vnd.Be.URL.ftp",
+ "types" = "application/x-vnd.Be.URL.gopher",
"types" = "application/x-vnd.Be.URL.http",
"types" = "application/x-vnd.Be.URL.https"
};
Index: Makefile.sources
===================================================================
--- Makefile.sources (revision 12271)
+++ Makefile.sources (working copy)
@@ -12,7 +12,8 @@
S_RENDER := box.c box_construct.c box_normalise.c favicon.c \
font.c form.c html.c html_interaction.c html_redraw.c \
- hubbub_binding.c imagemap.c layout.c list.c table.c textplain.c
+ hubbub_binding.c imagemap.c layout.c list.c table.c textplain.c \
+ gopher.c
S_UTILS := base64.c filename.c hashtable.c http.c locale.c messages.c \
talloc.c url.c utf8.c utils.c useragent.c filepath.c log.c
Index: utils/url.c
===================================================================
--- utils/url.c (revision 12271)
+++ utils/url.c (working copy)
@@ -856,6 +856,38 @@
}
/**
+ * Extract the gopher document type from an URL
+ *
+ * \param url an absolute URL
+ * \param result pointer to buffer to hold result
+ * \return URL_FUNC_OK on success
+ */
+
+url_func_result url_gopher_type(const char *url, char *result)
+{
+ url_func_result status;
+ struct url_components components;
+
+ assert(url);
+
+ status = url_get_components(url, &components);
+ if (status == URL_FUNC_OK) {
+ if (!components.path) {
+ status = URL_FUNC_FAILED;
+ } else {
+ if (strlen(components.path) < 2)
+ *result = '1';
+ else if (components.path[0] == '/')
+ *result = components.path[1];
+ else
+ status = URL_FUNC_FAILED;
+ }
+ }
+ url_destroy_components(&components);
+ return status;
+}
+
+/**
* Attempt to find a nice filename for a URL.
*
* \param url an absolute URL
Index: utils/url.h
===================================================================
--- utils/url.h (revision 12271)
+++ utils/url.h (working copy)
@@ -60,6 +60,7 @@
url_func_result url_path(const char *url, char **result);
url_func_result url_leafname(const char *url, char **result);
url_func_result url_fragment(const char *url, char **result);
+url_func_result url_gopher_type(const char *url, char *result);
url_func_result url_compare(const char *url1, const char *url2,
bool nofrag, bool *result);
Index: desktop/options.h
===================================================================
--- desktop/options.h (revision 12271)
+++ desktop/options.h (working copy)
@@ -49,6 +49,7 @@
extern int option_http_proxy_auth;
extern char *option_http_proxy_auth_user;
extern char *option_http_proxy_auth_pass;
+extern bool option_gopher_inline_images;
extern int option_font_size;
extern int option_font_min_size;
extern char *option_accept_language;
Index: desktop/options.c
===================================================================
--- desktop/options.c (revision 12271)
+++ desktop/options.c (working copy)
@@ -69,6 +69,8 @@
char *option_http_proxy_auth_user = 0;
/** Proxy authentication password */
char *option_http_proxy_auth_pass = 0;
+/** Inline image items in Gopher pages. Dangerous. */
+bool option_gopher_inline_images = false;
/** Default font size / 0.1pt. */
int option_font_size = 128;
/** Minimum font size. */
@@ -248,6 +250,7 @@
{ "http_proxy_auth", OPTION_INTEGER, &option_http_proxy_auth },
{ "http_proxy_auth_user", OPTION_STRING, &option_http_proxy_auth_user },
{ "http_proxy_auth_pass", OPTION_STRING, &option_http_proxy_auth_pass },
+ { "gopher_inline_images", OPTION_BOOL, &option_gopher_inline_images },
{ "font_size", OPTION_INTEGER, &option_font_size },
{ "font_min_size", OPTION_INTEGER, &option_font_min_size },
{ "font_sans", OPTION_STRING, &option_font_sans },
Index: content/content.c
===================================================================
--- content/content.c (revision 12271)
+++ content/content.c (working copy)
@@ -40,6 +40,7 @@
#include "desktop/options.h"
#include "render/html.h"
#include "render/textplain.h"
+#include "render/gopher.h"
#ifdef WITH_JPEG
#include "image/jpeg.h"
#endif
@@ -207,6 +208,7 @@
{"text/css", CONTENT_CSS},
{"text/html", CONTENT_HTML},
{"text/plain", CONTENT_TEXTPLAIN},
+ {"text/x-gopher-directory", CONTENT_GOPHER},
#ifdef WITH_MNG
{"video/mng", CONTENT_MNG},
{"video/x-mng", CONTENT_MNG},
@@ -263,6 +265,7 @@
#ifdef WITH_APPLE_IMAGE
"APPLE_IMAGE",
#endif
+ "GOPHER",
"OTHER",
"UNKNOWN"
};
@@ -403,6 +406,9 @@
{0, 0, apple_image_convert, 0, apple_image_destroy, 0, 0, 0,
apple_image_redraw, apple_image_redraw_tiled, 0, 0, apple_image_clone, false},
#endif
+ {gopher_create, 0, gopher_convert,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ true},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false}
};
#define HANDLER_MAP_COUNT (sizeof(handler_map) / sizeof(handler_map[0]))
Index: content/content_type.h
===================================================================
--- content/content_type.h (revision 12271)
+++ content/content_type.h (working copy)
@@ -77,6 +77,7 @@
#ifdef WITH_APPLE_IMAGE
CONTENT_APPLE_IMAGE,
#endif
+ CONTENT_GOPHER,
/* these must be the last two */
CONTENT_OTHER,
CONTENT_UNKNOWN /**< content-type not received yet */
Index: content/fetchers/curl.c
===================================================================
--- content/fetchers/curl.c (revision 12271)
+++ content/fetchers/curl.c (working copy)
@@ -44,6 +44,7 @@
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
+#include "render/gopher.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/schedule.h"
@@ -72,6 +73,7 @@
bool abort; /**< Abort requested. */
bool stopped; /**< Download stopped on purpose. */
bool only_2xx; /**< Only HTTP 2xx responses acceptable. */
+ bool gopher_type; /**< Indicates the type of document for gopher: url */
char *url; /**< URL of this fetch. */
char *host; /**< The hostname of this fetch. */
struct curl_slist *headers; /**< List of request headers. */
@@ -111,6 +113,10 @@
bool only_2xx, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers);
+static void * fetch_curl_setup_gopher(struct fetch *parent_fetch, const char *url,
+ bool only_2xx, const char *post_urlenc,
+ const struct fetch_multipart_data *post_multipart,
+ const char **headers);
static bool fetch_curl_start(void *vfetch);
static bool fetch_curl_initiate_fetch(struct curl_fetch_info *fetch,
CURL *handle);
@@ -220,14 +226,19 @@
data = curl_version_info(CURLVERSION_NOW);
for (i = 0; data->protocols[i]; i++) {
+ fetcher_setup_fetch setup_hook;
/* Ignore non-http(s) protocols */
- if (strcmp(data->protocols[i], "http") != 0 &&
- strcmp(data->protocols[i], "https") != 0)
+ if (strcmp(data->protocols[i], "http") == 0 ||
+ strcmp(data->protocols[i], "https") == 0)
+ setup_hook = fetch_curl_setup;
+ else if (strcmp(data->protocols[i], "gopher") == 0)
+ setup_hook = fetch_curl_setup_gopher;
+ else
continue;
if (!fetch_add_fetcher(data->protocols[i],
fetch_curl_initialise,
- fetch_curl_setup,
+ setup_hook,
fetch_curl_start,
fetch_curl_abort,
fetch_curl_free,
@@ -339,6 +350,7 @@
fetch->abort = false;
fetch->stopped = false;
fetch->only_2xx = only_2xx;
+ fetch->gopher_type = 0;
fetch->url = strdup(url);
fetch->headers = 0;
fetch->host = host;
@@ -410,6 +422,41 @@
}
+void * fetch_curl_setup_gopher(struct fetch *parent_fetch, const char *url,
+ bool only_2xx, const char *post_urlenc,
+ const struct fetch_multipart_data *post_multipart,
+ const char **headers)
+{
+ struct curl_fetch_info *f;
+ const char *mime;
+ char type;
+ f = fetch_curl_setup(parent_fetch, url, only_2xx, post_urlenc,
+ post_multipart, headers);
+ if (url_gopher_type(url, &type) == URL_FUNC_OK && type)
+ f->gopher_type = type;
+ else
+ f->http_code = 404;
+
+ mime = gopher_type_to_mime(type);
+ /* TODO: add a fetch_mimetype_by_ext() or fetch_mimetype_sniff_data() */
+ /*
+ if (mime == NULL)
+ mime = "application/octet-stream";
+ */
+
+ if (mime) {
+ char s[80];
+ /* fprintf(stderr, "gopher mime is '%s'\n", mime); */
+ snprintf(s, sizeof s, "Content-type: %s\r\n", mime);
+ s[sizeof s - 1] = 0;
+ fetch_send_callback(FETCH_HEADER, f->fetch_handle, s, strlen(s),
+ FETCH_ERROR_NO_ERROR);
+ }
+
+ return f;
+}
+
+
/**
* Dispatch a single job
*/
@@ -771,6 +818,8 @@
LOG(("done %s", f->url));
if (abort_fetch == false && result == CURLE_OK) {
+ //if (f->gopher_type)
+ //fetch_curl_gopher_data(NULL, 0, 0, f);
/* fetch completed normally */
if (f->stopped ||
(!f->had_headers &&
@@ -978,6 +1027,22 @@
struct curl_fetch_info *f = _f;
CURLcode code;
+ /* gopher data receives special treatment */
+ if (f->gopher_type && gopher_need_generate(f->gopher_type)) {
+ /* type 3 items report an error */
+ if (!f->http_code) {
+ if (data[0] == '3') {
+ /* TODO: try to guess better from the string ?
+ * like "3 '/bcd' doesn't exist!"
+ * TODO: what about other file types ?
+ */
+ f->http_code = 404;
+ } else {
+ f->http_code = 200;
+ }
+ }
+ }
+
/* ensure we only have to get this information once */
if (!f->http_code)
{
Conflicted files
Removed files