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.
Files changed (119) hide show
  1. package/README.md +103 -8
  2. package/dist/chunk-DQk6qfdC.mjs +18 -0
  3. package/dist/frontend/assets/main-C8GKnEhz.js +147 -0
  4. package/dist/frontend/assets/main-C8GKnEhz.js.br +0 -0
  5. package/dist/frontend/assets/main-C8GKnEhz.js.gz +0 -0
  6. package/dist/frontend/assets/main-bU4x_yEW.css +1 -0
  7. package/dist/frontend/assets/main-bU4x_yEW.css.br +0 -0
  8. package/dist/frontend/assets/main-bU4x_yEW.css.gz +0 -0
  9. package/{src/admin → dist/frontend}/index.html +12 -25
  10. package/dist/frontend/index.html.br +0 -0
  11. package/dist/frontend/index.html.gz +0 -0
  12. package/dist/index.d.mts +646 -0
  13. package/dist/index.mjs +3559 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
  16. package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
  17. package/examples/config-apex.js +1 -1
  18. package/examples/config-docker.js +1 -2
  19. package/package.json +48 -32
  20. package/examples/config-native.js +0 -68
  21. package/src/admin/client/charts.ts +0 -598
  22. package/src/admin/client/main.js +0 -3
  23. package/src/admin/client/tailwind.css +0 -41
  24. package/src/admin/js/api.ts +0 -170
  25. package/src/admin/js/app.ts +0 -614
  26. package/src/admin/js/eslint.config.js +0 -74
  27. package/src/admin/js/schemas.ts +0 -215
  28. package/src/admin/js/templates/config.ts +0 -147
  29. package/src/admin/js/templates/errorRow.ts +0 -21
  30. package/src/admin/js/templates/index.ts +0 -4
  31. package/src/admin/js/templates/poolCard.ts +0 -61
  32. package/src/admin/js/templates/traceRow.ts +0 -24
  33. package/src/admin/js/tsconfig.json +0 -24
  34. package/src/admin/js/types.ts +0 -336
  35. package/src/admin/js/ui/components.ts +0 -44
  36. package/src/admin/js/ui/table.ts +0 -173
  37. package/src/admin/js/ui/theme.ts +0 -93
  38. package/src/admin/js/ui/views.ts +0 -427
  39. package/src/admin/js/util/format.ts +0 -46
  40. package/src/admin/js/util/metrics.ts +0 -24
  41. package/src/admin/lib/chart.bundle.css +0 -1
  42. package/src/admin/lib/chart.bundle.js +0 -146
  43. package/src/admin/style.css +0 -1437
  44. package/src/bin/load-test.js +0 -202
  45. package/src/handler/handlerAdmin.js +0 -293
  46. package/src/handler/handlerLogger.js +0 -21
  47. package/src/handler/handlerUpload.js +0 -26
  48. package/src/handler/plsql/cgi.js +0 -131
  49. package/src/handler/plsql/errorPage.js +0 -113
  50. package/src/handler/plsql/handlerPlSql.js +0 -90
  51. package/src/handler/plsql/owaPageStream.js +0 -93
  52. package/src/handler/plsql/parsePage.js +0 -213
  53. package/src/handler/plsql/procedure.js +0 -426
  54. package/src/handler/plsql/procedureError.js +0 -40
  55. package/src/handler/plsql/procedureNamed.js +0 -282
  56. package/src/handler/plsql/procedureSanitize.js +0 -286
  57. package/src/handler/plsql/procedureVariable.js +0 -56
  58. package/src/handler/plsql/request.js +0 -124
  59. package/src/handler/plsql/requestError.js +0 -23
  60. package/src/handler/plsql/sendResponse.js +0 -174
  61. package/src/handler/plsql/stream.js +0 -28
  62. package/src/handler/plsql/upload.js +0 -109
  63. package/src/index.js +0 -19
  64. package/src/server/adminContext.js +0 -23
  65. package/src/server/config.js +0 -57
  66. package/src/server/connections.js +0 -40
  67. package/src/server/server.js +0 -304
  68. package/src/types.js +0 -150
  69. package/src/util/cache.js +0 -123
  70. package/src/util/errorToString.js +0 -24
  71. package/src/util/file.js +0 -91
  72. package/src/util/html.js +0 -53
  73. package/src/util/jsonLogger.js +0 -47
  74. package/src/util/oracle.js +0 -80
  75. package/src/util/shutdown.js +0 -44
  76. package/src/util/statsManager.js +0 -368
  77. package/src/util/trace.js +0 -326
  78. package/src/util/traceManager.js +0 -68
  79. package/src/util/type.js +0 -6
  80. package/src/util/util.js +0 -66
  81. package/src/version.js +0 -5
  82. package/types/admin/js/schemas.d.ts +0 -384
  83. package/types/admin/js/types.d.ts +0 -312
  84. package/types/handler/handlerAdmin.d.ts +0 -79
  85. package/types/handler/handlerLogger.d.ts +0 -2
  86. package/types/handler/handlerUpload.d.ts +0 -7
  87. package/types/handler/plsql/cgi.d.ts +0 -4
  88. package/types/handler/plsql/errorPage.d.ts +0 -11
  89. package/types/handler/plsql/handlerPlSql.d.ts +0 -13
  90. package/types/handler/plsql/owaPageStream.d.ts +0 -28
  91. package/types/handler/plsql/parsePage.d.ts +0 -3
  92. package/types/handler/plsql/procedure.d.ts +0 -13
  93. package/types/handler/plsql/procedureError.d.ts +0 -23
  94. package/types/handler/plsql/procedureNamed.d.ts +0 -14
  95. package/types/handler/plsql/procedureSanitize.d.ts +0 -11
  96. package/types/handler/plsql/procedureVariable.d.ts +0 -10
  97. package/types/handler/plsql/request.d.ts +0 -9
  98. package/types/handler/plsql/requestError.d.ts +0 -8
  99. package/types/handler/plsql/sendResponse.d.ts +0 -6
  100. package/types/handler/plsql/upload.d.ts +0 -5
  101. package/types/index.d.ts +0 -10
  102. package/types/server/adminContext.d.ts +0 -17
  103. package/types/server/config.d.ts +0 -2
  104. package/types/server/server.d.ts +0 -57
  105. package/types/types.d.ts +0 -328
  106. package/types/util/cache.d.ts +0 -69
  107. package/types/util/errorToString.d.ts +0 -1
  108. package/types/util/file.d.ts +0 -6
  109. package/types/util/html.d.ts +0 -3
  110. package/types/util/jsonLogger.d.ts +0 -45
  111. package/types/util/oracle.d.ts +0 -5
  112. package/types/util/shutdown.d.ts +0 -2
  113. package/types/util/statsManager.d.ts +0 -395
  114. package/types/util/trace.d.ts +0 -22
  115. package/types/util/traceManager.d.ts +0 -35
  116. package/types/util/type.d.ts +0 -1
  117. package/types/util/util.d.ts +0 -3
  118. package/types/version.d.ts +0 -1
  119. /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
- };