Skip to content

Commit 17e510b

Browse files
aknuds1stephenplusplus
authored andcommitted
Append newline to end of extensionHeaders if necessary
1 parent 4318829 commit 17e510b

2 files changed

Lines changed: 76 additions & 28 deletions

File tree

lib/storage/file.js

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,25 +1144,25 @@ File.prototype.getSignedPolicy = function(options, callback) {
11441144
*
11451145
* @throws {Error} if an expiration timestamp from the past is given.
11461146
*
1147-
* @param {object} options - Configuration object.
1148-
* @param {string} options.action - "read" (HTTP: GET), "write" (HTTP: PUT), or
1147+
* @param {object} config - Configuration object.
1148+
* @param {string} config.action - "read" (HTTP: GET), "write" (HTTP: PUT), or
11491149
* "delete" (HTTP: DELETE).
1150-
* @param {string=} options.contentMd5 - The MD5 digest value in base64. If you
1150+
* @param {string=} config.contentMd5 - The MD5 digest value in base64. If you
11511151
* provide this, the client must provide this HTTP header with this same
11521152
* value in its request.
1153-
* @param {string=} options.contentType - If you provide this value, the client
1153+
* @param {string=} config.contentType - If you provide this value, the client
11541154
* must provide this HTTP header set to the same value.
1155-
* @param {*} options.expires - A timestamp when this link will expire. Any
1156-
* value given is passed to `new Date()`.
1157-
* @param {string=} options.extensionHeaders - If these headers are used, the
1155+
* @param {*} config.expires - A timestamp when this link will expire. Any value
1156+
* given is passed to `new Date()`.
1157+
* @param {object=} config.extensionHeaders - If these headers are used, the
11581158
* server will check to make sure that the client provides matching values.
1159-
* @param {string=} options.promptSaveAs - The filename to prompt the user to
1159+
* @param {string=} config.promptSaveAs - The filename to prompt the user to
11601160
* save the file as when the signed url is accessed. This is ignored if
1161-
* options.responseDisposition is set.
1162-
* @param {string=} options.responseDisposition - The
1161+
* `config.responseDisposition` is set.
1162+
* @param {string=} config.responseDisposition - The
11631163
* [response-content-disposition parameter](http://goo.gl/yMWxQV) of the
11641164
* signed url.
1165-
* @param {string=} options.responseType - The response-content-type parameter
1165+
* @param {string=} config.responseType - The response-content-type parameter
11661166
* of the signed url.
11671167
* @param {function=} callback - The callback function.
11681168
* @param {?error} callback.err - An error returned while making this request
@@ -1215,26 +1215,26 @@ File.prototype.getSignedPolicy = function(options, callback) {
12151215
* });
12161216
* });
12171217
*/
1218-
File.prototype.getSignedUrl = function(options, callback) {
1219-
var expires = new Date(options.expires);
1218+
File.prototype.getSignedUrl = function(config, callback) {
1219+
var expires = new Date(config.expires);
12201220
var expiresInSeconds = Math.round(expires / 1000); // The API expects seconds.
12211221

12221222
if (expires < Date.now()) {
12231223
throw new Error('An expiration date cannot be in the past.');
12241224
}
12251225

1226-
options = extend({}, options);
1226+
config = extend({}, config);
12271227

1228-
options.action = {
1228+
config.action = {
12291229
read: 'GET',
12301230
write: 'PUT',
12311231
delete: 'DELETE'
1232-
}[options.action];
1232+
}[config.action];
12331233

12341234
var name = encodeURIComponent(this.name);
12351235
var targetGeneration = this.generation;
12361236

1237-
options.resource = '/' + this.bucket.name + '/' + name;
1237+
config.resource = '/' + this.bucket.name + '/' + name;
12381238

12391239
this.storage.getCredentials(function(err, credentials) {
12401240
if (err) {
@@ -1252,33 +1252,44 @@ File.prototype.getSignedUrl = function(options, callback) {
12521252
return;
12531253
}
12541254

1255+
var extensionHeadersString = '';
1256+
1257+
if (config.extensionHeaders) {
1258+
for (var headerName in config.extensionHeaders) {
1259+
extensionHeadersString += format('{name}:{value}\n', {
1260+
name: headerName,
1261+
value: config.extensionHeaders[headerName],
1262+
});
1263+
}
1264+
}
1265+
12551266
var sign = crypto.createSign('RSA-SHA256');
12561267
sign.update([
1257-
options.action,
1258-
(options.contentMd5 || ''),
1259-
(options.contentType || ''),
1268+
config.action,
1269+
(config.contentMd5 || ''),
1270+
(config.contentType || ''),
12601271
expiresInSeconds,
1261-
(options.extensionHeaders || '') + options.resource
1272+
extensionHeadersString + config.resource
12621273
].join('\n'));
12631274
var signature = sign.sign(credentials.private_key, 'base64');
12641275

12651276
var responseContentType = '';
1266-
if (is.string(options.responseType)) {
1277+
if (is.string(config.responseType)) {
12671278
responseContentType =
12681279
'&response-content-type=' +
1269-
encodeURIComponent(options.responseType);
1280+
encodeURIComponent(config.responseType);
12701281
}
12711282

12721283
var responseContentDisposition = '';
1273-
if (is.string(options.promptSaveAs)) {
1284+
if (is.string(config.promptSaveAs)) {
12741285
responseContentDisposition =
12751286
'&response-content-disposition=attachment; filename="' +
1276-
encodeURIComponent(options.promptSaveAs) + '"';
1287+
encodeURIComponent(config.promptSaveAs) + '"';
12771288
}
1278-
if (is.string(options.responseDisposition)) {
1289+
if (is.string(config.responseDisposition)) {
12791290
responseContentDisposition =
12801291
'&response-content-disposition=' +
1281-
encodeURIComponent(options.responseDisposition);
1292+
encodeURIComponent(config.responseDisposition);
12821293
}
12831294

12841295
var generation = '';
@@ -1287,7 +1298,7 @@ File.prototype.getSignedUrl = function(options, callback) {
12871298
}
12881299

12891300
callback(null, [
1290-
'https://storage.googleapis.com' + options.resource,
1301+
'https://storage.googleapis.com' + config.resource,
12911302
'?GoogleAccessId=' + credentials.client_email,
12921303
'&Expires=' + expiresInSeconds,
12931304
'&Signature=' + encodeURIComponent(signature),

test/storage/file.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var stream = require('stream');
2828
var through = require('through2');
2929
var tmp = require('tmp');
3030
var url = require('url');
31+
var crypto = require('crypto');
3132

3233
var Bucket = require('../../lib/storage/bucket.js');
3334
var ServiceObject = require('../../lib/common/service-object.js');
@@ -1913,6 +1914,42 @@ describe('File', function() {
19131914
}, /cannot be in the past/);
19141915
});
19151916
});
1917+
1918+
describe('extensionHeaders', function() {
1919+
it('should add headers to signature', function(done) {
1920+
var extensionHeaders = {
1921+
'x-goog-acl': 'public-read',
1922+
'x-foo': 'bar'
1923+
};
1924+
1925+
var expires = Date.now() + 5;
1926+
var expiresInSeconds = Math.round(expires / 1000);
1927+
var name = encodeURIComponent(directoryFile.name);
1928+
var resource = '/' + directoryFile.bucket.name + '/' + name;
1929+
1930+
var sign = crypto.createSign('RSA-SHA256');
1931+
1932+
sign.update([
1933+
'GET',
1934+
'',
1935+
'',
1936+
expiresInSeconds,
1937+
'x-goog-acl:public-read\nx-foo:bar\n' + resource
1938+
].join('\n'));
1939+
1940+
var expSignature = sign.sign(credentials.private_key, 'base64');
1941+
1942+
directoryFile.getSignedUrl({
1943+
action: 'read',
1944+
expires: expires,
1945+
extensionHeaders: extensionHeaders
1946+
}, function(err, signedUrl) {
1947+
assert.ifError(err);
1948+
assert(signedUrl.indexOf(encodeURIComponent(expSignature)) > -1);
1949+
done();
1950+
});
1951+
});
1952+
});
19161953
});
19171954

19181955
describe('makePrivate', function() {

0 commit comments

Comments
 (0)