web_plsql 1.0.0 → 1.3.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/README.md +108 -13
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/frontend/assets/main-0oxRTIkL.js +147 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.br +0 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.gz +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css +1 -0
- package/dist/frontend/assets/main-TYgLak6O.css.br +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css.gz +0 -0
- package/dist/frontend/index.html +516 -0
- package/dist/frontend/index.html.br +0 -0
- package/dist/frontend/index.html.gz +0 -0
- package/dist/index.d.mts +646 -0
- package/dist/index.mjs +3548 -0
- package/dist/index.mjs.map +1 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
- package/examples/config-apex.js +1 -1
- package/examples/config-docker.js +1 -2
- package/package.json +45 -32
- package/examples/config-native.js +0 -68
- package/src/admin/client/charts.ts +0 -299
- package/src/admin/client/main.js +0 -3
- package/src/admin/client/tailwind.css +0 -41
- package/src/admin/index.html +0 -315
- package/src/admin/js/api.ts +0 -95
- package/src/admin/js/app.ts +0 -306
- package/src/admin/js/eslint.config.js +0 -74
- package/src/admin/js/schemas.ts +0 -153
- package/src/admin/js/templates/config.ts +0 -146
- package/src/admin/js/templates/errorRow.ts +0 -18
- package/src/admin/js/templates/index.ts +0 -3
- package/src/admin/js/templates/poolCard.ts +0 -61
- package/src/admin/js/tsconfig.json +0 -24
- package/src/admin/js/types.ts +0 -223
- package/src/admin/js/ui/theme.ts +0 -93
- package/src/admin/js/ui/views.ts +0 -164
- package/src/admin/js/util/format.ts +0 -27
- package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
- package/src/admin/lib/chart.bundle.js +0 -139
- package/src/admin/style.css +0 -1321
- package/src/bin/load-test.js +0 -202
- package/src/handler/handlerAdmin.js +0 -198
- package/src/handler/handlerLogger.js +0 -21
- package/src/handler/handlerMetrics.js +0 -68
- package/src/handler/handlerUpload.js +0 -26
- package/src/handler/plsql/cgi.js +0 -131
- package/src/handler/plsql/errorPage.js +0 -117
- package/src/handler/plsql/handlerPlSql.js +0 -90
- package/src/handler/plsql/parsePage.js +0 -213
- package/src/handler/plsql/procedure.js +0 -356
- package/src/handler/plsql/procedureError.js +0 -40
- package/src/handler/plsql/procedureNamed.js +0 -268
- package/src/handler/plsql/procedureSanitize.js +0 -262
- package/src/handler/plsql/procedureVariable.js +0 -54
- package/src/handler/plsql/request.js +0 -124
- package/src/handler/plsql/requestError.js +0 -23
- package/src/handler/plsql/sendResponse.js +0 -136
- package/src/handler/plsql/stream.js +0 -28
- package/src/handler/plsql/upload.js +0 -109
- package/src/index.js +0 -20
- package/src/server/config.js +0 -57
- package/src/server/connections.js +0 -40
- package/src/server/server.js +0 -289
- package/src/types.js +0 -150
- package/src/util/cache.js +0 -123
- package/src/util/errorToString.js +0 -24
- package/src/util/file.js +0 -91
- package/src/util/html.js +0 -53
- package/src/util/jsonLogger.js +0 -47
- package/src/util/oracle.js +0 -80
- package/src/util/shutdown.js +0 -44
- package/src/util/trace.js +0 -325
- package/src/util/type.js +0 -6
- package/src/util/util.js +0 -66
- package/src/version.js +0 -5
- package/types/handler/handlerAdmin.d.ts +0 -9
- package/types/handler/handlerLogger.d.ts +0 -2
- package/types/handler/handlerMetrics.d.ts +0 -25
- package/types/handler/handlerUpload.d.ts +0 -7
- package/types/handler/plsql/cgi.d.ts +0 -4
- package/types/handler/plsql/errorPage.d.ts +0 -11
- package/types/handler/plsql/handlerPlSql.d.ts +0 -13
- package/types/handler/plsql/parsePage.d.ts +0 -3
- package/types/handler/plsql/procedure.d.ts +0 -12
- package/types/handler/plsql/procedureError.d.ts +0 -23
- package/types/handler/plsql/procedureNamed.d.ts +0 -14
- package/types/handler/plsql/procedureSanitize.d.ts +0 -11
- package/types/handler/plsql/procedureVariable.d.ts +0 -10
- package/types/handler/plsql/request.d.ts +0 -9
- package/types/handler/plsql/requestError.d.ts +0 -8
- package/types/handler/plsql/sendResponse.d.ts +0 -6
- package/types/handler/plsql/upload.d.ts +0 -5
- package/types/index.d.ts +0 -11
- package/types/server/config.d.ts +0 -2
- package/types/server/server.d.ts +0 -74
- package/types/types.d.ts +0 -328
- package/types/util/cache.d.ts +0 -69
- package/types/util/errorToString.d.ts +0 -1
- package/types/util/file.d.ts +0 -6
- package/types/util/html.d.ts +0 -3
- package/types/util/jsonLogger.d.ts +0 -45
- package/types/util/oracle.d.ts +0 -5
- package/types/util/shutdown.d.ts +0 -2
- package/types/util/trace.d.ts +0 -22
- package/types/util/type.d.ts +0 -1
- package/types/util/util.d.ts +0 -3
- package/types/version.d.ts +0 -1
- /package/{src/admin/favicon.svg → dist/frontend/assets/favicon-mQAM4tVu.svg} +0 -0
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import debugModule from 'debug';
|
|
2
|
-
const debug = debugModule('webplsql:procedureSanitize');
|
|
3
|
-
|
|
4
|
-
import oracledb from 'oracledb';
|
|
5
|
-
import z from 'zod';
|
|
6
|
-
import {RequestError} from './requestError.js';
|
|
7
|
-
import {errorToString} from '../../util/errorToString.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {import('express').Request} Request
|
|
11
|
-
* @typedef {import('express').Response} Response
|
|
12
|
-
* @typedef {import('oracledb').Connection} Connection
|
|
13
|
-
* @typedef {import('oracledb').Result<unknown>} Result
|
|
14
|
-
* @typedef {import('../../types.js').argObjType} argObjType
|
|
15
|
-
* @typedef {import('../../types.js').fileUploadType} fileUploadType
|
|
16
|
-
* @typedef {import('../../types.js').environmentType} environmentType
|
|
17
|
-
* @typedef {import('../../types.js').configPlSqlHandlerType} configPlSqlHandlerType
|
|
18
|
-
* @typedef {import('../../types.js').BindParameterConfig} BindParameterConfig
|
|
19
|
-
* @typedef {import('../../util/cache.js').Cache<string>} ProcedureNameCache
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
const DEFAULT_EXCLUSION_LIST = ['sys.', 'dbms_', 'utl_', 'owa_', 'htp.', 'htf.', 'wpg_docload.', 'ctxsys.', 'mdsys.'];
|
|
23
|
-
|
|
24
|
-
import {Cache} from '../../util/cache.js';
|
|
25
|
-
/** @type {Cache<{valid: boolean}>} */
|
|
26
|
-
const validationFunctionCache = new Cache();
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Resolve the procedure name using dbms_utility.name_resolve.
|
|
30
|
-
*
|
|
31
|
-
* @param {string} procName - The procedure name to resolve.
|
|
32
|
-
* @param {Connection} databaseConnection - The database connection.
|
|
33
|
-
* @param {ProcedureNameCache} procedureNameCache - The procedure name cache.
|
|
34
|
-
* @returns {Promise<string>} - The resolved canonical procedure name (SCHEMA.NAME).
|
|
35
|
-
*/
|
|
36
|
-
const resolveProcedureName = async (procName, databaseConnection, procedureNameCache) => {
|
|
37
|
-
// Check cache
|
|
38
|
-
const cachedName = procedureNameCache.get(procName);
|
|
39
|
-
if (cachedName) {
|
|
40
|
-
debug(`resolveProcedureName: Cache hit for "${procName}" -> "${cachedName}"`);
|
|
41
|
-
return cachedName;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
debug(`resolveProcedureName: Cache miss for "${procName}". Resolving in DB...`);
|
|
45
|
-
|
|
46
|
-
const sql = `
|
|
47
|
-
DECLARE
|
|
48
|
-
l_schema VARCHAR2(128);
|
|
49
|
-
l_part1 VARCHAR2(128);
|
|
50
|
-
l_part2 VARCHAR2(128);
|
|
51
|
-
l_dblink VARCHAR2(128);
|
|
52
|
-
l_part1_type NUMBER;
|
|
53
|
-
l_object_number NUMBER;
|
|
54
|
-
BEGIN
|
|
55
|
-
dbms_utility.name_resolve(
|
|
56
|
-
name => :name,
|
|
57
|
-
context => 1,
|
|
58
|
-
schema => l_schema,
|
|
59
|
-
part1 => l_part1,
|
|
60
|
-
part2 => l_part2,
|
|
61
|
-
dblink => l_dblink,
|
|
62
|
-
part1_type => l_part1_type,
|
|
63
|
-
object_number => l_object_number
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
-- Reconstruct the canonical name
|
|
67
|
-
-- If it's a package procedure: schema.package.procedure
|
|
68
|
-
-- If it's a standalone procedure: schema.procedure
|
|
69
|
-
|
|
70
|
-
IF l_part1 IS NOT NULL THEN
|
|
71
|
-
:resolved := l_schema || '.' || l_part1 || '.' || l_part2;
|
|
72
|
-
ELSE
|
|
73
|
-
:resolved := l_schema || '.' || l_part2;
|
|
74
|
-
END IF;
|
|
75
|
-
END;
|
|
76
|
-
`;
|
|
77
|
-
|
|
78
|
-
const bind = {
|
|
79
|
-
name: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procName},
|
|
80
|
-
resolved: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 400},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const result = await databaseConnection.execute(sql, bind);
|
|
85
|
-
const {resolved} = z.object({resolved: z.string()}).parse(result.outBinds);
|
|
86
|
-
|
|
87
|
-
if (!resolved) {
|
|
88
|
-
throw new RequestError(`Could not resolve procedure name "${procName}"`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
debug(`resolveProcedureName: Resolved "${procName}" -> "${resolved}"`);
|
|
92
|
-
|
|
93
|
-
// Update cache
|
|
94
|
-
procedureNameCache.set(procName, resolved);
|
|
95
|
-
|
|
96
|
-
return resolved;
|
|
97
|
-
} catch (err) {
|
|
98
|
-
debug(`resolveProcedureName: Error resolving "${procName}"`, err);
|
|
99
|
-
// Rethrow as RequestError to indicate 404/403
|
|
100
|
-
throw new RequestError(`Procedure "${procName}" not found or not accessible.\n${errorToString(err)}`);
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Sanitize the procedure name.
|
|
106
|
-
*
|
|
107
|
-
* @param {string} procName - The procedure name.
|
|
108
|
-
* @param {Connection} databaseConnection - The database connection
|
|
109
|
-
* @param {configPlSqlHandlerType} options - the options for the middleware.
|
|
110
|
-
* @param {ProcedureNameCache} procedureNameCache - The procedure name cache.
|
|
111
|
-
* @returns {Promise<string>} Promise resolving to final procedure name.
|
|
112
|
-
*/
|
|
113
|
-
export const sanitizeProcName = async (procName, databaseConnection, options, procedureNameCache) => {
|
|
114
|
-
debug('sanitizeProcName', procName);
|
|
115
|
-
|
|
116
|
-
// make lowercase and trim
|
|
117
|
-
let finalProcName = procName.toLowerCase().trim();
|
|
118
|
-
|
|
119
|
-
// remove special characters (basic sanity check before DB call)
|
|
120
|
-
finalProcName = removeSpecialCharacters(finalProcName);
|
|
121
|
-
|
|
122
|
-
// check for default exclusions
|
|
123
|
-
for (const i of DEFAULT_EXCLUSION_LIST) {
|
|
124
|
-
if (finalProcName.startsWith(i)) {
|
|
125
|
-
const error = `Procedure name "${procName}" is in default exclusion list "${DEFAULT_EXCLUSION_LIST.join(',')}"`;
|
|
126
|
-
debug(error);
|
|
127
|
-
throw new RequestError(error);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// check for custom exclusions
|
|
132
|
-
if (options.exclusionList && options.exclusionList.length > 0) {
|
|
133
|
-
for (const i of options.exclusionList) {
|
|
134
|
-
if (finalProcName.startsWith(i)) {
|
|
135
|
-
const error = `Procedure name "${procName}" is in custom exclusion list "${options.exclusionList.join(',')}"`;
|
|
136
|
-
debug(error);
|
|
137
|
-
throw new RequestError(error);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check request validation function
|
|
143
|
-
if (options.requestValidationFunction && options.requestValidationFunction.length > 0) {
|
|
144
|
-
// Note: We might want to scope this cache too, but for now let's focus on the procedure name cache.
|
|
145
|
-
// The original code used a global cache for this.
|
|
146
|
-
// For strict correctness, we should probably verify the *resolved* name,
|
|
147
|
-
// but legacy behavior checks the input name.
|
|
148
|
-
const valid = await requestValidationFunction(finalProcName, options.requestValidationFunction, databaseConnection);
|
|
149
|
-
if (!valid) {
|
|
150
|
-
const error = `Procedure name "${procName}" is not valid according to the request validation function "${options.requestValidationFunction}"`;
|
|
151
|
-
debug(error);
|
|
152
|
-
throw new RequestError(error);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// NEW: Resolve the procedure name against the database
|
|
157
|
-
// This prevents SQL injection and ensures the procedure exists
|
|
158
|
-
const resolvedName = await resolveProcedureName(finalProcName, databaseConnection, procedureNameCache);
|
|
159
|
-
|
|
160
|
-
return resolvedName;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* @param {string} str - string
|
|
165
|
-
* @returns {string} - string
|
|
166
|
-
*/
|
|
167
|
-
const removeSpecialCharacters = (str) => {
|
|
168
|
-
if (str === null || str === undefined) {
|
|
169
|
-
return '';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const chars = [];
|
|
173
|
-
|
|
174
|
-
for (const c of str) {
|
|
175
|
-
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '.' || c === '_' || c === '#' || c === '$') {
|
|
176
|
-
chars.push(c);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return chars.join('');
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* @param {string} procName - The procedure name.
|
|
185
|
-
* @param {string} requestValidationFunction - The request validation function.
|
|
186
|
-
* @param {Connection} databaseConnection - The database connection
|
|
187
|
-
* @returns {Promise<boolean>} Promise resolving to final procedure name.
|
|
188
|
-
*/
|
|
189
|
-
const loadRequestValid = async (procName, requestValidationFunction, databaseConnection) => {
|
|
190
|
-
/** @type {BindParameterConfig} */
|
|
191
|
-
const bind = {
|
|
192
|
-
proc: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procName},
|
|
193
|
-
valid: {dir: oracledb.BIND_OUT, type: oracledb.NUMBER},
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const SQL = [
|
|
197
|
-
'DECLARE',
|
|
198
|
-
' l_valid NUMBER := 0;',
|
|
199
|
-
'BEGIN',
|
|
200
|
-
` IF (${requestValidationFunction}(:proc)) THEN`,
|
|
201
|
-
' l_valid := 1;',
|
|
202
|
-
' END IF;',
|
|
203
|
-
' :valid := l_valid;',
|
|
204
|
-
'END;',
|
|
205
|
-
].join('\n');
|
|
206
|
-
|
|
207
|
-
/** @type {Result} */
|
|
208
|
-
let result = {};
|
|
209
|
-
try {
|
|
210
|
-
result = await databaseConnection.execute(SQL, bind);
|
|
211
|
-
} catch (err) {
|
|
212
|
-
debug('result', result);
|
|
213
|
-
const message = `Error when validating procedure name "${procName}"\n${SQL}\n${errorToString(err)}`;
|
|
214
|
-
throw new RequestError(message);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
const data = z.strictObject({valid: z.number()}).parse(result.outBinds);
|
|
219
|
-
return data.valid === 1;
|
|
220
|
-
} catch (err) {
|
|
221
|
-
debug('result', result.outBinds);
|
|
222
|
-
const message = `Internal error when parsing ${result.outBinds}\n${errorToString(err)}`;
|
|
223
|
-
throw new Error(message);
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Request validation function.
|
|
229
|
-
*
|
|
230
|
-
* @param {string} procName - The procedure name.
|
|
231
|
-
* @param {string} requestValidationFunction - The request validation function.
|
|
232
|
-
* @param {Connection} databaseConnection - The database connection
|
|
233
|
-
* @returns {Promise<boolean>} Promise resolving to final procedure name.
|
|
234
|
-
*/
|
|
235
|
-
const requestValidationFunction = async (procName, requestValidationFunction, databaseConnection) => {
|
|
236
|
-
debug('requestValidationFunction', procName, requestValidationFunction);
|
|
237
|
-
|
|
238
|
-
// calculate the key
|
|
239
|
-
const key = procName.toLowerCase();
|
|
240
|
-
|
|
241
|
-
// lookup in the cache
|
|
242
|
-
const cacheEntry = validationFunctionCache.get(key);
|
|
243
|
-
|
|
244
|
-
// if we found the procedure in the cache, we return it (hitCount already incremented by get)
|
|
245
|
-
if (cacheEntry !== undefined) {
|
|
246
|
-
if (debug.enabled) {
|
|
247
|
-
debug(`requestValidationFunction: procedure "${procName}" found in cache`);
|
|
248
|
-
}
|
|
249
|
-
return cacheEntry.valid;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// load from database
|
|
253
|
-
if (debug.enabled) {
|
|
254
|
-
debug(`requestValidationFunction: procedure "${procName}" not found in cache and must be loaded`);
|
|
255
|
-
}
|
|
256
|
-
const valid = await loadRequestValid(procName, requestValidationFunction, databaseConnection);
|
|
257
|
-
|
|
258
|
-
// add to the cache
|
|
259
|
-
validationFunctionCache.set(key, {valid});
|
|
260
|
-
|
|
261
|
-
return valid;
|
|
262
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Invoke the Oracle procedure and return the raw content of the page
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import debugModule from 'debug';
|
|
6
|
-
const debug = debugModule('webplsql:procedureVariable');
|
|
7
|
-
|
|
8
|
-
import oracledb from 'oracledb';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {import('express').Request} Request
|
|
12
|
-
* @typedef {import('oracledb').Connection} Connection
|
|
13
|
-
* @typedef {import('oracledb').Result<unknown>} Result
|
|
14
|
-
* @typedef {import('../../types.js').configPlSqlHandlerType} configPlSqlHandlerType
|
|
15
|
-
* @typedef {import('../../types.js').argObjType} argObjType
|
|
16
|
-
* @typedef {import('../../types.js').BindParameterConfig} BindParameterConfig
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get the sql statement and bindings for the procedure to execute for a variable number of arguments
|
|
21
|
-
* @param {Request} _req - The req object represents the HTTP request. (only used for debugging)
|
|
22
|
-
* @param {string} procName - The procedure to execute
|
|
23
|
-
* @param {argObjType} argObj - The arguments to pass to the procedure
|
|
24
|
-
* @returns {{sql: string; bind: BindParameterConfig}} - The SQL statement and bindings for the procedure to execute
|
|
25
|
-
*/
|
|
26
|
-
export const getProcedureVariable = (_req, procName, argObj) => {
|
|
27
|
-
if (debug.enabled) {
|
|
28
|
-
debug(`getProcedureVariable: ${procName} arguments=`, argObj);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const names = [];
|
|
32
|
-
const values = [];
|
|
33
|
-
|
|
34
|
-
for (const key in argObj) {
|
|
35
|
-
const value = argObj[key];
|
|
36
|
-
if (typeof value === 'string') {
|
|
37
|
-
names.push(key);
|
|
38
|
-
values.push(value);
|
|
39
|
-
} else if (Array.isArray(value)) {
|
|
40
|
-
value.forEach((item) => {
|
|
41
|
-
names.push(key);
|
|
42
|
-
values.push(item);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
sql: `${procName}(:argnames, :argvalues)`,
|
|
49
|
-
bind: {
|
|
50
|
-
argnames: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: names},
|
|
51
|
-
argvalues: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: values},
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
};
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Process the http request
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import debugModule from 'debug';
|
|
6
|
-
const debug = debugModule('webplsql:request');
|
|
7
|
-
|
|
8
|
-
import util from 'node:util';
|
|
9
|
-
import {invokeProcedure} from './procedure.js';
|
|
10
|
-
import {getCGI} from './cgi.js';
|
|
11
|
-
import {getFiles} from './upload.js';
|
|
12
|
-
import {RequestError} from './requestError.js';
|
|
13
|
-
import {isStringOrArrayOfString} from '../../util/type.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {import('express').Request} Request
|
|
17
|
-
* @typedef {import('express').Response} Response
|
|
18
|
-
* @typedef {import('oracledb').Pool} Pool
|
|
19
|
-
* @typedef {import('oracledb').Connection} Connection
|
|
20
|
-
* @typedef {import('../../types.js').argObjType} argObjType
|
|
21
|
-
* @typedef {import('../../types.js').configPlSqlHandlerType} configPlSqlHandlerType
|
|
22
|
-
* @typedef {import('../../util/cache.js').Cache<string>} ProcedureNameCache
|
|
23
|
-
* @typedef {import('../../util/cache.js').Cache<import('./procedureNamed.js').argsType>} ArgumentCache
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Execute the request
|
|
28
|
-
*
|
|
29
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
30
|
-
* @param {Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
|
|
31
|
-
* @param {configPlSqlHandlerType} options - the options for the middleware.
|
|
32
|
-
* @param {Pool} connectionPool - The connection pool.
|
|
33
|
-
* @param {ProcedureNameCache} procedureNameCache - The procedure name cache.
|
|
34
|
-
* @param {ArgumentCache} argumentCache - The argument cache.
|
|
35
|
-
* @returns {Promise<void>} - Promise resolving to th page
|
|
36
|
-
*/
|
|
37
|
-
export const processRequest = async (req, res, options, connectionPool, procedureNameCache, argumentCache) => {
|
|
38
|
-
debug('processRequest: ENTER');
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
if (Array.isArray(req.params.name)) {
|
|
42
|
-
console.warn(`processRequest: WARNING: the req.params.name is not a string but an array of string: ${req.params.name.join(',')}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// open database connection
|
|
46
|
-
const connection = await connectionPool.getConnection();
|
|
47
|
-
|
|
48
|
-
// Get the CGI
|
|
49
|
-
const cgiObj = getCGI(req, options.documentTable ?? '', options.cgi ?? {});
|
|
50
|
-
debug('processRequest: cgiObj=', cgiObj);
|
|
51
|
-
|
|
52
|
-
// Does the request contain any files
|
|
53
|
-
const filesToUpload = getFiles(req);
|
|
54
|
-
debug('processRequest: filesToUpload=', filesToUpload);
|
|
55
|
-
|
|
56
|
-
// Add the query properties
|
|
57
|
-
/** @type {argObjType} */
|
|
58
|
-
const argObj = {};
|
|
59
|
-
Object.assign(argObj, req.query);
|
|
60
|
-
|
|
61
|
-
// For add the files that must be uploaded, we now copy the actual filename to the appropriate parameter to the invoked procedure.
|
|
62
|
-
filesToUpload.reduce((aggregator, file) => {
|
|
63
|
-
aggregator[file.fieldname] = file.filename;
|
|
64
|
-
return aggregator;
|
|
65
|
-
}, argObj);
|
|
66
|
-
|
|
67
|
-
// Does the request contain a body
|
|
68
|
-
Object.assign(argObj, normalizeBody(req));
|
|
69
|
-
debug('processRequest: argObj=', argObj);
|
|
70
|
-
|
|
71
|
-
// invoke the Oracle procedure and get the page contenst
|
|
72
|
-
await invokeProcedure(req, res, argObj, cgiObj, filesToUpload, options, connection, procedureNameCache, argumentCache);
|
|
73
|
-
|
|
74
|
-
// transaction mode
|
|
75
|
-
if (options.transactionMode === 'rollback') {
|
|
76
|
-
debug('transactionMode: rollback');
|
|
77
|
-
await connection.rollback();
|
|
78
|
-
} else if (typeof options.transactionMode === 'function') {
|
|
79
|
-
debug('transactionMode: callback');
|
|
80
|
-
const procName = Array.isArray(req.params.name) ? req.params.name[0] : req.params.name;
|
|
81
|
-
const result = options.transactionMode(connection, procName ?? '');
|
|
82
|
-
debug('transactionMode: callback restult', result);
|
|
83
|
-
if (result && typeof result.then === 'function') {
|
|
84
|
-
await result;
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
debug('transactionMode: commit');
|
|
88
|
-
await connection.commit();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// close database connection
|
|
92
|
-
await connection.release();
|
|
93
|
-
|
|
94
|
-
debug('processRequest: EXIT');
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Normalize the body by making sure that only "simple" parameters and no nested objects are submitted
|
|
99
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
100
|
-
* @returns {Record<string, string | string[]>} - The normalized body.
|
|
101
|
-
*/
|
|
102
|
-
const normalizeBody = (req) => {
|
|
103
|
-
/** @type {Record<string, string | string[]>} */
|
|
104
|
-
const args = {};
|
|
105
|
-
|
|
106
|
-
/* v8 ignore else - body validation */
|
|
107
|
-
if (typeof req.body === 'object' && req.body !== null) {
|
|
108
|
-
for (const key in req.body) {
|
|
109
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
110
|
-
const value = req.body[key];
|
|
111
|
-
/* v8 ignore else - type validation */
|
|
112
|
-
if (isStringOrArrayOfString(value)) {
|
|
113
|
-
args[key] = value;
|
|
114
|
-
} else {
|
|
115
|
-
/* v8 ignore next - invalid body type */
|
|
116
|
-
throw new RequestError(
|
|
117
|
-
`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})}`,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return args;
|
|
124
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* RequestError
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class RequestError extends Error {
|
|
6
|
-
/** @type {Date} */
|
|
7
|
-
timestamp;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} message - The error message.
|
|
11
|
-
*/
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
|
|
15
|
-
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
16
|
-
if (Error.captureStackTrace) {
|
|
17
|
-
Error.captureStackTrace(this, RequestError);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Custom debugging information
|
|
21
|
-
this.timestamp = new Date();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Page the raw page content and return the content to the client
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import debugModule from 'debug';
|
|
6
|
-
const debug = debugModule('webplsql:sendResponse');
|
|
7
|
-
|
|
8
|
-
import stream from 'node:stream';
|
|
9
|
-
import {getBlock} from '../../util/trace.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {import('express').Request} Request
|
|
13
|
-
* @typedef {import('express').Response} Response
|
|
14
|
-
* @typedef {import('express').CookieOptions} CookieOptions
|
|
15
|
-
* @typedef {import('../../types.js').pageType} pageType
|
|
16
|
-
* @typedef {import('../../types.js').cookieType} cookieType
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Send "default" response to the browser
|
|
21
|
-
* @param {Request} _req - The req object represents the HTTP request.
|
|
22
|
-
* @param {Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
|
|
23
|
-
* @param {pageType} page - The page to send.
|
|
24
|
-
* @returns {Promise<void>}
|
|
25
|
-
*/
|
|
26
|
-
export const sendResponse = async (_req, res, page) => {
|
|
27
|
-
/** @type {string[]} */
|
|
28
|
-
const debugText = [];
|
|
29
|
-
|
|
30
|
-
// Send the "cookies"
|
|
31
|
-
page.head.cookies.forEach((cookie) => {
|
|
32
|
-
if (debug.enabled) {
|
|
33
|
-
debugText.push(`res.cookie: name="${cookie.name}" value="${cookie.value}" options=${JSON.stringify(cookie.options)}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// If there is a "redirectLocation" header, we immediately redirect and return
|
|
40
|
-
if (typeof page.head.redirectLocation === 'string' && page.head.redirectLocation.length > 0) {
|
|
41
|
-
if (debug.enabled) {
|
|
42
|
-
debugText.push(`res.redirect(302, "${page.head.redirectLocation}")`);
|
|
43
|
-
debug(getBlock('RESPONSE', debugText.join('\n')));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
res.redirect(302, page.head.redirectLocation);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Send all the "otherHeaders"
|
|
51
|
-
for (const key in page.head.otherHeaders) {
|
|
52
|
-
if (debug.enabled) {
|
|
53
|
-
debugText.push(`res.set("${key}", "${page.head.otherHeaders[key]}")`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
res.set(key, page.head.otherHeaders[key]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// If this is a file download, we eventually set the "Content-Type" and the file content and then return.
|
|
60
|
-
if (page.file.fileType === 'B' || page.file.fileType === 'F') {
|
|
61
|
-
/** @type {Record<string, string>} */
|
|
62
|
-
const headers = {};
|
|
63
|
-
|
|
64
|
-
if (typeof page.head.contentType === 'string' && page.head.contentType.length > 0) {
|
|
65
|
-
headers['Content-Type'] = page.head.contentType;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (typeof page.file.fileSize === 'number' && page.file.fileSize > 0) {
|
|
69
|
-
headers['Content-Length'] = page.file.fileSize.toString();
|
|
70
|
-
}
|
|
71
|
-
|
|
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);
|
|
77
|
-
}
|
|
78
|
-
|
|
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
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Is the a "contentType" header
|
|
110
|
-
if (typeof page.head.contentType === 'string' && page.head.contentType.length > 0) {
|
|
111
|
-
if (debug.enabled) {
|
|
112
|
-
debugText.push(`res.set("Content-Type", "${page.head.contentType}")`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
res.set('Content-Type', page.head.contentType);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// If we have a "Status" header, we send the header and then return.
|
|
119
|
-
if (typeof page.head.statusCode === 'number') {
|
|
120
|
-
if (debug.enabled) {
|
|
121
|
-
debugText.push(`res.status(page.head.statusCode).send("${page.head.statusDescription}")`);
|
|
122
|
-
debug(getBlock('RESPONSE', debugText.join('\n')));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
res.status(page.head.statusCode).send(page.head.statusDescription);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Send the body
|
|
130
|
-
if (debug.enabled) {
|
|
131
|
-
debugText.push(`${'-'.repeat(60)}\n${page.body}`);
|
|
132
|
-
debug(getBlock('RESPONSE', debugText.join('\n')));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
res.send(page.body);
|
|
136
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import stream from 'node:stream';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Convert a readable stream to a buffer.
|
|
5
|
-
*
|
|
6
|
-
* @param {stream.Readable} readable - The readable stream.
|
|
7
|
-
* @returns {Promise<Buffer>} The buffer.
|
|
8
|
-
*/
|
|
9
|
-
export const streamToBuffer = async (readable) => {
|
|
10
|
-
return new Promise((resolve, reject) => {
|
|
11
|
-
/** @type {Buffer[]} */
|
|
12
|
-
const buffers = [];
|
|
13
|
-
|
|
14
|
-
readable.on('data', (buffer) => {
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
16
|
-
buffers.push(buffer);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
readable.on('end', () => {
|
|
20
|
-
resolve(Buffer.concat(buffers));
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
readable.on('error', (err) => {
|
|
24
|
-
/* v8 ignore next - error handler */
|
|
25
|
-
reject(err);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
};
|