From 2d4143fde62a472b7c8058c22c22f232a6fe7eb3 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Mon, 1 Sep 2025 16:26:05 +0200 Subject: [PATCH] Use RE for proof extraxtion --- src/models/proof.ts | 118 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-) diff --git a/src/models/proof.ts b/src/models/proof.ts index a3019ff..679d0e8 100644 --- a/src/models/proof.ts +++ b/src/models/proof.ts @@ -72,6 +72,11 @@ type FlagMatchRule = { description: string; }; +type FlagMatchRuleValue = { + path: string; + value: string; +}; + type FlagMatchInfo = { field: string; value: string; @@ -87,13 +92,35 @@ const ruleScreenshot = (): FlagMatchRule[] => { return [ { field: - "integrity/c2pa/manifest_info/manifests[]/assertions[]/{label=c2pa.actions}/data/actions[]/{action=c2pa.created}/digitalSourceType", + "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]/digitalSourceType", match: [C2PASourceTypeScreenCapture], description: "Screen capture", }, ]; }; +const ruleCamera = (): FlagMatchRule[] => { + return [ + { + field: + "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]/digitalSourceType", + match: [C2PASourceTypeDigitalCapture, C2PASourceTypeComputationalCapture, C2PASourceTypeCompositeCapture], + description: "Captured by camera", + }, + ]; +}; + +const ruleAiGenerated = (): FlagMatchRule[] => { + return [ + { + field: + "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]/digitalSourceType", + match: [C2PASourceTypeTrainedAlgorithmicMedia, C2PASourceTypeCompositeWithTrainedAlgorithmicMedia], + description: "Generated by AI", + }, + ]; +}; + const aiHintFlags = (): FlagMatchRule[] => { const knownAIServices = [ "ChatGPT", @@ -122,13 +149,22 @@ const matchFlag = (rules: FlagMatchRule[], file: any) => { const matchParts = (parts: string[], o: any, re: RegExp[]): FlagMatch[] | undefined => { if (parts.length == 0 || o == undefined) return undefined; let part = parts[0]; - if (part.endsWith("[]")) { - part = part.substring(0, part.length - 2); + const lastBracket = part.lastIndexOf("["); + if (part.endsWith("]") && lastBracket > 0) { + const optionalConstraint = part.substring(lastBracket + 1, part.length - 1); + part = part.substring(0, lastBracket); if (o[part] != undefined) { let opart: any[] = o[part]; if (!Array.isArray(opart)) { opart = Object.values(opart) ?? []; } + + // Any constraints controlling what array object(s) to consider? + if (optionalConstraint) { + const [prop, val] = optionalConstraint.split("="); + opart = opart.filter((item) => item[prop] === val); + } + if (parts.length == 1) { let strings = opart as string[]; if (!strings) return undefined; @@ -156,13 +192,6 @@ const matchFlag = (rules: FlagMatchRule[], file: any) => { } else { return undefined; } - } else if (part.startsWith("{") && part.endsWith("}")) { - const [prop, val] = part.substring(1, part.length - 1).split("="); - if (o[prop] === val) { - return matchParts(parts.slice(1), o, re); - } else { - return undefined; - } } else { if (o[part] != undefined) { if (parts.length == 1) { @@ -203,6 +232,71 @@ const matchFlag = (rules: FlagMatchRule[], file: any) => { return { result: result, matches: resultInfo }; }; +const extractFlagValues = (flagPath: string, file: any): FlagMatchRuleValue[] => { + const getValues = (path: string[], objectPath: any[], actualPath: string, o: any): FlagMatchRuleValue[] | undefined => { + if (path.length == 0 || o == undefined) return undefined; + let part = path[0]; + const lastBracket = part.lastIndexOf("["); + if (part === "..") { + return getValues(path.slice(1), objectPath.slice(1), actualPath + "/..", objectPath[0]); + } + if (part.endsWith("]") && lastBracket > 0) { + const optionalConstraint = part.substring(lastBracket + 1, part.length - 1); + part = part.substring(0, lastBracket); + if (o[part] != undefined) { + let opart: any[] = o[part]; + if (!Array.isArray(opart)) { + opart = Object.values(opart) ?? []; + } + + // Any constraints controlling what array object(s) to consider? + if (optionalConstraint) { + const [prop, val] = optionalConstraint.split("="); + opart = opart.filter((item) => item[prop] === val); + } + + if (path.length == 1) { + let strings = opart as string[]; + return strings.map((s, i) => ({ path: actualPath + "/" + part + "[" + i + "]", value: s })); + } else { + return opart.reduce((res: FlagMatchRuleValue[] | undefined, o: any, i: number) => { + const newObjectPaths = [o, ...objectPath]; + let matches = getValues(path.slice(1), newObjectPaths, actualPath + "/" + part + "[" + i + "]", o); + if (matches) { + const r2 = res || []; + r2.push(...matches); + return r2; + } + return res; + }, undefined); + } + } else { + return undefined; + } + } else { + if (o[part] != undefined) { + if (path.length == 1) { + return [{ path: actualPath + "/" + part, value: o[part] }]; + } else { + const newObjectPaths = [o, ...objectPath]; + return getValues(path.slice(1), newObjectPaths, actualPath + "/" + part, o[part]); + } + } else { + return undefined; + } + } + }; + + let result: FlagMatchRuleValue[] = []; + try { + let parts = flagPath.split("/"); + result = getValues(parts, [], "", file) ?? []; + } catch (e) { + console.error("Invalid RE", e); + } + return result; +}; + export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined => { if (!proof) return undefined; @@ -258,6 +352,10 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined } } if (valid) { + + const dateCreated = extractFlagValues("integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]/../../../../signature_info/issuer", proof); + console.error("DATE CREATED", dateCreated); + screenshot = matchFlag(ruleScreenshot(), proof).result; const flags: ProofHintFlags = { aiGenerated,