UNPKG

@foxify/http

Version:
295 lines 10.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.settings = exports.DEFAULT_SETTINGS = void 0; const http_1 = require("http"); const net_1 = require("net"); const proxy_addr_1 = __importStar(require("proxy-addr")); const qs = __importStar(require("qs")); const type_is_1 = __importDefault(require("type-is")); const utils_1 = require("./utils"); // eslint-disable-next-line import/exports-last exports.DEFAULT_SETTINGS = { "query.parser": qs.parse, "trust.proxy": (0, proxy_addr_1.compile)([]), "subdomain.offset": 2, }; const SETTINGS = { ...exports.DEFAULT_SETTINGS }; class Request extends http_1.IncomingMessage { params = {}; _acceptsCache; _parsedUrl; _queryCache; /** * Parse the "Host" header field to a hostname. * * When the "trust.proxy" setting trusts the socket * address, the "X-Forwarded-Host" header field will * be trusted. */ get hostname() { let host = this.get("x-forwarded-host"); if (!host || !SETTINGS["trust.proxy"](this.socket.remoteAddress, 0)) { host = this.get("host"); } else if (host.includes(",")) { // Note: X-Forwarded-Host is normally only ever a // single value, but this is to be safe. host = host.slice(0, host.indexOf(",")).trimRight(); } if (!host) return; // IPv6 literal support const offset = host.startsWith("[") ? host.indexOf("]") + 1 : 0; const index = host.indexOf(":", offset); return index === -1 ? host : host.slice(0, index); } /** * Return the remote address from the trusted proxy. * * This is the remote address on the socket unless * "trust.proxy" is set. */ get ip() { return (0, proxy_addr_1.default)(this, SETTINGS["trust.proxy"]); } /** * When "trust.proxy" is set, trusted proxy addresses + client. * * For example if the value were "client, proxy1, proxy2" * you would receive the array `["client", "proxy1", "proxy2"]` * where "proxy2" is the furthest down-stream and "proxy1" and * "proxy2" were trusted. */ get ips() { const addresses = (0, proxy_addr_1.all)(this, SETTINGS["trust.proxy"]); // Reverse the order (to farthest -> closest) // and remove socket address addresses.reverse().pop(); return addresses; } /** * Short-hand for `url.parseUrl(req.url).pathname`. */ get path() { return (this._parsedUrl ?? (this._parsedUrl = (0, utils_1.parseUrl)(this.url))) .pathname; } /** * Return the protocol string "http" or "https" * when requested with TLS. When the "trust.proxy" * setting trusts the socket address, the * "X-Forwarded-Proto" header field will be trusted * and used if present. * * If you're running behind a reverse proxy that * supplies https for you this may be enabled. */ get protocol() { const proto = this.socket.encrypted ? "https" : "http"; if (!SETTINGS["trust.proxy"](this.socket.remoteAddress, 0)) return proto; // Note: X-Forwarded-Proto is normally only ever a // single value, but this is to be safe. const header = this.get("x-forwarded-proto") ?? proto; const index = header.indexOf(","); return index === -1 ? header.trim() : header.slice(0, index).trim(); } get query() { return (this._queryCache ?? (this._queryCache = SETTINGS["query.parser"]((this._parsedUrl ?? (this._parsedUrl = (0, utils_1.parseUrl)(this.url))).query))); } /** * Short-hand for: * * req.protocol === 'https' */ get secure() { return this.protocol === "https"; } /** * Return subdomains as an array. * * Subdomains are the dot-separated parts of the host before the main domain of * the app. By default, the domain of the app is assumed to be the last two * parts of the host. This can be changed by setting "subdomain offset". * * For example, if the domain is "tobi.ferrets.example.com": * If "subdomain.offset" is not set, req.subdomains is `["ferrets", "tobi"]`. * If "subdomain.offset" is 3, req.subdomains is `["tobi"]`. */ get subdomains() { const hostname = this.hostname; if (!hostname) return []; return ((0, net_1.isIP)(hostname) ? [hostname] : hostname.split(".").reverse()).slice(SETTINGS["subdomain.offset"]); } /** * Check if the request was an `_XMLHttpRequest_`. */ get xhr() { return ((this.get("x-requested-with") ?? "").toLowerCase() === "xmlhttprequest"); } get _accepts() { return this._acceptsCache ?? (this._acceptsCache = new utils_1.Accepts(this)); } /** * Check if the given `type(s)` is acceptable, returning * the best match when true, otherwise `undefined`, in which * case you should respond with 406 "Not Acceptable". * * The `type` value may be a single MIME type string * such as "application/json", an extension name * such as "json", a comma-delimited list such as "json, html, text/plain", * an argument list such as `"json", "html", "text/plain"`, * When a list is given, the _best_ match, if any is returned. * * @example * // Accept: text/html * req.accepts("html"); * // => "html" * @example * // Accept: text/*, application/json * req.accepts("html"); * // => "html" * req.accepts("text/html"); * // => "text/html" * req.accepts("json, text"); * // => "json" * req.accepts("application/json"); * // => "application/json" * @example * // Accept: text/*, application/json * req.accepts("image/png"); * req.accepts("png"); * // => undefined * @example * // Accept: text/*;q=.5, application/json * req.accepts("html", "json"); * req.accepts("html, json"); * // => "json" */ accepts(...types) { return this._accepts.types(types); } /** * Check if the given `charset`s are acceptable, * otherwise you should respond with 406 "Not Acceptable". */ acceptsCharsets(...charsets) { return this._accepts.charsets(charsets); } /** * Check if the given `encoding`s are accepted. */ acceptsEncodings(...encodings) { return this._accepts.encodings(encodings); } /** * Check if the given `language`s are acceptable, * otherwise you should respond with 406 "Not Acceptable". */ acceptsLanguages(...languages) { return this._accepts.languages(languages); } /** * Return request header. * * The `Referrer` header field is special-cased, * both `Referrer` and `Referer` are interchangeable. * * @example * req.get("Content-Type"); // => "text/plain" * @example * req.get("content-type"); // => "text/plain" * @example * req.get("Something"); // => undefined */ get(name) { name = name.toLowerCase(); switch (name) { case "referer": return this.headers.referer ?? this.headers.referrer; case "referrer": return this.headers.referrer ?? this.headers.referer; default: return this.headers[name]; } } /** * Check if the incoming request contains the "Content-Type" * header field, and it contains the give mime `type`. * * @example * // With Content-Type: text/html; charset=utf-8 * req.is("html"); * req.is("text/html"); * req.is("text/*"); * // => true * @example * // When Content-Type is application/json * req.is("json"); * req.is("application/json"); * req.is("application/*"); * // => true * @example * req.is("html"); * // => false */ is(...types) { return (0, type_is_1.default)(this, types); } /** * Parse Range header field, capping to the given `size`. * * Unspecified ranges such as "0-" require knowledge of your resource length. In * the case of a byte range this is of course the total number of bytes. If the * Range header field is not given `undefined` is returned, `-1` when unsatisfiable, * and `-2` when syntactically invalid. * * When ranges are returned, the array has a "type" property which is the type of * range that is required (most commonly, "bytes"). Each array element is an object * with a "start" and "end" property for the portion of the range. * * NOTE: remember that ranges are inclusive, so for example "Range: users=0-3" * should respond with 4 users when available, not 3. */ range(size, combine) { const range = this.get("range"); if (!range) return; return (0, utils_1.rangeParser)(size, range, combine); } } exports.default = Request; // eslint-disable-next-line @typescript-eslint/no-shadow function settings(settings = exports.DEFAULT_SETTINGS) { Object.assign(SETTINGS, settings); } exports.settings = settings; //# sourceMappingURL=Request.js.map