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