diff --git a/apps/app/src/services/renderer/recommended-whitelist.spec.ts b/apps/app/src/services/renderer/recommended-whitelist.spec.ts index e42b57037ba..800c9eaf234 100644 --- a/apps/app/src/services/renderer/recommended-whitelist.spec.ts +++ b/apps/app/src/services/renderer/recommended-whitelist.spec.ts @@ -1,3 +1,5 @@ +import { notDeepEqual } from 'assert'; + import { tagNames, attributes } from './recommended-whitelist'; describe('recommended-whitelist', () => { @@ -44,4 +46,31 @@ describe('recommended-whitelist', () => { expect(attributes.iframe).includes('src'); }); + test('.attributes.a should allow class and className by excluding partial className specification', () => { + expect(attributes).not.toBeNull(); + + assert(attributes != null); + + expect(Object.keys(attributes)).includes('a'); + expect(attributes.a).not.toContainEqual(['className', 'data-footnote-backref']); + }); + + test('.attributes.ul should allow class and className by excluding partial className specification', () => { + expect(attributes).not.toBeNull(); + + assert(attributes != null); + + expect(Object.keys(attributes)).includes('a'); + expect(attributes.a).not.toContainEqual(['className', 'data-footnote-backref']); + }); + + test('.attributes.li should allow class and className by excluding partial className specification', () => { + expect(attributes).not.toBeNull(); + + assert(attributes != null); + + expect(Object.keys(attributes)).includes('a'); + expect(attributes.a).not.toContainEqual(['className', 'data-footnote-backref']); + }); + }); diff --git a/apps/app/src/services/renderer/recommended-whitelist.ts b/apps/app/src/services/renderer/recommended-whitelist.ts index 3bb238e6491..270c27ed09c 100644 --- a/apps/app/src/services/renderer/recommended-whitelist.ts +++ b/apps/app/src/services/renderer/recommended-whitelist.ts @@ -3,6 +3,31 @@ import deepmerge from 'ts-deepmerge'; type Attributes = typeof defaultSchema.attributes; +type ExtractPropertyDefinition = T extends Record + ? U + : never; + +type PropertyDefinition = ExtractPropertyDefinition>; + +const excludeRestrictedClassAttributes = (propertyDefinitions: PropertyDefinition[]): PropertyDefinition[] => { + if (propertyDefinitions == null) { + return propertyDefinitions; + } + + return propertyDefinitions.filter((propertyDefinition) => { + if (!Array.isArray(propertyDefinition)) { + return true; + } + return propertyDefinition[0] !== 'class' && propertyDefinition[0] !== 'className'; + }); +}; + +// generate relaxed schema +const relaxedSchemaAttributes = structuredClone(defaultSchema.attributes) ?? {}; +relaxedSchemaAttributes.a = excludeRestrictedClassAttributes(relaxedSchemaAttributes.a); +relaxedSchemaAttributes.ul = excludeRestrictedClassAttributes(relaxedSchemaAttributes.ul); +relaxedSchemaAttributes.li = excludeRestrictedClassAttributes(relaxedSchemaAttributes.li); + /** * reference: https://meta.stackexchange.com/questions/1777/what-html-tags-are-allowed-on-stack-exchange-sites, * https://github.com/jch/html-pipeline/blob/70b6903b025c668ff3c02a6fa382031661182147/lib/html/pipeline/sanitization_filter.rb#L41 @@ -11,6 +36,7 @@ type Attributes = typeof defaultSchema.attributes; export const tagNames: Array = [ ...defaultSchema.tagNames ?? [], '-', 'bdi', + 'button', 'col', 'colgroup', 'data', 'iframe', @@ -19,12 +45,12 @@ export const tagNames: Array = [ ]; export const attributes: Attributes = deepmerge( - defaultSchema.attributes ?? {}, + relaxedSchemaAttributes, { 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*'], + '*': ['key', 'class', 'className', 'style', 'role', 'data*'], }, ); diff --git a/apps/app/src/styles/organisms/_wiki.scss b/apps/app/src/styles/organisms/_wiki.scss index 87d2875c50d..d4c4383a2bf 100644 --- a/apps/app/src/styles/organisms/_wiki.scss +++ b/apps/app/src/styles/organisms/_wiki.scss @@ -139,8 +139,8 @@ position: relative; // for absolute positioned .code-highlighted-title } - ul, - ol { + ul:not(.nav), + ol:not(.nav) { padding-left: 30px; margin: 20px 0; @@ -268,8 +268,8 @@ font-size: 0.9em * $ratio; } - ul, - ol { + ul:not(.nav), + ol:not(.nav) { padding-left: 15px; margin: 10px 0;