summaryrefslogtreecommitdiff
path: root/src/shared/messages.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/messages.js')
-rw-r--r--src/shared/messages.js69
1 files changed, 69 insertions, 0 deletions
diff --git a/src/shared/messages.js b/src/shared/messages.js
new file mode 100644
index 0000000..95a257e
--- /dev/null
+++ b/src/shared/messages.js
@@ -0,0 +1,69 @@
+/**
+ * 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);
+} \ No newline at end of file