UNPKG

ks3

Version:

本代码库为`金山云存储KS3`服务.主要提供`KS3 nodejs SDK`和`命令行工具`.

1,184 lines (1,048 loc) 35.8 kB
var auth = require('./../auth'); var config = require('./../../config'); var util = require('../util'); var mime = require('mime'); var fs = require('fs'); var Stream = require('stream'); var advance = require('../advance'); var debug = require('debug')('ks3:object'); var uploadMaxSize = config.uploadMaxSize; const crc64 = require("crc64-ecma182.js"); /** * get data from url and upload to ks3 * @param {*} params * @param {*} params.Bucket * @param {*} params.Key * @param {*} params.SourceUrl * @param {*} params.CallbackUrl * @param {*} params.ACL * @param {*} params.Taggings 非必填 eg:[{ key: k1, value: value1 }, { key: k2, value: vlaue2 }] * @param {*} cb * @description 详情请见 https://docs.ksyun.com/documents/42323 * */ function fetch (params, cb) { if (!params.SourceUrl) throw new Error('require the SourceUrl'); if (params.ACL && util.verifyAcl(params.ACL) == null) throw new Error('the illegal ACL'); var req = _getObjectRequestParams.call(this, 'fetch', 'PUT', params) if (params.SourceUrl) { req.headers['x-kss-sourceurl'] = params.SourceUrl } if (params.CallbackUrl) { req.headers['x-kss-callbackurl'] = params.CallbackUrl } if (params.ACL) { var attr_Acl = 'x-' + config.prefix + '-acl'; req.headers[attr_Acl] = params.ACL; } const Taggings = params.Taggings if (Taggings) { util.checkTag(Taggings) let tags = [] Taggings.forEach(item => { tags.push(`${item.key}=${item.value}`) }) req.headers['x-kss-tagging'] = tags.join('&') } var authStr = auth.generateAuth(this.ak, this.sk, req); this.request(req, null, authStr, cb); } /** * 删除单个Object * * @param {object} params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.VersionId 非必填 * @param {function} cb - callback * @description * - params: { * Bucket: '', // 非必传 * Key: '' // Object Key 必须传 * VersionId: '' // 非必填 * } */ function del(params, cb) { var req = _getObjectRequestParams.call(this, '', 'DELETE', params) var authStr = auth.generateAuth(this.ak, this.sk, req); this.request(req, null, authStr, cb); } /** * 获取object * * @param {object} params - params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.VersionId 非必填 * @param {String} params.headers 非必填 * @param {String} params.Output 非必填, 输出的文件路径或者一个写流,若不传入,则将完整内容写入回调函数data * @param {function} cb - callback * @description * - params: { * Bucket: '', //非必传 * Key: '', // Object Key 必须传 * VersionId: '' // 非必填 * headers: { Range: ''} 非必填 * } */ function get(params = {}, cb) { // params.dataType = 'xml'; var req = _getObjectRequestParams.call(this, '', 'GET', params) var reRange = /^bytes=(\d+)-(\d+)$/i; var range = params.headers && params.headers.Range if(range !== '' && reRange.test(range)) { req.headers['Range'] = range; } var authStr = auth.generateAuth(this.ak, this.sk, req); var bodyType = '' var outputStream = params.Output if (params.ReturnStream) { outputStream = new Stream.PassThrough(); bodyType = 'stream'; } else if (outputStream) { if (typeof params.Output == 'string') { try { outputStream = fs.createWriteStream(outputStream) } catch (err) { if (typeof cb === 'function') cb(err, null, null, null); } } bodyType = 'stream'; } else { bodyType = 'buffer' } req.Output = params.ReturnStream ? '' : outputStream req.ReturnStream = params.ReturnStream || false; req.rawBody = true // 使结果不受dataType的影响 返回buffer或者stream let request = this.request(req, null, authStr, function (err, data, response, body) { if (err) { if (outputStream) outputStream.emit('error', err) if (typeof cb === 'function') return cb(err, null, response, null); } let result = {}; if (data) { if (bodyType === 'buffer') { result = Buffer.from(data); } else { result = data } } if (typeof cb === 'function') cb(err, result, response); }); if (params.ReturnStream) return request; } /** * download stream * * @param {*} params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.VersionId 非必填 * @param {String} params.ReturnStream 非必填 * @param {String} params.headers 非必填 * @param {function} cb 非必填 * @returns Stream */ function getObjectStream (params, cb) { params.ReturnStream = true return get.call(this, params, cb) } /** * 获取Object ACL * * @param {object} params - params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.VersionId 非必填 * @param {function} cb * params: { * Key: '', // Object Key 必须传 * VersionId: '' // 非必填 * } */ function getAcl(params, cb) { var req = _getObjectRequestParams.call(this, 'acl', 'GET', params) var authStr = auth.generateAuth(this.ak, this.sk, req); this.request(req, null, authStr, cb); } /** * 获取Object ACL type * * @param {object} params - params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.VersionId 非必填 * @param {function} cb * params: { * Key: '', // Object Key 必须传 * VersionId: '' // 非必填 * } */ function getACLType(params, cb){ this.object.getAcl(params, function(err, body, res){ var type = 'private' if (err) { if (typeof cb === 'function') cb(err, type, res) return } var grant = body.AccessControlPolicy.AccessControlList.Grant const grantArray = Array.isArray(grant) ? grant : [grant] for(var grantee of grantArray) { if(grantee.Grantee.URI !== 'http://acs.ksyun.com/groups/global/AllUsers') { continue } if(grantee.Permission === 'READ'){ type = 'public-read' } } if (typeof cb === 'function') cb(err, type, res) }) } /** * 设置 Object ACL * * @param {object} params - params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.VersionId 非必填 * @param {String} params.ACL 非必填 * @param {function} cb * * @description * - params { * Key: '', // Object Key 必须传 * VersionId: '', * ACL: '' // Object ACL 必须 * } */ function putAcl(params, cb) { var ACL = params.ACL; if (!ACL) { throw new Error('require the permission'); } if (util.verifyAcl(ACL) == null) { throw new Error('the illegal ACL'); } var req = _getObjectRequestParams.call(this, 'acl', 'PUT', params) var attr_Acl = 'x-' + config.prefix + '-acl'; req.headers[attr_Acl] = ACL; var authStr = auth.generateAuth(this.ak, this.sk, req); this.request(req, null, authStr, cb); } /** * 获取指定object的元数据 * * @param {object} params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.VersionId 非必填 * @param {String} params.IfModifiedSince 非必填 * @param {function} cb * params { * Key: '' // 必须 * VersionId: '', // 非必填 * IfModifiedSince: ''// 指定时间后被修改,格式:格林尼治时间,如:'Wed, 13 Apr 2022 08:26:32 GMT' * } */ function headObject(params, cb) { var req = _getObjectRequestParams.call(this, '', 'HEAD', params) if (params.IfModifiedSince) { req.headers['If-Modified-Since'] = params.IfModifiedSince } var authStr = auth.generateAuth(this.ak, this.sk, req); this.request(req, null, authStr, cb); } /** * @function put方式上传Object * * @param {*} params Bucket/Key/FilePath/StorageClass/ACL/VersionId/Body,以及headers * @param {object} params * @param {String} params.Bucket 非必填 * @param {String} params.Key 必填 * @param {String} params.FilePath * @param {String} params.StorageClass 非必填 * @param {String} params.ACL 非必填 * @param {Buffer|ReadStream|String} params.Body * @param {String} params.VersionId 非必填 * @param {String} params.headers 非必填 * @param {function} cb结果回调 * @returns * @description * 1、如需保证传输过程中没有损坏,需要设置Content-MD5,会影响性能,不推荐使用 * 2、如需在服务端加密,需要设置ServerSideEncryption * 3、如需客户端加密, 需要设置SSECustomerAlgorithm、SSECustomerKey、SSECustomerKeyMD5 * 4、如果需要设置回调,回调详见文档:https://docs.ksyun.com/documents/956 * 5、为保持统一,将storageClass修改为StorageClass,大小写不影响 * 6、Body如果是stream,则content-length必须得传 * * 具体详见文档https://docs.ksyun.com/documents/960 * */ function put(params, cb) { var filePath = params.filePath || params.FilePath || null; var headers = params.headers ||{} var body = ''; var size = 0; let crcValues = 0; // 传递的是 STRING 或者BUFFER {Buffer || ReadStream || String} if (!filePath) { body = params.Body || ''; // if (util.isStream(body) && !headers['Content-Length']) { // throw new Error("upload stream require headers['Content-Length'] ") // } } else { // 传递的是文件路径 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { size = fs.statSync(filePath).size if (size >= uploadMaxSize) { throw new Error('The file size should be less than 5G.Plase use `Multipart upload`, visit http://ks3.ksyun.com/doc/api/multipart_upload.html'); } else { body = fs.createReadStream(filePath); } } else { throw new Error('the file is illegal'); } } console.log('body', body && body instanceof Stream); var req = _getObjectRequestParams.call(this, '', 'PUT', params) req.headers = Object.assign(req.headers, headers) // 在上传文件的时候默认一个content-type if (!req.type) req.type = params.isNoContent ? '' : mime.lookup(params.Key) req.isOnprogress = params.isOnprogress var acl = params.ACL; if (acl && util.verifyAcl(acl)) { var attr_Acl = 'x-' + config.prefix + '-acl'; req.headers[attr_Acl] = params.ACL; } var storageClass = params.storageClass || params.StorageClass if (storageClass && util.verifyStorageClass(storageClass)) { var storage_class = 'x-' + config.prefix + '-storage-class'; req.headers[storage_class] = storageClass } var authStr = auth.generateAuth(this.ak, this.sk, req, body); if(body && body instanceof Stream){ body.on('data', (chunk) => { crcValues = crc64.crc64(chunk, crcValues); // 更新哈希值 }); }else{ crcValues = crc64.crc64(Buffer.from(body)); // crcValues = crc64.crc64(body); } crcValues = crcValues.toString(); return this.request(req, body, authStr, function(err, data, res) { //请求失败直接返回 if(err) { if (typeof cb === 'function') cb(err, data, res); return } //put object copy 不用校验crc64 var copy_source = 'x-' + config.prefix + '-copy-source'; if(req.headers[copy_source]){ if (typeof cb === 'function') cb(err, data, res); return } let meta = util.getObjectMetadata(res); if(meta['x-kss-checksum-crc64ecma'] !== crcValues && config.uploadCheckCRC){ err = new Error('crc64校验失败' + 'server:' + meta['x-kss-checksum-crc64ecma'] + ';client:' + crcValues + ' requestId:' + res.headers['x-kss-request-id']) } if (typeof cb === 'function') cb(err, data, res) }); } /** * 下面这些部分都是关于分块上传的 */ /** * 初始化分块上传 * * @param {Object} params * @param {String} params.StorageClass * @param {String} params.ACL * @param {function} cb */ var multitpart_upload_init = function(params, cb) { var req = _getObjectRequestParams.call(this, 'uploads', 'POST', params) var acl = params.ACL; if (acl && util.verifyAcl(acl)) { var attr_Acl = 'x-' + config.prefix + '-acl'; req.headers[attr_Acl] = params.ACL; } var storageClass = params.storageClass || params.StorageClass if (storageClass && util.verifyStorageClass(storageClass)) { req.headers['x-kss-storage-class'] = storageClass } var body = null // TODO: x-kss-meta var authStr = auth.generateAuth(this.ak, this.sk, req, body); this.request(req, body, authStr, cb); } /** * 上传分块 * * @param {Object} params * @param {String} params.Bucket * @param {String} params.Key * @param {String} params.UploadId * @param {String} params.PartNumber * @param {String} params.Body * @param {String} params.Type * @param {String} params.Body * @param {String} params.Type * @param {String} params.headers * @param {function} cb * @returns */ function upload_part(params, cb){ var partNumber = (typeof params.PartNumber !== 'undefined') ? params.PartNumber: ''; var uploadId = params.UploadId || ''; if (partNumber==='' || !uploadId) { throw new Error('require the partNumber and uploadId'); } params.resource = `?partNumber=${partNumber}&uploadId=${uploadId}` var req = _getObjectRequestParams.call(this, '', 'PUT', params) var body = params.body || params.Body; if (!body) { throw new Error('require the Body or Body can not be null') } var authStr = auth.generateAuth(this.ak, this.sk, req, body); return this.request(req, body, authStr, cb); } /** * 分块拷贝 * * @param {Object} params * @param {String} params.Bucket * @param {String} params.Key * @param {String} params.SourceBucket * @param {String} params.SourceKey * @param {String} params.PartNumber * @param {String} params.UploadId * @param {String} params.Range bytes={first}-{last} * @param {String} params.headers * @param {function} cb */ function uploadPartCopy (params, cb) { var uploadId = params.UploadId || ''; if (!uploadId) { throw new Error('require the uploadId'); } var partNumber = (typeof params.PartNumber !== 'undefined') ? params.PartNumber: ''; params.resource = `?partNumber=${partNumber}&uploadId=${uploadId}` var req = _getObjectRequestParams.call(this, '', 'PUT', params) req.headers['x-kss-copy-source'] = `/${params.SourceBucket}/${util.encodeKey(params.SourceKey)}` req.headers['x-kss-copy-source-range'] = params.Range var authStr = auth.generateAuth(this.ak, this.sk, req, ''); return this.request(req, '', authStr, cb); } /** * 合并分块 * * @param {Object} params * @param {String} params.UploadId * @param {String} params.ACL * @param {function} cb */ function upload_complete(params,cb){ var uploadId = params.UploadId || ''; if (!uploadId) { throw new Error('require the uploadId'); } /** * http://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadComplete.html * <CompleteMultipartUpload> <Part> <PartNumber>PartNumber</PartNumber> <ETag>ETag</ETag> </Part> ... </CompleteMultipartUpload> */ params.type = 'text/plain' params.resource = `?uploadId=${uploadId}` var req = _getObjectRequestParams.call(this, '', 'POST', params) var acl = params.ACL; if (acl && util.verifyAcl(acl)) { var attr_Acl = 'x-' + config.prefix + '-acl'; req.headers[attr_Acl] = params.ACL; } var body = params.body || params.Body; if (!body) { throw new Error('require the Body or Body can not be null') } var authStr = auth.generateAuth(this.ak, this.sk, req, body); this.request(req, body, authStr, cb); } /** * 废弃分块 * * @param {Object} params * @param {String} params.UploadId * @param {function} cb */ function upload_abort(params,cb) { var uploadId = params.UploadId || ''; if (!uploadId) { throw new Error('require the uploadId'); } params.resource = `?uploadId=${uploadId}` var req = _getObjectRequestParams.call(this, '', 'DELETE', params) var body = params.body || params.Body || ''; var authStr = auth.generateAuth(this.ak, this.sk, req, body); this.request(req, body, authStr, cb); } /** * 列举分块 * * @param {Object} params * @param {String} params.UploadId * @param {function} cb */ function upload_list_part(params,cb){ var uploadId = params.UploadId || ''; if (!uploadId) { throw new Error('require the uploadId'); } params.dataType = 'xml' params.resource = `?uploadId=${uploadId}` var req = _getObjectRequestParams.call(this, null, 'GET', params) var body = params.body || params.Body || ''; var authStr = auth.generateAuth(this.ak, this.sk, req, body); this.request(req, body, authStr, cb); } /** * 将sourceBucket这个存储空间下的sourceKey这个object复制到destinationBucket这个存储空间下,并命名为destinationObject * 能拷贝的源文件大小最大为5GB, 如需要拷贝的源文件超过5GB,请使用sliceCopyFile接口 进行分块拷贝。 * @remark 当前接口不支持跨region复制 * @param {Object} params {"destinationBucket","destinationObject","sourceBucket","sourceKey", headers} * @param {Object} params.DestinationBucket * @param {Object} params.DestinationObject * @param {Object} params.SourceBucket * @param {Object} params.SourceKey * @param {Object} params.headers * @param {function} cb * */ function copy (params, cb) { var sourceBucket = params.SourceBucket || params.sourceBucket var sourceKey = params.SourceKey || params.sourceKey var destinationBucket = params.DestinationBucket || params.destinationBucket var destinationObject = params.DestinationObject || params.destinationObject if (!destinationBucket) throw new Error('require the destinationBucket') if (!destinationObject) throw new Error('require the destinationObject') const self = this // 先head一下source是否存在 如果存在则需要取其加密信息 否则会复制失败 如果不存在则直接返回error headObject.call(self, { Bucket: sourceBucket, Key: sourceKey, Endpoint: params.Endpoint }, function (err, data, res) { if(err) { if (typeof cb === 'function') cb(err, data, res); return } var headers = params.headers || {} headers = Object.assign({ 'x-kss-copy-source': '/' + sourceBucket + '/' + util.encodeKey(sourceKey), 'x-kss-storage-class': res.caseless.get('x-kss-storage-class') || 'STANDARD' }, headers) const { ServerSideEncryption, SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } = _getEncryptInfo(res) var p = { Bucket: destinationBucket, Key: destinationObject, headers, isNoContent: true, Endpoint: params.Endpoint, ServerSideEncryption, SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } put.apply(self, [p, cb]) }) } function copyObject (params, cb) { var sourceBucket = params.SourceBucket || params.sourceBucket var sourceKey = params.SourceKey || params.sourceKey var Bucket = params.Bucket || this.bucketName var Endpoint = params.Endpoint || this.endpoint var Key = params.Key if (!Bucket) throw new Error('require the Bucket') if (!Key) throw new Error('require the Key') const self = this // 先head一下source是否存在 如果存在则需要取其加密信息 否则会复制失败 如果不存在则直接返回error headObject.call(self, { Bucket: sourceBucket, Key: sourceKey, Endpoint }, function (err, data, res) { if(err) { if (typeof cb === 'function') cb(err, data, res); return } var headers = params.headers || {} headers = Object.assign({ 'x-kss-copy-source': '/' + sourceBucket + '/' + util.encodeKey(sourceKey), 'x-kss-storage-class': res.caseless.get('x-kss-storage-class') || 'STANDARD' }, headers) const { ServerSideEncryption, SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } = _getEncryptInfo(res) var p = { Bucket, Key, ACL: params.ACL, headers, isNoContent: true, Endpoint, ServerSideEncryption, SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } put.apply(self, [p, cb]) }) } /** * 解冻归档文件 * * @param {*} params { Bucket, Key } * @param {function} cb * @description 此接口只针对归档类型的Object的解冻操作。如果一个Object是标准或者低频访问类型,不要调用该接口。 * 具体说明详见:https://docs.ksyun.com/documents/5961 */ function restore (params, cb) { var req = _getObjectRequestParams.call(this, 'restore', 'POST', params) var authStr = auth.generateAuth(this.ak, this.sk, req); var body = null; this.request(req, body, authStr, cb); } /** * 文件重命名 * * @param {Object} params * @param {String} params.Bucket * @param {String} params.Key * @param {String} params.NewKey * @param {function} cb * @description 具体详情见:https://docs.ksyun.com/documents/908#2 */ function rename (params, cb) { params = util.toUpperCaseParmas(params) const { Bucket = this.bucketName, Key, NewKey, Endpoint = this.endpoint } = params const self = this if(params.NewKey === params.Key){ throw new Error('The newKey and the Key cannot be the same') } var p = { Bucket: Bucket, Key: NewKey, SourceBucket: Bucket, SourceKey: Key, onProgress: params.onProgress, Endpoint: Endpoint } advance.sliceCopyFile.apply(self, [p, function (err, data, res) { if (!err) { // 删原始文件 del.apply(self, [{ Bucket, Key, Endpoint }, cb]) } else { cb(err, data, res) } }]) } /** * 修改文件存储类型 * * @param {Object} params * @param {String} params.Bucket * @param {String} params.Key * @param {String} params.StorageClass StorageClass: "STANDARD"、"STANDARD_IA"、"ARCHIVE"。 * "STANDARD"表示标准存储,"STANDARD_IA"表示低频存储,如果不指定,默认为标准存储。 * @param {function} cb * @description 具体详见:https://docs.ksyun.com/documents/908#16 * */ function modifyStorageClass (params, cb) { const { Bucket = this.bucketName , Key, Endpoint = this.endpoint } = params; var storageClass = params.StorageClass || params.storageClass || 'STANDARD' // 修改之前需要先判断是否存在该object const self = this; headObject.apply(self, [{ Bucket, Key, Endpoint}, function (err, data, res) { if (!err) { const { ServerSideEncryption, SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } = _getEncryptInfo(res) const p = { Bucket, Key, headers: { 'x-kss-copy-source': '/' + Bucket + '/' + util.encodeKey(Key), 'x-kss-metadata-directive': 'REPLACE', 'x-kss-storage-class': storageClass }, ServerSideEncryption, SSECustomerAlgorithm, SSECustomerKey, Endpoint, SSECustomerKeyMD5 } put.apply(self, [p, cb]) } else { cb(err, data, res) } }]) } /** * 生成外链 可用于分享或者上传 * * @param {Object} params { Bucket, Key, Expiration, headers, Method, Sign } * @param {String} params.Bucket * @param {String} params.Key * @param {String} params.Expiration * @param {String} params.headers * @param {String} params.Method //默认GET * @param {String} params.Sign * @param {String} params.QueryParams 请求参数,{key: 'val'} 的格式 eg:{delimiter: '/'} * @description Expiration: 单位s,不指定的话则默认为15分钟后过期 * - 如果是外链上传,需要注意的是headers参数需要和前端传参保持一致 * - 如果是公开的文件 则Sign为false 可以不需要签名直接生成外链 */ function generatePresignedUrl (params, cb) { var { Bucket, Sign = true, QueryParams = {}, Endpoint = this.endpoint, Key} = params // 兼容老版本 之前使用的是isPublic if (params.hasOwnProperty('isPublic')) { Sign = !params.isPublic } //匿名访问时默认sign为false if(!this.ak && !this.sk){ Sign = false } // 判断Query格式是否正确 if(!util.isObject(QueryParams)){ throw new Error('Query must be an object'); } var bucketName = Bucket || this.bucketName || ''; if(!bucketName) { throw new Error('require the bucket name'); } var query = ''; if(params.resource){ const pairs = params.resource.split('&'); pairs.forEach(pair => { const [key, value] = pair.split('='); QueryParams[key] = value || ''; }); } if(QueryParams){ var Query = util.extractSubResourceAndQuery(QueryParams); //需要计算签名的Query var subResource = Query.subResource ? '?' + Query.subResource : ''; //不需要计算签名的Query var query = Query.query; if(Query.query){ if(Sign || subResource){ query = '&' + Query.query; }else{ query = '?' + Query.query; } } params.resource = subResource } if (!Sign) { var key = util.encodeKey(Key) // 生成外链的计算 需要对key进行二次处理 key = util.encodePresignedUrlKey(Key) let uri = config.protocol + '://' + bucketName + '.' + Endpoint + '/' + key + subResource + query if (this.domainMode) { uri = config.protocol + '://' + config.baseUrl + '/' + key + subResource + query } if (typeof cb === 'function') cb(null, uri, null); return } var expiration = params.expiration || params.Expiration || 15 * 60 const expiresTime = util.getExpiresTime(expiration); params.date = expiresTime const method = params.Method || params.method || 'GET' var req = _getObjectRequestParams.call(this, subResource, method, params) const sign = auth.generateToken(this.sk, req, null) const url = req.uri + (params.resource ? '&' : '?') +'KSSAccessKeyId=' + encodeURIComponent(this.ak) + '&Expires=' + expiresTime + '&Signature=' + encodeURIComponent(sign) + query; if (typeof cb === 'function') cb(null, url, null, null); } /** * 可同时更新acl与stroage class * * @deprecated 该方法存在缺陷,同时设置时只有acl生效,故从v1.2.0弃用 * * @param {object} params - params to func, { Bucket, Key, ACL, StorageClass } * @param {function} cb - callback function * @description * ACL: "private"、"public-read" * * StorageClass: "STANDARD"、"STANDARD_IA"、"ARCHIVE"。 */ function updateAclAndStorageClass ( params, cb) { console.warn('calling a deprecated method!') var req = _getObjectRequestParams.call(this, 'acl', 'PUT', params) var key = util.encodeKey(params.Key); var headers = req.headers var ACL = params.ACL; if (ACL && util.verifyAcl(ACL) !== null) { // 设置 ACL header var attr_Acl = 'x-' + config.prefix + '-acl'; headers[attr_Acl] = ACL; } // 如果是修改存储类型 if (params.storageClass) { headers['x-kss-copy-source'] = '/' + bucketName + '/' + key headers['x-kss-metadata-directive'] = 'REPLACE' headers['x-kss-storage-class'] = params.storageClass || params.StorageClass } var authStr = auth.generateAuth(this.ak, this.sk, req); var body = null; this.request(req, body, authStr, cb); } /** * 设置或更新指定Key的tagging * * @param {Object} params * @param {String} params.Bucket * @param {String} params.Key * @param {String} params.Taggings * @param {*} cb * * @description Taggings: [{key: k, value: v}] */ function putObjectTagging (params, cb) { const Taggings = params.Taggings if (!Taggings) { throw new Error('require the Taggings') } util.checkTag(Taggings) // refactor data structure const tags = [] const tagging = { Tagging: { TagSet: { Tag: tags } } } Taggings.forEach(item => { tags.push({ Key: item.key, Value: item.value }) }) const paramXml = util.objToXml(tagging) var body = paramXml; params.type = 'text/plain' var req = _getObjectRequestParams.call(this, 'tagging', 'PUT', params) var authStr = auth.generateAuth(this.ak, this.sk, req); this.request(req, body, authStr, cb); } /** * 获取指定key的tagging * * @param {*} params { Bucket, Key } * @param {function} cb */ function getObjectTagging (params, cb) { var bucketName = params.Bucket || this.bucketName || ''; if(!bucketName) { throw new Error('require the bucket name'); } var req = _getObjectRequestParams.call(this, 'tagging', 'GET', params) var authStr = auth.generateAuth(this.ak, this.sk, req); var body = null; this.request(req, body, authStr, cb); } /** * 删除指定Key的tagging * * @param {*} params { Bucket, Key } * @param {function} cb */ function deleteObjectTagging (params, cb) { var req = _getObjectRequestParams.call(this, 'tagging', 'delete', params) var authStr = auth.generateAuth(this.ak, this.sk, req); var body = null; this.request(req, body, authStr, cb); } /** * 恢复回收站中的Object * * @param {Object} params * @param {String} params.Key 必填,指定要恢复的Object * @param {Boolean} params.Overwrite 非必填,从回收站内被恢复的Object在Bucket中存在同名Object时,是否支持覆盖,有效值:true、false,默认为false,默认值:false * @param {String} params.RetentionId 非必填,指定恢复的Object的删除ID,不传默认恢复最新一个版本 * @param {function} cb */ function recoverObject (params, cb) { if (typeof params === 'function') { cb = params; params = {}; } if(!params.Key){ throw new Error('require the Key') } var headers = params.headers || {} if(params.RetentionId){ headers[`x-${config.prefix}-retention-id`] = params.RetentionId } if(params.Overwrite && typeof params.Overwrite === 'boolean'){ headers[`x-${config.prefix}-retention-overwrite`] = params.Overwrite } params.headers = headers const req = _getObjectRequestParams.call(this, 'recover', 'POST', params) var body = null; var curAuth = auth.generateAuth(this.ak, this.sk, req); this.request(req, body, curAuth, cb); } /** * 删除回收站中的Object * @param {String} params.Key 必填,指定要删除的Object的Key * @param {String} params.RetentionId 必填,指定Object的删除ID * @param {function} cb */ function clearObject (params, cb) { if (typeof params === 'function') { cb = params; params = {}; } if(!params.RetentionId){ throw new Error('require the RetentionId') } var headers = params.headers || {} headers[`x-${config.prefix}-retention-id`] = params.RetentionId params.headers = headers const req = _getObjectRequestParams.call(this, 'clear', 'DELETE', params) var body = null; var curAuth = auth.generateAuth(this.ak, this.sk, req); this.request(req, body, curAuth, cb); } // check key wheath is null function _checkObjectKey (options) { if (options.Key === null || options.Key === undefined) { throw new Error('require the Key'); } } /*** * @param {String} subres 指定的资源路径,比如:'?acl'、'?fetch'对应的subres为:'acl'、'fetch' * @param {String} method 请求方式 * @param {Object} options 请求参数 */ function _getObjectRequestParams (subres, method, options) { _checkObjectKey(options) var bucketName = options.Bucket || this.bucketName || ''; if(!bucketName) { throw new Error('require the bucket name'); } var key = util.encodeKey(options.Key) // 生成外链的计算 需要对key进行二次处理 if (options.date) { key = util.encodePresignedUrlKey(options.Key) } let endpoint = options.Endpoint || this.endpoint // according to different business generate resource to calc auth // eg: ?acl&versionId= 、?versionId= var queryStr = '' // 连接符 var sign = '?' if (subres) { queryStr = `${sign}${subres}` sign = '&' } if (options.VersionId) { queryStr += options.VersionId ? `${sign}versionId=${options.VersionId}` : ''; } var resource = '/' + key + (options.resource ? options.resource : `${queryStr}`) // 转码 主要是为了处理没有名称的文件夹 resource = resource.replace('//', '/%2F') var uri = config.protocol + '://' + bucketName + '.' + endpoint + resource // 是否为自定义域名 if (this.domainMode) { uri = config.protocol + '://' + endpoint + resource } const headers = options.headers || {} const type = options.Type || options.type const dataType = options.DataType || options.dataType var req = { method: method || 'GET', date: options.date || util.getDate(), uri, resource: '/' + bucketName + resource, headers: options.headers || {}, type: type || util.ignoreLowerCase(headers, 'content-type') || '',// 以设置的类型为主 dataType: dataType || '' }; // SSE-KS3 var encryption = options.ServerSideEncryption if (encryption) { if(!util.verifyServerEncryption(encryption)) throw new Error('check your encrypt type, we now only support "AES256".') req.headers['x-kss-server-side-encryption'] = encryption } // SSE-C const { SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } = options if (SSECustomerAlgorithm && SSECustomerKey && SSECustomerKeyMD5 ) { if(!util.verifyServerEncryption(SSECustomerAlgorithm)) throw new Error('check your encrypt type, we now only support "AES256".') req.headers['x-kss-server-side-encryption-customer-algorithm'] = SSECustomerAlgorithm req.headers['x-kss-server-side-encryption-customer-key'] = SSECustomerKey req.headers['x-kss-server-side-encryption-customer-key-MD5'] = SSECustomerKeyMD5 } // 被拷贝对象为客户端提供密钥加密方式 需要同时提供下面三个属性 const { SourceSSECustomerAlgorithm, SourceSSECustomerKey, SourceSSECustomerKeyMD5 } = options if (SourceSSECustomerAlgorithm && SourceSSECustomerKey && SourceSSECustomerKeyMD5) { req.headers['x-kss-copy-source-server-side-encryption-customer-algorithm'] = SSECustomerAlgorithm req.headers['x-kss-copy-source-server-side-encryption-customer-key'] = SSECustomerKey req.headers['x-kss-copy-source-server-side-encryption-customer-key-MD5'] = SSECustomerKeyMD5 } if(req.headers['x-' + config.prefix + '-tagging']){ req.headers['x-' + config.prefix + '-tagging'] = util.splitTag(req.headers['x-' + config.prefix + '-tagging']) } return req; } /** * 从response中提取加密信息 * * @param {*} response * @returns */ function _getEncryptInfo (response) { const ServerSideEncryption = response.caseless.get('x-kss-server-side-encryption') || '' const SSECustomerAlgorithm = response.caseless.get('x-kss-server-side​-encryption​-customer-algorithm') || '' const SSECustomerKey = response.caseless.get('x-kss-server-side​-encryption​-customer-key') || '' const SSECustomerKeyMD5 = response.caseless.get('x-kss-server-side-encryption-customer-key-MD5') || '' return { ServerSideEncryption, SSECustomerAlgorithm, SSECustomerKey, SSECustomerKeyMD5 } } module.exports = Object.assign({ fetch, del: del, get: get, getObjectStream, put: put, getAcl: getAcl, getACLType: getACLType, putAcl: putAcl, headObject, head: headObject, copy: copy, copyObject: copyObject, restore, rename, modifyStorageClass, generatePresignedUrl, multitpart_upload_init: multitpart_upload_init, upload_part:upload_part, upload_complete:upload_complete, upload_abort:upload_abort, upload_list_part:upload_list_part, initiateMultipartUpload: multitpart_upload_init, // 与api保持一致 uploadPart: upload_part, completaMultipartUpload: upload_complete, abortMultipartUpload: upload_abort, listParts: upload_list_part, uploadPartCopy: uploadPartCopy, putObjectTagging, getObjectTagging, deleteObjectTagging, recoverObject, clearObject }, advance)