Skip to content

Commit 343938f

Browse files
duan007aPeterRao
authored andcommitted
feat: signatureUrl refactor and support callback (#408)
* feat: es6 compatibility & add API which used for url with signature * feat: signatureUrl support callback parameter * test: add test case for signatureUrl with callback * docs: update README * fix: fix issue about review
1 parent 02b0efd commit 343938f

File tree

5 files changed

+171
-111
lines changed

5 files changed

+171
-111
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,12 @@ parameters:
14541454
- [content-disposition] {String} set the response content disposition
14551455
- [cache-control] {String} set the response cache control
14561456
- See more: https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
1457+
- [callback] {Object} set the callback for the operation
1458+
- url {String} set the url for callback
1459+
- [host] {String} set the host for callback
1460+
- body {String} set the body for callback
1461+
- [contentType] {String} set the type for body
1462+
- [customValue] {Object} set the custom value for callback,eg. {var1: value1,var2:value2}
14571463
14581464
Success will return signature url.
14591465

lib/browser/object.js

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const copy = require('copy-to');
1111
const path = require('path');
1212
const mime = require('mime');
1313
const callback = require('../common/callback');
14+
const signHelper = require('../common/signUtils');
1415
// var assert = require('assert');
1516

1617

@@ -426,56 +427,31 @@ proto.getACL = function* getACL(name, options) {
426427
};
427428

428429
proto.signatureUrl = function (name, options) {
430+
options = options || {};
429431
name = this._objectName(name);
432+
options.method = options.method || 'GET';
433+
const expires = utility.timestamp() + (options.expires || 1800);
430434
const params = {
431435
bucket: this.options.bucket,
432436
object: name,
433437
};
434-
options = options || {};
435-
const expires = utility.timestamp() + (options.expires || 1800);
436-
let resource = this._getResource(params);
437-
const query = {};
438-
const signList = [];
439-
440-
if (options.response) {
441-
Object.keys(options.response).forEach((k) => {
442-
const key = `response-${k.toLowerCase()}`;
443-
query[key] = options.response[k];
444-
signList.push(`${key}=${options.response[k]}`);
445-
});
446-
}
447-
if (this.options.stsToken) {
448-
query['security-token'] = this.options.stsToken;
449-
signList.push(`security-token=${this.options.stsToken}`);
450-
}
451-
if (options.process) {
452-
const processKeyword = 'x-oss-process';
453-
query[processKeyword] = options.process;
454-
const item = `${processKeyword}=${options.process}`;
455-
signList.push(item);
456-
}
457438

458-
if (signList.length > 0) {
459-
signList.sort();
460-
resource += `?${signList.join('&')}`;
439+
const resource = this._getResource(params);
440+
441+
if (this.options.stsToken) {
442+
options['security-token'] = this.options.stsToken;
461443
}
462444

463-
const stringToSign = [
464-
options.method || 'GET',
465-
options['content-md5'] || '', // Content-MD5
466-
options['content-type'] || '', // Content-Type
467-
expires,
468-
resource,
469-
].join('\n');
470-
const signature = this.signature(stringToSign);
445+
const signRes = signHelper._signatureForURL(this.options.accessKeySecret, options, resource, expires);
471446

472447
const url = urlutil.parse(this._getReqUrl(params));
473448
url.query = {
474449
OSSAccessKeyId: this.options.accessKeyId,
475450
Expires: expires,
476-
Signature: signature,
451+
Signature: signRes.Signature,
477452
};
478-
copy(query).to(url.query);
453+
454+
copy(signRes.subResource).to(url.query);
479455

480456
return url.format();
481457
};

lib/common/signUtils.js

Lines changed: 118 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
'use strict';
21

3-
var crypto = require('crypto');
4-
var is = require('is-type-of');
2+
const crypto = require('crypto');
3+
const is = require('is-type-of');
54

65
/**
76
*
@@ -10,25 +9,29 @@ var is = require('is-type-of');
109
* @return
1110
*/
1211
exports.buildCanonicalizedResource = function buildCanonicalizedResource(resourcePath, parameters) {
13-
var canonicalizedResource = '' + resourcePath;
14-
var separatorString = '?';
12+
let canonicalizedResource = `${resourcePath}`;
13+
let separatorString = '?';
1514

1615
if (is.string(parameters) && parameters.trim() !== '') {
1716
canonicalizedResource += separatorString + parameters;
1817
} else if (is.array(parameters)) {
1918
parameters.sort();
2019
canonicalizedResource += separatorString + parameters.join('&');
2120
} else if (parameters) {
22-
var keys = Object.keys(parameters).sort();
23-
for (var key in keys) {
24-
var paramKey = keys[key];
25-
canonicalizedResource += separatorString + paramKey;
26-
var parameterValue = parameters[paramKey];
27-
if (parameterValue) {
28-
canonicalizedResource += '=' + parameterValue;
21+
const compareFunc = (entry1, entry2) => {
22+
if (entry1[0] >= entry2[0]) {
23+
return 1;
24+
}
25+
return 0;
26+
};
27+
const processFunc = ([key, value]) => {
28+
canonicalizedResource += separatorString + key;
29+
if (value) {
30+
canonicalizedResource += `=${value}`;
2931
}
3032
separatorString = '&';
31-
}
33+
};
34+
Object.entries(parameters).sort(compareFunc).forEach(processFunc);
3235
}
3336

3437
return canonicalizedResource;
@@ -42,42 +45,49 @@ exports.buildCanonicalizedResource = function buildCanonicalizedResource(resourc
4245
* @return {String} canonicalString
4346
*/
4447
exports.buildCanonicalString = function canonicalString(method, resourcePath, request, expires) {
45-
var HEADER_CONTENT_TYPE = 'content-type';
46-
var HEADER_CONTENT_MD5 = 'content-md5';
47-
var OSS_PREFIX = 'x-oss-';
48+
request = request || {};
49+
const headers = request.headers || {};
50+
const HEADER_CONTENT_TYPE = 'content-type';
51+
const HEADER_CONTENT_MD5 = 'content-md5';
52+
const OSS_PREFIX = 'x-oss-';
53+
const canonicalElements = [method];
54+
const headersToSign = {};
4855

49-
var headers = request.headers || {};
50-
var canonicalElements = [method];
51-
52-
var headersToSign = {};
53-
for (var key in headers) {
54-
var lowerKey = key.toLowerCase();
56+
Object.entries(headers).forEach(([key, value]) => {
57+
const lowerKey = key.toLowerCase();
5558
if (lowerKey === HEADER_CONTENT_TYPE
56-
|| lowerKey === HEADER_CONTENT_MD5
57-
|| lowerKey.indexOf(OSS_PREFIX) === 0) {
58-
headersToSign[lowerKey] = String(headers[key]).trim();
59+
|| lowerKey === HEADER_CONTENT_MD5
60+
|| lowerKey.indexOf(OSS_PREFIX) === 0) {
61+
headersToSign[lowerKey] = String(value).trim();
5962
}
60-
}
63+
});
6164

62-
if (!headersToSign.hasOwnProperty(HEADER_CONTENT_MD5)) {
65+
if (!Object.prototype.hasOwnProperty.call(headersToSign, HEADER_CONTENT_MD5)) {
6366
headersToSign.HEADER_CONTENT_MD5 = '';
6467
}
6568

66-
if (!headersToSign.hasOwnProperty(HEADER_CONTENT_TYPE)) {
69+
if (!Object.prototype.hasOwnProperty.call(headersToSign, HEADER_CONTENT_TYPE)) {
6770
headersToSign.HEADER_CONTENT_TYPE = '';
6871
}
6972

70-
var keys = Object.keys(headersToSign).sort();
71-
for (var key in keys) {
72-
var parameterName = keys[key];
73-
var parameterValue = headersToSign[parameterName];
74-
if (parameterName.indexOf(OSS_PREFIX) !== 0) {
75-
canonicalElements.push(parameterValue);
73+
const compareFunc = (a, b) => {
74+
if (a[0] >= b[0]) {
75+
return 1;
76+
}
77+
return 0;
78+
};
79+
80+
const processFunc = ([key, value]) => {
81+
if (key.indexOf(OSS_PREFIX) !== 0) {
82+
canonicalElements.push(value);
7683
} else {
77-
canonicalElements.push(parameterName + ':' + parameterValue);
84+
canonicalElements.push(`${key}:${value}`);
7885
}
79-
}
80-
expires = expires || headersToSign["x-oss-date"];
86+
};
87+
88+
Object.entries(headersToSign).sort(compareFunc).forEach(processFunc);
89+
90+
expires = expires || headersToSign['x-oss-date'];
8191
canonicalElements.splice(3, 0, expires);
8292

8393
canonicalElements.push(this.buildCanonicalizedResource(resourcePath, request.parameters));
@@ -90,7 +100,7 @@ exports.buildCanonicalString = function canonicalString(method, resourcePath, re
90100
* @param {String} canonicalString
91101
*/
92102
exports.computeSignature = function computeSignature(accessKeySecret, canonicalString) {
93-
var signature = crypto.createHmac('sha1', accessKeySecret);
103+
const signature = crypto.createHmac('sha1', accessKeySecret);
94104
return signature.update(new Buffer(canonicalString, 'utf8')).digest('base64');
95105
};
96106

@@ -100,5 +110,74 @@ exports.computeSignature = function computeSignature(accessKeySecret, canonicalS
100110
* @param {String} canonicalString
101111
*/
102112
exports.authorization = function authorization(accessKeyId, accessKeySecret, canonicalString) {
103-
return 'OSS ' + accessKeyId + ':' + this.computeSignature(accessKeySecret, canonicalString);
113+
return `OSS ${accessKeyId}:${this.computeSignature(accessKeySecret, canonicalString)}`;
114+
};
115+
116+
/**
117+
*
118+
* @param {String} accessKeySecret
119+
* @param {Object} options
120+
* @param {String} resource
121+
* @param {Number} expires
122+
*/
123+
exports._signatureForURL = function _signatureForURL(accessKeySecret, options, resource, expires) {
124+
const headers = {};
125+
const subResource = {};
126+
127+
if (options.process) {
128+
const processKeyword = 'x-oss-process';
129+
subResource[processKeyword] = options.process;
130+
}
131+
132+
if (options.response) {
133+
Object.entries(options.response).forEach(([k, value]) => {
134+
const key = `response-${k.toLowerCase()}`;
135+
subResource[key] = value;
136+
});
137+
}
138+
139+
Object.entries(options).forEach(([key, value]) => {
140+
const lowerKey = key.toLowerCase();
141+
if (lowerKey.indexOf('x-oss-') === 0) {
142+
headers[lowerKey] = value;
143+
} else if (lowerKey !== 'expires' && lowerKey !== 'response' && lowerKey !== 'process' && lowerKey !== 'method') {
144+
subResource[lowerKey] = value;
145+
}
146+
});
147+
148+
if (Object.prototype.hasOwnProperty.call(options, 'security-token')) {
149+
subResource['security-token'] = options['security-token'];
150+
}
151+
152+
if (Object.prototype.hasOwnProperty.call(options, 'callback')) {
153+
const json = {
154+
callbackUrl: encodeURI(options.callback.url),
155+
callbackBody: options.callback.body,
156+
};
157+
if (options.callback.host) {
158+
json.callbackHost = options.callback.host;
159+
}
160+
if (options.callback.contentType) {
161+
json.callbackBodyType = options.callback.contentType;
162+
}
163+
subResource.callback = new Buffer(JSON.stringify(json)).toString('base64');
164+
165+
if (options.callback.customValue) {
166+
const callbackVar = {};
167+
Object.keys(options.callback.customValue).forEach((key) => {
168+
callbackVar[`x:${key}`] = options.callback.customValue[key];
169+
});
170+
subResource['callback-var'] = new Buffer(JSON.stringify(callbackVar)).toString('base64');
171+
}
172+
}
173+
174+
const canonicalString = this.buildCanonicalString(options.method, resource, {
175+
headers,
176+
parameters: subResource,
177+
}, expires.toString());
178+
179+
return {
180+
Signature: this.computeSignature(accessKeySecret, canonicalString),
181+
subResource,
182+
};
104183
};

lib/object.js

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const copy = require('copy-to');
99
const path = require('path');
1010
const mime = require('mime');
1111
const callback = require('./common/callback');
12+
const signHelper = require('./common/signUtils');
1213

1314

1415
const proto = exports;
@@ -430,56 +431,31 @@ proto.getACL = function* getACL(name, options) {
430431
};
431432

432433
proto.signatureUrl = function (name, options) {
434+
options = options || {};
433435
name = this._objectName(name);
436+
options.method = options.method || 'GET';
437+
const expires = utility.timestamp() + (options.expires || 1800);
434438
const params = {
435439
bucket: this.options.bucket,
436440
object: name,
437441
};
438-
options = options || {};
439-
const expires = utility.timestamp() + (options.expires || 1800);
440-
let resource = this._getResource(params);
441-
const query = {};
442-
const signList = [];
443-
444-
if (options.response) {
445-
Object.keys(options.response).forEach((k) => {
446-
const key = `response-${k.toLowerCase()}`;
447-
query[key] = options.response[k];
448-
signList.push(`${key}=${options.response[k]}`);
449-
});
450-
}
451-
if (this.options.stsToken) {
452-
query['security-token'] = this.options.stsToken;
453-
signList.push(`security-token=${this.options.stsToken}`);
454-
}
455-
if (options.process) {
456-
const processKeyword = 'x-oss-process';
457-
query[processKeyword] = options.process;
458-
const item = `${processKeyword}=${options.process}`;
459-
signList.push(item);
460-
}
461442

462-
if (signList.length > 0) {
463-
signList.sort();
464-
resource += `?${signList.join('&')}`;
443+
const resource = this._getResource(params);
444+
445+
if (this.options.stsToken) {
446+
options['security-token'] = this.options.stsToken;
465447
}
466448

467-
const stringToSign = [
468-
options.method || 'GET',
469-
options['content-md5'] || '', // Content-MD5
470-
options['content-type'] || '', // Content-Type
471-
expires,
472-
resource,
473-
].join('\n');
474-
const signature = this.signature(stringToSign);
449+
const signRes = signHelper._signatureForURL(this.options.accessKeySecret, options, resource, expires);
475450

476451
const url = urlutil.parse(this._getReqUrl(params));
477452
url.query = {
478453
OSSAccessKeyId: this.options.accessKeyId,
479454
Expires: expires,
480-
Signature: signature,
455+
Signature: signRes.Signature,
481456
};
482-
copy(query).to(url.query);
457+
458+
copy(signRes.subResource).to(url.query);
483459

484460
return url.format();
485461
};

0 commit comments

Comments
 (0)