📄 gopher.c
字号:
/* Gopher access protocol (RFC 1436) *//* $Id: gopher.c,v 1.31.2.2 2005/05/01 21:04:46 jonas Exp $ *//* Based on version of HTGopher.c in the lynx tree. * * Author tags: * TBL Tim Berners-Lee * FM Foteos Macrides * * A little history: * 26 Sep 90 Adapted from other accesses (News, HTTP) TBL * 29 Nov 91 Downgraded to C, for portable implementation. * 10 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Added a * form-based CSO/PH gateway. Can be invoked via a * "cso://host[:port]/" or "gopher://host:105/2" * URL. If a gopher URL is used with a query token * ('?'), the old ISINDEX procedure will be used * instead of the form-based gateway. * 15 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Pass * port 79, gtype 0 gopher URLs to the finger * gateway. */#ifdef HAVE_CONFIG_H#include <config.h>#endif#include <errno.h>#include <stdlib.h>#include <string.h>#include "elinks.h"#include "cache/cache.h"#include "intl/gettext/libintl.h"#include "lowlevel/connect.h"#include "modules/module.h"#include "protocol/gopher/gopher.h"#include "protocol/protocol.h"#include "protocol/uri.h"#include "sched/connection.h"#include "util/conv.h"#include "util/memory.h"#include "util/string.h"struct module gopher_protocol_module = struct_module( /* name: */ N_("Gopher"), /* options: */ NULL, /* hooks: */ NULL, /* submodules: */ NULL, /* data: */ NULL, /* init: */ NULL, /* done: */ NULL);/* Gopher entity types */enum gopher_entity { GOPHER_UNKNOWN = 0 , /* Special fall-back entity */ GOPHER_FILE = '0', GOPHER_DIRECTORY = '1', GOPHER_CSO = '2', GOPHER_ERROR = '3', GOPHER_MACBINHEX = '4', GOPHER_PCBINARY = '5', GOPHER_UUENCODED = '6', GOPHER_INDEX = '7', GOPHER_TELNET = '8', GOPHER_BINARY = '9', GOPHER_GIF = 'g', GOPHER_HTML = 'h', /* HTML */ GOPHER_CHTML = 'H', /* HTML */ GOPHER_MIME = 'm', GOPHER_SOUND = 's', GOPHER_WWW = 'w', /* W3 address */ GOPHER_IMAGE = 'I', GOPHER_TN3270 = 'T', GOPHER_INFO = 'i', /* Information or separator line */ GOPHER_DUPLICATE = '+', GOPHER_PLUS_IMAGE = ':', /* Addition from Gopher Plus */ GOPHER_PLUS_MOVIE = ';', GOPHER_PLUS_SOUND = '<', GOPHER_PLUS_PDF = 'P',};/* Default Gopher Node type is directory listing */#define DEFAULT_GOPHER_ENTITY GOPHER_DIRECTORY#define entity_needs_gopher_access(entity) \ ((entity) != GOPHER_TELNET \ && (entity) != GOPHER_TN3270 \ && (entity) != GOPHER_WWW)struct gopher_entity_info { enum gopher_entity type; unsigned char *description; unsigned char *content_type;};/* This table provides some hard-coded associations between entity type * and MIME type. A NULL MIME type in this table indicates * that the MIME type should be deduced from the extension. * * - Lynx uses "text/plain" for GOPHER_FILE, but it can be anything. * - Lynx uses "image/gif" for GOPHER_IMAGE and GOPHER_PLUS_IMAGE, * but they really can be anything. * - GOPHER_BINARY can be, for example, a tar ball, so using * "application/octet-stream" is a bad idea. */static struct gopher_entity_info gopher_entity_info[] = { { GOPHER_BINARY, " (BINARY)", NULL }, { GOPHER_CHTML, " (CHTML)", "text/html" }, { GOPHER_CSO, " (CSO)", "text/html" }, { GOPHER_DIRECTORY, " (DIRECTORY)", "text/html" }, { GOPHER_FILE, " (FILE)", NULL /* "text/plain" */ }, { GOPHER_GIF, " (GIF IMAGE)", "image/gif" }, { GOPHER_HTML, " (HTML)", "text/html" }, { GOPHER_IMAGE, " (IMAGE)", NULL /* "image/gif" */ }, { GOPHER_INDEX, " (INDEX)", "text/html" }, { GOPHER_MACBINHEX, "(BINARY HEX)", "application/octet-stream" }, { GOPHER_MIME, " (MIME)", "application/octet-stream" }, { GOPHER_PCBINARY, " (PCBINARY)", "application/octet-stream" }, { GOPHER_PLUS_IMAGE, " (IMAGE+)", NULL /* "image/gif" */ }, { GOPHER_PLUS_MOVIE, " (MOVIE)", "video/mpeg" }, { GOPHER_PLUS_PDF, " (PDF)", "application/pdf" }, { GOPHER_PLUS_SOUND, " (SOUND+)", "audio/basic" }, { GOPHER_SOUND, " (SOUND)", "audio/basic" }, { GOPHER_TELNET, " (TELNET)", NULL }, { GOPHER_TN3270, " (TN3270)", NULL }, { GOPHER_UUENCODED, " (UUENCODED)", "application/octet-stream" }, { GOPHER_WWW, "(W3 ADDRESS)", NULL }, { GOPHER_INFO, " ", NULL }, { GOPHER_ERROR, NULL, NULL }, /* XXX: Keep GOPHER_UNKNOWN last so it is easy to access. */ { GOPHER_UNKNOWN, " ", "application/octet-stream" },};static struct gopher_entity_info *get_gopher_entity_info(enum gopher_entity type){ int entry; for (entry = 0; entry < sizeof(gopher_entity_info) - 1; entry++) if (gopher_entity_info[entry].type == type) return &gopher_entity_info[entry]; assert(gopher_entity_info[entry].type == GOPHER_UNKNOWN); return &gopher_entity_info[entry];}static unsigned char *get_gopher_entity_description(enum gopher_entity type){ struct gopher_entity_info *info = get_gopher_entity_info(type); return info ? info->description : NULL;}struct gopher_connection_info { struct gopher_entity_info *entity; int commandlen; unsigned char command[1];};/* De-escape a selector into a command. *//* The % hex escapes are converted. Otherwise, the string is copied. */static voidadd_uri_decoded(struct string *command, unsigned char *string, int length, int replace_plus){ int oldlen = command->length; assert(string); if (!length) return; if (replace_plus) { /* Remove plus signs 921006 */ if (!add_string_replace(command, string, length, '+', ' ')) return; } else if (!add_bytes_to_string(command, string, length)) { return; } assert(command->length > oldlen); /* FIXME: Decoding the whole command string should not be a problem, * and I don't remember why I didn't do that in the first place. * --jonas */ decode_uri(command->source + oldlen); /* Evil decode_uri_string() modifies the string */ command->length = strlen(command->source);}static enum connection_state init_gopher_index_cache_entry(struct connection *conn);static enum connection_stateadd_gopher_command(struct connection *conn, struct string *command, enum gopher_entity entity, unsigned char *selector, int selectorlen){ unsigned char *query; int querylen; if (!init_string(command)) return S_OUT_OF_MEM; /* Look for search string */ query = memchr(selector, '?', selectorlen); /* Check if no search is required */ if (!query || !query[1]) { /* Exclude '?' */ if (query) selectorlen -= 1; query = NULL; querylen = 0; } else { query += 1; querylen = selector + selectorlen - query; /* Exclude '?' */ selectorlen -= querylen + 1; if (querylen >= 7 && !strncasecmp(query, "search=", 7)) { query += 7; querylen -= 7; } } switch (entity) { case GOPHER_INDEX: /* No search required? */ if (!query) { done_string(command); return init_gopher_index_cache_entry(conn); } add_uri_decoded(command, selector, selectorlen, 0); add_char_to_string(command, '\t'); add_uri_decoded(command, query, querylen, 1); break; case GOPHER_CSO: /* No search required */ if (!query) { done_string(command); /* Display "cover page" */#if 0 return init_gopher_cso_cache_entry(conn);#endif return S_GOPHER_CSO_ERROR; } add_uri_decoded(command, selector, selectorlen, 0); add_to_string(command, "query "); add_uri_decoded(command, query, querylen, 1); break; default: /* Not index */ add_uri_decoded(command, selector, selectorlen, 0); } add_crlf_to_string(command); return S_CONN;}static enum connection_stateinit_gopher_connection_info(struct connection *conn){ struct gopher_connection_info *gopher; enum connection_state state; struct string command; enum gopher_entity entity = DEFAULT_GOPHER_ENTITY; unsigned char *selector = conn->uri->data; int selectorlen = conn->uri->datalen; struct gopher_entity_info *entity_info; size_t size; /* Get entity type, and selector string. */ /* Pick up gopher_entity */ if (selectorlen > 0) { entity = *selector++; selectorlen--; } /* This is probably a hack. It serves as a work around when no entity is * available in the Gopher URI. Instead of segfaulting later the content * will be served as application/octet-stream. However, it could * possible break handling Gopher URIs with entities which are really * unknown because parts of the real Gopher entity character is added to * the selector. A possible work around is to always expect a '/' * _after_ the Gopher entity. If the <entity-char> '/' combo is not * found assume that the whole URI data part is the selector. */ entity_info = get_gopher_entity_info(entity); if (entity_info->type == GOPHER_UNKNOWN) { selector--; selectorlen++; } state = add_gopher_command(conn, &command, entity, selector, selectorlen); if (state != S_CONN) return state; /* Atleast the command should contain \r\n to ask the server * wazzup! */ assert(command.length >= 2); size = sizeof(*gopher) + command.length; gopher = mem_calloc(1, size); if (!gopher) { done_string(&command); return S_OUT_OF_MEM; } gopher->entity = entity_info; gopher->commandlen = command.length; memcpy(gopher->command, command.source, command.length); done_string(&command); conn->info = gopher; return S_CONN;}static voidend_gopher_connection(struct connection *conn, enum connection_state state){ if (state == S_OK && conn->cached) {#if 0 /* Seeing dots at all files .. trying to remove the end-marker * ".\r\n" */ struct gopher_connection_info *gopher = conn->info; if (gopher && gopher->entity && gopher->entity->type != GOPHER_DIRECTORY && gopher->entity->type != GOPHER_INDEX && gopher->entity->type != GOPHER_CSO) { conn->from -= 3; }#endif truncate_entry(conn->cached, conn->from, 1); conn->cached->incomplete = 0; } abort_conn_with_state(conn, state);}/* Add a link. The title of the destination is set, as there is no way of * knowing what the title is when we arrive. * * text points to the text to be put into the file, 0 terminated. * addr points to the hypertext reference address 0 terminated. */static voidadd_gopher_link(struct string *buffer, const unsigned char *text, const unsigned char *addr){ add_format_to_string(buffer, "<a href=\"%s\">%s</a>", addr, text);}static voidadd_gopher_search_field(struct string *buffer, const unsigned char *text, const unsigned char *addr){ add_format_to_string(buffer, "<form action=\"%s\">" "<table>" "<td> </td>" "<td>%s:</td>" "<td><input maxlength=\"256\" name=\"search\" value=\"\"></td>" "<td><input type=submit value=\"Search\"></td>" "</table>" "</form>", addr, text);}static voidadd_gopher_description(struct string *buffer, enum gopher_entity entity){ unsigned char *description = get_gopher_entity_description(entity); if (!description) return; add_to_string(buffer, "<b>"); add_to_string(buffer, description); add_to_string(buffer, "</b> ");}static voidencode_selector_string(struct string *buffer, unsigned char *selector){ unsigned char *slashes; /* Rather hackishly only convert slashes if there are * two successive ones. */ while ((slashes = strstr(selector, "//"))) { *slashes = 0; encode_uri_string(buffer, selector, 0); encode_uri_string(buffer, "//", 1); *slashes = '/'; selector = slashes + 2; } encode_uri_string(buffer, selector, 0);}static voidadd_gopher_menu_line(struct string *buffer, unsigned char *line){ /* Gopher menu fields */ unsigned char *name = line; unsigned char *selector = NULL; unsigned char *host = NULL; unsigned char *port = NULL; enum gopher_entity entity = *name++; if (!entity) { add_char_to_string(buffer, '\n');
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -