From 0e03a97ba89bb9e8fccf6978250addc14799c5c1 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 7 Nov 2020 22:41:00 +0200 Subject: Added support for Gopher Needs more testing. Queries are not supported yet. --- src/gopher.c | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 src/gopher.c (limited to 'src/gopher.c') diff --git a/src/gopher.c b/src/gopher.c new file mode 100644 index 00000000..9a503442 --- /dev/null +++ b/src/gopher.c @@ -0,0 +1,206 @@ +/* Copyright 2020 Jaakko Keränen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#include "gopher.h" + +#include + +iDefineTypeConstruction(Gopher) + +iLocalDef iBool isLineTerminator_(const char *str) { + return str[0] == '\r' && str[1] == '\n'; +} + +static iBool isPreformatted_(iRangecc text) { + int numPunct = 0; + int numSpace = 0; + for (const char *ch = text.start; ch != text.end; ch++) { + if (*ch > 32 && ispunct(*ch)) { + if (++numPunct == 4) + return iTrue; + } + else { + numPunct = 0; + } + if (*ch == ' ' || *ch == '\n') { + if (++numSpace == 3) return iTrue; + } + else { + numSpace = 0; + } + } + return iFalse; +} + +static void setPre_Gopher_(iGopher *d, iBool pre) { + if (pre && !d->isPre) { + appendCStr_Block(d->output, "```\n"); + } + else if (!pre && d->isPre) { + appendCStr_Block(d->output, "```\n"); + } + d->isPre = pre; +} + +static iBool convertSource_Gopher_(iGopher *d) { + iBool converted = iFalse; + iRangecc body = range_Block(&d->source); + iRegExp *pattern = new_RegExp("(.)([^\t]*)\t([^\t]*)\t([^\t]*)\t([0-9]+)", + caseInsensitive_RegExpOption); + for (;;) { + /* Find the end of the line. */ + iRangecc line = { body.start, body.start }; + while (line.end < body.end - 1 && !isLineTerminator_(line.end)) { + line.end++; + } + if (line.end >= body.end - 1 || !isLineTerminator_(line.end)) { + /* Not a complete line. */ + break; + } + body.start = line.end + 2; + iRegExpMatch m; + init_RegExpMatch(&m); + if (matchRange_RegExp(pattern, line, &m)) { + const char lineType = *capturedRange_RegExpMatch(&m, 1).start; + const iRangecc text = capturedRange_RegExpMatch(&m, 2); + const iRangecc path = capturedRange_RegExpMatch(&m, 3); + const iRangecc domain = capturedRange_RegExpMatch(&m, 4); + const iRangecc port = capturedRange_RegExpMatch(&m, 5); + iString *buf = new_String(); + switch (lineType) { + case 'i': + case '3': { + setPre_Gopher_(d, isPreformatted_(text)); + appendData_Block(d->output, text.start, size_Range(&text)); + appendCStr_Block(d->output, "\n"); + break; + } + case '0': + case '1': + case '7': + case '4': + case '5': + case '9': + case 'g': + case 'I': + case 's': { + iBeginCollect(); + setPre_Gopher_(d, iFalse); + format_String(buf, + "=> gopher://%s:%s/%c%s %s\n", + cstr_Rangecc(domain), + cstr_Rangecc(port), + lineType, + cstr_Rangecc(path), + cstr_Rangecc(text)); + appendData_Block(d->output, constBegin_String(buf), size_String(buf)); + iEndCollect(); + break; + } + default: + break; /* Ignore unknown types. */ + } + delete_String(buf); + } + } + iRelease(pattern); + remove_Block(&d->source, 0, body.start - constBegin_Block(&d->source)); + return converted; +} + +void init_Gopher(iGopher *d) { + d->socket = NULL; + d->type = 0; + init_Block(&d->source, 0); + d->isPre = iFalse; + d->meta = NULL; + d->output = NULL; +} + +void deinit_Gopher(iGopher *d) { + deinit_Block(&d->source); + iReleasePtr(&d->socket); +} + +void open_Gopher(iGopher *d, const iString *url) { + open_Socket(d->socket); + iUrl parts; + init_Url(&parts, url); + d->type = '1'; + if (!isEmpty_Range(&parts.path)) { + if (*parts.path.start == '/') { + parts.path.start++; + } + if (parts.path.start < parts.path.end) { + d->type = *parts.path.start; + parts.path.start++; + } + } + /* MIME type determined by the URI. */ + switch (d->type) { + case '0': + setCStr_String(d->meta, "text/plain"); + break; + case '1': + setCStr_String(d->meta, "text/gemini"); + break; + case '4': + setCStr_String(d->meta, "application/mac-binhex"); + break; + case 'g': + setCStr_String(d->meta, "image/gif"); + break; + case 'I': + setCStr_String(d->meta, "image/generic"); + break; + case 's': + setCStr_String(d->meta, "audio/wave"); + break; + default: + setCStr_String(d->meta, "application/octet-stream"); + break; + } + d->isPre = iFalse; + writeData_Socket(d->socket, parts.path.start, size_Range(&parts.path)); + writeData_Socket(d->socket, "\r\n", 2); +} + +void cancel_Gopher(iGopher *d) { + if (d->socket) { + close_Socket(d->socket); + } +} + +iBool processResponse_Gopher(iGopher *d, const iBlock *data) { + iBool changed = iFalse; + if (d->type == '1') { + append_Block(&d->source, data); + if (convertSource_Gopher_(d)) { + changed = iTrue; + } + } + else { + append_Block(d->output, data); + changed = iTrue; + } + return changed; +} -- cgit v1.2.3