ks3
Version:
本代码库为`金山云存储KS3`服务.主要提供`KS3 nodejs SDK`和`命令行工具`.
1,184 lines (1,048 loc) • 35.8 kB
JavaScript
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)