lib/network-origins.js
Rama main · réplica de lectura del repositorio original.
// Shared validation for configured browser origins and WebSocket handshakes.
var MAX_ORIGINS = 100;
var MAX_ORIGIN_LENGTH = 2048;
function invalid(reason) {
var error = new Error(reason);
error.reason = reason;
return error;
}
function normalizeOrigin(value) {
if (typeof value !== "string") throw invalid("must be text, for example https://clay.example.com.");
if (value.length > MAX_ORIGIN_LENGTH) throw invalid("is longer than " + MAX_ORIGIN_LENGTH + " characters.");
if (value.indexOf("*") !== -1) throw invalid("cannot contain wildcards (*). List each address separately.");
if (!/^https?:\/\//i.test(value)) throw invalid("must start with http:// or https://.");
if (!/^https?:\/\/[^\s/?#\\]+\/?$/i.test(value)) {
throw invalid("cannot include a path, query, fragment, or spaces. Use only the scheme, host, and optional port.");
}
var url;
try { url = new URL(value); } catch (e) { throw invalid("is not a valid address. Check the host name and port."); }
if (url.username || url.password) throw invalid("cannot include a username or password.");
return url.origin;
}
function normalizeAllowedOrigins(values) {
if (!Array.isArray(values)) throw invalid("Allowed origins must be a list.");
if (values.length > MAX_ORIGINS) throw invalid("Enter at most " + MAX_ORIGINS + " allowed origins.");
var normalized = [];
for (var i = 0; i < values.length; i++) {
try {
normalized.push(normalizeOrigin(values[i]));
} catch (e) {
var error = invalid("Entry " + (i + 1) + " " + e.reason);
error.index = i;
error.entryReason = e.reason;
throw error;
}
}
return Array.from(new Set(normalized));
}
// Describes stored entries for the editor without dropping invalid manual edits.
function describeConfiguredOrigins(raw) {
if (raw === undefined || raw === null) return { allowedOrigins: [], invalid: [] };
var list = Array.isArray(raw) ? raw : [raw];
var entries = [];
var problems = [];
for (var i = 0; i < list.length; i++) {
var value = list[i];
entries.push(typeof value === "string" ? value : JSON.stringify(value) || String(value));
try { normalizeOrigin(value); } catch (e) { problems.push({ index: i, reason: e.reason }); }
}
if (!Array.isArray(raw)) problems.unshift({ index: 0, reason: "is not stored as a list. Save to store it correctly." });
return { allowedOrigins: entries, invalid: problems };
}
function isAllowedOrigin(origin, host, allowedOrigins) {
// Non-browser clients may omit Origin; authentication is still required.
if (origin === undefined) return true;
try {
var normalized = normalizeOrigin(origin);
var configured = Array.isArray(allowedOrigins) ? allowedOrigins : [];
for (var i = 0; i < configured.length; i++) {
try {
if (normalizeOrigin(configured[i]) === normalized) return true;
} catch (e) { /* Invalid manual config entries never grant access. */ }
}
// Preserve Host-based access through TLS-terminating proxies. Forwarded
// headers are deliberately not trusted; rewritten hosts need an allowlist.
if (typeof host !== "string" || !host.trim()) return false;
var protocol = new URL(normalized).protocol;
return normalizeOrigin(protocol + "//" + host) === normalized;
} catch (e) {
return false;
}
}
function saveAllowedOrigins(config, origins, saveConfig) {
var normalized = normalizeAllowedOrigins(origins);
var nextConfig = Object.assign({}, config, { allowedOrigins: normalized });
saveConfig(nextConfig);
config.allowedOrigins = normalized;
}
module.exports = {
normalizeAllowedOrigins: normalizeAllowedOrigins,
describeConfiguredOrigins: describeConfiguredOrigins,
isAllowedOrigin: isAllowedOrigin,
saveAllowedOrigins: saveAllowedOrigins,
};