Skip to content

Commit

Permalink
Dev/2.13.0 (#197)
Browse files Browse the repository at this point in the history
1、安全性提升:不再支持 path-style
2、安全性提升:host 强制加入签名计算
3、修复 uploadFile 未对参数 Key 做校验
  • Loading branch information
livehigh authored Jan 29, 2024
1 parent c38c9a5 commit 3c53f3e
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 23 deletions.
2 changes: 1 addition & 1 deletion demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var cos = new COS({
ChunkSize: 1024 * 1024 * 8, // 控制分片大小,单位 B,在同园区上传可以设置较大的分片大小
Proxy: '',
Protocol: 'https:',
FollowRedirect: false,
Timeout: 10000,
});

var TaskId;
Expand Down
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ declare namespace COS {
Ip?: string;
/** 默认将host加入签名计算,关闭后可能导致越权风险,建议保持为true */
ForceSignHost?: boolean;
AutoSwitchHost?: boolean;
/** 获取签名的回调方法,如果没有 SecretId、SecretKey 时,必选 */
getAuthorization?: (
options: GetAuthorizationOptions,
Expand Down Expand Up @@ -311,6 +312,10 @@ declare namespace COS {
message: string;
/** 兼容老的错误信息字段,不建议使用,可能是参数错误、客户端出错、或服务端返回的错误 */
error: string | Error | { Code: string; Message: string };
/** 当前请求的Url */
url: string;
/** 当前请求的method */
method: string;
}
/** 回调的错误格式,其中服务端返回错误码可查看 @see https://cloud.tencent.com/document/product/436/7730 */
type CosError = null | CosSdkError;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cos-nodejs-sdk-v5",
"version": "2.12.6",
"version": "2.13.0",
"description": "cos nodejs sdk v5",
"main": "index.js",
"types": "index.d.ts",
Expand Down
13 changes: 9 additions & 4 deletions sdk/advance.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ function sliceUploadFile(params, callback) {
// 上传过程中出现错误,返回错误
ep.on('error', function (err) {
if (!self._isRunningTask(TaskId)) return;
var _err = {
UploadId: params.UploadData.UploadId || '',
err: err,
};
err.UploadId = params.UploadData.UploadId || '';
return callback(_err);
});

Expand Down Expand Up @@ -652,6 +649,7 @@ function uploadSliceItem(params, callback) {
util.getFileMd5(md5Body, function (err, md5) {
var contentMd5 = md5 ? util.binaryBase64(md5) : '';
var PartItem = UploadData.PartList[PartNumber - 1];
var switchHost = false;
Async.retry(
ChunkRetryTimes,
function (tryCallback) {
Expand All @@ -671,9 +669,13 @@ function uploadSliceItem(params, callback) {
Headers: headers,
onProgress: params.onProgress,
ContentMD5: contentMd5,
SwitchHost: switchHost,
},
function (err, data) {
if (!self._isRunningTask(TaskId)) return;
if (err) {
switchHost = err?.switchHost;
}
if (err) return tryCallback(err);
PartItem.Uploaded = true;
return tryCallback(null, data);
Expand All @@ -682,6 +684,9 @@ function uploadSliceItem(params, callback) {
});
},
function (err, data) {
if (err) {
delete err.switchHost;
}
if (!self._isRunningTask(TaskId)) return;
return callback(err, data);
}
Expand Down
97 changes: 81 additions & 16 deletions sdk/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -3020,6 +3020,7 @@ function multipartUpload(params, callback) {
headers: params.Headers,
onProgress: params.onProgress,
body: params.Body || null,
SwitchHost: params.SwitchHost,
},
function (err, data) {
if (err) return callback(err);
Expand Down Expand Up @@ -3644,9 +3645,7 @@ var getSignHost = function (opt) {
region: useAccelerate ? 'accelerate' : opt.Region,
});
var urlHost = url.replace(/^https?:\/\/([^/]+)(\/.*)?$/, '$1');
var standardHostReg = new RegExp('^([a-z\\d-]+-\\d+\\.)?(cos|cosv6|ci|pic)\\.([a-z\\d-]+)\\.myqcloud\\.com$');
if (standardHostReg.test(urlHost)) return urlHost;
return '';
return urlHost;
};

// 异步获取签名
Expand Down Expand Up @@ -3747,6 +3746,7 @@ function getAuthorizationAsync(params, callback) {
Token: StsData.Token || '',
ClientIP: StsData.ClientIP || '',
ClientUA: StsData.ClientUA || '',
SignFrom: 'client',
};
cb(null, AuthData);
};
Expand Down Expand Up @@ -3870,6 +3870,7 @@ function getAuthorizationAsync(params, callback) {
var AuthData = {
Authorization: Authorization,
SecurityToken: self.options.SecurityToken || self.options.XCosSecurityToken,
SignFrom: 'client',
};
cb(null, AuthData);
return AuthData;
Expand All @@ -3878,9 +3879,11 @@ function getAuthorizationAsync(params, callback) {
return '';
}

// 调整时间偏差
// 判断当前请求出错时能否重试
function allowRetry(err) {
var allowRetry = false;
var self = this;
var canRetry = false;
var networkError = false;
var isTimeError = false;
var serverDate = (err.headers && (err.headers.date || err.headers.Date)) || (err.error && err.error.ServerTime);
try {
Expand All @@ -3894,6 +3897,7 @@ function allowRetry(err) {
}
} catch (e) {}
if (err) {
// 调整时间偏差
if (isTimeError && serverDate) {
var serverTime = Date.parse(serverDate);
if (
Expand All @@ -3902,15 +3906,48 @@ function allowRetry(err) {
) {
console.error('error: Local time is too skewed.');
this.options.SystemClockOffset = serverTime - Date.now();
allowRetry = true;
canRetry = true;
}
} else if (Math.floor(err.statusCode / 100) === 5) {
allowRetry = true;
canRetry = true;
} else if (err.code === 'ECONNRESET') {
allowRetry = true;
canRetry = true;
}
/**
* 归为网络错误
* 1、no statusCode
* 2、statusCode === 3xx || 4xx || 5xx && no requestId
*/
if (!err.statusCode) {
canRetry = self.options.AutoSwitchHost;
networkError = true;
} else {
const statusCode = Math.floor(err.statusCode / 100);
const requestId = err?.headers && err?.headers['x-cos-request-id'];
if ([3, 4, 5].includes(statusCode) && !requestId) {
canRetry = self.options.AutoSwitchHost;
networkError = true;
}
}
}
return allowRetry;
return { canRetry, networkError };
}

/**
* requestUrl:请求的url,用于判断是否cos主域名,true才切
* clientCalcSign:是否客户端计算签名,服务端返回的签名不能切,true才切
* networkError:是否未知网络错误,true才切
* */
function canSwitchHost({ requestUrl, clientCalcSign, networkError }) {
if (!this.options.AutoSwitchHost) return false;
if (!requestUrl) return false;
if (!clientCalcSign) return false;
if (!networkError) return false;
const commonReg = /^https?:\/\/[^\/]*\.cos\.[^\/]*\.myqcloud\.com(\/.*)?$/;
const accelerateReg = /^https?:\/\/[^\/]*\.cos\.accelerate\.myqcloud\.com(\/.*)?$/;
// 当前域名是cos主域名才切换
const isCommonCosHost = commonReg.test(requestUrl) && !accelerateReg.test(requestUrl);
return isCommonCosHost;
}

// 获取签名并发起请求
Expand All @@ -3937,6 +3974,11 @@ function submitRequest(params, callback) {
params.SignHost || getSignHost.call(this, { Bucket: params.Bucket, Region: params.Region, Url: params.url });
var next = function (tryTimes) {
var oldClockOffset = self.options.SystemClockOffset;
if (params.SwitchHost) {
// 更换要签的host
SignHost = SignHost.replace(/myqcloud.com/, 'tencentcos.cn');
}

getAuthorizationAsync.call(
self,
{
Expand All @@ -3951,18 +3993,20 @@ function submitRequest(params, callback) {
ResourceKey: params.ResourceKey,
Scope: params.Scope,
ForceSignHost: self.options.ForceSignHost,
SwitchHost: params.SwitchHost,
},
function (err, AuthData) {
if (err) return callback(err);
params.AuthData = AuthData;
_submitRequest.call(self, params, function (err, data) {
if (
err &&
!(params.body && params.body.pipe) &&
!params.outputStream &&
tryTimes < 2 &&
(oldClockOffset !== self.options.SystemClockOffset || allowRetry.call(self, err))
) {
let canRetry = false;
let networkError = false;
if (err) {
const info = allowRetry.call(self, err);
canRetry = info.canRetry || oldClockOffset !== self.options.SystemClockOffset;
networkError = info.networkError;
}
if (err && !(params.body && params.body.pipe) && !params.outputStream && tryTimes < 2 && canRetry) {
if (params.headers) {
delete params.headers.Authorization;
delete params.headers['token'];
Expand All @@ -3971,8 +4015,23 @@ function submitRequest(params, callback) {
params.headers['x-cos-security-token'] && delete params.headers['x-cos-security-token'];
params.headers['x-ci-security-token'] && delete params.headers['x-ci-security-token'];
}
// 进入重试逻辑时 需判断是否需要切换cos备用域名
const switchHost = canSwitchHost.call(self, {
requestUrl: err?.url || '',
clientCalcSign: AuthData?.SignFrom === 'client',
networkError,
});
params.SwitchHost = switchHost;
next(tryTimes + 1);
} else {
if (err && params.Action === 'name/cos:UploadPart') {
const switchHost = canSwitchHost.call(self, {
requestUrl: err?.url || '',
clientCalcSign: AuthData?.SignFrom === 'client',
networkError,
});
err.switchHost = switchHost;
}
callback(err, data);
}
});
Expand Down Expand Up @@ -4018,6 +4077,10 @@ function _submitRequest(params, callback) {
region: region,
object: object,
});
if (params.SwitchHost) {
// 更换请求的url
url = url.replace(/myqcloud.com/, 'tencentcos.cn');
}
if (params.action) {
url = url + '?' + params.action;
}
Expand Down Expand Up @@ -4113,6 +4176,8 @@ function _submitRequest(params, callback) {
retResponse && retResponse.statusCode && (attrs.statusCode = retResponse.statusCode);
retResponse && retResponse.headers && (attrs.headers = retResponse.headers);
if (err) {
opt.url && (attrs.url = opt.url);
opt.method && (attrs.method = opt.method);
err = util.extend(err || {}, attrs);
callback(err, null);
} else {
Expand Down
8 changes: 8 additions & 0 deletions sdk/cos.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ var defaultOptions = {
UserAgent: '',
ConfCwd: '',
ForceSignHost: true, // 默认将host加入签名计算,关闭后可能导致越权风险,建议保持为true
AutoSwitchHost: true,
// 动态秘钥,优先级Credentials > SecretId/SecretKey。注意Cred内是小写的secretId、secretKey
Credentials: {
secretId: '',
Expand Down Expand Up @@ -101,13 +102,20 @@ var COS = function (options) {
console.error('error: SecretKey format is incorrect. Please check');
}
if (util.isWeb()) {
console.log('Tip: 使用 electron 等跨平台技术可正常使用Nodejs SDK,请忽略下方浏览器环境警告');
console.warn(
'warning: cos-nodejs-sdk-v5 不支持浏览器使用,请改用 cos-js-sdk-v5,参考文档: https://cloud.tencent.com/document/product/436/11459'
);
console.warn(
'warning: cos-nodejs-sdk-v5 does not support browsers. Please use cos-js-sdk-v5 instead, See: https://cloud.tencent.com/document/product/436/11459'
);
}
if (this.options.ForcePathStyle) {
console.warn(
'cos-nodejs-sdk-v5不再支持使用path-style,仅支持使用virtual-hosted-style,参考文档:https://cloud.tencent.com/document/product/436/96243'
);
throw new Error('ForcePathStyle is not supported');
}
event.init(this);
task.init(this);

Expand Down
3 changes: 2 additions & 1 deletion sdk/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ var hasMissingParams = function (apiName, params) {
apiName.indexOf('Object') > -1 ||
apiName.indexOf('multipart') > -1 ||
apiName === 'sliceUploadFile' ||
apiName === 'abortUploadTask'
apiName === 'abortUploadTask' ||
apiName === 'uploadFile'
) {
if (!Bucket) return 'Bucket';
if (!Region) return 'Region';
Expand Down

0 comments on commit 3c53f3e

Please sign in to comment.