mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-23 17:29:54 +02:00
251 lines
9.3 KiB
TypeScript
251 lines
9.3 KiB
TypeScript
import assignWithDepth from './assignWithDepth.js';
|
|
import { log } from './logger.js';
|
|
import theme from './themes/index.js';
|
|
import config from './defaultConfig.js';
|
|
import type { MermaidConfig } from './config.type.js';
|
|
import { sanitizeDirective } from './utils/sanitizeDirective.js';
|
|
|
|
export const defaultConfig: MermaidConfig = Object.freeze(config);
|
|
|
|
let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
|
|
let configFromInitialize: MermaidConfig;
|
|
let directives: MermaidConfig[] = [];
|
|
let currentConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
|
|
|
|
export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: MermaidConfig[]) => {
|
|
// start with config being the siteConfig
|
|
let cfg: MermaidConfig = assignWithDepth({}, siteCfg);
|
|
// let sCfg = assignWithDepth(defaultConfig, siteConfigDelta);
|
|
|
|
// Join directives
|
|
let sumOfDirectives: MermaidConfig = {};
|
|
for (const d of _directives) {
|
|
sanitize(d);
|
|
// Apply the data from the directive where the overrides the themeVariables
|
|
sumOfDirectives = assignWithDepth(sumOfDirectives, d);
|
|
}
|
|
|
|
cfg = assignWithDepth(cfg, sumOfDirectives);
|
|
|
|
if (sumOfDirectives.theme && sumOfDirectives.theme in theme) {
|
|
const tmpConfigFromInitialize = assignWithDepth({}, configFromInitialize);
|
|
const themeVariables = assignWithDepth(
|
|
tmpConfigFromInitialize.themeVariables ?? {},
|
|
sumOfDirectives.themeVariables
|
|
);
|
|
if (cfg.theme && cfg.theme in theme) {
|
|
cfg.themeVariables = theme[cfg.theme as keyof typeof theme].getThemeVariables(themeVariables);
|
|
}
|
|
}
|
|
|
|
currentConfig = cfg;
|
|
checkConfig(currentConfig);
|
|
return currentConfig;
|
|
};
|
|
|
|
/**
|
|
* ## setSiteConfig
|
|
*
|
|
* | Function | Description | Type | Values |
|
|
* | ------------- | ------------------------------------- | ----------- | --------------------------------------- |
|
|
* | setSiteConfig | Sets the siteConfig to desired values | Put Request | Any Values, except ones in secure array |
|
|
*
|
|
* **Notes:** Sets the siteConfig. The siteConfig is a protected configuration for repeat use. Calls
|
|
* to reset() will reset the currentConfig to siteConfig. Calls to reset(configApi.defaultConfig)
|
|
* will reset siteConfig and currentConfig to the defaultConfig Note: currentConfig is set in this
|
|
* function _Default value: At default, will mirror Global Config_
|
|
*
|
|
* @param conf - The base currentConfig to use as siteConfig
|
|
* @returns The new siteConfig
|
|
*/
|
|
export const setSiteConfig = (conf: MermaidConfig): MermaidConfig => {
|
|
siteConfig = assignWithDepth({}, defaultConfig);
|
|
siteConfig = assignWithDepth(siteConfig, conf);
|
|
|
|
// @ts-ignore: TODO Fix ts errors
|
|
if (conf.theme && theme[conf.theme]) {
|
|
// @ts-ignore: TODO Fix ts errors
|
|
siteConfig.themeVariables = theme[conf.theme].getThemeVariables(conf.themeVariables);
|
|
}
|
|
|
|
updateCurrentConfig(siteConfig, directives);
|
|
return siteConfig;
|
|
};
|
|
|
|
export const saveConfigFromInitialize = (conf: MermaidConfig): void => {
|
|
configFromInitialize = assignWithDepth({}, conf);
|
|
};
|
|
|
|
export const updateSiteConfig = (conf: MermaidConfig): MermaidConfig => {
|
|
siteConfig = assignWithDepth(siteConfig, conf);
|
|
updateCurrentConfig(siteConfig, directives);
|
|
|
|
return siteConfig;
|
|
};
|
|
/**
|
|
* ## getSiteConfig
|
|
*
|
|
* | Function | Description | Type | Values |
|
|
* | ------------- | ------------------------------------------------- | ----------- | -------------------------------- |
|
|
* | setSiteConfig | Returns the current siteConfig base configuration | Get Request | Returns Any Values in siteConfig |
|
|
*
|
|
* **Notes**: Returns **any** values in siteConfig.
|
|
*
|
|
* @returns The siteConfig
|
|
*/
|
|
export const getSiteConfig = (): MermaidConfig => {
|
|
return assignWithDepth({}, siteConfig);
|
|
};
|
|
/**
|
|
* ## setConfig
|
|
*
|
|
* | Function | Description | Type | Values |
|
|
* | ------------- | ------------------------------------- | ----------- | --------------------------------------- |
|
|
* | setSiteConfig | Sets the siteConfig to desired values | Put Request | Any Values, except ones in secure array |
|
|
*
|
|
* **Notes**: Sets the currentConfig. The parameter conf is sanitized based on the siteConfig.secure
|
|
* keys. Any values found in conf with key found in siteConfig.secure will be replaced with the
|
|
* corresponding siteConfig value.
|
|
*
|
|
* @param conf - The potential currentConfig
|
|
* @returns The currentConfig merged with the sanitized conf
|
|
*/
|
|
export const setConfig = (conf: MermaidConfig): MermaidConfig => {
|
|
checkConfig(conf);
|
|
assignWithDepth(currentConfig, conf);
|
|
|
|
return getConfig();
|
|
};
|
|
|
|
/**
|
|
* ## getConfig
|
|
*
|
|
* | Function | Description | Type | Return Values |
|
|
* | --------- | ------------------------- | ----------- | ------------------------------ |
|
|
* | getConfig | Obtains the currentConfig | Get Request | Any Values from current Config |
|
|
*
|
|
* **Notes**: Avoid calling this function repeatedly. Instead, store the result in a variable and use it, and pass it down to function calls.
|
|
*
|
|
* @returns The currentConfig
|
|
*/
|
|
export const getConfig = (): MermaidConfig => {
|
|
return assignWithDepth({}, currentConfig);
|
|
};
|
|
/**
|
|
* ## sanitize
|
|
*
|
|
* | Function | Description | Type | Values |
|
|
* | -------- | -------------------------------------- | ----------- | ------ |
|
|
* | sanitize | Sets the siteConfig to desired values. | Put Request | None |
|
|
*
|
|
* Ensures options parameter does not attempt to override siteConfig secure keys **Notes**: modifies
|
|
* options in-place
|
|
*
|
|
* @param options - The potential setConfig parameter
|
|
*/
|
|
export const sanitize = (options: any) => {
|
|
if (!options) {
|
|
return;
|
|
}
|
|
// Checking that options are not in the list of excluded options
|
|
['secure', ...(siteConfig.secure ?? [])].forEach((key) => {
|
|
if (Object.hasOwn(options, key)) {
|
|
// DO NOT attempt to print options[key] within `${}` as a malicious script
|
|
// can exploit the logger's attempt to stringify the value and execute arbitrary code
|
|
log.debug(`Denied attempt to modify a secure key ${key}`, options[key]);
|
|
delete options[key];
|
|
}
|
|
});
|
|
|
|
// Check that there no attempts of prototype pollution
|
|
Object.keys(options).forEach((key) => {
|
|
if (key.startsWith('__')) {
|
|
delete options[key];
|
|
}
|
|
});
|
|
// Check that there no attempts of xss, there should be no tags at all in the directive
|
|
// blocking data urls as base64 urls can contain svg's with inline script tags
|
|
Object.keys(options).forEach((key) => {
|
|
if (
|
|
typeof options[key] === 'string' &&
|
|
(options[key].includes('<') ||
|
|
options[key].includes('>') ||
|
|
options[key].includes('url(data:'))
|
|
) {
|
|
delete options[key];
|
|
}
|
|
if (typeof options[key] === 'object') {
|
|
sanitize(options[key]);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Pushes in a directive to the configuration
|
|
*
|
|
* @param directive - The directive to push in
|
|
*/
|
|
export const addDirective = (directive: MermaidConfig) => {
|
|
sanitizeDirective(directive);
|
|
|
|
// If the directive has a fontFamily, but no themeVariables, add the fontFamily to the themeVariables
|
|
if (directive.fontFamily && !directive.themeVariables?.fontFamily) {
|
|
directive.themeVariables = {
|
|
...directive.themeVariables,
|
|
fontFamily: directive.fontFamily,
|
|
};
|
|
}
|
|
|
|
directives.push(directive);
|
|
updateCurrentConfig(siteConfig, directives);
|
|
};
|
|
|
|
/**
|
|
* ## reset
|
|
*
|
|
* | Function | Description | Type | Required | Values |
|
|
* | -------- | ---------------------------- | ----------- | -------- | ------ |
|
|
* | reset | Resets currentConfig to conf | Put Request | Required | None |
|
|
*
|
|
* ## conf
|
|
*
|
|
* | Parameter | Description | Type | Required | Values |
|
|
* | --------- | -------------------------------------------------------------- | ---------- | -------- | -------------------------------------------- |
|
|
* | conf | base set of values, which currentConfig could be **reset** to. | Dictionary | Required | Any Values, with respect to the secure Array |
|
|
*
|
|
* **Notes**: (default: current siteConfig ) (optional, default `getSiteConfig()`)
|
|
*
|
|
* @param config - base set of values, which currentConfig could be **reset** to.
|
|
* Defaults to the current siteConfig (e.g returned by {@link getSiteConfig}).
|
|
*/
|
|
export const reset = (config = siteConfig): void => {
|
|
// Replace current config with siteConfig
|
|
directives = [];
|
|
updateCurrentConfig(config, directives);
|
|
};
|
|
|
|
const ConfigWarning = {
|
|
LAZY_LOAD_DEPRECATED:
|
|
'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
|
|
} as const;
|
|
|
|
type ConfigWarningStrings = keyof typeof ConfigWarning;
|
|
const issuedWarnings: Partial<Record<ConfigWarningStrings, boolean>> = {};
|
|
const issueWarning = (warning: ConfigWarningStrings) => {
|
|
if (issuedWarnings[warning]) {
|
|
return;
|
|
}
|
|
log.warn(ConfigWarning[warning]);
|
|
issuedWarnings[warning] = true;
|
|
};
|
|
|
|
const checkConfig = (config: MermaidConfig) => {
|
|
if (!config) {
|
|
return;
|
|
}
|
|
// @ts-expect-error Properties were removed in v10. Warning should exist.
|
|
if (config.lazyLoadedDiagrams || config.loadExternalDiagramsAtStartup) {
|
|
issueWarning('LAZY_LOAD_DEPRECATED');
|
|
}
|
|
};
|