-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpreload.js
More file actions
97 lines (84 loc) · 4.2 KB
/
Copy pathpreload.js
File metadata and controls
97 lines (84 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
const { contextBridge, ipcRenderer, webFrame } = require('electron');
const { marked } = require('marked');
const { markedHighlight } = require('marked-highlight');
const hljs = require('highlight.js');
// Configure marked with syntax highlighting
marked.use(markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return hljs.highlightAuto(code).value;
}
}));
marked.use({ gfm: true, breaks: false });
/**
* Sanitize HTML to remove XSS vectors before setting innerHTML.
* Strips: <script>, <iframe>, <object>, <embed>, <form>, inline event handlers (on*=),
* javascript: hrefs, and data: URIs in src/href/action attributes.
*
* This is a defense-in-depth measure. The primary protection is the CSP in index.html
* (script-src 'self') which already blocks inline scripts and external scripts.
* This sanitizer catches edge cases like javascript: links and on* handlers that
* the CSP does not fully address.
*/
function sanitizeHtml(html) {
// Remove <script ...>...</script> blocks (including noscript, iframe, object, embed, form)
let s = html.replace(/<(script|noscript|iframe|object|embed|form)[\s\S]*?<\/\1>/gi, '');
// Remove self-closing or void dangerous tags
s = s.replace(/<(script|iframe|object|embed|form)[^>]*\/?>/gi, '');
// Strip inline event handlers: on[event]="..." or on[event]='...' or on[event]=value
s = s.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '');
// Strip javascript: and vbscript: in href/src/action/formaction attributes
s = s.replace(/(href|src|action|formaction)\s*=\s*["']?\s*(?:javascript|vbscript)\s*:[^"'>]*/gi, '$1="#"');
// Strip data: URIs in src/href (allow data: in img only via CSP img-src data:, but strip from <a> etc)
s = s.replace(/(<a[^>]*)\s+href\s*=\s*["']?\s*data:[^"'>]*/gi, '$1 href="#"');
return s;
}
contextBridge.exposeInMainWorld('mdFloat', {
// Environment
homePath: process.env.HOME || '/home',
// File operations
openFileDialog: () => ipcRenderer.invoke('open-file-dialog'),
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
getRecentFiles: () => ipcRenderer.invoke('get-recent-files'),
watchFile: (filePath) => ipcRenderer.send('watch-file', filePath),
unwatchFile: (filePath) => ipcRenderer.send('unwatch-file', filePath),
// File events from main process
onFileOpened: (callback) => ipcRenderer.on('file-opened', (_, data) => callback(data)),
onFileChanged: (callback) => ipcRenderer.on('file-changed', (_, data) => callback(data)),
onOpenError: (callback) => ipcRenderer.on('open-error', (_, data) => callback(data)),
// Theme
onThemeConfig: (callback) => ipcRenderer.on('theme-config', (_, config) => callback(config)),
setTheme: (theme) => ipcRenderer.send('set-theme', theme),
getTheme: () => ipcRenderer.invoke('get-theme'),
detectTerminalColors: () => ipcRenderer.invoke('detect-terminal-colors'),
// Window controls
onPinState: (callback) => ipcRenderer.on('pin-state', (_, state) => callback(state)),
togglePin: () => ipcRenderer.send('toggle-pin'),
minimizeWindow: () => ipcRenderer.send('minimize-window'),
maximizeWindow: () => ipcRenderer.send('maximize-window'),
closeWindow: () => ipcRenderer.send('close-window'),
// External links
openExternal: (url) => ipcRenderer.invoke('open-external', url),
// Zoom
zoomIn: () => { webFrame.setZoomLevel(webFrame.getZoomLevel() + 0.5); return webFrame.getZoomLevel(); },
zoomOut: () => { webFrame.setZoomLevel(webFrame.getZoomLevel() - 0.5); return webFrame.getZoomLevel(); },
zoomReset: () => { webFrame.setZoomLevel(0); return 0; },
getZoomLevel: () => webFrame.getZoomLevel(),
setZoomLevel: (level) => webFrame.setZoomLevel(level),
// LLM rewrite for speech
llmRewrite: (text) => ipcRenderer.invoke('llm-rewrite', text),
// TTS
ttsSpeak: (text) => ipcRenderer.invoke('tts-speak', text),
ttsStop: () => ipcRenderer.send('tts-stop'),
// Rendering
renderMarkdown: (text) => sanitizeHtml(marked.parse(text)),
highlightCode: (code, lang) => {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return hljs.highlightAuto(code).value;
}
});