Fixes #1049 centralizes page URL encoding and decoding
parent
777a59e753
commit
b8081d970c
|
@ -1,6 +1,7 @@
|
||||||
import type { SpacePrimitives } from "./space_primitives.ts";
|
import type { SpacePrimitives } from "./space_primitives.ts";
|
||||||
import type { FileMeta } from "../../plug-api/types.ts";
|
import type { FileMeta } from "../../plug-api/types.ts";
|
||||||
import { flushCachesAndUnregisterServiceWorker } from "../sw_util.ts";
|
import { flushCachesAndUnregisterServiceWorker } from "../sw_util.ts";
|
||||||
|
import { encodePageURI } from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
|
|
||||||
const defaultFetchTimeout = 30000; // 30 seconds
|
const defaultFetchTimeout = 30000; // 30 seconds
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
||||||
const res = await this.authenticatedFetch(
|
const res = await this.authenticatedFetch(
|
||||||
`${this.url}/${encodeURIComponent(name)}`,
|
`${this.url}/${encodePageURI(name)}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -144,7 +145,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await this.authenticatedFetch(
|
const res = await this.authenticatedFetch(
|
||||||
`${this.url}/${encodeURIComponent(name)}`,
|
`${this.url}/${encodePageURI(name)}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers,
|
headers,
|
||||||
|
@ -157,7 +158,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
||||||
|
|
||||||
async deleteFile(name: string): Promise<void> {
|
async deleteFile(name: string): Promise<void> {
|
||||||
const req = await this.authenticatedFetch(
|
const req = await this.authenticatedFetch(
|
||||||
`${this.url}/${encodeURIComponent(name)}`,
|
`${this.url}/${encodePageURI(name)}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
},
|
},
|
||||||
|
@ -169,7 +170,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
||||||
|
|
||||||
async getFileMeta(name: string): Promise<FileMeta> {
|
async getFileMeta(name: string): Promise<FileMeta> {
|
||||||
const res = await this.authenticatedFetch(
|
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
|
// 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.
|
// so we'll use GET instead with a magic header which the server may or may not use to omit the body.
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
import { encodePageRef, parsePageRef, validatePageName } from "./page_ref.ts";
|
import {
|
||||||
import { assertEquals, AssertionError, assertThrows } from "@std/assert";
|
decodePageURI,
|
||||||
|
encodePageRef,
|
||||||
|
encodePageURI,
|
||||||
|
parsePageRef,
|
||||||
|
validatePageName,
|
||||||
|
} from "./page_ref.ts";
|
||||||
|
import {
|
||||||
|
assert,
|
||||||
|
assertEquals,
|
||||||
|
AssertionError,
|
||||||
|
assertThrows,
|
||||||
|
} from "@std/assert";
|
||||||
|
|
||||||
Deno.test("Page utility functions", () => {
|
Deno.test("Page utility functions", () => {
|
||||||
// Base cases
|
// 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");
|
||||||
|
});
|
||||||
|
|
|
@ -130,3 +130,21 @@ export function positionOfLine(
|
||||||
);
|
);
|
||||||
return targetPos - targetLine.length + columnOffset;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
maximumAttachmentSize,
|
maximumAttachmentSize,
|
||||||
} from "../../web/constants.ts";
|
} from "../../web/constants.ts";
|
||||||
import { resolvePath } from "@silverbulletmd/silverbullet/lib/resolve";
|
import { resolvePath } from "@silverbulletmd/silverbullet/lib/resolve";
|
||||||
|
import { encodePageURI } from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
|
|
||||||
export async function saveFile(file: UploadFile) {
|
export async function saveFile(file: UploadFile) {
|
||||||
const maxSize = await system.getSpaceConfig(
|
const maxSize = await system.getSpaceConfig(
|
||||||
|
@ -46,7 +47,7 @@ export async function saveFile(file: UploadFile) {
|
||||||
if (linkStyle === "wikilink") {
|
if (linkStyle === "wikilink") {
|
||||||
attachmentMarkdown = `[[${attachmentPath}]]`;
|
attachmentMarkdown = `[[${attachmentPath}]]`;
|
||||||
} else {
|
} else {
|
||||||
attachmentMarkdown = `[${finalFileName}](${encodeURI(finalFileName)})`;
|
attachmentMarkdown = `[${finalFileName}](${encodePageURI(finalFileName)})`;
|
||||||
}
|
}
|
||||||
if (file.contentType.startsWith("image/")) {
|
if (file.contentType.startsWith("image/")) {
|
||||||
attachmentMarkdown = "!" + attachmentMarkdown;
|
attachmentMarkdown = "!" + attachmentMarkdown;
|
||||||
|
|
|
@ -7,7 +7,10 @@ import {
|
||||||
import { findNodeOfType, renderToText } from "../../plug-api/lib/tree.ts";
|
import { findNodeOfType, renderToText } from "../../plug-api/lib/tree.ts";
|
||||||
import { replaceNodesMatching } from "../../plug-api/lib/tree.ts";
|
import { replaceNodesMatching } from "../../plug-api/lib/tree.ts";
|
||||||
import type { ParseTree } 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 = {
|
type ShareOption = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -85,7 +88,7 @@ export function cleanMarkdown(tree: ParseTree): ParseTree {
|
||||||
return {
|
return {
|
||||||
text: `[${linkText}](${
|
text: `[${linkText}](${
|
||||||
typeof location !== "undefined" ? location.origin : ""
|
typeof location !== "undefined" ? location.origin : ""
|
||||||
}/${encodeURI(pageRef.page)})`,
|
}/${encodePageURI(pageRef.page)})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "NamedAnchor":
|
case "NamedAnchor":
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { extendedMarkdownLanguage } from "$common/markdown_parser/parser.ts";
|
||||||
import { parse } from "$common/markdown_parser/parse_tree.ts";
|
import { parse } from "$common/markdown_parser/parse_tree.ts";
|
||||||
import { renderMarkdownToHtml } from "../plugs/markdown/markdown_render.ts";
|
import { renderMarkdownToHtml } from "../plugs/markdown/markdown_render.ts";
|
||||||
import {
|
import {
|
||||||
|
decodePageURI,
|
||||||
looksLikePathWithExtension,
|
looksLikePathWithExtension,
|
||||||
parsePageRef,
|
parsePageRef,
|
||||||
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
|
@ -149,7 +150,7 @@ export class HttpServer {
|
||||||
// Fallback, serve the UI index.html
|
// Fallback, serve the UI index.html
|
||||||
this.app.use("*", (c) => {
|
this.app.use("*", (c) => {
|
||||||
const url = new URL(c.req.url);
|
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);
|
return this.renderHtmlPage(this.spaceServer, pageName, c);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { FallbackSpacePrimitives } from "$common/spaces/fallback_space_primitive
|
||||||
import { FilteredSpacePrimitives } from "$common/spaces/filtered_space_primitives.ts";
|
import { FilteredSpacePrimitives } from "$common/spaces/filtered_space_primitives.ts";
|
||||||
import {
|
import {
|
||||||
encodePageRef,
|
encodePageRef,
|
||||||
|
encodePageURI,
|
||||||
validatePageName,
|
validatePageName,
|
||||||
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
import { ClientSystem } from "./client_system.ts";
|
import { ClientSystem } from "./client_system.ts";
|
||||||
|
@ -1015,10 +1016,10 @@ export class Client implements ConfigContainer {
|
||||||
if (newWindow) {
|
if (newWindow) {
|
||||||
console.log(
|
console.log(
|
||||||
"Navigating to new page in new window",
|
"Navigating to new page in new window",
|
||||||
`${location.origin}/${encodeURIComponent(encodePageRef(pageRef))}`,
|
`${location.origin}/${encodePageURI(encodePageRef(pageRef))}`,
|
||||||
);
|
);
|
||||||
const win = globalThis.open(
|
const win = globalThis.open(
|
||||||
`${location.origin}/${encodeURIComponent(encodePageRef(pageRef))}`,
|
`${location.origin}/${encodePageURI(encodePageRef(pageRef))}`,
|
||||||
"_blank",
|
"_blank",
|
||||||
);
|
);
|
||||||
if (win) {
|
if (win) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { decoratorStateField, isCursorInRange, LinkWidget } from "./util.ts";
|
||||||
import { resolvePath } from "@silverbulletmd/silverbullet/lib/resolve";
|
import { resolvePath } from "@silverbulletmd/silverbullet/lib/resolve";
|
||||||
import {
|
import {
|
||||||
encodePageRef,
|
encodePageRef,
|
||||||
|
encodePageURI,
|
||||||
parsePageRef,
|
parsePageRef,
|
||||||
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ export function cleanWikiLinkPlugin(client: Client) {
|
||||||
title: fileExists
|
title: fileExists
|
||||||
? `Navigate to ${encodePageRef(pageRef)}`
|
? `Navigate to ${encodePageRef(pageRef)}`
|
||||||
: `Create ${pageRef.page}`,
|
: `Create ${pageRef.page}`,
|
||||||
href: `/${encodeURIComponent(encodePageRef(pageRef))}`,
|
href: `/${encodePageURI(encodePageRef(pageRef))}`,
|
||||||
cssClass,
|
cssClass,
|
||||||
from,
|
from,
|
||||||
callback: (e) => {
|
callback: (e) => {
|
||||||
|
|
|
@ -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 type { Client } from "./client.ts";
|
||||||
import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve";
|
import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve";
|
||||||
import { renderTheTemplate } from "$common/syscalls/template.ts";
|
import { renderTheTemplate } from "$common/syscalls/template.ts";
|
||||||
|
@ -57,18 +61,18 @@ export class PathPageNavigator {
|
||||||
window.history.replaceState(
|
window.history.replaceState(
|
||||||
cleanState,
|
cleanState,
|
||||||
"",
|
"",
|
||||||
`/${encodeURIComponent(currentState.page)}`,
|
`/${encodePageURI(currentState.page)}`,
|
||||||
);
|
);
|
||||||
window.history.pushState(
|
window.history.pushState(
|
||||||
pageRef,
|
pageRef,
|
||||||
"",
|
"",
|
||||||
`/${encodeURIComponent(pageRef.page)}`,
|
`/${encodePageURI(pageRef.page)}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
window.history.replaceState(
|
window.history.replaceState(
|
||||||
pageRef,
|
pageRef,
|
||||||
"",
|
"",
|
||||||
`/${encodeURIComponent(pageRef.page)}`,
|
`/${encodePageURI(pageRef.page)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
globalThis.dispatchEvent(
|
globalThis.dispatchEvent(
|
||||||
|
|
|
@ -2,7 +2,10 @@ import type { FileContent } from "$common/spaces/datastore_space_primitives.ts";
|
||||||
import { simpleHash } from "$lib/crypto.ts";
|
import { simpleHash } from "$lib/crypto.ts";
|
||||||
import { DataStore } from "$lib/data/datastore.ts";
|
import { DataStore } from "$lib/data/datastore.ts";
|
||||||
import { IndexedDBKvPrimitives } from "$lib/data/indexeddb_kv_primitives.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}}";
|
const CACHE_NAME = "{{CACHE_NAME}}_{{CONFIG_HASH}}";
|
||||||
|
|
||||||
|
@ -124,7 +127,7 @@ async function handleLocalFileRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
pathname: string,
|
pathname: string,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const path = decodeURIComponent(pathname.slice(1));
|
const path = decodePageURI(pathname.slice(1));
|
||||||
const data = await ds?.get<FileContent>([...filesContentPrefix, path]);
|
const data = await ds?.get<FileContent>([...filesContentPrefix, path]);
|
||||||
if (data) {
|
if (data) {
|
||||||
// console.log("Serving from space", path);
|
// console.log("Serving from space", path);
|
||||||
|
|
Loading…
Reference in New Issue