express.exe 0.0.1769201820754
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/HISTORY.md +0 -0
- package/LICENSE +24 -0
- package/README.md +276 -0
- package/SECURITY.md +0 -0
- package/index.js +11 -0
- package/lib/application.js +631 -0
- package/lib/express.js +81 -0
- package/lib/request.js +557 -0
- package/lib/response.js +1075 -0
- package/lib/utils.js +271 -0
- package/lib/view.js +205 -0
- package/package.json +77 -0
package/lib/utils.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* express
|
|
3
|
+
* Copyright(c) 2009-2013 TJ Holowaychuk
|
|
4
|
+
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
5
|
+
* MIT Licensed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Module dependencies.
|
|
12
|
+
* @api private
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
var { METHODS } = require('node:http');
|
|
16
|
+
var contentType = require('content-type');
|
|
17
|
+
var etag = require('etag');
|
|
18
|
+
var mime = require('mime-types')
|
|
19
|
+
var proxyaddr = require('proxy-addr');
|
|
20
|
+
var qs = require('qs');
|
|
21
|
+
var querystring = require('node:querystring');
|
|
22
|
+
const { Buffer } = require('node:buffer');
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A list of lowercased HTTP methods that are supported by Node.js.
|
|
27
|
+
* @api private
|
|
28
|
+
*/
|
|
29
|
+
exports.methods = METHODS.map((method) => method.toLowerCase());
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Return strong ETag for `body`.
|
|
33
|
+
*
|
|
34
|
+
* @param {String|Buffer} body
|
|
35
|
+
* @param {String} [encoding]
|
|
36
|
+
* @return {String}
|
|
37
|
+
* @api private
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
exports.etag = createETagGenerator({ weak: false })
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Return weak ETag for `body`.
|
|
44
|
+
*
|
|
45
|
+
* @param {String|Buffer} body
|
|
46
|
+
* @param {String} [encoding]
|
|
47
|
+
* @return {String}
|
|
48
|
+
* @api private
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
exports.wetag = createETagGenerator({ weak: true })
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Normalize the given `type`, for example "html" becomes "text/html".
|
|
55
|
+
*
|
|
56
|
+
* @param {String} type
|
|
57
|
+
* @return {Object}
|
|
58
|
+
* @api private
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
exports.normalizeType = function(type){
|
|
62
|
+
return ~type.indexOf('/')
|
|
63
|
+
? acceptParams(type)
|
|
64
|
+
: { value: (mime.lookup(type) || 'application/octet-stream'), params: {} }
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Normalize `types`, for example "html" becomes "text/html".
|
|
69
|
+
*
|
|
70
|
+
* @param {Array} types
|
|
71
|
+
* @return {Array}
|
|
72
|
+
* @api private
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
exports.normalizeTypes = function(types) {
|
|
76
|
+
return types.map(exports.normalizeType);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse accept params `str` returning an
|
|
82
|
+
* object with `.value`, `.quality` and `.params`.
|
|
83
|
+
*
|
|
84
|
+
* @param {String} str
|
|
85
|
+
* @return {Object}
|
|
86
|
+
* @api private
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
function acceptParams (str) {
|
|
90
|
+
var length = str.length;
|
|
91
|
+
var colonIndex = str.indexOf(';');
|
|
92
|
+
var index = colonIndex === -1 ? length : colonIndex;
|
|
93
|
+
var ret = { value: str.slice(0, index).trim(), quality: 1, params: {} };
|
|
94
|
+
|
|
95
|
+
while (index < length) {
|
|
96
|
+
var splitIndex = str.indexOf('=', index);
|
|
97
|
+
if (splitIndex === -1) break;
|
|
98
|
+
|
|
99
|
+
var colonIndex = str.indexOf(';', index);
|
|
100
|
+
var endIndex = colonIndex === -1 ? length : colonIndex;
|
|
101
|
+
|
|
102
|
+
if (splitIndex > endIndex) {
|
|
103
|
+
index = str.lastIndexOf(';', splitIndex - 1) + 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var key = str.slice(index, splitIndex).trim();
|
|
108
|
+
var value = str.slice(splitIndex + 1, endIndex).trim();
|
|
109
|
+
|
|
110
|
+
if (key === 'q') {
|
|
111
|
+
ret.quality = parseFloat(value);
|
|
112
|
+
} else {
|
|
113
|
+
ret.params[key] = value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
index = endIndex + 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return ret;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Compile "etag" value to function.
|
|
124
|
+
*
|
|
125
|
+
* @param {Boolean|String|Function} val
|
|
126
|
+
* @return {Function}
|
|
127
|
+
* @api private
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
exports.compileETag = function(val) {
|
|
131
|
+
var fn;
|
|
132
|
+
|
|
133
|
+
if (typeof val === 'function') {
|
|
134
|
+
return val;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
switch (val) {
|
|
138
|
+
case true:
|
|
139
|
+
case 'weak':
|
|
140
|
+
fn = exports.wetag;
|
|
141
|
+
break;
|
|
142
|
+
case false:
|
|
143
|
+
break;
|
|
144
|
+
case 'strong':
|
|
145
|
+
fn = exports.etag;
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
throw new TypeError('unknown value for etag function: ' + val);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return fn;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Compile "query parser" value to function.
|
|
156
|
+
*
|
|
157
|
+
* @param {String|Function} val
|
|
158
|
+
* @return {Function}
|
|
159
|
+
* @api private
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
exports.compileQueryParser = function compileQueryParser(val) {
|
|
163
|
+
var fn;
|
|
164
|
+
|
|
165
|
+
if (typeof val === 'function') {
|
|
166
|
+
return val;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
switch (val) {
|
|
170
|
+
case true:
|
|
171
|
+
case 'simple':
|
|
172
|
+
fn = querystring.parse;
|
|
173
|
+
break;
|
|
174
|
+
case false:
|
|
175
|
+
break;
|
|
176
|
+
case 'extended':
|
|
177
|
+
fn = parseExtendedQueryString;
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
throw new TypeError('unknown value for query parser function: ' + val);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return fn;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Compile "proxy trust" value to function.
|
|
188
|
+
*
|
|
189
|
+
* @param {Boolean|String|Number|Array|Function} val
|
|
190
|
+
* @return {Function}
|
|
191
|
+
* @api private
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
exports.compileTrust = function(val) {
|
|
195
|
+
if (typeof val === 'function') return val;
|
|
196
|
+
|
|
197
|
+
if (val === true) {
|
|
198
|
+
// Support plain true/false
|
|
199
|
+
return function(){ return true };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (typeof val === 'number') {
|
|
203
|
+
// Support trusting hop count
|
|
204
|
+
return function(a, i){ return i < val };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (typeof val === 'string') {
|
|
208
|
+
// Support comma-separated values
|
|
209
|
+
val = val.split(',')
|
|
210
|
+
.map(function (v) { return v.trim() })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return proxyaddr.compile(val || []);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Set the charset in a given Content-Type string.
|
|
218
|
+
*
|
|
219
|
+
* @param {String} type
|
|
220
|
+
* @param {String} charset
|
|
221
|
+
* @return {String}
|
|
222
|
+
* @api private
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
exports.setCharset = function setCharset(type, charset) {
|
|
226
|
+
if (!type || !charset) {
|
|
227
|
+
return type;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// parse type
|
|
231
|
+
var parsed = contentType.parse(type);
|
|
232
|
+
|
|
233
|
+
// set charset
|
|
234
|
+
parsed.parameters.charset = charset;
|
|
235
|
+
|
|
236
|
+
// format type
|
|
237
|
+
return contentType.format(parsed);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Create an ETag generator function, generating ETags with
|
|
242
|
+
* the given options.
|
|
243
|
+
*
|
|
244
|
+
* @param {object} options
|
|
245
|
+
* @return {function}
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
function createETagGenerator (options) {
|
|
250
|
+
return function generateETag (body, encoding) {
|
|
251
|
+
var buf = !Buffer.isBuffer(body)
|
|
252
|
+
? Buffer.from(body, encoding)
|
|
253
|
+
: body
|
|
254
|
+
|
|
255
|
+
return etag(buf, options)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Parse an extended query string with qs.
|
|
261
|
+
*
|
|
262
|
+
* @param {String} str
|
|
263
|
+
* @return {Object}
|
|
264
|
+
* @private
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
function parseExtendedQueryString(str) {
|
|
268
|
+
return qs.parse(str, {
|
|
269
|
+
allowPrototypes: true
|
|
270
|
+
});
|
|
271
|
+
}
|
package/lib/view.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* express
|
|
3
|
+
* Copyright(c) 2009-2013 TJ Holowaychuk
|
|
4
|
+
* Copyright(c) 2013 Roman Shtylman
|
|
5
|
+
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
6
|
+
* MIT Licensed
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Module dependencies.
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
var debug = require('debug')('express:view');
|
|
17
|
+
var path = require('node:path');
|
|
18
|
+
var fs = require('node:fs');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Module variables.
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
var dirname = path.dirname;
|
|
26
|
+
var basename = path.basename;
|
|
27
|
+
var extname = path.extname;
|
|
28
|
+
var join = path.join;
|
|
29
|
+
var resolve = path.resolve;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Module exports.
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
module.exports = View;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize a new `View` with the given `name`.
|
|
40
|
+
*
|
|
41
|
+
* Options:
|
|
42
|
+
*
|
|
43
|
+
* - `defaultEngine` the default template engine name
|
|
44
|
+
* - `engines` template engine require() cache
|
|
45
|
+
* - `root` root path for view lookup
|
|
46
|
+
*
|
|
47
|
+
* @param {string} name
|
|
48
|
+
* @param {object} options
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
function View(name, options) {
|
|
53
|
+
var opts = options || {};
|
|
54
|
+
|
|
55
|
+
this.defaultEngine = opts.defaultEngine;
|
|
56
|
+
this.ext = extname(name);
|
|
57
|
+
this.name = name;
|
|
58
|
+
this.root = opts.root;
|
|
59
|
+
|
|
60
|
+
if (!this.ext && !this.defaultEngine) {
|
|
61
|
+
throw new Error('No default engine was specified and no extension was provided.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var fileName = name;
|
|
65
|
+
|
|
66
|
+
if (!this.ext) {
|
|
67
|
+
// get extension from default engine name
|
|
68
|
+
this.ext = this.defaultEngine[0] !== '.'
|
|
69
|
+
? '.' + this.defaultEngine
|
|
70
|
+
: this.defaultEngine;
|
|
71
|
+
|
|
72
|
+
fileName += this.ext;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!opts.engines[this.ext]) {
|
|
76
|
+
// load engine
|
|
77
|
+
var mod = this.ext.slice(1)
|
|
78
|
+
debug('require "%s"', mod)
|
|
79
|
+
|
|
80
|
+
// default engine export
|
|
81
|
+
var fn = require(mod).__express
|
|
82
|
+
|
|
83
|
+
if (typeof fn !== 'function') {
|
|
84
|
+
throw new Error('Module "' + mod + '" does not provide a view engine.')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
opts.engines[this.ext] = fn
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// store loaded engine
|
|
91
|
+
this.engine = opts.engines[this.ext];
|
|
92
|
+
|
|
93
|
+
// lookup path
|
|
94
|
+
this.path = this.lookup(fileName);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Lookup view by the given `name`
|
|
99
|
+
*
|
|
100
|
+
* @param {string} name
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
View.prototype.lookup = function lookup(name) {
|
|
105
|
+
var path;
|
|
106
|
+
var roots = [].concat(this.root);
|
|
107
|
+
|
|
108
|
+
debug('lookup "%s"', name);
|
|
109
|
+
|
|
110
|
+
for (var i = 0; i < roots.length && !path; i++) {
|
|
111
|
+
var root = roots[i];
|
|
112
|
+
|
|
113
|
+
// resolve the path
|
|
114
|
+
var loc = resolve(root, name);
|
|
115
|
+
var dir = dirname(loc);
|
|
116
|
+
var file = basename(loc);
|
|
117
|
+
|
|
118
|
+
// resolve the file
|
|
119
|
+
path = this.resolve(dir, file);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return path;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Render with the given options.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} options
|
|
129
|
+
* @param {function} callback
|
|
130
|
+
* @private
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
View.prototype.render = function render(options, callback) {
|
|
134
|
+
var sync = true;
|
|
135
|
+
|
|
136
|
+
debug('render "%s"', this.path);
|
|
137
|
+
|
|
138
|
+
// render, normalizing sync callbacks
|
|
139
|
+
this.engine(this.path, options, function onRender() {
|
|
140
|
+
if (!sync) {
|
|
141
|
+
return callback.apply(this, arguments);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// copy arguments
|
|
145
|
+
var args = new Array(arguments.length);
|
|
146
|
+
var cntx = this;
|
|
147
|
+
|
|
148
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
149
|
+
args[i] = arguments[i];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// force callback to be async
|
|
153
|
+
return process.nextTick(function renderTick() {
|
|
154
|
+
return callback.apply(cntx, args);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
sync = false;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Resolve the file within the given directory.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} dir
|
|
165
|
+
* @param {string} file
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
View.prototype.resolve = function resolve(dir, file) {
|
|
170
|
+
var ext = this.ext;
|
|
171
|
+
|
|
172
|
+
// <path>.<ext>
|
|
173
|
+
var path = join(dir, file);
|
|
174
|
+
var stat = tryStat(path);
|
|
175
|
+
|
|
176
|
+
if (stat && stat.isFile()) {
|
|
177
|
+
return path;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// <path>/index.<ext>
|
|
181
|
+
path = join(dir, basename(file, ext), 'index' + ext);
|
|
182
|
+
stat = tryStat(path);
|
|
183
|
+
|
|
184
|
+
if (stat && stat.isFile()) {
|
|
185
|
+
return path;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Return a stat, maybe.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} path
|
|
193
|
+
* @return {fs.Stats}
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
function tryStat(path) {
|
|
198
|
+
debug('stat "%s"', path);
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
return fs.statSync(path);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "express.exe",
|
|
3
|
+
"description": "Hello World",
|
|
4
|
+
"version": "0.0.1769201820754",
|
|
5
|
+
"author": "Seindi Rahmat Barus <xseindi@gmail.com>",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Seindi Rahmat Barus <xseindi@gmail.com>"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"express",
|
|
12
|
+
"vue",
|
|
13
|
+
"mongodb",
|
|
14
|
+
"appwrite",
|
|
15
|
+
"firebase",
|
|
16
|
+
"vercel",
|
|
17
|
+
"cloudflare"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"README.md",
|
|
22
|
+
"HISTORY.md",
|
|
23
|
+
"SECURITY.md",
|
|
24
|
+
"index.js",
|
|
25
|
+
"lib/"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"script.io.js": "^2026.124.357",
|
|
29
|
+
"node-appwrite": "^21.1.0",
|
|
30
|
+
"accepts": "^2.0.0",
|
|
31
|
+
"body-parser": "^2.2.1",
|
|
32
|
+
"content-disposition": "^1.0.0",
|
|
33
|
+
"content-type": "^1.0.5",
|
|
34
|
+
"cookie": "^0.7.1",
|
|
35
|
+
"cookie-signature": "^1.2.1",
|
|
36
|
+
"debug": "^4.4.0",
|
|
37
|
+
"depd": "^2.0.0",
|
|
38
|
+
"encodeurl": "^2.0.0",
|
|
39
|
+
"escape-html": "^1.0.3",
|
|
40
|
+
"etag": "^1.8.1",
|
|
41
|
+
"finalhandler": "^2.1.0",
|
|
42
|
+
"fresh": "^2.0.0",
|
|
43
|
+
"http-errors": "^2.0.0",
|
|
44
|
+
"merge-descriptors": "^2.0.0",
|
|
45
|
+
"mime-types": "^3.0.0",
|
|
46
|
+
"on-finished": "^2.4.1",
|
|
47
|
+
"once": "^1.4.0",
|
|
48
|
+
"parseurl": "^1.3.3",
|
|
49
|
+
"proxy-addr": "^2.0.7",
|
|
50
|
+
"qs": "^6.14.0",
|
|
51
|
+
"range-parser": "^1.2.1",
|
|
52
|
+
"router": "^2.2.0",
|
|
53
|
+
"send": "^1.1.0",
|
|
54
|
+
"serve-static": "^2.2.0",
|
|
55
|
+
"statuses": "^2.0.1",
|
|
56
|
+
"type-is": "^2.0.1",
|
|
57
|
+
"vary": "^1.1.2"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"after": "0.8.2",
|
|
61
|
+
"connect-redis": "^8.0.1",
|
|
62
|
+
"cookie-parser": "1.4.7",
|
|
63
|
+
"cookie-session": "2.1.1",
|
|
64
|
+
"ejs": "^3.1.10",
|
|
65
|
+
"eslint": "8.47.0",
|
|
66
|
+
"express-session": "^1.18.1",
|
|
67
|
+
"hbs": "4.2.0",
|
|
68
|
+
"marked": "^15.0.3",
|
|
69
|
+
"method-override": "3.0.0",
|
|
70
|
+
"mocha": "^10.7.3",
|
|
71
|
+
"morgan": "1.10.1",
|
|
72
|
+
"nyc": "^17.1.0",
|
|
73
|
+
"pbkdf2-password": "1.2.1",
|
|
74
|
+
"supertest": "^6.3.0",
|
|
75
|
+
"vhost": "~3.0.2"
|
|
76
|
+
}
|
|
77
|
+
}
|