Skip to content

Commit cb31b0b

Browse files
committed
feat: add support for null namespace in findAttr function
1 parent bd8eec3 commit cb31b0b

File tree

3 files changed

+75
-4
lines changed

3 files changed

+75
-4
lines changed

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ export interface XmlDSigVerifierOptions {
363363
* Names of XML attributes to treat as element identifiers.
364364
* Used when resolving URI references in signatures.
365365
* When passing strings, only the localName is matched, ignoring namespace.
366-
* To explicitly match attributes without namespaces, use: { localName: "Id", namespaceUri: undefined }
366+
* To explicitly match attributes without namespaces, use: { localName: "Id", namespaceUri: null }
367367
* @default {@link SignedXml.getDefaultIdAttributes()}
368368
* @example For WS-Security: [{ localName: "Id", namespaceUri: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" }]
369369
*/

src/utils.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,33 @@ export function isArrayHasLength(array: unknown): array is unknown[] {
66
return Array.isArray(array) && array.length > 0;
77
}
88

9-
function attrEqualsExplicitly(attr: Attr, localName: string, namespace?: string) {
9+
function attrEqualsExplicitly(attr: Attr, localName: string, namespace?: string | null) {
10+
if (namespace === null) {
11+
return attr.localName === localName && !attr.namespaceURI;
12+
}
1013
return attr.localName === localName && (attr.namespaceURI === namespace || namespace == null);
1114
}
1215

13-
function attrEqualsImplicitly(attr: Attr, localName: string, namespace?: string, node?: Element) {
16+
function attrEqualsImplicitly(
17+
attr: Attr,
18+
localName: string,
19+
namespace?: string | null,
20+
node?: Element,
21+
) {
22+
if (namespace === null) {
23+
return attr.localName === localName && !attr.namespaceURI;
24+
}
1425
return (
1526
attr.localName === localName &&
1627
((!attr.namespaceURI && node?.namespaceURI === namespace) || namespace == null)
1728
);
1829
}
1930

20-
export function findAttr(element: Element, localName: string, namespace?: string | undefined) {
31+
export function findAttr(
32+
element: Element,
33+
localName: string,
34+
namespace?: string | null | undefined,
35+
) {
2136
for (let i = 0; i < element.attributes.length; i++) {
2237
const attr = element.attributes[i];
2338

test/utils-tests.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,60 @@ describe("Utils tests", function () {
8080
expect(() => utils.pemToDer("not a pem")).to.throw();
8181
});
8282
});
83+
84+
describe("findAttr", function () {
85+
it("should find attribute with no namespace when null is passed as namespace", function () {
86+
const xml =
87+
'<root testAttr="value" xmlns:ns="http://example.com" ns:testAttr="nsValue"></root>';
88+
const doc = new xmldom.DOMParser().parseFromString(xml);
89+
const rootElement = doc.documentElement;
90+
91+
const attr = utils.findAttr(rootElement, "testAttr", null);
92+
93+
expect(attr).to.not.be.null;
94+
expect(attr?.value).to.equal("value");
95+
expect(attr?.namespaceURI).to.be.undefined;
96+
});
97+
98+
it("should not find namespaced attribute when null is passed as namespace", function () {
99+
const xml = '<root xmlns:ns="http://example.com" ns:testAttr="nsValue"></root>';
100+
const doc = new xmldom.DOMParser().parseFromString(xml);
101+
const rootElement = doc.documentElement;
102+
103+
const attr = utils.findAttr(rootElement, "testAttr", null);
104+
105+
expect(attr).to.be.null;
106+
});
107+
108+
it("should find namespaced attribute when matching namespace is provided", function () {
109+
const xml = '<root xmlns:ns="http://example.com" ns:testAttr="nsValue"></root>';
110+
const doc = new xmldom.DOMParser().parseFromString(xml);
111+
const rootElement = doc.documentElement;
112+
113+
const attr = utils.findAttr(rootElement, "testAttr", "http://example.com");
114+
115+
expect(attr).to.not.be.null;
116+
expect(attr?.value).to.equal("nsValue");
117+
expect(attr?.namespaceURI).to.equal("http://example.com");
118+
});
119+
120+
it("should distinguish between namespaced and non-namespaced attributes with same localName", function () {
121+
const xml =
122+
'<root testAttr="noNsValue" xmlns:ns="http://example.com" ns:testAttr="nsValue"></root>';
123+
const doc = new xmldom.DOMParser().parseFromString(xml);
124+
const rootElement = doc.documentElement;
125+
126+
// Find the non-namespaced attribute
127+
const noNsAttr = utils.findAttr(rootElement, "testAttr", null);
128+
expect(noNsAttr).to.not.be.null;
129+
expect(noNsAttr?.value).to.equal("noNsValue");
130+
expect(noNsAttr?.namespaceURI).to.be.undefined;
131+
132+
// Find the namespaced attribute
133+
const nsAttr = utils.findAttr(rootElement, "testAttr", "http://example.com");
134+
expect(nsAttr).to.not.be.null;
135+
expect(nsAttr?.value).to.equal("nsValue");
136+
expect(nsAttr?.namespaceURI).to.equal("http://example.com");
137+
});
138+
});
83139
});

0 commit comments

Comments
 (0)