« get me outta code hell

content: misc. changes to handle HTML sanitization - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-09-11 10:11:44 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-09-11 10:11:44 -0300
commit3eb82ab2e3f9d921095af05cf0bc284f335aaa35 (patch)
tree0cc43b09e745cdb7ffc8bdd4364d6438a5b3e74b
parentd878ab29f20c0727acafb4b1150d4e31d69c55c0 (diff)
content: misc. changes to handle HTML sanitization
-rw-r--r--src/content/dependencies/generateCoverGrid.js12
-rw-r--r--src/content/dependencies/generateFooterLocalizationLinks.js2
-rw-r--r--src/content/dependencies/generatePageLayout.js6
-rw-r--r--src/content/dependencies/linkTemplate.js2
-rw-r--r--src/content/dependencies/linkThing.js2
-rw-r--r--src/data/things/language.js29
6 files changed, 43 insertions, 10 deletions
diff --git a/src/content/dependencies/generateCoverGrid.js b/src/content/dependencies/generateCoverGrid.js
index 9822e1a..5636e4f 100644
--- a/src/content/dependencies/generateCoverGrid.js
+++ b/src/content/dependencies/generateCoverGrid.js
@@ -2,7 +2,7 @@ import {stitchArrays} from '#sugar';
 
 export default {
   contentDependencies: ['generateGridActionLinks'],
-  extraDependencies: ['html'],
+  extraDependencies: ['html', 'language'],
 
   relations(relation) {
     return {
@@ -20,7 +20,7 @@ export default {
     actionLinks: {validate: v => v.sparseArrayOf(v.isHTML)},
   },
 
-  generate(relations, slots, {html}) {
+  generate(relations, slots, {html, language}) {
     return (
       html.tag('div', {class: 'grid-listing'}, [
         stitchArrays({
@@ -42,8 +42,12 @@ export default {
                       ? slots.lazy
                       : false),
                 }),
-                html.tag('span', {[html.onlyIfContent]: true}, name),
-                html.tag('span', {[html.onlyIfContent]: true}, info),
+
+                html.tag('span', {[html.onlyIfContent]: true},
+                  language.sanitize(name)),
+
+                html.tag('span', {[html.onlyIfContent]: true},
+                  language.sanitize(info)),
               ],
             })),
 
diff --git a/src/content/dependencies/generateFooterLocalizationLinks.js b/src/content/dependencies/generateFooterLocalizationLinks.js
index b4970b1..5df8356 100644
--- a/src/content/dependencies/generateFooterLocalizationLinks.js
+++ b/src/content/dependencies/generateFooterLocalizationLinks.js
@@ -38,7 +38,7 @@ export default {
 
     return html.tag('div', {class: 'footer-localization-links'},
       language.$('misc.uiLanguage', {
-        languages: links.join('\n'),
+        languages: language.formatListWithoutSeparator(links),
       }));
   },
 };
diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js
index 95a5dbe..5377f80 100644
--- a/src/content/dependencies/generatePageLayout.js
+++ b/src/content/dependencies/generatePageLayout.js
@@ -105,7 +105,7 @@ export default {
     color: {validate: v => v.isColor},
 
     styleRules: {
-      validate: v => v.sparseArrayOf(v.isString),
+      validate: v => v.sparseArrayOf(v.isHTML),
       default: [],
     },
 
@@ -183,7 +183,7 @@ export default {
           } else {
             aggregate.call(v.validateProperties({
               path: v.strictArrayOf(v.isString),
-              title: v.isString,
+              title: v.isHTML,
             }), {
               path: object.path,
               title: object.title,
@@ -521,7 +521,7 @@ export default {
         ]),
       slots.bannerPosition === 'bottom' && slots.banner,
       footerHTML,
-    ].filter(Boolean).join('\n');
+    ];
 
     const pageHTML = html.tags([
       `<!DOCTYPE html>`,
diff --git a/src/content/dependencies/linkTemplate.js b/src/content/dependencies/linkTemplate.js
index ba7c7cd..7206e96 100644
--- a/src/content/dependencies/linkTemplate.js
+++ b/src/content/dependencies/linkTemplate.js
@@ -16,7 +16,7 @@ export default {
     path: {validate: v => v.validateArrayItems(v.isString)},
     hash: {type: 'string'},
 
-    tooltip: {validate: v => v.isString},
+    tooltip: {type: 'string'},
     attributes: {validate: v => v.isAttributes},
     color: {validate: v => v.isColor},
     content: {type: 'html'},
diff --git a/src/content/dependencies/linkThing.js b/src/content/dependencies/linkThing.js
index e3e2608..643bf4b 100644
--- a/src/content/dependencies/linkThing.js
+++ b/src/content/dependencies/linkThing.js
@@ -26,7 +26,7 @@ export default {
     preferShortName: {type: 'boolean', default: false},
 
     tooltip: {
-      validate: v => v.oneOf(v.isBoolean, v.isString),
+      validate: v => v.oneOf(v.isBoolean, v.isHTML),
       default: false,
     },
 
diff --git a/src/data/things/language.js b/src/data/things/language.js
index cc49b73..afa9f1e 100644
--- a/src/data/things/language.js
+++ b/src/data/things/language.js
@@ -214,6 +214,28 @@ export class Language extends Thing {
     return new Tag(null, null, output);
   }
 
+  // Similar to the above internal methods, but this one is public.
+  // It should be used when embedding content that may not have previously
+  // been sanitized directly into an HTML tag or template's contents.
+  // The templating engine usually handles this on its own, as does passing
+  // a value (sanitized or not) directly as an argument to formatString,
+  // but if you used a custom validation function ({validate: v => v.isHTML}
+  // instead of {type: 'string'} / {type: 'html'}) and are embedding the
+  // contents of a slot directly, it should be manually sanitized with this
+  // function first.
+  sanitize(arg) {
+    const escapeHTML = this.escapeHTML;
+
+    if (!escapeHTML) {
+      throw new Error(`escapeHTML unavailable`);
+    }
+
+    return (
+      (typeof arg === 'string'
+        ? new Tag(null, null, escapeHTML(arg))
+        : arg));
+  }
+
   formatDate(date) {
     this.assertIntlAvailable('intl_date');
     return this.intl_date.format(date);
@@ -301,6 +323,13 @@ export class Language extends Thing {
         array.map(item => this.#sanitizeStringArg(item))));
   }
 
+  // Lists without separator: A B C
+  formatListWithoutSeparator(array) {
+    return this.#wrapSanitized(
+      array.map(item => this.#sanitizeStringArg(item))
+        .join(' '));
+  }
+
   // File sizes: 42.5 kB, 127.2 MB, 4.13 GB, 998.82 TB
   formatFileSize(bytes) {
     if (!bytes) return '';