Lagrange [release]
Added option to show URL paths as encoded or decoded
[1mdiff --git a/res/about/version.gmi b/res/about/version.gmi[m
[1mindex 50ade298..42475e13 100644[m
[1m--- a/res/about/version.gmi[m
[1m+++ b/res/about/version.gmi[m
[36m@@ -8,6 +8,9 @@[m
[m
## 0.13[m
* Support for Internationalized Domain Names (IDN) in network requests.[m
[32m+[m[32m* IDNs show up in decoded form in the UI.[m
[32m+[m[32m* Percent-encoded Unicode characters in URL paths are decoded for the UI, and encoded in outgoing requests.[m
[32m+[m[32m* Added option to disable decoding of percent-encoded paths.[m
* Quick search via URL bar shows entries from subscribed feeds.[m
* Added keybindings for zooming.[m
* Improved usability of page content searching (${CTRL+}F, Escape).[m
[1mdiff --git a/src/app.c b/src/app.c[m
[1mindex 8a6b2a66..ae21d078 100644[m
[1m--- a/src/app.c[m
[1m+++ b/src/app.c[m
[36m@@ -188,6 +188,7 @@[m [mstatic iString *serializePrefs_App_(const iApp *d) {[m
appendFormat_String(str, "zoom.set arg:%d\n", d->prefs.zoomPercent);[m
appendFormat_String(str, "smoothscroll arg:%d\n", d->prefs.smoothScrolling);[m
appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling);[m
[32m+[m[32m appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs);[m
appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth);[m
appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph);[m
appendFormat_String(str, "prefs.sideicon.changed arg:%d\n", d->prefs.sideIcon);[m
[36m@@ -836,6 +837,8 @@[m [mstatic iBool handlePrefsCommands_(iWidget *d, const char *cmd) {[m
isSelected_Widget(findChild_Widget(d, "prefs.imageloadscroll")));[m
postCommandf_App("ostheme arg:%d",[m
isSelected_Widget(findChild_Widget(d, "prefs.ostheme")));[m
[32m+[m[32m postCommandf_App("decodeurls arg:%d",[m
[32m+[m[32m isSelected_Widget(findChild_Widget(d, "prefs.decodeurls")));[m
postCommandf_App("proxy.gemini address:%s",[m
cstr_String(text_InputWidget(findChild_Widget(d, "prefs.proxy.gemini"))));[m
postCommandf_App("proxy.gopher address:%s",[m
[36m@@ -1094,6 +1097,10 @@[m [miBool handleCommand_App(const char *cmd) {[m
d->prefs.smoothScrolling = arg_Command(cmd);[m
return iTrue;[m
}[m
[32m+[m[32m else if (equal_Command(cmd, "decodeurls")) {[m
[32m+[m[32m d->prefs.decodeUserVisibleURLs = arg_Command(cmd);[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
else if (equal_Command(cmd, "imageloadscroll")) {[m
d->prefs.loadImageInsteadOfScrolling = arg_Command(cmd);[m
return iTrue;[m
[36m@@ -1184,7 +1191,7 @@[m [miBool handleCommand_App(const char *cmd) {[m
return iTrue;[m
}[m
else if (equal_Command(cmd, "open")) {[m
[31m- const iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url"));[m
[32m+[m[32m iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url"));[m
const iBool noProxy = argLabel_Command(cmd, "noproxy");[m
iUrl parts;[m
init_Url(&parts, url);[m
[36m@@ -1214,6 +1221,12 @@[m [miBool handleCommand_App(const char *cmd) {[m
setInitialScroll_DocumentWidget(doc, argfLabel_Command(cmd, "scroll"));[m
setRedirectCount_DocumentWidget(doc, redirectCount);[m
setFlags_Widget(findWidget_App("document.progress"), hidden_WidgetFlag, iTrue);[m
[32m+[m[32m if (prefs_App()->decodeUserVisibleURLs) {[m
[32m+[m[32m urlDecodePath_String(url);[m
[32m+[m[32m }[m
[32m+[m[32m else {[m
[32m+[m[32m urlEncodePath_String(url);[m
[32m+[m[32m }[m
setUrlFromCache_DocumentWidget(doc, url, isHistory);[m
/* Optionally, jump to a text in the document. This will only work if the document[m
is already available, e.g., it's from "about:" or restored from cache. */[m
[36m@@ -1332,6 +1345,7 @@[m [miBool handleCommand_App(const char *cmd) {[m
dlg, format_CStr("prefs.saturation.%d", (int) (d->prefs.saturation * 3.99f))),[m
selected_WidgetFlag,[m
iTrue);[m
[32m+[m[32m setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs);[m
setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy);[m
setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy);[m
setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.httpProxy);[m
[1mdiff --git a/src/gmrequest.c b/src/gmrequest.c[m
[1mindex 991485bc..1f922142 100644[m
[1m--- a/src/gmrequest.c[m
[1m+++ b/src/gmrequest.c[m
[36m@@ -482,8 +482,6 @@[m [mvoid deinit_GmRequest(iGmRequest *d) {[m
deinit_Gopher(&d->gopher);[m
delete_Audience(d->finished);[m
delete_Audience(d->updated);[m
[31m-// delete_GmResponse(d->respPub);[m
[31m-// deinit_GmResponse(&d->respInt);[m
delete_GmResponse(d->resp);[m
deinit_String(&d->url);[m
delete_Mutex(d->mtx);[m
[36m@@ -494,6 +492,10 @@[m [mvoid setUrl_GmRequest(iGmRequest *d, const iString *url) {[m
/* Encode hostname to Punycode here because we want to submit the Punycode domain name[m
in the request. (TODO: Pending possible Gemini spec change.) */[m
punyEncodeUrlHost_String(&d->url);[m
[32m+[m[32m /* TODO: Gemini spec allows UTF-8 encoded URLs, but still need to percent-encode non-ASCII[m
[32m+[m[32m characters? Could be a server-side issue, e.g., if they're using a URL parser meant for[m
[32m+[m[32m the web. */[m
[32m+[m[32m urlEncodePath_String(&d->url);[m
urlEncodeSpaces_String(&d->url);[m
}[m
[m
[1mdiff --git a/src/gmutil.c b/src/gmutil.c[m
[1mindex 68525be9..afca4978 100644[m
[1m--- a/src/gmutil.c[m
[1m+++ b/src/gmutil.c[m
[36m@@ -151,6 +151,42 @@[m [mstatic iString *punyDecodeHost_(iRangecc host) {[m
return result;[m
}[m
[m
[32m+[m[32mvoid urlDecodePath_String(iString *d) {[m
[32m+[m[32m iUrl url;[m
[32m+[m[32m init_Url(&url, d);[m
[32m+[m[32m if (isEmpty_Range(&url.path)) {[m
[32m+[m[32m return;[m
[32m+[m[32m }[m
[32m+[m[32m iString *decoded = new_String();[m
[32m+[m[32m appendRange_String(decoded, (iRangecc){ constBegin_String(d), url.path.start });[m
[32m+[m[32m iString *path = newRange_String(url.path);[m
[32m+[m[32m iString *decPath = urlDecode_String(path);[m
[32m+[m[32m append_String(decoded, decPath);[m
[32m+[m[32m delete_String(decPath);[m
[32m+[m[32m delete_String(path);[m
[32m+[m[32m appendRange_String(decoded, (iRangecc){ url.path.end, constEnd_String(d) });[m
[32m+[m[32m set_String(d, decoded);[m
[32m+[m[32m delete_String(decoded);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid urlEncodePath_String(iString *d) {[m
[32m+[m[32m iUrl url;[m
[32m+[m[32m init_Url(&url, d);[m
[32m+[m[32m if (isEmpty_Range(&url.path)) {[m
[32m+[m[32m return;[m
[32m+[m[32m }[m
[32m+[m[32m iString *encoded = new_String();[m
[32m+[m[32m appendRange_String(encoded , (iRangecc){ constBegin_String(d), url.path.start });[m
[32m+[m[32m iString *path = newRange_String(url.path);[m
[32m+[m[32m iString *encPath = urlEncodeExclude_String(path, "%/ ");[m
[32m+[m[32m append_String(encoded, encPath);[m
[32m+[m[32m delete_String(encPath);[m
[32m+[m[32m delete_String(path);[m
[32m+[m[32m appendRange_String(encoded, (iRangecc){ url.path.end, constEnd_String(d) });[m
[32m+[m[32m set_String(d, encoded);[m
[32m+[m[32m delete_String(encoded);[m
[32m+[m[32m}[m
[32m+[m
const iString *absoluteUrl_String(const iString *d, const iString *urlMaybeRelative) {[m
iUrl orig;[m
iUrl rel;[m
[1mdiff --git a/src/gmutil.h b/src/gmutil.h[m
[1mindex bbadbafd..88ed1f32 100644[m
[1m--- a/src/gmutil.h[m
[1m+++ b/src/gmutil.h[m
[36m@@ -104,6 +104,8 @@[m [miRangecc urlScheme_String (const iString *);[m
iRangecc urlHost_String (const iString *);[m
const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative);[m
void punyEncodeUrlHost_String(iString *);[m
[32m+[m[32mvoid urlDecodePath_String (iString *);[m
[32m+[m[32mvoid urlEncodePath_String (iString *);[m
iString * makeFileUrl_String (const iString *localFilePath);[m
const char * makeFileUrl_CStr (const char *localFilePath);[m
void urlEncodeSpaces_String (iString *);[m
[1mdiff --git a/src/prefs.c b/src/prefs.c[m
[1mindex 574e07d0..31ffe03b 100644[m
[1m--- a/src/prefs.c[m
[1m+++ b/src/prefs.c[m
[36m@@ -33,6 +33,7 @@[m [mvoid init_Prefs(iPrefs *d) {[m
d->hoverOutline = iFalse;[m
d->smoothScrolling = iTrue;[m
d->loadImageInsteadOfScrolling = iFalse;[m
[32m+[m[32m d->decodeUserVisibleURLs = iTrue;[m
d->font = nunito_TextFont;[m
d->headingFont = nunito_TextFont;[m
d->monospaceGemini = iFalse;[m
[1mdiff --git a/src/prefs.h b/src/prefs.h[m
[1mindex 3f4f534f..e95a32da 100644[m
[1m--- a/src/prefs.h[m
[1m+++ b/src/prefs.h[m
[36m@@ -48,6 +48,7 @@[m [mstruct Impl_Prefs {[m
iBool smoothScrolling;[m
iBool loadImageInsteadOfScrolling;[m
/* Network */[m
[32m+[m[32m iBool decodeUserVisibleURLs;[m
iString geminiProxy;[m
iString gopherProxy;[m
iString httpProxy;[m
[1mdiff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c[m
[1mindex 8024b240..1167ebe3 100644[m
[1m--- a/src/ui/sidebarwidget.c[m
[1m+++ b/src/ui/sidebarwidget.c[m
[36m@@ -240,6 +240,13 @@[m [mstatic void updateItems_SidebarWidget_(iSidebarWidget *d) {[m
const iVisitedUrl *visit = i.ptr;[m
iSidebarItem *item = new_SidebarItem();[m
set_String(&item->url, &visit->url);[m
[32m+[m[32m set_String(&item->label, &visit->url);[m
[32m+[m[32m if (prefs_App()->decodeUserVisibleURLs) {[m
[32m+[m[32m urlDecodePath_String(&item->label);[m
[32m+[m[32m }[m
[32m+[m[32m else {[m
[32m+[m[32m urlEncodePath_String(&item->label);[m
[32m+[m[32m }[m
iDate date;[m
init_Date(&date, &visit->when);[m
if (date.day != on.day || date.month != on.month || date.year != on.year) {[m
[36m@@ -1211,7 +1218,7 @@[m [mstatic void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,[m
}[m
else {[m
iUrl parts;[m
[31m- init_Url(&parts, &d->url);[m
[32m+[m[32m init_Url(&parts, &d->label);[m
const iBool isAbout = equalCase_Rangecc(parts.scheme, "about");[m
const iBool isGemini = equalCase_Rangecc(parts.scheme, "gemini");[m
draw_Text(font,[m
[1mdiff --git a/src/ui/util.c b/src/ui/util.c[m
[1mindex 1ad3f30e..6c9d75dc 100644[m
[1m--- a/src/ui/util.c[m
[1m+++ b/src/ui/util.c[m
[36m@@ -1136,7 +1136,9 @@[m [miWidget *makePreferences_Widget(void) {[m
addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede")));[m
}[m
/* Proxies. */ {[m
[31m- appendTwoColumnPage_(tabs, "Proxies", '5', &headings, &values);[m
[32m+[m[32m appendTwoColumnPage_(tabs, "Network", '5', &headings, &values);[m
[32m+[m[32m addChild_Widget(headings, iClob(makeHeading_Widget("Decode paths:")));[m
[32m+[m[32m addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));[m
addChild_Widget(headings, iClob(makeHeading_Widget("Gemini proxy:")));[m
setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gemini");[m
addChild_Widget(headings, iClob(makeHeading_Widget("Gopher proxy:")));[m