mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Skip DOCTYPE comments/PIs and match root namespaces only as attributes
- The DOCTYPE scan now jumps over whole comments and processing instructions found outside a declaration string literal, so a quote or bracket inside them no longer leaves the scanner stuck before the root. - Root namespace repair walks the root's attributes in order, so declaration-like text inside a quoted value can no longer fill `seen` and cause the real declaration to be dropped as a duplicate. - Regression tests for both, plus linear-time cases in the isolated deadline test. - CURRENT-STATE: describe the scanner as a boundary check and leave the rest of the XML grammar to DOMParser. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5ca2266f53
commit
6b2aa95cc6
3 files changed
+76
-16
No files matched your search
+28
-11
@@ -1058,7 +1058,17 @@ export function repairKmlRootNamespaces(value, what = 'KML') {
|
||||
if (quote) {
|
||||
if (char === quote) quote = null;
|
||||
} else if (char === '"' || char === "'") quote = char;
|
||||
else if (char === '[') subsetDepth++;
|
||||
else if (char === '<' && text.startsWith('<!--', end)) {
|
||||
// Comments and processing instructions inside the internal subset
|
||||
// carry no declaration syntax; jump past them as a whole.
|
||||
const skip = text.indexOf('-->', end + 4);
|
||||
if (skip < 0) return text;
|
||||
end = skip + 2;
|
||||
} else if (char === '<' && text.startsWith('<?', end)) {
|
||||
const skip = text.indexOf('?>', end + 2);
|
||||
if (skip < 0) return text;
|
||||
end = skip + 1;
|
||||
} else if (char === '[') subsetDepth++;
|
||||
else if (char === ']') subsetDepth--;
|
||||
else if (char === '>' && subsetDepth === 0) break;
|
||||
}
|
||||
@@ -1098,17 +1108,24 @@ export function repairKmlRootNamespaces(value, what = 'KML') {
|
||||
msg('That {format} file is not valid XML.'),
|
||||
{ format: what },
|
||||
);
|
||||
// Walk the root's attributes in order so quoted values are never read as
|
||||
// declarations. Anything the walk cannot tokenize is kept verbatim for the
|
||||
// XML parser to judge.
|
||||
const seen = new Set();
|
||||
let root = text
|
||||
.slice(start, end)
|
||||
.replace(
|
||||
/\sxmlns:([\w.-]+)\s*=\s*(["'])[^"']*\2/g,
|
||||
(declaration, prefix) => {
|
||||
if (seen.has(prefix)) return '';
|
||||
seen.add(prefix);
|
||||
return declaration;
|
||||
},
|
||||
);
|
||||
const attribute = /(\s+)([^\s=/>"']+)\s*=\s*("[^"]*"|'[^']*')/y;
|
||||
let position = start + match[0].length;
|
||||
let root = text.slice(start, position);
|
||||
for (;;) {
|
||||
attribute.lastIndex = position;
|
||||
const found = attribute.exec(text);
|
||||
if (!found || attribute.lastIndex > end) break;
|
||||
position = attribute.lastIndex;
|
||||
const prefix = /^xmlns:([\w.-]+)$/.exec(found[2])?.[1];
|
||||
if (prefix && seen.has(prefix)) continue;
|
||||
if (prefix) seen.add(prefix);
|
||||
root += found[0];
|
||||
}
|
||||
root += text.slice(position, end);
|
||||
if (!seen.has('xsi') && /[<\s]xsi:/.test(text))
|
||||
root = root.replace(
|
||||
/(\/?)>$/,
|
||||
|
||||
@@ -1197,7 +1197,11 @@ test('KML root namespace repair respects quoted delimiters and preserves the doc
|
||||
test('repeated incomplete KML roots finish within an isolated process deadline', () => {
|
||||
const source = `import { repairKmlRootNamespaces } from ${JSON.stringify(new URL('./geoFileImport.js', import.meta.url).href)};
|
||||
try { repairKmlRootNamespaces('<kml '.repeat(1000000)); process.exit(1); }
|
||||
catch (error) { if (error.name !== 'GeoImportError') throw error; }`;
|
||||
catch (error) { if (error.name !== 'GeoImportError') throw error; }
|
||||
try { repairKmlRootNamespaces('<!DOCTYPE kml [' + ${JSON.stringify("<!-- ' --><?pi ' ?>")}.repeat(100000) + ']><kml '); process.exit(1); }
|
||||
catch (error) { if (error.name !== 'GeoImportError') throw error; }
|
||||
const wide = '<kml' + ${JSON.stringify(` xmlns:gx="a" note='xmlns:gx="b"'`)}.repeat(20000) + '/>';
|
||||
if (repairKmlRootNamespaces(wide).split('xmlns:gx="a"').length !== 2) process.exit(1);`;
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
['--input-type=module', '-e', source],
|
||||
@@ -1224,3 +1228,37 @@ test('KML namespace repair follows a doctype with a quoted root-like entity', ()
|
||||
input.replace(' xmlns:gx="b"', ''),
|
||||
);
|
||||
});
|
||||
|
||||
test('KML namespace repair skips doctype comments and processing instructions whole', () => {
|
||||
for (const prolog of [
|
||||
"<!DOCTYPE kml [<!-- don't count this quote -->]>",
|
||||
'<!DOCTYPE kml [<!-- a ] or > here --><!ENTITY x "y">]>',
|
||||
"<!DOCTYPE kml [<?note it's ] > fine?>]>",
|
||||
'<!DOCTYPE kml [<!ENTITY x "<!-- \' -->">]>',
|
||||
]) {
|
||||
const input = `${prolog}<kml xmlns:gx="a" xmlns:gx="b"><Placemark xsi:type="t"/></kml>`;
|
||||
assert.equal(
|
||||
repairKmlRootNamespaces(input),
|
||||
input.replace(
|
||||
' xmlns:gx="b">',
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">',
|
||||
),
|
||||
prolog,
|
||||
);
|
||||
}
|
||||
const unterminated = '<!DOCTYPE kml [<!-- open <kml xmlns:gx="a">';
|
||||
assert.equal(repairKmlRootNamespaces(unterminated), unterminated);
|
||||
});
|
||||
|
||||
test('KML namespace repair ignores declaration text inside quoted attribute values', () => {
|
||||
const input =
|
||||
'<kml note=\'a > b xmlns:gx="fake"\' xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:gx="dup"><gx:Track/></kml>';
|
||||
const result = repairKmlRootNamespaces(input);
|
||||
assert.equal(result, input.replace(' xmlns:gx="dup"', ''));
|
||||
assert.match(result, /\sxmlns:gx="http:\/\/www.google.com\/kml\/ext\/2.2"/);
|
||||
const xsi = '<kml note="xmlns:xsi=\'x\'"><Placemark xsi:type="t"/></kml>';
|
||||
assert.match(
|
||||
repairKmlRootNamespaces(xsi),
|
||||
/note="xmlns:xsi='x'" xmlns:xsi="http:\/\/www.w3.org\/2001\/XMLSchema-instance">/,
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user