web_plsql 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -8
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/frontend/assets/main-C8GKnEhz.js +147 -0
- package/dist/frontend/assets/main-C8GKnEhz.js.br +0 -0
- package/dist/frontend/assets/main-C8GKnEhz.js.gz +0 -0
- package/dist/frontend/assets/main-bU4x_yEW.css +1 -0
- package/dist/frontend/assets/main-bU4x_yEW.css.br +0 -0
- package/dist/frontend/assets/main-bU4x_yEW.css.gz +0 -0
- package/{src/admin → dist/frontend}/index.html +12 -25
- 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 +3559 -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 +48 -32
- package/examples/config-native.js +0 -68
- package/src/admin/client/charts.ts +0 -598
- package/src/admin/client/main.js +0 -3
- package/src/admin/client/tailwind.css +0 -41
- package/src/admin/js/api.ts +0 -170
- package/src/admin/js/app.ts +0 -614
- package/src/admin/js/eslint.config.js +0 -74
- package/src/admin/js/schemas.ts +0 -215
- package/src/admin/js/templates/config.ts +0 -147
- package/src/admin/js/templates/errorRow.ts +0 -21
- package/src/admin/js/templates/index.ts +0 -4
- package/src/admin/js/templates/poolCard.ts +0 -61
- package/src/admin/js/templates/traceRow.ts +0 -24
- package/src/admin/js/tsconfig.json +0 -24
- package/src/admin/js/types.ts +0 -336
- package/src/admin/js/ui/components.ts +0 -44
- package/src/admin/js/ui/table.ts +0 -173
- package/src/admin/js/ui/theme.ts +0 -93
- package/src/admin/js/ui/views.ts +0 -427
- package/src/admin/js/util/format.ts +0 -46
- package/src/admin/js/util/metrics.ts +0 -24
- package/src/admin/lib/chart.bundle.css +0 -1
- package/src/admin/lib/chart.bundle.js +0 -146
- package/src/admin/style.css +0 -1437
- package/src/bin/load-test.js +0 -202
- package/src/handler/handlerAdmin.js +0 -293
- package/src/handler/handlerLogger.js +0 -21
- package/src/handler/handlerUpload.js +0 -26
- package/src/handler/plsql/cgi.js +0 -131
- package/src/handler/plsql/errorPage.js +0 -113
- package/src/handler/plsql/handlerPlSql.js +0 -90
- package/src/handler/plsql/owaPageStream.js +0 -93
- package/src/handler/plsql/parsePage.js +0 -213
- package/src/handler/plsql/procedure.js +0 -426
- package/src/handler/plsql/procedureError.js +0 -40
- package/src/handler/plsql/procedureNamed.js +0 -282
- package/src/handler/plsql/procedureSanitize.js +0 -286
- package/src/handler/plsql/procedureVariable.js +0 -56
- package/src/handler/plsql/request.js +0 -124
- package/src/handler/plsql/requestError.js +0 -23
- package/src/handler/plsql/sendResponse.js +0 -174
- package/src/handler/plsql/stream.js +0 -28
- package/src/handler/plsql/upload.js +0 -109
- package/src/index.js +0 -19
- package/src/server/adminContext.js +0 -23
- package/src/server/config.js +0 -57
- package/src/server/connections.js +0 -40
- package/src/server/server.js +0 -304
- 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/statsManager.js +0 -368
- package/src/util/trace.js +0 -326
- package/src/util/traceManager.js +0 -68
- package/src/util/type.js +0 -6
- package/src/util/util.js +0 -66
- package/src/version.js +0 -5
- package/types/admin/js/schemas.d.ts +0 -384
- package/types/admin/js/types.d.ts +0 -312
- package/types/handler/handlerAdmin.d.ts +0 -79
- package/types/handler/handlerLogger.d.ts +0 -2
- 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/owaPageStream.d.ts +0 -28
- package/types/handler/plsql/parsePage.d.ts +0 -3
- package/types/handler/plsql/procedure.d.ts +0 -13
- 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 -10
- package/types/server/adminContext.d.ts +0 -17
- package/types/server/config.d.ts +0 -2
- package/types/server/server.d.ts +0 -57
- 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/statsManager.d.ts +0 -395
- package/types/util/trace.d.ts +0 -22
- package/types/util/traceManager.d.ts +0 -35
- 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,282 +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:procedureNamed');
|
|
7
|
-
|
|
8
|
-
import oracledb from 'oracledb';
|
|
9
|
-
import z from 'zod';
|
|
10
|
-
import {RequestError} from './requestError.js';
|
|
11
|
-
import {errorToString} from '../../util/errorToString.js';
|
|
12
|
-
import {stringToNumber} from '../../util/util.js';
|
|
13
|
-
import {toTable, warningMessage} from '../../util/trace.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {import('express').Request} Request
|
|
17
|
-
* @typedef {import('oracledb').Connection} Connection
|
|
18
|
-
* @typedef {import('oracledb').Result<unknown>} Result
|
|
19
|
-
* @typedef {import('../../types.js').configPlSqlHandlerType} configPlSqlHandlerType
|
|
20
|
-
* @typedef {import('../../types.js').argObjType} argObjType
|
|
21
|
-
* @typedef {import('../../types.js').BindParameterConfig} BindParameterConfig
|
|
22
|
-
* @typedef {import('../../types.js').BindParameter} BindParameter
|
|
23
|
-
* @typedef {Record<string, string>} argsType
|
|
24
|
-
* @typedef {import('../../util/cache.js').Cache<argsType>} ArgumentCache
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
const SQL_GET_ARGUMENT = [
|
|
28
|
-
'DECLARE',
|
|
29
|
-
' schemaName VARCHAR2(32767);',
|
|
30
|
-
' part1 VARCHAR2(32767);',
|
|
31
|
-
' part2 VARCHAR2(32767);',
|
|
32
|
-
' dblink VARCHAR2(32767);',
|
|
33
|
-
' objectType NUMBER;',
|
|
34
|
-
' objectID NUMBER;',
|
|
35
|
-
'BEGIN',
|
|
36
|
-
' dbms_utility.name_resolve(name=>UPPER(:name), context=>1, schema=>schemaName, part1=>part1, part2=>part2, dblink=>dblink, part1_type=>objectType, object_number=>objectID);',
|
|
37
|
-
' IF (part1 IS NOT NULL) THEN',
|
|
38
|
-
' SELECT argument_name, data_type BULK COLLECT INTO :names, :types FROM all_arguments WHERE owner = schemaName AND package_name = part1 AND object_name = part2 AND argument_name IS NOT NULL ORDER BY overload, sequence;',
|
|
39
|
-
' ELSE',
|
|
40
|
-
' SELECT argument_name, data_type BULK COLLECT INTO :names, :types FROM all_arguments WHERE owner = schemaName AND package_name IS NULL AND object_name = part2 AND argument_name IS NOT NULL ORDER BY overload, sequence;',
|
|
41
|
-
' END IF;',
|
|
42
|
-
'END;',
|
|
43
|
-
].join('\n');
|
|
44
|
-
|
|
45
|
-
const DATA_TYPES = Object.freeze({
|
|
46
|
-
VARCHAR2: 'VARCHAR2',
|
|
47
|
-
CHAR: 'CHAR',
|
|
48
|
-
BINARY_INTEGER: 'BINARY_INTEGER',
|
|
49
|
-
NUMBER: 'NUMBER',
|
|
50
|
-
DATE: 'DATE',
|
|
51
|
-
CLOB: 'CLOB',
|
|
52
|
-
PL_SQL_TABLE: 'PL/SQL TABLE',
|
|
53
|
-
// PL/SQL BOOLEAN
|
|
54
|
-
// PL/SQL RECORD
|
|
55
|
-
// OBJECT
|
|
56
|
-
// TABLE
|
|
57
|
-
// BLOB
|
|
58
|
-
// RAW
|
|
59
|
-
// VARRAY
|
|
60
|
-
// REF CURSOR
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Retrieve the argument types for a given procedure to be executed.
|
|
65
|
-
* This is important because if the procedure is defined to take a PL/SQL indexed table,
|
|
66
|
-
* we must provise a table, even if there is only one argument to be submitted.
|
|
67
|
-
* @param {string} procedure - The procedure
|
|
68
|
-
* @param {Connection} databaseConnection - The database connection
|
|
69
|
-
* @returns {Promise<argsType>} - The argument types
|
|
70
|
-
*/
|
|
71
|
-
const loadArguments = async (procedure, databaseConnection) => {
|
|
72
|
-
const MAX_PARAMETER_NUMBER = 1000;
|
|
73
|
-
|
|
74
|
-
/** @type {BindParameterConfig} */
|
|
75
|
-
const bind = {
|
|
76
|
-
name: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procedure},
|
|
77
|
-
names: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER},
|
|
78
|
-
types: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 60, maxArraySize: MAX_PARAMETER_NUMBER},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/** @type {Result} */
|
|
82
|
-
let result = {};
|
|
83
|
-
try {
|
|
84
|
-
result = await databaseConnection.execute(SQL_GET_ARGUMENT, bind);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
/* v8 ignore start */
|
|
87
|
-
debug('result', result);
|
|
88
|
-
/* v8 ignore stop */
|
|
89
|
-
|
|
90
|
-
const message = `Error when retrieving arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
|
|
91
|
-
throw new RequestError(message);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** @type {{names: (string | null)[], types: (string | null)[]}} */
|
|
95
|
-
let data;
|
|
96
|
-
try {
|
|
97
|
-
data = z
|
|
98
|
-
.object({
|
|
99
|
-
names: z.array(z.string().nullable()),
|
|
100
|
-
types: z.array(z.string().nullable()),
|
|
101
|
-
})
|
|
102
|
-
.parse(result.outBinds);
|
|
103
|
-
} catch (err) {
|
|
104
|
-
/* v8 ignore start */
|
|
105
|
-
debug('result.outBinds', result.outBinds);
|
|
106
|
-
/* v8 ignore stop */
|
|
107
|
-
|
|
108
|
-
const message = `Error when decoding arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
|
|
109
|
-
throw new RequestError(message);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (data.names.length !== data.types.length) {
|
|
113
|
-
throw new RequestError('Error when decoding arguments. The number of names and types does not match');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** @type {Record<string, string>} */
|
|
117
|
-
const argTypes = {};
|
|
118
|
-
for (let i = 0; i < data.names.length; i++) {
|
|
119
|
-
const name = data.names[i];
|
|
120
|
-
const type = data.types[i];
|
|
121
|
-
if (name && type) {
|
|
122
|
-
argTypes[name.toLowerCase()] = type;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return argTypes;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Find the argument types for a given procedure to be executed.
|
|
131
|
-
* As the arguments are cached, we first look up the cache and only if not yet available we load them.
|
|
132
|
-
* @param {string} procedure - The procedure
|
|
133
|
-
* @param {Connection} databaseConnection - The database connection
|
|
134
|
-
* @param {ArgumentCache} argumentCache - The argument cache.
|
|
135
|
-
* @returns {Promise<argsType>} - The argument types
|
|
136
|
-
*/
|
|
137
|
-
const findArguments = async (procedure, databaseConnection, argumentCache) => {
|
|
138
|
-
const key = procedure.toUpperCase();
|
|
139
|
-
|
|
140
|
-
// lookup in the cache
|
|
141
|
-
const cachedArgs = argumentCache.get(key);
|
|
142
|
-
|
|
143
|
-
// if we found the procedure in the cache, we return it
|
|
144
|
-
if (cachedArgs) {
|
|
145
|
-
/* v8 ignore start */
|
|
146
|
-
if (debug.enabled) {
|
|
147
|
-
debug(`findArguments: procedure "${procedure}" found in cache`);
|
|
148
|
-
}
|
|
149
|
-
/* v8 ignore stop */
|
|
150
|
-
|
|
151
|
-
return cachedArgs;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// load from database
|
|
155
|
-
/* v8 ignore start */
|
|
156
|
-
if (debug.enabled) {
|
|
157
|
-
debug(`findArguments: procedure "${procedure}" not found in cache and must be loaded`);
|
|
158
|
-
}
|
|
159
|
-
/* v8 ignore stop */
|
|
160
|
-
|
|
161
|
-
const args = await loadArguments(procedure, databaseConnection);
|
|
162
|
-
|
|
163
|
-
// add to the cache
|
|
164
|
-
argumentCache.set(key, args);
|
|
165
|
-
|
|
166
|
-
return args;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Get the bindling for an argument.
|
|
171
|
-
* @param {string} argName - The argument name.
|
|
172
|
-
* @param {unknown} argValue - The argument value.
|
|
173
|
-
* @param {string} argType - The argument type.
|
|
174
|
-
* @returns {BindParameter} - The binding.
|
|
175
|
-
*/
|
|
176
|
-
export const getBinding = (argName, argValue, argType) => {
|
|
177
|
-
if (argType === DATA_TYPES.VARCHAR2 || argType === DATA_TYPES.CHAR) {
|
|
178
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_VARCHAR, val: argValue};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (argType === DATA_TYPES.CLOB) {
|
|
182
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_CLOB, val: argValue};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (argType === DATA_TYPES.NUMBER || argType === DATA_TYPES.BINARY_INTEGER) {
|
|
186
|
-
const value = stringToNumber(argValue);
|
|
187
|
-
if (value === null) {
|
|
188
|
-
throw new Error(`Error in named parameter "${argName}": invalid value "${argValue}" for type "${argType}"`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_NUMBER, val: value};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (argType === DATA_TYPES.DATE) {
|
|
195
|
-
if (typeof argValue !== 'string') {
|
|
196
|
-
throw new Error(`Error in named parameter "${argName}": invalid value "${argValue}" for type "${argType}"`);
|
|
197
|
-
}
|
|
198
|
-
const value = new Date(argValue);
|
|
199
|
-
if (Number.isNaN(value.getTime())) {
|
|
200
|
-
throw new Error(`Error in named parameter "${argName}": invalid value "${argValue}" for type "${argType}"`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_VARCHAR, val: value};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (argType === DATA_TYPES.PL_SQL_TABLE || Array.isArray(argValue)) {
|
|
207
|
-
const value = typeof argValue === 'string' ? [argValue] : argValue;
|
|
208
|
-
|
|
209
|
-
return {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_DATE, val: value};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
throw new Error(`Error in named parameter "${argName}": invalid binding type "${argType}"`);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Get binding table for tracing.
|
|
217
|
-
* @param {argObjType} argObj - The arguments to pass to the procedure
|
|
218
|
-
* @param {argsType} argTypes - The argument types.
|
|
219
|
-
* @returns {string} - The text.
|
|
220
|
-
*/
|
|
221
|
-
const inspectBindings = (argObj, argTypes) => {
|
|
222
|
-
const rows = Object.entries(argObj).map(([key, value]) => {
|
|
223
|
-
return [key, value.toString(), typeof value, argTypes[key.toLowerCase()] ?? 'unknown'];
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const {text} = toTable(['id', 'value', 'value type', 'argument type'], rows);
|
|
227
|
-
|
|
228
|
-
return text;
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Get the sql statement and bindings for the procedure to execute for a fixed number of arguments
|
|
233
|
-
* @param {Request} req - The req object represents the HTTP request. (only used for debugging)
|
|
234
|
-
* @param {string} procName - The procedure to execute
|
|
235
|
-
* @param {argObjType} argObj - The arguments to pass to the procedure
|
|
236
|
-
* @param {Connection} databaseConnection - The database connection
|
|
237
|
-
* @param {ArgumentCache} argumentCache - The argument cache.
|
|
238
|
-
* @returns {Promise<{sql: string; bind: BindParameterConfig}>} - The SQL statement and bindings for the procedure to execute
|
|
239
|
-
*/
|
|
240
|
-
export const getProcedureNamed = async (req, procName, argObj, databaseConnection, argumentCache) => {
|
|
241
|
-
debug(`getProcedureNamed: ${procName} arguments=`, argObj);
|
|
242
|
-
|
|
243
|
-
// get the types of the arguments
|
|
244
|
-
const argTypes = await findArguments(procName, databaseConnection, argumentCache);
|
|
245
|
-
|
|
246
|
-
/** @type {string[]} */
|
|
247
|
-
const sqlParameter = [];
|
|
248
|
-
|
|
249
|
-
/** @type {BindParameterConfig} */
|
|
250
|
-
const bindings = {};
|
|
251
|
-
|
|
252
|
-
// bindings for the statement
|
|
253
|
-
for (const key in argObj) {
|
|
254
|
-
const parameterName = `p_${key}`;
|
|
255
|
-
const argValue = argObj[key];
|
|
256
|
-
const argType = argTypes[key.toLowerCase()];
|
|
257
|
-
/** @type {BindParameter} */
|
|
258
|
-
let bind = {dir: oracledb.BIND_IN, type: oracledb.DB_TYPE_VARCHAR, val: argValue};
|
|
259
|
-
|
|
260
|
-
if (argType) {
|
|
261
|
-
bind = getBinding(key, argValue, argType);
|
|
262
|
-
} else {
|
|
263
|
-
const text = inspectBindings(argObj, argTypes);
|
|
264
|
-
warningMessage({type: 'warning', message: `Error in named parameter "${key}": invalid binding type "${argType}"\n\n${text}`, req});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
sqlParameter.push(`${key}=>:${parameterName}`);
|
|
268
|
-
bindings[parameterName] = bind;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// select statement
|
|
272
|
-
const sql = `${procName}(${sqlParameter.join(', ')})`;
|
|
273
|
-
|
|
274
|
-
/* v8 ignore start */
|
|
275
|
-
if (debug.enabled) {
|
|
276
|
-
debug(sql);
|
|
277
|
-
debug(inspectBindings(argObj, argTypes));
|
|
278
|
-
}
|
|
279
|
-
/* v8 ignore stop */
|
|
280
|
-
|
|
281
|
-
return {sql, bind: bindings};
|
|
282
|
-
};
|
|
@@ -1,286 +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
|
-
/* v8 ignore start */
|
|
99
|
-
debug(`resolveProcedureName: Error resolving "${procName}"`, err);
|
|
100
|
-
/* v8 ignore stop */
|
|
101
|
-
|
|
102
|
-
// Rethrow as RequestError to indicate 404/403
|
|
103
|
-
throw new RequestError(`Procedure "${procName}" not found or not accessible.\n${errorToString(err)}`);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Sanitize the procedure name.
|
|
109
|
-
*
|
|
110
|
-
* @param {string} procName - The procedure name.
|
|
111
|
-
* @param {Connection} databaseConnection - The database connection
|
|
112
|
-
* @param {configPlSqlHandlerType} options - the options for the middleware.
|
|
113
|
-
* @param {ProcedureNameCache} procedureNameCache - The procedure name cache.
|
|
114
|
-
* @returns {Promise<string>} Promise resolving to final procedure name.
|
|
115
|
-
*/
|
|
116
|
-
export const sanitizeProcName = async (procName, databaseConnection, options, procedureNameCache) => {
|
|
117
|
-
debug('sanitizeProcName', procName);
|
|
118
|
-
|
|
119
|
-
// make lowercase and trim
|
|
120
|
-
let finalProcName = procName.toLowerCase().trim();
|
|
121
|
-
|
|
122
|
-
// remove special characters (basic sanity check before DB call)
|
|
123
|
-
finalProcName = removeSpecialCharacters(finalProcName);
|
|
124
|
-
|
|
125
|
-
// check for default exclusions
|
|
126
|
-
for (const i of DEFAULT_EXCLUSION_LIST) {
|
|
127
|
-
if (finalProcName.startsWith(i)) {
|
|
128
|
-
const error = `Procedure name "${procName}" is in default exclusion list "${DEFAULT_EXCLUSION_LIST.join(',')}"`;
|
|
129
|
-
/* v8 ignore start */
|
|
130
|
-
debug(error);
|
|
131
|
-
/* v8 ignore stop */
|
|
132
|
-
|
|
133
|
-
throw new RequestError(error);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// check for custom exclusions
|
|
138
|
-
if (options.exclusionList && options.exclusionList.length > 0) {
|
|
139
|
-
for (const i of options.exclusionList) {
|
|
140
|
-
if (finalProcName.startsWith(i)) {
|
|
141
|
-
const error = `Procedure name "${procName}" is in custom exclusion list "${options.exclusionList.join(',')}"`;
|
|
142
|
-
/* v8 ignore start */
|
|
143
|
-
debug(error);
|
|
144
|
-
/* v8 ignore stop */
|
|
145
|
-
|
|
146
|
-
throw new RequestError(error);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Check request validation function
|
|
152
|
-
if (options.requestValidationFunction && options.requestValidationFunction.length > 0) {
|
|
153
|
-
// Note: We might want to scope this cache too, but for now let's focus on the procedure name cache.
|
|
154
|
-
// The original code used a global cache for this.
|
|
155
|
-
// For strict correctness, we should probably verify the *resolved* name,
|
|
156
|
-
// but legacy behavior checks the input name.
|
|
157
|
-
const valid = await requestValidationFunction(finalProcName, options.requestValidationFunction, databaseConnection);
|
|
158
|
-
if (!valid) {
|
|
159
|
-
const error = `Procedure name "${procName}" is not valid according to the request validation function "${options.requestValidationFunction}"`;
|
|
160
|
-
/* v8 ignore start */
|
|
161
|
-
debug(error);
|
|
162
|
-
/* v8 ignore stop */
|
|
163
|
-
|
|
164
|
-
throw new RequestError(error);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// NEW: Resolve the procedure name against the database
|
|
169
|
-
// This prevents SQL injection and ensures the procedure exists
|
|
170
|
-
const resolvedName = await resolveProcedureName(finalProcName, databaseConnection, procedureNameCache);
|
|
171
|
-
|
|
172
|
-
return resolvedName;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* @param {string} str - string
|
|
177
|
-
* @returns {string} - string
|
|
178
|
-
*/
|
|
179
|
-
const removeSpecialCharacters = (str) => {
|
|
180
|
-
if (str === null || str === undefined) {
|
|
181
|
-
return '';
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const chars = [];
|
|
185
|
-
|
|
186
|
-
for (const c of str) {
|
|
187
|
-
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '.' || c === '_' || c === '#' || c === '$') {
|
|
188
|
-
chars.push(c);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return chars.join('');
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* @param {string} procName - The procedure name.
|
|
197
|
-
* @param {string} requestValidationFunction - The request validation function.
|
|
198
|
-
* @param {Connection} databaseConnection - The database connection
|
|
199
|
-
* @returns {Promise<boolean>} Promise resolving to final procedure name.
|
|
200
|
-
*/
|
|
201
|
-
const loadRequestValid = async (procName, requestValidationFunction, databaseConnection) => {
|
|
202
|
-
/** @type {BindParameterConfig} */
|
|
203
|
-
const bind = {
|
|
204
|
-
proc: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: procName},
|
|
205
|
-
valid: {dir: oracledb.BIND_OUT, type: oracledb.NUMBER},
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const SQL = [
|
|
209
|
-
'DECLARE',
|
|
210
|
-
' l_valid NUMBER := 0;',
|
|
211
|
-
'BEGIN',
|
|
212
|
-
` IF (${requestValidationFunction}(:proc)) THEN`,
|
|
213
|
-
' l_valid := 1;',
|
|
214
|
-
' END IF;',
|
|
215
|
-
' :valid := l_valid;',
|
|
216
|
-
'END;',
|
|
217
|
-
].join('\n');
|
|
218
|
-
|
|
219
|
-
/** @type {Result} */
|
|
220
|
-
let result = {};
|
|
221
|
-
try {
|
|
222
|
-
result = await databaseConnection.execute(SQL, bind);
|
|
223
|
-
} catch (err) {
|
|
224
|
-
/* v8 ignore start */
|
|
225
|
-
debug('result', result);
|
|
226
|
-
/* v8 ignore stop */
|
|
227
|
-
|
|
228
|
-
const message = `Error when validating procedure name "${procName}"\n${SQL}\n${errorToString(err)}`;
|
|
229
|
-
throw new RequestError(message);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
const data = z.strictObject({valid: z.number()}).parse(result.outBinds);
|
|
234
|
-
return data.valid === 1;
|
|
235
|
-
} catch (err) {
|
|
236
|
-
/* v8 ignore start */
|
|
237
|
-
debug('result', result.outBinds);
|
|
238
|
-
/* v8 ignore stop */
|
|
239
|
-
|
|
240
|
-
const message = `Internal error when parsing ${result.outBinds}\n${errorToString(err)}`;
|
|
241
|
-
throw new Error(message);
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Request validation function.
|
|
247
|
-
*
|
|
248
|
-
* @param {string} procName - The procedure name.
|
|
249
|
-
* @param {string} requestValidationFunction - The request validation function.
|
|
250
|
-
* @param {Connection} databaseConnection - The database connection
|
|
251
|
-
* @returns {Promise<boolean>} Promise resolving to final procedure name.
|
|
252
|
-
*/
|
|
253
|
-
const requestValidationFunction = async (procName, requestValidationFunction, databaseConnection) => {
|
|
254
|
-
debug('requestValidationFunction', procName, requestValidationFunction);
|
|
255
|
-
|
|
256
|
-
// calculate the key
|
|
257
|
-
const key = procName.toLowerCase();
|
|
258
|
-
|
|
259
|
-
// lookup in the cache
|
|
260
|
-
const cacheEntry = validationFunctionCache.get(key);
|
|
261
|
-
|
|
262
|
-
// if we found the procedure in the cache, we return it (hitCount already incremented by get)
|
|
263
|
-
if (cacheEntry !== undefined) {
|
|
264
|
-
/* v8 ignore start */
|
|
265
|
-
if (debug.enabled) {
|
|
266
|
-
debug(`requestValidationFunction: procedure "${procName}" found in cache`);
|
|
267
|
-
}
|
|
268
|
-
/* v8 ignore stop */
|
|
269
|
-
|
|
270
|
-
return cacheEntry.valid;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// load from database
|
|
274
|
-
/* v8 ignore start */
|
|
275
|
-
if (debug.enabled) {
|
|
276
|
-
debug(`requestValidationFunction: procedure "${procName}" not found in cache and must be loaded`);
|
|
277
|
-
}
|
|
278
|
-
/* v8 ignore stop */
|
|
279
|
-
|
|
280
|
-
const valid = await loadRequestValid(procName, requestValidationFunction, databaseConnection);
|
|
281
|
-
|
|
282
|
-
// add to the cache
|
|
283
|
-
validationFunctionCache.set(key, {valid});
|
|
284
|
-
|
|
285
|
-
return valid;
|
|
286
|
-
};
|
|
@@ -1,56 +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
|
-
/* v8 ignore start */
|
|
28
|
-
if (debug.enabled) {
|
|
29
|
-
debug(`getProcedureVariable: ${procName} arguments=`, argObj);
|
|
30
|
-
}
|
|
31
|
-
/* v8 ignore stop */
|
|
32
|
-
|
|
33
|
-
const names = [];
|
|
34
|
-
const values = [];
|
|
35
|
-
|
|
36
|
-
for (const key in argObj) {
|
|
37
|
-
const value = argObj[key];
|
|
38
|
-
if (typeof value === 'string') {
|
|
39
|
-
names.push(key);
|
|
40
|
-
values.push(value);
|
|
41
|
-
} else if (Array.isArray(value)) {
|
|
42
|
-
value.forEach((item) => {
|
|
43
|
-
names.push(key);
|
|
44
|
-
values.push(item);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
sql: `${procName}(:argnames, :argvalues)`,
|
|
51
|
-
bind: {
|
|
52
|
-
argnames: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: names},
|
|
53
|
-
argvalues: {dir: oracledb.BIND_IN, type: oracledb.STRING, val: values},
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
};
|