Skip to content

Commit 64aa8dc

Browse files
storage: download integrity check
1 parent 7b4e843 commit 64aa8dc

File tree

3 files changed

+82
-42
lines changed

3 files changed

+82
-42
lines changed

lib/storage/file.js

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var crypto = require('crypto');
2424
var duplexify = require('duplexify');
2525
var request = require('request');
2626
var streamEvents = require('stream-events');
27+
var through = require('through2');
2728

2829
/**
2930
* @type module:common/util
@@ -172,7 +173,6 @@ File.prototype.copy = function(destination, callback) {
172173
});
173174
};
174175

175-
176176
/**
177177
* Create a readable stream to read the contents of the remote file. It can be
178178
* piped to a writable stream or listened to for 'data' events to read a file's
@@ -194,34 +194,67 @@ File.prototype.copy = function(destination, callback) {
194194
* .on('error', function(err) {});
195195
*/
196196
File.prototype.createReadStream = function() {
197-
var storage = this.bucket.storage;
198-
var dup = duplexify();
197+
var that = this;
198+
var throughStream = through();
199+
200+
this.getMetadata(function(err, metadata) {
201+
if (err) {
202+
throughStream.emit('error', err);
203+
throughStream.end();
204+
return;
205+
}
206+
207+
createAuthorizedReq(metadata.mediaLink);
208+
});
209+
210+
return throughStream;
211+
212+
// Authenticate the request, then pipe the remote API request to the stream
213+
// returned to the user.
199214
function createAuthorizedReq(uri) {
200-
var reqOpts = { uri: uri };
201-
storage.makeAuthorizedRequest_(reqOpts, {
215+
var reqOpts = {
216+
uri: uri
217+
};
218+
219+
that.bucket.storage.makeAuthorizedRequest_(reqOpts, {
202220
onAuthorized: function(err, authorizedReqOpts) {
203221
if (err) {
204-
dup.emit('error', err);
205-
dup.end();
222+
throughStream.emit('error', err);
223+
throughStream.end();
206224
return;
207225
}
208-
dup.setReadable(request(authorizedReqOpts));
209-
}
210-
});
211-
}
212-
if (this.metadata.mediaLink) {
213-
createAuthorizedReq(this.metadata.mediaLink);
214-
} else {
215-
this.getMetadata(function(err, metadata) {
216-
if (err) {
217-
dup.emit('error', err);
218-
dup.end();
219-
return;
226+
227+
// For data integrity, hash the contents of the stream as we receive it
228+
// from the server.
229+
var localMd5Hash = crypto.createHash('md5');
230+
231+
request(authorizedReqOpts)
232+
.on('data', function(chunk) {
233+
localMd5Hash.update(chunk);
234+
})
235+
236+
.on('complete', function() {
237+
localMd5Hash = localMd5Hash.digest('base64');
238+
239+
if (that.metadata.md5Hash === localMd5Hash) {
240+
throughStream.emit('complete');
241+
} else {
242+
var error = new Error([
243+
'The downloaded data did not match the data from the server. ',
244+
'To be sure the content is the same, you should download the',
245+
'file again.'
246+
].join(''));
247+
248+
throughStream.emit('error', error);
249+
}
250+
251+
throughStream.end();
252+
})
253+
254+
.pipe(throughStream);
220255
}
221-
createAuthorizedReq(metadata.mediaLink);
222256
});
223257
}
224-
return dup;
225258
};
226259

227260
/**

regression/storage.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('storage', function() {
142142
});
143143
});
144144

145-
it('should read/write from/to a file in a directory', function(done) {
145+
it.only('should read/write from/to a file in a directory', function(done) {
146146
var file = bucket.file('directory/file');
147147
var contents = 'test';
148148

@@ -152,12 +152,17 @@ describe('storage', function() {
152152

153153
writeStream.on('error', done);
154154
writeStream.on('complete', function() {
155+
var data = new Buffer('');
156+
155157
file.createReadStream()
158+
.on('error', done)
156159
.on('data', function(chunk) {
157-
assert.equal(String(chunk), contents);
160+
data = Buffer.concat([data, chunk]);
158161
})
159-
.on('error', done)
160-
.on('end', done);
162+
.on('complete', function() {
163+
assert.equal(data.toString(), contents);
164+
done();
165+
});
161166
});
162167
});
163168

test/storage/file.js

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var duplexify = require('duplexify');
2424
var extend = require('extend');
2525
var nodeutil = require('util');
2626
var request = require('request');
27+
var stream = require('stream');
2728
var url = require('url');
2829
var util = require('../../lib/common/util');
2930

@@ -243,13 +244,15 @@ describe('File', function() {
243244
});
244245

245246
it('should create an authorized request', function(done) {
246-
request_Override = function(opts) {
247+
file.bucket.storage.makeAuthorizedRequest_ = function(opts) {
247248
assert.equal(opts.uri, metadata.mediaLink);
248249
done();
249250
};
251+
250252
file.getMetadata = function(callback) {
251253
callback(null, metadata);
252254
};
255+
253256
file.createReadStream();
254257
});
255258

@@ -272,33 +275,32 @@ describe('File', function() {
272275

273276
it('should get readable stream from request', function(done) {
274277
var fakeRequest = { a: 'b', c: 'd' };
275-
file.getMetadata = function(callback) {
276-
callback(null, metadata);
277-
};
278+
279+
// Faking a stream implementation so we can simulate an actual Request
280+
// request. The only thing we want to know is if the data passed to
281+
// request was correct.
278282
request_Override = function(req) {
283+
if (!(this instanceof request_Override)) {
284+
return new request_Override(req);
285+
}
286+
287+
stream.Readable.call(this);
288+
this._read = util.noop;
289+
279290
assert.deepEqual(req, fakeRequest);
280291
done();
281292
};
282-
file.bucket.storage.makeAuthorizedRequest_ = function(opts, callback) {
283-
(callback.onAuthorized || callback)(null, fakeRequest);
284-
};
285-
file.createReadStream();
286-
});
293+
nodeutil.inherits(request_Override, stream.Readable);
287294

288-
it('should set readable stream', function() {
289-
var dup = duplexify();
290295
file.getMetadata = function(callback) {
291296
callback(null, metadata);
292297
};
293-
request_Override = function() {
294-
return dup;
295-
};
298+
296299
file.bucket.storage.makeAuthorizedRequest_ = function(opts, callback) {
297-
(callback.onAuthorized || callback)();
300+
(callback.onAuthorized || callback)(null, fakeRequest);
298301
};
302+
299303
file.createReadStream();
300-
assert.deepEqual(readableStream, dup);
301-
readableStream = null;
302304
});
303305
});
304306

0 commit comments

Comments
 (0)