diff --git a/docs/casfs/index.md b/docs/casfs/index.md index 57096d16b..1d33407dc 100644 --- a/docs/casfs/index.md +++ b/docs/casfs/index.md @@ -53,7 +53,7 @@ const blob2 = await cas.get(hash); You can also delete blobs: ```js -await cas.delete(hash); +await cas.del(hash); ``` And retrieve information about blobs: diff --git a/src/crud-to-cas/CrudCas.ts b/src/crud-to-cas/CrudCas.ts index 7291f01df..1fff7a610 100644 --- a/src/crud-to-cas/CrudCas.ts +++ b/src/crud-to-cas/CrudCas.ts @@ -1,5 +1,5 @@ import { hashToLocation } from './util'; -import type { CasApi } from '../cas/types'; +import type { CasApi, CasGetOptions } from '../cas/types'; import type { CrudApi, CrudResourceInfo } from '../crud/types'; export interface CrudCasOptions { @@ -31,10 +31,15 @@ export class CrudCas implements CasApi { return digest; }; - public readonly get = async (hash: string): Promise => { + public readonly get = async (hash: string, options?: CasGetOptions): Promise => { const [collection, resource] = hashToLocation(hash); return await normalizeErrors(async () => { - return await this.crud.get(collection, resource); + const blob = await this.crud.get(collection, resource); + if (!options?.skipVerification) { + const digest = await this.options.hash(blob); + if (hash !== digest) throw new DOMException('Blob contents does not match hash', 'Integrity'); + } + return blob; }); }; diff --git a/src/crud-to-cas/__tests__/CrudCas.test.ts b/src/crud-to-cas/__tests__/CrudCas.test.ts index c7224aea6..1f16a18c7 100644 --- a/src/crud-to-cas/__tests__/CrudCas.test.ts +++ b/src/crud-to-cas/__tests__/CrudCas.test.ts @@ -5,6 +5,7 @@ import { onlyOnNode20 } from '../../__tests__/util'; import { NodeFileSystemDirectoryHandle } from '../../node-to-fsa'; import { FsaCrud } from '../../fsa-to-crud/FsaCrud'; import { CrudCas } from '../CrudCas'; +import { hashToLocation } from '../util'; const hash = async (blob: Uint8Array): Promise => { const shasum = createHash('sha1'); @@ -53,6 +54,27 @@ onlyOnNode20('CrudCas', () => { expect(err).toBeInstanceOf(DOMException); expect((err).name).toBe('BlobNotFound'); }); + + test('throws if blob contents does not match the hash', async () => { + const blob = b('hello world'); + const { cas, crud } = setup(); + const hash = await cas.put(blob); + const location = hashToLocation(hash); + await crud.put(location[0], location[1], b('hello world!')); + const [, err] = await of(cas.get(hash)); + expect(err).toBeInstanceOf(DOMException); + expect((err).name).toBe('Integrity'); + }); + + test('does not throw if integrity check is skipped', async () => { + const blob = b('hello world'); + const { cas, crud } = setup(); + const hash = await cas.put(blob); + const location = hashToLocation(hash); + await crud.put(location[0], location[1], b('hello world!')); + const blob2 = await cas.get(hash, { skipVerification: true }); + expect(blob2).toStrictEqual(b('hello world!')); + }); }); describe('.info()', () => {