chanjs 2.5.3 → 2.5.4
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/App.js +5 -2
- package/extend/art-template.js +22 -0
- package/helper/file.js +35 -0
- package/helper/index.js +1 -0
- package/helper/ip.js +4 -7
- package/middleware/index.js +1 -0
- package/middleware/log.js +41 -0
- package/middleware/waf.js +1 -1
- package/package.json +1 -1
- package/utils/keywords.js +1 -1
package/App.js
CHANGED
|
@@ -10,8 +10,8 @@ import DatabaseManager from "./base/Database.js";
|
|
|
10
10
|
// 引入配置和工具
|
|
11
11
|
import { Paths } from "./config/index.js";
|
|
12
12
|
import { getChildrenId, filterBody, filterImgFromStr, CODE as commonCode, sendMail, genRegEmailHtml, genResetPasswordEmail, pages, getHtmlFilesSync } from "./common/index.js";
|
|
13
|
-
import { loadConfig, loadController, loaderSort, formatDateFields, request, delImg, getIp, setToken, getToken, verifyToken, generateToken, getFileTree, readFileContent, saveFileContent, isPathSafe, arrToObj, htmlDecode } from "./helper/index.js";
|
|
14
|
-
import { Cors, setBody, setCookie, setFavicon, setHeader, setStatic, setTemplate, waf } from "./middleware/index.js";
|
|
13
|
+
import { loadConfig, loadController, loaderSort, formatDateFields, request, delImg, getIp, setToken, getToken, verifyToken, generateToken, getFileTree, readFileContent, saveFileContent, isPathSafe, getFolders, arrToObj, htmlDecode } from "./helper/index.js";
|
|
14
|
+
import { Cors, setBody, setCookie, setFavicon, setHeader, setStatic, setTemplate, waf, log } from "./middleware/index.js";
|
|
15
15
|
import { errorResponse, notFoundResponse, parseDatabaseError, error as responseError, fail, success, checkKeywords } from "./utils/index.js";
|
|
16
16
|
import { importFile, importjs } from "./global/import.js";
|
|
17
17
|
|
|
@@ -44,6 +44,7 @@ class Chan {
|
|
|
44
44
|
readFileContent,
|
|
45
45
|
saveFileContent,
|
|
46
46
|
isPathSafe,
|
|
47
|
+
getFolders,
|
|
47
48
|
arrToObj,
|
|
48
49
|
htmlDecode,
|
|
49
50
|
};
|
|
@@ -219,6 +220,7 @@ class Chan {
|
|
|
219
220
|
statics = [],
|
|
220
221
|
cors = {},
|
|
221
222
|
PROXY = "false",
|
|
223
|
+
logger = {},
|
|
222
224
|
waf: wafConfig = { enabled: false }
|
|
223
225
|
} = config;
|
|
224
226
|
|
|
@@ -228,6 +230,7 @@ class Chan {
|
|
|
228
230
|
setCookie(this.app, cookieKey);
|
|
229
231
|
setBody(this.app, BODY_LIMIT);
|
|
230
232
|
Cors(this.app, cors);
|
|
233
|
+
log(this.app, logger);
|
|
231
234
|
setTemplate(this.app, { views, NODE_ENV });
|
|
232
235
|
setHeader(this.app, { APP_NAME, APP_VERSION });
|
|
233
236
|
}
|
package/extend/art-template.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import template from "art-template";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { marked } = require('marked');
|
|
3
6
|
|
|
4
7
|
//template.defaults.native = false; // 禁用原生模板引擎 防止模板直接调用nodejs语法
|
|
5
8
|
//template.defaults.debug = false; // 禁用调试模式
|
|
@@ -45,3 +48,22 @@ template.defaults.imports.truncate = (str, length = 10) => {
|
|
|
45
48
|
template.defaults.imports.safeStringify = (obj) => {
|
|
46
49
|
return JSON.stringify(obj, null, 2);
|
|
47
50
|
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Markdown 渲染过滤器
|
|
54
|
+
* 自动检测内容是否为 Markdown 格式,如果是则渲染为 HTML
|
|
55
|
+
* @param {string} content - 文章内容
|
|
56
|
+
* @returns {string} 渲染后的 HTML
|
|
57
|
+
*/
|
|
58
|
+
template.defaults.imports.renderMarkdown = (content) => {
|
|
59
|
+
if (!content || typeof content !== 'string') {
|
|
60
|
+
return content || '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return marked.parse(content);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error('[renderMarkdown] Markdown 渲染失败:', err.message);
|
|
67
|
+
return content;
|
|
68
|
+
}
|
|
69
|
+
};
|
package/helper/file.js
CHANGED
|
@@ -156,3 +156,38 @@ export function isPathSafe(targetPath, basePath) {
|
|
|
156
156
|
const relativePath = path.relative(normalizedBase, normalizedTarget);
|
|
157
157
|
return !relativePath.startsWith('..');
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 获取指定文件夹下的所有文件夹
|
|
162
|
+
* @param {string} folderPath - 文件夹路径
|
|
163
|
+
* @returns {Array<string>} 文件夹名称数组
|
|
164
|
+
* @description
|
|
165
|
+
* 同步获取指定文件夹下的所有文件夹(不包含隐藏文件)
|
|
166
|
+
* @example
|
|
167
|
+
* const folders = getFolders('/path/to/directory');
|
|
168
|
+
* // 返回: ['default', 'test', ...]
|
|
169
|
+
*/
|
|
170
|
+
export function getFolders(folderPath) {
|
|
171
|
+
if (!fs.existsSync(folderPath)) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const items = fs.readdirSync(folderPath);
|
|
176
|
+
const folders = [];
|
|
177
|
+
|
|
178
|
+
for (const item of items) {
|
|
179
|
+
if (item.startsWith('.')) continue;
|
|
180
|
+
|
|
181
|
+
const itemPath = path.join(folderPath, item);
|
|
182
|
+
try {
|
|
183
|
+
const stat = fs.statSync(itemPath);
|
|
184
|
+
if (stat.isDirectory()) {
|
|
185
|
+
folders.push(item);
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return folders;
|
|
193
|
+
}
|
package/helper/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { getFileTree } from "./file.js";
|
|
|
8
8
|
export { readFileContent } from "./file.js";
|
|
9
9
|
export { saveFileContent } from "./file.js";
|
|
10
10
|
export { isPathSafe } from "./file.js";
|
|
11
|
+
export { getFolders } from "./file.js";
|
|
11
12
|
export { htmlDecode } from "./html.js";
|
|
12
13
|
export { getIp } from "./ip.js";
|
|
13
14
|
export { verifyToken } from "./jwt.js";
|
package/helper/ip.js
CHANGED
|
@@ -37,14 +37,11 @@ export function getIp(req) {
|
|
|
37
37
|
ip = ip.substring(7);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
ip === '0.0.0.0' ||
|
|
43
|
-
ip === 'localhost';
|
|
44
|
-
|
|
45
|
-
if (!isLocalAddress) {
|
|
46
|
-
return ip;
|
|
40
|
+
if (ip === '::1') {
|
|
41
|
+
ip = '127.0.0.1';
|
|
47
42
|
}
|
|
43
|
+
|
|
44
|
+
return ip;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
const headers = req.headers || {};
|
package/middleware/index.js
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import morgan from "morgan";
|
|
2
|
+
import { getIp } from "../helper/ip.js";
|
|
3
|
+
|
|
4
|
+
// 自定义 IP 令牌
|
|
5
|
+
morgan.token("ip", (req, res) => {
|
|
6
|
+
return getIp(req);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// 自定义用户信息令牌
|
|
10
|
+
morgan.token("user", (req, res) => {
|
|
11
|
+
if (req.user) {
|
|
12
|
+
return `${req.user.uid}:${req.user.username}`;
|
|
13
|
+
}
|
|
14
|
+
return "-";
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// 自定义时间令牌(带日期)
|
|
18
|
+
morgan.token("datetime", (req, res) => {
|
|
19
|
+
return new Date().toISOString();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 自定义 morgan 格式:时间 IP 用户 Method URL Status Length - Response-Time ms
|
|
23
|
+
morgan.format("chancms", (tokens, req, res) => {
|
|
24
|
+
return [
|
|
25
|
+
tokens.datetime(req, res),
|
|
26
|
+
tokens.ip(req, res),
|
|
27
|
+
tokens.user(req, res),
|
|
28
|
+
tokens.method(req, res),
|
|
29
|
+
tokens.url(req, res),
|
|
30
|
+
tokens.status(req, res),
|
|
31
|
+
tokens.res(req, res, "content-length") || "-",
|
|
32
|
+
"-",
|
|
33
|
+
tokens["response-time"](req, res),
|
|
34
|
+
"ms",
|
|
35
|
+
].join(" ");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const log = (app, logger) => {
|
|
39
|
+
const level = logger?.level || "chancms";
|
|
40
|
+
app.use(morgan(level === "chancms" ? "chancms" : level));
|
|
41
|
+
};
|
package/middleware/waf.js
CHANGED
|
@@ -104,7 +104,7 @@ function createWafMiddleware(wafConfig) {
|
|
|
104
104
|
|
|
105
105
|
if (matched) {
|
|
106
106
|
const { category, keyword } = matched;
|
|
107
|
-
console.
|
|
107
|
+
console.warn(`[WAF 拦截] IP:${clientIp} 路径:${requestPath} 关键词:${keyword} 类别:${category}`);
|
|
108
108
|
|
|
109
109
|
const htmlResponse = `
|
|
110
110
|
<!DOCTYPE html>
|
package/package.json
CHANGED
package/utils/keywords.js
CHANGED
|
@@ -16,7 +16,7 @@ export const KEYWORD_RULES = {
|
|
|
16
16
|
*/
|
|
17
17
|
extensions: [
|
|
18
18
|
".php", ".asp", ".aspx", ".jsp", ".jspx", ".do", ".action", ".cgi",
|
|
19
|
-
".py", ".pl", ".cfm", ".jhtml", ".shtml",".sql"
|
|
19
|
+
".py", ".pl", ".cfm", ".jhtml", ".shtml",".sql",".env",".git"
|
|
20
20
|
],
|
|
21
21
|
/**
|
|
22
22
|
* 敏感目录名称
|