summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--res/about/version.gmi7
-rw-r--r--src/app.c23
-rw-r--r--src/gmcerts.c8
-rw-r--r--src/gmrequest.c3
-rw-r--r--src/gmrequest.h1
-rw-r--r--src/prefs.c10
-rw-r--r--src/prefs.h2
-rw-r--r--src/ui/documentwidget.c8
-rw-r--r--src/ui/util.c11
9 files changed, 66 insertions, 7 deletions
diff --git a/res/about/version.gmi b/res/about/version.gmi
index c900ed04..c6057490 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,13 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.3
10* Only one instance of Lagrange is allowed to run per user directory.
11* A previously started instance can be controlled with command line options.
12* `--list-tab-urls` prints a list of the currently open URLs.
13* Added `--help`, `--version` options.
14* A server certificate can also be verified by Certificate Authorities. When "CA file" and/or "CA path" are set in Preferences, CA verification will mark a certificate as trusted.
15
9## 1.2.1 16## 1.2.1
10* Fixed crash when creating a bookmark. 17* Fixed crash when creating a bookmark.
11 18
diff --git a/src/app.c b/src/app.c
index 9a7ced62..29d0dd17 100644
--- a/src/app.c
+++ b/src/app.c
@@ -224,6 +224,8 @@ static iString *serializePrefs_App_(const iApp *d) {
224 appendFormat_String(str, "doctheme.dark.set arg:%d\n", d->prefs.docThemeDark); 224 appendFormat_String(str, "doctheme.dark.set arg:%d\n", d->prefs.docThemeDark);
225 appendFormat_String(str, "doctheme.light.set arg:%d\n", d->prefs.docThemeLight); 225 appendFormat_String(str, "doctheme.light.set arg:%d\n", d->prefs.docThemeLight);
226 appendFormat_String(str, "saturation.set arg:%d\n", (int) ((d->prefs.saturation * 100) + 0.5f)); 226 appendFormat_String(str, "saturation.set arg:%d\n", (int) ((d->prefs.saturation * 100) + 0.5f));
227 appendFormat_String(str, "ca.file noset:1 path:%s\n", cstr_String(&d->prefs.caFile));
228 appendFormat_String(str, "ca.path path:%s\n", cstr_String(&d->prefs.caPath));
227 appendFormat_String(str, "proxy.gemini address:%s\n", cstr_String(&d->prefs.geminiProxy)); 229 appendFormat_String(str, "proxy.gemini address:%s\n", cstr_String(&d->prefs.geminiProxy));
228 appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy)); 230 appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy));
229 appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy)); 231 appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy));
@@ -306,6 +308,7 @@ static void loadPrefs_App_(iApp *d) {
306 } 308 }
307 else { 309 else {
308 /* default preference values */ 310 /* default preference values */
311 setCACertificates_TlsRequest(&d->prefs.caFile, &d->prefs.caPath);
309 } 312 }
310#if !defined (LAGRANGE_CUSTOM_FRAME) 313#if !defined (LAGRANGE_CUSTOM_FRAME)
311 d->prefs.customFrame = iFalse; 314 d->prefs.customFrame = iFalse;
@@ -1205,6 +1208,10 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
1205 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.searchurl")))); 1208 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.searchurl"))));
1206 postCommandf_App("cachesize.set arg:%d", 1209 postCommandf_App("cachesize.set arg:%d",
1207 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); 1210 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize"))));
1211 postCommandf_App("ca.file path:%s",
1212 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.ca.file"))));
1213 postCommandf_App("ca.path path:%s",
1214 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.ca.path"))));
1208 postCommandf_App("proxy.gemini address:%s", 1215 postCommandf_App("proxy.gemini address:%s",
1209 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.proxy.gemini")))); 1216 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.proxy.gemini"))));
1210 postCommandf_App("proxy.gopher address:%s", 1217 postCommandf_App("proxy.gopher address:%s",
@@ -1586,6 +1593,20 @@ iBool handleCommand_App(const char *cmd) {
1586 setCStr_String(&d->prefs.downloadDir, suffixPtr_Command(cmd, "path")); 1593 setCStr_String(&d->prefs.downloadDir, suffixPtr_Command(cmd, "path"));
1587 return iTrue; 1594 return iTrue;
1588 } 1595 }
1596 else if (equal_Command(cmd, "ca.file")) {
1597 setCStr_String(&d->prefs.caFile, suffixPtr_Command(cmd, "path"));
1598 if (!argLabel_Command(cmd, "noset")) {
1599 setCACertificates_TlsRequest(&d->prefs.caFile, &d->prefs.caPath);
1600 }
1601 return iTrue;
1602 }
1603 else if (equal_Command(cmd, "ca.path")) {
1604 setCStr_String(&d->prefs.caPath, suffixPtr_Command(cmd, "path"));
1605 if (!argLabel_Command(cmd, "noset")) {
1606 setCACertificates_TlsRequest(&d->prefs.caFile, &d->prefs.caPath);
1607 }
1608 return iTrue;
1609 }
1589 else if (equal_Command(cmd, "open")) { 1610 else if (equal_Command(cmd, "open")) {
1590 iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url")); 1611 iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url"));
1591 const iBool noProxy = argLabel_Command(cmd, "noproxy"); 1612 const iBool noProxy = argLabel_Command(cmd, "noproxy");
@@ -1761,6 +1782,8 @@ iBool handleCommand_App(const char *cmd) {
1761 collectNewFormat_String("%d", d->prefs.maxCacheSize)); 1782 collectNewFormat_String("%d", d->prefs.maxCacheSize));
1762 setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); 1783 setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs);
1763 setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.searchUrl); 1784 setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.searchUrl);
1785 setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.caFile);
1786 setText_InputWidget(findChild_Widget(dlg, "prefs.ca.path"), &d->prefs.caPath);
1764 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy); 1787 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy);
1765 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy); 1788 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy);
1766 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.httpProxy); 1789 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.httpProxy);
diff --git a/src/gmcerts.c b/src/gmcerts.c
index da918279..3e629f8f 100644
--- a/src/gmcerts.c
+++ b/src/gmcerts.c
@@ -380,7 +380,9 @@ iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, const iTlsCertificate *ce
380 if (isExpired_TlsCertificate(cert)) { 380 if (isExpired_TlsCertificate(cert)) {
381 return iFalse; 381 return iFalse;
382 } 382 }
383 if (!verifyDomain_TlsCertificate(cert, domain)) { 383 /* We trust CA verification implicitly. */
384 const iBool isAuth = verify_TlsCertificate(cert) == authority_TlsCertificateVerifyStatus;
385 if (!isAuth && !verifyDomain_TlsCertificate(cert, domain)) {
384 return iFalse; 386 return iFalse;
385 } 387 }
386 /* TODO: Could call setTrusted_GmCerts() instead of duplicating the trust-setting. */ 388 /* TODO: Could call setTrusted_GmCerts() instead of duplicating the trust-setting. */
@@ -394,9 +396,7 @@ iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, const iTlsCertificate *ce
394 if (trust) { 396 if (trust) {
395 /* We already have it, check if it matches the one we trust for this domain (if it's 397 /* We already have it, check if it matches the one we trust for this domain (if it's
396 still valid. */ 398 still valid. */
397 iTime now; 399 if (!isAuth && elapsedSeconds_Time(&trust->validUntil) > 0) {
398 initCurrent_Time(&now);
399 if (secondsSince_Time(&trust->validUntil, &now) > 0) {
400 /* Trusted cert is still valid. */ 400 /* Trusted cert is still valid. */
401 const iBool isTrusted = cmp_Block(fingerprint, &trust->fingerprint) == 0; 401 const iBool isTrusted = cmp_Block(fingerprint, &trust->fingerprint) == 0;
402 unlock_Mutex(d->mtx); 402 unlock_Mutex(d->mtx);
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 0208dc94..ea0a2d80 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -163,6 +163,9 @@ static void checkServerCertificate_GmRequest_(iGmRequest *d) {
163 if (checkTrust_GmCerts(d->certs, domain, cert)) { 163 if (checkTrust_GmCerts(d->certs, domain, cert)) {
164 resp->certFlags |= trusted_GmCertFlag; 164 resp->certFlags |= trusted_GmCertFlag;
165 } 165 }
166 if (verify_TlsCertificate(cert) == authority_TlsCertificateVerifyStatus) {
167 resp->certFlags |= authorityVerified_GmCertFlag;
168 }
166 validUntil_TlsCertificate(cert, &resp->certValidUntil); 169 validUntil_TlsCertificate(cert, &resp->certValidUntil);
167 set_String(&resp->certSubject, collect_String(subject_TlsCertificate(cert))); 170 set_String(&resp->certSubject, collect_String(subject_TlsCertificate(cert)));
168 } 171 }
diff --git a/src/gmrequest.h b/src/gmrequest.h
index 6d4eb2f8..9f20e0eb 100644
--- a/src/gmrequest.h
+++ b/src/gmrequest.h
@@ -36,6 +36,7 @@ enum iGmCertFlags {
36 timeVerified_GmCertFlag = iBit(3), /* has not expired */ 36 timeVerified_GmCertFlag = iBit(3), /* has not expired */
37 domainVerified_GmCertFlag = iBit(4), /* cert matches server domain */ 37 domainVerified_GmCertFlag = iBit(4), /* cert matches server domain */
38 haveFingerprint_GmCertFlag = iBit(5), 38 haveFingerprint_GmCertFlag = iBit(5),
39 authorityVerified_GmCertFlag = iBit(6),
39}; 40};
40 41
41struct Impl_GmResponse { 42struct Impl_GmResponse {
diff --git a/src/prefs.c b/src/prefs.c
index 97ad7f48..e3b5d603 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -22,6 +22,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "prefs.h" 23#include "prefs.h"
24 24
25#include <the_Foundation/fileinfo.h>
26
25void init_Prefs(iPrefs *d) { 27void init_Prefs(iPrefs *d) {
26 d->dialogTab = 0; 28 d->dialogTab = 0;
27 d->useSystemTheme = iTrue; 29 d->useSystemTheme = iTrue;
@@ -48,6 +50,8 @@ void init_Prefs(iPrefs *d) {
48 d->docThemeDark = colorfulDark_GmDocumentTheme; 50 d->docThemeDark = colorfulDark_GmDocumentTheme;
49 d->docThemeLight = white_GmDocumentTheme; 51 d->docThemeLight = white_GmDocumentTheme;
50 d->saturation = 1.0f; 52 d->saturation = 1.0f;
53 init_String(&d->caFile);
54 init_String(&d->caPath);
51 init_String(&d->geminiProxy); 55 init_String(&d->geminiProxy);
52 init_String(&d->gopherProxy); 56 init_String(&d->gopherProxy);
53 init_String(&d->httpProxy); 57 init_String(&d->httpProxy);
@@ -56,6 +60,10 @@ void init_Prefs(iPrefs *d) {
56#if defined (iPlatformAppleMobile) 60#if defined (iPlatformAppleMobile)
57 d->hoverLink = iFalse; 61 d->hoverLink = iFalse;
58#endif 62#endif
63 /* TODO: Add some platform-specific common locations? */
64 if (fileExistsCStr_FileInfo("/etc/ssl/certs")) {
65 setCStr_String(&d->caPath, "/etc/ssl/certs");
66 }
59} 67}
60 68
61void deinit_Prefs(iPrefs *d) { 69void deinit_Prefs(iPrefs *d) {
@@ -64,4 +72,6 @@ void deinit_Prefs(iPrefs *d) {
64 deinit_String(&d->gopherProxy); 72 deinit_String(&d->gopherProxy);
65 deinit_String(&d->httpProxy); 73 deinit_String(&d->httpProxy);
66 deinit_String(&d->downloadDir); 74 deinit_String(&d->downloadDir);
75 deinit_String(&d->caPath);
76 deinit_String(&d->caFile);
67} 77}
diff --git a/src/prefs.h b/src/prefs.h
index 4bbe3ad5..56cef52c 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -51,6 +51,8 @@ struct Impl_Prefs {
51 iBool loadImageInsteadOfScrolling; 51 iBool loadImageInsteadOfScrolling;
52 iString searchUrl; 52 iString searchUrl;
53 /* Network */ 53 /* Network */
54 iString caFile;
55 iString caPath;
54 iBool decodeUserVisibleURLs; 56 iBool decodeUserVisibleURLs;
55 int maxCacheSize; /* MB */ 57 int maxCacheSize; /* MB */
56 iString geminiProxy; 58 iString geminiProxy;
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 947d9d5f..0af022c5 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1585,10 +1585,16 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1585 } 1585 }
1586 } 1586 }
1587 appendFormat_String(msg, 1587 appendFormat_String(msg,
1588 "\n%sCertificate Status:\n%s%s Domain name %s%s\n" 1588 "\n%sCertificate Status:\n"
1589 "%s%s %s by CA\n"
1590 "%s%s Domain name %s%s\n"
1589 "%s%s %s (%04d-%02d-%02d %02d:%02d:%02d)\n" 1591 "%s%s %s (%04d-%02d-%02d %02d:%02d:%02d)\n"
1590 "%s%s %s", 1592 "%s%s %s",
1591 uiHeading_ColorEscape, 1593 uiHeading_ColorEscape,
1594 d->certFlags & authorityVerified_GmCertFlag ?
1595 checked : uiTextAction_ColorEscape "\u2610",
1596 uiText_ColorEscape,
1597 d->certFlags & authorityVerified_GmCertFlag ? "Verified" : "Not verified",
1592 d->certFlags & domainVerified_GmCertFlag ? checked : unchecked, 1598 d->certFlags & domainVerified_GmCertFlag ? checked : unchecked,
1593 uiText_ColorEscape, 1599 uiText_ColorEscape,
1594 d->certFlags & domainVerified_GmCertFlag ? "matches" : "mismatch", 1600 d->certFlags & domainVerified_GmCertFlag ? "matches" : "mismatch",
diff --git a/src/ui/util.c b/src/ui/util.c
index 603a65cc..5fb3e9f3 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -1177,6 +1177,8 @@ iWidget *makePreferences_Widget(void) {
1177 addChild_Widget(headings, iClob(makeHeading_Widget("Downloads folder:"))); 1177 addChild_Widget(headings, iClob(makeHeading_Widget("Downloads folder:")));
1178 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads"); 1178 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads");
1179#endif 1179#endif
1180 addChild_Widget(headings, iClob(makeHeading_Widget("Search URL:")));
1181 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.searchurl");
1180 addChild_Widget(headings, iClob(makeHeading_Widget("Show URL on hover:"))); 1182 addChild_Widget(headings, iClob(makeHeading_Widget("Show URL on hover:")));
1181 addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); 1183 addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink")));
1182 addChild_Widget(headings, iClob(makeHeading_Widget("Vertical centering:"))); 1184 addChild_Widget(headings, iClob(makeHeading_Widget("Vertical centering:")));
@@ -1300,8 +1302,6 @@ iWidget *makePreferences_Widget(void) {
1300 } 1302 }
1301 /* Network. */ { 1303 /* Network. */ {
1302 appendTwoColumnPage_(tabs, "Network", '5', &headings, &values); 1304 appendTwoColumnPage_(tabs, "Network", '5', &headings, &values);
1303 addChild_Widget(headings, iClob(makeHeading_Widget("Search URL:")));
1304 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.searchurl");
1305 addChild_Widget(headings, iClob(makeHeading_Widget("Decode URLs:"))); 1305 addChild_Widget(headings, iClob(makeHeading_Widget("Decode URLs:")));
1306 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls"))); 1306 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));
1307 addChild_Widget(headings, iClob(makeHeading_Widget("Cache size:"))); 1307 addChild_Widget(headings, iClob(makeHeading_Widget("Cache size:")));
@@ -1312,6 +1312,11 @@ iWidget *makePreferences_Widget(void) {
1312 addChildFlags_Widget(cacheGroup, iClob(new_LabelWidget("MB", NULL)), frameless_WidgetFlag); 1312 addChildFlags_Widget(cacheGroup, iClob(new_LabelWidget("MB", NULL)), frameless_WidgetFlag);
1313 } 1313 }
1314 addChildFlags_Widget(values, iClob(cacheGroup), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 1314 addChildFlags_Widget(values, iClob(cacheGroup), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1315 makeTwoColumnHeading_("CERTIFICATES", headings, values);
1316 addChild_Widget(headings, iClob(makeHeading_Widget("CA file:")));
1317 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.ca.file");
1318 addChild_Widget(headings, iClob(makeHeading_Widget("CA path:")));
1319 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.ca.path");
1315 makeTwoColumnHeading_("PROXIES", headings, values); 1320 makeTwoColumnHeading_("PROXIES", headings, values);
1316 addChild_Widget(headings, iClob(makeHeading_Widget("Gemini proxy:"))); 1321 addChild_Widget(headings, iClob(makeHeading_Widget("Gemini proxy:")));
1317 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gemini"); 1322 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gemini");
@@ -1331,6 +1336,8 @@ iWidget *makePreferences_Widget(void) {
1331 /* Set input field sizes. */ { 1336 /* Set input field sizes. */ {
1332 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.searchurl")); 1337 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.searchurl"));
1333 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.downloads")); 1338 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.downloads"));
1339 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.ca.file"));
1340 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.ca.path"));
1334 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gemini")); 1341 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gemini"));
1335 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gopher")); 1342 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gopher"));
1336 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.http")); 1343 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.http"));