@cloudbase/manager-node 5.4.0-beta.1 → 5.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ const path_1 = __importDefault(require("path"));
16
16
  const make_dir_1 = __importDefault(require("make-dir"));
17
17
  const walkdir_1 = __importDefault(require("walkdir"));
18
18
  const micromatch_1 = __importDefault(require("micromatch"));
19
+ const mime_types_1 = __importDefault(require("mime-types"));
19
20
  const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
20
21
  const utils_1 = require("../utils");
21
22
  const error_1 = require("../error");
@@ -1314,11 +1315,18 @@ class StorageService {
1314
1315
  code: 'FileSizeLimitExceeded'
1315
1316
  });
1316
1317
  }
1317
- // 组装 Headers
1318
+ const finalContentType = this.resolveUploadContentType({
1319
+ contentType,
1320
+ localPath,
1321
+ objectName,
1322
+ body
1323
+ });
1318
1324
  const headers = {
1319
- Authorization: `Bearer ${accessToken}`,
1320
- 'Content-Type': contentType || 'application/octet-stream'
1325
+ Authorization: `Bearer ${accessToken}`
1321
1326
  };
1327
+ if (finalContentType) {
1328
+ headers['Content-Type'] = finalContentType;
1329
+ }
1322
1330
  if (fileSize !== undefined) {
1323
1331
  headers['Content-Length'] = String(fileSize);
1324
1332
  }
