web_plsql 0.3.2 → 0.5.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/.editorconfig +8 -8
- package/.eslintignore +3 -3
- package/.eslintrc.js +347 -273
- package/CHANGELOG.md +127 -106
- package/LICENSE +21 -21
- package/README.md +165 -164
- package/examples/apex.js +70 -70
- package/examples/credentials.js +22 -22
- package/examples/oracledb_example.js +30 -30
- package/examples/sample.js +101 -84
- package/examples/sql/doc_table.sql +13 -13
- package/examples/sql/install.sql +31 -31
- package/examples/sql/sample.pkb +223 -223
- package/examples/sql/sample.pks +24 -24
- package/examples/sql/uninstall.sql +5 -5
- package/examples/static/sample.css +25 -25
- package/jest.config.js +204 -0
- package/package.json +85 -109
- package/src/cgi.ts +95 -95
- package/src/config.ts +97 -97
- package/src/errorPage.ts +286 -286
- package/src/fileUpload.ts +126 -132
- package/src/index.ts +65 -66
- package/src/page.ts +275 -277
- package/src/procedure.ts +360 -359
- package/src/procedureError.ts +27 -27
- package/src/request.ts +139 -139
- package/src/requestError.ts +19 -19
- package/src/stream.ts +26 -26
- package/src/trace.ts +194 -193
- package/test/.eslintrc.json +5 -5
- package/test/{cgi.ts → __tests__/cgi.ts} +96 -95
- package/test/{config.ts → __tests__/config.ts} +41 -41
- package/test/__tests__/errorPage.ts +101 -0
- package/test/{oracledb_mock.ts → __tests__/oracledb_mock.ts} +98 -98
- package/test/{server.ts → __tests__/server.ts} +495 -498
- package/test/{stream.ts → __tests__/stream.ts} +21 -21
- package/test/mock/oracledb.ts +85 -85
- package/test/static/static.html +1 -1
- package/tsconfig.json +24 -23
- package/tsconfig.src.json +23 -22
- package/test/errorPage.ts +0 -101
- package/test/mocha.opts +0 -7
- package/tsconfig.test.json +0 -19
package/src/page.ts
CHANGED
|
@@ -1,277 +1,275 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Page the raw page content and return the content to the client
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {Trace} from './trace';
|
|
6
|
-
import express from 'express';
|
|
7
|
-
|
|
8
|
-
type cookieType = {
|
|
9
|
-
name: string;
|
|
10
|
-
value: string;
|
|
11
|
-
path?: string;
|
|
12
|
-
domain?: string;
|
|
13
|
-
secure?: string;
|
|
14
|
-
expires?: Date;
|
|
15
|
-
httpOnly?: boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type pageType = {
|
|
19
|
-
body: string;
|
|
20
|
-
head: {
|
|
21
|
-
cookies: Array<cookieType>;
|
|
22
|
-
contentType?: string;
|
|
23
|
-
contentLength?: number;
|
|
24
|
-
statusCode?: number;
|
|
25
|
-
statusDescription?: string;
|
|
26
|
-
redirectLocation?: string;
|
|
27
|
-
otherHeaders:
|
|
28
|
-
server?: string;
|
|
29
|
-
};
|
|
30
|
-
file: {
|
|
31
|
-
fileType: any;
|
|
32
|
-
fileSize: any;
|
|
33
|
-
fileBlob: any;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Parse the header and split it up into the individual components
|
|
39
|
-
*
|
|
40
|
-
* @param {string} text - The text returned from the PL/SQL procedure.
|
|
41
|
-
* @returns {Object} - The parsed page.
|
|
42
|
-
*/
|
|
43
|
-
export function parse(text: string): pageType {
|
|
44
|
-
const page: pageType = {
|
|
45
|
-
body: '',
|
|
46
|
-
head: {
|
|
47
|
-
cookies: [],
|
|
48
|
-
otherHeaders: {}
|
|
49
|
-
},
|
|
50
|
-
file: {
|
|
51
|
-
fileType: null,
|
|
52
|
-
fileSize: null,
|
|
53
|
-
fileBlob: null
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
// 1) Split up the text in header and body
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
// Find the end of the header identified by \n\n
|
|
62
|
-
let head = '';
|
|
63
|
-
const headerEndPosition = text.indexOf('\n\n');
|
|
64
|
-
if (headerEndPosition === -1) {
|
|
65
|
-
head = text;
|
|
66
|
-
} else {
|
|
67
|
-
head = text.substring(0, headerEndPosition + 2);
|
|
68
|
-
page.body = text.substring(headerEndPosition + 2);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
// 2) parse the headers
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
head.split('\n').forEach(line => {
|
|
76
|
-
const header = getHeader(line);
|
|
77
|
-
|
|
78
|
-
if (header) {
|
|
79
|
-
switch (header.name.toLowerCase()) {
|
|
80
|
-
case 'set-cookie':
|
|
81
|
-
{
|
|
82
|
-
const cookie = parseCookie(header.value);
|
|
83
|
-
/* istanbul ignore else */
|
|
84
|
-
if (cookie !== null) {
|
|
85
|
-
page.head.cookies.push(cookie);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
break;
|
|
89
|
-
|
|
90
|
-
case 'content-type':
|
|
91
|
-
page.head.contentType = header.value;
|
|
92
|
-
break;
|
|
93
|
-
|
|
94
|
-
case 'x-db-content-length':
|
|
95
|
-
{
|
|
96
|
-
const contentLength = parseInt(header.value, 10);
|
|
97
|
-
/* istanbul ignore else */
|
|
98
|
-
if (!Number.isNaN(contentLength)) {
|
|
99
|
-
page.head.contentLength = contentLength;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
break;
|
|
103
|
-
|
|
104
|
-
case 'status':
|
|
105
|
-
{
|
|
106
|
-
const statusCode = parseInt(header.value, 10);
|
|
107
|
-
/* istanbul ignore else */
|
|
108
|
-
if (!Number.isNaN(statusCode)) {
|
|
109
|
-
page.head.statusCode = statusCode;
|
|
110
|
-
const index = header.value.indexOf(' ');
|
|
111
|
-
/* istanbul ignore else */
|
|
112
|
-
if (index !== -1) {
|
|
113
|
-
page.head.statusDescription = header.value.substr(index + 1);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
|
|
119
|
-
case 'location':
|
|
120
|
-
page.head.redirectLocation = header.value;
|
|
121
|
-
break;
|
|
122
|
-
|
|
123
|
-
case 'x-oracle-ignore':
|
|
124
|
-
break;
|
|
125
|
-
|
|
126
|
-
default:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
trace.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (element.indexOf('
|
|
248
|
-
cookie.
|
|
249
|
-
} else if (element.toLowerCase().indexOf('
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Page the raw page content and return the content to the client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {Trace} from './trace';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
|
|
8
|
+
type cookieType = {
|
|
9
|
+
name: string;
|
|
10
|
+
value: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
domain?: string;
|
|
13
|
+
secure?: string;
|
|
14
|
+
expires?: Date;
|
|
15
|
+
httpOnly?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type pageType = {
|
|
19
|
+
body: string;
|
|
20
|
+
head: {
|
|
21
|
+
cookies: Array<cookieType>;
|
|
22
|
+
contentType?: string;
|
|
23
|
+
contentLength?: number;
|
|
24
|
+
statusCode?: number;
|
|
25
|
+
statusDescription?: string;
|
|
26
|
+
redirectLocation?: string;
|
|
27
|
+
otherHeaders: Record<string, unknown>;
|
|
28
|
+
server?: string;
|
|
29
|
+
};
|
|
30
|
+
file: {
|
|
31
|
+
fileType: any;
|
|
32
|
+
fileSize: any;
|
|
33
|
+
fileBlob: any;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parse the header and split it up into the individual components
|
|
39
|
+
*
|
|
40
|
+
* @param {string} text - The text returned from the PL/SQL procedure.
|
|
41
|
+
* @returns {Object} - The parsed page.
|
|
42
|
+
*/
|
|
43
|
+
export function parse(text: string): pageType {
|
|
44
|
+
const page: pageType = {
|
|
45
|
+
body: '',
|
|
46
|
+
head: {
|
|
47
|
+
cookies: [],
|
|
48
|
+
otherHeaders: {}
|
|
49
|
+
},
|
|
50
|
+
file: {
|
|
51
|
+
fileType: null,
|
|
52
|
+
fileSize: null,
|
|
53
|
+
fileBlob: null
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
//
|
|
58
|
+
// 1) Split up the text in header and body
|
|
59
|
+
//
|
|
60
|
+
|
|
61
|
+
// Find the end of the header identified by \n\n
|
|
62
|
+
let head = '';
|
|
63
|
+
const headerEndPosition = text.indexOf('\n\n');
|
|
64
|
+
if (headerEndPosition === -1) {
|
|
65
|
+
head = text;
|
|
66
|
+
} else {
|
|
67
|
+
head = text.substring(0, headerEndPosition + 2);
|
|
68
|
+
page.body = text.substring(headerEndPosition + 2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//
|
|
72
|
+
// 2) parse the headers
|
|
73
|
+
//
|
|
74
|
+
|
|
75
|
+
head.split('\n').forEach(line => {
|
|
76
|
+
const header = getHeader(line);
|
|
77
|
+
|
|
78
|
+
if (header) {
|
|
79
|
+
switch (header.name.toLowerCase()) {
|
|
80
|
+
case 'set-cookie':
|
|
81
|
+
{
|
|
82
|
+
const cookie = parseCookie(header.value);
|
|
83
|
+
/* istanbul ignore else */
|
|
84
|
+
if (cookie !== null) {
|
|
85
|
+
page.head.cookies.push(cookie);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'content-type':
|
|
91
|
+
page.head.contentType = header.value;
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
case 'x-db-content-length':
|
|
95
|
+
{
|
|
96
|
+
const contentLength = parseInt(header.value, 10);
|
|
97
|
+
/* istanbul ignore else */
|
|
98
|
+
if (!Number.isNaN(contentLength)) {
|
|
99
|
+
page.head.contentLength = contentLength;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case 'status':
|
|
105
|
+
{
|
|
106
|
+
const statusCode = parseInt(header.value, 10);
|
|
107
|
+
/* istanbul ignore else */
|
|
108
|
+
if (!Number.isNaN(statusCode)) {
|
|
109
|
+
page.head.statusCode = statusCode;
|
|
110
|
+
const index = header.value.indexOf(' ');
|
|
111
|
+
/* istanbul ignore else */
|
|
112
|
+
if (index !== -1) {
|
|
113
|
+
page.head.statusDescription = header.value.substr(index + 1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case 'location':
|
|
120
|
+
page.head.redirectLocation = header.value;
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case 'x-oracle-ignore':
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
default:
|
|
127
|
+
page.head.otherHeaders[header.name] = header.value;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return page;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/*
|
|
137
|
+
* get a header line
|
|
138
|
+
*/
|
|
139
|
+
function getHeader(line: string): {name: string; value: string} | null {
|
|
140
|
+
const index = line.indexOf(':');
|
|
141
|
+
|
|
142
|
+
if (index !== -1) {
|
|
143
|
+
return {
|
|
144
|
+
name: line.substr(0, index).trim(),
|
|
145
|
+
value: line.substr(index + 1).trim()
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* Send "default" response to the browser
|
|
154
|
+
*/
|
|
155
|
+
export function send(req: express.Request, res: express.Response, page: {body: string; head: any; file: any}, trace: Trace): void {
|
|
156
|
+
trace.write('send: ENTER');
|
|
157
|
+
|
|
158
|
+
// Send the "cookies"
|
|
159
|
+
page.head.cookies.forEach((cookie: Record<string, unknown>) => {
|
|
160
|
+
const name = cookie.name as string;
|
|
161
|
+
const value = cookie.value as string;
|
|
162
|
+
|
|
163
|
+
delete cookie.name;
|
|
164
|
+
delete cookie.value;
|
|
165
|
+
|
|
166
|
+
res.cookie(name, value, cookie);
|
|
167
|
+
trace.append(`send: res.cookie("${name}", "${value}")\n`);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// If there is a "redirectLocation" header, we immediately redirect and return
|
|
171
|
+
if (typeof page.head.redirectLocation === 'string' && page.head.redirectLocation.length > 0) {
|
|
172
|
+
res.redirect(302, page.head.redirectLocation);
|
|
173
|
+
trace.append(`send: res.redirect(302, "${page.head.redirectLocation}")\n`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Send all the "otherHeaders"
|
|
178
|
+
for (const key in page.head.otherHeaders) {
|
|
179
|
+
res.set(key, page.head.otherHeaders[key]);
|
|
180
|
+
trace.append(`send: res.set("${key}", "${page.head.otherHeaders[key]}")\n`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// If this is a file download, we eventually set the "Content-Type" and the file content and then return.
|
|
184
|
+
if (page.file.fileType === 'B' || page.file.fileType === 'F') {
|
|
185
|
+
if (typeof page.head.contentType === 'string' && page.head.contentType.length > 0) {
|
|
186
|
+
res.writeHead(200, {'Content-Type': page.head.contentType});
|
|
187
|
+
trace.append(`sendFile: res.writeHead("Content-Type", "${page.head.contentType}")\n`);
|
|
188
|
+
}
|
|
189
|
+
res.end(page.file.fileBlob, 'binary');
|
|
190
|
+
trace.append('send: res.end()\n');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Is the a "contentType" header
|
|
195
|
+
if (typeof page.head.contentType === 'string' && page.head.contentType.length > 0) {
|
|
196
|
+
res.set('Content-Type', page.head.contentType);
|
|
197
|
+
trace.append(`send: res.set("Content-Type", "${page.head.contentType}")\n`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// If we have a "Status" header, we send the header and then return.
|
|
201
|
+
if (typeof page.head.statusCode === 'number') {
|
|
202
|
+
res.status(page.head.statusCode).send(page.head.statusDescription);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Send the body
|
|
207
|
+
res.send(page.body);
|
|
208
|
+
trace.append(`send: res.send\n${'-'.repeat(30)}${page.body}\n${'-'.repeat(30)}\n`);
|
|
209
|
+
|
|
210
|
+
trace.write('send: EXIT');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
* Parses a cookie string
|
|
215
|
+
*/
|
|
216
|
+
function parseCookie(text: string): cookieType | null {
|
|
217
|
+
// validate
|
|
218
|
+
/* istanbul ignore next */
|
|
219
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// split the cookie into it's parts
|
|
224
|
+
let cookieElements = text.split(';');
|
|
225
|
+
|
|
226
|
+
// trim cookie elements
|
|
227
|
+
cookieElements = cookieElements.map(element => element.trim());
|
|
228
|
+
|
|
229
|
+
// get name and value
|
|
230
|
+
const index = cookieElements[0].indexOf('=');
|
|
231
|
+
/* istanbul ignore next */
|
|
232
|
+
if (index <= 0) {
|
|
233
|
+
// if the index is -1, there is no equal sign and if it's 0 the name is empty
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const cookie: any = {};
|
|
237
|
+
cookie.name = cookieElements[0].substring(0, index).trim();
|
|
238
|
+
cookie.value = cookieElements[0].substring(index + 1).trim();
|
|
239
|
+
|
|
240
|
+
// remove the fisrt element
|
|
241
|
+
cookieElements.shift();
|
|
242
|
+
|
|
243
|
+
// get the other options
|
|
244
|
+
cookieElements.forEach(element => {
|
|
245
|
+
if (element.indexOf('path=') === 0) {
|
|
246
|
+
cookie.path = element.substring(5);
|
|
247
|
+
} else if (element.toLowerCase().indexOf('domain=') === 0) {
|
|
248
|
+
cookie.domain = element.substring(7);
|
|
249
|
+
} else if (element.toLowerCase().indexOf('secure=') === 0) {
|
|
250
|
+
/* istanbul ignore next */
|
|
251
|
+
cookie.secure = element.substring(7);
|
|
252
|
+
} else if (element.toLowerCase().indexOf('expires=') === 0) {
|
|
253
|
+
const date = tryDecodeDate(element.substring(8));
|
|
254
|
+
if (date) {
|
|
255
|
+
cookie.expires = date;
|
|
256
|
+
}
|
|
257
|
+
} else if (element.toLowerCase().indexOf('httponly') === 0) {
|
|
258
|
+
cookie.httpOnly = true;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return cookie;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/*
|
|
266
|
+
* Decode a date
|
|
267
|
+
*/
|
|
268
|
+
function tryDecodeDate(value: string): Date | null {
|
|
269
|
+
try {
|
|
270
|
+
return new Date(value);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
/* istanbul ignore next */
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|