Fixes #1049 centralizes page URL encoding and decoding

pull/1054/head
Zef Hemel 2024-08-20 09:38:56 +02:00
parent 777a59e753
commit b8081d970c
10 changed files with 75 additions and 19 deletions

View File

@ -1,6 +1,7 @@
import type { SpacePrimitives } from "./space_primitives.ts";
import type { FileMeta } from "../../plug-api/types.ts";
import { flushCachesAndUnregisterServiceWorker } from "../sw_util.ts";
import { encodePageURI } from "@silverbulletmd/silverbullet/lib/page_ref";
const defaultFetchTimeout = 30000; // 30 seconds
@ -110,7 +111,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
name: string,
): Promise<{ data: Uint8Array; meta: FileMeta }> {
const res = await this.authenticatedFetch(
`${this.url}/${encodeURIComponent(name)}`,
`${this.url}/${encodePageURI(name)}`,
{
method: "GET",
headers: {
@ -144,7 +145,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
}
const res = await this.authenticatedFetch(
`${this.url}/${encodeURIComponent(name)}`,
`${this.url}/${encodePageURI(name)}`,
{
method: "PUT",
headers,
@ -157,7 +158,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
async deleteFile(name: string): Promise<void> {
const req = await this.authenticatedFetch(
`${this.url}/${encodeURIComponent(name)}`,
`${this.url}/${encodePageURI(name)}`,
{
method: "DELETE",
},
@ -169,7 +170,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
async getFileMeta(name: string): Promise<FileMeta> {
const res = await this.authenticatedFetch(
`${this.url}/${encodeURIComponent(name)}`,
`${this.url}/${encodePageURI(name)}`,
// This used to use HEAD, but it seems that Safari on iOS is blocking cookies/credentials to be sent along with HEAD requests
// so we'll use GET instead with a magic header which the server may or may not use to omit the body.
{

View File

@ -1,5 +1,16 @@
import { encodePageRef, parsePageRef, validatePageName } from "./page_ref.ts";
import { assertEquals, AssertionError, assertThrows } from "@std/assert";
import {
decodePageURI,
encodePageRef,
encodePageURI,
parsePageRef,
validatePageName,
} from "./page_ref.ts";
import {
assert,
assertEquals,
AssertionError,
assertThrows,
} from "@std/assert";
Deno.test("Page utility functions", () => {
// Base cases
@ -79,3 +90,15 @@ Deno.test("Page utility functions", () => {
);
}
});
Deno.test("Page URI encoding", () => {
assertEquals(encodePageURI("foo"), "foo");
assertEquals(encodePageURI("folder/foo"), "folder/foo");
assertEquals(encodePageURI("hello there"), "hello%20there");
assertEquals(encodePageURI("hello?there"), "hello%3Fthere");
// Now ensure all these cases are reversible
assertEquals(decodePageURI("foo"), "foo");
assertEquals(decodePageURI("folder/foo"), "folder/foo");
assertEquals(decodePageURI("hello%20there"), "hello there");
assertEquals(decodePageURI("hello%3Fthere"), "hello?there");
});

View File

@ -130,3 +130,21 @@ export function positionOfLine(
);
return targetPos - targetLine.length + columnOffset;
}
/**
* Encodes a page name for use in a URI. Basically does encodeURIComponent, but puts slashes back in place.
* @param page page name to encode
* @returns
*/
export function encodePageURI(page: string): string {
return encodeURIComponent(page).replace(/%2F/g, "/");
}
/**
* Decodes a page name from a URI.
* @param page page name to decode
* @returns
*/
export function decodePageURI(page: string): string {
return decodeURIComponent(page);
}

View File

@ -5,6 +5,7 @@ import {
maximumAttachmentSize,
} from "../../web/constants.ts";
import { resolvePath } from "@silverbulletmd/silverbullet/lib/resolve";
import { encodePageURI } from "@silverbulletmd/silverbullet/lib/page_ref";
export async function saveFile(file: UploadFile) {
const maxSize = await system.getSpaceConfig(
@ -46,7 +47,7 @@ export async function saveFile(file: UploadFile) {
if (linkStyle === "wikilink") {
attachmentMarkdown = `[[${attachmentPath}]]`;
} else {
attachmentMarkdown = `[${finalFileName}](${encodeURI(finalFileName)})`;
attachmentMarkdown = `[${finalFileName}](${encodePageURI(finalFileName)})`;
}
if (file.contentType.startsWith("image/")) {
attachmentMarkdown = "!" + attachmentMarkdown;

View File

@ -7,7 +7,10 @@ import {
import { findNodeOfType, renderToText } from "../../plug-api/lib/tree.ts";
import { replaceNodesMatching } from "../../plug-api/lib/tree.ts";
import type { ParseTree } from "../../plug-api/lib/tree.ts";
import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import {
encodePageURI,
parsePageRef,
} from "@silverbulletmd/silverbullet/lib/page_ref";
type ShareOption = {
id: string;
@ -85,7 +88,7 @@ export function cleanMarkdown(tree: ParseTree): ParseTree {
return {
text: `[${linkText}](${
typeof location !== "undefined" ? location.origin : ""
}/${encodeURI(pageRef.page)})`,
}/${encodePageURI(pageRef.page)})`,
};
}
case "NamedAnchor":

View File

@ -15,6 +15,7 @@ import { extendedMarkdownLanguage } from "$common/markdown_parser/parser.ts";
import { parse } from "$common/markdown_parser/parse_tree.ts";
import { renderMarkdownToHtml } from "../plugs/markdown/markdown_render.ts";
import {
decodePageURI,
looksLikePathWithExtension,
parsePageRef,
} from "@silverbulletmd/silverbullet/lib/page_ref";
@ -149,7 +150,7 @@ export class HttpServer {
// Fallback, serve the UI index.html
this.app.use("*", (c) => {
const url = new URL(c.req.url);
const pageName = decodeURIComponent(url.pathname.slice(1));
const pageName = decodePageURI(url.pathname.slice(1));
return this.renderHtmlPage(this.spaceServer, pageName, c);
});

View File

@ -41,6 +41,7 @@ import { FallbackSpacePrimitives } from "$common/spaces/fallback_space_primitive
import { FilteredSpacePrimitives } from "$common/spaces/filtered_space_primitives.ts";
import {
encodePageRef,
encodePageURI,
validatePageName,
} from "@silverbulletmd/silverbullet/lib/page_ref";
import { ClientSystem } from "./client_system.ts";
@ -1015,10 +1016,10 @@ export class Client implements ConfigContainer {
if (newWindow) {
console.log(
"Navigating to new page in new window",
`${location.origin}/${encodeURIComponent(encodePageRef(pageRef))}`,
`${location.origin}/${encodePageURI(encodePageRef(pageRef))}`,
);
const win = globalThis.open(
`${location.origin}/${encodeURIComponent(encodePageRef(pageRef))}`,
`${location.origin}/${encodePageURI(encodePageRef(pageRef))}`,
"_blank",
);
if (win) {

View File

@ -6,6 +6,7 @@ import { decoratorStateField, isCursorInRange, LinkWidget } from "./util.ts";
import { resolvePath } from "@silverbulletmd/silverbullet/lib/resolve";
import {
encodePageRef,
encodePageURI,
parsePageRef,
} from "@silverbulletmd/silverbullet/lib/page_ref";
@ -101,7 +102,7 @@ export function cleanWikiLinkPlugin(client: Client) {
title: fileExists
? `Navigate to ${encodePageRef(pageRef)}`
: `Create ${pageRef.page}`,
href: `/${encodeURIComponent(encodePageRef(pageRef))}`,
href: `/${encodePageURI(encodePageRef(pageRef))}`,
cssClass,
from,
callback: (e) => {

View File

@ -1,4 +1,8 @@
import { type PageRef, parsePageRef } from "../plug-api/lib/page_ref.ts";
import {
encodePageURI,
type PageRef,
parsePageRef,
} from "../plug-api/lib/page_ref.ts";
import type { Client } from "./client.ts";
import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve";
import { renderTheTemplate } from "$common/syscalls/template.ts";
@ -57,18 +61,18 @@ export class PathPageNavigator {
window.history.replaceState(
cleanState,
"",
`/${encodeURIComponent(currentState.page)}`,
`/${encodePageURI(currentState.page)}`,
);
window.history.pushState(
pageRef,
"",
`/${encodeURIComponent(pageRef.page)}`,
`/${encodePageURI(pageRef.page)}`,
);
} else {
window.history.replaceState(
pageRef,
"",
`/${encodeURIComponent(pageRef.page)}`,
`/${encodePageURI(pageRef.page)}`,
);
}
globalThis.dispatchEvent(

View File

@ -2,7 +2,10 @@ import type { FileContent } from "$common/spaces/datastore_space_primitives.ts";
import { simpleHash } from "$lib/crypto.ts";
import { DataStore } from "$lib/data/datastore.ts";
import { IndexedDBKvPrimitives } from "$lib/data/indexeddb_kv_primitives.ts";
import { looksLikePathWithExtension } from "@silverbulletmd/silverbullet/lib/page_ref";
import {
decodePageURI,
looksLikePathWithExtension,
} from "@silverbulletmd/silverbullet/lib/page_ref";
const CACHE_NAME = "{{CACHE_NAME}}_{{CONFIG_HASH}}";
@ -124,7 +127,7 @@ async function handleLocalFileRequest(
request: Request,
pathname: string,
): Promise<Response> {
const path = decodeURIComponent(pathname.slice(1));
const path = decodePageURI(pathname.slice(1));
const data = await ds?.get<FileContent>([...filesContentPrefix, path]);
if (data) {
// console.log("Serving from space", path);