Skip to content

Commit 320b5d1

Browse files
committed
checksum ref tus#347
1 parent 3cd7c72 commit 320b5d1

File tree

4 files changed

+66
-1
lines changed

4 files changed

+66
-1
lines changed

lib/browser/extensions/checksum.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export type AlgorithmsTypes = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'
2+
3+
export interface ChecksumInterface {
4+
algorithm: AlgorithmIdentifier;
5+
getHexDigest(data: ArrayBuffer): Promise<string>;
6+
}
7+
8+
class Checksum implements ChecksumInterface {
9+
static supportedAlgorithms: AlgorithmsTypes[] = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'];
10+
public algorithm: AlgorithmIdentifier;
11+
12+
constructor(algo: AlgorithmsTypes = 'SHA-256') {
13+
// Checking support for crypto on the browser
14+
if (!crypto) {
15+
throw new Error(`tus: this browser does not support checksum`)
16+
} else if (!Checksum.supportedAlgorithms.includes(algo)) {
17+
throw new Error(
18+
`tus: unsupported checksumAlgo provided. Supported values are : ${Checksum.supportedAlgorithms.join(
19+
',',
20+
)}`,
21+
)
22+
} else {
23+
this.algorithm = algo
24+
}
25+
}
26+
27+
getHexDigest = async (data: ArrayBuffer) => {
28+
try {
29+
const hashBuffer = await crypto.subtle.digest(this.algorithm, data)
30+
const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array
31+
const hashHex = hashArray
32+
.map((b) => b.toString(16).padStart(2, '0'))
33+
.join('') // convert bytes to hex string
34+
return hashHex
35+
} catch (err) {
36+
throw new Error('tus: could not compute checksum for integrity check ' + err)
37+
}
38+
};
39+
}
40+
41+
export default Checksum

lib/browser/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import { BrowserFileReader } from './BrowserFileReader.js'
88
import { XHRHttpStack as DefaultHttpStack } from './XHRHttpStack.js'
99
import { fingerprint } from './fileSignature.js'
1010
import { WebStorageUrlStorage, canStoreURLs } from './urlStorage.js'
11+
import Checksum from './extensions/checksum.js'
1112

1213
const defaultOptions = {
1314
...baseDefaultOptions,
1415
httpStack: new DefaultHttpStack(),
1516
fileReader: new BrowserFileReader(),
1617
urlStorage: canStoreURLs ? new WebStorageUrlStorage() : new NoopUrlStorage(),
18+
checksum: new Checksum(),
1719
fingerprint,
1820
}
1921

lib/options.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReadStream } from 'node:fs'
22
import type { Readable } from 'node:stream'
33
import type { DetailedError } from './DetailedError.js'
4+
import {AlgorithmsTypes, ChecksumInterface} from "./browser/extensions/checksum.js";
45

56
export const PROTOCOL_TUS_V1 = 'tus-v1'
67
export const PROTOCOL_IETF_DRAFT_03 = 'ietf-draft-03'
@@ -61,10 +62,12 @@ export interface UploadOptions {
6162
removeFingerprintOnSuccess: boolean
6263
uploadLengthDeferred: boolean
6364
uploadDataDuringCreation: boolean
65+
checksumAlgorithm?: AlgorithmsTypes
6466

6567
urlStorage: UrlStorage
6668
fileReader: FileReader
6769
httpStack: HttpStack
70+
checksum?: ChecksumInterface
6871

6972
protocol: typeof PROTOCOL_TUS_V1 | typeof PROTOCOL_IETF_DRAFT_03 | typeof PROTOCOL_IETF_DRAFT_05
7073
}
@@ -83,6 +86,7 @@ export interface UrlStorage {
8386
addUpload(fingerprint: string, upload: PreviousUpload): Promise<string | undefined>
8487
}
8588

89+
8690
export interface PreviousUpload {
8791
size: number | null
8892
metadata: { [key: string]: string }

lib/upload.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
type UploadOptions,
1717
} from './options.js'
1818
import { uuid } from './uuid.js'
19+
import Checksum from "./browser/extensions/checksum.js";
1920

2021
export const defaultOptions = {
2122
endpoint: undefined,
@@ -47,10 +48,12 @@ export const defaultOptions = {
4748
removeFingerprintOnSuccess: false,
4849
uploadLengthDeferred: false,
4950
uploadDataDuringCreation: false,
51+
checksumAlgo: undefined,
5052

5153
urlStorage: undefined,
5254
fileReader: undefined,
5355
httpStack: undefined,
56+
checksum: undefined,
5457

5558
protocol: PROTOCOL_TUS_V1 as UploadOptions['protocol'],
5659
}
@@ -104,6 +107,8 @@ export class BaseUpload {
104107
// parts, if the parallelUploads option is used.
105108
private _parallelUploadUrls?: string[]
106109

110+
private _checksum ?: Checksum
111+
107112
constructor(file: UploadInput, options: UploadOptions) {
108113
// Warn about removed options from previous versions
109114
if ('resume' in options) {
@@ -585,7 +590,10 @@ export class BaseUpload {
585590
if (!this.options.endpoint) {
586591
throw new Error('tus: unable to create upload because no endpoint is provided')
587592
}
588-
593+
if (this.options.checksumAlgorithm) {
594+
// @ts-ignore
595+
this._checksum = new this.options.checksum(this.options.checksumAlgorithm)
596+
}
589597
const req = this._openRequest('POST', this.options.endpoint)
590598

591599
if (this.options.uploadLengthDeferred) {
@@ -882,6 +890,16 @@ export class BaseUpload {
882890
) {
883891
req.setHeader('Upload-Complete', done ? '?1' : '?0')
884892
}
893+
894+
if (this.options.checksumAlgorithm) {
895+
if (this.options.checksum) {
896+
// @ts-ignore
897+
let chunkBuffer = await value.arrayBuffer()
898+
let hash = this.options.checksum.getHexDigest(chunkBuffer);
899+
req.setHeader('Upload-Checksum', `${this.options.checksum.algorithm} ${hash}`)
900+
}
901+
}
902+
885903
this._emitProgress(this._offset, this._size)
886904
return await this._sendRequest(req, value)
887905
}

0 commit comments

Comments
 (0)