diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-03 15:28:16 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-03 15:28:16 +0300 |
commit | 856fbf1bf25038997b3dd61fc8707370f085e6da (patch) | |
tree | 90c6526fc839184d24bdbd770a956c8568b2956b /src | |
parent | 922ad3220909b7314ca629949b44d7d3aa0336b2 (diff) |
Server certificate TOFU
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 15 | ||||
-rw-r--r-- | src/gmcerts.c | 121 | ||||
-rw-r--r-- | src/gmcerts.h | 2 | ||||
-rw-r--r-- | src/gmdocument.h | 2 | ||||
-rw-r--r-- | src/gmrequest.c | 47 | ||||
-rw-r--r-- | src/gmrequest.h | 17 | ||||
-rw-r--r-- | src/gmutil.c | 7 | ||||
-rw-r--r-- | src/gmutil.h | 1 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 35 |
9 files changed, 218 insertions, 29 deletions
@@ -41,7 +41,8 @@ static const char *dataDir_App_ = "~/Library/Application Support/fi.skyjake.Lagr | |||
41 | static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange"; | 41 | static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange"; |
42 | #endif | 42 | #endif |
43 | #if defined (iPlatformLinux) | 43 | #if defined (iPlatformLinux) |
44 | #define EMB_BIN "../../share/lagrange/resources.bin" | 44 | #define EMB_BIN "../../share/lagrange/resources.bin" |
45 | #define EMB_BIN2 "../resources.bin" /* try from build dir as well */ | ||
45 | static const char *dataDir_App_ = "~/.config/lagrange"; | 46 | static const char *dataDir_App_ = "~/.config/lagrange"; |
46 | #endif | 47 | #endif |
47 | static const char *prefsFileName_App_ = "prefs.cfg"; | 48 | static const char *prefsFileName_App_ = "prefs.cfg"; |
@@ -152,9 +153,11 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
152 | load_History(d->history, dataDir_App_); | 153 | load_History(d->history, dataDir_App_); |
153 | #if defined (iHaveLoadEmbed) | 154 | #if defined (iHaveLoadEmbed) |
154 | /* Load the resources from a file. */ { | 155 | /* Load the resources from a file. */ { |
155 | if (!load_Embed(concatPath_CStr(cstr_String(execPath_App()), EMB_BIN))) { | 156 | if (!load_Embed(concatPath_CStr(cstr_String(execPath_App()), "../resources.bin"))) { |
156 | fprintf(stderr, "failed to load resources.bin\n"); | 157 | if (!load_Embed(concatPath_CStr(cstr_String(execPath_App()), EMB_BIN))) { |
157 | exit(-1); | 158 | fprintf(stderr, "failed to load resources.bin: %s\n", strerror(errno)); |
159 | exit(-1); | ||
160 | } | ||
158 | } | 161 | } |
159 | } | 162 | } |
160 | #endif | 163 | #endif |
@@ -311,6 +314,10 @@ void addTicker_App(void (*ticker)(iAny *), iAny *context) { | |||
311 | insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); | 314 | insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); |
312 | } | 315 | } |
313 | 316 | ||
317 | iGmCerts *certs_App(void) { | ||
318 | return app_.certs; | ||
319 | } | ||
320 | |||
314 | const iHistory *history_App(void) { | 321 | const iHistory *history_App(void) { |
315 | return app_.history; | 322 | return app_.history; |
316 | } | 323 | } |
diff --git a/src/gmcerts.c b/src/gmcerts.c index cb4d8477..0aa084a4 100644 --- a/src/gmcerts.c +++ b/src/gmcerts.c | |||
@@ -1,15 +1,134 @@ | |||
1 | #include "gmcerts.h" | 1 | #include "gmcerts.h" |
2 | 2 | ||
3 | #include <the_Foundation/file.h> | ||
4 | #include <the_Foundation/path.h> | ||
5 | #include <the_Foundation/regexp.h> | ||
6 | #include <the_Foundation/stringhash.h> | ||
7 | #include <the_Foundation/time.h> | ||
8 | #include <ctype.h> | ||
9 | |||
10 | static const char *filename_GmCerts_ = "trusted.txt"; | ||
11 | |||
12 | iDeclareClass(TrustEntry) | ||
13 | |||
14 | struct Impl_TrustEntry { | ||
15 | iObject object; | ||
16 | iBlock fingerprint; | ||
17 | iTime validUntil; | ||
18 | }; | ||
19 | |||
20 | void init_TrustEntry(iTrustEntry *d, const iBlock *fingerprint, const iDate *until) { | ||
21 | initCopy_Block(&d->fingerprint, fingerprint); | ||
22 | init_Time(&d->validUntil, until); | ||
23 | } | ||
24 | |||
25 | void deinit_TrustEntry(iTrustEntry *d) { | ||
26 | deinit_Block(&d->fingerprint); | ||
27 | } | ||
28 | |||
29 | iDefineObjectConstructionArgs(TrustEntry, (const iBlock *fingerprint, const iDate *until), fingerprint, until) | ||
30 | iDefineClass(TrustEntry) | ||
31 | |||
32 | /*-----------------------------------------------------------------------------------------------*/ | ||
33 | |||
3 | struct Impl_GmCerts { | 34 | struct Impl_GmCerts { |
4 | iString saveDir; | 35 | iString saveDir; |
36 | iStringHash *trusted; | ||
5 | }; | 37 | }; |
6 | 38 | ||
7 | iDefineTypeConstructionArgs(GmCerts, (const char *saveDir), saveDir) | 39 | iDefineTypeConstructionArgs(GmCerts, (const char *saveDir), saveDir) |
8 | 40 | ||
41 | static void save_GmCerts_(const iGmCerts *d) { | ||
42 | iBeginCollect(); | ||
43 | iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, filename_GmCerts_))); | ||
44 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | ||
45 | iString line; | ||
46 | init_String(&line); | ||
47 | iConstForEach(StringHash, i, d->trusted) { | ||
48 | const iTrustEntry *trust = value_StringHashNode(i.value); | ||
49 | format_String(&line, | ||
50 | "%s %ld %s\n", | ||
51 | cstr_String(key_StringHashConstIterator(&i)), | ||
52 | integralSeconds_Time(&trust->validUntil), | ||
53 | cstrCollect_String(hexEncode_Block(&trust->fingerprint))); | ||
54 | write_File(f, &line.chars); | ||
55 | } | ||
56 | deinit_String(&line); | ||
57 | } | ||
58 | iRelease(f); | ||
59 | iEndCollect(); | ||
60 | } | ||
61 | |||
62 | static void load_GmCerts_(iGmCerts *d) { | ||
63 | iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, filename_GmCerts_))); | ||
64 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | ||
65 | iRegExp * pattern = new_RegExp("([^\\s]+) ([0-9]+) ([a-z0-9]+)", 0); | ||
66 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); | ||
67 | iRangecc line = iNullRange; | ||
68 | while (nextSplit_Rangecc(&src, "\n", &line)) { | ||
69 | iRegExpMatch m; | ||
70 | if (matchRange_RegExp(pattern, line, &m)) { | ||
71 | const iRangecc domain = capturedRange_RegExpMatch(&m, 1); | ||
72 | const iRangecc until = capturedRange_RegExpMatch(&m, 2); | ||
73 | const iRangecc fp = capturedRange_RegExpMatch(&m, 3); | ||
74 | time_t sec; | ||
75 | sscanf(until.start, "%ld", &sec); | ||
76 | iDate untilDate; | ||
77 | initSinceEpoch_Date(&untilDate, sec); | ||
78 | insert_StringHash(d->trusted, | ||
79 | collect_String(newRange_String(domain)), | ||
80 | new_TrustEntry(collect_Block(hexDecode_Rangecc(fp)), | ||
81 | &untilDate)); | ||
82 | } | ||
83 | } | ||
84 | iRelease(pattern); | ||
85 | } | ||
86 | iRelease(f); | ||
87 | } | ||
88 | |||
9 | void init_GmCerts(iGmCerts *d, const char *saveDir) { | 89 | void init_GmCerts(iGmCerts *d, const char *saveDir) { |
10 | initCStr_String(&d->saveDir, saveDir); | 90 | initCStr_String(&d->saveDir, saveDir); |
91 | d->trusted = new_StringHash(); | ||
92 | load_GmCerts_(d); | ||
11 | } | 93 | } |
12 | 94 | ||
13 | void deinit_GmCerts(iGmCerts *d) { | 95 | void deinit_GmCerts(iGmCerts *d) { |
96 | iRelease(d->trusted); | ||
14 | deinit_String(&d->saveDir); | 97 | deinit_String(&d->saveDir); |
15 | } | 98 | } |
99 | |||
100 | iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, const iTlsCertificate *cert) { | ||
101 | if (!cert) { | ||
102 | return iFalse; | ||
103 | } | ||
104 | if (isExpired_TlsCertificate(cert)) { | ||
105 | return iFalse; | ||
106 | } | ||
107 | if (!verifyDomain_TlsCertificate(cert, domain)) { | ||
108 | return iFalse; | ||
109 | } | ||
110 | /* Good certificate. If not already trusted, add it now. */ | ||
111 | const iString *key = collect_String(newRange_String(domain)); | ||
112 | iDate until; | ||
113 | validUntil_TlsCertificate(cert, &until); | ||
114 | iBlock *fingerprint = collect_Block(fingerprint_TlsCertificate(cert)); | ||
115 | iTrustEntry *trust = value_StringHash(d->trusted, key); | ||
116 | if (trust) { | ||
117 | /* We already have it, check if it matches the one we trust for this domain (if it's | ||
118 | still valid. */ | ||
119 | iTime now; | ||
120 | initCurrent_Time(&now); | ||
121 | if (secondsSince_Time(&trust->validUntil, &now) > 0) { | ||
122 | /* Trusted cert is still valid. */ | ||
123 | return cmp_Block(fingerprint, &trust->fingerprint) == 0; | ||
124 | } | ||
125 | /* Update the trusted cert. */ | ||
126 | init_Time(&trust->validUntil, &until); | ||
127 | set_Block(&trust->fingerprint, fingerprint); | ||
128 | } | ||
129 | else { | ||
130 | insert_StringHash(d->trusted, key, iClob(new_TrustEntry(fingerprint, &until))); | ||
131 | } | ||
132 | save_GmCerts_(d); | ||
133 | return iTrue; | ||
134 | } | ||
diff --git a/src/gmcerts.h b/src/gmcerts.h index 1990463c..a3df8f33 100644 --- a/src/gmcerts.h +++ b/src/gmcerts.h | |||
@@ -4,3 +4,5 @@ | |||
4 | 4 | ||
5 | iDeclareType(GmCerts) | 5 | iDeclareType(GmCerts) |
6 | iDeclareTypeConstructionArgs(GmCerts, const char *saveDir) | 6 | iDeclareTypeConstructionArgs(GmCerts, const char *saveDir) |
7 | |||
8 | iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); | ||
diff --git a/src/gmdocument.h b/src/gmdocument.h index db5a5451..215dcdb5 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -1,6 +1,6 @@ | |||
1 | #pragma once | 1 | #pragma once |
2 | 2 | ||
3 | #include "gemini.h" | 3 | #include "gmutil.h" |
4 | #include <the_Foundation/object.h> | 4 | #include <the_Foundation/object.h> |
5 | #include <the_Foundation/rect.h> | 5 | #include <the_Foundation/rect.h> |
6 | #include <the_Foundation/string.h> | 6 | #include <the_Foundation/string.h> |
diff --git a/src/gmrequest.c b/src/gmrequest.c index 32b386b2..dc08d23b 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -1,5 +1,7 @@ | |||
1 | #include "gmrequest.h" | 1 | #include "gmrequest.h" |
2 | #include "gmutil.h" | 2 | #include "gmutil.h" |
3 | #include "gmcerts.h" | ||
4 | #include "app.h" | ||
3 | 5 | ||
4 | #include <the_Foundation/file.h> | 6 | #include <the_Foundation/file.h> |
5 | #include <the_Foundation/tlsrequest.h> | 7 | #include <the_Foundation/tlsrequest.h> |
@@ -20,7 +22,7 @@ struct Impl_GmRequest { | |||
20 | iObject object; | 22 | iObject object; |
21 | iMutex mutex; | 23 | iMutex mutex; |
22 | enum iGmRequestState state; | 24 | enum iGmRequestState state; |
23 | enum iGmRequestCertification certState; | 25 | int certFlags; |
24 | iString url; | 26 | iString url; |
25 | iTlsRequest *req; | 27 | iTlsRequest *req; |
26 | enum iGmStatusCode code; | 28 | enum iGmStatusCode code; |
@@ -38,7 +40,7 @@ iDefineAudienceGetter(GmRequest, finished) | |||
38 | void init_GmRequest(iGmRequest *d) { | 40 | void init_GmRequest(iGmRequest *d) { |
39 | init_Mutex(&d->mutex); | 41 | init_Mutex(&d->mutex); |
40 | d->state = initialized_GmRequestState; | 42 | d->state = initialized_GmRequestState; |
41 | d->certState = notApplicable_GmRequestCertification; | 43 | d->certFlags = 0; |
42 | init_String(&d->url); | 44 | init_String(&d->url); |
43 | d->req = NULL; | 45 | d->req = NULL; |
44 | d->code = none_GmStatusCode; | 46 | d->code = none_GmStatusCode; |
@@ -165,11 +167,25 @@ static void requestFinished_GmRequest_(iAnyObject *obj) { | |||
165 | } | 167 | } |
166 | SDL_RemoveTimer(d->timeoutId); | 168 | SDL_RemoveTimer(d->timeoutId); |
167 | d->timeoutId = 0; | 169 | d->timeoutId = 0; |
168 | d->state = finished_GmRequestState; | 170 | d->state = finished_GmRequestState; |
169 | #if 1 | 171 | d->certFlags = 0; |
170 | /* Check the server certificate. */ { | 172 | /* Check the server certificate. */ { |
171 | d->certState = invalid_GmRequestCertification; /* check trust */ | ||
172 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); | 173 | const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); |
174 | if (cert) { | ||
175 | iGmCerts * certDb = certs_App(); | ||
176 | const iRangecc domain = urlHost_String(&d->url); | ||
177 | d->certFlags |= available_GmRequestCertFlag; | ||
178 | if (!isExpired_TlsCertificate(cert)) { | ||
179 | d->certFlags |= timeVerified_GmRequestCertFlag; | ||
180 | } | ||
181 | if (verifyDomain_TlsCertificate(cert, domain)) { | ||
182 | d->certFlags |= domainVerified_GmRequestCertFlag; | ||
183 | } | ||
184 | if (checkTrust_GmCerts(certDb, domain, cert)) { | ||
185 | d->certFlags |= trusted_GmRequestCertFlag; | ||
186 | } | ||
187 | } | ||
188 | #if 0 | ||
173 | printf("Server certificate:\n%s\n", cstrLocal_String(pem_TlsCertificate(cert))); | 189 | printf("Server certificate:\n%s\n", cstrLocal_String(pem_TlsCertificate(cert))); |
174 | iBlock *sha = fingerprint_TlsCertificate(cert); | 190 | iBlock *sha = fingerprint_TlsCertificate(cert); |
175 | printf("Fingerprint: %s\n", | 191 | printf("Fingerprint: %s\n", |
@@ -188,8 +204,8 @@ static void requestFinished_GmRequest_(iAnyObject *obj) { | |||
188 | verifyDomain_TlsCertificate(cert, parts.host) ? "valid" : "not valid"); | 204 | verifyDomain_TlsCertificate(cert, parts.host) ? "valid" : "not valid"); |
189 | } | 205 | } |
190 | fflush(stdout); | 206 | fflush(stdout); |
191 | } | ||
192 | #endif | 207 | #endif |
208 | } | ||
193 | unlock_Mutex(&d->mutex); | 209 | unlock_Mutex(&d->mutex); |
194 | iNotifyAudience(d, finished, GmRequestFinished); | 210 | iNotifyAudience(d, finished, GmRequestFinished); |
195 | } | 211 | } |
@@ -211,7 +227,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
211 | /* TODO: Check supported file types: images, audio */ | 227 | /* TODO: Check supported file types: images, audio */ |
212 | /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ | 228 | /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ |
213 | d->code = success_GmStatusCode; | 229 | d->code = success_GmStatusCode; |
214 | d->certState = notApplicable_GmRequestCertification; | 230 | d->certFlags = 0; |
215 | if (endsWithCase_String(path, ".gmi")) { | 231 | if (endsWithCase_String(path, ".gmi")) { |
216 | setCStr_String(&d->header, "text/gemini; charset=utf-8"); | 232 | setCStr_String(&d->header, "text/gemini; charset=utf-8"); |
217 | } | 233 | } |
@@ -231,6 +247,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
231 | setCStr_String(&d->header, "application/octet-stream"); | 247 | setCStr_String(&d->header, "application/octet-stream"); |
232 | } | 248 | } |
233 | set_Block(&d->body, collect_Block(readAll_File(f))); | 249 | set_Block(&d->body, collect_Block(readAll_File(f))); |
250 | d->state = receivingBody_GmRequestState; | ||
234 | iNotifyAudience(d, updated, GmRequestUpdated); | 251 | iNotifyAudience(d, updated, GmRequestUpdated); |
235 | } | 252 | } |
236 | else { | 253 | else { |
@@ -238,14 +255,14 @@ void submit_GmRequest(iGmRequest *d) { | |||
238 | setCStr_String(&d->header, cstr_String(path)); | 255 | setCStr_String(&d->header, cstr_String(path)); |
239 | } | 256 | } |
240 | iRelease(f); | 257 | iRelease(f); |
258 | d->certFlags = 0; | ||
241 | d->state = finished_GmRequestState; | 259 | d->state = finished_GmRequestState; |
242 | d->certState = notApplicable_GmRequestCertification; | ||
243 | iNotifyAudience(d, finished, GmRequestFinished); | 260 | iNotifyAudience(d, finished, GmRequestFinished); |
244 | return; | 261 | return; |
245 | } | 262 | } |
246 | else if (equalCase_Rangecc(&url.protocol, "data")) { | 263 | else if (equalCase_Rangecc(&url.protocol, "data")) { |
247 | d->code = success_GmStatusCode; | 264 | d->code = success_GmStatusCode; |
248 | d->certState = notApplicable_GmRequestCertification; | 265 | d->certFlags = 0; |
249 | iString *src = collectNewCStr_String(url.protocol.start + 5); | 266 | iString *src = collectNewCStr_String(url.protocol.start + 5); |
250 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; | 267 | iRangecc header = { constBegin_String(src), constBegin_String(src) }; |
251 | while (header.end < constEnd_String(src) && *header.end != ',') { | 268 | while (header.end < constEnd_String(src) && *header.end != ',') { |
@@ -269,6 +286,8 @@ void submit_GmRequest(iGmRequest *d) { | |||
269 | set_String(src, collect_String(urlDecode_String(src))); | 286 | set_String(src, collect_String(urlDecode_String(src))); |
270 | } | 287 | } |
271 | set_Block(&d->body, &src->chars); | 288 | set_Block(&d->body, &src->chars); |
289 | d->state = receivingBody_GmRequestState; | ||
290 | iNotifyAudience(d, updated, GmRequestUpdated); | ||
272 | d->state = finished_GmRequestState; | 291 | d->state = finished_GmRequestState; |
273 | iNotifyAudience(d, finished, GmRequestFinished); | 292 | iNotifyAudience(d, finished, GmRequestFinished); |
274 | return; | 293 | return; |
@@ -314,4 +333,14 @@ const iString *url_GmRequest(const iGmRequest *d) { | |||
314 | return &d->url; | 333 | return &d->url; |
315 | } | 334 | } |
316 | 335 | ||
336 | int certFlags_GmRequest(const iGmRequest *d) { | ||
337 | return d->certFlags; | ||
338 | } | ||
339 | |||
340 | iDate certExpirationDate_GmRequest(const iGmRequest *d) { | ||
341 | iDate expiry; | ||
342 | validUntil_TlsCertificate(serverCertificate_TlsRequest(d->req), &expiry); | ||
343 | return expiry; | ||
344 | } | ||
345 | |||
317 | iDefineClass(GmRequest) | 346 | iDefineClass(GmRequest) |
diff --git a/src/gmrequest.h b/src/gmrequest.h index 37f324eb..500ed71b 100644 --- a/src/gmrequest.h +++ b/src/gmrequest.h | |||
@@ -1,9 +1,9 @@ | |||
1 | #pragma once | 1 | #pragma once |
2 | 2 | ||
3 | #include <the_Foundation/audience.h> | 3 | #include <the_Foundation/audience.h> |
4 | #include <the_Foundation/object.h> | 4 | #include <the_Foundation/tlsrequest.h> |
5 | 5 | ||
6 | #include "gemini.h" | 6 | #include "gmutil.h" |
7 | 7 | ||
8 | iDeclareClass(GmRequest) | 8 | iDeclareClass(GmRequest) |
9 | iDeclareObjectConstruction(GmRequest) | 9 | iDeclareObjectConstruction(GmRequest) |
@@ -16,11 +16,11 @@ iDeclareAudienceGetter(GmRequest, finished) | |||
16 | void setUrl_GmRequest (iGmRequest *, const iString *url); | 16 | void setUrl_GmRequest (iGmRequest *, const iString *url); |
17 | void submit_GmRequest (iGmRequest *); | 17 | void submit_GmRequest (iGmRequest *); |
18 | 18 | ||
19 | enum iGmRequestCertification { | 19 | enum iGmRequestCertFlags { |
20 | notApplicable_GmRequestCertification, | 20 | available_GmRequestCertFlag = iBit(1), /* certificate provided by server */ |
21 | invalid_GmRequestCertification, | 21 | trusted_GmRequestCertFlag = iBit(2), /* TOFU status */ |
22 | valid_GmRequestCertification, | 22 | timeVerified_GmRequestCertFlag = iBit(3), /* has not expired */ |
23 | expired_GmRequestCertification, | 23 | domainVerified_GmRequestCertFlag = iBit(4), /* cert matches server domain */ |
24 | }; | 24 | }; |
25 | 25 | ||
26 | iBool isFinished_GmRequest (const iGmRequest *); | 26 | iBool isFinished_GmRequest (const iGmRequest *); |
@@ -28,3 +28,6 @@ enum iGmStatusCode status_GmRequest (const iGmRequest *); | |||
28 | const iString * meta_GmRequest (const iGmRequest *); | 28 | const iString * meta_GmRequest (const iGmRequest *); |
29 | const iBlock * body_GmRequest (const iGmRequest *); | 29 | const iBlock * body_GmRequest (const iGmRequest *); |
30 | const iString * url_GmRequest (const iGmRequest *); | 30 | const iString * url_GmRequest (const iGmRequest *); |
31 | |||
32 | int certFlags_GmRequest (const iGmRequest *); | ||
33 | iDate certExpirationDate_GmRequest(const iGmRequest *); | ||
diff --git a/src/gmutil.c b/src/gmutil.c index bf08e960..0caadd32 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -1,5 +1,4 @@ | |||
1 | #include "gmutil.h" | 1 | #include "gmutil.h" |
2 | #include "gemini.h" | ||
3 | 2 | ||
4 | #include <the_Foundation/regexp.h> | 3 | #include <the_Foundation/regexp.h> |
5 | #include <the_Foundation/object.h> | 4 | #include <the_Foundation/object.h> |
@@ -88,6 +87,12 @@ void cleanUrlPath_String(iString *d) { | |||
88 | deinit_String(&clean); | 87 | deinit_String(&clean); |
89 | } | 88 | } |
90 | 89 | ||
90 | iRangecc urlHost_String(const iString *d) { | ||
91 | iUrl url; | ||
92 | init_Url(&url, d); | ||
93 | return url.host; | ||
94 | } | ||
95 | |||
91 | const iString *absoluteUrl_String(const iString *d, const iString *urlMaybeRelative) { | 96 | const iString *absoluteUrl_String(const iString *d, const iString *urlMaybeRelative) { |
92 | iUrl orig; | 97 | iUrl orig; |
93 | iUrl rel; | 98 | iUrl rel; |
diff --git a/src/gmutil.h b/src/gmutil.h index 3342b262..6c1b01ad 100644 --- a/src/gmutil.h +++ b/src/gmutil.h | |||
@@ -54,5 +54,6 @@ struct Impl_Url { | |||
54 | 54 | ||
55 | void init_Url (iUrl *, const iString *text); | 55 | void init_Url (iUrl *, const iString *text); |
56 | 56 | ||
57 | iRangecc urlHost_String (const iString *); | ||
57 | const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); | 58 | const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); |
58 | void urlEncodeSpaces_String (iString *); | 59 | void urlEncodeSpaces_String (iString *); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 954ded0c..4c20a188 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1,11 +1,11 @@ | |||
1 | #include "documentwidget.h" | 1 | #include "documentwidget.h" |
2 | #include "scrollwidget.h" | 2 | #include "scrollwidget.h" |
3 | #include "inputwidget.h" | 3 | #include "inputwidget.h" |
4 | #include "labelwidget.h" | ||
4 | #include "paint.h" | 5 | #include "paint.h" |
5 | #include "command.h" | 6 | #include "command.h" |
6 | #include "util.h" | 7 | #include "util.h" |
7 | #include "app.h" | 8 | #include "app.h" |
8 | #include "../gemini.h" | ||
9 | #include "../gmdocument.h" | 9 | #include "../gmdocument.h" |
10 | #include "../gmrequest.h" | 10 | #include "../gmrequest.h" |
11 | #include "../gmutil.h" | 11 | #include "../gmutil.h" |
@@ -406,13 +406,37 @@ static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY) { | |||
406 | scroll_DocumentWidget_(d, 0); /* clamp it */ | 406 | scroll_DocumentWidget_(d, 0); /* clamp it */ |
407 | } | 407 | } |
408 | 408 | ||
409 | static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | 409 | static void updateTrust_DocumentWidget_(iDocumentWidget *d) { |
410 | iAssert(d->request); | ||
411 | #define openLock_CStr "\U0001f513" | ||
412 | #define closedLock_CStr "\U0001f512" | ||
413 | int certFlags = certFlags_GmRequest(d->request); | ||
414 | iLabelWidget *lock = findWidget_App("navbar.lock"); | ||
415 | if (~certFlags & available_GmRequestCertFlag) { | ||
416 | setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iTrue); | ||
417 | updateTextCStr_LabelWidget(lock, gray50_ColorEscape openLock_CStr); | ||
418 | return; | ||
419 | } | ||
420 | setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iFalse); | ||
421 | if (~certFlags & domainVerified_GmRequestCertFlag) { | ||
422 | updateTextCStr_LabelWidget(lock, red_ColorEscape closedLock_CStr); | ||
423 | } | ||
424 | else if (certFlags & trusted_GmRequestCertFlag) { | ||
425 | updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_CStr); | ||
426 | } | ||
427 | else { | ||
428 | updateTextCStr_LabelWidget(lock, orange_ColorEscape closedLock_CStr); | ||
429 | } | ||
430 | } | ||
431 | |||
432 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | ||
410 | if (!d->request) { | 433 | if (!d->request) { |
411 | return; | 434 | return; |
412 | } | 435 | } |
413 | enum iGmStatusCode statusCode = status_GmRequest(d->request); | 436 | enum iGmStatusCode statusCode = status_GmRequest(d->request); |
414 | if (d->state == fetching_DocumentState) { | 437 | if (d->state == fetching_DocumentState) { |
415 | d->state = receivedPartialResponse_DocumentState; | 438 | d->state = receivedPartialResponse_DocumentState; |
439 | updateTrust_DocumentWidget_(d); | ||
416 | switch (statusCode / 10) { | 440 | switch (statusCode / 10) { |
417 | case 1: /* input required */ { | 441 | case 1: /* input required */ { |
418 | iUrl parts; | 442 | iUrl parts; |
@@ -594,14 +618,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
594 | } | 618 | } |
595 | else if (isCommand_Widget(w, ev, "document.request.updated") && | 619 | else if (isCommand_Widget(w, ev, "document.request.updated") && |
596 | pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { | 620 | pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { |
597 | checkResponseCode_DocumentWidget_(d); | 621 | checkResponse_DocumentWidget_(d); |
598 | // updateSource_DocumentWidget_(d); | ||
599 | return iFalse; | 622 | return iFalse; |
600 | } | 623 | } |
601 | else if (isCommand_Widget(w, ev, "document.request.finished") && | 624 | else if (isCommand_Widget(w, ev, "document.request.finished") && |
602 | pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { | 625 | pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { |
603 | // updateSource_DocumentWidget_(d); | 626 | iAssert(d->state == receivedPartialResponse_DocumentState); /* must already have been updated at least once */ |
604 | checkResponseCode_DocumentWidget_(d); | 627 | checkResponse_DocumentWidget_(d); |
605 | d->state = ready_DocumentState; | 628 | d->state = ready_DocumentState; |
606 | iReleasePtr(&d->request); | 629 | iReleasePtr(&d->request); |
607 | postCommandf_App("document.changed url:%s", cstr_String(d->url)); | 630 | postCommandf_App("document.changed url:%s", cstr_String(d->url)); |