@@ -1994,9 +2002,16 @@ class StorageService {
1994
2002
  else if (Buffer.isBuffer(body) && fileSize === undefined) {
1995
2003
  fileSize = body.length;
1996
2004
  }
1997
- const headers = {
1998
- 'Content-Type': contentType || 'application/octet-stream'
1999
- };
2005
+ const finalContentType = this.resolveUploadContentType({
2006
+ contentType,
2007
+ localPath,
2008
+ objectName,
2009
+ body
2010
+ });
2011
+ const headers = {};
2012
+ if (finalContentType) {
2013
+ headers['Content-Type'] = finalContentType;
2014
+ }
2000
2015
  if (fileSize !== undefined) {
2001
2016
  headers['Content-Length'] = String(fileSize);
2002
2017
  }
@@ -2636,6 +2651,87 @@ class StorageService {
2636
2651
  res.map(item => /Error/gi.test(Object.prototype.toString.call(item)) && resultErrorArr.push(item));
2637
2652
  return resultErrorArr;
2638
2653
  }
2654
+ resolveUploadContentType(params) {
2655
+ const { contentType, localPath, objectName, body } = params;
2656
+ // 0. 用户显式指定优先
2657
+ const trimmedContentType = contentType === null || contentType === void 0 ? void 0 : contentType.trim();
2658
+ if (trimmedContentType) {
2659
+ return this.assertValidContentType(trimmedContentType);
2660
+ }
2661
+ // 1. 扩展名推断(优先)
2662
+ const byExt = this.inferMimeByExtension(localPath || objectName);
2663
+ if (byExt)
2664
+ return byExt;
2665
+ // 2. magic bytes 二次识别
2666
+ const byMagic = this.inferMimeByMagicBytes(localPath, body);
2667
+ if (byMagic)
2668
+ return byMagic;
2669
+ return undefined;
2670
+ }
2671
+ inferMimeByExtension(filePathOrName) {
2672
+ if (!filePathOrName)
2673
+ return undefined;
2674
+ const result = mime_types_1.default.lookup(filePathOrName);
2675
+ return typeof result === 'string' ? result : undefined;
2676
+ }
2677
+ inferMimeByMagicBytes(localPath, body) {
2678
+ let head;
2679
+ try {
2680
+ if (localPath) {
2681
+ const fd = fs_1.default.openSync(path_1.default.resolve(localPath), 'r');
2682
+ try {
2683
+ const buf = Buffer.alloc(512);
2684
+ const n = fs_1.default.readSync(fd, buf, 0, 512, 0);
2685
+ head = buf.slice(0, n);
2686
+ }
2687
+ finally {
2688
+ fs_1.default.closeSync(fd);
2689
+ }
2690
+ }
2691
+ else if (Buffer.isBuffer(body)) {
2692
+ head = body.slice(0, 512);
2693
+ }
2694
+ }
2695
+ catch (_a) {
2696
+ return undefined;
2697
+ }
2698
+ if (!head || head.length === 0)
2699
+ return undefined;
2700
+ const startsWith = (sig) => head.length >= sig.length && sig.every((b, i) => head[i] === b);
2701
+ if (startsWith([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]))
2702
+ return 'image/png';
2703
+ if (startsWith([0xFF, 0xD8, 0xFF]))
2704
+ return 'image/jpeg';
2705
+ if (head.slice(0, 6).toString('ascii') === 'GIF87a' || head.slice(0, 6).toString('ascii') === 'GIF89a')
2706
+ return 'image/gif';
2707
+ if (head.slice(0, 4).toString('ascii') === 'RIFF' && head.slice(8, 12).toString('ascii') === 'WEBP')
2708
+ return 'image/webp';
2709
+ if (head.slice(0, 4).toString('ascii') === '%PDF')
2710
+ return 'application/pdf';
2711
+ if (startsWith([0x50, 0x4B, 0x03, 0x04]))
2712
+ return 'application/zip';
2713
+ if (startsWith([0x1F, 0x8B]))
2714
+ return 'application/gzip';
2715
+ if (head.length >= 262 && head.slice(257, 262).toString('ascii') === 'ustar')
2716
+ return 'application/x-tar';
2717
+ return undefined;
2718
+ }
2719
+ assertValidContentType(input) {
2720
+ const ct = input.trim();
2721
+ if (!ct)
2722
+ throw new error_1.CloudBaseError('contentType 不能为空');
2723
+ // 防 header 注入 / 控制字符
2724
+ if (/[\u0000-\u001F\u007F]/.test(ct)) {
2725
+ throw new error_1.CloudBaseError('contentType 非法:包含控制字符');
2726
+ }
2727
+ // 只校验主类型(; 后参数放宽)
2728
+ const base = ct.split(';', 1)[0].trim();
2729
+ const typeSubtypeRe = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
2730
+ if (!typeSubtypeRe.test(base)) {
2731
+ throw new error_1.CloudBaseError('contentType 非法:主类型格式不正确');
2732
+ }
2733
+ return ct;
2734
+ }
2639
2735
  }
2640
2736
  exports.StorageService = StorageService;
2641
2737
  __decorate([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.4.0-beta.1",
3
+ "version": "5.4.0-beta.2",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -25,6 +25,7 @@
25
25
  "@lerna/create-symlink": "^6.4.1",
26
26
  "@types/fs-extra": "^11.0.4",
27
27
  "@types/jest": "^24.0.18",
28
+ "@types/mime-types": "^3.0.1",
28
29
  "@types/node": "^12.7.4",
29
30
  "@types/node-fetch": "^2.5.0",
30
31
  "@types/unzipper": "^0.10.11",
@@ -49,6 +50,7 @@
49
50
  "lodash": "^4.17.21",
50
51
  "make-dir": "^3.0.0",
51
52
  "micromatch": "^4.0.2",
53
+ "mime-types": "^3.0.2",
52
54
  "node-fetch": "^2.6.0",
53
55
  "query-string": "^6.8.3",
54
56
  "unzipper": "^0.12.3",
@@ -569,5 +569,9 @@ export declare class StorageService {
569
569
  * @returns
570
570
  */
571
571
  private determineDownLoadResultIsError;
572
+ private resolveUploadContentType;
573
+ private inferMimeByExtension;
574
+ private inferMimeByMagicBytes;
575
+ private assertValidContentType;
572
576
  }
573
577
  export {};