Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "^5.0.0",
"@ccx-public/ingest": "file:etc/packages/ingest-4.3.0.tgz",
"@contentauth/c2pa-web": "^0.7.1",
"@contentauth/c2pa-web": "^0.11.0",
"@intl/adobe-locales": "file:etc/packages/adobe-locales-2.35.2.tgz",
"@mapbox/mapbox-sdk": "^0.13.7",
"@netlify/functions": "^2.1.0",
Expand Down
48 changes: 13 additions & 35 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 59 additions & 16 deletions src/lib/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ import {
} from './selectors/validationResult';
import { selectWeb3 } from './selectors/web3Info';
import { selectWebsite } from './selectors/website';
import { loadThumbnail, type ThumbnailInfo } from './thumbnail';
import { toAbsoluteIdentifier } from './resolveThumbnails';
import {
loadThumbnail,
type ThumbnailInfo,
type ThumbnailResult,
} from './thumbnail';
import type { Disposable } from './types';

const MANIFEST_STORE_MIME_TYPE = 'application/x-c2pa-manifest-store';
Expand Down Expand Up @@ -132,9 +137,11 @@ export function getIngredientDataType(
export async function resultToAssetMap({
manifestStore,
source,
thumbnails,
}: {
manifestStore: ManifestStore;
source: Blob | File;
thumbnails: Map<string, Blob>;
}): Promise<DisposableAssetDataMap> {
const assetMap: AssetDataMap = {};
const disposers: (() => void)[] = [];
Expand Down Expand Up @@ -177,6 +184,26 @@ export async function resultToAssetMap({
}
}

async function lookupThumbnail(
ref: Thumbnail | null | undefined,
containingManifestLabel: string,
): Promise<ThumbnailResult> {
const blob = ref?.identifier
? thumbnails.get(toAbsoluteIdentifier(ref.identifier, containingManifestLabel))
: undefined;

if (!blob) {
return loadThumbnail(ref?.format, undefined);
}

const url = URL.createObjectURL(blob);

return loadThumbnail(ref?.format, {
url,
dispose: () => URL.revokeObjectURL(url),
});
}

if (!isManifest && (!manifestStore || hasError || hasOtgp)) {
// Fallback for raw files: render the original source file directly
const url = URL.createObjectURL(source);
Expand Down Expand Up @@ -231,14 +258,11 @@ export async function resultToAssetMap({
runtimeValidationStatuses: ManifestLabelValidationStatusMap,
id: string,
): Promise<AssetData> {
const manifest = manifestStore.manifests?.[manifestStore.active_manifest || ''];
const activeManifestLabel = manifestStore.active_manifest || '';
const manifest = manifestStore.manifests?.[activeManifestLabel];
if (!manifest) throw new Error('Active manifest not found');

// 0.17.x SDK dropped internal thumbnail generation. Pass undefined to skip WASM fetch.
let thumbnail = await loadThumbnail(
manifest.thumbnail?.format,
undefined
);
let thumbnail = await lookupThumbnail(manifest.thumbnail, activeManifestLabel);

if (
!thumbnail.info &&
Expand All @@ -248,7 +272,7 @@ export async function resultToAssetMap({
// Fallback for active manifest: render the original source file directly
const url = URL.createObjectURL(source);
thumbnail = await loadThumbnail(
source.type,
source.type,
{ url, dispose: () => URL.revokeObjectURL(url) }
);
}
Expand All @@ -263,6 +287,7 @@ export async function resultToAssetMap({
manifestStore,
runtimeValidationStatuses,
id,
activeManifestLabel,
),
manifestData: await getManifestData(manifest, rootValidationResult),
dataType: null,
Expand All @@ -284,17 +309,32 @@ export async function resultToAssetMap({
manifestStore: ManifestStore,
runtimeValidationStatuses: ManifestLabelValidationStatusMap,
id: string,
containingManifestLabel: string,
): Promise<AssetData> {
const ingredientManifestLabel = ingredient.active_manifest || (ingredient as ExtendedIngredient).activeManifest;
const ingredientManifest = ingredientManifestLabel ? manifestStore.manifests?.[ingredientManifestLabel] : null;



// 0.17.x SDK dropped internal thumbnail generation. Skip WASM fetch for ingredients.
const thumbnail = await loadThumbnail(
ingredient.thumbnail?.format,
undefined,
);
// The code prioritizes the signed ingredient's own c2pa.thumbnail.claim, if present,
// over the c2pa.thumbnail.ingredient assertion that the consuming manifest's signer
// references via ingredient.thumbnail. When a signed ingredient already provides
// its own claim thumbnail, a consuming signer's separate ingredient assertion
// thumbnail could be an intentional override pointing at a misleading image. The
// claim thumbnail more faithfully represents the ingredient.
//
// When no claim thumbnail is available for an ingredient, we fall back to
// c2pa.thumbnail.ingredient only if the containing manifest is trusted. If it is
// untrusted, we suppress the thumbnail so an untrusted signer can't force a false
// thumbnail to display for an ingredient that has no claim thumbnail of its own. A
// signed ingredient's own claim thumbnail is still shown when present (untrusted
// state is flagged in the UI).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
 * The code prioritizes the signed ingredient's own c2pa.thumbnail.claim, if present,
 * over the c2pa.thumbnail.ingredient assertion that the consuming manifest's signer
 * references via ingredient.thumbnail. When a signed ingredient already provides
 * its own claim thumbnail, a consuming signer's separate ingredient assertion
 * thumbnail could be an intentional override pointing at a misleading image. The
 * claim thumbnail more faithfully represents the ingredient.
 *
 * When no claim thumbnail is available for an ingredient, we fall back to
 * c2pa.thumbnail.ingredient only if the containing manifest is trusted. If it is
 * untrusted, we suppress the thumbnail so an untrusted signer can't force a false
 * thumbnail to display for an ingredient that has no claim thumbnail of its own. A
 * signed ingredient's own claim thumbnail is still shown when present (untrusted
 * state is flagged in the UI)
 */

Can we use a multiline comment here to make it more readable

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MassivaM I just fixed this. Thanks!

const containingManifestUntrusted = (runtimeValidationStatuses[containingManifestLabel] ?? [])
.some((s) => s.code.includes('signingCredential.untrusted') || s.code.includes('signingCredential.invalid'));

const thumbnail = ingredientManifestLabel && ingredientManifest?.thumbnail
Comment thread
tmathern marked this conversation as resolved.
? await lookupThumbnail(ingredientManifest.thumbnail, ingredientManifestLabel)
: !containingManifestUntrusted
? await lookupThumbnail(ingredient.thumbnail, containingManifestLabel)
: await loadThumbnail(undefined, undefined);

const activeManifestValidationResults =
(ingredient.validation_results?.activeManifest || (ingredient as ExtendedIngredient).validationResults?.activeManifest) ?? undefined;
Expand All @@ -317,12 +357,13 @@ export async function resultToAssetMap({
title: ingredient.title ?? null,
thumbnail: thumbnail.info,
mimeType: ingredient.format || '',
children: (showChildren && ingredientManifest?.ingredients)
children: (showChildren && ingredientManifest?.ingredients && ingredientManifestLabel)
? await processIngredients(
ingredientManifest.ingredients,
manifestStore,
runtimeValidationStatuses,
id,
ingredientManifestLabel,
)
: [],
manifestData: await getManifestData(ingredientManifest, validationResult),
Expand Down Expand Up @@ -469,6 +510,7 @@ export async function resultToAssetMap({
manifestStore: ManifestStore,
runtimeValidationStatuses: ManifestLabelValidationStatusMap,
id: string,
containingManifestLabel: string,
): Promise<string[]> {
const ingredientIds = ingredients.map(async (ingredient, idx) => {
const ingredientId = `${id}.${idx}`;
Expand All @@ -478,6 +520,7 @@ export async function resultToAssetMap({
manifestStore,
runtimeValidationStatuses,
ingredientId,
containingManifestLabel,
);

return ingredientId;
Expand Down
Loading