mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-22 22:37:18 +00:00

The checksum-stream being situated in front of the block-stream, which ensures block size alignment to multiples of 512, and pads the last block, caused the checksum to be incorrectly calculated for images where the last block needed to be padded. Change-Type: patch
159 lines
3.8 KiB
JavaScript
159 lines
3.8 KiB
JavaScript
/*
|
|
* Copyright 2017 resin.io
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
'use strict'
|
|
|
|
const stream = require('readable-stream')
|
|
const crypto = require('crypto')
|
|
const CRC32Stream = require('crc32-stream')
|
|
const _ = require('lodash')
|
|
|
|
/**
|
|
* @summary Get a hash stream
|
|
* @function
|
|
* @private
|
|
* @example
|
|
* var md5Stream = getHashStream('md5')
|
|
*
|
|
* @param {String} algorithm - either `crc32` or anything supported by `crypto.Hash`
|
|
* @returns {Stream.Transform}
|
|
*/
|
|
const getHashStream = (algorithm) => {
|
|
if (algorithm === 'crc32') {
|
|
return new CRC32Stream()
|
|
}
|
|
return crypto.createHash(algorithm)
|
|
}
|
|
|
|
/**
|
|
* @summary Create an instance of ChecksumStream
|
|
* @name ChecksumStream
|
|
* @class
|
|
*/
|
|
class ChecksumStream extends stream.Transform {
|
|
/**
|
|
* @summary Create an instance of ChecksumStream
|
|
* @name ChecksumStream
|
|
* @class
|
|
* @param {Object} options - options
|
|
* @param {String[]} options.algorithms - hash algorithms
|
|
* @example
|
|
* var checksum = new ChecksumStream({
|
|
* algorithms: [ 'crc32', 'md5' ]
|
|
* })
|
|
*
|
|
* checksum.once('checksum', (checksum) => {
|
|
* // checksum: {
|
|
* // crc32: 'EF28AF1C',
|
|
* // md5: ''
|
|
* // }
|
|
* })
|
|
*
|
|
* fs.createReadStream( 'os-image.img' )
|
|
* .pipe( checksum )
|
|
* .pipe( fs.createWriteStream( '/dev/rdisk2' ) )
|
|
* .once( 'finish', () => { ... })
|
|
*/
|
|
constructor (options = {}) {
|
|
super(options)
|
|
this.results = {}
|
|
this.algorithms = options.algorithms || []
|
|
this.hashes = _.map(this.algorithms, (algorithm) => {
|
|
return this._createHash(algorithm)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @summary Create & pipe to the Hash streams
|
|
* @private
|
|
* @param {String[]} algorithm - hash algorithm
|
|
* @returns {Stream}
|
|
* @example
|
|
* const hash = this._createHash(algorithm)
|
|
*/
|
|
_createHash (algorithm) {
|
|
const hash = _.attempt(getHashStream, algorithm)
|
|
|
|
if (_.isError(hash)) {
|
|
hash.message += ` "${algorithm}"`
|
|
throw hash
|
|
}
|
|
|
|
/**
|
|
* @summary Check for all checksums to have been calculated
|
|
* @private
|
|
* @example
|
|
* hash.once('end', check)
|
|
*/
|
|
const check = () => {
|
|
if (_.keys(this.results).length === this.algorithms.length) {
|
|
this.emit('checksum', _.clone(this.results))
|
|
}
|
|
}
|
|
|
|
hash.once('error', (error) => {
|
|
return this.emit('error', error)
|
|
})
|
|
|
|
if (algorithm === 'crc32') {
|
|
hash.once('end', () => {
|
|
this.results[algorithm] = hash.digest('hex')
|
|
check()
|
|
})
|
|
hash.resume()
|
|
} else {
|
|
hash.once('readable', () => {
|
|
this.results[algorithm] = hash.read().toString('hex')
|
|
check()
|
|
})
|
|
}
|
|
|
|
return hash
|
|
}
|
|
|
|
/**
|
|
* @summary Pass through chunks
|
|
* @private
|
|
* @param {Buffer} chunk - chunk
|
|
* @param {String} encoding - encoding
|
|
* @param {Function} next - callback
|
|
* @example
|
|
* checksumStream.write(buffer)
|
|
*/
|
|
_transform (chunk, encoding, next) {
|
|
for (let index = 0; index < this.hashes.length; index += 1) {
|
|
this.hashes[index].write(chunk)
|
|
}
|
|
next(null, chunk)
|
|
}
|
|
|
|
/**
|
|
* @summary End the hash streams once this stream ends
|
|
* @private
|
|
* @param {Function} done - callback
|
|
* @example
|
|
* checksumStream.end()
|
|
*/
|
|
_flush (done) {
|
|
for (let index = 0; index < this.hashes.length; index += 1) {
|
|
this.hashes[index].end()
|
|
}
|
|
done()
|
|
}
|
|
}
|
|
|
|
module.exports = ChecksumStream
|