Files
3dverse-testtask/vendor/deno.land/x/oak@v12.1.0/request.ts
T

265 lines
8.9 KiB
TypeScript

// Copyright 2018-2022 the oak authors. All rights reserved. MIT license.
import type {
Body,
BodyBytes,
BodyForm,
BodyFormData,
BodyJson,
BodyOptions,
BodyReader,
BodyStream,
BodyText,
} from "./body.ts";
import { RequestBody } from "./body.ts";
import { accepts, acceptsEncodings, acceptsLanguages } from "./deps.ts";
import type { HTTPMethods, ServerRequest } from "./types.d.ts";
export interface OakRequestOptions {
jsonBodyReviver?: (key: string, value: unknown) => unknown;
proxy?: boolean;
secure?: boolean;
}
/** An interface which provides information about the current request. The
* instance related to the current request is available on the
* {@linkcode Context}'s `.request` property.
*
* The interface contains several properties to get information about the
* request as well as several methods, which include content negotiation and
* the ability to decode a request body.
*/
export class Request {
#body: RequestBody;
#proxy: boolean;
#secure: boolean;
#serverRequest: ServerRequest;
#url?: URL;
#getRemoteAddr(): string {
return this.#serverRequest.remoteAddr ?? "";
}
/** Is `true` if the request might have a body, otherwise `false`.
*
* **WARNING** this is an unreliable API. In HTTP/2 in many situations you
* cannot determine if a request has a body or not unless you attempt to read
* the body, due to the streaming nature of HTTP/2. As of Deno 1.16.1, for
* HTTP/1.1, Deno also reflects that behaviour. The only reliable way to
* determine if a request has a body or not is to attempt to read the body.
*/
get hasBody(): boolean {
return this.#body.has();
}
/** The `Headers` supplied in the request. */
get headers(): Headers {
return this.#serverRequest.headers;
}
/** Request remote address. When the application's `.proxy` is true, the
* `X-Forwarded-For` will be used to determine the requesting remote address.
*/
get ip(): string {
return (this.#proxy ? this.ips[0] : this.#getRemoteAddr()) ?? "";
}
/** When the application's `.proxy` is `true`, this will be set to an array of
* IPs, ordered from upstream to downstream, based on the value of the header
* `X-Forwarded-For`. When `false` an empty array is returned. */
get ips(): string[] {
return this.#proxy
? (this.#serverRequest.headers.get("x-forwarded-for") ??
this.#getRemoteAddr()).split(/\s*,\s*/)
: [];
}
/** The HTTP Method used by the request. */
get method(): HTTPMethods {
return this.#serverRequest.method as HTTPMethods;
}
/** Shortcut to `request.url.protocol === "https:"`. */
get secure(): boolean {
return this.#secure;
}
/** Set to the value of the _original_ Deno server request. */
get originalRequest(): ServerRequest {
return this.#serverRequest;
}
/** A parsed URL for the request which complies with the browser standards.
* When the application's `.proxy` is `true`, this value will be based off of
* the `X-Forwarded-Proto` and `X-Forwarded-Host` header values if present in
* the request. */
get url(): URL {
if (!this.#url) {
const serverRequest = this.#serverRequest;
if (!this.#proxy) {
// between 1.9.0 and 1.9.1 the request.url of the native HTTP started
// returning the full URL, where previously it only returned the path
// so we will try to use that URL here, but default back to old logic
// if the URL isn't valid.
try {
if (serverRequest.rawUrl) {
this.#url = new URL(serverRequest.rawUrl);
return this.#url;
}
} catch {
// we don't care about errors here
}
}
let proto: string;
let host: string;
if (this.#proxy) {
proto = serverRequest
.headers.get("x-forwarded-proto")?.split(/\s*,\s*/, 1)[0] ??
"http";
host = serverRequest.headers.get("x-forwarded-host") ??
serverRequest.headers.get("host") ?? "";
} else {
proto = this.#secure ? "https" : "http";
host = serverRequest.headers.get("host") ?? "";
}
try {
this.#url = new URL(`${proto}://${host}${serverRequest.url}`);
} catch {
throw new TypeError(
`The server request URL of "${proto}://${host}${serverRequest.url}" is invalid.`,
);
}
}
return this.#url;
}
constructor(
serverRequest: ServerRequest,
{ proxy = false, secure = false, jsonBodyReviver }: OakRequestOptions = {},
) {
this.#proxy = proxy;
this.#secure = secure;
this.#serverRequest = serverRequest;
this.#body = new RequestBody(
serverRequest.getBody(),
serverRequest.headers,
jsonBodyReviver,
);
}
/** Returns an array of media types, accepted by the requestor, in order of
* preference. If there are no encodings supplied by the requestor,
* then accepting any is implied is returned.
*/
accepts(): string[] | undefined;
/** For a given set of media types, return the best match accepted by the
* requestor. If there are no encoding that match, then the method returns
* `undefined`.
*/
accepts(...types: string[]): string | undefined;
accepts(...types: string[]): string | string[] | undefined {
if (!this.#serverRequest.headers.has("Accept")) {
return types.length ? types[0] : ["*/*"];
}
if (types.length) {
return accepts(this.#serverRequest, ...types);
}
return accepts(this.#serverRequest);
}
/** Returns an array of encodings, accepted by the requestor, in order of
* preference. If there are no encodings supplied by the requestor,
* then `["*"]` is returned, matching any.
*/
acceptsEncodings(): string[] | undefined;
/** For a given set of encodings, return the best match accepted by the
* requestor. If there are no encodings that match, then the method returns
* `undefined`.
*
* **NOTE:** You should always supply `identity` as one of the encodings
* to ensure that there is a match when the `Accept-Encoding` header is part
* of the request.
*/
acceptsEncodings(...encodings: string[]): string | undefined;
acceptsEncodings(...encodings: string[]): string[] | string | undefined {
if (!this.#serverRequest.headers.has("Accept-Encoding")) {
return encodings.length ? encodings[0] : ["*"];
}
if (encodings.length) {
return acceptsEncodings(this.#serverRequest, ...encodings);
}
return acceptsEncodings(this.#serverRequest);
}
/** Returns an array of languages, accepted by the requestor, in order of
* preference. If there are no languages supplied by the requestor,
* `["*"]` is returned, indicating any language is accepted.
*/
acceptsLanguages(): string[] | undefined;
/** For a given set of languages, return the best match accepted by the
* requestor. If there are no languages that match, then the method returns
* `undefined`. */
acceptsLanguages(...langs: string[]): string | undefined;
acceptsLanguages(...langs: string[]): string[] | string | undefined {
if (!this.#serverRequest.headers.get("Accept-Language")) {
return langs.length ? langs[0] : ["*"];
}
if (langs.length) {
return acceptsLanguages(this.#serverRequest, ...langs);
}
return acceptsLanguages(this.#serverRequest);
}
body(options: BodyOptions<"bytes">): BodyBytes;
body(options: BodyOptions<"form">): BodyForm;
body(options: BodyOptions<"form-data">): BodyFormData;
body(options: BodyOptions<"json">): BodyJson;
body(options: BodyOptions<"reader">): BodyReader;
body(options: BodyOptions<"stream">): BodyStream;
body(options: BodyOptions<"text">): BodyText;
body(options?: BodyOptions): Body;
/** Access the body of the request. This is a method, because there are
* several options which can be provided which can influence how the body is
* handled. */
body(options: BodyOptions = {}): Body | BodyReader | BodyStream {
return this.#body.get(options);
}
[Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) {
const { hasBody, headers, ip, ips, method, secure, url } = this;
return `${this.constructor.name} ${
inspect({
hasBody,
headers,
ip,
ips,
method,
secure,
url: url.toString(),
})
}`;
}
[Symbol.for("nodejs.util.inspect.custom")](
depth: number,
// deno-lint-ignore no-explicit-any
options: any,
inspect: (value: unknown, options?: unknown) => string,
) {
if (depth < 0) {
return options.stylize(`[${this.constructor.name}]`, "special");
}
const newOptions = Object.assign({}, options, {
depth: options.depth === null ? null : options.depth - 1,
});
const { hasBody, headers, ip, ips, method, secure, url } = this;
return `${options.stylize(this.constructor.name, "special")} ${
inspect(
{ hasBody, headers, ip, ips, method, secure, url },
newOptions,
)
}`;
}
}