util.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. 'use strict';
  2. var fs = require('fs');
  3. var crypto = require('crypto');
  4. var { XMLParser, XMLBuilder } = require('fast-xml-parser');
  5. var xmlParser = new XMLParser({
  6. ignoreDeclaration: true, // 忽略 XML 声明
  7. ignoreAttributes: true, // 忽略属性
  8. parseTagValue: false, // 关闭自动解析
  9. trimValues: false, // 关闭默认 trim
  10. });
  11. var xmlBuilder = new XMLBuilder();
  12. function camSafeUrlEncode(str) {
  13. return encodeURIComponent(str)
  14. .replace(/!/g, '%21')
  15. .replace(/'/g, '%27')
  16. .replace(/\(/g, '%28')
  17. .replace(/\)/g, '%29')
  18. .replace(/\*/g, '%2A');
  19. }
  20. var getObjectKeys = function (obj, forKey) {
  21. var list = [];
  22. for (var key in obj) {
  23. if (obj.hasOwnProperty(key)) {
  24. list.push(forKey ? camSafeUrlEncode(key).toLowerCase() : key);
  25. }
  26. }
  27. return list.sort(function (a, b) {
  28. a = a.toLowerCase();
  29. b = b.toLowerCase();
  30. return a === b ? 0 : a > b ? 1 : -1;
  31. });
  32. };
  33. /**
  34. * obj转为string
  35. * @param {Object} obj 需要转的对象,必须
  36. * @param {Boolean} lowerCaseKey key是否转为小写,默认false,非必须
  37. * @return {String} data 返回字符串
  38. */
  39. var obj2str = function (obj, lowerCaseKey) {
  40. var i, key, val;
  41. var list = [];
  42. var keyList = getObjectKeys(obj);
  43. for (i = 0; i < keyList.length; i++) {
  44. key = keyList[i];
  45. val = obj[key] === undefined || obj[key] === null ? '' : '' + obj[key];
  46. key = lowerCaseKey ? camSafeUrlEncode(key).toLowerCase() : camSafeUrlEncode(key);
  47. val = camSafeUrlEncode(val) || '';
  48. list.push(key + '=' + val);
  49. }
  50. return list.join('&');
  51. };
  52. // 可以签入签名的headers
  53. var signHeaders = [
  54. 'content-disposition',
  55. 'content-encoding',
  56. 'content-length',
  57. 'content-md5',
  58. 'expect',
  59. 'expires',
  60. 'host',
  61. 'if-match',
  62. 'if-modified-since',
  63. 'if-none-match',
  64. 'if-unmodified-since',
  65. 'origin',
  66. 'range',
  67. 'transfer-encoding',
  68. 'pic-operations',
  69. ];
  70. var getSignHeaderObj = function (headers) {
  71. var signHeaderObj = {};
  72. for (var i in headers) {
  73. var key = i.toLowerCase();
  74. if (key.indexOf('x-cos-') > -1 || key.indexOf('x-ci-') > -1 || signHeaders.indexOf(key) > -1) {
  75. signHeaderObj[i] = headers[i];
  76. }
  77. }
  78. return signHeaderObj;
  79. };
  80. //测试用的key后面可以去掉
  81. var getAuth = function (opt) {
  82. opt = opt || {};
  83. var SecretId = opt.SecretId;
  84. var SecretKey = opt.SecretKey;
  85. var KeyTime = opt.KeyTime;
  86. var method = (opt.method || opt.Method || 'get').toLowerCase();
  87. var queryParams = clone(opt.Query || opt.params || {});
  88. var headers = getSignHeaderObj(clone(opt.Headers || opt.headers || {}));
  89. var Key = opt.Key || '';
  90. var pathname;
  91. if (opt.UseRawKey) {
  92. pathname = opt.Pathname || opt.pathname || '/' + Key;
  93. } else {
  94. pathname = opt.Pathname || opt.pathname || Key;
  95. pathname.indexOf('/') !== 0 && (pathname = '/' + pathname);
  96. }
  97. // ForceSignHost明确传入false才不加入host签名
  98. var forceSignHost = opt.ForceSignHost === false ? false : true;
  99. // 如果有传入存储桶,那么签名默认加 Host 参与计算,避免跨桶访问
  100. if (!headers.Host && !headers.host && opt.Bucket && opt.Region && forceSignHost)
  101. headers.Host = opt.Bucket + '.cos.' + opt.Region + '.myqcloud.com';
  102. if (!SecretId) throw new Error('missing param SecretId');
  103. if (!SecretKey) throw new Error('missing param SecretKey');
  104. // 签名有效起止时间
  105. var now = Math.round(getSkewTime(opt.SystemClockOffset) / 1000) - 1;
  106. var exp = now;
  107. var Expires = opt.Expires || opt.expires;
  108. if (Expires === undefined) {
  109. exp += 900; // 签名过期时间为当前 + 900s
  110. } else {
  111. exp += Expires * 1 || 0;
  112. }
  113. // 要用到的 Authorization 参数列表
  114. var qSignAlgorithm = 'sha1';
  115. var qAk = SecretId;
  116. var qSignTime = KeyTime || now + ';' + exp;
  117. var qKeyTime = KeyTime || now + ';' + exp;
  118. var qHeaderList = getObjectKeys(headers, true).join(';').toLowerCase();
  119. var qUrlParamList = getObjectKeys(queryParams, true).join(';').toLowerCase();
  120. // 签名算法说明文档:https://www.qcloud.com/document/product/436/7778
  121. // 步骤一:计算 SignKey
  122. var signKey = crypto.createHmac('sha1', SecretKey).update(qKeyTime).digest('hex');
  123. // 步骤二:构成 FormatString
  124. var formatString = [method, pathname, obj2str(queryParams, true), obj2str(headers, true), ''].join('\n');
  125. formatString = Buffer.from(formatString, 'utf8');
  126. // 步骤三:计算 StringToSign
  127. var res = crypto.createHash('sha1').update(formatString).digest('hex');
  128. var stringToSign = ['sha1', qSignTime, res, ''].join('\n');
  129. // 步骤四:计算 Signature
  130. var qSignature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');
  131. // 步骤五:构造 Authorization
  132. var authorization = [
  133. 'q-sign-algorithm=' + qSignAlgorithm,
  134. 'q-ak=' + qAk,
  135. 'q-sign-time=' + qSignTime,
  136. 'q-key-time=' + qKeyTime,
  137. 'q-header-list=' + qHeaderList,
  138. 'q-url-param-list=' + qUrlParamList,
  139. 'q-signature=' + qSignature,
  140. ].join('&');
  141. return authorization;
  142. };
  143. var getV4Auth = function (opt) {
  144. if (!opt.SecretId) return console.error('missing param SecretId');
  145. if (!opt.SecretKey) return console.error('missing param SecretKey');
  146. if (!opt.Bucket) return console.error('missing param Bucket');
  147. var longBucket = opt.Bucket;
  148. var ShortBucket = longBucket.substr(0, longBucket.lastIndexOf('-'));
  149. var AppId = longBucket.substr(longBucket.lastIndexOf('-') + 1);
  150. var random = Math.round(Math.random() * Math.pow(2, 32));
  151. var now = Math.round(Date.now() / 1000);
  152. var e = now + (opt.Expires === undefined ? 900 : opt.Expires);
  153. var path =
  154. '/' +
  155. AppId +
  156. '/' +
  157. ShortBucket +
  158. '/' +
  159. encodeURIComponent((opt.Key || '').replace(/(^\/*)/g, '')).replace(/%2F/g, '/');
  160. var plainText =
  161. 'a=' + AppId + '&b=' + ShortBucket + '&k=' + opt.SecretId + '&t=' + now + '&e=' + e + '&r=' + random + '&f=' + path;
  162. var signKey = crypto.createHmac('sha1', opt.SecretKey).update(plainText).digest();
  163. var sign = Buffer.concat([signKey, Buffer.from(plainText)]).toString('base64');
  164. return sign;
  165. };
  166. var getSourceParams = function (source) {
  167. var parser = this.options.CopySourceParser;
  168. if (parser) return parser(source);
  169. var m = source.match(/^([^.]+-\d+)\.cos(v6|-cdc|-internal)?\.([^.]+)\.((myqcloud\.com)|(tencentcos\.cn))\/(.+)$/);
  170. if (!m) return null;
  171. return { Bucket: m[1], Region: m[3], Key: m[7] };
  172. };
  173. var noop = function () {};
  174. // 清除对象里值为的 undefined 或 null 的属性
  175. var clearKey = function (obj) {
  176. var retObj = {};
  177. for (var key in obj) {
  178. if (obj.hasOwnProperty(key) && obj[key] !== undefined && obj[key] !== null) {
  179. retObj[key] = obj[key];
  180. }
  181. }
  182. return retObj;
  183. };
  184. // 删掉不需要的#text
  185. var textNodeName = '#text';
  186. var deleteTextNodes = function (obj) {
  187. if (!isObject(obj)) return;
  188. for (let i in obj) {
  189. var item = obj[i];
  190. if (typeof item === 'string') {
  191. if (i === textNodeName) {
  192. delete obj[i];
  193. }
  194. } else if (Array.isArray(item)) {
  195. item.forEach(function (i) {
  196. deleteTextNodes(i);
  197. });
  198. } else if (isObject(item)) {
  199. deleteTextNodes(item);
  200. }
  201. }
  202. };
  203. // XML 对象转 JSON 对象
  204. var xml2json = function (bodyStr) {
  205. var json = xmlParser.parse(bodyStr);
  206. deleteTextNodes(json);
  207. return json;
  208. };
  209. // JSON 对象转 XML 对象
  210. var json2xml = function (json) {
  211. var xml = xmlBuilder.build(json);
  212. return xml;
  213. };
  214. // 计算 MD5
  215. var md5 = function (str, encoding) {
  216. return crypto
  217. .createHash('md5')
  218. .update(str)
  219. .digest(encoding || 'hex');
  220. };
  221. // 获取文件分片
  222. var fileSlice = function (FilePath, start, end, callback) {
  223. if (FilePath) {
  224. try {
  225. var readStream = fs.createReadStream(FilePath, { start: start, end: end - 1 });
  226. readStream.isSdkCreated = true;
  227. callback(readStream);
  228. } catch (e) {}
  229. } else {
  230. callback(null);
  231. }
  232. };
  233. // 获取文件内容的 MD5
  234. var getBodyMd5 = function (UploadCheckContentMd5, Body, callback) {
  235. callback = callback || noop;
  236. if (UploadCheckContentMd5) {
  237. if (Body instanceof Buffer || typeof Body === 'string') {
  238. callback(util.md5(Body));
  239. } else {
  240. callback();
  241. }
  242. } else {
  243. callback();
  244. }
  245. };
  246. // 获取文件 md5 值
  247. var getFileMd5 = function (readStream, callback) {
  248. var md5 = crypto.createHash('md5');
  249. readStream.on('data', function (chunk) {
  250. md5.update(chunk);
  251. });
  252. readStream.on('error', function (err) {
  253. callback(util.error(err));
  254. });
  255. readStream.on('end', function () {
  256. var hash = md5.digest('hex');
  257. callback(null, hash);
  258. });
  259. };
  260. function clone(obj) {
  261. return map(obj, function (v) {
  262. return typeof v === 'object' && v !== null ? clone(v) : v;
  263. });
  264. }
  265. function attr(obj, name, defaultValue) {
  266. return obj && name in obj ? obj[name] : defaultValue;
  267. }
  268. function extend(target, source) {
  269. each(source, function (val, key) {
  270. target[key] = source[key];
  271. });
  272. return target;
  273. }
  274. function isArray(arr) {
  275. return arr instanceof Array;
  276. }
  277. function isObject(obj) {
  278. return Object.prototype.toString.call(obj) === '[object Object]';
  279. }
  280. function isInArray(arr, item) {
  281. var flag = false;
  282. for (var i = 0; i < arr.length; i++) {
  283. if (item === arr[i]) {
  284. flag = true;
  285. break;
  286. }
  287. }
  288. return flag;
  289. }
  290. function makeArray(arr) {
  291. return isArray(arr) ? arr : [arr];
  292. }
  293. function each(obj, fn) {
  294. for (var i in obj) {
  295. if (obj.hasOwnProperty(i)) {
  296. fn(obj[i], i);
  297. }
  298. }
  299. }
  300. function map(obj, fn) {
  301. var o = isArray(obj) ? [] : {};
  302. for (var i in obj) {
  303. if (obj.hasOwnProperty(i)) {
  304. o[i] = fn(obj[i], i);
  305. }
  306. }
  307. return o;
  308. }
  309. function filter(obj, fn) {
  310. var iaArr = isArray(obj);
  311. var o = iaArr ? [] : {};
  312. for (var i in obj) {
  313. if (obj.hasOwnProperty(i)) {
  314. if (fn(obj[i], i)) {
  315. if (iaArr) {
  316. o.push(obj[i]);
  317. } else {
  318. o[i] = obj[i];
  319. }
  320. }
  321. }
  322. }
  323. return o;
  324. }
  325. var binaryBase64 = function (str) {
  326. var i,
  327. len,
  328. char,
  329. arr = [];
  330. for (i = 0, len = str.length / 2; i < len; i++) {
  331. char = parseInt(str[i * 2] + str[i * 2 + 1], 16);
  332. arr.push(char);
  333. }
  334. return Buffer.from(arr).toString('base64');
  335. };
  336. var uuid = function () {
  337. var S4 = function () {
  338. return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  339. };
  340. return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
  341. };
  342. var hasMissingParams = function (apiName, params) {
  343. var Bucket = params.Bucket;
  344. var Region = params.Region;
  345. var Key = params.Key;
  346. if (
  347. apiName.indexOf('Bucket') > -1 ||
  348. apiName === 'deleteMultipleObject' ||
  349. apiName === 'multipartList' ||
  350. apiName === 'listObjectVersions'
  351. ) {
  352. if (!Bucket) return 'Bucket';
  353. if (!Region) return 'Region';
  354. } else if (
  355. apiName.indexOf('Object') > -1 ||
  356. apiName.indexOf('multipart') > -1 ||
  357. apiName === 'sliceUploadFile' ||
  358. apiName === 'abortUploadTask' ||
  359. apiName === 'uploadFile'
  360. ) {
  361. if (!Bucket) return 'Bucket';
  362. if (!Region) return 'Region';
  363. if (!Key) return 'Key';
  364. }
  365. return false;
  366. };
  367. var formatParams = function (apiName, params) {
  368. // 复制参数对象
  369. params = extend({}, params);
  370. // 统一处理 Headers
  371. if (apiName !== 'getAuth' && apiName !== 'getV4Auth' && apiName !== 'getObjectUrl') {
  372. var Headers = params.Headers || {};
  373. if (params && typeof params === 'object') {
  374. (function () {
  375. for (var key in params) {
  376. if (params.hasOwnProperty(key) && key.indexOf('x-cos-') > -1) {
  377. Headers[key] = params[key];
  378. }
  379. }
  380. })();
  381. var headerMap = {
  382. // params headers
  383. 'x-cos-mfa': 'MFA',
  384. 'Content-MD5': 'ContentMD5',
  385. 'Content-Length': 'ContentLength',
  386. 'Content-Type': 'ContentType',
  387. Expect: 'Expect',
  388. Expires: 'Expires',
  389. 'Cache-Control': 'CacheControl',
  390. 'Content-Disposition': 'ContentDisposition',
  391. 'Content-Encoding': 'ContentEncoding',
  392. Range: 'Range',
  393. 'If-Modified-Since': 'IfModifiedSince',
  394. 'If-Unmodified-Since': 'IfUnmodifiedSince',
  395. 'If-Match': 'IfMatch',
  396. 'If-None-Match': 'IfNoneMatch',
  397. 'x-cos-copy-source': 'CopySource',
  398. 'x-cos-copy-source-Range': 'CopySourceRange',
  399. 'x-cos-metadata-directive': 'MetadataDirective',
  400. 'x-cos-copy-source-If-Modified-Since': 'CopySourceIfModifiedSince',
  401. 'x-cos-copy-source-If-Unmodified-Since': 'CopySourceIfUnmodifiedSince',
  402. 'x-cos-copy-source-If-Match': 'CopySourceIfMatch',
  403. 'x-cos-copy-source-If-None-Match': 'CopySourceIfNoneMatch',
  404. 'x-cos-acl': 'ACL',
  405. 'x-cos-grant-read': 'GrantRead',
  406. 'x-cos-grant-write': 'GrantWrite',
  407. 'x-cos-grant-full-control': 'GrantFullControl',
  408. 'x-cos-grant-read-acp': 'GrantReadAcp',
  409. 'x-cos-grant-write-acp': 'GrantWriteAcp',
  410. 'x-cos-storage-class': 'StorageClass',
  411. 'x-cos-traffic-limit': 'TrafficLimit',
  412. 'x-cos-mime-limit': 'MimeLimit',
  413. // SSE-C
  414. 'x-cos-server-side-encryption-customer-algorithm': 'SSECustomerAlgorithm',
  415. 'x-cos-server-side-encryption-customer-key': 'SSECustomerKey',
  416. 'x-cos-server-side-encryption-customer-key-MD5': 'SSECustomerKeyMD5',
  417. // SSE-COS、SSE-KMS
  418. 'x-cos-server-side-encryption': 'ServerSideEncryption',
  419. 'x-cos-server-side-encryption-cos-kms-key-id': 'SSEKMSKeyId',
  420. 'x-cos-server-side-encryption-context': 'SSEContext',
  421. // 上传时图片处理
  422. 'Pic-Operations': 'PicOperations',
  423. };
  424. util.each(headerMap, function (paramKey, headerKey) {
  425. if (params[paramKey] !== undefined) {
  426. Headers[headerKey] = params[paramKey];
  427. }
  428. });
  429. params.Headers = clearKey(Headers);
  430. }
  431. }
  432. return params;
  433. };
  434. var apiWrapper = function (apiName, apiFn) {
  435. return function (params, callback) {
  436. var self = this;
  437. // 处理参数
  438. if (typeof params === 'function') {
  439. callback = params;
  440. params = {};
  441. }
  442. // 整理参数格式
  443. params = formatParams(apiName, params);
  444. // 代理回调函数
  445. var formatResult = function (result) {
  446. if (result && result.headers) {
  447. result.headers['x-ci-request-id'] && (result.RequestId = result.headers['x-ci-request-id']);
  448. result.headers['x-cos-request-id'] && (result.RequestId = result.headers['x-cos-request-id']);
  449. result.headers['x-cos-version-id'] && (result.VersionId = result.headers['x-cos-version-id']);
  450. result.headers['x-cos-delete-marker'] && (result.DeleteMarker = result.headers['x-cos-delete-marker']);
  451. }
  452. return result;
  453. };
  454. var _callback = function (err, data) {
  455. callback && callback(formatResult(err), formatResult(data));
  456. };
  457. var checkParams = function () {
  458. if (apiName !== 'getService' && apiName !== 'abortUploadTask') {
  459. // 判断参数是否完整
  460. var missingResult = hasMissingParams(apiName, params);
  461. if (missingResult) {
  462. return 'missing param ' + missingResult;
  463. }
  464. // 判断 region 格式
  465. if (params.Region) {
  466. if (params.Region.indexOf('cos.') > -1) {
  467. return 'param Region should not be start with "cos."';
  468. } else if (!/^([a-z\d-]+)$/.test(params.Region)) {
  469. return 'Region format error.';
  470. }
  471. // 判断 region 格式
  472. if (
  473. !self.options.CompatibilityMode &&
  474. params.Region.indexOf('-') === -1 &&
  475. params.Region !== 'yfb' &&
  476. params.Region !== 'default' &&
  477. params.Region !== 'accelerate'
  478. ) {
  479. console.warn(
  480. 'warning: param Region format error, find help here: https://cloud.tencent.com/document/product/436/6224'
  481. );
  482. }
  483. }
  484. // 兼容不带 AppId 的 Bucket
  485. if (params.Bucket) {
  486. if (!/^([a-z\d-]+)-(\d+)$/.test(params.Bucket)) {
  487. if (params.AppId) {
  488. params.Bucket = params.Bucket + '-' + params.AppId;
  489. } else if (self.options.AppId) {
  490. params.Bucket = params.Bucket + '-' + self.options.AppId;
  491. } else {
  492. return 'Bucket should format as "test-1250000000".';
  493. }
  494. }
  495. if (params.AppId) {
  496. console.warn(
  497. 'warning: AppId has been deprecated, Please put it at the end of parameter Bucket(E.g Bucket:"test-1250000000" ).'
  498. );
  499. delete params.AppId;
  500. }
  501. }
  502. // 如果 Key 是 / 开头,强制去掉第一个 /
  503. if (!self.options.UseRawKey && params.Key && params.Key.substr(0, 1) === '/') {
  504. params.Key = params.Key.substr(1);
  505. }
  506. }
  507. };
  508. var errMsg = checkParams();
  509. var isSync = ['getAuth', 'getV4Auth', 'getObjectUrl'].includes(apiName) || apiName.indexOf('Stream') > -1;
  510. if (Promise && !isSync && !callback) {
  511. return new Promise(function (resolve, reject) {
  512. callback = function (err, data) {
  513. err ? reject(err) : resolve(data);
  514. };
  515. if (errMsg) return _callback(util.error(new Error(errMsg)));
  516. apiFn.call(self, params, _callback);
  517. });
  518. } else {
  519. if (errMsg) return _callback(util.error(new Error(errMsg)));
  520. var res = apiFn.call(self, params, _callback);
  521. if (isSync) return res;
  522. }
  523. };
  524. };
  525. var throttleOnProgress = function (total, onProgress) {
  526. var self = this;
  527. var size0 = 0;
  528. var size1 = 0;
  529. var time0 = Date.now();
  530. var time1;
  531. var timer;
  532. function update() {
  533. timer = 0;
  534. if (onProgress && typeof onProgress === 'function') {
  535. time1 = Date.now();
  536. var speed = Math.max(0, Math.round(((size1 - size0) / ((time1 - time0) / 1000)) * 100) / 100) || 0;
  537. var percent;
  538. if (size1 === 0 && total === 0) {
  539. percent = 1;
  540. } else {
  541. percent = Math.floor((size1 / total) * 100) / 100 || 0;
  542. }
  543. time0 = time1;
  544. size0 = size1;
  545. try {
  546. onProgress({ loaded: size1, total: total, speed: speed, percent: percent });
  547. } catch (e) {}
  548. }
  549. }
  550. return function (info, immediately) {
  551. if (info) {
  552. size1 = info.loaded;
  553. total = info.total;
  554. }
  555. if (immediately) {
  556. clearTimeout(timer);
  557. update();
  558. } else {
  559. if (timer) return;
  560. timer = setTimeout(update, self.options.ProgressInterval);
  561. }
  562. };
  563. };
  564. var getFileSize = function (api, params, callback) {
  565. var size;
  566. if (api === 'sliceUploadFile') {
  567. if (params.FilePath) {
  568. fs.stat(params.FilePath, function (err, fileStats) {
  569. if (err) {
  570. if (params.ContentLength !== undefined) {
  571. size = params.ContentLength;
  572. } else {
  573. return callback(err);
  574. }
  575. } else {
  576. params.FileStat = fileStats;
  577. params.FileStat.FilePath = params.FilePath;
  578. size = fileStats.isDirectory() ? 0 : fileStats.size;
  579. }
  580. params.ContentLength = size = size || 0;
  581. callback(null, size);
  582. });
  583. return;
  584. } else {
  585. callback(util.error(new Error('missing param FilePath')));
  586. return;
  587. }
  588. } else {
  589. if (params.Body !== undefined) {
  590. if (typeof params.Body === 'string') {
  591. params.Body = global.Buffer.from(params.Body);
  592. }
  593. if (params.Body instanceof global.Buffer) {
  594. size = params.Body.length;
  595. } else if (typeof params.Body.pipe === 'function') {
  596. if (params.ContentLength === undefined) {
  597. size = undefined;
  598. } else {
  599. size = params.ContentLength;
  600. }
  601. } else {
  602. callback(util.error(new Error('params Body format error, Only allow Buffer|Stream|String.')));
  603. return;
  604. }
  605. } else {
  606. callback(util.error(new Error('missing param Body')));
  607. return;
  608. }
  609. }
  610. params.ContentLength = size;
  611. callback(null, size);
  612. };
  613. // 获取调正的时间戳
  614. var getSkewTime = function (offset) {
  615. return Date.now() + (offset || 0);
  616. };
  617. // 重写 callback,等待流结束后才 callback
  618. var callbackAfterStreamFinish = function (stream, callback) {
  619. if (!stream) return callback;
  620. var err,
  621. data,
  622. count = 2,
  623. loaded = false;
  624. var cb = function (e, d) {
  625. if (loaded) return;
  626. // 如果有数据,且没有错误,清理 设置错误
  627. if ((d && !data) || e || err) {
  628. data = d;
  629. }
  630. if (e && !err) {
  631. err = e;
  632. data = null;
  633. }
  634. if (err || --count === 0) {
  635. loaded = true;
  636. callback(err, data);
  637. }
  638. };
  639. stream.on('error', function (err) {
  640. cb(err);
  641. });
  642. stream.on('finish', function () {
  643. cb();
  644. });
  645. return cb;
  646. };
  647. var error = function (err, opt) {
  648. var sourceErr = err;
  649. err.message = err.message || null;
  650. if (typeof opt === 'string') {
  651. err.error = opt;
  652. err.message = opt;
  653. } else if (typeof opt === 'object' && opt !== null) {
  654. extend(err, opt);
  655. if (opt.code || opt.name) err.code = opt.code || opt.name;
  656. if (opt.message) err.message = opt.message;
  657. if (opt.stack) err.stack = opt.stack;
  658. }
  659. if (typeof Object.defineProperty === 'function') {
  660. Object.defineProperty(err, 'name', { writable: true, enumerable: false });
  661. Object.defineProperty(err, 'message', { enumerable: true });
  662. }
  663. err.name = (opt && opt.name) || err.name || err.code || 'Error';
  664. if (!err.code) err.code = err.name;
  665. if (!err.error) {
  666. var objectType = Object.prototype.toString.call(err);
  667. if (objectType === '[object Object]') {
  668. // 兼容老的错误格式
  669. err.error = clone(sourceErr);
  670. } else if (objectType === '[object Error]') {
  671. // 有环境报出[object Error]对象的情况,兼容处理一下
  672. err = {
  673. code: err.code || err.name || 'Error',
  674. name: err.name || err.code || 'Error',
  675. message: err.reason || err.message || 'Error',
  676. };
  677. }
  678. }
  679. return err;
  680. };
  681. var isWeb = function () {
  682. return typeof window === 'object';
  683. };
  684. var isCIHost = function (url) {
  685. return /^https?:\/\/([^/]+\.)?ci\.[^/]+/.test(url);
  686. };
  687. var encodeBase64 = function (str, safe) {
  688. let base64Str = Buffer.from(str).toString('base64');
  689. // 万象使用的安全base64格式需要特殊处理
  690. if (safe) {
  691. base64Str = base64Str.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
  692. }
  693. return base64Str;
  694. };
  695. var simplifyPath = function (path) {
  696. const names = path.split('/');
  697. const stack = [];
  698. for (const name of names) {
  699. if (name === '..') {
  700. if (stack.length) {
  701. stack.pop();
  702. }
  703. } else if (name.length && name !== '.') {
  704. stack.push(name);
  705. }
  706. }
  707. return '/' + stack.join('/');
  708. };
  709. // 解析响应体,兼容 xml、json
  710. var parseResBody = function (responseBody) {
  711. var json;
  712. if (responseBody && typeof responseBody === 'string') {
  713. var trimBody = responseBody.trim();
  714. var isXml = trimBody.indexOf('<') === 0;
  715. var isJson = trimBody.indexOf('{') === 0;
  716. if (isXml) {
  717. // xml 解析,解析失败返回{}
  718. json = util.xml2json(responseBody) || {};
  719. } else if (isJson) {
  720. // json解析,解析失败返回原始 Body
  721. try {
  722. // 替换 json 中的换行符为空格,否则解析会出错
  723. var formatBody = responseBody.replace(/\n/g, ' ');
  724. var parsedBody = JSON.parse(formatBody);
  725. // 确保解析出 json 对象
  726. if (Object.prototype.toString.call(parsedBody) === '[object Object]') {
  727. json = parsedBody;
  728. } else {
  729. json = responseBody;
  730. }
  731. } catch (e) {
  732. json = responseBody;
  733. }
  734. } else {
  735. json = responseBody;
  736. }
  737. } else {
  738. json = responseBody || {};
  739. }
  740. return json;
  741. };
  742. var util = {
  743. noop: noop,
  744. formatParams: formatParams,
  745. apiWrapper: apiWrapper,
  746. xml2json: xml2json,
  747. json2xml: json2xml,
  748. md5: md5,
  749. clearKey: clearKey,
  750. fileSlice: fileSlice,
  751. getBodyMd5: getBodyMd5,
  752. getFileMd5: getFileMd5,
  753. binaryBase64: binaryBase64,
  754. extend: extend,
  755. isArray: isArray,
  756. isInArray: isInArray,
  757. makeArray: makeArray,
  758. each: each,
  759. map: map,
  760. filter: filter,
  761. clone: clone,
  762. attr: attr,
  763. uuid: uuid,
  764. camSafeUrlEncode: camSafeUrlEncode,
  765. throttleOnProgress: throttleOnProgress,
  766. getFileSize: getFileSize,
  767. getSkewTime: getSkewTime,
  768. error: error,
  769. getAuth: getAuth,
  770. callbackAfterStreamFinish: callbackAfterStreamFinish,
  771. getV4Auth: getV4Auth,
  772. isBrowser: false,
  773. obj2str: obj2str,
  774. isWeb: isWeb,
  775. isCIHost: isCIHost,
  776. getSourceParams: getSourceParams,
  777. encodeBase64: encodeBase64,
  778. simplifyPath: simplifyPath,
  779. parseResBody: parseResBody,
  780. };
  781. module.exports = util;