From 9cc8590f93e960d994929bccdb7c1c413c864282 Mon Sep 17 00:00:00 2001 From: Munawwar Date: Thu, 13 Jul 2023 16:41:35 +0530 Subject: [PATCH 1/5] fix escaping of character entities in XML e.g. & in a url in an attribute should become & in XML --- src/libsaml.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libsaml.ts b/src/libsaml.ts index d04b964..a0ad49b 100644 --- a/src/libsaml.ts +++ b/src/libsaml.ts @@ -240,6 +240,17 @@ const libSaml = () => { return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1); } + const characterEntitiesMapping = { + '<': '<', + '>': '>', + '&': '&', + '\'': ''', + '"': '"', + }; + function escapeCharacterEntities(text: string): string { + return text.replace(/[<>&'"]/g, (character) => characterEntitiesMapping[character]); + } + return { createXPath, @@ -259,7 +270,10 @@ const libSaml = () => { */ replaceTagsByValue(rawXML: string, tagValues: any): string { Object.keys(tagValues).forEach(t => { - rawXML = rawXML.replace(new RegExp(`{${t}}`, 'g'), tagValues[t]); + rawXML = rawXML.replace( + new RegExp(`{${t}}`, 'g'), + escapeCharacterEntities(tagValues[t]) + ); }); return rawXML; }, From 95a6578bb54789b82932924f540b3b84dd11160d Mon Sep 17 00:00:00 2001 From: Munawwar Date: Thu, 13 Jul 2023 17:05:15 +0530 Subject: [PATCH 2/5] linting --- src/libsaml.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsaml.ts b/src/libsaml.ts index a0ad49b..b6a1967 100644 --- a/src/libsaml.ts +++ b/src/libsaml.ts @@ -248,7 +248,7 @@ const libSaml = () => { '"': '"', }; function escapeCharacterEntities(text: string): string { - return text.replace(/[<>&'"]/g, (character) => characterEntitiesMapping[character]); + return text.replace(/[<>&'"]/g, character => characterEntitiesMapping[character]); } return { From 040860e9640835548d99d2c61eacfd3205be0ae4 Mon Sep 17 00:00:00 2001 From: Munawwar Date: Thu, 13 Jul 2023 17:26:03 +0530 Subject: [PATCH 3/5] tests --- src/libsaml.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libsaml.ts b/src/libsaml.ts index b6a1967..1bb7eb4 100644 --- a/src/libsaml.ts +++ b/src/libsaml.ts @@ -248,6 +248,7 @@ const libSaml = () => { '"': '"', }; function escapeCharacterEntities(text: string): string { + if (!text || typeof text !== 'string') return text; return text.replace(/[<>&'"]/g, character => characterEntitiesMapping[character]); } From eea8501c660465d4dfb57d651adfaf6ab477cc4d Mon Sep 17 00:00:00 2001 From: Munawwar Date: Wed, 16 Aug 2023 11:29:13 +0530 Subject: [PATCH 4/5] fix entity escaping to only escape tag attributes --- src/libsaml.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libsaml.ts b/src/libsaml.ts index 1bb7eb4..f2c2ce8 100644 --- a/src/libsaml.ts +++ b/src/libsaml.ts @@ -247,9 +247,12 @@ const libSaml = () => { '\'': ''', '"': '"', }; - function escapeCharacterEntities(text: string): string { - if (!text || typeof text !== 'string') return text; - return text.replace(/[<>&'"]/g, character => characterEntitiesMapping[character]); + function escapeCharacterEntities(text: string): (...args: string[]) => string { + return (match: string, quote?: string) => { + // not having a quote means this interpolation isn't for an attribute, and so does not need escaping + if (!quote || !text || typeof text !== 'string') return (quote || '') + text; + return '"' + text.replace(/[<>&'"]/g, character => characterEntitiesMapping[character]); + } } return { @@ -272,7 +275,7 @@ const libSaml = () => { replaceTagsByValue(rawXML: string, tagValues: any): string { Object.keys(tagValues).forEach(t => { rawXML = rawXML.replace( - new RegExp(`{${t}}`, 'g'), + new RegExp(`("?)\\{${t}\\}`, 'g'), escapeCharacterEntities(tagValues[t]) ); }); From 4db90425ad1f83f1684807565b39db026691a9d9 Mon Sep 17 00:00:00 2001 From: Munawwar Date: Thu, 15 Feb 2024 17:07:36 +0400 Subject: [PATCH 5/5] use xml-escape npm module --- package.json | 1 + src/libsaml.ts | 15 ++++----------- yarn.lock | 5 +++++ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index c672bda..743850f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "uuid": "^8.3.2", "xml": "^1.0.1", "xml-crypto": "^3.0.1", + "xml-escape": "^1.1.0", "xpath": "^0.0.32" }, "devDependencies": { diff --git a/src/libsaml.ts b/src/libsaml.ts index f2c2ce8..125b0d3 100644 --- a/src/libsaml.ts +++ b/src/libsaml.ts @@ -15,6 +15,7 @@ import * as xmlenc from '@authenio/xml-encryption'; import { extract } from './extractor'; import camelCase from 'camelcase'; import { getContext } from './api'; +import xmlEscape from 'xml-escape'; const signatureAlgorithms = algorithms.signature; const digestAlgorithms = algorithms.digest; @@ -240,18 +241,10 @@ const libSaml = () => { return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1); } - const characterEntitiesMapping = { - '<': '<', - '>': '>', - '&': '&', - '\'': ''', - '"': '"', - }; - function escapeCharacterEntities(text: string): (...args: string[]) => string { + function escapeTag(text: string): (...args: string[]) => string { return (match: string, quote?: string) => { // not having a quote means this interpolation isn't for an attribute, and so does not need escaping - if (!quote || !text || typeof text !== 'string') return (quote || '') + text; - return '"' + text.replace(/[<>&'"]/g, character => characterEntitiesMapping[character]); + return quote ? `${quote}${xmlEscape(text || '')}` : text; } } @@ -276,7 +269,7 @@ const libSaml = () => { Object.keys(tagValues).forEach(t => { rawXML = rawXML.replace( new RegExp(`("?)\\{${t}\\}`, 'g'), - escapeCharacterEntities(tagValues[t]) + escapeTag(tagValues[t]) ); }); return rawXML; diff --git a/yarn.lock b/yarn.lock index 3f093a8..b86e1e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2430,6 +2430,11 @@ xml-crypto@^3.0.1: "@xmldom/xmldom" "^0.8.5" xpath "0.0.32" +xml-escape@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xml-escape/-/xml-escape-1.1.0.tgz#3904c143fa8eb3a0030ec646d2902a2f1b706c44" + integrity sha512-B/T4sDK8Z6aUh/qNr7mjKAwwncIljFuUP+DO/D5hloYFj+90O88z8Wf7oSucZTHxBAsC1/CTP4rtx/x1Uf72Mg== + xml@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"