Просмотр исходного кода

tidy up rehype-sanitize configuration modules

Yuki Takei 1 год назад
Родитель
Сommit
5c4ea7e065

+ 6 - 6
apps/app/src/client/services/renderer/renderer.tsx

@@ -3,7 +3,6 @@ import assert from 'assert';
 import { isClient } from '@growi/core/dist/utils/browser-utils';
 import { isClient } from '@growi/core/dist/utils/browser-utils';
 import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
 import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
 import * as drawio from '@growi/remark-drawio';
 import * as drawio from '@growi/remark-drawio';
-// eslint-disable-next-line import/extensions
 import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client';
 import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client';
 import katex from 'rehype-katex';
 import katex from 'rehype-katex';
 import sanitize from 'rehype-sanitize';
 import sanitize from 'rehype-sanitize';
@@ -20,8 +19,8 @@ import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
 import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
 import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
 import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import * as mermaid from '~/features/mermaid';
 import * as mermaid from '~/features/mermaid';
-import { RehypeSanitizeOption } from '~/interfaces/rehype';
 import type { RendererOptions } from '~/interfaces/renderer-options';
 import type { RendererOptions } from '~/interfaces/renderer-options';
+import { RehypeSanitizeType } from '~/interfaces/services/rehype-sanitize';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-line-number-attribute';
 import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-line-number-attribute';
 import * as keywordHighlighter from '~/services/renderer/rehype-plugins/keyword-highlighter';
 import * as keywordHighlighter from '~/services/renderer/rehype-plugins/keyword-highlighter';
@@ -36,6 +35,7 @@ import loggerFactory from '~/utils/logger';
 
 
 // import EasyGrid from './PreProcessor/EasyGrid';
 // import EasyGrid from './PreProcessor/EasyGrid';
 
 
+
 import '@growi/remark-lsx/dist/client/style.css';
 import '@growi/remark-lsx/dist/client/style.css';
 import '@growi/remark-attachment-refs/dist/client/style.css';
 import '@growi/remark-attachment-refs/dist/client/style.css';
 
 
@@ -71,7 +71,7 @@ export const generateViewOptions = (
     remarkPlugins.push(breaks);
     remarkPlugins.push(breaks);
   }
   }
 
 
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+  if (config.sanitizeType === RehypeSanitizeType.CUSTOM) {
     injectCustomSanitizeOption(config);
     injectCustomSanitizeOption(config);
   }
   }
 
 
@@ -132,7 +132,7 @@ export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementN
   // add remark plugins
   // add remark plugins
   // remarkPlugins.push();
   // remarkPlugins.push();
 
 
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+  if (config.sanitizeType === RehypeSanitizeType.CUSTOM) {
     injectCustomSanitizeOption(config);
     injectCustomSanitizeOption(config);
   }
   }
 
 
@@ -184,7 +184,7 @@ export const generateSimpleViewOptions = (
     remarkPlugins.push(breaks);
     remarkPlugins.push(breaks);
   }
   }
 
 
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+  if (config.sanitizeType === RehypeSanitizeType.CUSTOM) {
     injectCustomSanitizeOption(config);
     injectCustomSanitizeOption(config);
   }
   }
 
 
@@ -277,7 +277,7 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
     remarkPlugins.push(breaks);
     remarkPlugins.push(breaks);
   }
   }
 
 
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+  if (config.sanitizeType === RehypeSanitizeType.CUSTOM) {
     injectCustomSanitizeOption(config);
     injectCustomSanitizeOption(config);
   }
   }
 
 

+ 0 - 6
apps/app/src/interfaces/rehype.ts

@@ -1,6 +0,0 @@
-export const RehypeSanitizeOption = {
-  RECOMMENDED: 'Recommended',
-  CUSTOM: 'Custom',
-} as const;
-
-export type RehypeSanitizeOption = typeof RehypeSanitizeOption[keyof typeof RehypeSanitizeOption];

+ 15 - 0
apps/app/src/interfaces/services/rehype-sanitize.ts

