Skip to content

Commit

Permalink
feat: add BracketAwareCsvParser to parse CSV file better (#494)
Browse files Browse the repository at this point in the history
* fix: improve policy line parsing

This change makes the policy parser more robust and flexible, allowing for

* chore: remove csv-parse dependency from package.json

* refactor: use const instead of let for tokens array in Helper class

* fix: policy line parsing for nested expressions and quoted values

* feat: enhance csv-parse for policy

* refactor: reorganize policy parsing and loading logic
  • Loading branch information
HashCookie authored Nov 22, 2024
1 parent ca66e06 commit 4c73883
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 15 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@casbin/expression-eval": "^5.3.0",
"await-lock": "^2.0.1",
"buffer": "^6.0.3",
"csv-parse": "^5.3.5",
"csv-parse": "^5.5.6",
"minimatch": "^7.4.2"
},
"files": [
Expand Down
6 changes: 3 additions & 3 deletions src/persist/fileAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ export class FileAdapter implements Adapter {
private async loadPolicyFile(model: Model, handler: (line: string, model: Model) => void): Promise<void> {
const bodyBuf = await (this.fs ? this.fs : mustGetDefaultFileSystem()).readFileSync(this.filePath);
const lines = bodyBuf.toString().split('\n');
lines.forEach((n: string, index: number) => {
if (!n) {
lines.forEach((line: string) => {
if (!line || line.trim().startsWith('#')) {
return;
}
handler(n, model);
handler(line, model);
});
}

Expand Down
88 changes: 82 additions & 6 deletions src/persist/helper.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,83 @@
import { Model } from '../model';
import { parse } from 'csv-parse/sync';

export class Helper {
public static loadPolicyLine(line: string, model: Model): void {
export interface IPolicyParser {
parse(line: string): string[][] | null;
}

export class BasicCsvParser implements IPolicyParser {
parse(line: string): string[][] | null {
if (!line || line.trimStart().charAt(0) === '#') {
return;
return null;
}

const tokens = parse(line, {
return parse(line, {
delimiter: ',',
skip_empty_lines: true,
trim: true,
relax_quotes: true,
});
}
}

export class BracketAwareCsvParser implements IPolicyParser {
private readonly baseParser: IPolicyParser;

constructor(baseParser: IPolicyParser = new BasicCsvParser()) {
this.baseParser = baseParser;
}

parse(line: string): string[][] | null {
const rawTokens = this.baseParser.parse(line);
if (!rawTokens || !rawTokens[0]) {
return null;
}

const tokens = rawTokens[0];
const processedTokens: string[] = [];
let currentToken = '';
let bracketCount = 0;

for (const token of tokens) {
for (const char of token) {
if (char === '(') bracketCount++;
else if (char === ')') bracketCount--;
}

currentToken += (currentToken ? ',' : '') + token;

if (bracketCount === 0) {
processedTokens.push(currentToken);
currentToken = '';
}
}

if (bracketCount !== 0) {
throw new Error(`Unmatched brackets in policy line: ${line}`);
}

return processedTokens.length > 0 ? [processedTokens] : null;
}
}

export class PolicyLoader {
private readonly parser: IPolicyParser;

constructor(parser: IPolicyParser = new BracketAwareCsvParser()) {
this.parser = parser;
}

loadPolicyLine(line: string, model: Model): void {
const tokens = this.parser.parse(line);
if (!tokens || !tokens[0]) {
return;
}

const key = tokens[0][0];
let key = tokens[0][0].trim();
if (key.startsWith('"') && key.endsWith('"')) {
key = key.slice(1, -1);
}

const sec = key.substring(0, 1);
const item = model.model.get(sec);
if (!item) {
Expand All @@ -28,6 +88,22 @@ export class Helper {
if (!policy) {
return;
}
policy.policy.push(tokens[0].slice(1));

const values = tokens[0].slice(1).map((v) => {
if (v.startsWith('"') && v.endsWith('"')) {
v = v.slice(1, -1);
}
return v.replace(/""/g, '"').trim();
});

policy.policy.push(values);
}
}

export class Helper {
private static readonly policyLoader = new PolicyLoader();

public static loadPolicyLine(line: string, model: Model): void {
Helper.policyLoader.loadPolicyLine(line, model);
}
}
2 changes: 1 addition & 1 deletion test/persist/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
['admin', '/', 'POST'],
['admin', '/', 'PUT'],
['admin', '/', 'DELETE'],
[' admin', '/ ', 'PATCH'],
['admin', '/', 'PATCH'],
];

testdata.forEach((n) => {
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2282,10 +2282,10 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"

csv-parse@^5.3.5:
version "5.3.5"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.3.5.tgz#9924bbba9f7056122f06b7af18edc1a7f022ce99"
integrity sha512-8O5KTIRtwmtD3+EVfW6BCgbwZqJbhTYsQZry12F1TP5RUp0sD9tp1UnCWic3n0mLOhzeocYaCZNYxOGSg3dmmQ==
csv-parse@^5.5.6:
version "5.5.6"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.6.tgz#0d726d58a60416361358eec291a9f93abe0b6b1a"
integrity sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A==

[email protected]:
version "3.2.0"
Expand Down

0 comments on commit 4c73883

Please sign in to comment.