diff --git a/README.md b/README.md
index abf2225cb..ba7c10889 100644
--- a/README.md
+++ b/README.md
@@ -42,9 +42,7 @@ Node.js >= 8.0.0 required. You can use 4.x in Node.js < 8.
### QA
-You can join DingDing Talk Group, [Group Link](https://qr.dingtalk.com/action/joingroup?code=v1,k1,E60EuCmxajfilkaR/kknRcGR9UissskPEXu/1td36z0=)
-
-
+Please log in to the official website and contact technical support.
## License
@@ -162,6 +160,7 @@ All operation use es7 async/await to implement. All api is async function.
- [.listUploads(query[, options])](#listuploadsquery-options)
- [.abortMultipartUpload(name, uploadId[, options])](#abortmultipartuploadname-uploadid-options)
- [.calculatePostSignature(policy)](#calculatePostSignaturepolicy)
+ - [.signPostObjectPolicyV4(policy, date)](#signpostobjectpolicyv4policy-date)
- [.getObjectTagging(name, [, options])](#getObjectTaggingname-options)
- [.putObjectTagging(name, tag[, options])](#putObjectTaggingname-tag-options)
- [.deleteObjectTagging(name, [, options])](#deleteObjectTaggingname-options)
@@ -2450,7 +2449,7 @@ Success will return objects list on `objects` properties.
- storageClass {String} storage class type, e.g.: `Standard`
- owner {Object} object owner, including `id` and `displayName`
- restoreInfo {Object|undefined} The restoration status of the object
- - ongoingRequest {Boolean} Whether the restoration is complete
+ - ongoingRequest {Boolean} Whether the restoration is ongoing
- expireDate {Date|undefined} The time before which the restored object can be read
- prefixes {Array} prefix list
- isTruncated {Boolean} truncate or not
@@ -2519,7 +2518,7 @@ Success will return objects list on `objects` properties.
- storageClass {String} storage class type, e.g.: `Standard`
- owner {Object|null} object owner, including `id` and `displayName`
- restoreInfo {Object|undefined} The restoration status of the object
- - ongoingRequest {Boolean} Whether the restoration is complete
+ - ongoingRequest {Boolean} Whether the restoration is ongoing
- expireDate {Date|undefined} The time before which the restored object can be read
- prefixes {Array} prefix list
- isTruncated {Boolean} truncate or not
@@ -2602,7 +2601,7 @@ Success will return objects list on `objects` properties.
- storageClass {String} storage class type, e.g.: `Standard`
- owner {Object} object owner, including `id` and `displayName`
- restoreInfo {Object|undefined} The restoration status of the object
- - ongoingRequest {Boolean} Whether the restoration is complete
+ - ongoingRequest {Boolean} Whether the restoration is ongoing
- expireDate {Date|undefined} The time before which the restored object can be read
- deleteMarker {Array} object delete marker info list
Each `ObjectDeleteMarker`
@@ -2963,6 +2962,8 @@ parameters:
- [timeout] {Number} the operation timeout
- [versionId] {String} the version id of history object
- [type] {String} the default type is Archive
+ - [Days] {number} The duration within which the object remains in the restored state. The default value is 2.
+ - [JobParameters] {string} The container that stores the restoration priority. This parameter is valid only when you restore Cold Archive or Deep Cold Archive objects. The default value is Standard.
Success will return:
@@ -2974,48 +2975,48 @@ Success will return:
example:
-- Restore an object with Archive type
+- Restore an Archive object
```js
const result = await store.restore('ossdemo.txt');
console.log(result.status);
```
-- Restore an object with ColdArchive type
+- Restore a Cold Archive object
```js
const result = await store.restore('ossdemo.txt', { type: 'ColdArchive' });
console.log(result.status);
```
-- Days for unfreezing Specifies the days for unfreezing
+- Restore a Cold Archive object with Days
```js
const result = await store.restore('ossdemo.txt', { type: 'ColdArchive', Days: 2 });
console.log(result.status);
```
-- JobParameters for unfreezing Specifies the JobParameters for unfreezing
+- Restore a Cold Archive object with Days and JobParameters
```js
const result = await store.restore('ossdemo.txt', { type: 'ColdArchive', Days: 2, JobParameters: 'Standard' });
console.log(result.status);
```
-- Restore an object with DeepColdArchive type
+- Restore a Deep Cold Archive object
```js
const result = await store.restore('ossdemo.txt', { type: 'DeepColdArchive' });
console.log(result.status);
```
-- Days for unfreezing Specifies the days for unfreezing
+- Restore a Deep Cold Archive object with Days
```js
const result = await store.restore('ossdemo.txt', { type: 'DeepColdArchive', Days: 2 });
console.log(result.status);
```
-- JobParameters for unfreezing Specifies the JobParameters for unfreezing
+- Restore a Deep Cold Archive object with Days and JobParameters
```js
const result = await store.restore('ossdemo.txt', { type: 'DeepColdArchive', Days: 2, JobParameters: 'Standard' });
@@ -3852,6 +3853,89 @@ Object:
- Signature {String}
- policy {Object} response info
+### .signPostObjectPolicyV4(policy, date)
+
+Get a V4 signature of the PostObject request.
+
+parameters:
+
+- policy {string | Object} The policy form field in a PostObject request is used to specify the expiration time and conditions of the PostObject request that you initiate to upload an object by using an HTML form. The value of the policy form field is a JSON string or an object.
+- date {Date} The time when the request was initiated.
+
+Success will return a V4 signature of the PostObject request.
+
+example:
+
+```js
+const axios = require('axios');
+const dateFormat = require('dateformat');
+const FormData = require('form-data');
+const { getCredential } = require('ali-oss/lib/common/signUtils');
+const { getStandardRegion } = require('ali-oss/lib/common/utils/getStandardRegion');
+const { policy2Str } = require('ali-oss/lib/common/utils/policy2Str');
+const OSS = require('ali-oss');
+
+const client = new OSS({
+ accessKeyId: 'yourAccessKeyId',
+ accessKeySecret: 'yourAccessKeySecret',
+ stsToken: 'yourSecurityToken',
+ bucket: 'yourBucket',
+ region: 'oss-cn-hangzhou'
+});
+const name = 'yourObjectName';
+const formData = new FormData();
+formData.append('key', name);
+formData.append('Content-Type', 'yourObjectContentType');
+// your object cache control
+formData.append('Cache-Control', 'max-age=30');
+const url = client.generateObjectUrl(name).replace(name, '');
+const date = new Date();
+// The expiration parameter specifies the expiration time of the request.
+const expirationDate = new Date(date);
+expirationDate.setMinutes(date.getMinutes() + 1);
+// The time must follow the ISO 8601 standard
+const formattedDate = dateFormat(date, "UTC:yyyymmdd'T'HHMMss'Z'");
+const credential = getCredential(formattedDate.split('T')[0], getStandardRegion(client.options.region), client.options.accessKeyId);
+formData.append('x-oss-date', formattedDate);
+formData.append('x-oss-credential', credential);
+formData.append('x-oss-signature-version', 'OSS4-HMAC-SHA256');
+const policy = {
+ expiration: expirationDate.toISOString(),
+ conditions: [
+ { bucket: client.options.bucket },
+ {'x-oss-credential': credential},
+ {'x-oss-date': formattedDate},
+ {'x-oss-signature-version': 'OSS4-HMAC-SHA256'},
+ ['content-length-range', 1, 10],
+ ['eq', '$success_action_status', '200'],
+ ['starts-with', '$key', 'yourObjectName'],
+ ['in', '$content-type', ['image/jpg', 'text/plain']],
+ ['not-in', '$cache-control', ['no-cache']]
+ ]
+};
+
+if (client.options.stsToken) {
+ policy.conditions.push({'x-oss-security-token': client.options.stsToken});
+ formData.append('x-oss-security-token', client.options.stsToken);
+}
+
+const signature = client.signPostObjectPolicyV4(policy, date);
+formData.append('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64'));
+formData.append('x-oss-signature', signature);
+formData.append('success_action_status', '200');
+formData.append('file', 'yourFileContent');
+
+axios.post(url, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+}).then((result) => {
+ console.log(result.status);
+}).catch((e) => {
+ console.log(e);
+});
+```
+
### .getObjectTagging(name[, options])
Obtains the tags of an object.
diff --git a/karma.conf.js b/karma.conf.js
index e69c46e42..92c43328d 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -34,7 +34,7 @@ module.exports = function (config) {
concurrency: 1,
client: {
mocha: {
- timeout: 6000
+ timeout: 10000
}
}
});
diff --git a/lib/browser/object.js b/lib/browser/object.js
index df202f6ff..c569ea4b5 100644
--- a/lib/browser/object.js
+++ b/lib/browser/object.js
@@ -167,6 +167,7 @@ merge(proto, require('../common/object/generateObjectUrl'));
merge(proto, require('../common/object/signatureUrl'));
merge(proto, require('../common/object/asyncSignatureUrl'));
merge(proto, require('../common/object/signatureUrlV4'));
+merge(proto, require('../common/object/signPostObjectPolicyV4'));
proto.putMeta = async function putMeta(name, meta, options) {
const copyResult = await this.copy(name, name, {
@@ -301,20 +302,22 @@ proto.restore = async function restore(name, options = { type: 'Archive' }) {
options.subres.versionId = options.versionId;
}
const params = this._objectRequestParams('POST', name, options);
+ const paramsXMLObj = {
+ RestoreRequest: {
+ Days: options.Days ? options.Days : 2
+ }
+ };
+
if (options.type === 'ColdArchive' || options.type === 'DeepColdArchive') {
- const paramsXMLObj = {
- RestoreRequest: {
- Days: options.Days ? options.Days : 2,
- JobParameters: {
- Tier: options.JobParameters ? options.JobParameters : 'Standard'
- }
- }
+ paramsXMLObj.RestoreRequest.JobParameters = {
+ Tier: options.JobParameters ? options.JobParameters : 'Standard'
};
- params.content = obj2xml(paramsXMLObj, {
- headers: true
- });
- params.mime = 'xml';
}
+
+ params.content = obj2xml(paramsXMLObj, {
+ headers: true
+ });
+ params.mime = 'xml';
params.successStatuses = [202];
const result = await this.request(params);
diff --git a/lib/common/object/index.js b/lib/common/object/index.js
index d0091b359..45a4194d2 100644
--- a/lib/common/object/index.js
+++ b/lib/common/object/index.js
@@ -24,3 +24,4 @@ merge(proto, require('./getObjectUrl'));
merge(proto, require('./signatureUrl'));
merge(proto, require('./asyncSignatureUrl'));
merge(proto, require('./signatureUrlV4'));
+merge(proto, require('./signPostObjectPolicyV4'));
diff --git a/lib/common/object/signPostObjectPolicyV4.d.ts b/lib/common/object/signPostObjectPolicyV4.d.ts
new file mode 100644
index 000000000..fd2f3b852
--- /dev/null
+++ b/lib/common/object/signPostObjectPolicyV4.d.ts
@@ -0,0 +1 @@
+export declare function signPostObjectPolicyV4(this: any, policy: string | object, date: Date): string;
diff --git a/lib/common/object/signPostObjectPolicyV4.js b/lib/common/object/signPostObjectPolicyV4.js
new file mode 100644
index 000000000..c58ad084d
--- /dev/null
+++ b/lib/common/object/signPostObjectPolicyV4.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const __importDefault =
+ (this && this.__importDefault) ||
+ function (mod) {
+ return mod && mod.__esModule ? mod : { default: mod };
+ };
+Object.defineProperty(exports, '__esModule', { value: true });
+exports.signPostObjectPolicyV4 = void 0;
+const dateformat_1 = __importDefault(require('dateformat'));
+const getStandardRegion_1 = require('../utils/getStandardRegion');
+const policy2Str_1 = require('../utils/policy2Str');
+const signUtils_1 = require('../signUtils');
+
+function signPostObjectPolicyV4(policy, date) {
+ const policyStr = Buffer.from(policy2Str_1.policy2Str(policy), 'utf8').toString('base64');
+ const formattedDate = dateformat_1.default(date, "UTC:yyyymmdd'T'HHMMss'Z'");
+ const onlyDate = formattedDate.split('T')[0];
+ const signature = signUtils_1.getSignatureV4(
+ this.options.accessKeySecret,
+ onlyDate,
+ getStandardRegion_1.getStandardRegion(this.options.region),
+ policyStr
+ );
+ return signature;
+}
+exports.signPostObjectPolicyV4 = signPostObjectPolicyV4;
diff --git a/lib/common/object/signPostObjectPolicyV4.ts b/lib/common/object/signPostObjectPolicyV4.ts
new file mode 100644
index 000000000..4a3729b65
--- /dev/null
+++ b/lib/common/object/signPostObjectPolicyV4.ts
@@ -0,0 +1,20 @@
+import dateFormat from 'dateformat';
+
+import { getStandardRegion } from '../utils/getStandardRegion';
+import { policy2Str } from '../utils/policy2Str';
+import { getSignatureV4 } from '../signUtils';
+
+export function signPostObjectPolicyV4(this: any, policy: string | object, date: Date): string {
+ const policyStr = Buffer.from(policy2Str(policy), 'utf8').toString('base64');
+ const formattedDate = dateFormat(date, "UTC:yyyymmdd'T'HHMMss'Z'");
+ const onlyDate = formattedDate.split('T')[0];
+
+ const signature = getSignatureV4(
+ this.options.accessKeySecret,
+ onlyDate,
+ getStandardRegion(this.options.region),
+ policyStr
+ );
+
+ return signature;
+}
diff --git a/lib/common/object/signatureUrlV4.js b/lib/common/object/signatureUrlV4.js
index 01dfebcb1..8417557b7 100644
--- a/lib/common/object/signatureUrlV4.js
+++ b/lib/common/object/signatureUrlV4.js
@@ -31,7 +31,7 @@ proto.signatureUrlV4 = async function signatureUrlV4(method, expires, request, o
if (fixedAdditionalHeaders.length > 0) {
queries['x-oss-additional-headers'] = fixedAdditionalHeaders.join(';');
}
- queries['x-oss-credential'] = `${this.options.accessKeyId}/${onlyDate}/${region}/oss/aliyun_v4_request`;
+ queries['x-oss-credential'] = signHelper.getCredential(onlyDate, region, this.options.accessKeyId);
queries['x-oss-date'] = formattedDate;
queries['x-oss-expires'] = expires;
queries['x-oss-signature-version'] = 'OSS4-HMAC-SHA256';
diff --git a/lib/common/signUtils.js b/lib/common/signUtils.js
index fcc3a3cd9..8f0509168 100644
--- a/lib/common/signUtils.js
+++ b/lib/common/signUtils.js
@@ -180,6 +180,23 @@ exports.getCanonicalRequest = function getCanonicalRequest(method, request, buck
return signContent.join('\n');
};
+/**
+ * @param {string} date yyyymmdd
+ * @param {string} region Standard region, e.g. cn-hangzhou
+ * @param {string} [accessKeyId] Access Key ID
+ * @param {string} [product] Product name, default is oss
+ * @returns {string}
+ */
+exports.getCredential = function getCredential(date, region, accessKeyId, product = 'oss') {
+ const tempCredential = `${date}/${region}/${product}/aliyun_v4_request`;
+
+ if (accessKeyId) {
+ return `${accessKeyId}/${tempCredential}`;
+ }
+
+ return tempCredential;
+};
+
/**
* @param {string} region Standard region, e.g. cn-hangzhou
* @param {string} date ISO8601 UTC:yyyymmdd'T'HHMMss'Z'
@@ -190,7 +207,7 @@ exports.getStringToSign = function getStringToSign(region, date, canonicalReques
const stringToSign = [
'OSS4-HMAC-SHA256',
date, // TimeStamp
- `${date.split('T')[0]}/${region}/oss/aliyun_v4_request`, // Scope
+ this.getCredential(date.split('T')[0], region), // Scope
crypto.createHash('sha256').update(canonicalRequest).digest('hex') // Hashed Canonical Request
];
@@ -261,7 +278,7 @@ exports.authorizationV4 = function authorizationV4(
const additionalHeadersValue =
fixedAdditionalHeaders.length > 0 ? `AdditionalHeaders=${fixedAdditionalHeaders.join(';')},` : '';
- return `OSS4-HMAC-SHA256 Credential=${accessKeyId}/${onlyDate}/${region}/oss/aliyun_v4_request,${additionalHeadersValue}Signature=${signatureValue}`;
+ return `OSS4-HMAC-SHA256 Credential=${this.getCredential(onlyDate, region, accessKeyId)},${additionalHeadersValue}Signature=${signatureValue}`;
};
/**
diff --git a/lib/common/utils/parseRestoreInfo.d.ts b/lib/common/utils/parseRestoreInfo.d.ts
index 53f08b406..cf3590e09 100644
--- a/lib/common/utils/parseRestoreInfo.d.ts
+++ b/lib/common/utils/parseRestoreInfo.d.ts
@@ -1,5 +1,15 @@
interface IRestoreInfo {
+ /**
+ * Whether the restoration is ongoing
+ * If a RestoreObject request is sent but the restoration is not complete, the value is true.
+ * If a RestoreObject request is sent and the restoration is complete, the value is false.
+ */
ongoingRequest: boolean;
+ /**
+ * The time before which the restored object can be read.
+ * If a RestoreObject request is sent but the restoration is not complete, the value is undefined.
+ * If a RestoreObject request is sent and the restoration is complete, the value is Date.
+ */
expiryDate?: Date;
}
export declare const parseRestoreInfo: (originalRestoreInfo?: string | undefined) => IRestoreInfo | undefined;
diff --git a/lib/common/utils/parseRestoreInfo.ts b/lib/common/utils/parseRestoreInfo.ts
index 75149fe50..e97f4f739 100644
--- a/lib/common/utils/parseRestoreInfo.ts
+++ b/lib/common/utils/parseRestoreInfo.ts
@@ -1,5 +1,15 @@
interface IRestoreInfo {
+ /**
+ * Whether the restoration is ongoing
+ * If a RestoreObject request is sent but the restoration is not complete, the value is true.
+ * If a RestoreObject request is sent and the restoration is complete, the value is false.
+ */
ongoingRequest: boolean;
+ /**
+ * The time before which the restored object can be read.
+ * If a RestoreObject request is sent but the restoration is not complete, the value is undefined.
+ * If a RestoreObject request is sent and the restoration is complete, the value is Date.
+ */
expiryDate?: Date;
}
diff --git a/lib/object.js b/lib/object.js
index 21770ec01..a48f46a29 100644
--- a/lib/object.js
+++ b/lib/object.js
@@ -324,20 +324,22 @@ proto.restore = async function restore(name, options = { type: 'Archive' }) {
options.subres.versionId = options.versionId;
}
const params = this._objectRequestParams('POST', name, options);
+ const paramsXMLObj = {
+ RestoreRequest: {
+ Days: options.Days ? options.Days : 2
+ }
+ };
+
if (options.type === 'ColdArchive' || options.type === 'DeepColdArchive') {
- const paramsXMLObj = {
- RestoreRequest: {
- Days: options.Days ? options.Days : 2,
- JobParameters: {
- Tier: options.JobParameters ? options.JobParameters : 'Standard'
- }
- }
+ paramsXMLObj.RestoreRequest.JobParameters = {
+ Tier: options.JobParameters ? options.JobParameters : 'Standard'
};
- params.content = obj2xml(paramsXMLObj, {
- headers: true
- });
- params.mime = 'xml';
}
+
+ params.content = obj2xml(paramsXMLObj, {
+ headers: true
+ });
+ params.mime = 'xml';
params.successStatuses = [202];
const result = await this.request(params);
diff --git a/package-lock.json b/package-lock.json
index 1cf44052e..628971f0e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -55,14 +55,14 @@
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"aliasify": "^2.0.0",
- "axios": "0.27.2",
+ "axios": "^0.27.2",
"babelify": "^10.0.0",
"beautify-benchmark": "^0.2.4",
"benchmark": "^2.1.1",
"bluebird": "^3.1.5",
"browserify": "^17.0.0",
"core-js": "^3.6.5",
- "crypto-js": "^3.1.9-1",
+ "crypto-js": "^4.2.0",
"dotenv": "^8.2.0",
"eslint": "^8.44.0",
"eslint-config-airbnb": "^19.0.4",
@@ -4407,8 +4407,9 @@
},
"node_modules/axios": {
"version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+ "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"follow-redirects": "^1.14.9",
"form-data": "^4.0.0"
@@ -6064,9 +6065,10 @@
}
},
"node_modules/crypto-js": {
- "version": "3.3.0",
- "dev": true,
- "license": "MIT"
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "dev": true
},
"node_modules/crypto-random-string": {
"version": "4.0.0",
@@ -7801,7 +7803,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
- "version": "1.15.5",
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"dev": true,
"funding": [
{
@@ -7809,7 +7813,6 @@
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
- "license": "MIT",
"engines": {
"node": ">=4.0"
},
diff --git a/package.json b/package.json
index 3e6722170..b226e6430 100644
--- a/package.json
+++ b/package.json
@@ -88,14 +88,14 @@
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"aliasify": "^2.0.0",
- "axios": "0.27.2",
+ "axios": "^0.27.2",
"babelify": "^10.0.0",
"beautify-benchmark": "^0.2.4",
"benchmark": "^2.1.1",
"bluebird": "^3.1.5",
"browserify": "^17.0.0",
"core-js": "^3.6.5",
- "crypto-js": "^3.1.9-1",
+ "crypto-js": "^4.2.0",
"dotenv": "^8.2.0",
"eslint": "^8.44.0",
"eslint-config-airbnb": "^19.0.4",
diff --git a/task/dingding.jpg b/task/dingding.jpg
deleted file mode 100644
index 385d413a7..000000000
Binary files a/task/dingding.jpg and /dev/null differ
diff --git a/test/browser/browser.test.js b/test/browser/browser.test.js
index 034ab04d2..86e6b69d8 100644
--- a/test/browser/browser.test.js
+++ b/test/browser/browser.test.js
@@ -1,5 +1,8 @@
const assert = require('assert');
const mm = require('mm');
+const axios = require('axios');
+const dateFormat = require('dateformat');
+const timemachine = require('timemachine');
/* eslint no-undef: [0] */
const oss = OSS;
const urllib = require('urllib');
@@ -13,9 +16,11 @@ const platform = require('platform');
const crypto1 = require('crypto');
const { Readable } = require('stream');
const { prefix } = require('./browser-utils');
+const { getCredential } = require('../../lib/common/signUtils');
+const { getStandardRegion } = require('../../lib/common/utils/getStandardRegion');
+const { policy2Str } = require('../../lib/common/utils/policy2Str');
let ossConfig;
-const timemachine = require('timemachine');
timemachine.reset();
@@ -2658,10 +2663,12 @@ describe('browser', () => {
try {
await store.restore(name);
+ assert.fail('Expect throw an error');
} catch (e) {
- assert.equal(e.status, 400);
+ assert.strictEqual(e.status, 400);
}
});
+
it('Should return 202 when restore is called first', async () => {
const name = '/oss/restore.js';
await store.put(name, Buffer.from('abc'), {
@@ -2671,115 +2678,137 @@ describe('browser', () => {
});
const info = await store.restore(name);
- assert.equal(info.res.status, 202);
+ assert.strictEqual(info.res.status, 202);
// in 1 minute verify RestoreAlreadyInProgressError.
try {
await store.restore(name);
+ assert.fail('Expect throw an error');
} catch (err) {
- assert.equal(err.name, 'RestoreAlreadyInProgressError');
+ assert.strictEqual(err.name, 'RestoreAlreadyInProgressError');
}
});
- //
- it('Category should be Archive', async () => {
+ it('Restore Archive object with setting of Days', async () => {
+ const name = '/oss/restore2.js';
+ await store.put(name, Buffer.from('abc'), {
+ headers: {
+ 'x-oss-storage-class': 'Archive'
+ }
+ });
+
+ const info = await store.restore(name, {
+ Days: 1
+ });
+ assert.strictEqual(info.res.status, 202);
+ });
+
+ it('Should not set JobParameters when restore Archive object', async () => {
const name = '/oss/restore.js';
- await store.put(name, Buffer.from('abc'));
+
try {
await store.restore(name, { type: 'ColdArchive' });
+ assert.fail('expect Error');
} catch (err) {
- assert.equal(err.code, 'OperationNotSupported');
+ assert.strictEqual(err.code, 'MalformedXML');
}
});
- it('ColdArchive choice Days', async () => {
+ it('Restore Cold Archive object with default settings', async () => {
+ const name = '/oss/coldRestore.js';
+ await store.put(name, Buffer.from('abc'), {
+ headers: {
+ 'x-oss-storage-class': 'ColdArchive'
+ }
+ });
+ const result = await store.restore(name, {
+ type: 'ColdArchive'
+ });
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
+ });
+
+ it('Restore Cold Archive object with setting of Days', async () => {
const name = '/oss/daysColdRestore.js';
- const options = {
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'ColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
type: 'ColdArchive',
- Days: 2
+ Days: 1
});
- assert.equal(result.res.status, 202);
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
});
- it('ColdArchive choice JobParameters', async () => {
+ it('Restore Cold Archive object with settings of Days and JobParameters', async () => {
const name = '/oss/JobParametersColdRestore.js';
- const options = {
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'ColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
type: 'ColdArchive',
- Days: 2,
- JobParameters: 'Standard'
+ Days: 3,
+ JobParameters: 'Expedited'
});
- assert.equal(result.res.status, 202);
- });
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Expedited');
- it('ColdArchive is Accepted', async () => {
- const name = '/oss/coldRestore.js';
- const options = {
+ const name2 = 'oss/JobParametersColdRestore2.js';
+ await store.put(name2, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'ColdArchive'
}
- };
- await store.put(name, Buffer.from(__filename), options);
- const result = await store.restore(name, {
- type: 'ColdArchive'
});
- assert.equal(result.res.status, 202);
+ const result2 = await store.restore(name2, {
+ type: 'ColdArchive',
+ Days: 5,
+ JobParameters: 'Bulk'
+ });
+ assert.strictEqual(result2.res.headers['x-oss-object-restore-priority'], 'Bulk');
});
- it('DeepColdArchive choice Days', async () => {
- const name = '/oss/daysDeepColdRestore.js';
- const options = {
+ it('Restore Deep Cold Archive object with default settings', async () => {
+ const name = '/oss/deepColdRestore.js';
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'DeepColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
- type: 'DeepColdArchive',
- Days: 2
+ type: 'DeepColdArchive'
});
- assert.equal(result.res.status, 202);
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
});
- it('DeepColdArchive choice JobParameters', async () => {
- const name = '/oss/JobParametersDeepColdRestore.js';
- const options = {
+ it('Restore Deep Cold Archive object with setting of Days', async () => {
+ const name = '/oss/daysDeepColdRestore.js';
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'DeepColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
type: 'DeepColdArchive',
- Days: 2,
- JobParameters: 'Standard'
+ Days: 1
});
- assert.equal(result.res.status, 202);
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
});
- it('DeepColdArchive is Accepted', async () => {
- const name = '/oss/deepColdRestore.js';
- const options = {
+ it('Restore Deep Cold Archive object with settings of Days and JobParameters', async () => {
+ const name = '/oss/JobParametersDeepColdRestore.js';
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'DeepColdArchive'
}
- };
- await store.put(name, Buffer.from(__filename), options);
+ });
const result = await store.restore(name, {
- type: 'DeepColdArchive'
+ type: 'DeepColdArchive',
+ Days: 3,
+ JobParameters: 'Expedited'
});
- assert.equal(result.res.status, 202);
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Expedited');
});
});
@@ -2910,6 +2939,78 @@ describe('browser', () => {
}
});
});
+
+ describe('signPostObjectPolicyV4()', () => {
+ it('should PostObject with V4 signature', async () => {
+ const store = oss({ ...ossConfig, ...moreConfigs });
+ const name = 'testPostObjectUseV4Signature.txt';
+ const formData = new FormData();
+ formData.append('key', name);
+ formData.append('Content-Type', 'text/plain');
+ formData.append('Cache-Control', 'max-age=30');
+ const url = store.generateObjectUrl(name).replace(name, '');
+ const date = new Date();
+ const expirationDate = new Date(date);
+ expirationDate.setMinutes(date.getMinutes() + 1);
+ const formattedDate = dateFormat(date, "UTC:yyyymmdd'T'HHMMss'Z'");
+ const credential = getCredential(
+ formattedDate.split('T')[0],
+ getStandardRegion(store.options.region),
+ store.options.accessKeyId
+ );
+ formData.append('x-oss-date', formattedDate);
+ formData.append('x-oss-credential', credential);
+ formData.append('x-oss-signature-version', 'OSS4-HMAC-SHA256');
+ const policy = {
+ expiration: expirationDate.toISOString(),
+ conditions: [
+ { bucket: store.options.bucket },
+ { 'x-oss-credential': credential },
+ { 'x-oss-date': formattedDate },
+ { 'x-oss-signature-version': 'OSS4-HMAC-SHA256' },
+ ['content-length-range', 1, 10],
+ ['eq', '$success_action_status', '200'],
+ ['starts-with', '$key', 'testPostObject'],
+ ['in', '$content-type', ['image/jpg', 'text/plain']],
+ ['not-in', '$cache-control', ['no-cache']]
+ ]
+ };
+
+ if (store.options.stsToken) {
+ policy.conditions.push({ 'x-oss-security-token': store.options.stsToken });
+ formData.append('x-oss-security-token', store.options.stsToken);
+ }
+
+ const signature = store.signPostObjectPolicyV4(policy, date);
+ const signature2 = store.signPostObjectPolicyV4(JSON.stringify(policy), date);
+ assert.strictEqual(signature, signature2);
+
+ formData.append('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64'));
+ formData.append('x-oss-signature', signature);
+ formData.append('success_action_status', '200');
+ formData.append('file', 'test');
+
+ const result = await axios.post(url, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ });
+ assert.strictEqual(result.status, 200);
+ const headRes = await store.head(name);
+ assert.strictEqual(headRes.status, 200);
+ });
+
+ it('should throw error when policy is not legal JSON string or Object', () => {
+ const store = oss({ ...ossConfig, ...moreConfigs });
+
+ try {
+ store.signPostObjectPolicyV4('test', new Date());
+ assert(false);
+ } catch (error) {
+ assert(error.message.startsWith('Policy string is not a valid JSON:'));
+ }
+ });
+ });
});
});
});
diff --git a/test/node/object.test.js b/test/node/object.test.js
index 9ecf3a318..9ae899a5d 100644
--- a/test/node/object.test.js
+++ b/test/node/object.test.js
@@ -15,6 +15,10 @@ const crypto = require('crypto');
const urlutil = require('url');
const axios = require('axios');
const FormData = require('form-data');
+const dateFormat = require('dateformat');
+const { getCredential } = require('../../lib/common/signUtils');
+const { getStandardRegion } = require('../../lib/common/utils/getStandardRegion');
+const { policy2Str } = require('../../lib/common/utils/policy2Str');
const tmpdir = path.join(__dirname, '.tmp');
if (!fs.existsSync(tmpdir)) {
@@ -2421,159 +2425,158 @@ describe('test/object.test.js', () => {
});
describe('restore()', () => {
- before(async () => {
- await store.put('/oss/coldRestore.js', __filename, {
- headers: {
- 'x-oss-storage-class': 'ColdArchive'
- }
- });
- await store.put('/oss/daysRestore.js', __filename, {
- headers: {
- 'x-oss-storage-class': 'ColdArchive'
- }
- });
- });
- after(async () => {
- await store.useBucket(bucket);
- });
-
- it('Should return OperationNotSupportedError when the type of bucket is not archive', async () => {
+ it('Should return OperationNotSupportedError when the type of object is not archive', async () => {
const name = '/oss/restore.js';
await store.put(name, __filename);
try {
await store.restore(name);
- throw new Error('should not run this');
+ assert.fail('Expect throw an error');
} catch (err) {
- assert.equal(err.status, 400);
+ assert.strictEqual(err.status, 400);
}
});
+
it('Should return 202 when restore is called first', async () => {
- store.setBucket(archiveBucket);
const name = '/oss/restore.js';
- await store.put(name, __filename);
+ await store.put(name, Buffer.from('abc'), {
+ headers: {
+ 'x-oss-storage-class': 'Archive'
+ }
+ });
const info = await store.restore(name);
- assert.equal(info.res.status, 202);
+ assert.strictEqual(info.res.status, 202);
// in 1 minute verify RestoreAlreadyInProgressError
try {
await store.restore(name);
+ assert.fail('Expect throw an error');
} catch (err) {
- assert.equal(err.name, 'RestoreAlreadyInProgressError');
+ assert.strictEqual(err.name, 'RestoreAlreadyInProgressError');
}
});
- it('Category should be Archive', async () => {
+ it('Restore Archive object with setting of Days', async () => {
+ const name = '/oss/restore2.js';
+ await store.put(name, Buffer.from('abc'), {
+ headers: {
+ 'x-oss-storage-class': 'Archive'
+ }
+ });
+
+ const info = await store.restore(name, {
+ Days: 1
+ });
+ assert.strictEqual(info.res.status, 202);
+ });
+
+ it('Should not set JobParameters when restore Archive object', async () => {
const name = '/oss/restore.js';
+
try {
await store.restore(name, { type: 'ColdArchive' });
+ assert.fail('expect Error');
} catch (err) {
- assert.equal(err.code, 'MalformedXML');
+ assert.strictEqual(err.code, 'MalformedXML');
}
- await store.useBucket(bucket, bucketRegion);
});
- it('ColdArchive choice Days', async () => {
+ it('Restore Cold Archive object with default settings', async () => {
+ const name = '/oss/coldRestore.js';
+ await store.put(name, Buffer.from('abc'), {
+ headers: {
+ 'x-oss-storage-class': 'ColdArchive'
+ }
+ });
+ const result = await store.restore(name, {
+ type: 'ColdArchive'
+ });
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
+ });
+
+ it('Restore Cold Archive object with setting of Days', async () => {
const name = '/oss/daysColdRestore.js';
- const options = {
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'ColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
type: 'ColdArchive',
- Days: 2
+ Days: 1
});
- assert.equal(
- ['Expedited', 'Standard', 'Bulk'].includes(result.res.headers['x-oss-object-restore-priority']),
- true
- );
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
});
- it('ColdArchive choice JobParameters', async () => {
+ it('Restore Cold Archive object with settings of Days and JobParameters', async () => {
const name = '/oss/JobParametersColdRestore.js';
- const options = {
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'ColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
type: 'ColdArchive',
- Days: 2,
- JobParameters: 'Standard'
+ Days: 3,
+ JobParameters: 'Expedited'
});
- assert.equal(
- ['Expedited', 'Standard', 'Bulk'].includes(result.res.headers['x-oss-object-restore-priority']),
- true
- );
- });
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Expedited');
- it('ColdArchive is Accepted', async () => {
- const name = '/oss/coldRestore.js';
- const result = await store.restore(name, {
- type: 'ColdArchive'
+ const name2 = 'oss/JobParametersColdRestore2.js';
+ await store.put(name2, Buffer.from('abc'), {
+ headers: {
+ 'x-oss-storage-class': 'ColdArchive'
+ }
});
- assert.equal(
- ['Expedited', 'Standard', 'Bulk'].includes(result.res.headers['x-oss-object-restore-priority']),
- true
- );
+ const result2 = await store.restore(name2, {
+ type: 'ColdArchive',
+ Days: 5,
+ JobParameters: 'Bulk'
+ });
+ assert.strictEqual(result2.res.headers['x-oss-object-restore-priority'], 'Bulk');
});
- it('DeepColdArchive choice Days', async () => {
- const name = '/oss/daysDeepColdRestore.js';
- const options = {
+ it('Restore Deep Cold Archive object with default settings', async () => {
+ const name = '/oss/deepColdRestore.js';
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'DeepColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
- type: 'DeepColdArchive',
- Days: 2
+ type: 'DeepColdArchive'
});
- assert.equal(
- ['Expedited', 'Standard', 'Bulk'].includes(result.res.headers['x-oss-object-restore-priority']),
- true
- );
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
});
- it('DeepColdArchive choice JobParameters', async () => {
- const name = '/oss/JobParametersDeepColdRestore.js';
- const options = {
+ it('Restore Deep Cold Archive object with setting of Days', async () => {
+ const name = '/oss/daysDeepColdRestore.js';
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'DeepColdArchive'
}
- };
- await store.put(name, Buffer.from('abc'), options);
+ });
const result = await store.restore(name, {
type: 'DeepColdArchive',
- Days: 2,
- JobParameters: 'Standard'
+ Days: 1
});
- assert.equal(
- ['Expedited', 'Standard', 'Bulk'].includes(result.res.headers['x-oss-object-restore-priority']),
- true
- );
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Standard');
});
- it('DeepColdArchive is Accepted', async () => {
- const name = '/oss/deepColdRestore.js';
- const options = {
+ it('Restore Deep Cold Archive object with settings of Days and JobParameters', async () => {
+ const name = '/oss/JobParametersDeepColdRestore.js';
+ await store.put(name, Buffer.from('abc'), {
headers: {
'x-oss-storage-class': 'DeepColdArchive'
}
- };
- await store.put(name, Buffer.from(__filename), options);
+ });
const result = await store.restore(name, {
- type: 'DeepColdArchive'
+ type: 'DeepColdArchive',
+ Days: 3,
+ JobParameters: 'Expedited'
});
- assert.equal(
- ['Expedited', 'Standard', 'Bulk'].includes(result.res.headers['x-oss-object-restore-priority']),
- true
- );
+ assert.strictEqual(result.res.headers['x-oss-object-restore-priority'], 'Expedited');
});
});
@@ -2674,6 +2677,75 @@ describe('test/object.test.js', () => {
});
});
+ describe('signPostObjectPolicyV4()', () => {
+ it('should PostObject with V4 signature', async () => {
+ const name = 'testPostObjectUseV4Signature.txt';
+ const formData = new FormData();
+ formData.append('key', name);
+ formData.append('Content-Type', 'text/plain');
+ formData.append('Cache-Control', 'max-age=30');
+ const url = store.generateObjectUrl(name).replace(name, '');
+ const date = new Date();
+ const expirationDate = new Date(date);
+ expirationDate.setMinutes(date.getMinutes() + 1);
+ const formattedDate = dateFormat(date, "UTC:yyyymmdd'T'HHMMss'Z'");
+ const credential = getCredential(
+ formattedDate.split('T')[0],
+ getStandardRegion(store.options.region),
+ store.options.accessKeyId
+ );
+ formData.append('x-oss-date', formattedDate);
+ formData.append('x-oss-credential', credential);
+ formData.append('x-oss-signature-version', 'OSS4-HMAC-SHA256');
+ const policy = {
+ expiration: expirationDate.toISOString(),
+ conditions: [
+ { bucket: store.options.bucket },
+ { 'x-oss-credential': credential },
+ { 'x-oss-date': formattedDate },
+ { 'x-oss-signature-version': 'OSS4-HMAC-SHA256' },
+ ['content-length-range', 1, 10],
+ ['eq', '$success_action_status', '200'],
+ ['starts-with', '$key', 'testPostObject'],
+ ['in', '$content-type', ['image/jpg', 'text/plain']],
+ ['not-in', '$cache-control', ['no-cache']]
+ ]
+ };
+
+ if (store.options.stsToken) {
+ policy.conditions.push({ 'x-oss-security-token': store.options.stsToken });
+ formData.append('x-oss-security-token', store.options.stsToken);
+ }
+
+ const signature = store.signPostObjectPolicyV4(policy, date);
+ const signature2 = store.signPostObjectPolicyV4(JSON.stringify(policy), date);
+ assert.strictEqual(signature, signature2);
+
+ formData.append('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64'));
+ formData.append('x-oss-signature', signature);
+ formData.append('success_action_status', '200');
+ formData.append('file', 'test');
+
+ const result = await axios.post(url, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ });
+ assert.strictEqual(result.status, 200);
+ const headRes = await store.head(name);
+ assert.strictEqual(headRes.status, 200);
+ });
+
+ it('should throw error when policy is not legal JSON string or Object', () => {
+ try {
+ store.signPostObjectPolicyV4('test', new Date());
+ assert(false);
+ } catch (error) {
+ assert(error.message.startsWith('Policy string is not a valid JSON:'));
+ }
+ });
+ });
+
describe('getObjectTagging() putObjectTagging() deleteObjectTagging()', () => {
const name = '/oss/tagging.js';