From c6a3afccfc5ef679a82275c5f045336c2d48f643 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 17 Feb 2021 16:57:49 +0200 Subject: Added a built-in Atom-to-Gemini feed translator If no user-configured MIME hooks handle an Atom XML document, it will be translated using a built-in filter hook. Only Atom is supported. IssueID #78 --- src/gmrequest.c | 8 ++-- src/mimehooks.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/src/gmrequest.c b/src/gmrequest.c index 8626403f..9cdd627e 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c @@ -133,6 +133,7 @@ struct Impl_GmRequest { iTlsRequest * req; iGopher gopher; iGmResponse * resp; + iBool isFilterEnabled; iBool isRespLocked; iBool isRespFiltered; iAtomicInt allowUpdate; @@ -208,7 +209,7 @@ static int processIncomingData_GmRequest_(iGmRequest *d, const iBlock *data) { resp->statusCode = code; d->state = receivingBody_GmRequestState; notifyUpdate = iTrue; - if (willTryFilter_MimeHooks(mimeHooks_App(), &resp->meta)) { + if (d->isFilterEnabled && willTryFilter_MimeHooks(mimeHooks_App(), &resp->meta)) { d->isRespFiltered = iTrue; } } @@ -457,8 +458,9 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, void init_GmRequest(iGmRequest *d, iGmCerts *certs) { d->mtx = new_Mutex(); d->resp = new_GmResponse(); - d->isRespLocked = iFalse; - d->isRespFiltered = iFalse; + d->isFilterEnabled = iTrue; + d->isRespLocked = iFalse; + d->isRespFiltered = iFalse; set_Atomic(&d->allowUpdate, iTrue); init_String(&d->url); init_Gopher(&d->gopher); diff --git a/src/mimehooks.c b/src/mimehooks.c index f4ec6bf4..b2110cfb 100644 --- a/src/mimehooks.c +++ b/src/mimehooks.c @@ -6,6 +6,7 @@ #include #include #include +#include iDefineTypeConstruction(FilterHook) @@ -69,6 +70,109 @@ iBlock *run_FilterHook_(const iFilterHook *d, const iString *mime, const iBlock /*----------------------------------------------------------------------------------------------*/ +static iRegExp *xmlMimePattern_(void) { + static iRegExp *xmlMime_; + if (!xmlMime_) { + xmlMime_ = new_RegExp("(application|text)/(atom\\+)?xml", caseInsensitive_RegExpOption); + } + return xmlMime_; +} + +static iBlock *translateAtomXmlToGeminiFeed_(const iString *mime, const iBlock *source, + const iString *requestUrl) { + iRegExpMatch m; + init_RegExpMatch(&m); + if (!matchString_RegExp(xmlMimePattern_(), mime, &m)) { + return NULL; + } + iBlock * output = NULL; + iXmlDocument *doc = new_XmlDocument(); + iString src; + initBlock_String(&src, source); /* assume it's UTF-8 */ + if (!parse_XmlDocument(doc, &src)) { + goto finished; + } + const iXmlElement *feed = &doc->root; + if (!equal_Rangecc(feed->name, "feed")) { + goto finished; + } + if (!equal_Rangecc(attribute_XmlElement(feed, "xmlns"), "http://www.w3.org/2005/Atom")) { + goto finished; + } + iString *title = collect_String(decodedContent_XmlElement(child_XmlElement(feed, "title"))); + if (isEmpty_String(title)) { + goto finished; + } + iString *subtitle = collect_String(decodedContent_XmlElement(child_XmlElement(feed, "subtitle"))); + iString out; + init_String(&out); + format_String(&out, + "20 text/gemini\r\n" + "# %s\n\n", cstr_String(title)); + if (!isEmpty_String(subtitle)) { + appendFormat_String(&out, "## %s\n\n", cstr_String(subtitle)); + } + iRegExp *datePattern = + iClob(new_RegExp("^([0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9])T.*", caseSensitive_RegExpOption)); + iBeginCollect(); + iConstForEach(PtrArray, i, &feed->children) { + iEndCollect(); + iBeginCollect(); + const iXmlElement *entry = i.ptr; + if (!equal_Rangecc(entry->name, "entry")) { + continue; + } + title = collect_String(decodedContent_XmlElement(child_XmlElement(entry, "title"))); + if (isEmpty_String(title)) { + continue; + } + const iString *updated = + collect_String(decodedContent_XmlElement(child_XmlElement(entry, "updated"))); + iRegExpMatch m; + init_RegExpMatch(&m); + if (!matchString_RegExp(datePattern, updated, &m)) { + continue; + } + iRangecc url = iNullRange; + iConstForEach(PtrArray, j, &entry->children) { + const iXmlElement *link = j.ptr; + if (!equal_Rangecc(link->name, "link")) { + continue; + } + const iRangecc href = attribute_XmlElement(link, "href"); + const iRangecc rel = attribute_XmlElement(link, "rel"); + const iRangecc type = attribute_XmlElement(link, "type"); + if (startsWithCase_Rangecc(href, "gemini:")) { + url = href; + /* We're happy with the first gemini URL. */ + /* TODO: Are we? */ + break; + } + url = href; + iUnused(rel, type); + } + if (isEmpty_Range(&url)) { + continue; + } + appendFormat_String(&out, "=> %s %s - %s\n", + cstr_Rangecc(url), + cstr_Rangecc(capturedRange_RegExpMatch(&m, 1)), + cstr_String(title)); + } + iEndCollect(); + appendCStr_String(&out, + "This Atom XML document has been automatically translated to a Gemini feed " + "to allow subscribing to it.\n"); + output = copy_Block(utf8_String(&out)); + deinit_String(&out); +finished: + delete_XmlDocument(doc); + deinit_String(&src); + return output; +} + +/*----------------------------------------------------------------------------------------------*/ + struct Impl_MimeHooks { iPtrArray filters; }; @@ -87,7 +191,7 @@ void deinit_MimeHooks(iMimeHooks *d) { } iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) { - /* TODO: Combine this function with tryFilter_MimeHooks? */ + /* TODO: Combine this function with tryFilter_MimeHooks! */ iRegExpMatch m; iConstForEach(PtrArray, i, &d->filters) { const iFilterHook *xc = i.ptr; @@ -96,6 +200,11 @@ iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) { return iTrue; } } + /* Built-in filters. */ + init_RegExpMatch(&m); + if (matchString_RegExp(xmlMimePattern_(), mime, &m)) { + return iTrue; + } return iFalse; } @@ -112,6 +221,14 @@ iBlock *tryFilter_MimeHooks(const iMimeHooks *d, const iString *mime, const iBlo } } } + /* Built-in filters. */ + init_RegExpMatch(&m); + if (matchString_RegExp(xmlMimePattern_(), mime, &m)) { + iBlock *result = translateAtomXmlToGeminiFeed_(mime, body, requestUrl); + if (result) { + return result; + } + } return NULL; } -- cgit v1.2.3