Skip to content

Commit e177eb4

Browse files
Attachments: resolve windows paths issue
1 parent 310c23c commit e177eb4

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

application/client/src/app/ui/views/sidebar/attachments/preview/image/component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export class Preview extends ChangesDetector implements AfterViewInit, AfterCont
199199
closable: false,
200200
},
201201
);
202-
new URLFileReader(`attachment://${this.attachment.filepath}`)
202+
new URLFileReader(`attachment://${encodeURIComponent(this.attachment.filepath)}`)
203203
.read('blob')
204204
.then((response) => {
205205
if (!(response instanceof Blob)) {
@@ -225,7 +225,7 @@ export class Preview extends ChangesDetector implements AfterViewInit, AfterCont
225225
}
226226

227227
protected update() {
228-
this.url = `attachment://${this.attachment.filepath}`;
228+
this.url = `attachment://${encodeURIComponent(this.attachment.filepath)}`;
229229
}
230230
}
231231
export interface Preview extends IlcInterface {}

application/client/src/app/ui/views/sidebar/attachments/preview/text/component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class Preview extends ChangesDetector implements AfterContentInit {
8989
protected update() {
9090
this.reading = true;
9191
this.detectChanges();
92-
new URLFileReader(`attachment://${this.attachment.filepath}`)
92+
new URLFileReader(`attachment://${encodeURIComponent(this.attachment.filepath)}`)
9393
.read()
9494
.then((response) => {
9595
if (typeof response !== 'string') {

application/holder/src/service/electron/protocol.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,47 @@ import { protocol } from 'electron';
33
import * as url from 'url';
44
import * as fs from 'fs';
55

6+
/// The primary communication channel between the renderer and the backend is IPC.
7+
/// However, attachments are delivered via direct links. For security reasons these
8+
/// links are URL‑encoded, which is especially important to safely carry Windows
9+
/// paths with backslashes.
610
export class Protocol {
11+
/**
12+
* Registers the custom `attachment:` scheme handler in the Electron main process.
13+
*
14+
* The handler serves local files referenced by `attachment://` URLs.
15+
* See {@link Protocol.attachment}.
16+
*
17+
* @remarks
18+
* This uses `protocol.handle` (Electron 20+) and binds the instance method as the
19+
* request handler.
20+
*/
721
public register() {
822
protocol.handle('attachment', this.attachment.bind(this));
923
}
1024

25+
/**
26+
* Attachment handler that resolves an `attachment:` URL to a local file and
27+
* returns it as a `Response`.
28+
*
29+
* @param request - Incoming request targeting the `attachment:` scheme.
30+
* @returns A promise that resolves to:
31+
* - `200 OK` with the file content in the body when the file exists and can be read;
32+
* - `404 Not Found` when the file cannot be read (e.g., missing or inaccessible).
33+
*
34+
* @remarks
35+
* - The URL is first decoded (with `decodeURIComponent`) and converted to a `file:`
36+
* URL, then to a filesystem path via `url.fileURLToPath`. This preserves Windows
37+
* paths that contain backslashes and disallows accidental scheme confusion.
38+
* - If the path does not exist, the method rejects the promise with an Error.
39+
* Any read errors are converted into a `404` HTTP-like response.
40+
* - Do not call `fetch(file://…)` in Node/Electron; Node’s `fetch` (undici)
41+
* does not implement the `file:` scheme. Read the file directly, as below.
42+
*/
1143
protected async attachment(request: Request): Promise<Response> {
12-
const path = url.fileURLToPath(new URL(request.url.replace(/^attachment:/, 'file:')));
44+
const path = url.fileURLToPath(
45+
new URL(decodeURIComponent(request.url.replace(/^attachment:/, 'file:'))),
46+
);
1347
if (!fs.existsSync(path)) {
1448
return Promise.reject(new Error(`File doesn't exist`));
1549
}

0 commit comments

Comments
 (0)