Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
// Socket Community Patch: https://socket.dev
// Date: Mon, 16 Mar 2026 22:23:23 GMT
// For more information see https://socket.dev/patch/66ab43a3-7f62-4fcb-bd27-330b23591c44
// This file includes modifications made by Socket, Inc. on Mon, 16 Mar 2026; these modifications are called the "Patch". In some cases, Socket may be required to make the Patch available to you under specific terms, or may be prohibited from restricting certain rights you may have. For example, the terms of another applicable license may require Socket to make the Patch available under specific terms. In those cases, the Patch is made available to you under the required terms, and Socket does not seek to restrict your rights relative to the Patch where prohibited. In all other cases, the Patch is available to you exclusively under the PolyForm Shield License 1.0.0 (https://polyformproject.org/licenses/shield/1.0.0/). The Patch was distributed by Socket with additional information concerning licensing, attribution, and limitation of liability which may be relevant to you and your use of the Patch. As far as the law allows, the Patch and the software including the patch come as is, without any warranty or condition, and Socket will not be liable to you for any damages arising out of the applicable license terms or the use or nature of the Patch or the software including the patch, under any kind of legal claim.
// Original License: MIT

import {isName} from '../util.js';

//TODO: handle comments
export default function readDocType(xmlData, i){

const entities = {};
if( xmlData[i + 3] === 'O' &&
xmlData[i + 4] === 'C' &&
xmlData[i + 5] === 'T' &&
xmlData[i + 6] === 'Y' &&
xmlData[i + 7] === 'P' &&
xmlData[i + 8] === 'E')
{
i = i+9;
let angleBracketsCount = 1;
let hasBody = false, comment = false;
let exp = "";
for(;i<xmlData.length;i++){
if (xmlData[i] === '<' && !comment) { //Determine the tag type
if( hasBody && hasSeq(xmlData, "!ENTITY",i)){
i += 7;
let entityName, val;
[entityName, val,i] = readEntityExp(xmlData,i+1);
if(val.indexOf("&") === -1){ //Parameter entities are not supported
const escaped = entityName.replace(/[.\-+*:]/g, '\.');
entities[ entityName ] = {
regx : RegExp( `&${escaped};`,"g"),
val: val
};
}
}
else if( hasBody && hasSeq(xmlData, "!ELEMENT",i)) {
i += 8;//Not supported
const {index} = readElementExp(xmlData,i+1);
i = index;
}else if( hasBody && hasSeq(xmlData, "!ATTLIST",i)){
i += 8;//Not supported
// const {index} = readAttlistExp(xmlData,i+1);
// i = index;
}else if( hasBody && hasSeq(xmlData, "!NOTATION",i)) {
i += 9;//Not supported
const {index} = readNotationExp(xmlData,i+1);
i = index;
}else if( hasSeq(xmlData, "!--",i) ) comment = true;
else throw new Error(`Invalid DOCTYPE`);

angleBracketsCount++;
exp = "";
} else if (xmlData[i] === '>') { //Read tag content
if(comment){
if( xmlData[i - 1] === "-" && xmlData[i - 2] === "-"){
comment = false;
angleBracketsCount--;
}
}else{
angleBracketsCount--;
}
if (angleBracketsCount === 0) {
break;
}
}else if( xmlData[i] === '['){
hasBody = true;
}else{
exp += xmlData[i];
}
}
if(angleBracketsCount !== 0){
throw new Error(`Unclosed DOCTYPE`);
}
}else{
throw new Error(`Invalid Tag instead of DOCTYPE`);
}
return {entities, i};
}

const skipWhitespace = (data, index) => {
while (index < data.length && /\s/.test(data[index])) {
index++;
}
return index;
};

function readEntityExp(xmlData, i) {
//External entities are not supported
// <!ENTITY ext SYSTEM "http://normal-website.com" >

//Parameter entities are not supported
// <!ENTITY entityname "&anotherElement;">

//Internal entities are supported
// <!ENTITY entityname "replacement text">

// Skip leading whitespace after <!ENTITY
i = skipWhitespace(xmlData, i);

// Read entity name
let entityName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i]) && xmlData[i] !== '"' && xmlData[i] !== "'") {
entityName += xmlData[i];
i++;
}
validateEntityName(entityName);

// Skip whitespace after entity name
i = skipWhitespace(xmlData, i);

// Check for unsupported constructs (external entities or parameter entities)
if (xmlData.substring(i, i + 6).toUpperCase() === "SYSTEM") {
throw new Error("External entities are not supported");
}else if (xmlData[i] === "%") {
throw new Error("Parameter entities are not supported");
}

// Read entity value (internal entity)
let entityValue = "";
[i, entityValue] = readIdentifierVal(xmlData, i, "entity");
i--;
return [entityName, entityValue, i ];
}

function readNotationExp(xmlData, i) {
// Skip leading whitespace after <!NOTATION
i = skipWhitespace(xmlData, i);

// Read notation name
let notationName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
notationName += xmlData[i];
i++;
}
validateEntityName(notationName);

// Skip whitespace after notation name
i = skipWhitespace(xmlData, i);

// Check identifier type (SYSTEM or PUBLIC)
const identifierType = xmlData.substring(i, i + 6).toUpperCase();
if (identifierType !== "SYSTEM" && identifierType !== "PUBLIC") {
throw new Error(`Expected SYSTEM or PUBLIC, found "${identifierType}"`);
}
i += identifierType.length;

// Skip whitespace after identifier type
i = skipWhitespace(xmlData, i);

// Read public identifier (if PUBLIC)
let publicIdentifier = null;
let systemIdentifier = null;