@@ -0,0 +1,15 @@
+import type { Attributes } from 'hast-util-sanitize/lib';
+
+export const RehypeSanitizeType = {
+  RECOMMENDED: 'Recommended',
+  CUSTOM: 'Custom',
+} as const;
+
+export type RehypeSanitizeType = typeof RehypeSanitizeType[keyof typeof RehypeSanitizeType];
+
+export type RehypeSanitizeConfiguration = {
+  isEnabledXssPrevention: boolean,
+  sanitizeType: RehypeSanitizeType,
+  customTagWhitelist?: Array<string> | null,
+  customAttrWhitelist?: Attributes | null,
+}

+ 2 - 2
apps/app/src/interfaces/services/renderer.ts

@@ -1,4 +1,4 @@
-import { XssOptionConfig } from '~/services/xss/xssOption';
+import type { RehypeSanitizeConfiguration } from './rehype-sanitize';
 
 
 export type RendererConfig = {
 export type RendererConfig = {
   isSharedPage?: boolean
   isSharedPage?: boolean
@@ -11,4 +11,4 @@ export type RendererConfig = {
 
 
   drawioUri: string,
   drawioUri: string,
   plantumlUri: string,
   plantumlUri: string,
-} & XssOptionConfig;
+} & RehypeSanitizeConfiguration;

+ 12 - 23
apps/app/src/services/renderer/renderer.tsx

@@ -2,7 +2,7 @@ import growiDirective from '@growi/remark-growi-directive';
 import type { Schema as SanitizeOption } from 'hast-util-sanitize';
 import type { Schema as SanitizeOption } from 'hast-util-sanitize';
 import katex from 'rehype-katex';
 import katex from 'rehype-katex';
 import raw from 'rehype-raw';
 import raw from 'rehype-raw';
-import sanitize, { defaultSchema as rehypeSanitizeDefaultSchema } from 'rehype-sanitize';
+import sanitize from 'rehype-sanitize';
 import slug from 'rehype-slug';
 import slug from 'rehype-slug';
 import breaks from 'remark-breaks';
 import breaks from 'remark-breaks';
 import emoji from 'remark-emoji';
 import emoji from 'remark-emoji';
@@ -16,17 +16,19 @@ import type { Pluggable, PluginTuple } from 'unified';
 
 
 import { CodeBlock } from '~/components/ReactMarkdownComponents/CodeBlock';
 import { CodeBlock } from '~/components/ReactMarkdownComponents/CodeBlock';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
-import { RehypeSanitizeOption } from '~/interfaces/rehype';
 import type { RendererOptions } from '~/interfaces/renderer-options';
 import type { RendererOptions } from '~/interfaces/renderer-options';
+import { RehypeSanitizeType } from '~/interfaces/services/rehype-sanitize';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import { tagNames as recommendedTagNames, attributes as recommendedAttributes } from './recommended-whitelist';
 import * as addClass from './rehype-plugins/add-class';
 import * as addClass from './rehype-plugins/add-class';
 import { relativeLinks } from './rehype-plugins/relative-links';
 import { relativeLinks } from './rehype-plugins/relative-links';
 import { relativeLinksByPukiwikiLikeLinker } from './rehype-plugins/relative-links-by-pukiwiki-like-linker';
 import { relativeLinksByPukiwikiLikeLinker } from './rehype-plugins/relative-links-by-pukiwiki-like-linker';
 import { pukiwikiLikeLinker } from './remark-plugins/pukiwiki-like-linker';
 import { pukiwikiLikeLinker } from './remark-plugins/pukiwiki-like-linker';
 import * as xsvToTable from './remark-plugins/xsv-to-table';
 import * as xsvToTable from './remark-plugins/xsv-to-table';
 
 
+
 // import EasyGrid from './PreProcessor/EasyGrid';
 // import EasyGrid from './PreProcessor/EasyGrid';
 
 
 
 
@@ -36,31 +38,18 @@ const logger = loggerFactory('growi:services:renderer');
 
 
 type SanitizePlugin = PluginTuple<[SanitizeOption]>;
 type SanitizePlugin = PluginTuple<[SanitizeOption]>;
 
 
-const baseSanitizeSchema = {
-  tagNames: ['iframe', 'section', 'video'],
-  attributes: {
-    iframe: ['allow', 'referrerpolicy', 'sandbox', 'src', 'srcdoc'],
-    video: ['controls', 'src', 'muted', 'preload', 'width', 'height', 'autoplay'],
-    // The special value 'data*' as a property name can be used to allow all data properties.
-    // see: https://github.com/syntax-tree/hast-util-sanitize/
-    '*': ['key', 'class', 'className', 'style', 'data*'],
-  },
+export const commonSanitizeOption: SanitizeOption = {
+  tagNames: recommendedTagNames,
+  attributes: recommendedAttributes,
+  clobberPrefix: '', // remove clobber prefix
 };
 };
 
 
-export const commonSanitizeOption: SanitizeOption = deepmerge(
-  rehypeSanitizeDefaultSchema,
-  baseSanitizeSchema,
-  {
-    clobberPrefix: '', // remove clobber prefix
-  },
-);
-
 let isInjectedCustomSanitaizeOption = false;
 let isInjectedCustomSanitaizeOption = false;
 
 
 export const injectCustomSanitizeOption = (config: RendererConfig): void => {
 export const injectCustomSanitizeOption = (config: RendererConfig): void => {
-  if (!isInjectedCustomSanitaizeOption && config.isEnabledXssPrevention && config.xssOption === RehypeSanitizeOption.CUSTOM) {
-    commonSanitizeOption.tagNames = baseSanitizeSchema.tagNames.concat(config.tagWhitelist ?? []);
-    commonSanitizeOption.attributes = deepmerge(baseSanitizeSchema.attributes, config.attrWhitelist ?? {});
+  if (!isInjectedCustomSanitaizeOption && config.isEnabledXssPrevention && config.sanitizeType === RehypeSanitizeType.CUSTOM) {
+    commonSanitizeOption.tagNames = config.customTagWhitelist ?? recommendedTagNames;
+    commonSanitizeOption.attributes = config.customAttrWhitelist ?? recommendedAttributes;
     isInjectedCustomSanitaizeOption = true;
     isInjectedCustomSanitaizeOption = true;
   }
   }
 };
 };
@@ -142,7 +131,7 @@ export const generateSSRViewOptions = (
     remarkPlugins.push(breaks);
     remarkPlugins.push(breaks);
   }
   }
 
 
