diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-02 07:26:33 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-02 07:26:33 +0200 |
commit | cface45b7cfd084f65ead1d5bfa6ecdc639d7017 (patch) | |
tree | 0c2b7f900ee3f116474f9789173b60423b98cf4f | |
parent | 5b147a3976fcf007a75e9b16cc0cea96011f2e9b (diff) |
Added MimeHooks
This is a very powerful mechanism: translate the contents of any request to something else when the original MIME type matches a configured regexp.
The external hook command may still elect not to process the request.
-rw-r--r-- | CMakeLists.txt | 2 | ||||
m--------- | lib/the_Foundation | 0 | ||||
-rw-r--r-- | src/app.c | 10 | ||||
-rw-r--r-- | src/app.h | 2 | ||||
-rw-r--r-- | src/gmrequest.c | 55 | ||||
-rw-r--r-- | src/mimehooks.c | 139 | ||||
-rw-r--r-- | src/mimehooks.h | 31 |
7 files changed, 224 insertions, 15 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d21f8fe..0c200e21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -113,6 +113,8 @@ set (SOURCES | |||
113 | src/lookup.h | 113 | src/lookup.h |
114 | src/media.c | 114 | src/media.c |
115 | src/media.h | 115 | src/media.h |
116 | src/mimehooks.c | ||
117 | src/mimehooks.h | ||
116 | src/prefs.c | 118 | src/prefs.c |
117 | src/prefs.h | 119 | src/prefs.h |
118 | src/stb_image.h | 120 | src/stb_image.h |
diff --git a/lib/the_Foundation b/lib/the_Foundation | |||
Subproject 2e1cba43a1e5d67e69192548e91c296b9ce788c | Subproject 9fa87875c27164b4844c994202180756d1e928f | ||
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include "defs.h" | 25 | #include "defs.h" |
26 | #include "embedded.h" | 26 | #include "embedded.h" |
27 | #include "feeds.h" | 27 | #include "feeds.h" |
28 | #include "mimehooks.h" | ||
28 | #include "gmcerts.h" | 29 | #include "gmcerts.h" |
29 | #include "gmdocument.h" | 30 | #include "gmdocument.h" |
30 | #include "gmutil.h" | 31 | #include "gmutil.h" |
@@ -91,6 +92,7 @@ static const char *downloadDir_App_ = "~/Downloads"; | |||
91 | struct Impl_App { | 92 | struct Impl_App { |
92 | iCommandLine args; | 93 | iCommandLine args; |
93 | iString * execPath; | 94 | iString * execPath; |
95 | iMimeHooks * mimehooks; | ||
94 | iGmCerts * certs; | 96 | iGmCerts * certs; |
95 | iVisited * visited; | 97 | iVisited * visited; |
96 | iBookmarks * bookmarks; | 98 | iBookmarks * bookmarks; |
@@ -342,6 +344,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
342 | d->running = iFalse; | 344 | d->running = iFalse; |
343 | d->window = NULL; | 345 | d->window = NULL; |
344 | set_Atomic(&d->pendingRefresh, iFalse); | 346 | set_Atomic(&d->pendingRefresh, iFalse); |
347 | d->mimehooks = new_MimeHooks(); | ||
345 | d->certs = new_GmCerts(dataDir_App_); | 348 | d->certs = new_GmCerts(dataDir_App_); |
346 | d->visited = new_Visited(); | 349 | d->visited = new_Visited(); |
347 | d->bookmarks = new_Bookmarks(); | 350 | d->bookmarks = new_Bookmarks(); |
@@ -355,6 +358,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
355 | load_Keys(dataDir_App_); | 358 | load_Keys(dataDir_App_); |
356 | load_Visited(d->visited, dataDir_App_); | 359 | load_Visited(d->visited, dataDir_App_); |
357 | load_Bookmarks(d->bookmarks, dataDir_App_); | 360 | load_Bookmarks(d->bookmarks, dataDir_App_); |
361 | load_MimeHooks(d->mimehooks, dataDir_App_); | ||
358 | if (isFirstRun) { | 362 | if (isFirstRun) { |
359 | /* Create the default bookmarks for a quick start. */ | 363 | /* Create the default bookmarks for a quick start. */ |
360 | add_Bookmarks(d->bookmarks, | 364 | add_Bookmarks(d->bookmarks, |
@@ -433,6 +437,8 @@ static void deinit_App(iApp *d) { | |||
433 | save_Visited(d->visited, dataDir_App_); | 437 | save_Visited(d->visited, dataDir_App_); |
434 | delete_Visited(d->visited); | 438 | delete_Visited(d->visited); |
435 | delete_GmCerts(d->certs); | 439 | delete_GmCerts(d->certs); |
440 | save_MimeHooks(d->mimehooks); | ||
441 | delete_MimeHooks(d->mimehooks); | ||
436 | deinit_SortedArray(&d->tickers); | 442 | deinit_SortedArray(&d->tickers); |
437 | delete_Window(d->window); | 443 | delete_Window(d->window); |
438 | d->window = NULL; | 444 | d->window = NULL; |
@@ -709,6 +715,10 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) { | |||
709 | remove_SortedArray(&d->tickers, &(iTicker){ context, ticker }); | 715 | remove_SortedArray(&d->tickers, &(iTicker){ context, ticker }); |
710 | } | 716 | } |
711 | 717 | ||
718 | iMimeHooks *mimeHooks_App(void) { | ||
719 | return app_.mimehooks; | ||
720 | } | ||
721 | |||
712 | iGmCerts *certs_App(void) { | 722 | iGmCerts *certs_App(void) { |
713 | return app_.certs; | 723 | return app_.certs; |
714 | } | 724 | } |
@@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
34 | iDeclareType(Bookmarks) | 34 | iDeclareType(Bookmarks) |
35 | iDeclareType(DocumentWidget) | 35 | iDeclareType(DocumentWidget) |
36 | iDeclareType(GmCerts) | 36 | iDeclareType(GmCerts) |
37 | iDeclareType(MimeHooks) | ||
37 | iDeclareType(Visited) | 38 | iDeclareType(Visited) |
38 | iDeclareType(Window) | 39 | iDeclareType(Window) |
39 | 40 | ||
@@ -65,6 +66,7 @@ enum iColorTheme colorTheme_App (void); | |||
65 | const iString * schemeProxy_App (iRangecc scheme); | 66 | const iString * schemeProxy_App (iRangecc scheme); |
66 | iBool willUseProxy_App (const iRangecc scheme); | 67 | iBool willUseProxy_App (const iRangecc scheme); |
67 | 68 | ||
69 | iMimeHooks * mimeHooks_App (void); | ||
68 | iGmCerts * certs_App (void); | 70 | iGmCerts * certs_App (void); |
69 | iVisited * visited_App (void); | 71 | iVisited * visited_App (void); |
70 | iBookmarks * bookmarks_App (void); | 72 | iBookmarks * bookmarks_App (void); |
diff --git a/src/gmrequest.c b/src/gmrequest.c index b51d3b1f..572e6a5c 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include "gmcerts.h" | 25 | #include "gmcerts.h" |
26 | #include "gopher.h" | 26 | #include "gopher.h" |
27 | #include "app.h" /* dataDir_App() */ | 27 | #include "app.h" /* dataDir_App() */ |
28 | #include "mimehooks.h" | ||
28 | #include "feeds.h" | 29 | #include "feeds.h" |
29 | #include "ui/text.h" | 30 | #include "ui/text.h" |
30 | #include "embedded.h" | 31 | #include "embedded.h" |
@@ -131,7 +132,8 @@ struct Impl_GmRequest { | |||
131 | iTlsRequest * req; | 132 | iTlsRequest * req; |
132 | iGopher gopher; | 133 | iGopher gopher; |
133 | iGmResponse * resp; | 134 | iGmResponse * resp; |
134 | iBool respLocked; | 135 | iBool isRespLocked; |
136 | iBool isRespFiltered; | ||
135 | iAtomicInt allowUpdate; | 137 | iAtomicInt allowUpdate; |
136 | iAudience * updated; | 138 | iAudience * updated; |
137 | iAudience * finished; | 139 | iAudience * finished; |
@@ -164,13 +166,10 @@ static void checkServerCertificate_GmRequest_(iGmRequest *d) { | |||
164 | } | 166 | } |
165 | } | 167 | } |
166 | 168 | ||
167 | static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { | 169 | static int processIncomingData_GmRequest_(iGmRequest *d, const iBlock *data) { |
168 | iBool notifyUpdate = iFalse; | 170 | iBool notifyUpdate = iFalse; |
169 | iBool notifyDone = iFalse; | 171 | iBool notifyDone = iFalse; |
170 | lock_Mutex(d->mtx); | 172 | iGmResponse *resp = d->resp; |
171 | iGmResponse *resp =d->resp; | ||
172 | iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ | ||
173 | iBlock *data = readAll_TlsRequest(req); | ||
174 | if (d->state == receivingHeader_GmRequestState) { | 173 | if (d->state == receivingHeader_GmRequestState) { |
175 | appendCStrN_String(&resp->meta, constData_Block(data), size_Block(data)); | 174 | appendCStrN_String(&resp->meta, constData_Block(data), size_Block(data)); |
176 | /* Check if the header line is complete. */ | 175 | /* Check if the header line is complete. */ |
@@ -208,6 +207,9 @@ static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { | |||
208 | resp->statusCode = code; | 207 | resp->statusCode = code; |
209 | d->state = receivingBody_GmRequestState; | 208 | d->state = receivingBody_GmRequestState; |
210 | notifyUpdate = iTrue; | 209 | notifyUpdate = iTrue; |
210 | if (willTryFilter_MimeHooks(mimeHooks_App(), &resp->meta)) { | ||
211 | d->isRespFiltered = iTrue; | ||
212 | } | ||
211 | } | 213 | } |
212 | checkServerCertificate_GmRequest_(d); | 214 | checkServerCertificate_GmRequest_(d); |
213 | iRelease(metaPattern); | 215 | iRelease(metaPattern); |
@@ -217,10 +219,21 @@ static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { | |||
217 | append_Block(&resp->body, data); | 219 | append_Block(&resp->body, data); |
218 | notifyUpdate = iTrue; | 220 | notifyUpdate = iTrue; |
219 | } | 221 | } |
222 | return (notifyUpdate ? 1 : 0) | (notifyDone ? 2 : 0); | ||
223 | } | ||
224 | |||
225 | static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { | ||
226 | lock_Mutex(d->mtx); | ||
227 | iGmResponse *resp = d->resp; | ||
228 | iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ | ||
229 | iBlock * data = readAll_TlsRequest(req); | ||
230 | const int ubits = processIncomingData_GmRequest_(d, data); | ||
231 | iBool notifyUpdate = (ubits & 1) != 0; | ||
232 | iBool notifyDone = (ubits & 2) != 0; | ||
220 | initCurrent_Time(&resp->when); | 233 | initCurrent_Time(&resp->when); |
221 | delete_Block(data); | 234 | delete_Block(data); |
222 | unlock_Mutex(d->mtx); | 235 | unlock_Mutex(d->mtx); |
223 | if (notifyUpdate) { | 236 | if (notifyUpdate && !d->isRespFiltered) { |
224 | const iBool allowed = exchange_Atomic(&d->allowUpdate, iFalse); | 237 | const iBool allowed = exchange_Atomic(&d->allowUpdate, iFalse); |
225 | if (allowed) { | 238 | if (allowed) { |
226 | iNotifyAudience(d, updated, GmRequestUpdated); | 239 | iNotifyAudience(d, updated, GmRequestUpdated); |
@@ -247,7 +260,18 @@ static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) { | |||
247 | set_String(&d->resp->meta, errorMessage_TlsRequest(req)); | 260 | set_String(&d->resp->meta, errorMessage_TlsRequest(req)); |
248 | } | 261 | } |
249 | checkServerCertificate_GmRequest_(d); | 262 | checkServerCertificate_GmRequest_(d); |
250 | unlock_Mutex(d->mtx); | 263 | /* Check for mimehooks. */ |
264 | if (d->isRespFiltered && d->state == finished_GmRequestState) { | ||
265 | iBlock *xbody = tryFilter_MimeHooks(mimeHooks_App(), &d->resp->meta, &d->resp->body); | ||
266 | if (xbody) { | ||
267 | clear_String(&d->resp->meta); | ||
268 | clear_Block(&d->resp->body); | ||
269 | d->state = receivingHeader_GmRequestState; | ||
270 | processIncomingData_GmRequest_(d, xbody); | ||
271 | d->state = finished_GmRequestState; | ||
272 | } | ||
273 | } | ||
274 | unlock_Mutex(d->mtx); | ||
251 | iNotifyAudience(d, finished, GmRequestFinished); | 275 | iNotifyAudience(d, finished, GmRequestFinished); |
252 | } | 276 | } |
253 | 277 | ||
@@ -425,7 +449,8 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, | |||
425 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { | 449 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { |
426 | d->mtx = new_Mutex(); | 450 | d->mtx = new_Mutex(); |
427 | d->resp = new_GmResponse(); | 451 | d->resp = new_GmResponse(); |
428 | d->respLocked = iFalse; | 452 | d->isRespLocked = iFalse; |
453 | d->isRespFiltered = iFalse; | ||
429 | set_Atomic(&d->allowUpdate, iTrue); | 454 | set_Atomic(&d->allowUpdate, iTrue); |
430 | init_String(&d->url); | 455 | init_String(&d->url); |
431 | init_Gopher(&d->gopher); | 456 | init_Gopher(&d->gopher); |
@@ -623,16 +648,16 @@ void cancel_GmRequest(iGmRequest *d) { | |||
623 | } | 648 | } |
624 | 649 | ||
625 | iGmResponse *lockResponse_GmRequest(iGmRequest *d) { | 650 | iGmResponse *lockResponse_GmRequest(iGmRequest *d) { |
626 | iAssert(!d->respLocked); | 651 | iAssert(!d->isRespLocked); |
627 | lock_Mutex(d->mtx); | 652 | lock_Mutex(d->mtx); |
628 | d->respLocked = iTrue; | 653 | d->isRespLocked = iTrue; |
629 | return d->resp; | 654 | return d->resp; |
630 | } | 655 | } |
631 | 656 | ||
632 | void unlockResponse_GmRequest(iGmRequest *d) { | 657 | void unlockResponse_GmRequest(iGmRequest *d) { |
633 | if (d) { | 658 | if (d) { |
634 | iAssert(d->respLocked); | 659 | iAssert(d->isRespLocked); |
635 | d->respLocked = iFalse; | 660 | d->isRespLocked = iFalse; |
636 | set_Atomic(&d->allowUpdate, iTrue); | 661 | set_Atomic(&d->allowUpdate, iTrue); |
637 | unlock_Mutex(d->mtx); | 662 | unlock_Mutex(d->mtx); |
638 | } | 663 | } |
diff --git a/src/mimehooks.c b/src/mimehooks.c new file mode 100644 index 00000000..3d999aba --- /dev/null +++ b/src/mimehooks.c | |||
@@ -0,0 +1,139 @@ | |||
1 | #include "mimehooks.h" | ||
2 | |||
3 | #include <the_Foundation/file.h> | ||
4 | #include <the_Foundation/path.h> | ||
5 | #include <the_Foundation/process.h> | ||
6 | #include <the_Foundation/stringlist.h> | ||
7 | |||
8 | iDefineTypeConstruction(FilterHook) | ||
9 | |||
10 | void init_FilterHook(iFilterHook *d) { | ||
11 | init_String(&d->label); | ||
12 | init_String(&d->mimePattern); | ||
13 | init_String(&d->command); | ||
14 | d->mimeRegex = NULL; | ||
15 | } | ||
16 | |||
17 | void deinit_FilterHook(iFilterHook *d) { | ||
18 | iRelease(d->mimeRegex); | ||
19 | deinit_String(&d->command); | ||
20 | deinit_String(&d->mimePattern); | ||
21 | deinit_String(&d->label); | ||
22 | } | ||
23 | |||
24 | void setMimePattern_FilterHook(iFilterHook *d, const iString *pattern) { | ||
25 | iReleasePtr(&d->mimeRegex); | ||
26 | d->mimeRegex = new_RegExp(cstr_String(pattern), caseInsensitive_RegExpOption); | ||
27 | } | ||
28 | |||
29 | void setCommand_FilterHook(iFilterHook *d, const iString *command) { | ||
30 | set_String(&d->command, command); | ||
31 | } | ||
32 | |||
33 | iBlock *run_FilterHook_(const iFilterHook *d, const iString *mime, const iBlock *body) { | ||
34 | iProcess * proc = new_Process(); | ||
35 | iStringList *args = new_StringList(); | ||
36 | iRangecc seg = iNullRange; | ||
37 | while (nextSplit_Rangecc(range_String(&d->command), ";", &seg)) { | ||
38 | pushBackRange_StringList(args, seg); | ||
39 | } | ||
40 | seg = iNullRange; | ||
41 | while (nextSplit_Rangecc(range_String(mime), ";", &seg)) { | ||
42 | pushBackRange_StringList(args, seg); | ||
43 | } | ||
44 | setArguments_Process(proc, args); | ||
45 | iRelease(args); | ||
46 | start_Process(proc); | ||
47 | writeInput_Process(proc, body); | ||
48 | waitForFinished_Process(proc); | ||
49 | iBlock *output = readOutput_Process(proc); | ||
50 | if (!startsWith_Rangecc(range_Block(output), "20")) { | ||
51 | /* Didn't produce valid output. */ | ||
52 | delete_Block(output); | ||
53 | output = NULL; | ||
54 | } | ||
55 | iRelease(proc); | ||
56 | return output; | ||
57 | } | ||
58 | |||
59 | /*----------------------------------------------------------------------------------------------*/ | ||
60 | |||
61 | struct Impl_MimeHooks { | ||
62 | iPtrArray filters; | ||
63 | }; | ||
64 | |||
65 | iDefineTypeConstruction(MimeHooks) | ||
66 | |||
67 | void init_MimeHooks(iMimeHooks *d) { | ||
68 | init_PtrArray(&d->filters); | ||
69 | } | ||
70 | |||
71 | void deinit_MimeHooks(iMimeHooks *d) { | ||
72 | iForEach(PtrArray, i, &d->filters) { | ||
73 | delete_FilterHook(i.ptr); | ||
74 | } | ||
75 | deinit_PtrArray(&d->filters); | ||
76 | } | ||
77 | |||
78 | iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) { | ||
79 | /* TODO: Combine this function with tryFilter_MimeHooks? */ | ||
80 | iRegExpMatch m; | ||
81 | iConstForEach(PtrArray, i, &d->filters) { | ||
82 | const iFilterHook *xc = i.ptr; | ||
83 | init_RegExpMatch(&m); | ||
84 | if (matchString_RegExp(xc->mimeRegex, mime, &m)) { | ||
85 | return iTrue; | ||
86 | } | ||
87 | } | ||
88 | return iFalse; | ||
89 | } | ||
90 | |||
91 | iBlock *tryFilter_MimeHooks(const iMimeHooks *d, const iString *mime, const iBlock *body) { | ||
92 | iRegExpMatch m; | ||
93 | iConstForEach(PtrArray, i, &d->filters) { | ||
94 | const iFilterHook *xc = i.ptr; | ||
95 | init_RegExpMatch(&m); | ||
96 | if (matchString_RegExp(xc->mimeRegex, mime, &m)) { | ||
97 | iBlock *result = run_FilterHook_(xc, mime, body); | ||
98 | if (result) { | ||
99 | return result; | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | return NULL; | ||
104 | } | ||
105 | |||
106 | static const char *mimeHooksFilename_MimeHooks_ = "mimehooks.txt"; | ||
107 | |||
108 | void load_MimeHooks(iMimeHooks *d, const char *saveDir) { | ||
109 | iFile *f = newCStr_File(concatPath_CStr(saveDir, mimeHooksFilename_MimeHooks_)); | ||
110 | if (open_File(f, read_FileMode | text_FileMode)) { | ||
111 | iBlock * src = readAll_File(f); | ||
112 | iRangecc srcLine = iNullRange; | ||
113 | int pos = 0; | ||
114 | iRangecc lines[3]; | ||
115 | iZap(lines); | ||
116 | while (nextSplit_Rangecc(range_Block(src), "\n", &srcLine)) { | ||
117 | iRangecc line = srcLine; | ||
118 | trim_Rangecc(&line); | ||
119 | if (isEmpty_Range(&line)) { | ||
120 | continue; | ||
121 | } | ||
122 | lines[pos++] = line; | ||
123 | if (pos == 3) { | ||
124 | iFilterHook *hook = new_FilterHook(); | ||
125 | setRange_String(&hook->label, lines[0]); | ||
126 | setMimePattern_FilterHook(hook, collect_String(newRange_String(lines[1]))); | ||
127 | setCommand_FilterHook(hook, collect_String(newRange_String(lines[2]))); | ||
128 | pushBack_PtrArray(&d->filters, hook); | ||
129 | } | ||
130 | } | ||
131 | delete_Block(src); | ||
132 | } | ||
133 | iRelease(f); | ||
134 | } | ||
135 | |||
136 | void save_MimeHooks(const iMimeHooks *d) { | ||
137 | iUnused(d); | ||
138 | } | ||
139 | |||
diff --git a/src/mimehooks.h b/src/mimehooks.h new file mode 100644 index 00000000..c78a3c86 --- /dev/null +++ b/src/mimehooks.h | |||
@@ -0,0 +1,31 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include <the_Foundation/audience.h> | ||
4 | #include <the_Foundation/ptrarray.h> | ||
5 | #include <the_Foundation/regexp.h> | ||
6 | #include <the_Foundation/string.h> | ||
7 | |||
8 | iDeclareType(FilterHook) | ||
9 | iDeclareTypeConstruction(FilterHook) | ||
10 | |||
11 | struct Impl_FilterHook { | ||
12 | iString label; | ||
13 | iString mimePattern; | ||
14 | iRegExp *mimeRegex; | ||
15 | iString command; | ||
16 | }; | ||
17 | |||
18 | void setMimePattern_FilterHook (iFilterHook *, const iString *pattern); | ||
19 | void setCommand_FilterHook (iFilterHook *, const iString *command); | ||
20 | |||
21 | /*----------------------------------------------------------------------------------------------*/ | ||
22 | |||
23 | iDeclareType(MimeHooks) | ||
24 | iDeclareTypeConstruction(MimeHooks) | ||
25 | |||
26 | iBool willTryFilter_MimeHooks (const iMimeHooks *, const iString *mime); | ||
27 | iBlock * tryFilter_MimeHooks (const iMimeHooks *, const iString *mime, | ||
28 | const iBlock *body); | ||
29 | |||
30 | void load_MimeHooks (iMimeHooks *, const char *saveDir); | ||
31 | void save_MimeHooks (const iMimeHooks *); | ||