if (identifierType === "PUBLIC") {
[i, publicIdentifier ] = readIdentifierVal(xmlData, i, "publicIdentifier");

// Skip whitespace after public identifier
i = skipWhitespace(xmlData, i);

// Optionally read system identifier
if (xmlData[i] === '"' || xmlData[i] === "'") {
[i, systemIdentifier ] = readIdentifierVal(xmlData, i,"systemIdentifier");
}
} else if (identifierType === "SYSTEM") {
// Read system identifier (mandatory for SYSTEM)
[i, systemIdentifier ] = readIdentifierVal(xmlData, i, "systemIdentifier");

if (!systemIdentifier) {
throw new Error("Missing mandatory system identifier for SYSTEM notation");
}
}

return {notationName, publicIdentifier, systemIdentifier, index: --i};
}

function readIdentifierVal(xmlData, i, type) {
let identifierVal = "";
const startChar = xmlData[i];
if (startChar !== '"' && startChar !== "'") {
throw new Error(`Expected quoted string, found "${startChar}"`);
}
i++;

while (i < xmlData.length && xmlData[i] !== startChar) {
identifierVal += xmlData[i];
i++;
}

if (xmlData[i] !== startChar) {
throw new Error(`Unterminated ${type} value`);
}
i++;
return [i, identifierVal];
}

function readElementExp(xmlData, i) {
// <!ELEMENT br EMPTY>
// <!ELEMENT div ANY>
// <!ELEMENT title (#PCDATA)>
// <!ELEMENT book (title, author+)>
// <!ELEMENT name (content-model)>

// Skip leading whitespace after <!ELEMENT
i = skipWhitespace(xmlData, i);

// Read element name
let elementName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
elementName += xmlData[i];
i++;
}

// Validate element name
if (!validateEntityName(elementName)) {
throw new Error(`Invalid element name: "${elementName}"`);
}

// Skip whitespace after element name
i = skipWhitespace(xmlData, i);
let contentModel = "";
// Expect '(' to start content model
if(xmlData[i] === "E" && hasSeq(xmlData, "MPTY",i)) i+=4;
else if(xmlData[i] === "A" && hasSeq(xmlData, "NY",i)) i+=2;
else if (xmlData[i] === "(") {
i++; // Move past '('

// Read content model
while (i < xmlData.length && xmlData[i] !== ")") {
contentModel += xmlData[i];
i++;
}
if (xmlData[i] !== ")") {
throw new Error("Unterminated content model");
}

}else{
throw new Error(`Invalid Element Expression, found "${xmlData[i]}"`);
}

return {
elementName,
contentModel: contentModel.trim(),
index: i
};
}

function readAttlistExp(xmlData, i) {
// Skip leading whitespace after <!ATTLIST
i = skipWhitespace(xmlData, i);

// Read element name
let elementName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
elementName += xmlData[i];
i++;
}

// Validate element name
validateEntityName(elementName)

// Skip whitespace after element name
i = skipWhitespace(xmlData, i);

// Read attribute name
let attributeName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
attributeName += xmlData[i];
i++;
}

// Validate attribute name
if (!validateEntityName(attributeName)) {
throw new Error(`Invalid attribute name: "${attributeName}"`);
}

// Skip whitespace after attribute name
i = skipWhitespace(xmlData, i);

// Read attribute type
let attributeType = "";
if (xmlData.substring(i, i + 8).toUpperCase() === "NOTATION") {
attributeType = "NOTATION";
i += 8; // Move past "NOTATION"

// Skip whitespace after "NOTATION"
i = skipWhitespace(xmlData, i);

// Expect '(' to start the list of notations
if (xmlData[i] !== "(") {
throw new Error(`Expected '(', found "${xmlData[i]}"`);
}
i++; // Move past '('

// Read the list of allowed notations
let allowedNotations = [];
while (i < xmlData.length && xmlData[i] !== ")") {
let notation = "";
while (i < xmlData.length && xmlData[i] !== "|" && xmlData[i] !== ")") {
notation += xmlData[i];
i++;
}

// Validate notation name
notation = notation.trim();
if (!validateEntityName(notation)) {
throw new Error(`Invalid notation name: "${notation}"`);
}

allowedNotations.push(notation);

// Skip '|' separator or exit loop
if (xmlData[i] === "|") {
i++; // Move past '|'
i = skipWhitespace(xmlData, i); // Skip optional whitespace after '|'
}
}

if (xmlData[i] !== ")") {
throw new Error("Unterminated list of notations");
}
i++; // Move past ')'

// Store the allowed notations as part of the attribute type
attributeType += " (" + allowedNotations.join("|") + ")";
} else {
// Handle simple types (e.g., CDATA, ID, IDREF, etc.)
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
attributeType += xmlData[i];
i++;
}

// Validate simple attribute type
const validTypes = ["CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN", "NMTOKENS"];
if (!validTypes.includes(attributeType.toUpperCase())) {
throw new Error(`Invalid attribute type: "${attributeType}"`);
}
}

// Skip whitespace after attribute type
i = skipWhitespace(xmlData, i);

// Read default value
let defaultValue = "";
if (xmlData.substring(i, i + 8).toUpperCase() === "#REQUIRED") {
defaultValue = "#REQUIRED";
i += 8;
} else if (xmlData.substring(i, i + 7).toUpperCase() === "#IMPLIED") {
defaultValue = "#IMPLIED";
i += 7;
} else {
[i, defaultValue] = readIdentifierVal(xmlData, i, "ATTLIST");
}

return {
elementName,
attributeName,
attributeType,
defaultValue,
index: i
}
}

function hasSeq(data, seq,i){
for(let j=0;j<seq.length;j++){
if(seq[j]!==data[i+j+1]) return false;
}
return true;
}

function validateEntityName(name){
if (isName(name))
return name;
else
throw new Error(`Invalid entity name ${name}`);
}
Loading