nehonix-uri-processor
Version:
A powerful URI processor for encoding, decoding, and analyzing URI data securely.
169 lines • 6.8 kB
JavaScript
import { RuleType, ActionType, ConditionType } from "./types/ShieldTypes";
import * as fs from 'fs';
import * as path from 'path';
class NSHSyntaxError extends Error {
constructor(message, line, column, filePath) {
super(`${message}\n at line ${line}${column ? `, column ${column}` : ''}${filePath ? `\n in ${filePath}` : ''}`);
this.line = line;
this.column = column;
this.filePath = filePath;
this.name = 'NSHSyntaxError';
}
}
export class NSHParser {
/**
* Parse a .nsh file into ShieldRules
* Example .nsh file format:
*
* # CSP Rule
* type: csp
* priority: 100
* condition:
* type: match_path
* value: /api/*
* action:
* type: modify
* value:
* directive: script-src
* sources: ['https://api.example.com']
* ---
* # CSRF Rule
* type: csrf
* priority: 90
* condition:
* type: match_path
* value: /public/*
* action:
* type: allow
*/
static parseFile(filePath) {
if (!filePath.endsWith('.nsh')) {
throw new Error('Invalid file extension. Only .nsh files are supported.');
}
const content = fs.readFileSync(filePath, 'utf8');
return this.parseContent(content);
}
static parseContent(content, filePath) {
const rules = [];
const ruleBlocks = content.split(this.RULE_SEPARATOR);
let currentLine = 1;
for (const block of ruleBlocks) {
if (!block.trim()) {
currentLine += block.split('\n').length;
continue;
}
const lines = block.split('\n');
const blockStartLine = currentLine;
try {
const filteredLines = lines
.map((line, idx) => ({
content: line.trim(),
originalLine: blockStartLine + idx,
indentation: line.length - line.trimLeft().length
}))
.filter(line => line.content && !line.content.startsWith(this.COMMENT_PREFIX));
const rule = this.parseRuleBlock(filteredLines, filePath);
rules.push(rule);
}
catch (error) {
if (error instanceof NSHSyntaxError) {
console.error(error.message);
}
else {
console.error(`Unexpected error in rule block starting at line ${blockStartLine}${filePath ? ` in ${filePath}` : ''}`);
}
}
currentLine += lines.length;
}
return rules;
}
static parseRuleBlock(lines, filePath) {
let currentRule = {
id: crypto.randomUUID()
};
let currentSection = null;
let currentObject = {};
for (const { content: line, originalLine, indentation } of lines) {
if (line.includes(':')) {
const [key, value] = line.split(':').map(part => part.trim());
// Validate known properties
if (!currentSection && !this.KNOWN_PROPERTIES.includes(key)) {
throw new NSHSyntaxError(`Unknown property '${key}'. Expected one of: ${this.KNOWN_PROPERTIES.join(', ')}`, originalLine, indentation + 1, filePath);
}
if (['condition', 'action'].includes(key)) {
if (indentation !== 0) {
throw new NSHSyntaxError(`Invalid indentation for section '${key}'. Must be at root level`, originalLine, 1, filePath);
}
currentSection = key;
currentObject = {};
continue;
}
if (currentSection) {
currentObject[key] = this.parseValue(value, originalLine, indentation, filePath);
}
else {
currentRule[key] = this.parseValue(value, originalLine, indentation, filePath);
}
}
if (currentSection && Object.keys(currentObject).length > 0) {
currentRule[currentSection] = currentObject;
}
}
return this.validateRule(currentRule);
}
static parseValue(value, line, column, filePath) {
if (value.startsWith('[') && value.endsWith(']')) {
return value.slice(1, -1).split(',').map(v => v.trim());
}
if (value.startsWith('{') && value.endsWith('}')) {
try {
return JSON.parse(value);
}
catch (_a) {
return value;
}
}
if (value === 'true')
return true;
if (value === 'false')
return false;
if (!isNaN(Number(value)))
return Number(value);
return value;
}
static validateRule(rule, filePath) {
var _a, _b;
if (!rule.type || !this.KNOWN_TYPES.includes(rule.type)) {
throw new NSHSyntaxError(`Invalid rule type: '${rule.type}'. Expected one of: ${this.KNOWN_TYPES.join(', ')}`, 0, 0, filePath);
}
if (!rule.priority || typeof rule.priority !== 'number') {
throw new NSHSyntaxError('Priority must be a number', 0, 0, filePath);
}
if (!rule.condition || !rule.condition.type || !this.KNOWN_CONDITION_TYPES.includes(rule.condition.type)) {
throw new NSHSyntaxError(`Invalid condition type: '${(_a = rule.condition) === null || _a === void 0 ? void 0 : _a.type}'. Expected one of: ${this.KNOWN_CONDITION_TYPES.join(', ')}`, 0, 0, filePath);
}
if (!rule.action || !rule.action.type || !this.KNOWN_ACTION_TYPES.includes(rule.action.type)) {
throw new NSHSyntaxError(`Invalid action type: '${(_b = rule.action) === null || _b === void 0 ? void 0 : _b.type}'. Expected one of: ${this.KNOWN_ACTION_TYPES.join(', ')}`, 0, 0, filePath);
}
return rule;
}
static async loadDirectory(dirPath) {
const rules = [];
const files = await fs.promises.readdir(dirPath);
for (const file of files) {
if (file.endsWith('.nsh')) {
const filePath = path.join(dirPath, file);
const fileRules = this.parseFile(filePath);
rules.push(...fileRules);
}
}
return rules;
}
}
NSHParser.RULE_SEPARATOR = "---";
NSHParser.COMMENT_PREFIX = "#";
NSHParser.KNOWN_PROPERTIES = ['type', 'priority', 'condition', 'action'];
NSHParser.KNOWN_TYPES = Object.values(RuleType);
NSHParser.KNOWN_CONDITION_TYPES = Object.values(ConditionType);
NSHParser.KNOWN_ACTION_TYPES = Object.values(ActionType);
//# sourceMappingURL=NSHParser.js.map