@foxify/http
Version:
Foxify HTTP module
295 lines • 10.5 kB
JavaScript
"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