fix: deduplicate JS escape helper + add clipboard payload size limit
- Remove duplicate escapeForJSInsert, reuse escapeForJSString from content_injector.go (same package) - Add 10MB size limit on /api/clipboard-set to prevent memory exhaustion
- commit
- 5023d63f5938d664fedd6de3522b0c4f99e85d4b
- author
- brice · · 2026-03-25 06:45 UTC
- committer
- brice · · 2026-03-28 04:39 UTC
- parent
- 48e3ed9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +8 -12
M internal/infrastructure/cef/input_bridge.go +2 -12
| @@ -2,7 +2,6 @@ package cef | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | - | "strings" | |
| 6 | 5 | "sync" | |
| 7 | 6 | ||
| 8 | 7 | purecef "github.com/bnema/purego-cef/cef" | |
| @@ -463,7 +462,7 @@ func (ib *inputBridge) pasteFromClipboard() { | |||
| 463 | 462 | } | |
| 464 | 463 | ||
| 465 | 464 | // Escape the text for embedding in a JS string literal. | |
| 466 | - | escaped := escapeForJSInsert(text) | |
| 465 | + | escaped := escapeForJSString(text) | |
| 467 | 466 | js := "document.execCommand('insertText',false,'" + escaped + "')" | |
| 468 | 467 | frame.ExecuteJavaScript(js, "", 0) | |
| 469 | 468 | log.Debug().Msg("cef: paste JS executed") | |
| @@ -473,16 +472,7 @@ func (ib *inputBridge) pasteFromClipboard() { | |||
| 473 | 472 | log.Debug().Msg("cef: ReadTextAsync dispatched") | |
| 474 | 473 | } | |
| 475 | 474 | ||
| 476 | - | // escapeForJSInsert escapes text for safe embedding in a JS single-quoted string. | |
| 477 | - | func escapeForJSInsert(s string) string { | |
| 478 | - | s = strings.ReplaceAll(s, "\\", "\\\\") | |
| 479 | - | s = strings.ReplaceAll(s, "'", "\\'") | |
| 480 | - | s = strings.ReplaceAll(s, "\n", "\\n") | |
| 481 | - | s = strings.ReplaceAll(s, "\r", "\\r") | |
| 482 | - | s = strings.ReplaceAll(s, "\u2028", "\\u2028") | |
| 483 | - | s = strings.ReplaceAll(s, "\u2029", "\\u2029") | |
| 484 | - | return s | |
| 485 | - | } | |
| 475 | + | // escapeForJSString is defined in content_injector.go — reused here. | |
| 486 | 476 | ||
| 487 | 477 | // --------------------------------------------------------------------------- | |
| 488 | 478 | // Translation helpers | |
| @@ -2,7 +2,6 @@ package cef | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | - | "strings" | |||
| 6 | "sync" | 5 | "sync" | ||
| 7 | 6 | ||||
| 8 | purecef "github.com/bnema/purego-cef/cef" | 7 | purecef "github.com/bnema/purego-cef/cef" | ||
| @@ -463,7 +462,7 @@ func (ib *inputBridge) pasteFromClipboard() { | |||||
| 463 | } | 462 | } | ||
| 464 | 463 | ||||
| 465 | // Escape the text for embedding in a JS string literal. | 464 | // Escape the text for embedding in a JS string literal. | ||
| 466 | - | escaped := escapeForJSInsert(text) | 465 | + | escaped := escapeForJSString(text) |
| 467 | js := "document.execCommand('insertText',false,'" + escaped + "')" | 466 | js := "document.execCommand('insertText',false,'" + escaped + "')" | ||
| 468 | frame.ExecuteJavaScript(js, "", 0) | 467 | frame.ExecuteJavaScript(js, "", 0) | ||
| 469 | log.Debug().Msg("cef: paste JS executed") | 468 | log.Debug().Msg("cef: paste JS executed") | ||
| @@ -473,16 +472,7 @@ func (ib *inputBridge) pasteFromClipboard() { | |||||
| 473 | log.Debug().Msg("cef: ReadTextAsync dispatched") | 472 | log.Debug().Msg("cef: ReadTextAsync dispatched") | ||
| 474 | } | 473 | } | ||
| 475 | 474 | ||||
| 476 | - | // escapeForJSInsert escapes text for safe embedding in a JS single-quoted string. | 475 | + | // escapeForJSString is defined in content_injector.go — reused here. |
| 477 | - | func escapeForJSInsert(s string) string { | |||
| 478 | - | s = strings.ReplaceAll(s, "\\", "\\\\") | |||
| 479 | - | s = strings.ReplaceAll(s, "'", "\\'") | |||
| 480 | - | s = strings.ReplaceAll(s, "\n", "\\n") | |||
| 481 | - | s = strings.ReplaceAll(s, "\r", "\\r") | |||
| 482 | - | s = strings.ReplaceAll(s, "\u2028", "\\u2028") | |||
| 483 | - | s = strings.ReplaceAll(s, "\u2029", "\\u2029") | |||
| 484 | - | return s | |||
| 485 | - | } | |||
| 486 | 476 | ||||
| 487 | // --------------------------------------------------------------------------- | 477 | // --------------------------------------------------------------------------- | ||
| 488 | // Translation helpers | 478 | // Translation helpers | ||
M internal/infrastructure/cef/scheme_handler.go +6 -0
| @@ -216,6 +216,8 @@ func (h *dumbSchemeHandler) handleConfigAPI(cfg *config.Config) purecef.Resource | |||
| 216 | 216 | ||
| 217 | 217 | // handleClipboardSet receives copied text from JS copy/cut events and writes | |
| 218 | 218 | // it to the system clipboard via the engine callback. | |
| 219 | + | const maxClipboardBytes = 10 << 20 // 10 MB | |
| 220 | + | ||
| 219 | 221 | func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler { | |
| 220 | 222 | h.logger.Debug().Msg("cef: /api/clipboard-set request received") | |
| 221 | 223 | ||
| @@ -224,6 +226,10 @@ func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef. | |||
| 224 | 226 | h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)") | |
| 225 | 227 | return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"}) | |
| 226 | 228 | } | |
| 229 | + | if len(body) > maxClipboardBytes { | |
| 230 | + | h.logger.Warn().Int("body_len", len(body)).Msg("cef: clipboard-set — payload too large") | |
| 231 | + | return h.newJSONResourceHandler(http.StatusRequestEntityTooLarge, map[string]string{"error": "payload too large"}) | |
| 232 | + | } | |
| 227 | 233 | ||
| 228 | 234 | var payload struct { | |
| 229 | 235 | Text string `json:"text"` | |
| @@ -216,6 +216,8 @@ func (h *dumbSchemeHandler) handleConfigAPI(cfg *config.Config) purecef.Resource | |||||
| 216 | 216 | ||||
| 217 | // handleClipboardSet receives copied text from JS copy/cut events and writes | 217 | // handleClipboardSet receives copied text from JS copy/cut events and writes | ||
| 218 | // it to the system clipboard via the engine callback. | 218 | // it to the system clipboard via the engine callback. | ||
| 219 | + | const maxClipboardBytes = 10 << 20 // 10 MB | |||
| 220 | + | ||||
| 219 | func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler { | 221 | func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler { | ||
| 220 | h.logger.Debug().Msg("cef: /api/clipboard-set request received") | 222 | h.logger.Debug().Msg("cef: /api/clipboard-set request received") | ||
| 221 | 223 | ||||
| @@ -224,6 +226,10 @@ func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef. | |||||
| 224 | h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)") | 226 | h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)") | ||
| 225 | return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"}) | 227 | return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"}) | ||
| 226 | } | 228 | } | ||
| 229 | + | if len(body) > maxClipboardBytes { | |||
| 230 | + | h.logger.Warn().Int("body_len", len(body)).Msg("cef: clipboard-set — payload too large") | |||
| 231 | + | return h.newJSONResourceHandler(http.StatusRequestEntityTooLarge, map[string]string{"error": "payload too large"}) | |||
| 232 | + | } | |||
| 227 | 233 | ||||
| 228 | var payload struct { | 234 | var payload struct { | ||
| 229 | Text string `json:"text"` | 235 | Text string `json:"text"` | ||