History

fix(server): canonicalise media paths the same way their callers do (#9575)

Julius Marminge committed 2 months ago

Commit 5f4c7161 parent 912276b3 files · +22 −11Browse files

Unified · Split

3 files
  1. apps/server/src/assets/AssetAccess.test.ts +2 −2
  2. apps/server/src/assets/MediaFile.ts +10 −1
  3. apps/server/src/project/ProjectFaviconResolver.test.ts +10 −8
apps/server/src/assets/AssetAccess.test.ts+2−2
@@ -754,7 +754,7 @@ describe("AssetAccess", () => { · 753 unmodified lines
754754 projectFaviconPath: "brand/custom.svg",
755755 });
756756
757− expect(result.sourcePath).toBe("brand/custom.svg");
757+ expect(result.sourcePath).toBe(path.join("brand", "custom.svg"));
758758 expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-custom\.svg$/);
759759 }).pipe(Effect.provide(testLayer)),
760760 );
@@ -813,7 +813,7 @@ describe("AssetAccess", () => { · 52 unmodified lines
813813 projectFaviconPath: "brand/saved.svg",
814814 });
815815
816− expect(result.sourcePath).toBe("brand/saved.svg");
816+ expect(result.sourcePath).toBe(path.join("brand", "saved.svg"));
817817 expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-saved\.svg$/);
818818 }).pipe(Effect.provide(testLayer)),
819819 );
apps/server/src/assets/MediaFile.ts+10−1
@@ -37,6 +37,11 @@ export interface OpenMediaFile { · 36 unmodified lines
3737 readonly info: NodeFS.BigIntStats;
3838 }
3939
40+const realpathLikeFileSystem = (filePath: string) =>
41+ new Promise<string>((resolve, reject) => {
42+ NodeFS.realpath(filePath, (error, resolved) => (error ? reject(error) : resolve(resolved)));
43+ });
44+
4045 /** Opens a canonical media path once. Replacements cannot change the response's source. */
4146 export const openMediaFile = Effect.fn("openMediaFile")(function* (
4247 filePath: string,
@@ -71,7 +76,11 @@ export const openMediaFile = Effect.fn("openMediaFile")(function* ( · 28 unmodified lines
7176 ) {
7277 return null;
7378 }
74− if ((await NodeFSP.realpath(filePath)) !== filePath) return null;
79+ // Callers canonicalise with Effect's FileSystem.realPath, which is
80+ // Node's JS realpath. fs/promises.realpath is the native binding and
81+ // on Windows also expands 8.3 short names, so a path that is already
82+ // canonical by the caller's rules would still look swapped here.
83+ if ((await realpathLikeFileSystem(filePath)) !== filePath) return null;
7584 const after = await NodeFSP.lstat(filePath, { bigint: true });
7685 if (!after.isFile() || info.dev !== after.dev || info.ino !== after.ino) return null;
7786 accepted = true;
apps/server/src/project/ProjectFaviconResolver.test.ts+10−8
@@ -126,6 +126,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 125 unmodified lines
126126 it.effect("prefers a t3.json iconPath over well-known files", () =>
127127 Effect.gen(function* () {
128128 const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
129+ const path = yield* Path.Path;
129130 const cwd = yield* makeTempDir;
130131 yield* writeTextFile(cwd, "t3.json", '{ "iconPath": "brand/mark.svg" }');
131132 yield* writeTextFile(cwd, "brand/mark.svg", "<svg>mark</svg>");
@@ -134,13 +135,14 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 2 unmodified lines
134135 const resolved = yield* resolver.resolvePath(cwd);
135136
136137 expect(resolved).not.toBeNull();
137− expect(resolved).toContain("brand/mark.svg");
138+ expect(resolved).toBe(path.join(cwd, "brand", "mark.svg"));
138139 }),
139140 );
140141
141142 it.effect("uses a saved project favicon override", () =>
142143 Effect.gen(function* () {
143144 const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
145+ const path = yield* Path.Path;
144146 const cwd = yield* makeTempDir;
145147 yield* writeTextFile(cwd, "brand/custom.svg", "<svg>custom</svg>");
146148 yield* writeTextFile(cwd, "favicon.svg", "<svg>automatic</svg>");
@@ -148,7 +150,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 1 unmodified line
148150 const resolved = yield* resolver.resolvePath(cwd, "brand/custom.svg");
149151
150152 expect(resolved).not.toBeNull();
151− expect(resolved).toContain("brand/custom.svg");
153+ expect(resolved).toBe(path.join(cwd, "brand", "custom.svg"));
152154 }),
153155 );
154156
@@ -232,7 +234,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 77 unmodified lines
232234 const resolved = yield* resolver.resolvePath(cwd);
233235
234236 expect(resolved).not.toBeNull();
235− expect(resolved).toContain("public/brand/logo.svg");
237+ expect(resolved).toBe((yield* Path.Path).join(cwd, "public", "brand", "logo.svg"));
236238 }),
237239 );
238240
@@ -257,7 +259,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 18 unmodified lines
257259 const resolved = yield* resolver.resolvePath(cwd);
258260
259261 expect(resolved).not.toBeNull();
260− expect(resolved).toContain("public/brand/logo.svg");
262+ expect(resolved).toBe((yield* Path.Path).join(cwd, "public", "brand", "logo.svg"));
261263 }),
262264 );
263265
@@ -275,7 +277,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 11 unmodified lines
275277 const resolved = yield* resolver.resolvePath(cwd);
276278
277279 expect(resolved).not.toBeNull();
278− expect(resolved).toContain("public/brand/logo.svg");
280+ expect(resolved).toBe((yield* Path.Path).join(cwd, "public", "brand", "logo.svg"));
279281 }),
280282 );
281283
@@ -293,7 +295,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 11 unmodified lines
293295 const resolved = yield* resolver.resolvePath(cwd);
294296
295297 expect(resolved).not.toBeNull();
296− expect(resolved).toContain("public/brand/logo.svg");
298+ expect(resolved).toBe((yield* Path.Path).join(cwd, "public", "brand", "logo.svg"));
297299 }),
298300 );
299301
@@ -311,7 +313,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 11 unmodified lines
311313 const resolved = yield* resolver.resolvePath(cwd);
312314
313315 expect(resolved).not.toBeNull();
314− expect(resolved).toContain("public/brand/logo.svg");
316+ expect(resolved).toBe((yield* Path.Path).join(cwd, "public", "brand", "logo.svg"));
315317 }),
316318 );
317319
@@ -459,7 +461,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 141 unmodified lines
459461 const resolved = yield* resolver.resolvePath(cwd);
460462
461463 expect(resolved).not.toBeNull();
462− expect(resolved).toContain("public/brand/logo.svg");
464+ expect(resolved).toBe((yield* Path.Path).join(cwd, "public", "brand", "logo.svg"));
463465 }),
464466 );
465467 });

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t