1 |
|
2 | var fs = require("fs")
|
3 | , content = require("./content")
|
4 | , event = require("./event")
|
5 | , path = require("./path")
|
6 | , util = require("./util")
|
7 | , defaultOpts = {
|
8 | maxBodySize: 1e6,
|
9 | maxNameSize: 100,
|
10 | maxFields: 1000,
|
11 | maxFieldSize: 1000,
|
12 | maxFiles: 1000,
|
13 | maxFileSize: Infinity,
|
14 | maxURILength: 2000,
|
15 | log: console,
|
16 | exitTime: 5000,
|
17 | accept: {
|
18 | "application/json;filename=;select=;space=": function(data, negod) {
|
19 | return JSON.stringify(
|
20 | data,
|
21 | negod.select ? negod.select.split(",") : null,
|
22 | +negod.space || negod.space
|
23 |
|
24 |
|
25 | ).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")
|
26 | },
|
27 |
|
28 | "text/csv;br=\"\r\n\";delimiter=\",\";fields=;filename=;header=;NULL=;select=": require("./csv.js").encode,
|
29 | "application/sql;fields=;filename=;NULL=NULL;select=;table=table": function(data, negod) {
|
30 | negod.re = /\D/
|
31 | negod.br = "),\n("
|
32 | negod.prefix = "INSERT INTO " +
|
33 | negod.table + (negod.fields ? " (" + negod.fields + ")" : "") + " VALUES ("
|
34 | negod.postfix = ");"
|
35 | return csv.encode(data, negod)
|
36 | }
|
37 | },
|
38 | bodyRe: /^(?:PATCH|POST|PUT)$/i,
|
39 | charset: "UTF-8",
|
40 | compress: false,
|
41 | encoding: {
|
42 | "deflate;q=0.1": "createDeflate",
|
43 | "gzip;q=0.2": "createGzip",
|
44 | "br": "createBrotliCompress"
|
45 | },
|
46 | error: {
|
47 | "URIError": { code: 400 }
|
48 | },
|
49 | method: {
|
50 | "DELETE": "del",
|
51 | "GET": "get",
|
52 | "PATCH": "patch",
|
53 | "POST": "post",
|
54 | "PUT": "put"
|
55 | },
|
56 | mime: {
|
57 | "asf": "video/x-ms-asf",
|
58 | "asx": "video/x-ms-asx",
|
59 | "avi": "video/x-msvideo",
|
60 | "css": "text/css",
|
61 | "csv": "text/csv",
|
62 | "cur": "image/vnd.microsoft.icon",
|
63 | "doc": "application/msword",
|
64 | "drw": "application/drafting",
|
65 | "dvi": "application/x-dvi",
|
66 | "dwg": "application/acad",
|
67 | "dxf": "application/dxf",
|
68 | "gif": "image/gif",
|
69 | "gz": "application/x-gzip",
|
70 | "htm": "text/html",
|
71 | "html": "text/html",
|
72 | "ico": "image/x-icon",
|
73 | "jar": "application/java-archive",
|
74 | "jpeg": "image/jpeg",
|
75 | "jpg": "image/jpeg",
|
76 | "js": "text/javascript",
|
77 | "json": "application/json",
|
78 | "m3u": "audio/x-mpegurl",
|
79 | "manifest": "text/cache-manifest",
|
80 | "midi": "audio/midi",
|
81 | "mjs": "text/javascript",
|
82 | "mp3": "audio/mpeg",
|
83 | "mp4": "video/mp4",
|
84 | "mpeg": "video/mpeg",
|
85 | "mpg": "video/mpeg",
|
86 | "mpga": "audio/mpeg",
|
87 | "pdf": "application/pdf",
|
88 | "pgp": "application/pgp",
|
89 | "png": "image/png",
|
90 | "ppz": "application/vnd.ms-powerpoint",
|
91 | "ps": "application/postscript",
|
92 | "qt": "video/quicktime",
|
93 | "ra": "audio/x-realaudio",
|
94 | "rar": "application/x-rar-compressed",
|
95 | "rm": "audio/x-pn-realaudio",
|
96 | "rtf": "text/rtf",
|
97 | "rtx": "text/richtext",
|
98 | "sgml": "text/sgml",
|
99 | "sh": "application/x-sh",
|
100 | "snd": "audio/basic",
|
101 | "sql": "application/sql",
|
102 | "svg": "image/svg+xml",
|
103 | "tex": "application/x-tex",
|
104 | "tgz": "application/x-tar-gz",
|
105 | "tiff": "image/tiff",
|
106 | "tsv": "text/tab-separated-values",
|
107 | "txt": "text/plain",
|
108 | "wav": "audio/x-wav",
|
109 | "wma": "audio/x-ms-wma",
|
110 | "wmv": "video/x-ms-wmv",
|
111 | "xls": "application/vnd.ms-excel",
|
112 | "xlw": "application/vnd.ms-excel",
|
113 | "xml": "text/xml",
|
114 | "zip": "application/zip"
|
115 | },
|
116 | rangeSize: 500 * 1024,
|
117 | status: {
|
118 | 200: "OK",
|
119 | 201: "Created",
|
120 | 202: "Accepted",
|
121 | 203: "Non-Authoritative Information",
|
122 | 204: "No Content",
|
123 | 205: "Reset Content",
|
124 | 206: "Partial Content",
|
125 | 207: "Multi-Status",
|
126 | 208: "Already Reported",
|
127 | 226: "IM Used",
|
128 | 300: "Multiple Choices",
|
129 | 301: "Moved Permanently",
|
130 | 302: "Found",
|
131 | 303: "See Other",
|
132 | 304: "Not Modified",
|
133 | 305: "Use Proxy",
|
134 | 307: "Temporary Redirect",
|
135 | 308: "Permanent Redirect",
|
136 | 400: "Bad Request",
|
137 | 401: "Unauthorized",
|
138 | 402: "Payment Required",
|
139 | 403: "Forbidden",
|
140 | 404: "Not Found",
|
141 | 405: "Method Not Allowed",
|
142 | 406: "Not Acceptable",
|
143 | 407: "Proxy Authentication Required",
|
144 | 408: "Request Timeout",
|
145 | 409: "Conflict",
|
146 | 410: "Gone",
|
147 | 411: "Length Required",
|
148 | 412: "Precondition Failed",
|
149 | 413: "Payload Too Large",
|
150 | 414: "URI Too Long",
|
151 | 415: "Unsupported Media Type",
|
152 | 416: "Range Not Satisfiable",
|
153 | 417: "Expectation Failed",
|
154 | 421: "Misdirected Request",
|
155 | 422: "Unprocessable Entity",
|
156 | 423: "Locked",
|
157 | 424: "Failed Dependency",
|
158 | 425: "Too Early",
|
159 | 426: "Upgrade Required",
|
160 | 428: "Precondition Required",
|
161 | 429: "Too Many Requests",
|
162 | 451: "Unavailable For Legal Reasons",
|
163 | 500: "Internal Server Error",
|
164 | 501: "Not Implemented",
|
165 | 502: "Bad Gateway",
|
166 | 503: "Service Unavailable",
|
167 | 504: "Gateway Timeout",
|
168 | 505: "HTTP Version Not Supported",
|
169 | 506: "Variant Also Negotiates",
|
170 | 507: "Insufficient Storage",
|
171 | 508: "Loop Detected",
|
172 | 509: "Bandwidth Limit Exceeded",
|
173 | 510: "Not Extended",
|
174 | 511: "Network Authentication Required"
|
175 | },
|
176 | tmp: (
|
177 | process.env.TMPDIR ||
|
178 | process.env.TEMP ||
|
179 | process.env.TMP ||
|
180 | (
|
181 | process.platform === "win32" ?
|
182 |
|
183 | (process.env.SystemRoot || process.env.windir) + "\\temp" :
|
184 | "/tmp"
|
185 | )
|
186 | ).replace(/([^:])[\/\\]+$/, "$1") + "/up-" + process.pid + "-",
|
187 | http: {
|
188 | port: 8080
|
189 | }
|
190 | }
|
191 | , hasOwn = defaultOpts.hasOwnProperty
|
192 | , cookieRe = /[^!#-~]|[%,;\\]/g
|
193 | , rangeRe = /^bytes=(\d*)-(\d*)^/
|
194 |
|
195 | module.exports = createApp
|
196 | createApp.setCookie = setCookie
|
197 | createApp.getCookie = getCookie
|
198 |
|
199 | function createApp(opts_) {
|
200 | var key
|
201 | , uses = []
|
202 | , opts = util.deepAssign(app.opts = {defaults: defaultOpts}, defaultOpts, opts_)
|
203 |
|
204 | event.asEmitter(app)
|
205 | if (!opts._accept) opts._accept = require("./accept").accept(opts.accept)
|
206 | if (!opts._encoding) opts._encoding = require("./accept").accept(opts.encoding)
|
207 |
|
208 | Object.keys(opts.method).forEach(function(method) {
|
209 | app[opts.method[method]] = function() {
|
210 | var arr = uses.slice.call(arguments)
|
211 | if (typeof arr[0] === "function") {
|
212 | arr.unshift(null)
|
213 | }
|
214 | arr.unshift(method)
|
215 | return use.apply(app, arr)
|
216 | }
|
217 | })
|
218 |
|
219 | app.listen = listen
|
220 | app.readBody = readBody
|
221 | app.static = createStatic
|
222 | app.use = use
|
223 |
|
224 | return app
|
225 |
|
226 | function app(req, res, _next) {
|
227 | var oldPath, oldUrl
|
228 | , tryCatch = true
|
229 | , usePos = 0
|
230 | , forwarded = req.headers[opts.ipHeader || "x-forwarded-for"]
|
231 |
|
232 | if (!res.send) {
|
233 | req.date = new Date()
|
234 | req.ip = forwarded ? forwarded.split(/[\s,]+/)[0] : req.connection && req.connection.remoteAddress
|
235 | req.opts = res.opts = opts
|
236 | req.res = res
|
237 | res.req = req
|
238 | res.send = send
|
239 | res.sendStatus = sendStatus
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | if (req.url.length > opts.maxURILength) {
|
245 | return sendError(res, opts, "URI Too Long")
|
246 | }
|
247 |
|
248 | req.content = content
|
249 | req.cookie = getCookie
|
250 | req.originalUrl = req.url
|
251 |
|
252 | res.cookie = setCookie
|
253 | res.link = setLink
|
254 | res.sendFile = sendFile
|
255 | }
|
256 |
|
257 | next()
|
258 |
|
259 | function next(err) {
|
260 | if (err) {
|
261 | return sendError(res, opts, err)
|
262 | }
|
263 | var method = uses[usePos]
|
264 | , path = uses[usePos + 1]
|
265 | , pos = usePos += 3
|
266 |
|
267 | if (method && method !== req.method || path && path !== req.url.slice(0, path.length)) {
|
268 | next()
|
269 | } else if (uses[pos - 1] === void 0) {
|
270 | if (typeof _next === "function") {
|
271 | _next()
|
272 | } else {
|
273 | res.sendStatus(404)
|
274 | }
|
275 | } else {
|
276 | method = uses[pos - 1]
|
277 | if (path) {
|
278 | oldPath = req.baseUrl
|
279 | oldUrl = req.url
|
280 | req.baseUrl = path
|
281 | req.url = req.url.slice(path.length) || "/"
|
282 | }
|
283 | if (tryCatch === true) {
|
284 | tryCatch = false
|
285 | try {
|
286 | method.call(app, req, res, path ? nextPath : next, opts)
|
287 | } catch(e) {
|
288 | return sendError(res, opts, e)
|
289 | }
|
290 | } else {
|
291 | method.call(app, req, res, path ? nextPath : next, opts)
|
292 | }
|
293 | if (pos === usePos) {
|
294 | tryCatch = true
|
295 | }
|
296 | }
|
297 | }
|
298 | function nextPath(e) {
|
299 | req.baseUrl = oldPath
|
300 | req.url = oldUrl
|
301 | next(e)
|
302 | }
|
303 | }
|
304 |
|
305 | function use(method, path) {
|
306 | var fn
|
307 | , arr = Array.from(arguments)
|
308 | , len = arr.length
|
309 | , i = 2
|
310 | if (typeof method === "function") {
|
311 | method = path = null
|
312 | i = 0
|
313 | } else if (typeof path === "function") {
|
314 | path = method
|
315 | method = null
|
316 | i = 1
|
317 | }
|
318 | for (; i < len; ) {
|
319 | if (typeof arr[i] !== "function") throw Error("Not a function")
|
320 | uses.push(method, path, arr[i++])
|
321 | }
|
322 | return app
|
323 | }
|
324 | }
|
325 |
|
326 | function createStatic(root_, opts_) {
|
327 | var root = path.resolve(root_)
|
328 | , opts = util.deepAssign({
|
329 | index: "index.html",
|
330 | maxAge: 31536000,
|
331 | cache: {
|
332 | "cache.manifest": 0,
|
333 | "worker.js": 0
|
334 | }
|
335 | }, opts_)
|
336 |
|
337 | resolveFile("cache")
|
338 | resolveFile("headers")
|
339 |
|
340 | return function(req, res, next) {
|
341 | var file
|
342 |
|
343 | if (req.method !== "GET" && req.method !== "HEAD") {
|
344 | res.setHeader("Allow", "GET, HEAD")
|
345 | return res.sendStatus(405)
|
346 | }
|
347 |
|
348 | if (req.url === "/" && !opts.index) {
|
349 | return res.sendStatus(404)
|
350 | }
|
351 |
|
352 | try {
|
353 | file = opts.url = path.resolve(root, (
|
354 | req.url === "/" ?
|
355 | opts.index :
|
356 | "." + decodeURIComponent(req.url.split("?")[0].replace(/\+/g, " "))
|
357 | ))
|
358 | } catch (e) {
|
359 | return res.sendStatus(400)
|
360 | }
|
361 |
|
362 | if (file.slice(0, root.length) !== root) {
|
363 | return res.sendStatus(404)
|
364 | }
|
365 | res.sendFile(file, opts, function(err) {
|
366 | next()
|
367 | })
|
368 | }
|
369 |
|
370 | function resolveFile(name) {
|
371 | if (!opts[name]) return
|
372 | var file
|
373 | , map = opts[name]
|
374 | opts[name] = {}
|
375 | for (file in map) if (hasOwn.call(map, file)) {
|
376 | opts[name][file === "*" ? file : path.resolve(root, file)] = map[file]
|
377 | }
|
378 | }
|
379 | }
|
380 |
|
381 | function readBody(req, res, next, opts) {
|
382 | if (req.body === void 0 && opts.bodyRe.test(req.method)) {
|
383 | req.content(next)
|
384 | } else {
|
385 | next()
|
386 | }
|
387 | }
|
388 |
|
389 | function send(body, opts_) {
|
390 | var tmp
|
391 | , res = this
|
392 | , reqHead = res.req.headers
|
393 | , resHead = {}
|
394 | , negod = res.opts._accept(reqHead.accept || reqHead["content-type"] || "*/*")
|
395 | , opts = Object.assign({statusCode: res.statusCode}, res.opts, negod, opts_)
|
396 | , outStream = opts.stream || res
|
397 |
|
398 | if (!negod.match) {
|
399 | return res.sendStatus(406)
|
400 | }
|
401 |
|
402 | tmp = opts.cache && opts.filename && opts.cache[opts.filename] || opts.maxAge
|
403 | if (typeof tmp === "number") {
|
404 |
|
405 | resHead["Cache-Control"] = tmp > 0 ? "public, max-age=" + tmp : "no-cache, max-age=0"
|
406 | }
|
407 |
|
408 | if (opts.mtime && opts.mtime > Date.parse(reqHead["if-modified-since"])) {
|
409 | return res.sendStatus(304)
|
410 | }
|
411 |
|
412 | if (typeof body !== "string") {
|
413 | negod.select = opts && opts.select || res.req.url.split("$select")[1] || ""
|
414 | body = negod.o(body, negod)
|
415 | opts.mimeType = negod.rule
|
416 | }
|
417 |
|
418 | resHead["Content-Type"] = opts.mimeType + (
|
419 | opts.charset && opts.mimeType.slice(0, 5) === "text/" ? "; charset=" + opts.charset : ""
|
420 | )
|
421 |
|
422 | if (opts.size > 0 || opts.size === 0) {
|
423 | resHead["Content-Length"] = opts.size
|
424 | if (opts.size > opts.rangeSize) {
|
425 | resHead["Accept-Ranges"] = "bytes"
|
426 | resHead["Content-Length"] = opts.size
|
427 |
|
428 | if (tmp = reqHead.range && !reqHead["if-range"] && rangeRe.exec(reqHead.range)) {
|
429 | opts.start = range[1] ? +range[1] : range[2] ? opts.size - range[2] - 1 : 0
|
430 | opts.end = range[1] && range[2] ? +range[2] : opts.size - 1
|
431 |
|
432 | if (opts.start > opts.end || opts.end >= opts.size) {
|
433 | opts.start = 0
|
434 | opts.end = opts.size - 1
|
435 | } else {
|
436 | opts.statusCode = 206
|
437 | resHead["Content-Length"] = opts.end - opts.start
|
438 | resHead["Content-Range"] = "bytes " + opts.start + "-" + opts.end + "/" + opts.size
|
439 | }
|
440 | }
|
441 | }
|
442 | }
|
443 |
|
444 | if (opts.filename) {
|
445 | resHead["Content-Disposition"] = "attachment; filename=" + (
|
446 | typeof opts.filename === "function" ? opts.filename() : opts.filename
|
447 | )
|
448 | }
|
449 |
|
450 | negod = opts.compress && opts._encoding(reqHead["accept-encoding"])
|
451 | if (negod.match) {
|
452 |
|
453 |
|
454 |
|
455 |
|
456 | delete resHead["Content-Length"]
|
457 | resHead["Content-Encoding"] = negod.match
|
458 | resHead.Vary = "Accept-Encoding"
|
459 | outStream = typeof negod.o === "string" ? require("zlib")[negod.o]() : negod.o()
|
460 | outStream.pipe(res)
|
461 | }
|
462 |
|
463 | if (opts.headers) Object.assign(resHead, opts.headers["*"], opts.headers[opts.url || res.req.url])
|
464 | res.writeHead(opts.statusCode || 200, resHead)
|
465 |
|
466 | if (res.req.method == "HEAD") {
|
467 | return res.end()
|
468 | }
|
469 |
|
470 | if (opts.sendfile) {
|
471 | fs.createReadStream(opts.sendfile, {start: opts.start, end: opts.end}).pipe(outStream)
|
472 | } else {
|
473 | outStream.end(body)
|
474 | }
|
475 | }
|
476 |
|
477 | function sendFile(file, opts_, next_) {
|
478 | var res = this
|
479 | , opts = typeof opts_ === "function" ? (next = opts_) && {} : opts_ || {}
|
480 | , next = typeof next_ === "function" ? next_ : function(code) {
|
481 | res.sendStatus(code)
|
482 | }
|
483 |
|
484 | fs.stat(file, function(err, stat) {
|
485 | if (err) return next(404)
|
486 | if (stat.isDirectory()) return next(403)
|
487 |
|
488 | opts.mtime = stat.mtime
|
489 | opts.size = stat.size
|
490 | opts.filename = opts.download === true ? file.split("/").pop() : opts.download
|
491 | opts.mimeType = res.opts.mime[ file.split(".").pop() ] || "application/octet-stream"
|
492 | opts.sendfile = file
|
493 |
|
494 | res.send(file, opts)
|
495 | })
|
496 | }
|
497 |
|
498 | function sendStatus(code, message) {
|
499 | var res = this
|
500 | res.statusCode = code
|
501 | if (code > 199 && code != 204 && code != 304) {
|
502 | res.setHeader("Content-Type", "text/plain")
|
503 | message = (message || res.opts.status[code] || code) + "\n"
|
504 | res.setHeader("Content-Length", message.length)
|
505 | if ("HEAD" != res.req.method) {
|
506 | res.write(message)
|
507 | }
|
508 | }
|
509 | res.end()
|
510 | }
|
511 |
|
512 | function sendError(res, opts, e) {
|
513 | var message = typeof e === "string" ? e : e.message
|
514 | , map = opts.error && (opts.error[message] || opts.error[e.name]) || {}
|
515 | , error = {
|
516 | id: Math.random().toString(36).slice(2,10),
|
517 | time: res.req.date,
|
518 | code: map.code || e.code || 500,
|
519 | message: map.message || message
|
520 | }
|
521 | res.statusCode = error.code
|
522 | res.statusMessage = opts.status[error.code] || message
|
523 |
|
524 | res.send(error)
|
525 |
|
526 | opts.log.error(
|
527 | (e.stack || (e.name || "Error") + ": " + error.message).replace(":", ":" + error.id)
|
528 | )
|
529 | }
|
530 |
|
531 | function setLink(url, rel) {
|
532 | var res = this
|
533 | , existing = res.getHeader("link") || []
|
534 |
|
535 | if (!Array.isArray(existing)) {
|
536 | existing = [ existing ]
|
537 | }
|
538 |
|
539 | existing.push("<" + encodeURI(url) + ">; rel=\"" + rel + "\"")
|
540 |
|
541 | res.setHeader("Link", existing)
|
542 | }
|
543 |
|
544 | function listen() {
|
545 | var exiting
|
546 | , server = this
|
547 | , opts = server.opts
|
548 |
|
549 | process.on("uncaughtException", function(e) {
|
550 | if (opts.log) opts.log.error(
|
551 | "\nUNCAUGHT EXCEPTION!\n" +
|
552 | (e.stack || (e.name || "Error") + ": " + (e.message || e))
|
553 | )
|
554 | else throw e
|
555 | ;(opts.exit || exit).call(server, 1)
|
556 | })
|
557 |
|
558 | process.on("SIGINT", function() {
|
559 | if (exiting) {
|
560 | opts.log.info("\nKilling from SIGINT (got Ctrl-C twice)")
|
561 | return process.exit()
|
562 | }
|
563 | exiting = true
|
564 | opts.log.info("\nGracefully shutting down from SIGINT (Ctrl-C)")
|
565 | ;(opts.exit || exit).call(server, 0)
|
566 | })
|
567 |
|
568 | process.on("SIGTERM", function() {
|
569 | opts.log.info("Gracefully shutting down from SIGTERM (kill)")
|
570 | ;(opts.exit || exit).call(server, 0)
|
571 | })
|
572 |
|
573 | process.on("SIGHUP", function() {
|
574 | opts.log.info("Reloading configuration from SIGHUP")
|
575 | server.listen(true)
|
576 | server.emit("reload")
|
577 | })
|
578 |
|
579 | server.listen = opts.listen || function() {
|
580 | ;["http", "https", "http2"].forEach(createNet)
|
581 | }
|
582 |
|
583 | server.listen()
|
584 |
|
585 | return server
|
586 |
|
587 | function createNet(proto) {
|
588 | var map = opts[proto]
|
589 | , net = server["_" + proto] && !server["_" + proto].close()
|
590 | if (!map || !map.port) return
|
591 | net = server["_" + proto] = (
|
592 | proto == "http" ?
|
593 | require(proto).createServer(map.redirect ? forceHttps : server) :
|
594 | require(proto).createSecureServer(map, map.redirect ? forceHttps : server)
|
595 | )
|
596 | .listen(map.port, map.host || "0.0.0.0", function() {
|
597 | var addr = this.address()
|
598 | opts.log.info("Listening %s at %s:%s", proto, addr.address, addr.port)
|
599 | this.on("close", function() {
|
600 | opts.log.info("Stop listening %s at %s:%s", proto, addr.address, addr.port)
|
601 | })
|
602 | if (map.noDelay) this.on("connection", setNoDelay)
|
603 | })
|
604 | if (map.sessionReuse) {
|
605 | var sessionStore = {}
|
606 | , timeout = map.sessionTimeout || 300
|
607 |
|
608 | net
|
609 | .on("newSession", function(id, data, cb) {
|
610 | sessionStore[id] = data
|
611 | cb()
|
612 | })
|
613 | .on("resumeSession", function(id, cb) {
|
614 | cb(null, sessionStore[id] || null)
|
615 | })
|
616 | }
|
617 | }
|
618 |
|
619 | function setNoDelay(socket) {
|
620 | if (socket.setNoDelay) socket.setNoDelay(true)
|
621 | }
|
622 |
|
623 | function forceHttps(req, res) {
|
624 |
|
625 |
|
626 |
|
627 | var port = opts.https && opts.https.port || 8443
|
628 | , host = (req.headers.host || "localhost").split(":")[0]
|
629 | , url = "https://" + (port == 443 ? host : host + ":" + port) + req.url
|
630 |
|
631 | res.writeHead(301, {"Content-Type": "text/html", "Location": url})
|
632 | res.end("Redirecting to <a href=\"" + url + "\">" + url + "</a>")
|
633 | }
|
634 |
|
635 | function exit(code) {
|
636 | var softKill = util.wait(function() {
|
637 | opts.log.info("Everything closed cleanly")
|
638 | process.exit(code)
|
639 | }, 1)
|
640 |
|
641 | server.emit("beforeExit", softKill)
|
642 |
|
643 | try {
|
644 | if (server._http) server._http.close(softKill.wait()).unref()
|
645 | if (server._https) server._https.close(softKill.wait()).unref()
|
646 | if (server._http2) server._http2.close(softKill.wait()).unref()
|
647 | } catch(e) {}
|
648 |
|
649 | setTimeout(function() {
|
650 | opts.log.warn("Kill (timeout)")
|
651 | process.exit(code)
|
652 | }, opts.exitTime).unref()
|
653 |
|
654 | softKill()
|
655 | }
|
656 | }
|
657 |
|
658 |
|
659 | function setCookie(opts, value) {
|
660 | var res = this
|
661 | , existing = res.getHeader("set-cookie")
|
662 | , cookie = (typeof opts === "string" ? (opts = { name: opts }) : opts).name +
|
663 | ("=" + value).replace(cookieRe, encodeURIComponent) +
|
664 | (opts.maxAge ? "; Expires=" + new Date(opts.maxAge > 0 ? Date.now() + (opts.maxAge*1000) : 0).toUTCString() : "") +
|
665 | (opts.path ? "; Path=" + opts.path : "") +
|
666 | (opts.domain ? "; Domain=" + opts.domain : "") +
|
667 | (opts.secure ? "; Secure" : "") +
|
668 | (opts.httpOnly ? "; HttpOnly" : "") +
|
669 | (opts.sameSite ? "; SameSite=" + opts.sameSite : "")
|
670 |
|
671 | if (Array.isArray(existing)) {
|
672 | existing.push(cookie)
|
673 | } else {
|
674 | res.setHeader("Set-Cookie", existing ? [existing, cookie] : cookie)
|
675 | }
|
676 | }
|
677 |
|
678 | function getCookie(opts) {
|
679 | var req = this
|
680 | , name = (typeof opts === "string" ? (opts = { name: opts }) : opts).name
|
681 | , junks = ("; " + req.headers.cookie).split("; " + name + "=")
|
682 |
|
683 | if (junks.length > 2) {
|
684 | ;(opts.path || "").split("/").map(function(val, key, arr) {
|
685 | var map = {
|
686 | name: name,
|
687 | maxAge: -1,
|
688 | path: arr.slice(0, key + 1).join("/")
|
689 | }
|
690 | , domain = opts.domain
|
691 | req.res.cookie(map, "")
|
692 | if (domain) {
|
693 | map.domain = domain
|
694 | req.res.cookie(map, "")
|
695 |
|
696 | if (domain !== (domain = domain.replace(/^[^.]+(?=\.(?=.+\.))/, "*"))) {
|
697 | map.domain = domain
|
698 | req.res.cookie(map, "")
|
699 | }
|
700 | }
|
701 | })
|
702 | req.opts.log.warn("Cookie fixation detected: %s", req.headers.cookie)
|
703 | } else try {
|
704 | return decodeURIComponent((junks[1] || "").split(";")[0])
|
705 | } catch(e) {
|
706 | req.opts.log.warn("Invalid cookie '%s' in: %s", name, req.headers.cookie)
|
707 | }
|
708 | return ""
|
709 | }
|
710 |
|
711 |
|