@cnbcool/cnb-api-generate 2.8.2 → 2.9.1
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.
- package/client/core.ts +42 -34
- package/package.json +1 -1
- package/skills-template/SKILL.md +1 -1
package/client/core.ts
CHANGED
|
@@ -6,6 +6,25 @@ import { generateUniqueId } from './utils/generate-unique-id';
|
|
|
6
6
|
import { resolveToken } from './utils/resolve-token';
|
|
7
7
|
import { resolveApiDomain } from './utils/resolve-api-domain';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* 将 buffer 写入临时文件并返回文件路径
|
|
11
|
+
*/
|
|
12
|
+
async function writeBufferToTempFile(buffer: Buffer, extension: string, customFileName?: string): Promise<string> {
|
|
13
|
+
const tempDir = os.tmpdir();
|
|
14
|
+
const uploadDir = path.join(tempDir, 'cnb-api');
|
|
15
|
+
// recursive: true 在目录已存在时不会报错,无需先 existsSync 检查(消除 TOCTOU)
|
|
16
|
+
await fs.promises.mkdir(uploadDir, { recursive: true });
|
|
17
|
+
const fileName = customFileName || `${generateUniqueId()}${extension}`;
|
|
18
|
+
const filePath = path.join(uploadDir, fileName);
|
|
19
|
+
// 安全检查:使用 path.relative 确保最终路径不跳出 uploadDir
|
|
20
|
+
const relative = path.relative(path.resolve(uploadDir), path.resolve(filePath));
|
|
21
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
22
|
+
throw new Error(`Rejected path traversal: ${fileName}`);
|
|
23
|
+
}
|
|
24
|
+
await fs.promises.writeFile(filePath, buffer);
|
|
25
|
+
return filePath;
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
/**
|
|
10
29
|
* 格式化 API 响应
|
|
11
30
|
* 构建包含以下字段的响应对象:
|
|
@@ -13,7 +32,7 @@ import { resolveApiDomain } from './utils/resolve-api-domain';
|
|
|
13
32
|
* - trace: traceparent(用于调试追踪)
|
|
14
33
|
* - header: 原始分页 header(x-cnb-page 等)
|
|
15
34
|
* - page/pageSize/total/totalPages: 从 header 计算的分页信息(仅列表 API)
|
|
16
|
-
* - data: 响应体(JSON
|
|
35
|
+
* - data: 响应体(JSON/文本/二进制文件路径)
|
|
17
36
|
*
|
|
18
37
|
* 注意:所有字段始终保留,由 index.ts 的输出层决定展示哪些
|
|
19
38
|
*/
|
|
@@ -62,51 +81,40 @@ async function formatResponse(data: any, response: any) {
|
|
|
62
81
|
'text/json',
|
|
63
82
|
].some((t) => contentType.includes(t));
|
|
64
83
|
const isText = contentType.startsWith('text/') || contentType.includes('xml');
|
|
65
|
-
const isImage = contentType.startsWith('image/');
|
|
66
84
|
try {
|
|
67
85
|
if (isJson) {
|
|
68
86
|
responseData.data = await response.json();
|
|
69
87
|
} else if (isText) {
|
|
70
88
|
responseData.data = await response.text();
|
|
71
|
-
} else
|
|
89
|
+
} else {
|
|
90
|
+
// 非文本/JSON 响应视为二进制数据,保存到文件
|
|
72
91
|
const arrayBuffer = await response.arrayBuffer();
|
|
73
92
|
const buffer = Buffer.from(arrayBuffer);
|
|
74
93
|
|
|
75
|
-
//
|
|
76
|
-
let
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
fs.mkdirSync(uploadDir, { recursive: true });
|
|
94
|
+
// 从下载 URL 中解析文件名(含扩展名)
|
|
95
|
+
let fileName: string | undefined;
|
|
96
|
+
try {
|
|
97
|
+
const urlObj = new URL(data.url, 'https://dummy.example');
|
|
98
|
+
const lastSegment = path.basename(urlObj.pathname);
|
|
99
|
+
if (lastSegment && lastSegment.includes('.')) {
|
|
100
|
+
let decoded = decodeURIComponent(lastSegment);
|
|
101
|
+
// 拒绝空字节(不同 OS/Node.js 版本行为不一致)
|
|
102
|
+
if (decoded.includes('\0')) throw new Error('Invalid filename: contains null byte');
|
|
103
|
+
// 只保留安全字符:字母、数字、下划线、连字符、点号
|
|
104
|
+
fileName = decoded.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.warn('[cnb-api] Failed to parse filename from URL:', err instanceof Error ? err.message : err);
|
|
90
108
|
}
|
|
91
109
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} else {
|
|
98
|
-
// 其他二进制数据保持原有的 Base64 逻辑,或者也可以按需保存
|
|
99
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
100
|
-
const buffer = Buffer.from(arrayBuffer);
|
|
101
|
-
const base64String = buffer.toString('base64');
|
|
102
|
-
|
|
103
|
-
responseData.data = {
|
|
104
|
-
type: 'base64',
|
|
105
|
-
data: base64String,
|
|
106
|
-
mimeType: contentType || 'application/octet-stream',
|
|
107
|
-
};
|
|
110
|
+
if (fileName) {
|
|
111
|
+
responseData.data = await writeBufferToTempFile(buffer, '', fileName);
|
|
112
|
+
} else {
|
|
113
|
+
responseData.data = await writeBufferToTempFile(buffer, '.bin');
|
|
114
|
+
}
|
|
108
115
|
}
|
|
109
116
|
} catch (err: any) {
|
|
117
|
+
console.error('[cnb-api] formatResponse error:', err);
|
|
110
118
|
responseData.data = err?.message || 'Unknown Error';
|
|
111
119
|
}
|
|
112
120
|
|
package/package.json
CHANGED