-  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+  if (config.sanitizeType === RehypeSanitizeType.CUSTOM) {
     injectCustomSanitizeOption(config);
     injectCustomSanitizeOption(config);
   }
   }
 
 

+ 0 - 29
apps/app/src/services/xss/xssOption.ts

@@ -1,29 +0,0 @@
-import type { Attributes } from 'hast-util-sanitize/lib';
-
-import type { RehypeSanitizeOption } from '~/interfaces/rehype';
-import { tagNames as recommendedTagNames, attributes as recommendedAttributes } from '~/services/xss/recommended-whitelist';
-
-export type XssOptionConfig = {
-  isEnabledXssPrevention: boolean,
-  xssOption: RehypeSanitizeOption,
-  customTagWhitelist: Array<string>,
-  customAttrWhitelist: Attributes,
-}
-
-export default class XssOption {
-
-  isEnabledXssPrevention: boolean;
-
-  tagWhitelist: Array<string>;
-
-  attrWhitelist: Attributes;
-
-  constructor(config: XssOptionConfig) {
-    const initializedConfig: Partial<XssOptionConfig> = (config != null) ? config : {};
-
-    this.isEnabledXssPrevention = initializedConfig.isEnabledXssPrevention ?? true;
-    this.tagWhitelist = initializedConfig.customTagWhitelist || recommendedTagNames;
-    this.attrWhitelist = initializedConfig.customAttrWhitelist || recommendedAttributes;
-  }
-
-}