web_plsql 0.17.0 → 0.18.0
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/package.json +12 -9
- package/src/handler/plsql/cgi.js +1 -1
- package/src/handler/plsql/errorPage.js +2 -6
- package/src/handler/plsql/parsePage.js +8 -8
- package/src/handler/plsql/procedure.js +47 -57
- package/src/handler/plsql/procedureNamed.js +0 -5
- package/src/handler/plsql/procedureSanitize.js +0 -4
- package/src/handler/plsql/request.js +9 -4
- package/src/handler/plsql/sendResponse.js +42 -10
- package/src/handler/plsql/stream.js +1 -1
- package/src/handler/plsql/upload.js +1 -1
- package/src/index.js +5 -1
- package/src/server/config.js +1 -1
- package/src/types.js +1 -1
- package/src/util/file.js +5 -9
- package/src/{server/version.js → version.js} +1 -1
- package/types/handler/plsql/sendResponse.d.ts +1 -1
- package/types/index.d.ts +3 -1
- package/types/types.d.ts +1 -1
- package/types/handler/plsql/stream.d.ts +0 -2
- /package/types/{server/version.d.ts → version.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web_plsql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"author": "Dieter Oberkofler <dieter.oberkofler@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "The Express Middleware for Oracle PL/SQL",
|
|
@@ -44,10 +44,12 @@
|
|
|
44
44
|
"type": "module",
|
|
45
45
|
"scripts": {
|
|
46
46
|
"lint": "prettier --check --experimental-cli . && eslint . && tsc --noEmit",
|
|
47
|
-
"test": "
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:coverage": "vitest run --coverage",
|
|
48
49
|
"types": "rm -rf types && tsc --noEmit false --declaration true --declarationDir types --emitDeclarationOnly true --allowJs true --target es2020 --module es2020 --moduleResolution node --allowSyntheticDefaultImports true src/*.js",
|
|
49
50
|
"clean": "shx rm -f *.tgz && shx rm -f *.log",
|
|
50
|
-
"ci": "npm run clean && npm run
|
|
51
|
+
"ci": "npm run clean && npm run version && npm run types && npm run lint && npm run test:coverage",
|
|
52
|
+
"version": "node update-version.js",
|
|
51
53
|
"create-package": "shx rm -f *.tgz && npm pack",
|
|
52
54
|
"image-build": "docker build --no-cache --progress=plain --tag=web_plsql .",
|
|
53
55
|
"image-save": "docker save web_plsql | gzip > web_plsql.tar.gz"
|
|
@@ -56,7 +58,6 @@
|
|
|
56
58
|
"oracledb": "6.10.0"
|
|
57
59
|
},
|
|
58
60
|
"dependencies": {
|
|
59
|
-
"basic-auth": "2.0.1",
|
|
60
61
|
"compression": "1.8.1",
|
|
61
62
|
"cookie-parser": "1.4.7",
|
|
62
63
|
"debug": "4.4.3",
|
|
@@ -64,25 +65,27 @@
|
|
|
64
65
|
"http-parser-js": "0.5.10",
|
|
65
66
|
"morgan": "1.10.1",
|
|
66
67
|
"multer": "2.0.2",
|
|
67
|
-
"rotating-file-stream": "3.2.
|
|
68
|
+
"rotating-file-stream": "3.2.8",
|
|
68
69
|
"zod": "4.3.6"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
|
-
"@types/basic-auth": "1.1.8",
|
|
72
72
|
"@types/compression": "1.8.1",
|
|
73
73
|
"@types/cookie-parser": "1.4.10",
|
|
74
74
|
"@types/debug": "4.1.12",
|
|
75
75
|
"@types/morgan": "1.9.10",
|
|
76
76
|
"@types/multer": "2.0.0",
|
|
77
|
-
"@types/node": "25.
|
|
77
|
+
"@types/node": "25.2.1",
|
|
78
78
|
"@types/oracledb": "6.10.1",
|
|
79
79
|
"@types/supertest": "6.0.3",
|
|
80
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
80
81
|
"eslint": "9.39.2",
|
|
81
|
-
"eslint-plugin-jsdoc": "62.
|
|
82
|
+
"eslint-plugin-jsdoc": "62.5.2",
|
|
83
|
+
"oracledb": "6.10.0",
|
|
82
84
|
"prettier": "3.8.1",
|
|
83
85
|
"shx": "0.4.0",
|
|
84
86
|
"supertest": "7.2.2",
|
|
85
87
|
"typescript": "5.9.3",
|
|
86
|
-
"typescript-eslint": "8.
|
|
88
|
+
"typescript-eslint": "8.54.0",
|
|
89
|
+
"vitest": "^4.0.18"
|
|
87
90
|
}
|
|
88
91
|
}
|
package/src/handler/plsql/cgi.js
CHANGED
|
@@ -109,7 +109,7 @@ export const getCGI = (req, doctable, cgi) => {
|
|
|
109
109
|
const CGI = {
|
|
110
110
|
SERVER_PORT: typeof req.socket.localPort === 'number' ? req.socket.localPort.toString() : '',
|
|
111
111
|
REQUEST_METHOD: req.method,
|
|
112
|
-
PATH_INFO: req.params.name,
|
|
112
|
+
PATH_INFO: Array.isArray(req.params.name) ? req.params.name[0] : req.params.name,
|
|
113
113
|
SCRIPT_NAME: PATH.script,
|
|
114
114
|
REMOTE_ADDR: (req.ip ?? '').replace('::ffff:', ''),
|
|
115
115
|
SERVER_PROTOCOL: `${PROTOCOL}/${req.httpVersion}`,
|
|
@@ -36,30 +36,26 @@ const getError = (req, error) => {
|
|
|
36
36
|
// what type of Error did we receive
|
|
37
37
|
if (error instanceof ProcedureError) {
|
|
38
38
|
timestamp = error.timestamp;
|
|
39
|
-
/* istanbul ignore next */
|
|
40
39
|
message = error.stack ?? '';
|
|
41
40
|
environment = error.environment;
|
|
42
41
|
sql = error.sql;
|
|
43
42
|
bind = error.bind;
|
|
44
43
|
} else if (error instanceof RequestError) {
|
|
45
44
|
timestamp = error.timestamp;
|
|
46
|
-
/* istanbul ignore next */
|
|
47
45
|
message = error.stack ?? '';
|
|
48
46
|
} else if (error instanceof Error) {
|
|
49
|
-
/* istanbul ignore next */
|
|
50
47
|
message = errorToString(error);
|
|
51
48
|
} else {
|
|
52
49
|
if (typeof error === 'string') {
|
|
53
|
-
/* istanbul ignore next */
|
|
54
50
|
message = `${error}\n`;
|
|
55
51
|
}
|
|
52
|
+
/* v8 ignore start - unreachable code: creating Error without throwing */
|
|
56
53
|
try {
|
|
57
|
-
/* istanbul ignore next */
|
|
58
54
|
new Error();
|
|
59
55
|
} catch (err) {
|
|
60
|
-
/* istanbul ignore next */
|
|
61
56
|
message += errorToString(err);
|
|
62
57
|
}
|
|
58
|
+
/* v8 ignore stop */
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
return getFormattedMessage({type: 'error', timestamp, message, req, environment, sql, bind});
|
|
@@ -55,7 +55,7 @@ export const parsePage = (text) => {
|
|
|
55
55
|
{
|
|
56
56
|
const cookie = parseCookie(header.value);
|
|
57
57
|
debug(`oracle header "set-cookie" with value "${header.value}" was received has been parsed to ${JSON.stringify(cookie)}`);
|
|
58
|
-
/*
|
|
58
|
+
/* v8 ignore else - parseCookie validation ensures non-null */
|
|
59
59
|
if (cookie !== null) {
|
|
60
60
|
page.head.cookies.push(cookie);
|
|
61
61
|
} else {
|
|
@@ -72,7 +72,7 @@ export const parsePage = (text) => {
|
|
|
72
72
|
case 'x-db-content-length':
|
|
73
73
|
{
|
|
74
74
|
const contentLength = parseInt(header.value, 10);
|
|
75
|
-
/*
|
|
75
|
+
/* v8 ignore else - parseInt validation */
|
|
76
76
|
if (!Number.isNaN(contentLength)) {
|
|
77
77
|
page.head.contentLength = contentLength;
|
|
78
78
|
debug(`oracle header "x-db-content-length" with value "${page.head.contentLength}" was parsed`);
|
|
@@ -85,12 +85,12 @@ export const parsePage = (text) => {
|
|
|
85
85
|
case 'status':
|
|
86
86
|
{
|
|
87
87
|
const statusCode = parseInt(header.value, 10);
|
|
88
|
-
/*
|
|
88
|
+
/* v8 ignore else - parseInt validation */
|
|
89
89
|
if (!Number.isNaN(statusCode)) {
|
|
90
90
|
page.head.statusCode = statusCode;
|
|
91
91
|
debug(`oracle header "status" with value "${page.head.statusCode}" was parsed`);
|
|
92
92
|
const index = header.value.indexOf(' ');
|
|
93
|
-
/*
|
|
93
|
+
/* v8 ignore else - status code may not have description */
|
|
94
94
|
if (index !== -1) {
|
|
95
95
|
page.head.statusDescription = header.value.substring(index + 1);
|
|
96
96
|
}
|
|
@@ -143,7 +143,7 @@ const getHeader = (line) => {
|
|
|
143
143
|
*/
|
|
144
144
|
const parseCookie = (text) => {
|
|
145
145
|
// validate
|
|
146
|
-
/*
|
|
146
|
+
/* v8 ignore next - input validation */
|
|
147
147
|
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
148
148
|
return null;
|
|
149
149
|
}
|
|
@@ -156,7 +156,7 @@ const parseCookie = (text) => {
|
|
|
156
156
|
|
|
157
157
|
// get name and value
|
|
158
158
|
const index = cookieElements[0].indexOf('=');
|
|
159
|
-
/*
|
|
159
|
+
/* v8 ignore next - cookie format validation */
|
|
160
160
|
if (index <= 0) {
|
|
161
161
|
// if the index is -1, there is no equal sign and if it's 0 the name is empty
|
|
162
162
|
return null;
|
|
@@ -179,7 +179,7 @@ const parseCookie = (text) => {
|
|
|
179
179
|
} else if (element.toLowerCase().startsWith('domain=')) {
|
|
180
180
|
cookie.options.domain = element.substring(7);
|
|
181
181
|
} else if (element.toLowerCase().startsWith('secure=')) {
|
|
182
|
-
/*
|
|
182
|
+
/* v8 ignore next - secure cookie attribute */
|
|
183
183
|
cookie.options.secure = true;
|
|
184
184
|
} else if (element.toLowerCase().startsWith('expires=')) {
|
|
185
185
|
const date = tryDecodeDate(element.substring(8));
|
|
@@ -203,7 +203,7 @@ const tryDecodeDate = (value) => {
|
|
|
203
203
|
try {
|
|
204
204
|
return new Date(value);
|
|
205
205
|
} catch (err) {
|
|
206
|
-
/*
|
|
206
|
+
/* v8 ignore next - invalid date format */
|
|
207
207
|
return null;
|
|
208
208
|
}
|
|
209
209
|
};
|
|
@@ -9,7 +9,6 @@ import oracledb from 'oracledb';
|
|
|
9
9
|
import stream from 'node:stream';
|
|
10
10
|
import z from 'zod';
|
|
11
11
|
|
|
12
|
-
import {streamToBuffer} from './stream.js';
|
|
13
12
|
import {uploadFile} from './upload.js';
|
|
14
13
|
import {getProcedureVariable} from './procedureVariable.js';
|
|
15
14
|
import {getProcedureNamed} from './procedureNamed.js';
|
|
@@ -82,7 +81,7 @@ const procedurePrepare = async (cgiObj, databaseConnection) => {
|
|
|
82
81
|
try {
|
|
83
82
|
await databaseConnection.execute(sqlStatement);
|
|
84
83
|
} catch (err) {
|
|
85
|
-
throw new ProcedureError(`
|
|
84
|
+
throw new ProcedureError(`procedurePrepare: error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, {});
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
// htbuf_len: reduce this limit based on your worst-case character size.
|
|
@@ -99,7 +98,7 @@ const procedurePrepare = async (cgiObj, databaseConnection) => {
|
|
|
99
98
|
try {
|
|
100
99
|
await databaseConnection.execute(sqlStatement, bindParameter);
|
|
101
100
|
} catch (err) {
|
|
102
|
-
throw new ProcedureError(`
|
|
101
|
+
throw new ProcedureError(`procedurePrepare: error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, bindParameter);
|
|
103
102
|
}
|
|
104
103
|
};
|
|
105
104
|
|
|
@@ -116,7 +115,7 @@ const procedureExecute = async (para, databaseConnection) => {
|
|
|
116
115
|
try {
|
|
117
116
|
await databaseConnection.execute(sqlStatement, para.bind);
|
|
118
117
|
} catch (err) {
|
|
119
|
-
throw new ProcedureError(`
|
|
118
|
+
throw new ProcedureError(`procedureExecute: error when executing procedure:\n${sqlStatement}\n${errorToString(err)}`, {}, para.sql, para.bind);
|
|
120
119
|
}
|
|
121
120
|
};
|
|
122
121
|
|
|
@@ -144,18 +143,18 @@ const procedureGetPage = async (test, databaseConnection) => {
|
|
|
144
143
|
result = await databaseConnection.execute(sqlStatement, bindParameter);
|
|
145
144
|
} catch (err) {
|
|
146
145
|
if (debug.enabled) {
|
|
147
|
-
debug(getBlock('results', inspect(result)));
|
|
146
|
+
debug(getBlock('procedureGetPage: results', inspect(result)));
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
throw new ProcedureError(`
|
|
149
|
+
throw new ProcedureError(`procedureGetPage: error when getting page returned by procedure\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
const {lines, irows} = z.object({irows: z.number(), lines: z.array(z.string())}).parse(result.outBinds);
|
|
154
153
|
|
|
155
154
|
// Make sure that we have retrieved all the rows
|
|
156
155
|
if (irows > MAX_IROWS) {
|
|
157
|
-
/*
|
|
158
|
-
throw new ProcedureError(`
|
|
156
|
+
/* v8 ignore next - defensive check for row limit */
|
|
157
|
+
throw new ProcedureError(`procedureGetPage: error when retrieving rows. irows="${irows}"`, {}, sqlStatement, bindParameter);
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
return lines.join('');
|
|
@@ -199,10 +198,10 @@ END;
|
|
|
199
198
|
result = await databaseConnection.execute(sqlStatement, bindParameter);
|
|
200
199
|
} catch (err) {
|
|
201
200
|
if (debug.enabled) {
|
|
202
|
-
debug(getBlock('results', inspect(result)));
|
|
201
|
+
debug(getBlock('procedureDownloadFiles: results', inspect(result)));
|
|
203
202
|
}
|
|
204
203
|
|
|
205
|
-
throw new ProcedureError(`
|
|
204
|
+
throw new ProcedureError(`procedureDownloadFiles: error when downloading files\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
return z
|
|
@@ -233,12 +232,9 @@ END;
|
|
|
233
232
|
* @returns {Promise<void>} Promise resolving to the page content generated by the executed procedure
|
|
234
233
|
*/
|
|
235
234
|
export const invokeProcedure = async (req, res, argObj, cgiObj, filesToUpload, options, databaseConnection) => {
|
|
236
|
-
debug('invokeProcedure:
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
// UPLOAD FILES
|
|
240
|
-
//
|
|
235
|
+
debug('invokeProcedure: begin');
|
|
241
236
|
|
|
237
|
+
// 1) upload files
|
|
242
238
|
debug(`invokeProcedure: upload "${filesToUpload.length}" files`);
|
|
243
239
|
if (filesToUpload.length > 0) {
|
|
244
240
|
if (typeof options.documentTable === 'string' && options.documentTable.length > 0) {
|
|
@@ -250,71 +246,65 @@ export const invokeProcedure = async (req, res, argObj, cgiObj, filesToUpload, o
|
|
|
250
246
|
}
|
|
251
247
|
}
|
|
252
248
|
|
|
253
|
-
//
|
|
254
|
-
// GET SQL STATEMENT AND ARGUMENTS
|
|
255
|
-
//
|
|
249
|
+
// 2) get procedure to execute and the arguments
|
|
256
250
|
|
|
257
|
-
|
|
251
|
+
debug('invokeProcedure: get procedure to execute and the arguments');
|
|
252
|
+
const para = await getProcedure(req, Array.isArray(req.params.name) ? req.params.name[0] : req.params.name, argObj, options, databaseConnection);
|
|
258
253
|
|
|
259
|
-
//
|
|
260
|
-
// PROCEDURE PREPARE
|
|
261
|
-
//
|
|
254
|
+
// 3) prepare the session
|
|
262
255
|
|
|
256
|
+
debug('invokeProcedure: prepare the session');
|
|
263
257
|
await procedurePrepare(cgiObj, databaseConnection);
|
|
264
258
|
|
|
265
|
-
//
|
|
266
|
-
// PROCEDURE EXECUTE
|
|
267
|
-
//
|
|
259
|
+
// 4) execute the procedure
|
|
268
260
|
|
|
261
|
+
debug('invokeProcedure: execute the session');
|
|
269
262
|
await procedureExecute(para, databaseConnection);
|
|
270
263
|
|
|
271
|
-
//
|
|
272
|
-
// PROCEDURE GET PAGE
|
|
273
|
-
//
|
|
264
|
+
// 5) get the page returned from the procedure
|
|
274
265
|
|
|
266
|
+
debug('invokeProcedure: get the page returned from the procedure');
|
|
275
267
|
const lines = await procedureGetPage(true, databaseConnection);
|
|
276
268
|
if (debug.enabled) {
|
|
277
269
|
debug(getBlock('data', lines));
|
|
278
270
|
}
|
|
279
271
|
|
|
280
|
-
//
|
|
281
|
-
// PROCEDURE DOWNLOAD FILE
|
|
282
|
-
//
|
|
272
|
+
// 6) download files
|
|
283
273
|
|
|
274
|
+
debug('invokeProcedure: download files');
|
|
284
275
|
const fileBlob = await databaseConnection.createLob(oracledb.BLOB);
|
|
285
|
-
const fileDownload = await procedureDownloadFiles(fileBlob, databaseConnection);
|
|
286
|
-
if (debug.enabled) {
|
|
287
|
-
debug(getBlock('data', inspect(fileDownload)));
|
|
288
|
-
}
|
|
289
276
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
277
|
+
try {
|
|
278
|
+
const fileDownload = await procedureDownloadFiles(fileBlob, databaseConnection);
|
|
279
|
+
if (debug.enabled) {
|
|
280
|
+
debug(getBlock('fileDownload', inspect({fileType: fileDownload.fileType, fileSize: fileDownload.fileSize})));
|
|
281
|
+
}
|
|
293
282
|
|
|
294
|
-
|
|
295
|
-
const pageComponents = parsePage(lines);
|
|
283
|
+
// 7) parse the page
|
|
296
284
|
|
|
297
|
-
|
|
298
|
-
|
|
285
|
+
debug('invokeProcedure: parse the page');
|
|
286
|
+
const pageComponents = parsePage(lines);
|
|
299
287
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
pageComponents.file.fileType = fileDownload.fileType;
|
|
303
|
-
pageComponents.file.fileSize = fileDownload.fileSize;
|
|
304
|
-
pageComponents.file.fileBlob = await streamToBuffer(fileDownload.fileBlob);
|
|
305
|
-
}
|
|
288
|
+
// add "Server" header
|
|
289
|
+
pageComponents.head.server = cgiObj.SERVER_SOFTWARE;
|
|
306
290
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
291
|
+
// add file download information
|
|
292
|
+
if (fileDownload.fileType !== '' && fileDownload.fileSize > 0 && fileDownload.fileBlob !== null) {
|
|
293
|
+
pageComponents.file.fileType = fileDownload.fileType;
|
|
294
|
+
pageComponents.file.fileSize = fileDownload.fileSize;
|
|
295
|
+
pageComponents.file.fileBlob = fileDownload.fileBlob;
|
|
296
|
+
}
|
|
310
297
|
|
|
311
|
-
|
|
298
|
+
// 8) send the page to browser
|
|
312
299
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
300
|
+
debug('invokeProcedure: send the page to browser');
|
|
301
|
+
await sendResponse(req, res, pageComponents);
|
|
302
|
+
} finally {
|
|
303
|
+
// 9) cleanup
|
|
316
304
|
|
|
317
|
-
|
|
305
|
+
debug('invokeProcedure: cleanup');
|
|
306
|
+
fileBlob.destroy();
|
|
307
|
+
}
|
|
318
308
|
|
|
319
|
-
debug('invokeProcedure:
|
|
309
|
+
debug('invokeProcedure: end');
|
|
320
310
|
};
|
|
@@ -89,9 +89,7 @@ const loadArguments = async (procedure, databaseConnection) => {
|
|
|
89
89
|
result = await databaseConnection.execute(SQL_GET_ARGUMENT, bind);
|
|
90
90
|
} catch (err) {
|
|
91
91
|
debug('result', result);
|
|
92
|
-
/* istanbul ignore next */
|
|
93
92
|
const message = `Error when retrieving arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
|
|
94
|
-
/* istanbul ignore next */
|
|
95
93
|
throw new RequestError(message);
|
|
96
94
|
}
|
|
97
95
|
|
|
@@ -106,14 +104,11 @@ const loadArguments = async (procedure, databaseConnection) => {
|
|
|
106
104
|
.parse(result.outBinds);
|
|
107
105
|
} catch (err) {
|
|
108
106
|
debug('result.outBinds', result.outBinds);
|
|
109
|
-
/* istanbul ignore next */
|
|
110
107
|
const message = `Error when decoding arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
|
|
111
|
-
/* istanbul ignore next */
|
|
112
108
|
throw new RequestError(message);
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
if (data.names.length !== data.types.length) {
|
|
116
|
-
/* istanbul ignore next */
|
|
117
112
|
throw new RequestError('Error when decoding arguments. The number of names and types does not match');
|
|
118
113
|
}
|
|
119
114
|
|
|
@@ -128,9 +128,7 @@ const loadRequestValid = async (procName, requestValidationFunction, databaseCon
|
|
|
128
128
|
result = await databaseConnection.execute(SQL, bind);
|
|
129
129
|
} catch (err) {
|
|
130
130
|
debug('result', result);
|
|
131
|
-
/* istanbul ignore next */
|
|
132
131
|
const message = `Error when validating procedure name "${procName}"\n${SQL}\n${errorToString(err)}`;
|
|
133
|
-
/* istanbul ignore next */
|
|
134
132
|
throw new RequestError(message);
|
|
135
133
|
}
|
|
136
134
|
|
|
@@ -139,9 +137,7 @@ const loadRequestValid = async (procName, requestValidationFunction, databaseCon
|
|
|
139
137
|
return data.valid === 1;
|
|
140
138
|
} catch (err) {
|
|
141
139
|
debug('result', result.outBinds);
|
|
142
|
-
/* istanbul ignore next */
|
|
143
140
|
const message = `Internal error when parsing ${result.outBinds}\n${errorToString(err)}`;
|
|
144
|
-
/* istanbul ignore next */
|
|
145
141
|
throw new Error(message);
|
|
146
142
|
}
|
|
147
143
|
};
|
|
@@ -33,6 +33,11 @@ import {isStringOrArrayOfString} from '../../util/type.js';
|
|
|
33
33
|
export const processRequest = async (req, res, options, connectionPool) => {
|
|
34
34
|
debug('processRequest: ENTER');
|
|
35
35
|
|
|
36
|
+
//
|
|
37
|
+
if (Array.isArray(req.params.name)) {
|
|
38
|
+
console.warn(`processRequest: WARNING: the req.params.name is not a string but an array of string: ${req.params.name.join(',')}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
// open database connection
|
|
37
42
|
const connection = await connectionPool.getConnection();
|
|
38
43
|
|
|
@@ -68,7 +73,7 @@ export const processRequest = async (req, res, options, connectionPool) => {
|
|
|
68
73
|
await connection.rollback();
|
|
69
74
|
} else if (typeof options.transactionMode === 'function') {
|
|
70
75
|
debug('transactionMode: callback');
|
|
71
|
-
const result = options.transactionMode(connection, req.params.name);
|
|
76
|
+
const result = options.transactionMode(connection, Array.isArray(req.params.name) ? req.params.name[0] : req.params.name);
|
|
72
77
|
debug('transactionMode: callback restult', result);
|
|
73
78
|
if (result && typeof result.then === 'function') {
|
|
74
79
|
await result;
|
|
@@ -93,16 +98,16 @@ const normalizeBody = (req) => {
|
|
|
93
98
|
/** @type {Record<string, string | string[]>} */
|
|
94
99
|
const args = {};
|
|
95
100
|
|
|
96
|
-
/*
|
|
101
|
+
/* v8 ignore else - body validation */
|
|
97
102
|
if (typeof req.body === 'object' && req.body !== null) {
|
|
98
103
|
for (const key in req.body) {
|
|
99
104
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
100
105
|
const value = req.body[key];
|
|
101
|
-
/*
|
|
106
|
+
/* v8 ignore else - type validation */
|
|
102
107
|
if (isStringOrArrayOfString(value)) {
|
|
103
108
|
args[key] = value;
|
|
104
109
|
} else {
|
|
105
|
-
/*
|
|
110
|
+
/* v8 ignore next - invalid body type */
|
|
106
111
|
throw new RequestError(
|
|
107
112
|
`The element "${key}" in the body is not a string or an array of strings!\n${util.inspect(req.body, {showHidden: false, depth: null, colors: false})}`,
|
|
108
113
|
);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import debugModule from 'debug';
|
|
6
6
|
const debug = debugModule('webplsql:sendResponse');
|
|
7
7
|
|
|
8
|
+
import stream from 'node:stream';
|
|
8
9
|
import {getBlock} from '../../util/trace.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -20,9 +21,9 @@ import {getBlock} from '../../util/trace.js';
|
|
|
20
21
|
* @param {Request} req - The req object represents the HTTP request.
|
|
21
22
|
* @param {Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
|
|
22
23
|
* @param {pageType} page - The page to send.
|
|
23
|
-
* @returns {void}
|
|
24
|
+
* @returns {Promise<void>}
|
|
24
25
|
*/
|
|
25
|
-
export const sendResponse = (req, res, page) => {
|
|
26
|
+
export const sendResponse = async (req, res, page) => {
|
|
26
27
|
/** @type {string[]} */
|
|
27
28
|
const debugText = [];
|
|
28
29
|
|
|
@@ -57,20 +58,51 @@ export const sendResponse = (req, res, page) => {
|
|
|
57
58
|
|
|
58
59
|
// If this is a file download, we eventually set the "Content-Type" and the file content and then return.
|
|
59
60
|
if (page.file.fileType === 'B' || page.file.fileType === 'F') {
|
|
61
|
+
/** @type {Record<string, string>} */
|
|
62
|
+
const headers = {};
|
|
63
|
+
|
|
60
64
|
if (typeof page.head.contentType === 'string' && page.head.contentType.length > 0) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
65
|
+
headers['Content-Type'] = page.head.contentType;
|
|
66
|
+
}
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
if (typeof page.file.fileSize === 'number' && page.file.fileSize > 0) {
|
|
69
|
+
headers['Content-Length'] = page.file.fileSize.toString();
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
if (Object.keys(headers).length > 0) {
|
|
73
|
+
if (debug.enabled) {
|
|
74
|
+
debugText.push(`res.writeHead(200, ${JSON.stringify(headers)})`);
|
|
75
|
+
}
|
|
76
|
+
res.writeHead(200, headers);
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
|
|
79
|
+
// Check if fileBlob is a stream
|
|
80
|
+
if (page.file.fileBlob instanceof stream.Readable) {
|
|
81
|
+
if (debug.enabled) {
|
|
82
|
+
debugText.push(`res.pipe("${page.file.fileType}") - streaming`);
|
|
83
|
+
debug(getBlock('RESPONSE', debugText.join('\n')));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @type {Promise<void>} */
|
|
87
|
+
const streamComplete = new Promise((resolve, reject) => {
|
|
88
|
+
if (page.file.fileBlob instanceof stream.Readable) {
|
|
89
|
+
page.file.fileBlob.pipe(res);
|
|
90
|
+
page.file.fileBlob.on('end', () => resolve());
|
|
91
|
+
/* v8 ignore next - error handler */
|
|
92
|
+
page.file.fileBlob.on('error', (/** @type {Error} */ err) => reject(err));
|
|
93
|
+
/* v8 ignore next - error handler */
|
|
94
|
+
res.on('close', () => resolve());
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
await streamComplete;
|
|
98
|
+
} else {
|
|
99
|
+
if (debug.enabled) {
|
|
100
|
+
debugText.push(`res.end("${page.file.fileType}") - buffer`);
|
|
101
|
+
debug(getBlock('RESPONSE', debugText.join('\n')));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
res.end(page.file.fileBlob, 'binary');
|
|
105
|
+
}
|
|
74
106
|
return;
|
|
75
107
|
}
|
|
76
108
|
|
|
@@ -70,7 +70,7 @@ export const getFiles = (req) => {
|
|
|
70
70
|
export const uploadFile = async (file, doctable, databaseConnection) => {
|
|
71
71
|
debug(`uploadFile`, file, doctable);
|
|
72
72
|
|
|
73
|
-
/*
|
|
73
|
+
/* v8 ignore next - defensive validation */
|
|
74
74
|
if (typeof doctable !== 'string' || doctable.length === 0) {
|
|
75
75
|
throw new Error(`Unable to upload file "${file.filename}" because the option ""doctable" has not been defined`);
|
|
76
76
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
// libraries
|
|
2
|
+
import oracledb from 'oracledb';
|
|
3
|
+
export {oracledb};
|
|
4
|
+
|
|
1
5
|
// server
|
|
2
|
-
export {getVersion} from './
|
|
6
|
+
export {getVersion} from './version.js';
|
|
3
7
|
export {createServer, startServer, loadConfig, startServerConfig} from './server/server.js';
|
|
4
8
|
export * from './util/shutdown.js';
|
|
5
9
|
|
package/src/server/config.js
CHANGED
package/src/types.js
CHANGED
|
@@ -140,5 +140,5 @@ export const z$configType = z.strictObject({
|
|
|
140
140
|
* @property {object} file - The file.
|
|
141
141
|
* @property {string | null} file.fileType - The file type.
|
|
142
142
|
* @property {number | null} file.fileSize - The file size.
|
|
143
|
-
* @property {Buffer | null} file.fileBlob - The file blob.
|
|
143
|
+
* @property {import('node:stream').Readable | Buffer | null} file.fileBlob - The file blob.
|
|
144
144
|
*/
|
package/src/util/file.js
CHANGED
|
@@ -10,7 +10,6 @@ export const readFileSyncUtf8 = (filePath) => {
|
|
|
10
10
|
try {
|
|
11
11
|
return readFileSync(filePath, 'utf8');
|
|
12
12
|
} catch (err) {
|
|
13
|
-
/* istanbul ignore next */
|
|
14
13
|
throw new Error(`Unable to read file "${filePath}"`);
|
|
15
14
|
}
|
|
16
15
|
};
|
|
@@ -21,11 +20,10 @@ export const readFileSyncUtf8 = (filePath) => {
|
|
|
21
20
|
* @param {string} filePath - File name.
|
|
22
21
|
* @returns {Promise<Buffer>} The buffer.
|
|
23
22
|
*/
|
|
24
|
-
export const readFile = (filePath) => {
|
|
23
|
+
export const readFile = async (filePath) => {
|
|
25
24
|
try {
|
|
26
|
-
return fs.readFile(filePath);
|
|
25
|
+
return await fs.readFile(filePath);
|
|
27
26
|
} catch (err) {
|
|
28
|
-
/* istanbul ignore next */
|
|
29
27
|
throw new Error(`Unable to read file "${filePath}"`);
|
|
30
28
|
}
|
|
31
29
|
};
|
|
@@ -36,11 +34,10 @@ export const readFile = (filePath) => {
|
|
|
36
34
|
* @param {string} filePath - File name.
|
|
37
35
|
* @returns {Promise<void>}.
|
|
38
36
|
*/
|
|
39
|
-
export const removeFile = (filePath) => {
|
|
37
|
+
export const removeFile = async (filePath) => {
|
|
40
38
|
try {
|
|
41
|
-
return fs.unlink(filePath);
|
|
39
|
+
return await fs.unlink(filePath);
|
|
42
40
|
} catch (err) {
|
|
43
|
-
/* istanbul ignore next */
|
|
44
41
|
throw new Error(`Unable to remove file "${filePath}"`);
|
|
45
42
|
}
|
|
46
43
|
};
|
|
@@ -52,9 +49,8 @@ export const removeFile = (filePath) => {
|
|
|
52
49
|
* @returns {unknown} The json object.
|
|
53
50
|
*/
|
|
54
51
|
export const getJsonFile = (filePath) => {
|
|
55
|
-
const fileContent = readFileSync(filePath, 'utf8');
|
|
56
|
-
|
|
57
52
|
try {
|
|
53
|
+
const fileContent = readFileSync(filePath, 'utf8');
|
|
58
54
|
return JSON.parse(fileContent);
|
|
59
55
|
} catch (err) {
|
|
60
56
|
throw new Error(`Unable to load file "${filePath}"`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function sendResponse(req: Request, res: Response, page: pageType): void
|
|
1
|
+
export function sendResponse(req: Request, res: Response, page: pageType): Promise<void>;
|
|
2
2
|
export type Request = import("express").Request;
|
|
3
3
|
export type Response = import("express").Response;
|
|
4
4
|
export type CookieOptions = import("express").CookieOptions;
|
package/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { oracledb };
|
|
2
|
+
export { getVersion } from "./version.js";
|
|
2
3
|
export * from "./util/shutdown.js";
|
|
3
4
|
export * from "./util/oracle.js";
|
|
4
5
|
export * from "./util/file.js";
|
|
@@ -6,4 +7,5 @@ export { handlerWebPlSql } from "./handler/plsql/handlerPlSql.js";
|
|
|
6
7
|
export { handlerLogger } from "./handler/handlerLogger.js";
|
|
7
8
|
export { handlerUpload } from "./handler/handlerUpload.js";
|
|
8
9
|
export { handlerMetrics } from "./handler/handlerMetrics.js";
|
|
10
|
+
import oracledb from 'oracledb';
|
|
9
11
|
export { createServer, startServer, loadConfig, startServerConfig } from "./server/server.js";
|
package/types/types.d.ts
CHANGED
|
File without changes
|