diff --git a/main/preload.ts b/main/preload.ts index 0e4892842..30b96bb22 100644 --- a/main/preload.ts +++ b/main/preload.ts @@ -117,6 +117,10 @@ const ipc = { ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, link); }, + openDataURL(link: string, filename: string) { + ipcRenderer.send(IPC_MESSAGES.OPEN_DATA_URL, { link, filename }); + }, + async deleteFile(filePath: string) { return (await ipcRenderer.invoke( IPC_ACTIONS.DELETE_FILE, diff --git a/main/registerIpcMainMessageListeners.ts b/main/registerIpcMainMessageListeners.ts index 909bc38cf..802d3c6b1 100644 --- a/main/registerIpcMainMessageListeners.ts +++ b/main/registerIpcMainMessageListeners.ts @@ -1,8 +1,48 @@ -import { ipcMain, Menu, shell } from 'electron'; +import { ipcMain, Menu, shell, app } from 'electron'; +import fs from 'fs'; +import path from 'path'; import { Main } from '../main'; import { IPC_MESSAGES } from '../utils/messages'; import { emitMainProcessError } from 'backend/helpers'; +type DataURLParseResult = { + mediaType: string; + contentType: string; + base64: string; + data: string; + encoding: string; + buffer: Buffer; +}; +function parseDataURL(url: string): null | DataURLParseResult { + const regex = + /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z0-9-.!#$%*+.{}|~`]+=[a-z0-9-.!#$%*+.{}()_|~`]+)*)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s<>]*?)$/i; + + const parts = url.trim().match(regex); + if (!parts) return null; + + const mediaType = (parts[1] || 'text/plain;charset=us-ascii').toLowerCase(); + + const mediaTypeParts: string[] = mediaType + .split(';') + .map((x: string) => x.toLowerCase()); + const contentType = mediaTypeParts[0]; + + mediaTypeParts.slice(1).forEach((attribute) => { + const p = attribute.split('='); + if (p.length >= 2) (parsed as object)[p[0]] = p[1]; + }); + + const base64 = !!parts[parts.length - 2]; + const data = parts[parts.length - 1] || ''; + const encoding = base64 ? 'base64' : 'utf8'; + const buffer = Buffer.from( + base64 ? data : decodeURIComponent(data), + encoding + ); + + return { mediaType, contentType, base64, data, encoding, buffer }; +} + export default function registerIpcMainMessageListeners(main: Main) { ipcMain.on(IPC_MESSAGES.OPEN_MENU, (event) => { if (event.sender === null) { @@ -49,6 +89,22 @@ export default function registerIpcMainMessageListeners(main: Main) { shell.openExternal(link).catch((err) => emitMainProcessError(err)); }); + ipcMain.on( + IPC_MESSAGES.OPEN_DATA_URL, + (_, { link, filename }: { link: string; filename: string }) => { + const data = parseDataURL(link); + if (data) { + const s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const temp = Array.from(Array(16), () => + s.charAt(Math.floor(Math.random() * s.length)) + ).join(''); + const filepath = path.join(app.getPath('temp'), `${temp} ${filename}`); + fs.writeFileSync(filepath, data.buffer); + void shell.openPath(filepath); + } + } + ); + ipcMain.on(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, (_, filePath: string) => { return shell.showItemInFolder(filePath); }); diff --git a/src/components/Controls/Attachment.vue b/src/components/Controls/Attachment.vue index 06d355a2a..58219cecf 100644 --- a/src/components/Controls/Attachment.vue +++ b/src/components/Controls/Attachment.vue @@ -33,6 +33,14 @@ /> + + +