UNPKG

ks3-js-sdk

Version:

js sdk for ks3

883 lines (806 loc) 26.6 kB
var md5 = require('../lib/md5'); /* * url endpoints for different regions * Endpoint与Region的对应关系(官方文档): https://docs.ksyun.com/documents/6761 */ const ENDPOINT = { BEIJING : 'ks3-cn-beijing.ksyuncs.com', SHANGHAI: 'ks3-cn-shanghai.ksyuncs.com', GUANGZHOU: 'ks3-cn-guangzhou.ksyuncs.com', HONGKONG: 'ks3-cn-hk-1.ksyuncs.com', RUSSIA: 'ks3-rus.ksyuncs.com', SINGAPORE: 'ks3-sgp.ksyuncs.com', JR_BEIJING: 'ks3-jr-beijing.ksyuncs.com', JR_SHANGHAI: 'ks3-jr-shanghai.ksyuncs.com', GOV_BEIJING: 'ks3-gov-beijing.ksyuncs.com' }; /** * 给url添加请求参数 * @param url * @param obj * @returns {string} 带请求参数的url */ function addURLParam(url, obj){ url += url.indexOf("?") == -1 ? "?" : ""; var ret = []; for(var key in obj){ key = encodeURIComponent(key); var value = obj[key]; if(value && Object.prototype.toString.call(value) == '[object String]'){ ret.push(key + '=' + encodeURIComponent(value)); } } return url + ret.join('&'); }; /** * key 进行encodeURIComponent编码,'/'不能被编码 * */ function encodeKey(key) { var newKey = encodeURIComponent(key); newKey = newKey.replace(/%2F/g, '/'); if (newKey.startsWith('/')) newKey = newKey.replace('/', '%2F') // '//'needs convert to '/%2F' if (newKey.includes('//')) newKey = newKey.replace(/\/\//g, '/%2F') newKey = newKey.replace(/\!/g, '%21').replace(/\*/g, '%2A').replace(/\'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29'); return newKey } /*基于Javascript的Base64加解密算法*/ const Base64 = { encTable :[ /*Base64编码表*/ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' ,'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' ], // decTable:[ /*Base64解码表*/ // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, // 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, // -1, -1, -1, -1, -1, 00, 01, 02, 03, 04, // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, // 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // 49, 50, 51, -1, -1, -1, -1, -1 // ], encUTF8: function(str) { /*将任意字符串按UTF8编码*/ var code, res =[], len =str.length; var byte1, byte2, byte3, byte4, byte5, byte6; for (var i = 0; i < len; i++) { //Unicode码:按范围确定字节数 code = str.charCodeAt(i); //单字节ascii字符:U+00000000 – U+0000007F 0xxxxxxx if (code > 0x0000 && code <= 0x007F) res.push(code); //双字节字符:U+00000080 – U+000007FF 110xxxxx 10xxxxxx else if (code >= 0x0080 && code <= 0x07FF) { byte1 = 0xC0 | ((code >> 6) & 0x1F); byte2 = 0x80 | (code & 0x3F); res.push(byte1, byte2); } //三字节字符:U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx else if (code >= 0x0800 && code <= 0xFFFF) { byte1 = 0xE0 | ((code >> 12) & 0x0F); byte2 = 0x80 | ((code >> 6) & 0x3F); byte3 = 0x80 | (code & 0x3F); res.push(byte1, byte2, byte3); } //四字节字符:U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx else if (code >= 0x00010000 && code <= 0x001FFFFF) { byte1 =0xF0 | ((code>>18) & 0x07); byte2 =0x80 | ((code>>12) & 0x3F); byte3 =0x80 | ((code>>6) & 0x3F); byte4 =0x80 | (code & 0x3F); res.push(byte1, byte2, byte3, byte4); } //五字节字符:U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx else if (code >= 0x00200000 && code <= 0x03FFFFFF) { byte1 =0xF0 | ((code>>24) & 0x03); byte2 =0xF0 | ((code>>18) & 0x3F); byte3 =0x80 | ((code>>12) & 0x3F); byte4 =0x80 | ((code>>6) & 0x3F); byte5 =0x80 | (code & 0x3F); res.push(byte1, byte2, byte3, byte4, byte5); } //六字节字符:U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx else if (code >= 0x04000000 && code <= 0x7FFFFFFF) { byte1 =0xF0 | ((code>>30) & 0x01); byte2 =0xF0 | ((code>>24) & 0x3F); byte3 =0xF0 | ((code>>18) & 0x3F); byte4 =0x80 | ((code>>12) & 0x3F); byte5 =0x80 | ((code>>6) & 0x3F); byte6 =0x80 | (code & 0x3F); res.push(byte1, byte2, byte3, byte4, byte5, byte6); } } return res; }, encode: function(str) { /** * 将任意字符串用Base64加密 * str:要加密的字符串 * utf8编码格式 */ if (!str) return ''; var bytes = this.encUTF8(str); var i = 0, len = bytes.length, res = []; var c1, c2, c3; while (i < len) { c1 = bytes[i++] & 0xFF; res.push(Base64.encTable[c1 >> 2]); //结尾剩一个字节补2个= if (i == len) { res.push(Base64.encTable[(c1 & 0x03) << 4], '=='); break; } c2 = bytes[i++]; //结尾剩两个字节补1个= if (i == len) { res.push(Base64.encTable[((c1 & 0x03) << 4) | ((c2 >> 4) & 0x0F)]); res.push(Base64.encTable[(c2 & 0x0F) << 2], '='); break; } c3 = bytes[i++]; res.push(Base64.encTable[((c1 & 0x3) << 4) | ((c2 >> 4) & 0x0F)]); res.push(Base64.encTable[((c2 & 0x0F) << 2) | ((c3 & 0xC0) >> 6)]); res.push(Base64.encTable[c3 & 0x3F]); } return res.join(''); } }; const chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ const b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */ /* * //使用hmac_sha1算法计算字符串的签名 * return base-64 encoded strings */ let b64_hmac_sha1 = function(key, data){ return binb2b64(core_hmac_sha1(key, data)); } /* * Calculate the HMAC-SHA1 of a key and some data */ let core_hmac_sha1 = function(key, data){ var bkey = str2binb(key); if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); var ipad = Array(16), opad = Array(16); for(var i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); return core_sha1(opad.concat(hash), 512 + 160); } /* * Convert an array of big-endian words to a base-64 string */ let binb2b64 = function(binarray){ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var str = ""; for(var i = 0; i < binarray.length * 4; i += 3) { var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); for(var j = 0; j < 4; j++) { if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } } return str; } /* * Convert an 8-bit or 16-bit string to an array of big-endian words * In 8-bit function, characters >255 have their hi-byte silently ignored. */ let str2binb = function(str){ var bin = Array(); var mask = (1 << chrsz) - 1; for(var i = 0; i < str.length * chrsz; i += chrsz) bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); return bin; } /* * Calculate the SHA-1 of an array of big-endian words, and a bit length */ let core_sha1 = function(x, len){ /* append padding */ x[len >> 5] |= 0x80 << (24 - len % 32); x[((len + 64 >> 9) << 4) + 15] = len; var w = Array(80); var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var e = -1009589776; for(var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; var olde = e; for(var j = 0; j < 80; j++) { if(j < 16) w[j] = x[i + j]; else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j))); e = d; d = c; c = rol(b, 30); b = a; a = t; } a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); e = safe_add(e, olde); } return Array(a, b, c, d, e); } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ let safe_add = function(x, y){ var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /* * Bitwise rotate a 32-bit number to the left. */ let rol = function(num, cnt){ return (num << cnt) | (num >>> (32 - cnt)); } /* * Perform the appropriate triplet combination function for the current * iteration */ let sha1_ft = function(t, b, c, d){ if(t < 20) return (b & c) | ((~b) & d); if(t < 40) return b ^ c ^ d; if(t < 60) return (b & c) | (b & d) | (c & d); return b ^ c ^ d; } /* * Determine the appropriate additive constant for the current iteration */ let sha1_kt = function(t){ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; } var uuid = function () { var S4 = function () { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); }; return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4(); }; /** * 产生headers * CanonicalizedKssHeaders */ let generateHeaders =function(header) { var str = ''; var arr = []; if(header){ var prefix = 'x-kss'; let sortArr = []; for (const key in header) { if(key.indexOf(prefix) == 0){ sortArr.push({ key, value: header[key] }); } }; // step2 : 根据属性名排序 sortArr.sort(function(a, b) { return (a.key > b.key ? 1 : -1); }); // step3 : 拼接起来 sortArr.forEach((element, index) => { if(index === sortArr.length - 1){ str += element.key + ':' + element.value; }else{ str += element.key + ':' + element.value + '\n'; } }); } return str; } /** * 根据SK和请求生成Signature(用于Authorization头部) * @param sk secrete key * @param bucket bucket name * @param resource ObjectKey[?subResource] * @param httpVerb PUT/GET/POST/DELETE * @param headers headers of request * @param time_stamp expiration time * @returns {*} */ let getAuth = async function (sk, bucket, key, resource, httpVerb, headers, time_stamp, params){ if(params.signature){ if(typeof params.signature !== 'function') { return signatureResult; } const signatureResult = await params.signature({ bucket: bucket, key: key, resource: resource, method: httpVerb, headers: headers, date: time_stamp, config: this.config }); return signatureResult; } var Resource = encodeKey(key) + resource; var authHeaders = key2Lower(headers); var canonicalizedKssHeaders = generateHeaders(authHeaders); var canonicalizedResource = '/' + bucket + '/' + Resource; var contentType = authHeaders['content-type'] || ''; var contentMD5 = authHeaders['content-md5'] || ''; if (canonicalizedKssHeaders !== '') { var string2Sign = httpVerb.toUpperCase() + '\n' + contentMD5 + '\n' + contentType + '\n' + time_stamp + '\n' + canonicalizedKssHeaders + '\n' + canonicalizedResource; } else { var string2Sign = httpVerb.toUpperCase() + '\n' + contentMD5 + '\n' + contentType + '\n' + time_stamp + '\n' + canonicalizedResource; } console.log('string2Sign:' + string2Sign); var signature = b64_hmac_sha1(sk, string2Sign); console.log('signature:' + signature); return signature; } function each(obj, fn) { for (var i in obj) { if (obj.hasOwnProperty(i)) { fn(obj[i], i); } } } //将header内的key都转成小写 function key2Lower(obj) { var headers = {}; for (var key in obj) { headers[key.toLowerCase()] = obj[key] } return headers; } // 获取文件分片 function fileSlice(file, start, end, callback) { var blob; if (file.slice) { blob = file.slice(start, end); } else if (file.mozSlice) { blob = file.mozSlice(start, end); } else if (file.webkitSlice) { blob = file.webkitSlice(start, end); } var reader = new FileReader(); reader.onload = function(e) { callback(e.target.result); }; reader.readAsArrayBuffer(blob); }; // 获取文件分片 新 var fileSliceForUpload = function (file, start, end, isUseToUpload, callback) { var blob; if (file.slice) { // W3C blob = file.slice(start, end); } else if (file.mozSlice) { // 火狐 blob = file.mozSlice(start, end); } else if (file.webkitSlice) { // chrome blob = file.webkitSlice(start, end); } if (isUseToUpload) { var reader = new FileReader(); reader.onload = function (e) { // 直接返回 ArrayBuffer,不需要重新包装成 Blob callback(e.target.result); // 清理资源 reader.onload = null; reader.onerror = null; }; reader.onerror = function() { callback(null); reader.onload = null; reader.onerror = null; }; reader.readAsArrayBuffer(blob); } else { callback(blob); } }; var readAsBinaryString = function (blob, callback) { var readFun; var fr = new FileReader(); if (FileReader.prototype.readAsBinaryString) { readFun = FileReader.prototype.readAsBinaryString; fr.onload = function () { callback(this.result); fr.onload = null; fr.onerror = null; }; } else if (FileReader.prototype.readAsArrayBuffer) { // 在 ie11 添加 readAsBinaryString 兼容 readFun = function (fileData) { var binary = ''; var reader = new FileReader(); reader.onload = function (e) { var bytes = new Uint8Array(reader.result); var length = bytes.byteLength; for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } callback(binary); reader.onload = null; reader.onerror = null; }; reader.readAsArrayBuffer(fileData); }; fr.onload = null; fr.onerror = null; } else { console.error('FileReader not support readAsBinaryString'); } readFun.call(fr, blob); }; // 获取文件 md5 值 var md5ChunkSize = 1024 * 1024; var getFileMd5 = function (blob, callback, onProgress) { var size = blob.size; var loaded = 0; var md5ctx = md5.getCtx(); var next = function (start) { if (start >= size) { var hash = md5ctx.digest('hex'); callback(null, hash); return; } var end = Math.min(size, start + md5ChunkSize); fileSliceForUpload(blob, start, end, false,function (chunk) { readAsBinaryString(chunk, function (content) { chunk = null; md5ctx = md5ctx.update(content, true); loaded += content.length; content = null; if (onProgress) onProgress({ loaded: loaded, total: size, percent: Math.round((loaded / size) * 10000) / 10000 }); next(start + md5ChunkSize); }); }); }; next(0); }; /** * 生成合并分块上传使用的xml */ function generateCompleteXML(partList) { var length = partList.length; var str = ''; if (length > 0) { str = '<CompleteMultipartUpload>'; for (var i = 0; i < length; i++) { str += '<Part><PartNumber>' + partList[i].partNumber + '</PartNumber><ETag>' + partList[i].etag + '</ETag></Part>' } str += '</CompleteMultipartUpload>'; } return str; } // 函数节流 - 进度 var throttleOnProgress = function (total, onProgress) { var self = this; var size0 = 0; var size1 = 0; var time0 = Date.now(); var time1; var timer; function update() { timer = 0; if (onProgress && (typeof onProgress === 'function')) { time1 = Date.now(); var speed = Math.max(0, Math.round((size1 - size0) / ((time1 - time0) / 1000) * 100) / 100) || 0; var percent; if (size1 === 0 && total === 0) { percent = 1; } else { percent = Math.floor(size1 / total * 100) / 100 || 0; } time0 = time1; size0 = size1; try { onProgress({loaded: size1, total: total, speed: speed, percent: percent}); } catch (e) { } } } return function (info, immediately) { if (info) { size1 = info.loaded; total = info.total; } if (immediately) { clearTimeout(timer); update(); } else { if (timer) return; timer = setTimeout(update, self.config.progressInterval); } }; }; // 控制并发数量 /** * arr Array 遍历集合 * limit number 每次异步操作时允许的并发执行的任务数量 * iteratee AsyncFunction 应用于遍历arr的异步函数,数组下标并不通过迭代器,如 果你需要用到index,使用eachOfLimit,调用 (item,callback) * callback function<optional> 当所有迭代器结束或者出现一个错误调用(err) */ var syncLimit = function (arr, limit, iterator, callback) { callback = callback || function () {}; if (!arr.length || limit <= 0) { return callback(); } var completed = 0; var started = 0; var running = 0; (function replenish () { if (completed >= arr.length) { return callback(); } while (running < limit && started < arr.length) { started += 1; running += 1; iterator(arr[started - 1], function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; running -= 1; if (completed >= arr.length) { callback(); } else { replenish(); } } }); } })(); }; /** * 获取post方式签名 * @param {*} sk * @param {*} policy * @returns */ var getPostSignature = function (sk, policy) { var stringToSign = Base64.encode(JSON.stringify(policy)); var signature = b64_hmac_sha1(sk, stringToSign); return signature; } /** * 获取字符串的base64结果 * @param {*} policy * @returns */ var getStrBase64 = function (policy) { return Base64.encode(JSON.stringify(policy)) } var getSourceParams = function (source) { var m = source.match(/\/([^\s]+?)\/(.+)$/im); if (!m) return null; return { Bucket: m[1], Key: m[2] }; } var generatePresignedUrl = function(params, sk, ak, url){ var StringToSign = ''; var expiresTime = parseInt(new Date().getTime() / 1000) + params.expiration; var type = ''; var MD5 = ''; if(params.headers && params.headers['Content-Type']){ type = params.headers['Content-Type']; } if(params.headers && params.headers['Content-MD5']){ MD5 = params.headers['Content-MD5']; } var canonicalizedResource = '/' + params.bucket + '/' + params.key; //生成url var StringToSign = `${params.method}\n${MD5}\n${type}\n${expiresTime}\n${canonicalizedResource}`; console.log(StringToSign) var sign = b64_hmac_sha1(sk, StringToSign); if(url){ return `${url}?KSSAccessKeyId=${ak}&Expires=${expiresTime}&Signature=${encodeURIComponent(sign)}`; } return sign; } var getFileSize = function (api, params, callback) { var size; if (typeof params.body === 'string') { // 如果是字符串,转化为 Blob 对象 params.body = new Blob([params.body], { type: 'text/plain' }); } else if (params.file instanceof ArrayBuffer) { params.body = new Blob([params.body]); } // 保证 Body 存在,并且属于 Blob 对象或 File 对象 if ( params.body && (params.body instanceof Blob || params.body.toString() === '[object File]' || params.body.toString() === '[object Blob]') ) { size = params.body.size; } else { callback(utils.error(new Error('params body format error, Only allow File|Blob|String.'))); return; } params.contentLength = size; callback(null, size); }; function isArray(arr) { return arr instanceof Array; } function map(obj, fn) { var o = isArray(obj) ? [] : {}; for (var i in obj) { if (obj.hasOwnProperty(i)) { o[i] = fn(obj[i], i); } } return o; } function clone(obj) { return map(obj, function (v) { return typeof v === 'object' && v !== null ? clone(v) : v; }); } function extend(target, source) { each(source, function (val, key) { target[key] = source[key]; }); return target; } var error = function (err, opt) { var sourceErr = err; err.message = err.message || null; if (typeof opt === 'string') { err.error = opt; err.message = opt; } else if (typeof opt === 'object' && opt !== null) { extend(err, opt); if (opt.code || opt.name) err.code = opt.code || opt.name; if (opt.message) err.message = opt.message; if (opt.stack) err.stack = opt.stack; } if (typeof Object.defineProperty === 'function') { Object.defineProperty(err, 'name', { writable: true, enumerable: false }); Object.defineProperty(err, 'message', { enumerable: true }); } err.name = (opt && opt.name) || err.name || err.code || 'Error'; if (!err.code) err.code = err.name; if (!err.error) err.error = clone(sourceErr); // 兼容老的错误格式 return err; }; function filter(obj, fn) { var iaArr = isArray(obj); var o = iaArr ? [] : {}; for (var i in obj) { if (obj.hasOwnProperty(i)) { if (fn(obj[i], i)) { if (iaArr) { o.push(obj[i]); } else { o[i] = obj[i]; } } } } return o; } function isInArray(arr, item) { var flag = false; for (var i = 0; i < arr.length; i++) { if (item === arr[i]) { flag = true; break; } } return flag; } // 获取文件内容的 MD5 var getBodyMd5 = function (UploadCheckContentMd5, Body, callback) { if (UploadCheckContentMd5) { if (typeof Body === 'string') { callback(utils.md5(Body, true)); } else if (Blob && Body instanceof Blob) { utils.getFileMd5( Body, function (err, md5) { callback(md5); }, // onProgress ); } else { callback(); } } else { callback(); } }; var getBodyMd5Promise = function (uploadCheckContentMd5, Body) { return new Promise((resolve, reject) => { if (!uploadCheckContentMd5) { resolve(); return; } if (typeof Body === 'string') { resolve(utils.md5(Body, true)); return; } if (Blob && Body instanceof Blob) { utils.getFileMd5( Body, (err, md5) => { if (err) { reject(err); } else { resolve(md5); } }, // onProgress // 如果有进度回调,保持不变 ); } else { resolve(); // 对于未知类型的 Body,直接 resolve() } }); }; var b64 = function (str) { var i, len, char, res = ''; for (i = 0, len = str.length / 2; i < len; i++) { char = parseInt(str[i * 2] + str[i * 2 + 1], 16); res += String.fromCharCode(char); } return btoa(res); }; var utils = { addURLParam, b64_hmac_sha1, getAuth, getPostSignature, getStrBase64, each, isInArray, filter, fileSlice, fileSliceForUpload, md5, getFileMd5, generateCompleteXML, throttleOnProgress, syncLimit, getSourceParams, encodeKey, generatePresignedUrl, ENDPOINT, uuid, b64, getFileSize, map, error, extend, getBodyMd5, getBodyMd5Promise }; module.exports = utils;