summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/gmrequest.c8
-rw-r--r--src/mimehooks.c119
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 {
133 iTlsRequest * req; 133 iTlsRequest * req;
134 iGopher gopher; 134 iGopher gopher;
135 iGmResponse * resp; 135 iGmResponse * resp;
136 iBool isFilterEnabled;
136 iBool isRespLocked; 137 iBool isRespLocked;
137 iBool isRespFiltered; 138 iBool isRespFiltered;
138 iAtomicInt allowUpdate; 139 iAtomicInt allowUpdate;
@@ -208,7 +209,7 @@ static int processIncomingData_GmRequest_(iGmRequest *d, const iBlock *data) {
208 resp->statusCode = code; 209 resp->statusCode = code;
209 d->state = receivingBody_GmRequestState; 210 d->state = receivingBody_GmRequestState;
210 notifyUpdate = iTrue; 211 notifyUpdate = iTrue;
211 if (willTryFilter_MimeHooks(mimeHooks_App(), &resp->meta)) { 212 if (d->isFilterEnabled && willTryFilter_MimeHooks(mimeHooks_App(), &resp->meta)) {
212 d->isRespFiltered = iTrue; 213 d->isRespFiltered = iTrue;
213 } 214 }
214 } 215 }
@@ -457,8 +458,9 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
457void init_GmRequest(iGmRequest *d, iGmCerts *certs) { 458void init_GmRequest(iGmRequest *d, iGmCerts *certs) {
458 d->mtx = new_Mutex(); 459 d->mtx = new_Mutex();
459 d->resp = new_GmResponse(); 460 d->resp = new_GmResponse();
460 d->isRespLocked = iFalse; 461 d->isFilterEnabled = iTrue;
461 d->isRespFiltered = iFalse; 462 d->isRespLocked = iFalse;
463 d->isRespFiltered = iFalse;
462 set_Atomic(&d->allowUpdate, iTrue); 464 set_Atomic(&d->allowUpdate, iTrue);
463 init_String(&d->url); 465 init_String(&d->url);
464 init_Gopher(&d->gopher); 466 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 @@
6#include <the_Foundation/path.h> 6#include <the_Foundation/path.h>
7#include <the_Foundation/process.h> 7#include <the_Foundation/process.h>
8#include <the_Foundation/stringlist.h> 8#include <the_Foundation/stringlist.h>
9#include <the_Foundation/xml.h>
9 10
10iDefineTypeConstruction(FilterHook) 11iDefineTypeConstruction(FilterHook)
11 12
@@ -69,6 +70,109 @@ iBlock *run_FilterHook_(const iFilterHook *d, const iString *mime, const iBlock
69 70
70/*----------------------------------------------------------------------------------------------*/ 71/*----------------------------------------------------------------------------------------------*/
71 72
73static iRegExp *xmlMimePattern_(void) {
74 static iRegExp *xmlMime_;
75 if (!xmlMime_) {
76 xmlMime_ = new_RegExp("(application|text)/(atom\\+)?xml", caseInsensitive_RegExpOption);
77 }
78 return xmlMime_;
79}
80
81static iBlock *translateAtomXmlToGeminiFeed_(const iString *mime, const iBlock *source,
82 const iString *requestUrl) {
83 iRegExpMatch m;
84 init_RegExpMatch(&m);
85 if (!matchString_RegExp(xmlMimePattern_(), mime, &m)) {
86 return NULL;
87 }
88 iBlock * output = NULL;
89 iXmlDocument *doc = new_XmlDocument();
90 iString src;
91 initBlock_String(&src, source); /* assume it's UTF-8 */
92 if (!parse_XmlDocument(doc, &src)) {
93 goto finished;
94 }
95 const iXmlElement *feed = &doc->root;
96 if (!equal_Rangecc(feed->name, "feed")) {
97 goto finished;
98 }
99 if (!equal_Rangecc(attribute_XmlElement(feed, "xmlns"), "http://www.w3.org/2005/Atom")) {
100 goto finished;
101 }
102 iString *title = collect_String(decodedContent_XmlElement(child_XmlElement(feed, "title")));
103 if (isEmpty_String(title)) {
104 goto finished;
105 }
106 iString *subtitle = collect_String(decodedContent_XmlElement(child_XmlElement(feed, "subtitle")));
107 iString out;
108 init_String(&out);
109 format_String(&out,
110 "20 text/gemini\r\n"
111 "# %s\n\n", cstr_String(title));
112 if (!isEmpty_String(subtitle)) {
113 appendFormat_String(&out, "## %s\n\n", cstr_String(subtitle));
114 }
115 iRegExp *datePattern =
116 iClob(new_RegExp("^([0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9])T.*", caseSensitive_RegExpOption));
117 iBeginCollect();
118 iConstForEach(PtrArray, i, &feed->children) {
119 iEndCollect();
120 iBeginCollect();
121 const iXmlElement *entry = i.ptr;
122 if (!equal_Rangecc(entry->name, "entry")) {
123 continue;
124 }
125 title = collect_String(decodedContent_XmlElement(child_XmlElement(entry, "title")));
126 if (isEmpty_String(title)) {
127 continue;
128 }
129 const iString *updated =
130 collect_String(decodedContent_XmlElement(child_XmlElement(entry, "updated")));
131 iRegExpMatch m;
132 init_RegExpMatch(&m);
133 if (!matchString_RegExp(datePattern, updated, &m)) {
134 continue;
135 }
136 iRangecc url = iNullRange;
137 iConstForEach(PtrArray, j, &entry->children) {
138 const iXmlElement *link = j.ptr;
139 if (!equal_Rangecc(link->name, "link")) {
140 continue;
141 }
142 const iRangecc href = attribute_XmlElement(link, "href");
143 const iRangecc rel = attribute_XmlElement(link, "rel");
144 const iRangecc type = attribute_XmlElement(link, "type");
145 if (startsWithCase_Rangecc(href, "gemini:")) {
146 url = href;
147 /* We're happy with the first gemini URL. */
148 /* TODO: Are we? */
149 break;
150 }
151 url = href;
152 iUnused(rel, type);
153 }
154 if (isEmpty_Range(&url)) {
155 continue;
156 }
157 appendFormat_String(&out, "=> %s %s - %s\n",
158 cstr_Rangecc(url),
159 cstr_Rangecc(capturedRange_RegExpMatch(&m, 1)),
160 cstr_String(title));
161 }
162 iEndCollect();
163 appendCStr_String(&out,
164 "This Atom XML document has been automatically translated to a Gemini feed "
165 "to allow subscribing to it.\n");
166 output = copy_Block(utf8_String(&out));
167 deinit_String(&out);
168finished:
169 delete_XmlDocument(doc);
170 deinit_String(&src);
171 return output;
172}
173
174/*----------------------------------------------------------------------------------------------*/
175
72struct Impl_MimeHooks { 176struct Impl_MimeHooks {
73 iPtrArray filters; 177 iPtrArray filters;
74}; 178};
@@ -87,7 +191,7 @@ void deinit_MimeHooks(iMimeHooks *d) {
87} 191}
88 192
89iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) { 193iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) {
90 /* TODO: Combine this function with tryFilter_MimeHooks? */ 194 /* TODO: Combine this function with tryFilter_MimeHooks! */
91 iRegExpMatch m; 195 iRegExpMatch m;
92 iConstForEach(PtrArray, i, &d->filters) { 196 iConstForEach(PtrArray, i, &d->filters) {
93 const iFilterHook *xc = i.ptr; 197 const iFilterHook *xc = i.ptr;
@@ -96,6 +200,11 @@ iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) {
96 return iTrue; 200 return iTrue;
97 } 201 }
98 } 202 }
203 /* Built-in filters. */
204 init_RegExpMatch(&m);
205 if (matchString_RegExp(xmlMimePattern_(), mime, &m)) {
206 return iTrue;
207 }
99 return iFalse; 208 return iFalse;
100} 209}
101 210
@@ -112,6 +221,14 @@ iBlock *tryFilter_MimeHooks(const iMimeHooks *d, const iString *mime, const iBlo
112 } 221 }
113 } 222 }
114 } 223 }
224 /* Built-in filters. */
225 init_RegExpMatch(&m);
226 if (matchString_RegExp(xmlMimePattern_(), mime, &m)) {
227 iBlock *result = translateAtomXmlToGeminiFeed_(mime, body, requestUrl);
228 if (result) {
229 return result;
230 }
231 }
115 return NULL; 232 return NULL;
116} 233}
117 234