Skip to content

Commit 764ac37

Browse files
committed
Fix (webkit) encode URLs with dot in path name to circumvent Safari
overriding headers (fixes opening e.g. PDF files)
1 parent cabe886 commit 764ac37

File tree

1 file changed

+28
-0
lines changed

1 file changed

+28
-0
lines changed

client/spaces/http_space_primitives.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@ import { headersToFileMeta } from "../lib/util.ts";
1212

1313
const defaultFetchTimeout = 30000; // 30 seconds
1414

15+
// WebKit (Safari, WKWebView) strips custom response headers (X-Last-Modified,
16+
// etc.) when it recognizes a file extension in the URL. Encoding the last dot
17+
// as %2E prevents this; the server decodes it transparently.
18+
const isWebKit = typeof navigator !== "undefined" &&
19+
/AppleWebKit/.test(navigator.userAgent) &&
20+
!/Chrome/.test(navigator.userAgent);
21+
22+
function encodeExtensionDot(url: string): string {
23+
// Only encode the file extension dot, not dots in the path prefix (like /.fs).
24+
// Manipulate the raw string — Safari's URL.pathname setter rejects %2E.
25+
const fsIdx = url.indexOf("/.fs/");
26+
if (fsIdx < 0) return url;
27+
const afterFs = fsIdx + 5; // position after "/.fs/"
28+
const filePart = url.substring(afterFs);
29+
const qIdx = filePart.indexOf("?");
30+
const path = qIdx >= 0 ? filePart.substring(0, qIdx) : filePart;
31+
const rest = qIdx >= 0 ? filePart.substring(qIdx) : "";
32+
const lastDot = path.lastIndexOf(".");
33+
if (lastDot > path.lastIndexOf("/")) {
34+
return url.substring(0, afterFs) +
35+
path.substring(0, lastDot) + "%2E" + path.substring(lastDot + 1) + rest;
36+
}
37+
return url;
38+
}
39+
1540
export class HttpSpacePrimitives implements SpacePrimitives {
1641
constructor(
1742
readonly url: string,
@@ -40,6 +65,9 @@ export class HttpSpacePrimitives implements SpacePrimitives {
4065
}
4166

4267
try {
68+
if (isWebKit) {
69+
url = encodeExtensionDot(url);
70+
}
4371
options.signal = AbortSignal.timeout(fetchTimeout);
4472
options.redirect = "manual";
4573
const result = await fetch(url, options);

0 commit comments

Comments
 (0)