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