diff --git a/front/src/components/Dialogs/TaskImportDialog.vue b/front/src/components/Dialogs/TaskImportDialog.vue index 5018d52ba..21277ac4f 100644 --- a/front/src/components/Dialogs/TaskImportDialog.vue +++ b/front/src/components/Dialogs/TaskImportDialog.vue @@ -26,6 +26,29 @@ + + @@ -95,6 +121,7 @@ import { Ctf, makeId } from 'src/ctfnote/models'; import parsers, { ParsedTask } from 'src/ctfnote/parsers'; import { defineComponent, ref } from 'vue'; import TaskTagsList from 'src/components/Task/TaskTagsList.vue'; +import RawParser from 'src/ctfnote/parsers/raw'; export default defineComponent({ components: { @@ -107,7 +134,11 @@ export default defineComponent({ setup() { const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent(); - const parserOptions = parsers.map((p) => ({ label: p.name, value: p })); + const parserOptions = parsers.map((p) => ({ + label: p.name, + value: p, + amount: 0, + })); const columns = [ { name: 'keep', label: '', field: 'keep' }, @@ -165,14 +196,53 @@ export default defineComponent({ } }, autoDetectParser() { - for (const parser of parsers) { - if (parser.isValid(this.model)) { - const p = this.parserOptions.find((opt) => opt.value == parser); - if (p) this.currentParser = p; + const outputOfParser = parsers.map((p) => { + let challenges: ParsedTask[] = []; + try { + challenges = p.parse(this.model); + } catch (e) {} + + return { + parser: p, + challenges: challenges, + }; + }); + + // assign the amount of challenges to the parser options + this.parserOptions.forEach((opt) => { + const parser = outputOfParser.find( + (p) => p.parser.name === opt.value.name + ); + if (parser) { + opt.amount = parser.challenges.length; } + }); + + // find the parser with the most tasks, but exclude the raw parser + // since it will count the amount of newlines which does not always make sense + const max = outputOfParser + .filter((p) => p.parser.name !== RawParser.name) + .reduce( + (acc, cur) => + cur.challenges.length > acc ? cur.challenges.length : acc, + 0 + ); + const bestParser = outputOfParser.find((p) => p.challenges.length == max); + if ( + bestParser && + bestParser.challenges.length > 0 && + (bestParser.challenges.length > this.currentParser.amount || + this.currentParser.label == RawParser.name) // it must be an improvement, except overriding the raw parser is allowed + ) { + const p = this.parserOptions.find( + (opt) => opt.value == bestParser.parser + ); + if (p) this.currentParser = p; + } else if (this.currentParser.amount == 0) { + this.currentParser = this.parserOptions[0]; } }, - onPaste() { + detectParser() { void this.$nextTick(() => this.autoDetectParser()); }, normalizeTags(tags: string[]): string[] { diff --git a/front/src/ctfnote/parsers/angstrom.ts b/front/src/ctfnote/parsers/angstrom.ts index d31580575..551cb0e67 100644 --- a/front/src/ctfnote/parsers/angstrom.ts +++ b/front/src/ctfnote/parsers/angstrom.ts @@ -1,5 +1,5 @@ import { ParsedTask, Parser } from '.'; -import { parseJson, parseJsonStrict } from '../utils'; +import { parseJsonStrict } from '../utils'; const AngstromParser: Parser = { name: 'ångstromCTF parser', @@ -26,19 +26,6 @@ const AngstromParser: Parser = { } return tasks; }, - isValid(s) { - const data = - parseJson< - Array<{ title: string; category: string; description: string }> - >(s); - return ( - Array.isArray(data) && - data.length > 0 && - data[0].title != null && - data[0].category != null && - data[0].description != null - ); - }, }; export default AngstromParser; diff --git a/front/src/ctfnote/parsers/cini.ts b/front/src/ctfnote/parsers/cini.ts index d1d9c311f..07c8f2466 100644 --- a/front/src/ctfnote/parsers/cini.ts +++ b/front/src/ctfnote/parsers/cini.ts @@ -1,5 +1,5 @@ import { ParsedTask, Parser } from '.'; -import { parseJson, parseJsonStrict } from '../utils'; +import { parseJsonStrict } from '../utils'; interface Events { gamePause?: unknown; @@ -46,11 +46,6 @@ const CINIParser: Parser = { return tasks; }, - isValid(s) { - const data = parseJson(s); - if (data == null) return false; - return data.gamePause !== undefined && data.events !== undefined; - }, }; export default CINIParser; diff --git a/front/src/ctfnote/parsers/ctfd.ts b/front/src/ctfnote/parsers/ctfd.ts index 6048cff60..20ceb4185 100644 --- a/front/src/ctfnote/parsers/ctfd.ts +++ b/front/src/ctfnote/parsers/ctfd.ts @@ -1,5 +1,5 @@ import { ParsedTask, Parser } from '.'; -import { parseJson, parseJsonStrict } from '../utils'; +import { parseJsonStrict } from '../utils'; interface CTFdTags { value: string; @@ -30,10 +30,6 @@ const CTFDParser: Parser = { } return tasks; }, - isValid(s) { - const data = parseJson<{ data?: unknown }>(s); - return Array.isArray(data?.data); - }, }; export default CTFDParser; diff --git a/front/src/ctfnote/parsers/hitcon.ts b/front/src/ctfnote/parsers/hitcon.ts index 464916013..20c75ff68 100644 --- a/front/src/ctfnote/parsers/hitcon.ts +++ b/front/src/ctfnote/parsers/hitcon.ts @@ -28,20 +28,6 @@ const HitconParser: Parser = { return tasks; }, - isValid(s) { - const data = - parseJsonStrict< - [{ name: string; category: string; description: string }] - >(s); - if (data == null || data.length < 1) { - return false; - } - return ( - data[0].name != null && - data[0].category != null && - data[0].description != null - ); - }, }; export default HitconParser; diff --git a/front/src/ctfnote/parsers/htb.ts b/front/src/ctfnote/parsers/htb.ts index cb854dd44..4e7139c10 100644 --- a/front/src/ctfnote/parsers/htb.ts +++ b/front/src/ctfnote/parsers/htb.ts @@ -1,5 +1,5 @@ import { ParsedTask, Parser } from '.'; -import { parseJson, parseJsonStrict } from '../utils'; +import { parseJsonStrict } from '../utils'; // output of https://ctf.hackthebox.com/api/public/challengeCategories const challengeCategories: { [index: number]: string } = { @@ -67,17 +67,6 @@ const HTBParser: Parser = { } return tasks; }, - isValid(s) { - const data = parseJson<{ - challenges: Array<{ - id: number; - name: string; - description: string; - challenge_category_id: number; - }>; - }>(s); - return Array.isArray(data?.challenges); - }, }; export default HTBParser; diff --git a/front/src/ctfnote/parsers/index.ts b/front/src/ctfnote/parsers/index.ts index ca4c13606..add3bdce4 100644 --- a/front/src/ctfnote/parsers/index.ts +++ b/front/src/ctfnote/parsers/index.ts @@ -17,7 +17,6 @@ export type ParsedTask = { export type Parser = { name: string; hint: string; - isValid(s: string): boolean; parse(s: string): ParsedTask[]; }; diff --git a/front/src/ctfnote/parsers/justctf.ts b/front/src/ctfnote/parsers/justctf.ts index 1d076a29d..e5297bdd9 100644 --- a/front/src/ctfnote/parsers/justctf.ts +++ b/front/src/ctfnote/parsers/justctf.ts @@ -1,5 +1,5 @@ import { ParsedTask, Parser } from '.'; -import { parseJson, parseJsonStrict } from '../utils'; +import { parseJsonStrict } from '../utils'; const justCTFParser: Parser = { name: 'justCTF parser', @@ -25,7 +25,11 @@ const justCTFParser: Parser = { } for (const challenge of data) { - if (!challenge.description || !challenge.name) { + if ( + !challenge.description || + !challenge.name || + !Array.isArray(challenge.categories) + ) { continue; } @@ -37,29 +41,6 @@ const justCTFParser: Parser = { } return tasks; }, - isValid(s) { - const data = parseJson< - [ - { - id: number; - name: string; - categories: [string]; - difficult: string; - description: string; - points: number; - solvers: number; - } - ] - >(s); - return ( - data != null && - data?.length > 0 && - data[0].id != null && - data[0].name != null && - Array.isArray(data[0].categories) && - data[0].points != null - ); - }, }; export default justCTFParser; diff --git a/front/src/ctfnote/parsers/pico.ts b/front/src/ctfnote/parsers/pico.ts index 62e90a0be..dc9536beb 100755 --- a/front/src/ctfnote/parsers/pico.ts +++ b/front/src/ctfnote/parsers/pico.ts @@ -1,5 +1,5 @@ import { ParsedTask, Parser } from '.'; -import { parseJson, parseJsonStrict } from '../utils'; +import { parseJsonStrict } from '../utils'; const PicoParser: Parser = { name: 'picoCTF parser', @@ -21,18 +21,6 @@ const PicoParser: Parser = { } return tasks; }, - isValid(s) { - const data = parseJson<{ - results: Array<{ name: string; category: { name: string } }>; - }>(s); - return ( - data?.results != null && - Array.isArray(data?.results) && - data?.results.length > 0 && - data?.results[0].name != null && - data?.results[0].category.name != null - ); - }, }; export default PicoParser; diff --git a/front/src/ctfnote/parsers/raw.ts b/front/src/ctfnote/parsers/raw.ts index 22544f6a8..e57a3093c 100644 --- a/front/src/ctfnote/parsers/raw.ts +++ b/front/src/ctfnote/parsers/raw.ts @@ -19,12 +19,6 @@ const RawParser: Parser = { } return tasks; }, - isValid(s) { - return s - .trim() - .split('\n') - .every((s) => /[^|]+\|[^|]+/.exec(s)); - }, }; export default RawParser;