summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app.c2
-rw-r--r--src/bookmarks.c6
-rw-r--r--src/feeds.c4
-rw-r--r--src/gempub.c4
-rw-r--r--src/gmdocument.c3
-rw-r--r--src/gmutil.c26
-rw-r--r--src/gmutil.h1
-rw-r--r--src/ui/documentwidget.c14
-rw-r--r--src/ui/inputwidget.c2
-rw-r--r--src/ui/root.c2
-rw-r--r--src/ui/sidebarwidget.c6
-rw-r--r--src/visited.c2
12 files changed, 51 insertions, 21 deletions
diff --git a/src/app.c b/src/app.c
index b25a213e..2de4c370 100644
--- a/src/app.c
+++ b/src/app.c
@@ -2815,7 +2815,7 @@ iStringSet *listOpenURLs_App(void) {
2815 iStringSet *set = new_StringSet(); 2815 iStringSet *set = new_StringSet();
2816 iObjectList *docs = listDocuments_App(NULL); 2816 iObjectList *docs = listDocuments_App(NULL);
2817 iConstForEach(ObjectList, i, docs) { 2817 iConstForEach(ObjectList, i, docs) {
2818 insert_StringSet(set, withSpacesEncoded_String(url_DocumentWidget(i.object))); 2818 insert_StringSet(set, canonicalUrl_String(url_DocumentWidget(i.object)));
2819 } 2819 }
2820 iRelease(docs); 2820 iRelease(docs);
2821 return set; 2821 return set;
diff --git a/src/bookmarks.c b/src/bookmarks.c
index 1f887c98..65cc7982 100644
--- a/src/bookmarks.c
+++ b/src/bookmarks.c
@@ -158,6 +158,7 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) {
158 appendChar_String(&bm->url, '/'); 158 appendChar_String(&bm->url, '/');
159 } 159 }
160 stripDefaultUrlPort_String(&bm->url); 160 stripDefaultUrlPort_String(&bm->url);
161 set_String(&bm->url, canonicalUrl_String(&bm->url));
161 } 162 }
162 nextSplit_Rangecc(src, "\n", &line); 163 nextSplit_Rangecc(src, "\n", &line);
163 setRange_String(&bm->title, line); 164 setRange_String(&bm->title, line);
@@ -201,7 +202,7 @@ uint32_t add_Bookmarks(iBookmarks *d, const iString *url, const iString *title,
201 iChar icon) { 202 iChar icon) {
202 lock_Mutex(d->mtx); 203 lock_Mutex(d->mtx);
203 iBookmark *bm = new_Bookmark(); 204 iBookmark *bm = new_Bookmark();
204 set_String(&bm->url, url); 205 set_String(&bm->url, canonicalUrl_String(url));
205 set_String(&bm->title, title); 206 set_String(&bm->title, title);
206 if (tags) { 207 if (tags) {
207 set_String(&bm->tags, tags); 208 set_String(&bm->tags, tags);
@@ -298,6 +299,7 @@ static iBool matchUrl_(void *url, const iBookmark *bm) {
298 299
299uint32_t findUrl_Bookmarks(const iBookmarks *d, const iString *url) { 300uint32_t findUrl_Bookmarks(const iBookmarks *d, const iString *url) {
300 /* TODO: O(n), boo */ 301 /* TODO: O(n), boo */
302 url = canonicalUrl_String(url);
301 const iPtrArray *found = list_Bookmarks(d, NULL, matchUrl_, (void *) url); 303 const iPtrArray *found = list_Bookmarks(d, NULL, matchUrl_, (void *) url);
302 if (isEmpty_PtrArray(found)) return 0; 304 if (isEmpty_PtrArray(found)) return 0;
303 return id_Bookmark(constFront_PtrArray(found)); 305 return id_Bookmark(constFront_PtrArray(found));
@@ -448,7 +450,7 @@ void requestFinished_Bookmarks(iBookmarks *d, iGmRequest *req) {
448 const iRangecc url = capturedRange_RegExpMatch(&m, 1); 450 const iRangecc url = capturedRange_RegExpMatch(&m, 1);
449 const iRangecc title = capturedRange_RegExpMatch(&m, 3); 451 const iRangecc title = capturedRange_RegExpMatch(&m, 3);
450 iString * urlStr = newRange_String(url); 452 iString * urlStr = newRange_String(url);
451 const iString *absUrl = absoluteUrl_String(url_GmRequest(req), urlStr); 453 const iString *absUrl = canonicalUrl_String(absoluteUrl_String(url_GmRequest(req), urlStr));
452 if (!findUrl_Bookmarks(d, absUrl)) { 454 if (!findUrl_Bookmarks(d, absUrl)) {
453 iString *titleStr = newRange_String(title); 455 iString *titleStr = newRange_String(title);
454 if (isEmpty_String(titleStr)) { 456 if (isEmpty_String(titleStr)) {
diff --git a/src/feeds.c b/src/feeds.c
index 6b102fef..a68fd726 100644
--- a/src/feeds.c
+++ b/src/feeds.c
@@ -215,7 +215,7 @@ static void parseResult_FeedJob_(iFeedJob *d) {
215 entry->discovered = now; 215 entry->discovered = now;
216 entry->bookmarkId = d->bookmarkId; 216 entry->bookmarkId = d->bookmarkId;
217 setRange_String(&entry->url, url); 217 setRange_String(&entry->url, url);
218 set_String(&entry->url, absoluteUrl_String(url_GmRequest(d->request), &entry->url)); 218 set_String(&entry->url, canonicalUrl_String(absoluteUrl_String(url_GmRequest(d->request), &entry->url)));
219 setRange_String(&entry->title, title); 219 setRange_String(&entry->title, title);
220 trimTitle_(&entry->title); 220 trimTitle_(&entry->title);
221 int year, month, day; 221 int year, month, day;
@@ -241,7 +241,7 @@ static void parseResult_FeedJob_(iFeedJob *d) {
241 entry->bookmarkId = d->bookmarkId; 241 entry->bookmarkId = d->bookmarkId;
242 iString *title = newRange_String(line); 242 iString *title = newRange_String(line);
243 set_String(&entry->title, title); 243 set_String(&entry->title, title);
244 set_String(&entry->url, &d->url); 244 set_String(&entry->url, canonicalUrl_String(&d->url));
245 appendChar_String(&entry->url, '#'); 245 appendChar_String(&entry->url, '#');
246 append_String(&entry->url, collect_String(urlEncode_String(title))); 246 append_String(&entry->url, collect_String(urlEncode_String(title)));
247 delete_String(title); 247 delete_String(title);
diff --git a/src/gempub.c b/src/gempub.c
index 1f5d58ce..23846414 100644
--- a/src/gempub.c
+++ b/src/gempub.c
@@ -246,10 +246,10 @@ size_t navSize_Gempub(const iGempub *d) {
246 246
247size_t navIndex_Gempub(const iGempub *d, const iString *url) { 247size_t navIndex_Gempub(const iGempub *d, const iString *url) {
248 parseNavigationLinks_Gempub_(d); 248 parseNavigationLinks_Gempub_(d);
249 const iString *normUrl = withSpacesEncoded_String(url); 249 const iString *canonUrl = withSpacesEncoded_String(url);
250 iConstForEach(Array, i, d->navLinks) { 250 iConstForEach(Array, i, d->navLinks) {
251 const iGempubNavLink *nav = i.value; 251 const iGempubNavLink *nav = i.value;
252 if (equalCase_String(&nav->url, normUrl)) { 252 if (equalCase_String(&nav->url, canonUrl)) {
253 return index_ArrayConstIterator(&i); 253 return index_ArrayConstIterator(&i);
254 } 254 }
255 } 255 }
diff --git a/src/gmdocument.c b/src/gmdocument.c
index f15d9d1d..8832271a 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -1554,13 +1554,14 @@ static void normalize_GmDocument(iGmDocument *d) {
1554 printf("wasNormalized: %d\n", wasNormalized); 1554 printf("wasNormalized: %d\n", wasNormalized);
1555 fflush(stdout); 1555 fflush(stdout);
1556 set_String(&d->source, collect_String(normalized)); 1556 set_String(&d->source, collect_String(normalized));
1557 normalize_String(&d->source); /* NFC */ 1557 //normalize_String(&d->source); /* NFC */
1558 printf("orig:%zu norm:%zu\n", size_String(&d->unormSource), size_String(&d->source)); 1558 printf("orig:%zu norm:%zu\n", size_String(&d->unormSource), size_String(&d->source));
1559 /* normalized source has an extra newline at the end */ 1559 /* normalized source has an extra newline at the end */
1560// iAssert(wasNormalized || equal_String(&d->unormSource, &d->source)); 1560// iAssert(wasNormalized || equal_String(&d->unormSource, &d->source));
1561} 1561}
1562 1562
1563void setUrl_GmDocument(iGmDocument *d, const iString *url) { 1563void setUrl_GmDocument(iGmDocument *d, const iString *url) {
1564 url = canonicalUrl_String(url);
1564 set_String(&d->url, url); 1565 set_String(&d->url, url);
1565 iUrl parts; 1566 iUrl parts;
1566 init_Url(&parts, url); 1567 init_Url(&parts, url);
diff --git a/src/gmutil.c b/src/gmutil.c
index 7a1ae938..846405c3 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -537,9 +537,29 @@ const iString *withSpacesEncoded_String(const iString *d) {
537 if (isDataUrl_String(d)) { 537 if (isDataUrl_String(d)) {
538 return d; 538 return d;
539 } 539 }
540 iString *enc = copy_String(d); 540 /* Only make a copy if we need to modify the URL. */
541 urlEncodeSpaces_String(enc); 541 if (indexOfCStr_String(d, " ") != iInvalidPos) {
542 return collect_String(enc); 542 iString *enc = copy_String(d);
543 urlEncodeSpaces_String(enc);
544 return collect_String(enc);
545 }
546 return d;
547}
548
549const iString *canonicalUrl_String(const iString *d) {
550 /* The "canonical" form, used for internal storage and comparisons, is:
551 - all non-reserved characters decoded (i.e., it's an IRI)
552 - expect for spaces, which are always `%20`
553 This means a canonical URL can be used on a gemtext link line without modifications. */
554 iString *canon = maybeUrlDecodeExclude_String(d, "/?:;#&= ");
555 /* `canon` may now be NULL if nothing was decoded. */
556 if (indexOfCStr_String(canon ? canon : d, " ") != iInvalidPos) {
557 if (!canon) {
558 canon = copy_String(d);
559 }
560 urlEncodeSpaces_String(canon);
561 }
562 return canon ? collect_String(canon) : d;
543} 563}
544 564
545iRangecc mediaTypeWithoutParameters_Rangecc(iRangecc mime) { 565iRangecc mediaTypeWithoutParameters_Rangecc(iRangecc mime) {
diff --git a/src/gmutil.h b/src/gmutil.h
index e7ff7cc5..be6b471e 100644
--- a/src/gmutil.h
+++ b/src/gmutil.h
@@ -123,6 +123,7 @@ const char * makeFileUrl_CStr (const char *localFilePath);
123iString * localFilePathFromUrl_String(const iString *); 123iString * localFilePathFromUrl_String(const iString *);
124void urlEncodeSpaces_String (iString *); 124void urlEncodeSpaces_String (iString *);
125const iString * withSpacesEncoded_String(const iString *); 125const iString * withSpacesEncoded_String(const iString *);
126const iString * canonicalUrl_String (const iString *);
126 127
127const char * mediaType_Path (const iString *path); 128const char * mediaType_Path (const iString *path);
128iRangecc mediaTypeWithoutParameters_Rangecc (iRangecc mime); 129iRangecc mediaTypeWithoutParameters_Rangecc (iRangecc mime);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index cb1fde28..509933af 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1470,7 +1470,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1470 baseName_Path(collect_String(newRange_String(parts.path))).start; 1470 baseName_Path(collect_String(newRange_String(parts.path))).start;
1471 } 1471 }
1472 format_String(&str, "=> %s %s\n", 1472 format_String(&str, "=> %s %s\n",
1473 cstr_String(withSpacesEncoded_String(d->mod.url)), 1473 cstr_String(canonicalUrl_String(d->mod.url)),
1474 linkTitle); 1474 linkTitle);
1475 setData_Media(media_GmDocument(d->doc), 1475 setData_Media(media_GmDocument(d->doc),
1476 imgLinkId, 1476 imgLinkId,
@@ -1638,7 +1638,7 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1638} 1638}
1639 1639
1640static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { 1640static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1641 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); 1641 const iRecentUrl *recent = findUrl_History(d->mod.history, canonicalUrl_String(d->mod.url));
1642 if (recent && recent->cachedResponse) { 1642 if (recent && recent->cachedResponse) {
1643 iChangeFlags(d->flags, 1643 iChangeFlags(d->flags,
1644 openedFromSidebar_DocumentWidgetFlag, 1644 openedFromSidebar_DocumentWidgetFlag,
@@ -1845,8 +1845,11 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1845 d->sourceStatus = statusCode; 1845 d->sourceStatus = statusCode;
1846 switch (category_GmStatusCode(statusCode)) { 1846 switch (category_GmStatusCode(statusCode)) {
1847 case categoryInput_GmStatusCode: { 1847 case categoryInput_GmStatusCode: {
1848 /* Let the navigation history know that we have been to this URL even though
1849 it is only displayed as an input dialog. */
1850 visitUrl_Visited(visited_App(), d->mod.url, transient_VisitedUrlFlag);
1848 iUrl parts; 1851 iUrl parts;
1849 init_Url(&parts, d->mod.url); 1852 init_Url(&parts, d->mod.url);
1850 iWidget *dlg = makeValueInput_Widget( 1853 iWidget *dlg = makeValueInput_Widget(
1851 as_Widget(d), 1854 as_Widget(d),
1852 NULL, 1855 NULL,
@@ -2646,11 +2649,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2646 } 2649 }
2647 else if (equal_Command(cmd, "document.copylink") && document_App() == d) { 2650 else if (equal_Command(cmd, "document.copylink") && document_App() == d) {
2648 if (d->contextLink) { 2651 if (d->contextLink) {
2649 SDL_SetClipboardText(cstr_String(withSpacesEncoded_String(absoluteUrl_String( 2652 SDL_SetClipboardText(cstr_String(canonicalUrl_String(absoluteUrl_String(
2650 d->mod.url, linkUrl_GmDocument(d->doc, d->contextLink->linkId))))); 2653 d->mod.url, linkUrl_GmDocument(d->doc, d->contextLink->linkId)))));
2651 } 2654 }
2652 else { 2655 else {
2653 SDL_SetClipboardText(cstr_String(withSpacesEncoded_String(d->mod.url))); 2656 SDL_SetClipboardText(cstr_String(canonicalUrl_String(d->mod.url)));
2654 } 2657 }
2655 return iTrue; 2658 return iTrue;
2656 } 2659 }
@@ -4870,6 +4873,7 @@ void deserializeState_DocumentWidget(iDocumentWidget *d, iStream *ins) {
4870} 4873}
4871 4874
4872static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { 4875static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) {
4876 url = canonicalUrl_String(url);
4873 if (!equal_String(d->mod.url, url)) { 4877 if (!equal_String(d->mod.url, url)) {
4874 d->flags |= urlChanged_DocumentWidgetFlag; 4878 d->flags |= urlChanged_DocumentWidgetFlag;
4875 set_String(d->mod.url, url); 4879 set_String(d->mod.url, url);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index b108ee17..8ea21cff 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -903,7 +903,7 @@ static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) {
903 iString *str = collect_String(newUnicodeN_String(constAt_Array(&d->text, m.start), 903 iString *str = collect_String(newUnicodeN_String(constAt_Array(&d->text, m.start),
904 size_Range(&m))); 904 size_Range(&m)));
905 SDL_SetClipboardText( 905 SDL_SetClipboardText(
906 cstr_String(d->inFlags & isUrl_InputWidgetFlag ? withSpacesEncoded_String(str) : str)); 906 cstr_String(d->inFlags & isUrl_InputWidgetFlag ? canonicalUrl_String(str) : str));
907 if (doCut) { 907 if (doCut) {
908 pushUndo_InputWidget_(d); 908 pushUndo_InputWidget_(d);
909 deleteMarked_InputWidget_(d); 909 deleteMarked_InputWidget_(d);
diff --git a/src/ui/root.c b/src/ui/root.c
index 5266978b..c78c9632 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -725,7 +725,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
725 const iString *urlStr = collect_String(suffix_Command(cmd, "url")); 725 const iString *urlStr = collect_String(suffix_Command(cmd, "url"));
726 trimCache_App(); 726 trimCache_App();
727 trimMemory_App(); 727 trimMemory_App();
728 visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */ 728 visitUrl_Visited(visited_App(), urlStr, 0); /* TODO: internal URI normalization */
729 postCommand_App("visited.changed"); /* sidebar will update */ 729 postCommand_App("visited.changed"); /* sidebar will update */
730 setText_InputWidget(url, urlStr); 730 setText_InputWidget(url, urlStr);
731 checkLoadAnimation_Root_(get_Root()); 731 checkLoadAnimation_Root_(get_Root());
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index c0a22e99..4c2a9d64 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -217,7 +217,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
217 iBool isEmpty = iFalse; /* show blank? */ 217 iBool isEmpty = iFalse; /* show blank? */
218 switch (d->mode) { 218 switch (d->mode) {
219 case feeds_SidebarMode: { 219 case feeds_SidebarMode: {
220 const iString *docUrl = withSpacesEncoded_String(url_DocumentWidget(document_App())); 220 const iString *docUrl = canonicalUrl_String(url_DocumentWidget(document_App()));
221 /* TODO: internal URI normalization */ 221 /* TODO: internal URI normalization */
222 iTime now; 222 iTime now;
223 iDate on; 223 iDate on;
@@ -1111,7 +1111,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1111 else if (isCommand_Widget(w, ev, "bookmark.copy")) { 1111 else if (isCommand_Widget(w, ev, "bookmark.copy")) {
1112 const iSidebarItem *item = d->contextItem; 1112 const iSidebarItem *item = d->contextItem;
1113 if (d->mode == bookmarks_SidebarMode && item) { 1113 if (d->mode == bookmarks_SidebarMode && item) {
1114 SDL_SetClipboardText(cstr_String(withSpacesEncoded_String(&item->url))); 1114 SDL_SetClipboardText(cstr_String(canonicalUrl_String(&item->url)));
1115 } 1115 }
1116 return iTrue; 1116 return iTrue;
1117 } 1117 }
@@ -1374,7 +1374,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1374 else if (isCommand_Widget(w, ev, "history.copy")) { 1374 else if (isCommand_Widget(w, ev, "history.copy")) {
1375 const iSidebarItem *item = d->contextItem; 1375 const iSidebarItem *item = d->contextItem;
1376 if (item && !isEmpty_String(&item->url)) { 1376 if (item && !isEmpty_String(&item->url)) {
1377 SDL_SetClipboardText(cstr_String(withSpacesEncoded_String(&item->url))); 1377 SDL_SetClipboardText(cstr_String(canonicalUrl_String(&item->url)));
1378 } 1378 }
1379 return iTrue; 1379 return iTrue;
1380 } 1380 }
diff --git a/src/visited.c b/src/visited.c
index f4769e97..94cff492 100644
--- a/src/visited.c
+++ b/src/visited.c
@@ -144,6 +144,7 @@ static size_t find_Visited_(const iVisited *d, const iString *url) {
144 144
145void visitUrl_Visited(iVisited *d, const iString *url, uint16_t visitFlags) { 145void visitUrl_Visited(iVisited *d, const iString *url, uint16_t visitFlags) {
146 if (isEmpty_String(url)) return; 146 if (isEmpty_String(url)) return;
147 url = canonicalUrl_String(url);
147 iVisitedUrl visit; 148 iVisitedUrl visit;
148 init_VisitedUrl(&visit); 149 init_VisitedUrl(&visit);
149 visit.flags = visitFlags; 150 visit.flags = visitFlags;
@@ -165,6 +166,7 @@ void visitUrl_Visited(iVisited *d, const iString *url, uint16_t visitFlags) {
165} 166}
166 167
167void removeUrl_Visited(iVisited *d, const iString *url) { 168void removeUrl_Visited(iVisited *d, const iString *url) {
169 url = canonicalUrl_String(url);
168 iGuardMutex(d->mtx, { 170 iGuardMutex(d->mtx, {
169 size_t pos = find_Visited_(d, url); 171 size_t pos = find_Visited_(d, url);
170 if (pos < size_SortedArray(&d->visited)) { 172 if (pos < size_SortedArray(&d->visited)) {