@@ -3,13 +3,47 @@ import { protocol } from 'electron';
33import * as url from 'url' ;
44import * 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.
610export 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 ( / ^ a t t a c h m e n t : / , 'file:' ) ) ) ;
44+ const path = url . fileURLToPath (
45+ new URL ( decodeURIComponent ( request . url . replace ( / ^ a t t a c h m e n t : / , 'file:' ) ) ) ,
46+ ) ;
1347 if ( ! fs . existsSync ( path ) ) {
1448 return Promise . reject ( new Error ( `File doesn't exist` ) ) ;
1549 }
0 commit comments