/** * Message validation helpers (background side). * * All messages received from content scripts must pass through these * validators before they are used. The page origin is untrusted * (Security.md ยง7): every field is type-checked and length-limited. */ import { CONTENT_TO_BG, MAX_SELECTION_LENGTH } from './constants.js'; /** * Generate a random request id. Uses crypto.randomUUID when available and * falls back to a random-hex string otherwise. * @returns {string} */ export function createRequestId() { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } const bytes = new Uint8Array(16); if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { crypto.getRandomValues(bytes); } else { for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256); } return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); } /** * @param {*} message * @returns {boolean} true when the message is a valid translate request. */ export function isValidTranslateRequest(message) { if (message === null || typeof message !== 'object' || Array.isArray(message)) { return false; } if (message.type !== CONTENT_TO_BG.TRANSLATE) return false; if (typeof message.requestId !== 'string' || message.requestId.length === 0 || message.requestId.length > 128) { return false; } if (typeof message.text !== 'string') return false; if (message.text.length === 0) return false; if (message.text.length > MAX_SELECTION_LENGTH) return false; return true; } /** * @param {*} message * @returns {boolean} true when the message is a valid cancel request. */ export function isValidCancelRequest(message) { if (message === null || typeof message !== 'object' || Array.isArray(message)) { return false; } if (message.type !== CONTENT_TO_BG.CANCEL) return false; return typeof message.requestId === 'string' && message.requestId.length > 0 && message.requestId.length <= 128; } /** * Check whether the sender is a background-trusted extension context * (i.e. a message coming from our own content script, popup or options). * @param {unknown} sender WebExtensions MessageSender * @returns {boolean} */ export function isTrustedSender(sender) { // Only extension-internal senders carry an id; external senders are not // accepted anyway because onMessageExternal is not used. return Boolean(sender && sender.id); }