2020
2121'use strict' ;
2222
23+ var crc = require ( 'fast-crc32c' ) ;
2324var crypto = require ( 'crypto' ) ;
2425var duplexify = require ( 'duplexify' ) ;
2526var request = require ( 'request' ) ;
@@ -213,6 +214,13 @@ File.prototype.copy = function(destination, callback) {
213214 * 911. If you receive this error, the best recourse is to try downloading the
214215 * file again.
215216 *
217+ * @param {object= } options - Configuration object.
218+ * @param {string|boolean } options.validation - Possible values: `"md5"`,
219+ * `"crc32c"`, or `false`. By default, data integrity is validated with an
220+ * MD5 checksum for maximum reliability. CRC32c will provide better
221+ * performance with less reliability. You may also choose to skip validation
222+ * completely, however this is **not recommended**.
223+ *
216224 * @example
217225 * //-
218226 * // <h4>Downloading a File</h4>
@@ -228,19 +236,45 @@ File.prototype.copy = function(destination, callback) {
228236 * .pipe(fs.createWriteStream('/Users/stephen/Photos/image.png'))
229237 * .on('error', function(err) {});
230238 */
231- File . prototype . createReadStream = function ( ) {
239+ File . prototype . createReadStream = function ( options ) {
240+ options = options || { } ;
241+
232242 var that = this ;
233243 var throughStream = through ( ) ;
234244
235- this . getMetadata ( function ( err , metadata ) {
236- if ( err ) {
237- throughStream . emit ( 'error' , err ) ;
238- throughStream . end ( ) ;
239- return ;
245+ var validations = [ 'crc32c' , 'md5' ] ;
246+ var validation ;
247+
248+ if ( util . is ( options . validation , 'string' ) ) {
249+ options . validation = options . validation . toLowerCase ( ) ;
250+
251+ if ( validations . indexOf ( options . validation ) > - 1 ) {
252+ validation = options . validation ;
253+ } else {
254+ validation = 'all' ;
240255 }
256+ }
241257
242- createAuthorizedReq ( metadata . mediaLink ) ;
243- } ) ;
258+ if ( util . is ( options . validation , 'undefined' ) ) {
259+ validation = 'all' ;
260+ }
261+
262+ var crc32c = validation === 'crc32c' || validation === 'all' ;
263+ var md5 = validation === 'md5' || validation === 'all' ;
264+
265+ if ( this . metadata . mediaLink ) {
266+ createAuthorizedReq ( this . metadata . mediaLink ) ;
267+ } else {
268+ this . getMetadata ( function ( err , metadata ) {
269+ if ( err ) {
270+ throughStream . emit ( 'error' , err ) ;
271+ throughStream . end ( ) ;
272+ return ;
273+ }
274+
275+ createAuthorizedReq ( metadata . mediaLink ) ;
276+ } ) ;
277+ }
244278
245279 return throughStream ;
246280
@@ -261,6 +295,7 @@ File.prototype.createReadStream = function() {
261295
262296 // For data integrity, hash the contents of the stream as we receive it
263297 // from the server.
298+ var localCrc32cHash ;
264299 var localMd5Hash = crypto . createHash ( 'md5' ) ;
265300
266301 request ( authorizedReqOpts )
@@ -270,15 +305,46 @@ File.prototype.createReadStream = function() {
270305 } )
271306
272307 . on ( 'data' , function ( chunk ) {
273- localMd5Hash . update ( chunk ) ;
308+ if ( crc32c ) {
309+ localCrc32cHash = crc . calculate ( chunk , localCrc32cHash ) ;
310+ }
311+
312+ if ( md5 ) {
313+ localMd5Hash . update ( chunk ) ;
314+ }
274315 } )
275316
276- . on ( 'complete' , function ( ) {
277- localMd5Hash = localMd5Hash . digest ( 'base64' ) ;
317+ . on ( 'complete' , function ( res ) {
318+ var failed = false ;
278319
279- if ( that . metadata . md5Hash === localMd5Hash ) {
280- throughStream . emit ( 'complete' ) ;
281- } else {
320+ if ( crc32c ) {
321+ localCrc32cHash = new Buffer ( [ localCrc32cHash ] ) ;
322+ localCrc32cHash = localCrc32cHash . toString ( 'base64' ) ;
323+ }
324+
325+ if ( md5 ) {
326+ localMd5Hash = localMd5Hash . digest ( 'base64' ) ;
327+ }
328+
329+ var hashes = { } ;
330+ res . headers [ 'x-goog-hash' ] . split ( ',' ) . forEach ( function ( hash ) {
331+ var hashType = hash . split ( '=' ) [ 0 ] ;
332+ hashes [ hashType ] = hash . substr ( hash . indexOf ( '=' ) + 1 ) ;
333+ } ) ;
334+
335+ if ( validation === 'all' ) {
336+ if ( hashes . md5 ) {
337+ failed = localMd5Hash !== hashes . md5 ;
338+ } else if ( hashes . crc32 ) {
339+ failed = localCrc32cHash !== hashes . crc32c . substr ( 4 ) ;
340+ }
341+ } else if ( md5 ) {
342+ failed = localMd5Hash !== hashes . md5 ;
343+ } else if ( crc32c ) {
344+ failed = localCrc32cHash !== hashes . crc32c . substr ( 4 ) ;
345+ }
346+
347+ if ( failed ) {
282348 var error = new Error ( {
283349 code : 911 ,
284350 message : [
@@ -289,6 +355,8 @@ File.prototype.createReadStream = function() {
289355 } ) ;
290356
291357 throughStream . emit ( 'error' , error ) ;
358+ } else {
359+ throughStream . emit ( 'complete' ) ;
292360 }
293361
294362 throughStream . end ( ) ;
0 commit comments