@maiyunnet/kebab 9.13.5 → 9.13.6

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/doc/kebab-rag.md CHANGED
@@ -1360,7 +1360,7 @@ index/variables/VER.md
1360
1360
 
1361
1361
  # Variable: VER
1362
1362
 
1363
- > `const` **VER**: `"9.13.5"` = `'9.13.5'`
1363
+ > `const` **VER**: `"9.13.6"` = `'9.13.6'`
1364
1364
 
1365
1365
  Defined in: [index.ts:10](https://github.com/maiyunnet/kebab/blob/master/index.ts#L10)
1366
1366
 
package/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "9.13.5";
8
+ export declare const VER = "9.13.6";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  /** --- 框架的 LIB,以 / 结尾 --- */
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '9.13.5';
9
+ export const VER = '9.13.6';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "9.13.5",
3
+ "version": "9.13.6",
4
4
  "description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
5
5
  "type": "module",
6
6
  "keywords": [
package/sys/route.js CHANGED
@@ -902,7 +902,7 @@ export function getPost(req) {
902
902
  * @param limits 文件上传限制
903
903
  */
904
904
  export function getFormData(req, events = {}, limits = {}) {
905
- return new Promise(function (resolve) {
905
+ return new Promise(resolve => {
906
906
  if (req.readableEnded) {
907
907
  resolve({ 'post': {}, 'files': {} });
908
908
  return;
@@ -954,6 +954,38 @@ export function getFormData(req, events = {}, limits = {}) {
954
954
  let writeFileLength = 0;
955
955
  /** --- 当前读取是否已经完全结束 --- */
956
956
  let readEnd = false;
957
+ /** --- 是否有文件被限制拒绝(整体返回 false) --- */
958
+ let rejected = false;
959
+ /** --- 清理 rtn.files 中所有已写入的临时文件 --- */
960
+ function cleanupFiles() {
961
+ for (const key in rtn.files) {
962
+ let files = rtn.files[key];
963
+ if (!Array.isArray(files)) {
964
+ files = [files];
965
+ }
966
+ for (const file of files) {
967
+ lFs.unlink(file.path).catch(() => { });
968
+ }
969
+ }
970
+ }
971
+ /** --- 拒绝当前文件:销毁流、删临时文件、标记 rejected --- */
972
+ function rejectFile() {
973
+ rejected = true;
974
+ ftmpStream.destroy();
975
+ lFs.unlink(kebab.FTMP_CWD + ftmpName).catch(() => { });
976
+ ftmpName = '';
977
+ --writeFileLength;
978
+ }
979
+ /** --- 最终输出 --- */
980
+ function finalize() {
981
+ if (rejected) {
982
+ cleanupFiles();
983
+ resolve(false);
984
+ }
985
+ else {
986
+ resolve(rtn);
987
+ }
988
+ }
957
989
  // --- 开始读取 ---
958
990
  req.on('data', function (chunk) {
959
991
  buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
@@ -983,12 +1015,18 @@ export function getFormData(req, events = {}, limits = {}) {
983
1015
  ++writeFileLength;
984
1016
  state = EState.FILE;
985
1017
  fileName = match[1];
1018
+ // --- 已被拒绝则不创建文件流,仅丢弃数据 ---
1019
+ if (rejected) {
1020
+ ftmpName = '';
1021
+ break;
1022
+ }
986
1023
  // --- 检查文件扩展名限制 ---
987
1024
  if (limits.allowedExts?.length) {
988
1025
  const extIo = fileName.lastIndexOf('.');
989
1026
  const ext = extIo !== -1 ? fileName.slice(extIo).toLowerCase() : '';
990
1027
  if (!limits.allowedExts.includes(ext)) {
991
- // --- 扩展名不允许,跳过该文件 ---
1028
+ // --- 扩展名不允许,拒绝整体上传 ---
1029
+ rejected = true;
992
1030
  ftmpName = '';
993
1031
  break;
994
1032
  }
@@ -1003,6 +1041,7 @@ export function getFormData(req, events = {}, limits = {}) {
1003
1041
  date.getUTCHours().toString().padStart(2, '0') +
1004
1042
  date.getUTCMinutes().toString().padStart(2, '0') + '_' + lCore.random() + '.ftmp';
1005
1043
  ftmpStream = lFs.createWriteStream(kebab.FTMP_CWD + ftmpName);
1044
+ ftmpStream.on('error', () => { });
1006
1045
  ftmpSize = 0;
1007
1046
  }
1008
1047
  else {
@@ -1042,22 +1081,23 @@ export function getFormData(req, events = {}, limits = {}) {
1042
1081
  // --- 没找到结束标语,将预留 boundary 长度之前的写入到文件 ---
1043
1082
  const writeBuffer = buffer.subarray(0, -boundary.length - 4);
1044
1083
  if (ftmpName) {
1045
- // --- 检查文件大小限制 ---
1046
- if (limits.maxFileSize &&
1047
- (ftmpSize + Buffer.byteLength(writeBuffer) > limits.maxFileSize)) {
1048
- ftmpStream.destroy();
1049
- lFs.unlink(kebab.FTMP_CWD + ftmpName).catch(() => { });
1050
- ftmpName = '';
1051
- --writeFileLength;
1052
- }
1053
- else {
1054
- ftmpStream.write(writeBuffer);
1055
- ftmpSize += Buffer.byteLength(writeBuffer);
1084
+ if (writeBuffer.length > 0) {
1085
+ // --- 检查文件大小限制 ---
1086
+ if (limits.maxFileSize &&
1087
+ (ftmpSize + Buffer.byteLength(writeBuffer) > limits.maxFileSize)) {
1088
+ rejectFile();
1089
+ }
1090
+ else {
1091
+ ftmpStream.write(writeBuffer);
1092
+ ftmpSize += Buffer.byteLength(writeBuffer);
1093
+ }
1056
1094
  }
1057
1095
  }
1058
1096
  else {
1059
1097
  // --- 跳过该文件 ---
1060
- events.onfiledata?.(writeBuffer);
1098
+ if (writeBuffer.length > 0) {
1099
+ events.onfiledata?.(writeBuffer);
1100
+ }
1061
1101
  }
1062
1102
  buffer = buffer.subarray(-boundary.length - 4);
1063
1103
  return;
@@ -1065,44 +1105,51 @@ export function getFormData(req, events = {}, limits = {}) {
1065
1105
  // --- 找到结束标语,结束标语之前的写入文件,之后的重新放回 buffer ---
1066
1106
  const writeBuffer = buffer.subarray(0, io);
1067
1107
  if (ftmpName) {
1068
- ftmpStream.write(writeBuffer);
1069
- ftmpSize += Buffer.byteLength(writeBuffer);
1070
- ftmpStream.end(() => {
1071
- --writeFileLength;
1072
- if (!readEnd) {
1073
- // --- request 没读完,不管 ---
1074
- return;
1075
- }
1076
- if (writeFileLength) {
1077
- // --- req 读完了但文件还没写完,不管 ---
1078
- return;
1079
- }
1080
- // --- 文件也写完了 ---
1081
- resolve(rtn);
1082
- });
1083
- // --- POST 部分 ---
1084
- let fname = fileName.replace(/\\/g, '/');
1085
- const nlio = fname.lastIndexOf('/');
1086
- if (nlio !== -1) {
1087
- fname = fname.slice(nlio + 1);
1108
+ // --- 检查文件大小限制(最后一个分片) ---
1109
+ if (limits.maxFileSize &&
1110
+ (ftmpSize + Buffer.byteLength(writeBuffer) > limits.maxFileSize)) {
1111
+ rejectFile();
1088
1112
  }
1089
- const val = {
1090
- 'name': fname,
1091
- 'origin': fileName,
1092
- 'size': ftmpSize,
1093
- 'path': kebab.FTMP_CWD + ftmpName
1094
- };
1095
- if (rtn.files[name]) {
1096
- if (Array.isArray(rtn.files[name])) {
1097
- rtn.files[name].push(val);
1113
+ else {
1114
+ ftmpStream.write(writeBuffer);
1115
+ ftmpSize += Buffer.byteLength(writeBuffer);
1116
+ ftmpStream.end(() => {
1117
+ --writeFileLength;
1118
+ if (!readEnd) {
1119
+ // --- request 没读完,不管 ---
1120
+ return;
1121
+ }
1122
+ if (writeFileLength) {
1123
+ // --- req 读完了但文件还没写完,不管 ---
1124
+ return;
1125
+ }
1126
+ // --- 文件也写完了 ---
1127
+ finalize();
1128
+ });
1129
+ // --- POST 部分 ---
1130
+ let fname = fileName.replace(/\\/g, '/');
1131
+ const nlio = fname.lastIndexOf('/');
1132
+ if (nlio !== -1) {
1133
+ fname = fname.slice(nlio + 1);
1134
+ }
1135
+ const val = {
1136
+ 'name': fname,
1137
+ 'origin': fileName,
1138
+ 'size': ftmpSize,
1139
+ 'path': kebab.FTMP_CWD + ftmpName
1140
+ };
1141
+ if (rtn.files[name]) {
1142
+ if (Array.isArray(rtn.files[name])) {
1143
+ rtn.files[name].push(val);
1144
+ }
1145
+ else {
1146
+ rtn.files[name] = [rtn.files[name], val];
1147
+ }
1098
1148
  }
1099
1149
  else {
1100
- rtn.files[name] = [rtn.files[name], val];
1150
+ rtn.files[name] = val;
1101
1151
  }
1102
1152
  }
1103
- else {
1104
- rtn.files[name] = val;
1105
- }
1106
1153
  }
1107
1154
  else {
1108
1155
  // --- 跳过该文件 ---
@@ -1114,6 +1161,10 @@ export function getFormData(req, events = {}, limits = {}) {
1114
1161
  break;
1115
1162
  }
1116
1163
  }
1164
+ // --- 被拒绝则丢弃剩余数据,等待 'end' 事件后 resolve ---
1165
+ if (rejected) {
1166
+ return;
1167
+ }
1117
1168
  }
1118
1169
  });
1119
1170
  req.on('error', function (e) {
@@ -1122,17 +1173,23 @@ export function getFormData(req, events = {}, limits = {}) {
1122
1173
  }
1123
1174
  lCore.debug('[ROUTE][GETFORMDATA] request error before getFormData: ' + e.message);
1124
1175
  lCore.log({}, '[ROUTE][GETFORMDATA] request error before getFormData: ' + (e.stack ?? ''), '-error');
1176
+ cleanupFiles();
1125
1177
  resolve(false);
1126
1178
  });
1127
1179
  req.on('end', function () {
1128
1180
  readEnd = true;
1181
+ // --- 被拒绝则清理文件并 resolve(false) ---
1182
+ if (rejected) {
1183
+ finalize();
1184
+ return;
1185
+ }
1129
1186
  if (state === EState.FILE) {
1130
1187
  if (ftmpName) {
1131
1188
  ftmpStream.end(() => {
1132
1189
  --writeFileLength;
1133
1190
  if (!writeFileLength) {
1134
1191
  // --- 文件也写完了 ---
1135
- resolve(rtn);
1192
+ finalize();
1136
1193
  }
1137
1194
  });
1138
1195
  return;
@@ -1146,7 +1203,7 @@ export function getFormData(req, events = {}, limits = {}) {
1146
1203
  return;
1147
1204
  }
1148
1205
  // --- 文件写完了 ----
1149
- resolve(rtn);
1206
+ finalize();
1150
1207
  });
1151
1208
  });
1152
1209
  }
@@ -64,33 +64,6 @@ export default class extends sCtr.Ctr {
64
64
  private _dbTable;
65
65
  vector(): Promise<any>;
66
66
  kv(): Promise<kebab.Json>;
67
- net(): Promise<string>;
68
- netPipe(): Promise<kebab.Json>;
69
- netPost(): Promise<kebab.Json>;
70
- netPost1(): string;
71
- netPostString(): Promise<string>;
72
- netPostString1(): kebab.Json[];
73
- netOpen(): Promise<kebab.Json>;
74
- netFormTest(): Promise<string>;
75
- netUpload(): Promise<string>;
76
- netUpload1(): Promise<string>;
77
- netCookie(): Promise<string>;
78
- netCookie1(): string;
79
- netCookie2(): string;
80
- netSave(): Promise<string>;
81
- netFollow(): Promise<string>;
82
- netFollow1(): void;
83
- netFollow2(): kebab.Json;
84
- netReuse(): Promise<string>;
85
- netError(): Promise<string>;
86
- netHosts(): Promise<string>;
87
- netMproxy(): Promise<string | boolean>;
88
- netMproxy1(): Promise<string | boolean>;
89
- netMproxy2(): any[];
90
- netFilterheaders(): string;
91
- netFetch(): Promise<string | boolean>;
92
- netFetch1(): any[];
93
- netGetResponseJson(): Promise<string>;
94
67
  getResponseJson1(): any[];
95
68
  getResponseJson2(): any[];
96
69
  undici(): Promise<string>;
@@ -192,6 +165,14 @@ export default class extends sCtr.Ctr {
192
165
  * --- CORS 精细化配置测试 ---
193
166
  */
194
167
  ctrCrossFine(): string;
168
+ /**
169
+ * --- 文件上传限制演示页面 ---
170
+ */
171
+ ctrUploadLimits(): string;
172
+ /**
173
+ * --- 文件上传限制演示接口 ---
174
+ */
175
+ ctrUploadLimits1(): Promise<kebab.Json[]>;
195
176
  /**
196
177
  * --- END ---
197
178
  */
@@ -1,7 +1,6 @@
1
1
  // --- 库 ---
2
2
  import * as kebab from '#kebab/index.js';
3
3
  import * as lCore from '#kebab/lib/core.js';
4
- import * as lNet from '#kebab/lib/net.js';
5
4
  import * as lUndici from '#kebab/lib/undici.js';
6
5
  import * as lDb from '#kebab/lib/db.js';
7
6
  import * as lVector from '#kebab/lib/vector.js';
@@ -126,6 +125,7 @@ export default class extends sCtr.Ctr {
126
125
  `<br><a href="${this._config.const.urlBase}test/ctr-timeout-short">View "test/ctr-timeout-short"</a>`,
127
126
  `<br><a href="${this._config.const.urlBase}test/ctr-session-token">View "test/ctr-session-token"</a>`,
128
127
  `<br><a href="${this._config.const.urlBase}test/ctr-500">View "test/ctr-500"</a>`,
128
+ `<br><a href="${this._config.const.urlBase}test/ctr-upload-limits">View "test/ctr-upload-limits" (File upload limits demo)</a>`,
129
129
  '<br><br><b>Middle:</b>',
130
130
  `<br><br><a href="${this._config.const.urlBase}test/middle">View "test/middle"</a>`,
131
131
  '<br><br><b>Model test:</b>',
@@ -171,25 +171,6 @@ export default class extends sCtr.Ctr {
171
171
  `<br><br><a href="${this._config.const.urlBase}test/vector">View "test/vector"</a>`,
172
172
  '<br><br><b>Kv:</b>',
173
173
  `<br><br><a href="${this._config.const.urlBase}test/kv">View "test/kv"</a>`,
174
- '<br><br><b>Net:</b>',
175
- `<br><br><a href="${this._config.const.urlBase}test/net">View "test/net"</a>`,
176
- `<br><a href="${this._config.const.urlBase}test/net-pipe">View "test/net-pipe"</a>`,
177
- `<br><a href="${this._config.const.urlBase}test/net-post">View "test/net-post"</a>`,
178
- `<br><a href="${this._config.const.urlBase}test/net-post-string">View "test/net-post-string"</a>`,
179
- `<br><a href="${this._config.const.urlBase}test/net-open">View "test/net-open"</a>`,
180
- `<br><a href="${this._config.const.urlBase}test/net-form-test">View "test/net-form-test"</a>`,
181
- `<br><a href="${this._config.const.urlBase}test/net-upload">View "test/net-upload"</a>`,
182
- `<br><a href="${this._config.const.urlBase}test/net-cookie">View "test/net-cookie"</a>`,
183
- `<br><a href="${this._config.const.urlBase}test/net-save">View "test/net-save"</a>`,
184
- `<br><a href="${this._config.const.urlBase}test/net-follow">View "test/net-follow"</a>`,
185
- `<br><a href="${this._config.const.urlBase}test/net-reuse">View "test/net-reuse"</a>`,
186
- `<br><a href="${this._config.const.urlBase}test/net-error">View "test/net-error"</a>`,
187
- `<br><a href="${this._config.const.urlBase}test/net-hosts">View "test/net-hosts"</a>`,
188
- `<br><a href="${this._config.const.urlBase}test/net-rproxy/dist/core.js">View "test/net-rproxy/dist/core.js"</a> <a href="${this._config.const.urlBase}test/net-rproxy/package.json">View "package.json"</a>`,
189
- `<br><a href="${this._config.const.urlBase}test/net-mproxy">View "test/net-mproxy"</a>`,
190
- `<br><a href="${this._config.const.urlBase}test/net-filterheaders">View "test/net-filterheaders"</a>`,
191
- `<br><a href="${this._config.const.urlBase}test/net-fetch">View "test/net-fetch"</a>`,
192
- `<br><a href="${this._config.const.urlBase}test/net-get-response-json">View "test/net-get-response-json"</a>`,
193
174
  '<br><br><b>Undici:</b>',
194
175
  `<br><br><a href="${this._config.const.urlBase}test/undici">View "test/undici"</a>`,
195
176
  `<br><a href="${this._config.const.urlBase}test/undici-pipe">View "test/undici-pipe"</a>`,
@@ -1882,481 +1863,6 @@ echo[echo.length - 1] = echo[echo.length - 1].slice(0, -4);</pre>`);
1882
1863
  '<a href="' + this._config.const.urlBase + 'test/kv?ac=other">Other</a> | ' +
1883
1864
  '<a href="' + this._config.const.urlBase + 'test">Return</a>' + echo.join('') + '<br><br>' + this._getEnd();
1884
1865
  }
1885
- async net() {
1886
- const echo = [];
1887
- const res = await lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json');
1888
- echo.push(`<pre>Net.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json');</pre>
1889
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
1890
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
1891
- error: ${JSON.stringify(res.error)}`);
1892
- const res2 = await lNet.get(this._internalUrl + 'test?abc');
1893
- echo.push(`<pre>Net.get('${this._internalUrl}test?abc');</pre>
1894
- headers: <pre>${JSON.stringify(res2.headers, null, 4)}</pre>
1895
- content: <pre>${(await res2.getContent())?.toString().slice(0, 500) ?? 'null'}</pre>
1896
- error: ${JSON.stringify(res2.error)}`);
1897
- return echo.join('') + '<br><br>' + this._getEnd();
1898
- }
1899
- async netPipe() {
1900
- const echo = [];
1901
- const res = await lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json');
1902
- echo.push(`<pre>Net.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json');</pre>
1903
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
1904
- content: <pre>`, res, `</pre>
1905
- error: ${JSON.stringify(res.error)}
1906
- <br><br>` + this._getEnd());
1907
- return echo;
1908
- }
1909
- async netPost() {
1910
- const echo = [];
1911
- const res = await lNet.post(this._internalUrl + 'test/netPost1', { 'a': '1', 'b': '2', 'c': ['1', '2', '3'] });
1912
- echo.push(`<pre>lNet.post('${this._internalUrl}test/netPost1', { 'a': '1', 'b': '2', 'c': ['1', '2', '3'] });</pre>
1913
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
1914
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
1915
- error: ${JSON.stringify(res.error)}`);
1916
- return echo.join('') + '<br><br>' + this._getEnd();
1917
- }
1918
- netPost1() {
1919
- return `_post:\n\n${JSON.stringify(this._post)}\n\nRequest headers:\n\n${JSON.stringify(this._headers, null, 4)}\n\nIP: ${this._req.socket.remoteAddress ?? ''}`;
1920
- }
1921
- async netPostString() {
1922
- const echo = [];
1923
- const res = await lNet.post(this._internalUrl + 'test/netPostString1', 'HeiHei');
1924
- echo.push(`<pre>lNet.post('${this._internalUrl}test/netPostString1', 'HeiHei');</pre>
1925
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
1926
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
1927
- error: ${JSON.stringify(res.error)}`);
1928
- return echo.join('') + '<br><br>' + this._getEnd();
1929
- }
1930
- netPostString1() {
1931
- return [1, this._input];
1932
- }
1933
- async netOpen() {
1934
- const echo = [];
1935
- const res = await lNet.open(this._internalUrl + 'test/netPost1').post().data({ 'a': '2', 'b': '0', 'c': ['0', '1', '3'] }).request();
1936
- echo.push(`<pre>lNet.open('${this._internalUrl}test/netPost1').post().data({ 'a': '2', 'b': '0', 'c': ['0', '1', '3'] }).request();</pre>
1937
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
1938
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
1939
- error: ${JSON.stringify(res.error)}`);
1940
- return echo.join('') + this._getEnd();
1941
- }
1942
- async netFormTest() {
1943
- if (!await this._handleFormData()) {
1944
- return '';
1945
- }
1946
- const echo = [
1947
- '<pre>',
1948
- JSON.stringify(this._post, null, 4),
1949
- '\n-----\n',
1950
- JSON.stringify(this._files, null, 4),
1951
- '</pre>'
1952
- ];
1953
- echo.push(`<form enctype="multipart/form-data" method="post">
1954
- text a: <input type="text" name="a" value="a1"> <input type="text" name="a" value="a2"><br>
1955
- file b: <input type="file" name="b"><br>
1956
- file c: <input type="file" name="c"><input type="file" name="c"><br>
1957
- fi d[]: <input type="file" name="d[]"><input type="file" name="d[]"><br>
1958
- <input type="submit" value="Upload">
1959
- </form>
1960
- <hr>
1961
- <form method="post">
1962
- name a: <input type="text" name="a" value="a&1"> <input type="text" name="a" value="a&2"><br>
1963
- na b[]: <input type="text" name="b[]" value="b1"> <input type="text" name="b[]" value="b2"><br>
1964
- name d: <input type="text" name="d" value="d"><br>
1965
- <input type="submit" value="Default post">
1966
- </form>`);
1967
- return echo.join('') + this._getEnd();
1968
- }
1969
- async netUpload() {
1970
- const echo = [];
1971
- const fd = lNet.getFormData();
1972
- fd.putString('a', '1');
1973
- await fd.putFile('file', kebab.LIB_PATH + 'net/cacert.pem');
1974
- await fd.putFile('multiple', kebab.LIB_PATH + 'net/cacert.pem');
1975
- await fd.putFile('multiple', kebab.LIB_PATH + 'net/cacert.pem');
1976
- const res = await lNet.post(this._internalUrl + 'test/net-upload1', fd);
1977
- echo.push(`<pre>const fd = lNet.getFormData();
1978
- fd.putString('a', '1');
1979
- await fd.putFile('file', def.LIB_PATH + 'net/cacert.pem');
1980
- await fd.putFile('multiple', def.LIB_PATH + 'net/cacert.pem');
1981
- await fd.putFile('multiple', def.LIB_PATH + 'net/cacert.pem');
1982
- lNet.post('${this._internalUrl}test/net-upload1', fd);</pre>
1983
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
1984
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
1985
- error: ${JSON.stringify(res.error)}`);
1986
- return echo.join('') + '<br><br>' + this._getEnd();
1987
- }
1988
- async netUpload1() {
1989
- if (!await this._handleFormData()) {
1990
- return '{}';
1991
- }
1992
- return JSON.stringify(this._post, null, 4) + '\n\n' + JSON.stringify(this._files, null, 4);
1993
- }
1994
- async netCookie() {
1995
- const echo = [];
1996
- const cookie = {};
1997
- let res = await lNet.get(this._internalUrl + 'test/net-cookie1', {
1998
- 'cookie': cookie
1999
- });
2000
- echo.push(`<pre>const cookie = {};
2001
- lNet.get(this._internalUrl + 'test/net-cookie1', {
2002
- 'cookie': cookie
2003
- });</pre>
2004
- headers: <pre>${lText.htmlescape(JSON.stringify(res.headers, null, 4))}</pre>
2005
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
2006
- cookie: <pre>${JSON.stringify(cookie, null, 4)}</pre><hr>`);
2007
- res = await lNet.get(this._internalUrl + 'test/net-cookie2', {
2008
- 'cookie': cookie
2009
- });
2010
- echo.push(`<pre>lNet.get(this._internalUrl + 'test/net-cookie2', {
2011
- 'cookie': cookie
2012
- });</pre>
2013
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
2014
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>`);
2015
- lCookie.setCookie(cookie, 'custom1', 'abc1', this._config.const.host);
2016
- lCookie.setCookie(cookie, 'custom2', 'abc2', '172.17.0.1');
2017
- res = await lNet.get(this._internalUrl + 'test/net-cookie2', {
2018
- 'cookie': cookie
2019
- });
2020
- echo.push(`<pre>lCookie.setCookie(cookie, 'custom1', 'abc1', ${this._config.const.host});
2021
- lCookie.setCookie(cookie, 'custom2', 'abc2', '172.17.0.1');
2022
- lNet.get(this._internalUrl + 'test/net-cookie2', {
2023
- 'cookie': cookie
2024
- });</pre>
2025
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
2026
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>`);
2027
- return echo.join('') + this._getEnd();
2028
- }
2029
- netCookie1() {
2030
- lCore.setCookie(this, 'test0', 'session');
2031
- lCore.setCookie(this, 'test1', 'normal', {
2032
- 'ttl': 10
2033
- });
2034
- lCore.setCookie(this, 'test2', 'baidu.com', {
2035
- 'ttl': 20,
2036
- 'path': '/',
2037
- 'domain': 'baidu.com'
2038
- });
2039
- lCore.setCookie(this, 'test3', this._config.const.hostname, {
2040
- 'ttl': 30,
2041
- 'path': '/',
2042
- 'domain': this._config.const.hostname
2043
- });
2044
- lCore.setCookie(this, 'test4', '/ok/', {
2045
- 'ttl': 40,
2046
- 'path': '/ok/'
2047
- });
2048
- lCore.setCookie(this, 'test5', 'secure', {
2049
- 'ttl': 50,
2050
- 'ssl': true
2051
- });
2052
- lCore.setCookie(this, 'test6', '0.1', {
2053
- 'ttl': 40,
2054
- 'path': '/',
2055
- 'domain': '0.1'
2056
- });
2057
- lCore.setCookie(this, 'test7', 'localhost', {
2058
- 'ttl': 30,
2059
- 'path': '/',
2060
- 'domain': 'localhost'
2061
- });
2062
- lCore.setCookie(this, 'test8', 'com', {
2063
- 'ttl': 20,
2064
- 'path': '/',
2065
- 'domain': 'com'
2066
- });
2067
- lCore.setCookie(this, 'test9', 'com.cn', {
2068
- 'ttl': 10,
2069
- 'path': '/',
2070
- 'domain': 'com.cn'
2071
- });
2072
- lCore.setCookie(this, 'test10', 'httponly', {
2073
- 'ttl': 60,
2074
- 'httponly': true
2075
- });
2076
- // --- 用于测试 BUG 的额外 Cookie ---
2077
- this._res.setHeader('Set-Cookie', [
2078
- ...this._res.getHeader('Set-Cookie'),
2079
- 'test-del=; Max-Age=0; Path=/',
2080
- 'test-equals=val=with=equals; Path=/',
2081
- 'test-expires=val; Expires=Wed, 21 Oct 2035 07:28:00 GMT; Path=/',
2082
- 'test-invalid=%invalid; Path=/'
2083
- ]);
2084
- return `lCore.setCookie(this, 'test0', 'session');
2085
- lCore.setCookie(this, 'test1', 'normal', {
2086
- 'ttl': 10
2087
- });
2088
- lCore.setCookie(this, 'test2', 'baidu.com', {
2089
- 'ttl': 20,
2090
- 'path': '/',
2091
- 'domain': 'baidu.com'
2092
- });
2093
- lCore.setCookie(this, 'test3', this._config.const.hostname, {
2094
- 'ttl': 30,
2095
- 'path': '/',
2096
- 'domain': this._config.const.hostname
2097
- });
2098
- lCore.setCookie(this, 'test4', '/ok/', {
2099
- 'ttl': 40,
2100
- 'path': '/ok/'
2101
- });
2102
- lCore.setCookie(this, 'test5', 'secure', {
2103
- 'ttl': 50,
2104
- 'ssl': true
2105
- });
2106
- lCore.setCookie(this, 'test6', '0.1', {
2107
- 'ttl': 40,
2108
- 'path': '/',
2109
- 'domain': '0.1'
2110
- });
2111
- lCore.setCookie(this, 'test7', 'localhost', {
2112
- 'ttl': 30,
2113
- 'path': '/',
2114
- 'domain': 'localhost'
2115
- });
2116
- lCore.setCookie(this, 'test8', 'com', {
2117
- 'ttl': 20,
2118
- 'path': '/',
2119
- 'domain': 'com'
2120
- });
2121
- lCore.setCookie(this, 'test9', 'com.cn', {
2122
- 'ttl': 10,
2123
- 'path': '/',
2124
- 'domain': 'com.cn'
2125
- });
2126
- lCore.setCookie(this, 'test10', 'httponly', {
2127
- 'ttl': 60,
2128
- 'httponly': true
2129
- });`;
2130
- }
2131
- netCookie2() {
2132
- return 'this._cookie: \n\n' + JSON.stringify(this._cookie, null, 4);
2133
- }
2134
- async netSave() {
2135
- const echo = [];
2136
- const res = await lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json', {
2137
- 'follow': 5,
2138
- 'save': kebab.LOG_CWD + 'test-must-remove.json'
2139
- });
2140
- echo.push(`<pre>lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json', {
2141
- 'follow': 5,
2142
- 'save': def.LOG_PATH + 'test-must-remove.json'
2143
- });</pre>
2144
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
2145
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
2146
- error: ${JSON.stringify(res.error)}`);
2147
- return echo.join('') + this._getEnd();
2148
- }
2149
- async netFollow() {
2150
- const echo = [];
2151
- const res = await lNet.post(this._internalUrl + 'test/net-follow1', {
2152
- 'a': '1',
2153
- 'b': '2'
2154
- }, {
2155
- 'follow': 5
2156
- });
2157
- echo.push(`<pre>lNet.post(this._internalUrl + 'test/net-follow1', {
2158
- 'a': '1',
2159
- 'b': '2'
2160
- }, {
2161
- 'follow': 5
2162
- });</pre>
2163
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
2164
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
2165
- error: ${JSON.stringify(res.error)}</pre>`);
2166
- return echo.join('') + this._getEnd();
2167
- }
2168
- netFollow1() {
2169
- this._location('test/net-follow2');
2170
- }
2171
- netFollow2() {
2172
- return [1, { 'post': this._post['a'] + ',' + this._post['b'] }];
2173
- }
2174
- async netReuse() {
2175
- const echo = [];
2176
- echo.push('<strong>Reuse:</strong>');
2177
- let time0 = Date.now();
2178
- await lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json');
2179
- let time1 = Date.now();
2180
- echo.push("<pre>lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/package.json');</pre>" + Math.round(time1 - time0).toString() + 'ms.');
2181
- time0 = Date.now();
2182
- await lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/README.md');
2183
- time1 = Date.now();
2184
- echo.push("<pre>lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/README.md');</pre>" + Math.round(time1 - time0).toString() + 'ms.');
2185
- time0 = Date.now();
2186
- await lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/LICENSE');
2187
- time1 = Date.now();
2188
- echo.push("<pre>lNet.get('https://cdn.jsdelivr.net/npm/deskrt@2.0.10/LICENSE');</pre>" + Math.round(time1 - time0).toString() + 'ms.<hr>');
2189
- return echo.join('') + this._getEnd();
2190
- }
2191
- async netError() {
2192
- const echo = [];
2193
- const res = await lNet.get('https://192.111.000.222/xxx.zzz');
2194
- echo.push(`<pre>lNet.get('https://192.111.000.222/xxx.zzz');</pre>
2195
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
2196
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
2197
- error: <pre>${JSON.stringify(res.error, null, 4)}</pre>`);
2198
- return echo.join('') + this._getEnd();
2199
- }
2200
- async netHosts() {
2201
- const echo = [];
2202
- const res = await lNet.get('http://nodejs.org:' + this._config.const.hostport.toString() + this._config.const.urlBase + 'test', {
2203
- 'hosts': {
2204
- 'nodejs.org': '127.0.0.1'
2205
- }
2206
- });
2207
- echo.push(`<pre>lNet.get('http://nodejs.org:${this._config.const.hostport.toString() + this._config.const.urlBase}test', {
2208
- 'hosts': {
2209
- 'nodejs.org': '127.0.0.1'
2210
- }
2211
- });</pre>
2212
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
2213
- content: <pre>${lText.htmlescape((await res.getContent())?.toString() ?? '')}</pre>
2214
- error: <pre>${JSON.stringify(res.error, null, 4)}</pre>`);
2215
- return echo.join('') + this._getEnd();
2216
- }
2217
- async netMproxy() {
2218
- const echo = [];
2219
- const res = await lNet.postJson(this._internalUrl + 'test/net-mproxy2', {
2220
- 'abc': 'ok',
2221
- }, {
2222
- 'mproxy': {
2223
- 'url': this._internalUrl + 'test/net-mproxy1',
2224
- 'auth': '123456',
2225
- 'data': { 'test': '123' },
2226
- }
2227
- });
2228
- echo.push(`<pre>lNet.get('${this._internalUrl}test/net-mproxy2', {
2229
- 'mproxy': {
2230
- 'url': '${this._internalUrl}test/net-mproxy1',
2231
- 'auth': '123456',
2232
- 'data': { 'test': '123' },
2233
- }
2234
- });</pre>
2235
- headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
2236
- content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
2237
- error: ${JSON.stringify(res.error)}`);
2238
- return echo.join('') + '<br><br>' + this._getEnd();
2239
- }
2240
- async netMproxy1() {
2241
- const data = lNet.mproxyData(this);
2242
- lCore.debug('Got data', data);
2243
- const rtn = await lNet.mproxy(this, '123456');
2244
- if (rtn > 0) {
2245
- return false;
2246
- }
2247
- return 'Nothing(' + rtn + ')';
2248
- }
2249
- netMproxy2() {
2250
- return [1, {
2251
- 'data': this._post,
2252
- }];
2253
- }
2254
- netFilterheaders() {
2255
- const echo = [];
2256
- const headers = {
2257
- 'host': 'www.maiyun.net',
2258
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/180.0.0.0 Safari/537.36',
2259
- 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
2260
- 'accept-encoding': 'gzip, deflate',
2261
- 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
2262
- 'cache-control': 'no-cache',
2263
- 'cdn-loop': 'TencentEdgeOne; loops=2',
2264
- 'eo-connecting-ip': 'xx:xx:xx:xx:xx',
2265
- 'eo-log-uuid': '102780998859203995',
2266
- 'pragma': 'no-cache',
2267
- 'priority': 'u=0, i',
2268
- 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="180"',
2269
- 'sec-ch-ua-mobile': '?0',
2270
- 'sec-ch-ua-platform': '"Windows"',
2271
- 'sec-fetch-dest': 'document',
2272
- 'sec-fetch-mode': 'navigate',
2273
- 'sec-fetch-site': 'none',
2274
- 'sec-fetch-user': '?1',
2275
- 'upgrade-insecure-requests': '1',
2276
- 'x-forwarded-for': '127.1.2.3',
2277
- 'x-forwarded-host': 'www.maiyun.net',
2278
- 'x-forwarded-proto': 'http',
2279
- 'authorization': ''
2280
- };
2281
- echo.push(`origin:<pre>${JSON.stringify(headers, null, 4)}</pre>`);
2282
- const filtered = lNet.filterHeaders(headers, undefined, h => {
2283
- return !['x-', 'eo-'].some(i => h.startsWith(i));
2284
- });
2285
- echo.push(`const filtered = lNet.filterHeaders(headers);<pre>${JSON.stringify(filtered, null, 4)}</pre>`);
2286
- return echo.join('') + this._getEnd();
2287
- }
2288
- async netFetch() {
2289
- const echo = [];
2290
- try {
2291
- const res = await lNet.fetch(this._internalUrl + 'test/net-fetch1', {
2292
- 'method': 'POST',
2293
- 'headers': {
2294
- 'content-type': 'application/json',
2295
- },
2296
- 'body': JSON.stringify({ 'test': '123' }),
2297
- });
2298
- echo.push(`<pre>lNet.fetch('${this._internalUrl}test/net-fetch1', {
2299
- 'method': 'POST',
2300
- 'headers': {
2301
- 'content-type': 'application/json',
2302
- },
2303
- 'body': JSON.stringify({ 'test': '123' }),
2304
- });</pre>
2305
- headers: <pre>${JSON.stringify(Object.fromEntries(res.headers.entries()), null, 4)}</pre>
2306
- content: <pre>${await res.text()}</pre>`);
2307
- }
2308
- catch (e) {
2309
- echo.push(`error: <pre>${JSON.stringify(e)}</pre>`);
2310
- }
2311
- echo.push(`mproxy:`);
2312
- try {
2313
- const res = await lNet.fetch(this._internalUrl + 'test/net-fetch1', {
2314
- 'method': 'POST',
2315
- 'headers': {
2316
- 'content-type': 'application/json',
2317
- },
2318
- 'body': JSON.stringify({ 'test': '456' }),
2319
- 'mproxy': {
2320
- 'url': this._internalUrl + 'test/net-mproxy1',
2321
- 'auth': '123456',
2322
- 'data': { 'test': '789' },
2323
- },
2324
- });
2325
- echo.push(`<pre>lNet.fetch('${this._internalUrl}test/net-fetch1', {
2326
- 'method': 'POST',
2327
- 'headers': {
2328
- 'content-type': 'application/json',
2329
- },
2330
- 'body': JSON.stringify({ 'test': '123' }),
2331
- 'mproxy': {
2332
- 'url': this._internalUrl + 'test/net-mproxy1',
2333
- 'auth': '123456',
2334
- 'data': { 'test': '789' },
2335
- },
2336
- });</pre>
2337
- headers: <pre>${JSON.stringify(Object.fromEntries(res.headers.entries()), null, 4)}</pre>
2338
- content: <pre>${await res.text()}</pre>`);
2339
- }
2340
- catch (e) {
2341
- echo.push(`error: <pre>${JSON.stringify(e)}</pre>`);
2342
- }
2343
- return echo.join('') + '<br>' + this._getEnd();
2344
- }
2345
- netFetch1() {
2346
- return [1, {
2347
- 'post': this._post,
2348
- }];
2349
- }
2350
- async netGetResponseJson() {
2351
- const echo = [];
2352
- const json = await lNet.getResponseJson(this._internalUrl + 'test/get-response-json1');
2353
- echo.push(`<pre>const json = await lNet.getResponseJson('${this._internalUrl}test/get-response-json1');</pre>
2354
- result: <pre>${lText.htmlescape(JSON.stringify(json, null, 4))}</pre>`);
2355
- const json2 = await lNet.getResponseJson(this._internalUrl + 'test/get-response-json2');
2356
- echo.push(`<pre>const json2 = await lNet.getResponseJson('${this._internalUrl}test/get-response-json2');</pre>
2357
- result: <pre>${lText.htmlescape(JSON.stringify(json2, null, 4))}</pre>`);
2358
- return echo.join('') + '<br><br>' + this._getEnd();
2359
- }
2360
1866
  getResponseJson1() {
2361
1867
  return [1, {
2362
1868
  'data': 'This is test data from getResponseJson1',
@@ -4730,6 +4236,70 @@ send.addEventListener('click', async () => {
4730
4236
  echo.push(`Response CORS Headers: <pre>${JSON.stringify(headers, null, 4)}</pre>`);
4731
4237
  return echo.join('') + '<br><br>' + this._getEnd();
4732
4238
  }
4239
+ /**
4240
+ * --- 文件上传限制演示页面 ---
4241
+ */
4242
+ ctrUploadLimits() {
4243
+ const urlBase = this._config.const.urlBase;
4244
+ return `
4245
+ <b>File Upload Limits Demo</b><br><br>
4246
+ <b>Rules:</b>
4247
+ <ul>
4248
+ <li>Allowed extensions: <code>.jpg</code>, <code>.png</code>, <code>.pdf</code></li>
4249
+ <li>Max file size: <code>100 KB</code></li>
4250
+ <li>If any file is rejected, the entire upload is rejected (returns <code>false</code>)</li>
4251
+ </ul>
4252
+ <b>Test cases:</b>
4253
+ <ol>
4254
+ <li>Upload a <code>.jpg</code> under 100KB → should succeed</li>
4255
+ <li>Upload a <code>.exe</code> → should be rejected (extension)</li>
4256
+ <li>Upload a <code>.jpg</code> over 100KB → should be rejected (size)</li>
4257
+ <li>Upload 2 files: one valid + one invalid → should reject entire upload</li>
4258
+ </ol>
4259
+ <form id="uploadForm" enctype="multipart/form-data">
4260
+ <input type="file" id="fileInput" multiple><br><br>
4261
+ <input type="button" value="Upload" onclick="doUpload()">
4262
+ </form>
4263
+ Result:<pre id="result">Nothing.</pre>
4264
+ <script>
4265
+ function doUpload() {
4266
+ var fd = new FormData();
4267
+ var files = document.getElementById('fileInput').files;
4268
+ for (var i = 0; i < files.length; i++) {
4269
+ fd.append('file' + i, files[i]);
4270
+ }
4271
+ document.getElementById('result').innerText = 'Uploading...';
4272
+ fetch('${urlBase}test/ctr-upload-limits1', {
4273
+ method: 'POST',
4274
+ body: fd
4275
+ }).then(function(r) { return r.text(); }).then(function(t) {
4276
+ document.getElementById('result').innerText = t;
4277
+ }).catch(function(e) {
4278
+ document.getElementById('result').innerText = 'Error: ' + e.message;
4279
+ });
4280
+ }
4281
+ </script>` + this._getEnd();
4282
+ }
4283
+ /**
4284
+ * --- 文件上传限制演示接口 ---
4285
+ */
4286
+ async ctrUploadLimits1() {
4287
+ const rtn = await this._handleFormData({}, {
4288
+ 'maxFileSize': 100 * 1024,
4289
+ 'allowedExts': ['.jpg', '.png', '.pdf'],
4290
+ });
4291
+ if (!rtn) {
4292
+ return [0, 'Upload rejected. File extension not allowed or size exceeds 100KB limit.'];
4293
+ }
4294
+ const fileSummary = {};
4295
+ for (const key in this._files) {
4296
+ const f = this._files[key];
4297
+ fileSummary[key] = Array.isArray(f)
4298
+ ? f.map(item => ({ 'name': item.name, 'size': item.size }))
4299
+ : { 'name': f.name, 'size': f.size };
4300
+ }
4301
+ return [1, { 'files': fileSummary, 'post': this._post }];
4302
+ }
4733
4303
  /**
4734
4304
  * --- END ---
4735
4305
  */