update
このコミットが含まれているのは:
コミット
bf270d2feb
|
@ -4,18 +4,17 @@ import AssignmentTags from "./AssignmentTags.js";
|
|||
export default {
|
||||
components: { Assignment, AssignmentTags },
|
||||
template: `
|
||||
<section id="section-1" v-show="assignments.length">
|
||||
<h2 class="font-bold mb-2">{{ title }} <span>({{ assignments.length }})</span> </h2>
|
||||
<section id="section-1" v-show="visibleAssignments.length">
|
||||
<h2 class="font-bold mb-2">{{ title }} <span>({{ visibleAssignments.length }})</span> </h2>
|
||||
|
||||
<assignment-tags
|
||||
:initTags = "assignments.map(a => a.tag)"
|
||||
:current-tag = "currentTag"
|
||||
:initTags="assignments.map(a => a.tag)"
|
||||
:currentTag="currentTag"
|
||||
@change="currentTag = $event"
|
||||
|
||||
/>
|
||||
|
||||
<ul class="mt-6">
|
||||
<assignment v-for="assignment in assignments" :key="assignment.id" :assignment="assignment"/>
|
||||
<assignment v-for="assignment in visibleAssignments" :key="assignment.id" :assignment="assignment"/>
|
||||
</ul>
|
||||
</section>
|
||||
`,
|
||||
|
@ -32,14 +31,11 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
filteredAssignments() {
|
||||
return this.assignments.filter(
|
||||
(a) => a.tag === this.currentTag || this.currentTag === "all"
|
||||
);
|
||||
},
|
||||
|
||||
tags() {
|
||||
return new Set(this.assignments.map((a) => a.tag));
|
||||
visibleAssignments() {
|
||||
if (this.currentTag === "all") {
|
||||
return this.assignments;
|
||||
}
|
||||
return this.assignments.filter((a) => a.tag === this.currentTag);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ export default {
|
|||
class="border rounded p-1 text-xs"
|
||||
@click="$emit('change', tag)"
|
||||
v-for="tag in tags"
|
||||
:class="{ 'border-blue-500 text-blue-400': tag === currentTag}"
|
||||
:class="getButtonClass(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</button>
|
||||
|
@ -22,4 +22,12 @@ export default {
|
|||
return ["all", ...new Set(this.initTags)];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getButtonClass(tag) {
|
||||
return {
|
||||
"border-blue-500 text-blue-400": tag === this.currentTag,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,11 +20,7 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
assignments: [
|
||||
{ name: "Check Project", complete: true, id: 1, tag: "math" },
|
||||
{ name: "Read Books", complete: false, id: 2, tag: "science" },
|
||||
{ name: "Learn Vue", complete: false, id: 3, tag: "programming" },
|
||||
],
|
||||
assignments: [],
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -32,13 +28,19 @@ export default {
|
|||
filters() {
|
||||
return {
|
||||
inProgress: this.assignments.filter(
|
||||
(assignment) => !assignment.complete
|
||||
(assignment) => !assignment.complete,
|
||||
),
|
||||
completed: this.assignments.filter((assignment) => assignment.complete),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
fetch("http://localhost:3001/assignments")
|
||||
.then((res) => res.json())
|
||||
.then((data) => (this.assignments = data));
|
||||
},
|
||||
|
||||
methods: {
|
||||
add(name) {
|
||||
this.assignments.push({
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"assignments": [
|
||||
{ "name": "Check Project", "complete": true, "id": 1, "tag": "math" },
|
||||
{ "name": "Read Books", "complete": false, "id": 2, "tag": "science" },
|
||||
{ "name": "Learn Vue", "complete": false, "id": 3, "tag": "programming" },
|
||||
{ "name": "Learn React", "complete": false, "id": 4, "tag": "programming" },
|
||||
{
|
||||
"name": "Complete Laravel",
|
||||
"complete": true,
|
||||
"id": 5,
|
||||
"tag": "programming"
|
||||
},
|
||||
{ "name": "Do Algebra", "complete": false, "id": 6, "tag": "math" }
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
../json-server/lib/bin.js
|
|
@ -0,0 +1 @@
|
|||
../json5/lib/cli.js
|
|
@ -0,0 +1 @@
|
|||
../mime/bin/cli.js
|
|
@ -0,0 +1,42 @@
|
|||
const qs = require('querystring');
|
||||
|
||||
/**
|
||||
* @typedef ParsedURL
|
||||
* @type {import('.').ParsedURL}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Request
|
||||
* @property {string} url
|
||||
* @property {ParsedURL} _parsedUrl
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
* @returns {ParsedURL|void}
|
||||
*/
|
||||
function parse(req) {
|
||||
let raw = req.url;
|
||||
if (raw == null) return;
|
||||
|
||||
let prev = req._parsedUrl;
|
||||
if (prev && prev.raw === raw) return prev;
|
||||
|
||||
let pathname=raw, search='', query;
|
||||
|
||||
if (raw.length > 1) {
|
||||
let idx = raw.indexOf('?', 1);
|
||||
|
||||
if (idx !== -1) {
|
||||
search = raw.substring(idx);
|
||||
pathname = raw.substring(0, idx);
|
||||
if (search.length > 1) {
|
||||
query = qs.parse(search.substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return req._parsedUrl = { pathname, search, query, raw };
|
||||
}
|
||||
|
||||
exports.parse = parse;
|
|
@ -0,0 +1,40 @@
|
|||
import * as qs from 'querystring';
|
||||
|
||||
/**
|
||||
* @typedef ParsedURL
|
||||
* @type {import('.').ParsedURL}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Request
|
||||
* @property {string} url
|
||||
* @property {ParsedURL} _parsedUrl
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
* @returns {ParsedURL|void}
|
||||
*/
|
||||
export function parse(req) {
|
||||
let raw = req.url;
|
||||
if (raw == null) return;
|
||||
|
||||
let prev = req._parsedUrl;
|
||||
if (prev && prev.raw === raw) return prev;
|
||||
|
||||
let pathname=raw, search='', query;
|
||||
|
||||
if (raw.length > 1) {
|
||||
let idx = raw.indexOf('?', 1);
|
||||
|
||||
if (idx !== -1) {
|
||||
search = raw.substring(idx);
|
||||
pathname = raw.substring(0, idx);
|
||||
if (search.length > 1) {
|
||||
query = qs.parse(search.substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return req._parsedUrl = { pathname, search, query, raw };
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import type { IncomingMessage } from 'http';
|
||||
|
||||
export interface ParsedURL {
|
||||
pathname: string;
|
||||
search: string;
|
||||
query: Record<string, string | string[]> | void;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export function parse(req: IncomingMessage): ParsedURL;
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"version": "1.0.0-next.25",
|
||||
"name": "@polka/url",
|
||||
"repository": "lukeed/polka",
|
||||
"description": "Super fast, memoized `req.url` parser",
|
||||
"module": "build.mjs",
|
||||
"types": "index.d.ts",
|
||||
"main": "build.js",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./build.mjs",
|
||||
"require": "./build.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"build.*",
|
||||
"index.d.*"
|
||||
],
|
||||
"author": {
|
||||
"name": "Luke Edwards",
|
||||
"email": "luke@lukeed.com",
|
||||
"url": "https://lukeed.com"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
# @polka/url [![npm](https://badgen.now.sh/npm/v/@polka/url)](https://npmjs.org/package/@polka/url) [![licenses](https://licenses.dev/b/npm/%40polka%2Furl)](https://licenses.dev/npm/%40polka%2Furl)
|
||||
|
||||
> Super fast, memoized `req.url` parser; _not_ limited to [Polka][polka]!
|
||||
|
||||
Parses the `url` from a [`IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) request. The returned object will always only contain the following keys: `search`, `query`, `pathname`, and `raw`.
|
||||
|
||||
> **Note:** This library does not process `protocol`, `hostname`, `port`, etc.<br>This is because the incoming `req.url` value only begins with the path information.
|
||||
|
||||
Parsed requests will be mutated with a `_parsedUrl` key, containing the returned output. This is used for future memoization, avoiding the need to fully parse the same `url` value multiple times.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm install --save @polka/url
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const parse = require('@polka/url');
|
||||
|
||||
let req = {
|
||||
url: '/foo/bar?fizz=buzz'
|
||||
};
|
||||
let output = parse(req);
|
||||
//=> {
|
||||
//=> pathname: '/foo/bar',
|
||||
//=> raw: '/foo/bar?fizz=buzz',
|
||||
//=> search: '?fizz=buzz',
|
||||
//=> query: {
|
||||
//=> fizz: 'buzz'
|
||||
//=> },
|
||||
//=> }
|
||||
|
||||
// Attaches result for future memoization
|
||||
assert.deepEqual(output, req._parsedUrl); //=> true
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### url(req)
|
||||
Returns: `Object` or `undefined`
|
||||
|
||||
> **Important:** The `req` must have a `url` key, otherwise `undefined` will be returned.<br>If no input is provided at all, a `TypeError` will be thrown.
|
||||
|
||||
#### req
|
||||
Type: `IncomingMessage` or `{ url: string }`
|
||||
|
||||
The incoming HTTP request (`req`) or a plain `Object` with a `url` key.
|
||||
|
||||
> **Note:** In Node.js servers, the [`req.url`](https://nodejs.org/api/http.html#http_message_url) begins with a pathname & does not include a `hash`.
|
||||
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Check out the [`bench`](/bench) directory for in-depth benchmark results and comparisons.
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
Any issues or questions can be sent to the [Polka][polka] repository.<br>However, please specify that your inquiry is about `@polka/url` specifically.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Luke Edwards](https://lukeed.com)
|
||||
|
||||
[polka]: https://github.com/lukeed/polka
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,117 @@
|
|||
# @tinyhttp/accepts
|
||||
|
||||
> [`accepts`](https://github.com/jshttp/accepts) rewrite in TypeScript.
|
||||
|
||||
Higher level content negotiation based on
|
||||
[negotiator](https://www.npmjs.com/package/negotiator). Extracted from
|
||||
[koa](https://www.npmjs.com/package/koa) for general use.
|
||||
|
||||
In addition to negotiator, it allows:
|
||||
|
||||
- Allows types as an array or arguments list, ie
|
||||
`(['text/html', 'application/json'])` as well as
|
||||
`('text/html', 'application/json')`.
|
||||
- Allows type shorthands such as `json`.
|
||||
- Returns `false` when no types match
|
||||
- Treats non-existent headers as `*`
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/accepts
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import { Accepts } from '@tinyhttp/accepts'
|
||||
```
|
||||
|
||||
### accepts(req)
|
||||
|
||||
Create a new `Accepts` object for the given `req`.
|
||||
|
||||
#### `.charset(charsets)`
|
||||
|
||||
Return the first accepted charset. If nothing in `charsets` is accepted, then
|
||||
`false` is returned.
|
||||
|
||||
#### `.charsets()`
|
||||
|
||||
Return the charsets that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
#### `.encoding(encodings)`
|
||||
|
||||
Return the first accepted encoding. If nothing in `encodings` is accepted, then
|
||||
`false` is returned.
|
||||
|
||||
#### `.encodings()`
|
||||
|
||||
Return the encodings that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
#### `.language(languages)`
|
||||
|
||||
Return the first accepted language. If nothing in `languages` is accepted, then
|
||||
`false` is returned.
|
||||
|
||||
#### `.languages()`
|
||||
|
||||
Return the languages that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
#### `.type(types)`
|
||||
|
||||
Return the first accepted type (and it is returned as the same text as what
|
||||
appears in the `types` array). If nothing in `types` is accepted, then `false`
|
||||
is returned.
|
||||
|
||||
The `types` array can contain full MIME types or file extensions. Any value that
|
||||
is not a full MIME types is passed to `require('mime-types').lookup`.
|
||||
|
||||
#### `.types()`
|
||||
|
||||
Return the types that the request accepts, in the order of the client's
|
||||
preference (most preferred first).
|
||||
|
||||
## Example
|
||||
|
||||
This simple example shows how to use `accepts` to return a different typed
|
||||
respond body based on what the client wants to accept. The server lists it's
|
||||
preferences in order and will get back the best match between the client and
|
||||
server.
|
||||
|
||||
```ts
|
||||
import Accepts from '@tinyhttp/accepts'
|
||||
import { createServer } from 'node:http'
|
||||
|
||||
createServer((req, res) => {
|
||||
const accept = new Accepts(req)
|
||||
|
||||
// the order of this list is significant; should be server preferred order
|
||||
switch (accept.type(['json', 'html'])) {
|
||||
case 'json':
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.write('{"hello":"world!"}')
|
||||
break
|
||||
case 'html':
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.write('<b>hello, world!</b>')
|
||||
break
|
||||
default:
|
||||
// the fallback is text/plain, so no need to specify it above
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
res.write('hello, world!')
|
||||
break
|
||||
}
|
||||
|
||||
res.end()
|
||||
}).listen(3000)
|
||||
```
|
||||
|
||||
You can test this out with the cURL program:
|
||||
|
||||
```sh
|
||||
curl -I -H 'Accept: text/html' http://localhost:3000/
|
||||
```
|
|
@ -0,0 +1,49 @@
|
|||
/// <reference types="node" />
|
||||
import Negotiator from 'negotiator';
|
||||
import { IncomingMessage as I, IncomingHttpHeaders } from 'node:http';
|
||||
export declare class Accepts {
|
||||
headers: IncomingHttpHeaders;
|
||||
negotiator: Negotiator;
|
||||
constructor(req: Pick<I, 'headers'>);
|
||||
/**
|
||||
* Check if the given `type(s)` is acceptable, returning the best match when true, otherwise `false`, in which case you should respond with 406 "Not Acceptable".
|
||||
*
|
||||
* The `type` value may be a single mime type string such as "application/json", the extension name such as "json" or an array `["json", "html", "text/plain"]`. When a list or array is given the _best_ match, if any is returned. When no types are given as arguments, returns all types accepted by the client in the preference order.
|
||||
*/
|
||||
types(types: string | string[], ...args: string[]): string[] | string | false;
|
||||
get type(): (types: string | string[], ...args: string[]) => string[] | string | false;
|
||||
/**
|
||||
* Return accepted encodings or best fit based on `encodings`.
|
||||
*
|
||||
* Given `Accept-Encoding: gzip, deflate`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['gzip', 'deflate']
|
||||
*/
|
||||
encodings(encodings: string | string[], ...args: string[]): string | string[] | boolean;
|
||||
get encoding(): (encodings: string | string[], ...args: string[]) => string | string[] | boolean;
|
||||
/**
|
||||
* Return accepted charsets or best fit based on `charsets`.
|
||||
*
|
||||
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['utf-8', 'utf-7', 'iso-8859-1']
|
||||
*/
|
||||
charsets(charsets?: string | string[], ...args: string[]): string | string[] | boolean;
|
||||
get charset(): (charsets: string | string[], ...args: string[]) => string | string[] | boolean;
|
||||
/**
|
||||
* Return accepted languages or best fit based on `langs`.
|
||||
*
|
||||
* Given `Accept-Language: en;q=0.8, es, pt`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['es', 'pt', 'en']
|
||||
*
|
||||
*/
|
||||
languages(languages: string | string[], ...args: string[]): string | string[] | boolean;
|
||||
get lang(): (languages: string | string[], ...args: string[]) => string | string[] | boolean;
|
||||
get langs(): (languages: string | string[], ...args: string[]) => string | string[] | boolean;
|
||||
get language(): (languages: string | string[], ...args: string[]) => string | string[] | boolean;
|
||||
}
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,OAAO,UAAU,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,eAAe,IAAI,CAAC,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAOrE,qBAAa,OAAO;IAClB,OAAO,EAAE,mBAAmB,CAAA;IAC5B,UAAU,EAAE,UAAU,CAAA;gBACV,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS,CAAC;IAInC;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,KAAK;IA0B7E,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,GAAG,MAAM,GAAG,KAAK,CAErF;IACD;;;;;;;OAOG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO;IAevF,IAAI,QAAQ,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAE/F;IACD;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO;IAetF,IAAI,OAAO,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAE7F;IACD;;;;;;;;OAQG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO;IAevF,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAE3F;IACD,IAAI,KAAK,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAE5F;IACD,IAAI,QAAQ,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAE/F;CACF"}
|
|
@ -0,0 +1,110 @@
|
|||
import Negotiator from "negotiator";
|
||||
import mime from "mime";
|
||||
const extToMime = (type) => type.indexOf("/") == -1 ? mime.getType(type) : type;
|
||||
const validMime = (type) => typeof type == "string";
|
||||
class Accepts {
|
||||
constructor(req) {
|
||||
this.headers = req.headers;
|
||||
this.negotiator = new Negotiator(req);
|
||||
}
|
||||
/**
|
||||
* Check if the given `type(s)` is acceptable, returning the best match when true, otherwise `false`, in which case you should respond with 406 "Not Acceptable".
|
||||
*
|
||||
* The `type` value may be a single mime type string such as "application/json", the extension name such as "json" or an array `["json", "html", "text/plain"]`. When a list or array is given the _best_ match, if any is returned. When no types are given as arguments, returns all types accepted by the client in the preference order.
|
||||
*/
|
||||
types(types, ...args) {
|
||||
let mimeTypes = [];
|
||||
if (types && !Array.isArray(types)) {
|
||||
mimeTypes = [types, ...args];
|
||||
} else if (types) {
|
||||
mimeTypes = [...types, ...args];
|
||||
}
|
||||
if (!mimeTypes || mimeTypes.length == 0) {
|
||||
return this.negotiator.mediaTypes();
|
||||
}
|
||||
if (!this.headers["accept"]) {
|
||||
return mimeTypes[0];
|
||||
}
|
||||
const mimes = mimeTypes.map(extToMime);
|
||||
const accepts = this.negotiator.mediaTypes(mimes.filter(validMime));
|
||||
const [first] = accepts;
|
||||
return first ? mimeTypes[mimes.indexOf(first)] : false;
|
||||
}
|
||||
get type() {
|
||||
return this.types;
|
||||
}
|
||||
/**
|
||||
* Return accepted encodings or best fit based on `encodings`.
|
||||
*
|
||||
* Given `Accept-Encoding: gzip, deflate`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['gzip', 'deflate']
|
||||
*/
|
||||
encodings(encodings, ...args) {
|
||||
let _encodings = encodings;
|
||||
if (_encodings && !Array.isArray(_encodings)) {
|
||||
_encodings = [_encodings, ...args];
|
||||
}
|
||||
if (!_encodings || _encodings.length == 0) {
|
||||
return this.negotiator.encodings();
|
||||
}
|
||||
return this.negotiator.encodings(_encodings)[0] || false;
|
||||
}
|
||||
get encoding() {
|
||||
return this.encodings;
|
||||
}
|
||||
/**
|
||||
* Return accepted charsets or best fit based on `charsets`.
|
||||
*
|
||||
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['utf-8', 'utf-7', 'iso-8859-1']
|
||||
*/
|
||||
charsets(charsets, ...args) {
|
||||
let _charsets = charsets;
|
||||
if (_charsets && !Array.isArray(_charsets)) {
|
||||
_charsets = [_charsets, ...args];
|
||||
}
|
||||
if (!_charsets || _charsets.length == 0) {
|
||||
return this.negotiator.charsets();
|
||||
}
|
||||
return this.negotiator.charsets(_charsets)[0] || false;
|
||||
}
|
||||
get charset() {
|
||||
return this.charsets;
|
||||
}
|
||||
/**
|
||||
* Return accepted languages or best fit based on `langs`.
|
||||
*
|
||||
* Given `Accept-Language: en;q=0.8, es, pt`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['es', 'pt', 'en']
|
||||
*
|
||||
*/
|
||||
languages(languages, ...args) {
|
||||
let _languages = languages;
|
||||
if (_languages && !Array.isArray(_languages)) {
|
||||
_languages = [_languages, ...args];
|
||||
}
|
||||
if (!_languages || _languages.length == 0) {
|
||||
return this.negotiator.languages();
|
||||
}
|
||||
return this.negotiator.languages(_languages)[0] || false;
|
||||
}
|
||||
get lang() {
|
||||
return this.languages;
|
||||
}
|
||||
get langs() {
|
||||
return this.languages;
|
||||
}
|
||||
get language() {
|
||||
return this.languages;
|
||||
}
|
||||
}
|
||||
export {
|
||||
Accepts
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
長すぎる行があるためファイル差分は表示されません
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@tinyhttp/accepts",
|
||||
"description": "accepts rewrite in TypeScript",
|
||||
"version": "2.2.1",
|
||||
"license": "MIT",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/cookie-signature"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/negotiator": "^0.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime": "4.0.1",
|
||||
"negotiator": "^0.6.3"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,25 @@
|
|||
# @tinyhttp/app
|
||||
|
||||
The core of tinyhttp. Contains the `App`, `Request` and `Response`. Additionally, it provides special tinyhttp-specific types.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/app
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { App } from '@tinyhttp/app'
|
||||
import type { Request, Response, NextFunction } from '@tinyhttp/app'
|
||||
|
||||
new App()
|
||||
.use((req: Request, res: Response, next: NextFunction) => {
|
||||
console.log('Did a request')
|
||||
next()
|
||||
})
|
||||
.get('/', (_, res) => res.send('<h1>Hello World</h1>'))
|
||||
.get('/page/:page', (req, res) => res.send(`You opened ${req.params.page}`))
|
||||
.listen(3000)
|
||||
```
|
|
@ -0,0 +1,106 @@
|
|||
/// <reference types="node" />
|
||||
import { Server } from 'node:http';
|
||||
import type { Request } from './request.js';
|
||||
import type { Response } from './response.js';
|
||||
import type { ErrorHandler } from './onError.js';
|
||||
import type { Middleware, Handler, NextFunction, UseMethodParams } from '@tinyhttp/router';
|
||||
import { Router } from '@tinyhttp/router';
|
||||
import { AppConstructor, AppRenderOptions, AppSettings, TemplateEngine } from './types.js';
|
||||
import { TemplateEngineOptions } from './index.js';
|
||||
/**
|
||||
* `App` class - the starting point of tinyhttp app.
|
||||
*
|
||||
* With the `App` you can:
|
||||
* * use routing methods and `.use(...)`
|
||||
* * set no match (404) and error (500) handlers
|
||||
* * configure template engines
|
||||
* * store data in locals
|
||||
* * listen the http server on a specified port
|
||||
*
|
||||
* In case you use TypeScript, you can pass custom types to this class because it is also a generic class.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* interface CoolReq extends Request {
|
||||
* genericsAreDope: boolean
|
||||
* }
|
||||
*
|
||||
* const app = App<any, CoolReq, Response>()
|
||||
* ```
|
||||
*/
|
||||
export declare class App<Req extends Request = Request, Res extends Response = Response> extends Router<App, Req, Res> {
|
||||
#private;
|
||||
middleware: Middleware<Req, Res>[];
|
||||
locals: Record<string, unknown>;
|
||||
noMatchHandler: Handler;
|
||||
onError: ErrorHandler;
|
||||
settings: AppSettings;
|
||||
engines: Record<string, TemplateEngine>;
|
||||
applyExtensions: (req: Request, res: Response, next: NextFunction) => void;
|
||||
attach: (req: Req, res: Res) => void;
|
||||
cache: Record<string, unknown>;
|
||||
constructor(options?: AppConstructor<Req, Res>);
|
||||
/**
|
||||
* Set app setting
|
||||
* @param setting setting name
|
||||
* @param value setting value
|
||||
*/
|
||||
set<K extends keyof AppSettings>(setting: K, value: AppSettings[K]): this;
|
||||
/**
|
||||
* Enable app setting
|
||||
* @param setting Setting name
|
||||
*/
|
||||
enable<K extends keyof AppSettings>(setting: K): this;
|
||||
/**
|
||||
* Check if setting is enabled
|
||||
* @param setting Setting name
|
||||
* @returns
|
||||
*/
|
||||
enabled<K extends keyof AppSettings>(setting: K): boolean;
|
||||
/**
|
||||
* Disable app setting
|
||||
* @param setting Setting name
|
||||
*/
|
||||
disable<K extends keyof AppSettings>(setting: K): this;
|
||||
/**
|
||||
* Return the app's absolute pathname
|
||||
* based on the parent(s) that have
|
||||
* mounted it.
|
||||
*
|
||||
* For example if the application was
|
||||
* mounted as `"/admin"`, which itself
|
||||
* was mounted as `"/blog"` then the
|
||||
* return value would be `"/blog/admin"`.
|
||||
*
|
||||
*/
|
||||
path(): string;
|
||||
/**
|
||||
* Register a template engine with extension
|
||||
*/
|
||||
engine<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(ext: string, fn: TemplateEngine<RenderOptions>): this;
|
||||
/**
|
||||
* Render a template
|
||||
* @param file What to render
|
||||
* @param data data that is passed to a template
|
||||
* @param options Template engine options
|
||||
* @param cb Callback that consumes error and html
|
||||
*/
|
||||
render<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(name: string, data: Record<string, unknown>, options: AppRenderOptions<RenderOptions>, cb: (err: unknown, html?: unknown) => void): void;
|
||||
use(...args: UseMethodParams<Req, Res, App>): this;
|
||||
route(path: string): App;
|
||||
/**
|
||||
* Extends Req / Res objects, pushes 404 and 500 handlers, dispatches middleware
|
||||
* @param req Req object
|
||||
* @param res Res object
|
||||
*/
|
||||
handler<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions>(req: Req, res: Res, next?: NextFunction): void;
|
||||
/**
|
||||
* Creates HTTP server and dispatches middleware
|
||||
* @param port server listening port
|
||||
* @param Server callback after server starts listening
|
||||
* @param host server listening host
|
||||
*/
|
||||
listen(port?: number, cb?: () => void, host?: string): Server;
|
||||
}
|
||||
//# sourceMappingURL=app.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
|
@ -0,0 +1,10 @@
|
|||
import { Request } from './request.js';
|
||||
import type { NextFunction } from '@tinyhttp/router';
|
||||
import type { Response } from './response.js';
|
||||
import { App } from './app.js';
|
||||
import { TemplateEngineOptions } from './types.js';
|
||||
/**
|
||||
* Extends Request and Response objects with custom properties and methods
|
||||
*/
|
||||
export declare const extendMiddleware: <EngineOptions extends TemplateEngineOptions = TemplateEngineOptions>(app: App) => (req: Request, res: Response<EngineOptions>, next: NextFunction) => void;
|
||||
//# sourceMappingURL=extend.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"extend.d.ts","sourceRoot":"","sources":["../src/extend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,OAAO,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAkC7C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAElD;;GAEG;AACH,eAAO,MAAM,gBAAgB,6EACgD,GAAG,WACxE,OAAO,sCAAsC,YAAY,KAAG,IAsDjE,CAAA"}
|
|
@ -0,0 +1,15 @@
|
|||
export { App } from './app.js';
|
||||
export * from './request.js';
|
||||
export * from './response.js';
|
||||
export { extendMiddleware } from './extend.js';
|
||||
export { onErrorHandler, type ErrorHandler } from './onError.js';
|
||||
export { View } from './view.js';
|
||||
export type { AppSettings, TemplateEngineOptions, TemplateEngine, AppConstructor } from './types.js';
|
||||
import type { Request } from './request.js';
|
||||
import type { Response } from './response.js';
|
||||
import type { NextFunction, Handler as RHandler, AsyncHandler as RAsyncHandler, SyncHandler as RSyncHandler, Middleware } from '@tinyhttp/router';
|
||||
export type Handler = RHandler<Request, Response>;
|
||||
export type AsyncHandler = RAsyncHandler<Request, Response>;
|
||||
export type SyncHandler = RSyncHandler<Request, Response>;
|
||||
export type { NextFunction, Middleware, Request, Response };
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEpG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,IAAI,QAAQ,EACnB,YAAY,IAAI,aAAa,EAC7B,WAAW,IAAI,YAAY,EAC3B,UAAU,EACX,MAAM,kBAAkB,CAAA;AAEzB,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;AACjD,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;AAC3D,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@ -0,0 +1,464 @@
|
|||
import { STATUS_CODES, createServer } from "node:http";
|
||||
import { proxyaddr, all, compile } from "@tinyhttp/proxy-addr";
|
||||
import { isIP } from "node:net";
|
||||
import { getRequestHeader, getQueryParams, getRangeFromHeader, getAccepts, getAcceptsCharsets, getAcceptsEncodings, getAcceptsLanguages, checkIfXMLHttpRequest, getFreshOrStale, getPathname, getURLParams } from "@tinyhttp/req";
|
||||
import { getURLParams as getURLParams2 } from "@tinyhttp/req";
|
||||
import { Router, pushMiddleware } from "@tinyhttp/router";
|
||||
import { getResponseHeader, setHeader, send, json, status, sendStatus, sendFile, setContentType, setLocationHeader, setLinksHeader, setVaryHeader, setCookie, clearCookie, formatResponse, redirect, attachment, download, append } from "@tinyhttp/res";
|
||||
import { parse } from "regexparam";
|
||||
import { extname, resolve, dirname, basename, join } from "node:path";
|
||||
import { statSync } from "node:fs";
|
||||
const trustRemoteAddress = ({ socket }) => {
|
||||
const val = socket.remoteAddress;
|
||||
if (typeof val === "string")
|
||||
return compile(val.split(",").map((x) => x.trim()));
|
||||
return compile(val || []);
|
||||
};
|
||||
const getProtocol = (req) => {
|
||||
const proto = `http${req.secure ? "s" : ""}`;
|
||||
if (!trustRemoteAddress(req))
|
||||
return proto;
|
||||
const header = req.headers["X-Forwarded-Proto"] || proto;
|
||||
const index = header.indexOf(",");
|
||||
return index !== -1 ? header.substring(0, index).trim() : header.trim();
|
||||
};
|
||||
const getHostname = (req) => {
|
||||
let host = req.get("X-Forwarded-Host");
|
||||
if (!host || !trustRemoteAddress(req))
|
||||
host = req.get("Host");
|
||||
if (!host)
|
||||
return;
|
||||
const index = host.indexOf(":", host[0] === "[" ? host.indexOf("]") + 1 : 0);
|
||||
return index !== -1 ? host.substring(0, index) : host;
|
||||
};
|
||||
const getIP = (req) => proxyaddr(req, trustRemoteAddress(req)).replace(/^.*:/, "");
|
||||
const getIPs = (req) => all(req, trustRemoteAddress(req));
|
||||
const getSubdomains = (req, subdomainOffset = 2) => {
|
||||
const hostname = getHostname(req);
|
||||
if (!hostname)
|
||||
return [];
|
||||
const subdomains = isIP(hostname) ? [hostname] : hostname.split(".").reverse();
|
||||
return subdomains.slice(subdomainOffset);
|
||||
};
|
||||
const onErrorHandler = function(err, _req, res) {
|
||||
if (this.onError === onErrorHandler && this.parent)
|
||||
return this.parent.onError(err, _req, res);
|
||||
if (err instanceof Error)
|
||||
console.error(err);
|
||||
const code = err.code in STATUS_CODES ? err.code : err.status;
|
||||
if (typeof err === "string" || Buffer.isBuffer(err))
|
||||
res.writeHead(500).end(err);
|
||||
else if (code in STATUS_CODES)
|
||||
res.writeHead(code).end(STATUS_CODES[code]);
|
||||
else
|
||||
res.writeHead(500).end(err.message);
|
||||
};
|
||||
const renderTemplate = (_req, res, app) => (file, data, options) => {
|
||||
app.render(file, data ? { ...res.locals, ...data } : res.locals, options, (err, html) => {
|
||||
if (err)
|
||||
throw err;
|
||||
res.send(html);
|
||||
});
|
||||
return res;
|
||||
};
|
||||
const extendMiddleware = (app) => (req, res, next) => {
|
||||
const { settings } = app;
|
||||
res.get = getResponseHeader(res);
|
||||
req.get = getRequestHeader(req);
|
||||
if (settings == null ? void 0 : settings.bindAppToReqRes) {
|
||||
req.app = app;
|
||||
res.app = app;
|
||||
}
|
||||
if (settings == null ? void 0 : settings.networkExtensions) {
|
||||
req.protocol = getProtocol(req);
|
||||
req.secure = req.protocol === "https";
|
||||
req.hostname = getHostname(req);
|
||||
req.subdomains = getSubdomains(req, settings.subdomainOffset);
|
||||
req.ip = getIP(req);
|
||||
req.ips = getIPs(req);
|
||||
}
|
||||
req.query = getQueryParams(req.url);
|
||||
req.range = getRangeFromHeader(req);
|
||||
req.accepts = getAccepts(req);
|
||||
req.acceptsCharsets = getAcceptsCharsets(req);
|
||||
req.acceptsEncodings = getAcceptsEncodings(req);
|
||||
req.acceptsLanguages = getAcceptsLanguages(req);
|
||||
req.xhr = checkIfXMLHttpRequest(req);
|
||||
res.header = res.set = setHeader(res);
|
||||
res.send = send(req, res);
|
||||
res.json = json(res);
|
||||
res.status = status(res);
|
||||
res.sendStatus = sendStatus(req, res);
|
||||
res.sendFile = sendFile(req, res);
|
||||
res.type = setContentType(res);
|
||||
res.location = setLocationHeader(req, res);
|
||||
res.links = setLinksHeader(res);
|
||||
res.vary = setVaryHeader(res);
|
||||
res.cookie = setCookie(req, res);
|
||||
res.clearCookie = clearCookie(req, res);
|
||||
res.render = renderTemplate(req, res, app);
|
||||
res.format = formatResponse(req, res, next);
|
||||
res.redirect = redirect(req, res, next);
|
||||
res.attachment = attachment(res);
|
||||
res.download = download(req, res);
|
||||
res.append = append(res);
|
||||
res.locals = res.locals || /* @__PURE__ */ Object.create(null);
|
||||
Object.defineProperty(req, "fresh", { get: getFreshOrStale.bind(null, req, res), configurable: true });
|
||||
req.stale = !req.fresh;
|
||||
next();
|
||||
};
|
||||
function tryStat(path) {
|
||||
try {
|
||||
return statSync(path);
|
||||
} catch (e) {
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
class View {
|
||||
constructor(name, opts = {}) {
|
||||
this.ext = extname(name);
|
||||
this.name = name;
|
||||
this.root = opts.root;
|
||||
this.defaultEngine = opts.defaultEngine;
|
||||
if (!this.ext && !this.defaultEngine)
|
||||
throw new Error("No default engine was specified and no extension was provided.");
|
||||
let fileName = name;
|
||||
if (!this.ext) {
|
||||
this.ext = this.defaultEngine[0] !== "." ? "." + this.defaultEngine : this.defaultEngine;
|
||||
fileName += this.ext;
|
||||
}
|
||||
if (!opts.engines[this.ext])
|
||||
throw new Error(`No engine was found for ${this.ext}`);
|
||||
this.engine = opts.engines[this.ext];
|
||||
this.path = this.#lookup(fileName);
|
||||
}
|
||||
#lookup(name) {
|
||||
let path;
|
||||
const roots = [].concat(this.root);
|
||||
for (let i = 0; i < roots.length && !path; i++) {
|
||||
const root = roots[i];
|
||||
const loc = resolve(root, name);
|
||||
const dir = dirname(loc);
|
||||
const file = basename(loc);
|
||||
path = this.#resolve(dir, file);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
#resolve(dir, file) {
|
||||
const ext = this.ext;
|
||||
let path = join(dir, file);
|
||||
let stat = tryStat(path);
|
||||
if (stat && stat.isFile()) {
|
||||
return path;
|
||||
}
|
||||
path = join(dir, basename(file, ext), "index" + ext);
|
||||
stat = tryStat(path);
|
||||
if (stat && stat.isFile()) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
render(options, data, cb) {
|
||||
this.engine(this.path, data, options, cb);
|
||||
}
|
||||
}
|
||||
const lead = (x) => x.charCodeAt(0) === 47 ? x : "/" + x;
|
||||
const mount = (fn) => fn instanceof App ? fn.attach : fn;
|
||||
const applyHandler = (h) => async (req, res, next) => {
|
||||
try {
|
||||
if (h[Symbol.toStringTag] === "AsyncFunction") {
|
||||
await h(req, res, next);
|
||||
} else
|
||||
h(req, res, next);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
class App extends Router {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.middleware = [];
|
||||
this.locals = {};
|
||||
this.engines = {};
|
||||
this.onError = (options == null ? void 0 : options.onError) || onErrorHandler;
|
||||
this.noMatchHandler = (options == null ? void 0 : options.noMatchHandler) || this.onError.bind(this, { code: 404 });
|
||||
this.settings = {
|
||||
view: View,
|
||||
xPoweredBy: true,
|
||||
views: `${process.cwd()}/views`,
|
||||
"view cache": process.env.NODE_ENV === "production",
|
||||
...options.settings
|
||||
};
|
||||
this.applyExtensions = options == null ? void 0 : options.applyExtensions;
|
||||
this.attach = (req, res) => setImmediate(this.handler.bind(this, req, res, void 0), req, res);
|
||||
this.cache = {};
|
||||
}
|
||||
/**
|
||||
* Set app setting
|
||||
* @param setting setting name
|
||||
* @param value setting value
|
||||
*/
|
||||
set(setting, value) {
|
||||
this.settings[setting] = value;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enable app setting
|
||||
* @param setting Setting name
|
||||
*/
|
||||
enable(setting) {
|
||||
this.settings[setting] = true;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Check if setting is enabled
|
||||
* @param setting Setting name
|
||||
* @returns
|
||||
*/
|
||||
enabled(setting) {
|
||||
return Boolean(this.settings[setting]);
|
||||
}
|
||||
/**
|
||||
* Disable app setting
|
||||
* @param setting Setting name
|
||||
*/
|
||||
disable(setting) {
|
||||
this.settings[setting] = false;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Return the app's absolute pathname
|
||||
* based on the parent(s) that have
|
||||
* mounted it.
|
||||
*
|
||||
* For example if the application was
|
||||
* mounted as `"/admin"`, which itself
|
||||
* was mounted as `"/blog"` then the
|
||||
* return value would be `"/blog/admin"`.
|
||||
*
|
||||
*/
|
||||
path() {
|
||||
return this.parent ? this.parent.path() + this.mountpath : "";
|
||||
}
|
||||
/**
|
||||
* Register a template engine with extension
|
||||
*/
|
||||
engine(ext, fn) {
|
||||
this.engines[ext[0] === "." ? ext : `.${ext}`] = fn;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Render a template
|
||||
* @param file What to render
|
||||
* @param data data that is passed to a template
|
||||
* @param options Template engine options
|
||||
* @param cb Callback that consumes error and html
|
||||
*/
|
||||
render(name, data = {}, options = {}, cb) {
|
||||
let view;
|
||||
const { _locals, ...opts } = options;
|
||||
let locals = this.locals;
|
||||
if (_locals)
|
||||
locals = { ...locals, ..._locals };
|
||||
locals = { ...locals, ...data };
|
||||
if (opts.cache == null)
|
||||
opts.cache = this.enabled("view cache");
|
||||
if (opts.cache) {
|
||||
view = this.cache[name];
|
||||
}
|
||||
if (!view) {
|
||||
const View2 = this.settings["view"];
|
||||
view = new View2(name, {
|
||||
defaultEngine: this.settings["view engine"],
|
||||
root: this.settings.views,
|
||||
engines: this.engines
|
||||
});
|
||||
if (!view.path) {
|
||||
const dirs = Array.isArray(view.root) && view.root.length > 1 ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"' : 'directory "' + view.root + '"';
|
||||
const err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
|
||||
return cb(err);
|
||||
}
|
||||
if (opts.cache) {
|
||||
this.cache[name] = view;
|
||||
}
|
||||
}
|
||||
try {
|
||||
view.render(opts, locals, cb);
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
}
|
||||
use(...args) {
|
||||
const base = args[0];
|
||||
const fns = args.slice(1).flat();
|
||||
let pathArray = [];
|
||||
if (typeof base === "function" || base instanceof App) {
|
||||
fns.unshift(base);
|
||||
} else {
|
||||
let basePaths = [];
|
||||
if (Array.isArray(base))
|
||||
basePaths = [...base];
|
||||
else if (typeof base === "string")
|
||||
basePaths = [base];
|
||||
basePaths = basePaths.filter((element) => {
|
||||
if (typeof element === "string") {
|
||||
pathArray.push(element);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
fns.unshift(...basePaths);
|
||||
}
|
||||
pathArray = pathArray.length ? pathArray.map((path) => lead(path)) : ["/"];
|
||||
const mountpath = pathArray.join(", ");
|
||||
let regex;
|
||||
for (const fn of fns) {
|
||||
if (fn instanceof App) {
|
||||
pathArray.forEach((path) => {
|
||||
regex = parse(path, true);
|
||||
fn.mountpath = mountpath;
|
||||
this.apps[path] = fn;
|
||||
fn.parent = this;
|
||||
});
|
||||
}
|
||||
}
|
||||
pathArray.forEach((path) => {
|
||||
var _a;
|
||||
const handlerPaths = [];
|
||||
const handlerFunctions = [];
|
||||
const handlerPathBase = path === "/" ? "" : lead(path);
|
||||
for (const fn of fns) {
|
||||
if (fn instanceof App && ((_a = fn.middleware) == null ? void 0 : _a.length)) {
|
||||
for (const mw of fn.middleware) {
|
||||
handlerPaths.push(handlerPathBase + lead(mw.path));
|
||||
handlerFunctions.push(fn);
|
||||
}
|
||||
} else {
|
||||
handlerPaths.push("");
|
||||
handlerFunctions.push(fn);
|
||||
}
|
||||
}
|
||||
pushMiddleware(this.middleware)({
|
||||
path,
|
||||
regex,
|
||||
type: "mw",
|
||||
handler: mount(handlerFunctions[0]),
|
||||
handlers: handlerFunctions.slice(1).map(mount),
|
||||
fullPaths: handlerPaths
|
||||
});
|
||||
});
|
||||
return this;
|
||||
}
|
||||
route(path) {
|
||||
const app = new App({ settings: this.settings });
|
||||
this.use(path, app);
|
||||
return app;
|
||||
}
|
||||
#find(url) {
|
||||
return this.middleware.filter((m) => {
|
||||
m.regex = m.regex || parse(m.path, m.type === "mw");
|
||||
let fullPathRegex;
|
||||
m.fullPath && typeof m.fullPath === "string" ? fullPathRegex = parse(m.fullPath, m.type === "mw") : fullPathRegex = null;
|
||||
return m.regex.pattern.test(url) && (m.type === "mw" && fullPathRegex ? fullPathRegex.pattern.test(url) : true);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Extends Req / Res objects, pushes 404 and 500 handlers, dispatches middleware
|
||||
* @param req Req object
|
||||
* @param res Res object
|
||||
*/
|
||||
handler(req, res, next) {
|
||||
const { xPoweredBy } = this.settings;
|
||||
if (xPoweredBy)
|
||||
res.setHeader("X-Powered-By", typeof xPoweredBy === "string" ? xPoweredBy : "tinyhttp");
|
||||
const exts = this.applyExtensions || extendMiddleware(this);
|
||||
req.originalUrl = req.url || req.originalUrl;
|
||||
const pathname = getPathname(req.originalUrl);
|
||||
const matched = this.#find(pathname);
|
||||
const mw = [
|
||||
{
|
||||
handler: exts,
|
||||
type: "mw",
|
||||
path: "/"
|
||||
},
|
||||
...matched.filter((x) => req.method === "HEAD" || (x.method ? x.method === req.method : true))
|
||||
];
|
||||
if (matched[0] != null) {
|
||||
mw.push({
|
||||
type: "mw",
|
||||
handler: (req2, res2, next2) => {
|
||||
if (req2.method === "HEAD") {
|
||||
res2.statusCode = 204;
|
||||
return res2.end("");
|
||||
}
|
||||
next2();
|
||||
},
|
||||
path: "/"
|
||||
});
|
||||
}
|
||||
mw.push({
|
||||
handler: this.noMatchHandler,
|
||||
type: "mw",
|
||||
path: "/"
|
||||
});
|
||||
const handle = (mw2) => async (req2, res2, next2) => {
|
||||
var _a;
|
||||
const { path, handler, regex } = mw2;
|
||||
let params;
|
||||
try {
|
||||
params = regex ? getURLParams(regex, pathname) : {};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e instanceof URIError)
|
||||
return res2.sendStatus(400);
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
let prefix = path;
|
||||
if (regex) {
|
||||
for (const key of regex.keys) {
|
||||
if (key === "wild") {
|
||||
prefix = prefix.replace("*", params.wild);
|
||||
} else {
|
||||
prefix = prefix.replace(`:${key}`, params[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
req2.params = { ...req2.params, ...params };
|
||||
if (mw2.type === "mw") {
|
||||
req2.url = lead(req2.originalUrl.substring(prefix.length));
|
||||
}
|
||||
if (!req2.path)
|
||||
req2.path = getPathname(req2.url);
|
||||
if ((_a = this.settings) == null ? void 0 : _a.enableReqRoute)
|
||||
req2.route = mw2;
|
||||
await applyHandler(handler)(req2, res2, next2);
|
||||
};
|
||||
let idx = 0;
|
||||
const loop = () => res.writableEnded || idx < mw.length && handle(mw[idx++])(req, res, next);
|
||||
next = next || ((err) => err ? this.onError(err, req, res) : loop());
|
||||
loop();
|
||||
}
|
||||
/**
|
||||
* Creates HTTP server and dispatches middleware
|
||||
* @param port server listening port
|
||||
* @param Server callback after server starts listening
|
||||
* @param host server listening host
|
||||
*/
|
||||
listen(port, cb, host) {
|
||||
return createServer().on("request", this.attach).listen(port, host, cb);
|
||||
}
|
||||
}
|
||||
export {
|
||||
App,
|
||||
View,
|
||||
extendMiddleware,
|
||||
getHostname,
|
||||
getIP,
|
||||
getIPs,
|
||||
getProtocol,
|
||||
getSubdomains,
|
||||
getURLParams2 as getURLParams,
|
||||
onErrorHandler,
|
||||
renderTemplate
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
長すぎる行があるためファイル差分は表示されません
|
@ -0,0 +1,7 @@
|
|||
import type { NextFunction } from '@tinyhttp/router';
|
||||
import type { Request } from './request.js';
|
||||
import type { Response } from './response.js';
|
||||
import type { App } from './app.js';
|
||||
export type ErrorHandler = (this: App, err: any, req: Request, res: Response, next?: NextFunction) => void;
|
||||
export declare const onErrorHandler: ErrorHandler;
|
||||
//# sourceMappingURL=onError.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"onError.d.ts","sourceRoot":"","sources":["../src/onError.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,IAAI,CAAA;AAE1G,eAAO,MAAM,cAAc,EAAE,YAU5B,CAAA"}
|
|
@ -0,0 +1,56 @@
|
|||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import type { ParsedUrlQuery } from 'node:querystring';
|
||||
import { Options, Ranges } from 'header-range-parser';
|
||||
import { App } from './app.js';
|
||||
import type { Middleware } from '@tinyhttp/router';
|
||||
import type { URLParams } from '@tinyhttp/req';
|
||||
import type { Socket } from 'node:net';
|
||||
import type { TLSSocket } from 'node:tls';
|
||||
export { getURLParams } from '@tinyhttp/req';
|
||||
export declare const getProtocol: (req: Request) => Protocol;
|
||||
export declare const getHostname: (req: Request) => string | undefined;
|
||||
export declare const getIP: (req: Pick<Request, 'headers' | 'connection' | 'socket'>) => string | undefined;
|
||||
export declare const getIPs: (req: Pick<Request, 'headers' | 'connection' | 'socket'>) => string[] | undefined;
|
||||
export declare const getSubdomains: (req: Request, subdomainOffset?: number) => string[];
|
||||
export type Connection = IncomingMessage['socket'] & {
|
||||
encrypted: boolean;
|
||||
};
|
||||
export type Protocol = 'http' | 'https' | string;
|
||||
export type { URLParams };
|
||||
type AcceptsReturns = string | boolean | string[];
|
||||
export interface Request extends IncomingMessage {
|
||||
originalUrl: string;
|
||||
path: string;
|
||||
url: string;
|
||||
query: ParsedUrlQuery;
|
||||
params: URLParams;
|
||||
connection: Connection;
|
||||
socket: TLSSocket | Socket;
|
||||
route?: Middleware;
|
||||
protocol: Protocol;
|
||||
secure: boolean;
|
||||
xhr: boolean;
|
||||
hostname?: string;
|
||||
ip?: string;
|
||||
ips?: string[];
|
||||
subdomains?: string[];
|
||||
get: (header: string) => string | string[] | undefined;
|
||||
range: (size: number, options?: Options) => -1 | -2 | -3 | Ranges | undefined;
|
||||
accepts: (...types: string[]) => AcceptsReturns;
|
||||
acceptsEncodings: (...encodings: string[]) => AcceptsReturns;
|
||||
acceptsCharsets: (...charsets: string[]) => AcceptsReturns;
|
||||
acceptsLanguages: (...languages: string[]) => AcceptsReturns;
|
||||
is: (...types: string[]) => boolean;
|
||||
cookies?: any;
|
||||
signedCookies?: any;
|
||||
secret?: string | string[];
|
||||
fresh?: boolean;
|
||||
stale?: boolean;
|
||||
body?: any;
|
||||
app?: App;
|
||||
}
|
||||
//# sourceMappingURL=request.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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"}
|
|
@ -0,0 +1,45 @@
|
|||
/// <reference types="node" />
|
||||
import { ServerResponse } from 'node:http';
|
||||
import type { SerializeOptions } from '@tinyhttp/cookie';
|
||||
import { Request } from './request.js';
|
||||
import { App } from './app.js';
|
||||
import type { ReadStreamOptions, FormatProps, DownloadOptions } from '@tinyhttp/res';
|
||||
import type { AppRenderOptions, TemplateEngineOptions } from './types.js';
|
||||
export declare const renderTemplate: <O extends TemplateEngineOptions = TemplateEngineOptions>(_req: Request, res: Response, app: App) => (file: string, data?: Record<string, unknown>, options?: AppRenderOptions<O>) => Response;
|
||||
export interface Response<B = unknown> extends ServerResponse {
|
||||
header(field: string | Record<string, unknown>, val?: string | any[]): Response<B>;
|
||||
set(field: string | Record<string, unknown>, val?: string | any[]): Response<B>;
|
||||
get(field: string): string | number | string[];
|
||||
send(body: B): Response<B>;
|
||||
sendFile(path: string, options?: ReadStreamOptions, cb?: (err?: unknown) => void): Response<B>;
|
||||
json(body: B): Response<B>;
|
||||
status(status: number): Response<B>;
|
||||
sendStatus(statusCode: number): Response<B>;
|
||||
cookie(name: string, value: string | Record<string, unknown>, options?: SerializeOptions & Partial<{
|
||||
signed: boolean;
|
||||
}>): Response<B>;
|
||||
clearCookie(name: string, options?: SerializeOptions): Response<B>;
|
||||
location(url: string): Response<B>;
|
||||
links(links: {
|
||||
[key: string]: string;
|
||||
}): Response<B>;
|
||||
render<O extends TemplateEngineOptions = TemplateEngineOptions>(file: string, data?: Record<string, any>, options?: AppRenderOptions<O>): Response<B>;
|
||||
vary(field: string): Response<B>;
|
||||
format(obj: FormatProps): Response<B>;
|
||||
redirect(url: string, status?: number): Response<B>;
|
||||
type(type: string): Response<B>;
|
||||
download(path: string, filename: string, options?: DownloadOptions, cb?: (err?: unknown) => void): Response<B>;
|
||||
attachment(filename?: string): Response<B>;
|
||||
app?: App;
|
||||
locals: Record<string, any>;
|
||||
/**
|
||||
* Send JSON response with JSONP callback support.
|
||||
*
|
||||
* To enable this method, install the `@tinyhttp/jsonp` package and attach the method to `res.jsonp` property.
|
||||
*
|
||||
* @param obj Response object
|
||||
*/
|
||||
jsonp(obj: any): Response<B>;
|
||||
append(field: string, value: any): Response<B>;
|
||||
}
|
||||
//# sourceMappingURL=response.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEzE,eAAO,MAAM,cAAc,kEACuC,OAAO,OAAO,QAAQ,OAAO,GAAG,YACzF,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,oCAAkC,QAM9E,CAAA;AAEH,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO,CAAE,SAAQ,cAAc;IAC3D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAClF,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC/E,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAA;IAC9C,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC9F,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACnC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC3C,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,GACxD,QAAQ,CAAC,CAAC,CAAC,CAAA;IACd,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAClE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAClC,KAAK,CAAC,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACpD,MAAM,CAAC,CAAC,SAAS,qBAAqB,GAAG,qBAAqB,EAC5D,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC5B,QAAQ,CAAC,CAAC,CAAC,CAAA;IACd,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAChC,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACnD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC9G,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC1C,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3B;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAE5B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;CAC/C"}
|
|
@ -0,0 +1,39 @@
|
|||
import type { Handler, NextFunction } from '@tinyhttp/router';
|
||||
import type { ErrorHandler } from './onError.js';
|
||||
import type { Request } from './request.js';
|
||||
import type { Response } from './response.js';
|
||||
import type { View } from './view.js';
|
||||
/**
|
||||
* tinyhttp App has a few settings for toggling features
|
||||
*/
|
||||
export type AppSettings = Partial<{
|
||||
networkExtensions: boolean;
|
||||
subdomainOffset: number;
|
||||
bindAppToReqRes: boolean;
|
||||
xPoweredBy: string | boolean;
|
||||
enableReqRoute: boolean;
|
||||
views: string | string[];
|
||||
view: typeof View;
|
||||
'view cache': boolean;
|
||||
'view engine': string;
|
||||
}>;
|
||||
export type TemplateEngineOptions = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/**
|
||||
* Function that processes the template
|
||||
*/
|
||||
export type TemplateEngine<O extends TemplateEngineOptions = TemplateEngineOptions> = (path: string, locals: Record<string, unknown>, opts: AppRenderOptions<O>, cb: (err: Error | null, html: unknown) => void) => void;
|
||||
export type AppRenderOptions<O extends TemplateEngineOptions = TemplateEngineOptions> = O & Partial<{
|
||||
cache: boolean;
|
||||
ext: string;
|
||||
viewsFolder: string;
|
||||
_locals: Record<string, unknown>;
|
||||
}>;
|
||||
export type AppConstructor<Req extends Request = Request, Res extends Response = Response> = Partial<{
|
||||
noMatchHandler: Handler<Req, Res>;
|
||||
onError: ErrorHandler;
|
||||
settings: AppSettings;
|
||||
applyExtensions: (req: Request, res: Response, next: NextFunction) => void;
|
||||
}>;
|
||||
//# sourceMappingURL=types.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC;IAChC,iBAAiB,EAAE,OAAO,CAAA;IAC1B,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,OAAO,CAAA;IACxB,UAAU,EAAE,MAAM,GAAG,OAAO,CAAA;IAC5B,cAAc,EAAE,OAAO,CAAA;IACvB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,IAAI,EAAE,OAAO,IAAI,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;CACtB,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,qBAAqB,GAAG,qBAAqB,IAAI,CACpF,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EACzB,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,KAC3C,IAAI,CAAA;AAET,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,qBAAqB,GAAG,qBAAqB,IAAI,CAAC,GACvF,OAAO,CAAC;IACN,KAAK,EAAE,OAAO,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC,CAAC,CAAA;AAEJ,MAAM,MAAM,cAAc,CAAC,GAAG,SAAS,OAAO,GAAG,OAAO,EAAE,GAAG,SAAS,QAAQ,GAAG,QAAQ,IAAI,OAAO,CAAC;IACnG,cAAc,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACjC,OAAO,EAAE,YAAY,CAAA;IACrB,QAAQ,EAAE,WAAW,CAAA;IACrB,eAAe,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;CAC3E,CAAC,CAAA"}
|
|
@ -0,0 +1,38 @@
|
|||
/*!
|
||||
* Ported from https://github.com/expressjs/express/blob/master/lib/view.js
|
||||
* express
|
||||
* Copyright(c) 2009-2013 TJ Holowaychuk
|
||||
* Copyright(c) 2013 Roman Shtylman
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
import { TemplateEngineOptions, TemplateEngine } from './types.js';
|
||||
/**
|
||||
* Initialize a new `View` with the given `name`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `defaultEngine` the default template engine name
|
||||
* - `engines` template engine require() cache
|
||||
* - `root` root path for view lookup
|
||||
*
|
||||
* @param name
|
||||
* @param options
|
||||
* @public
|
||||
*/
|
||||
export declare class View<RenderOptions extends TemplateEngineOptions = TemplateEngineOptions> {
|
||||
#private;
|
||||
ext: string;
|
||||
defaultEngine: string;
|
||||
name: string;
|
||||
engine: TemplateEngine<RenderOptions>;
|
||||
path: string;
|
||||
root: string | string[];
|
||||
constructor(name: string, opts?: Partial<{
|
||||
defaultEngine: string;
|
||||
root: string | string[];
|
||||
engines: Record<string, TemplateEngine<RenderOptions>>;
|
||||
}>);
|
||||
render(options: RenderOptions, data: Record<string, unknown>, cb: (err: Error | null, html: unknown) => void): void;
|
||||
}
|
||||
//# sourceMappingURL=view.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAWlE;;;;;;;;;;;;GAYG;AAEH,qBAAa,IAAI,CAAC,aAAa,SAAS,qBAAqB,GAAG,qBAAqB;;IACnF,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,MAAM,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,cAAc,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;gBAErB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,OAAO,CAAC;QACZ,aAAa,EAAE,MAAM,CAAA;QACrB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;QACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC,CAAA;KACvD,CAAM;IA4DT,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI;CAG7G"}
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "@tinyhttp/app",
|
||||
"version": "2.2.3",
|
||||
"description": "0-legacy, tiny & fast web framework as a replacement of Express",
|
||||
"type": "module",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/app"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"router",
|
||||
"backend",
|
||||
"http",
|
||||
"framework",
|
||||
"api"
|
||||
],
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"header-range-parser": "1.1.3",
|
||||
"regexparam": "^2.0.1",
|
||||
"@tinyhttp/cookie": "2.1.0",
|
||||
"@tinyhttp/res": "2.2.2",
|
||||
"@tinyhttp/proxy-addr": "2.1.3",
|
||||
"@tinyhttp/req": "2.2.2",
|
||||
"@tinyhttp/router": "2.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,107 @@
|
|||
# @tinyhttp/content-disposition
|
||||
|
||||
> [`content-disposition`](https://github.com/jshttp/content-disposition) rewrite
|
||||
> in TypeScript.
|
||||
|
||||
Create and parse HTTP `Content-Disposition` header
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/content-disposition
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import { contentDisposition, parse } from '@tinyhttp/content-disposition'
|
||||
```
|
||||
|
||||
### `contentDisposition(filename)`
|
||||
|
||||
Create an attachment `Content-Disposition` header value using the given file
|
||||
name, if supplied. The `filename` is optional and if no file name is desired,
|
||||
but you want to specify `options`, set `filename` to `undefined`.
|
||||
|
||||
```js
|
||||
res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
|
||||
```
|
||||
|
||||
**note** HTTP headers are of the ISO-8859-1 character set. If you are writing
|
||||
this header through a means different from `setHeader` in Node.js, you'll want
|
||||
to specify the `'binary'` encoding in Node.js.
|
||||
|
||||
#### Options
|
||||
|
||||
`contentDisposition` accepts these properties in the options object.
|
||||
|
||||
##### `fallback`
|
||||
|
||||
If the `filename` option is outside ISO-8859-1, then the file name is actually
|
||||
stored in a supplemental field for clients that support Unicode file names and a
|
||||
ISO-8859-1 version of the file name is automatically generated.
|
||||
|
||||
This specifies the ISO-8859-1 file name to override the automatic generation or
|
||||
disables the generation all together, defaults to `true`.
|
||||
|
||||
- A string will specify the ISO-8859-1 file name to use in place of automatic
|
||||
generation.
|
||||
- `false` will disable including a ISO-8859-1 file name and only include the
|
||||
Unicode version (unless the file name is already ISO-8859-1).
|
||||
- `true` will enable automatic generation if the file name is outside
|
||||
ISO-8859-1.
|
||||
|
||||
If the `filename` option is ISO-8859-1 and this option is specified and has a
|
||||
different value, then the `filename` option is encoded in the extended field and
|
||||
this set as the fallback field, even though they are both ISO-8859-1.
|
||||
|
||||
##### `type`
|
||||
|
||||
Specifies the disposition type, defaults to `"attachment"`. This can also be
|
||||
`"inline"`, or any other value (all values except inline are treated like
|
||||
`attachment`, but can convey additional information if both parties agree to
|
||||
it). The type is normalized to lower-case.
|
||||
|
||||
### `contentDisposition.parse(string)`
|
||||
|
||||
```js
|
||||
contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt')
|
||||
```
|
||||
|
||||
Parse a `Content-Disposition` header string. This automatically handles extended
|
||||
("Unicode") parameters by decoding them and providing them under the standard
|
||||
parameter name. This will return an object with the following properties
|
||||
(examples are shown for the string
|
||||
`'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'`):
|
||||
|
||||
- `type`: The disposition type (always lower case). Example: `'attachment'`
|
||||
|
||||
- `parameters`: An object of the parameters in the disposition (name of
|
||||
parameter always lower case and extended versions replace non-extended
|
||||
versions). Example: `{filename: "€ rates.txt"}`
|
||||
|
||||
## Example
|
||||
|
||||
This simple example shows how to use `accepts` to return a different typed
|
||||
respond body based on what the client wants to accept. The server lists it's
|
||||
preferences in order and will get back the best match between the client and
|
||||
server.
|
||||
|
||||
```ts
|
||||
import { contentDisposition } from '@tinyhttp/content-disposition'
|
||||
import destroy from 'destroy'
|
||||
import fs from 'node:fs'
|
||||
import { createServer } from 'node:http'
|
||||
import onFinished from 'on-finished'
|
||||
|
||||
const filePath = '/path/to/public/plans.pdf'
|
||||
|
||||
createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'application/pdf')
|
||||
res.setHeader('Content-Disposition', contentDisposition(filePath))
|
||||
|
||||
const stream = fs.createReadStream(filePath)
|
||||
stream.pipe(res)
|
||||
onFinished(res, () => destroy(stream))
|
||||
})
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
export declare class ContentDisposition {
|
||||
type: string;
|
||||
parameters: Record<string, unknown>;
|
||||
constructor(type: string, parameters: Record<string, unknown>);
|
||||
}
|
||||
/**
|
||||
* Create an attachment Content-Disposition header.
|
||||
*
|
||||
* @param filename file name
|
||||
* @param options
|
||||
*/
|
||||
export declare function contentDisposition(filename?: string, options?: Partial<{
|
||||
type: string;
|
||||
fallback: string | boolean;
|
||||
}>): string;
|
||||
/**
|
||||
* Parse Content-Disposition header string.
|
||||
* @param header string
|
||||
*/
|
||||
export declare function parse(header: string): ContentDisposition;
|
|
@ -0,0 +1,138 @@
|
|||
const ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g;
|
||||
const HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/;
|
||||
const HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g;
|
||||
const NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g;
|
||||
const QESC_REGEXP = /\\([\u0000-\u007f])/g;
|
||||
const QUOTE_REGEXP = /([\\"])/g;
|
||||
const PARAM_REGEXP = /;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g;
|
||||
const TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/;
|
||||
const TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/;
|
||||
const EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/;
|
||||
const DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/;
|
||||
const getlatin1 = (val) => {
|
||||
return String(val).replace(NON_LATIN1_REGEXP, "?");
|
||||
};
|
||||
class ContentDisposition {
|
||||
constructor(type, parameters) {
|
||||
this.type = type;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
const qstring = (val) => '"' + String(val).replace(QUOTE_REGEXP, "\\$1") + '"';
|
||||
const pencode = (char) => "%" + String(char).charCodeAt(0).toString(16).toUpperCase();
|
||||
function ustring(val) {
|
||||
const str = String(val);
|
||||
const encoded = encodeURIComponent(str).replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode);
|
||||
return "UTF-8''" + encoded;
|
||||
}
|
||||
const basename = (str) => str.slice(str.lastIndexOf("/") + 1);
|
||||
function format({
|
||||
parameters,
|
||||
type
|
||||
}) {
|
||||
if (!type || typeof type !== "string" || !TOKEN_REGEXP.test(type)) {
|
||||
throw new TypeError("invalid type");
|
||||
}
|
||||
let string = String(type).toLowerCase();
|
||||
if (parameters && typeof parameters === "object") {
|
||||
const params = Object.keys(parameters).sort();
|
||||
for (const param of params) {
|
||||
const val = param.slice(-1) === "*" ? ustring(parameters[param]) : qstring(parameters[param]);
|
||||
string += "; " + param + "=" + val;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
function createParams(filename, fallback) {
|
||||
if (filename === void 0)
|
||||
return;
|
||||
const params = {};
|
||||
if (fallback === void 0)
|
||||
fallback = true;
|
||||
if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) {
|
||||
throw new TypeError("fallback must be ISO-8859-1 string");
|
||||
}
|
||||
const name = basename(filename);
|
||||
const isQuotedString = TEXT_REGEXP.test(name);
|
||||
const fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename(fallback);
|
||||
const hasFallback = typeof fallbackName === "string" && fallbackName !== name;
|
||||
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
|
||||
params["filename*"] = name;
|
||||
}
|
||||
if (isQuotedString || hasFallback) {
|
||||
params.filename = hasFallback ? fallbackName : name;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
const pdecode = (_str, hex) => String.fromCharCode(parseInt(hex, 16));
|
||||
function contentDisposition(filename, options = {}) {
|
||||
return format(new ContentDisposition(options.type || "attachment", createParams(filename, options.fallback)));
|
||||
}
|
||||
function decodefield(str) {
|
||||
const match = EXT_VALUE_REGEXP.exec(str);
|
||||
if (!match)
|
||||
throw new TypeError("invalid extended field value");
|
||||
const charset = match[1].toLowerCase();
|
||||
const encoded = match[2];
|
||||
let value;
|
||||
switch (charset) {
|
||||
case "iso-8859-1":
|
||||
value = getlatin1(encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode));
|
||||
break;
|
||||
case "utf-8":
|
||||
try {
|
||||
value = decodeURIComponent(encoded);
|
||||
} catch {
|
||||
throw new TypeError("invalid encoded utf-8");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new TypeError("unsupported charset in extended field");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function parse(header) {
|
||||
let match = DISPOSITION_TYPE_REGEXP.exec(header);
|
||||
if (!match)
|
||||
throw new TypeError("invalid type format");
|
||||
let index = match[0].length;
|
||||
const type = match[1].toLowerCase();
|
||||
let key;
|
||||
const names = [];
|
||||
const params = {};
|
||||
let value;
|
||||
index = PARAM_REGEXP.lastIndex = match[0].slice(-1) === ";" ? index - 1 : index;
|
||||
while (match = PARAM_REGEXP.exec(header)) {
|
||||
if (match.index !== index)
|
||||
throw new TypeError("invalid parameter format");
|
||||
index += match[0].length;
|
||||
key = match[1].toLowerCase();
|
||||
value = match[2];
|
||||
if (names.indexOf(key) !== -1) {
|
||||
throw new TypeError("invalid duplicate parameter");
|
||||
}
|
||||
names.push(key);
|
||||
if (key.indexOf("*") + 1 === key.length) {
|
||||
key = key.slice(0, -1);
|
||||
value = decodefield(value);
|
||||
params[key] = value;
|
||||
continue;
|
||||
}
|
||||
if (typeof params[key] === "string")
|
||||
continue;
|
||||
if (value[0] === '"') {
|
||||
value = value.slice(1, value.length - 1).replace(QESC_REGEXP, "$1");
|
||||
}
|
||||
params[key] = value;
|
||||
}
|
||||
if (index !== -1 && index !== header.length) {
|
||||
throw new TypeError("invalid parameter format");
|
||||
}
|
||||
return new ContentDisposition(type, params);
|
||||
}
|
||||
export {
|
||||
ContentDisposition,
|
||||
contentDisposition,
|
||||
parse
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
長すぎる行があるためファイル差分は表示されません
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "@tinyhttp/content-disposition",
|
||||
"description": "content-disposition rewrite in TypeScript",
|
||||
"version": "2.2.0",
|
||||
"license": "MIT",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/content-disposition"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,82 @@
|
|||
# @tinyhttp/content-type
|
||||
|
||||
[![Version][v-badge-url]][npm-url] [![Downloads][dl-badge-url]][npm-url] [![GitHub Workflow Status][gh-actions-img]][github-actions] [![Codecov][cov-badge-url]][cov-url]
|
||||
|
||||
> [`content-type`](https://github.com/jshttp/content-type) rewrite in TypeScript and ESM.
|
||||
|
||||
Create and parse HTTP Content-Type header according to RFC 7231
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/content-type
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import { parse, format } from '@tinyhttp/content-type'
|
||||
```
|
||||
|
||||
### `parse(string: string | Request | Response)`
|
||||
|
||||
```ts
|
||||
const obj = parse('image/svg+xml; charset=utf-8')
|
||||
```
|
||||
|
||||
Parse a `Content-Type` header. This will return an object with the following
|
||||
properties (examples are shown for the string `'image/svg+xml; charset=utf-8'`):
|
||||
|
||||
- `type`: The media type (the type and subtype, always lower case).
|
||||
Example: `'image/svg+xml'`
|
||||
|
||||
- `parameters`: An object of the parameters in the media type (name of parameter
|
||||
always lower case). Example: `{charset: 'utf-8'}`
|
||||
|
||||
Throws a `TypeError` if the string is missing or invalid.
|
||||
|
||||
```ts
|
||||
const obj = contentType.parse(req)
|
||||
```
|
||||
|
||||
Parse the `Content-Type` header from the given `req`. Short-cut for
|
||||
`contentType.parse(req.headers['content-type'])`.
|
||||
|
||||
Throws a `TypeError` if the `Content-Type` header is missing or invalid.
|
||||
|
||||
```js
|
||||
const obj = contentType.parse(res)
|
||||
```
|
||||
|
||||
Parse the `Content-Type` header set on the given `res`. Short-cut for
|
||||
`contentType.parse(res.getHeader('content-type'))`.
|
||||
|
||||
Throws a `TypeError` if the `Content-Type` header is missing or invalid.
|
||||
|
||||
### `format(obj)`
|
||||
|
||||
```ts
|
||||
const str = contentType.format({
|
||||
type: 'image/svg+xml',
|
||||
parameters: { charset: 'utf-8' },
|
||||
})
|
||||
```
|
||||
|
||||
Format an object into a `Content-Type` header. This will return a string of the
|
||||
content type for the given object with the following properties (examples are
|
||||
shown that produce the string `'image/svg+xml; charset=utf-8'`):
|
||||
|
||||
- `type`: The media type (will be lower-cased). Example: `'image/svg+xml'`
|
||||
|
||||
- `parameters`: An object of the parameters in the media type (name of the
|
||||
parameter will be lower-cased). Example: `{charset: 'utf-8'}`
|
||||
|
||||
Throws a `TypeError` if the object contains an invalid type or parameter names.
|
||||
|
||||
[v-badge-url]: https://img.shields.io/npm/v/@tinyhttp/content-type.svg?style=for-the-badge&color=FF69B4&label=&logo=npm
|
||||
[npm-url]: https://www.npmjs.com/package/@tinyhttp/content-type
|
||||
[cov-badge-url]: https://img.shields.io/coveralls/github/tinyhttp/content-type?style=for-the-badge&color=FF69B4
|
||||
[cov-url]: https://coveralls.io/github/tinyhttp/@tinyhttp/content-type
|
||||
[dl-badge-url]: https://img.shields.io/npm/dt/@tinyhttp/content-type?style=for-the-badge&color=FF69B4
|
||||
[github-actions]: https://github.com/tinyhttp/content-type/actions
|
||||
[gh-actions-img]: https://img.shields.io/github/actions/workflow/status/tinyhttp/content-type/ci.yml?branch=master&style=for-the-badge&color=FF69B4&label=&logo=github
|
|
@ -0,0 +1,24 @@
|
|||
import { IncomingHttpHeaders, ServerResponse } from 'node:http';
|
||||
|
||||
type Request = {
|
||||
headers: IncomingHttpHeaders;
|
||||
};
|
||||
type Response = Pick<ServerResponse, 'getHeader'>;
|
||||
/**
|
||||
* Class to represent a content type.
|
||||
*/
|
||||
declare class ContentType {
|
||||
parameters?: Record<string, unknown>;
|
||||
type: string;
|
||||
constructor(type: string);
|
||||
}
|
||||
/**
|
||||
* Format object to media type.
|
||||
*/
|
||||
declare function format(obj: ContentType): string;
|
||||
/**
|
||||
* Parse media type to object.
|
||||
*/
|
||||
declare function parse(string: string | Request | Response): ContentType;
|
||||
|
||||
export { format, parse };
|
|
@ -0,0 +1,89 @@
|
|||
// src/index.ts
|
||||
var PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g;
|
||||
var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/;
|
||||
var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
||||
var QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g;
|
||||
var QUOTE_REGEXP = /([\\"])/g;
|
||||
var TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
||||
function qstring(val) {
|
||||
const str = String(val);
|
||||
if (TOKEN_REGEXP.test(str))
|
||||
return str;
|
||||
if (str.length > 0 && !TEXT_REGEXP.test(str))
|
||||
throw new TypeError("invalid parameter value");
|
||||
return '"' + str.replace(QUOTE_REGEXP, "\\$1") + '"';
|
||||
}
|
||||
function getcontenttype(obj) {
|
||||
let header;
|
||||
if ("getHeader" in obj && typeof obj.getHeader === "function") {
|
||||
header = obj.getHeader("content-type");
|
||||
} else if ("headers" in obj && typeof obj.headers === "object") {
|
||||
const h = obj.headers;
|
||||
header = h && h["content-type"];
|
||||
}
|
||||
if (typeof header !== "string") {
|
||||
throw new TypeError("content-type header is missing from object");
|
||||
}
|
||||
return header;
|
||||
}
|
||||
var ContentType = class {
|
||||
parameters;
|
||||
type;
|
||||
constructor(type) {
|
||||
this.parameters = {};
|
||||
this.type = type;
|
||||
}
|
||||
};
|
||||
function format(obj) {
|
||||
if (!obj || typeof obj !== "object")
|
||||
throw new TypeError("argument obj is required");
|
||||
const { parameters, type } = obj;
|
||||
if (!type || !TYPE_REGEXP.test(type))
|
||||
throw new TypeError("invalid type");
|
||||
let string = type;
|
||||
if (parameters && typeof parameters == "object") {
|
||||
const params = Object.keys(parameters).sort();
|
||||
for (const param of params) {
|
||||
if (!TOKEN_REGEXP.test(param))
|
||||
throw new TypeError("invalid parameter name");
|
||||
string += "; " + param + "=" + qstring(parameters[param]);
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
function parse(string) {
|
||||
if (!string)
|
||||
throw new TypeError("argument string is required");
|
||||
const header = typeof string == "object" ? getcontenttype(string) : string;
|
||||
if (typeof header !== "string")
|
||||
throw new TypeError("argument string is required to be a string");
|
||||
let index = header.indexOf(";");
|
||||
const type = index != -1 ? header.slice(0, index).trim() : header.trim();
|
||||
if (!TYPE_REGEXP.test(type))
|
||||
throw new TypeError("invalid media type");
|
||||
const obj = new ContentType(type.toLowerCase());
|
||||
if (index != -1) {
|
||||
let key;
|
||||
let match;
|
||||
let value;
|
||||
PARAM_REGEXP.lastIndex = index;
|
||||
while (match = PARAM_REGEXP.exec(header)) {
|
||||
if (match.index !== index)
|
||||
throw new TypeError("invalid parameter format");
|
||||
index += match[0].length;
|
||||
key = match[1].toLowerCase();
|
||||
value = match[2];
|
||||
if (value[0] == '"') {
|
||||
value = value.slice(1, value.length - 1).replace(QESC_REGEXP, "$1");
|
||||
}
|
||||
obj.parameters[key] = value;
|
||||
}
|
||||
if (index != header.length)
|
||||
throw new TypeError("invalid parameter format");
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
export {
|
||||
format,
|
||||
parse
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "@tinyhttp/content-type",
|
||||
"description": "content-type rewrite in TypeScript and ESM",
|
||||
"version": "0.1.4",
|
||||
"repository": "https://github.com/tinyhttp/content-type.git",
|
||||
"engines": {
|
||||
"node": ">=12.4"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "v1rtl <hi@v1rtl.site>",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.6.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
"@typescript-eslint/parser": "^6.7.2",
|
||||
"c8": "^8.0.1",
|
||||
"eslint": "^8.50.0",
|
||||
"tsm": "^2.3.0",
|
||||
"tsup": "^7.2.0",
|
||||
"typescript": "^5.2.2",
|
||||
"uvu": "^0.5.6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format esm --dts",
|
||||
"test": "uvu -r tsm test",
|
||||
"test:coverage": "c8 --include=src pnpm test",
|
||||
"test:report": "c8 report --reporter=text-lcov > coverage.lcov"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,25 @@
|
|||
# @tinyhttp/cookie-signature
|
||||
|
||||
[![npm (scoped)](https://img.shields.io/npm/v/@tinyhttp/cookie-signature?style=flat-square)](https://npmjs.com/package/@tinyhttp/cookie-signature) [![npm](https://img.shields.io/npm/dt/@tinyhttp/cookie-signature?style=flat-square)](https://npmjs.com/package/@tinyhttp/cookie-signature)
|
||||
|
||||
HTTP cookie signing and unsigning. A rewrite of [cookie-signature](https://github.com/tj/node-cookie-signature) module.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/cookie-signature
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
import { sign, unsign } from '@tinyhttp/cookie-signature'
|
||||
```
|
||||
|
||||
### `sign(val, secret)`
|
||||
|
||||
Signd the given `val` with `secret`.
|
||||
|
||||
### `unsign(val, secret)`
|
||||
|
||||
Unsign and decode the given `val` with `secret`, returning `false` if the signature is invalid.
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Sign the given `val` with `secret`.
|
||||
*/
|
||||
export declare const sign: (val: string, secret: string) => string;
|
||||
/**
|
||||
* Unsign and decode the given `val` with `secret`,
|
||||
* returning `false` if the signature is invalid.
|
||||
*/
|
||||
export declare const unsign: (val: string, secret: string) => string | false;
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,IAAI,QAAS,MAAM,UAAU,MAAM,KAAG,MACuC,CAAA;AAE1F;;;GAGG;AACH,eAAO,MAAM,MAAM,QAAS,MAAM,UAAU,MAAM,KAAG,MAAM,GAAG,KAQ7D,CAAA"}
|
|
@ -0,0 +1,11 @@
|
|||
import { createHmac, timingSafeEqual } from "node:crypto";
|
||||
const sign = (val, secret) => `${val}.${createHmac("sha256", secret).update(val).digest("base64").replace(/=+$/, "")}`;
|
||||
const unsign = (val, secret) => {
|
||||
const str = val.slice(0, val.lastIndexOf(".")), mac = sign(str, secret), macBuffer = Buffer.from(mac), valBuffer = Buffer.alloc(macBuffer.length);
|
||||
valBuffer.write(val);
|
||||
return timingSafeEqual(macBuffer, valBuffer) ? str : false;
|
||||
};
|
||||
export {
|
||||
sign,
|
||||
unsign
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@tinyhttp/cookie-signature",
|
||||
"version": "2.1.0",
|
||||
"description": "HTTP cookie signing and unsigning",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/cookie-signature"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"node.js",
|
||||
"web framework",
|
||||
"web",
|
||||
"backend",
|
||||
"static",
|
||||
"cookie"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"dependencies": {},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,176 @@
|
|||
# @tinyhttp/cookie
|
||||
|
||||
[![npm (scoped)](https://img.shields.io/npm/v/@tinyhttp/cookie?style=flat-square)](https://npmjs.com/package/@tinyhttp/cookie) [![npm](https://img.shields.io/npm/dt/@tinyhttp/cookie?style=flat-square)](https://npmjs.com/package/@tinyhttp/cookie)
|
||||
|
||||
> A rewrite of [cookie](https://github.com/jshttp/cookie) module.
|
||||
|
||||
HTTP cookie parser and serializer for Node.js.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/cookie
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
import { parse, serialize } from '@tinyhttp/cookie'
|
||||
```
|
||||
|
||||
### `parse(str, options)`
|
||||
|
||||
Parse an HTTP `Cookie` header string and returning an object of all cookie name-value pairs.
|
||||
The `str` argument is the string representing a `Cookie` header value and `options` is an
|
||||
optional object containing additional parsing options.
|
||||
|
||||
```js
|
||||
import { parse } from '@tinyhttp/cookie'
|
||||
|
||||
parse('foo=bar; equation=E%3Dmc%5E2')
|
||||
// { foo: 'bar', equation: 'E=mc^2' }
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
`parse` accepts these properties in the options object.
|
||||
|
||||
##### `decode`
|
||||
|
||||
Specifies a function that will be used to decode a cookie's value. Since the value of a cookie
|
||||
has a limited character set (and must be a simple string), this function can be used to decode
|
||||
a previously-encoded cookie value into a JavaScript string or other object.
|
||||
|
||||
The default function is the global `decodeURIComponent`, which will decode any URL-encoded
|
||||
sequences into their byte representations.
|
||||
|
||||
**note** if an error is thrown from this function, the original, non-decoded cookie value will
|
||||
be returned as the cookie's value.
|
||||
|
||||
### `serialize(name, value, options)`
|
||||
|
||||
Serialize a cookie name-value pair into a `Set-Cookie` header string. The `name` argument is the
|
||||
name for the cookie, the `value` argument is the value to set the cookie to, and the `options`
|
||||
argument is an optional object containing additional serialization options.
|
||||
|
||||
```js
|
||||
import { serialize } from '@tinyhttp/cookie'
|
||||
|
||||
serialize('foo', 'bar')
|
||||
// foo=bar
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
`serialize` accepts these properties in the options object.
|
||||
|
||||
##### `domain`
|
||||
|
||||
Specifies the value for the [`Domain` `Set-Cookie` attribute][rfc-6265-5.2.3]. By default, no
|
||||
domain is set, and most clients will consider the cookie to apply to only the current domain.
|
||||
|
||||
##### `encode`
|
||||
|
||||
Specifies a function that will be used to encode a cookie's value. Since value of a cookie
|
||||
has a limited character set (and must be a simple string), this function can be used to encode
|
||||
a value into a string suited for a cookie's value.
|
||||
|
||||
The default function is the global `encodeURIComponent`, which will encode a JavaScript string
|
||||
into UTF-8 byte sequences and then URL-encode any that fall outside of the cookie range.
|
||||
|
||||
##### `expires`
|
||||
|
||||
Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute][rfc-6265-5.2.1].
|
||||
By default, no expiration is set, and most clients will consider this a "non-persistent cookie" and
|
||||
will delete it on a condition like exiting a web browser application.
|
||||
|
||||
**note** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and
|
||||
`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
|
||||
so if both are set, they should point to the same date and time.
|
||||
|
||||
##### `httpOnly`
|
||||
|
||||
Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute][rfc-6265-5.2.6]. When truthy,
|
||||
the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set.
|
||||
|
||||
**note** be careful when setting this to `true`, as compliant clients will not allow client-side
|
||||
JavaScript to see the cookie in `document.cookie`.
|
||||
|
||||
##### `maxAge`
|
||||
|
||||
Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute][rfc-6265-5.2.2].
|
||||
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
|
||||
|
||||
**note** the [cookie storage model specification][rfc-6265-5.3] states that if both `expires` and
|
||||
`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
|
||||
so if both are set, they should point to the same date and time.
|
||||
|
||||
##### `path`
|
||||
|
||||
Specifies the value for the [`Path` `Set-Cookie` attribute][rfc-6265-5.2.4]. By default, the path
|
||||
is considered the ["default path"][rfc-6265-5.1.4].
|
||||
|
||||
##### `sameSite`
|
||||
|
||||
Specifies the `boolean` or `string` to be the value for the [`SameSite` `Set-Cookie` attribute][rfc-6265bis-03-4.1.2.7].
|
||||
|
||||
- `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
|
||||
- `false` will not set the `SameSite` attribute.
|
||||
- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
|
||||
- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
|
||||
- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
|
||||
|
||||
More information about the different enforcement levels can be found in
|
||||
[the specification][rfc-6265bis-03-4.1.2.7].
|
||||
|
||||
**note** This is an attribute that has not yet been fully standardized, and may change in the future.
|
||||
This also means many clients may ignore this attribute until they understand it.
|
||||
|
||||
##### `secure`
|
||||
|
||||
Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute][rfc-6265-5.2.5]. When truthy,
|
||||
the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
|
||||
|
||||
**note** be careful when setting this to `true`, as compliant clients will not send the cookie back to
|
||||
the server in the future if the browser does not have an HTTPS connection.
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { App } from '@tinyhttp/app'
|
||||
import { parse, serialize } from '@tinyhttp/cookie'
|
||||
import { escapeHTML } from 'es-escape-html'
|
||||
|
||||
new App()
|
||||
.use((req, res) => {
|
||||
if (req.query?.name) {
|
||||
// Set a new cookie with the name
|
||||
res.set(
|
||||
'Set-Cookie',
|
||||
serialize('name', String(query.name), {
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 60 * 24 * 7 // 1 week
|
||||
})
|
||||
)
|
||||
|
||||
// Redirect back after setting cookie
|
||||
res
|
||||
.status(302)
|
||||
.set('Location', req.headers.referer || '/')
|
||||
.end()
|
||||
}
|
||||
|
||||
const cookie = parse(req.headers.cookie || '')
|
||||
|
||||
const { name } = cookie
|
||||
|
||||
res.set('Content-Type', 'text/html; charset=UTF-8')
|
||||
|
||||
res.write(name ? `<p>Welcome back, <strong>${escapeHTML(name)}</strong>!</p>` : '<p>Hello, new visitor!</p>')
|
||||
|
||||
res.write('<form method="GET">')
|
||||
res.write('<input placeholder="enter your name" name="name"><input type="submit" value="Set Name">')
|
||||
res.end('</form>')
|
||||
})
|
||||
.listen(3000)
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Parse a cookie header.
|
||||
*
|
||||
* Parse the given cookie header string into an object
|
||||
* The object has the various cookies as keys(names) => values
|
||||
*
|
||||
*/
|
||||
export declare function parse(str: string, options?: {
|
||||
decode: (str: string) => string;
|
||||
}): Record<string, string>;
|
||||
export type SerializeOptions = Partial<{
|
||||
encode: (str: string) => string;
|
||||
maxAge: number;
|
||||
domain: string;
|
||||
path: string;
|
||||
httpOnly: boolean;
|
||||
secure: boolean;
|
||||
sameSite: boolean | 'Strict' | 'strict' | 'Lax' | 'lax' | 'None' | 'none' | string;
|
||||
expires: Date;
|
||||
}>;
|
||||
export declare function serialize(name: string, val: string, opt?: SerializeOptions): string;
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,wBAAgB,KAAK,CACnB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IACP,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CAGhC,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBxB;AAED,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC;IACrC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IAClF,OAAO,EAAE,IAAI,CAAA;CACd,CAAC,CAAA;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,gBAAqB,GAAG,MAAM,CAyDvF"}
|
|
@ -0,0 +1,81 @@
|
|||
const pairSplitRegExp = /; */;
|
||||
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
|
||||
function tryDecode(str, decode) {
|
||||
try {
|
||||
return decode(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
function parse(str, options = {
|
||||
decode: decodeURIComponent
|
||||
}) {
|
||||
const obj = {};
|
||||
const pairs = str.split(pairSplitRegExp);
|
||||
for (const pair of pairs) {
|
||||
let eqIdx = pair.indexOf("=");
|
||||
if (eqIdx < 0)
|
||||
continue;
|
||||
const key = pair.substr(0, eqIdx).trim();
|
||||
let val = pair.substr(++eqIdx, pair.length).trim();
|
||||
if ('"' == val[0])
|
||||
val = val.slice(1, -1);
|
||||
if (obj[key] == null)
|
||||
obj[key] = tryDecode(val, options.decode);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
function serialize(name, val, opt = {}) {
|
||||
if (!opt.encode)
|
||||
opt.encode = encodeURIComponent;
|
||||
if (!fieldContentRegExp.test(name))
|
||||
throw new TypeError("argument name is invalid");
|
||||
const value = opt.encode(val);
|
||||
if (value && !fieldContentRegExp.test(value))
|
||||
throw new TypeError("argument val is invalid");
|
||||
let str = name + "=" + value;
|
||||
if (null != opt.maxAge) {
|
||||
const maxAge = opt.maxAge - 0;
|
||||
if (isNaN(maxAge) || !isFinite(maxAge))
|
||||
throw new TypeError("option maxAge is invalid");
|
||||
str += "; Max-Age=" + Math.floor(maxAge);
|
||||
}
|
||||
if (opt.domain) {
|
||||
if (!fieldContentRegExp.test(opt.domain))
|
||||
throw new TypeError("option domain is invalid");
|
||||
str += "; Domain=" + opt.domain;
|
||||
}
|
||||
if (opt.path) {
|
||||
if (!fieldContentRegExp.test(opt.path))
|
||||
throw new TypeError("option path is invalid");
|
||||
str += "; Path=" + opt.path;
|
||||
}
|
||||
if (opt.expires)
|
||||
str += "; Expires=" + opt.expires.toUTCString();
|
||||
if (opt.httpOnly)
|
||||
str += "; HttpOnly";
|
||||
if (opt.secure)
|
||||
str += "; Secure";
|
||||
if (opt.sameSite) {
|
||||
const sameSite = typeof opt.sameSite === "string" ? opt.sameSite.toLowerCase() : opt.sameSite;
|
||||
switch (sameSite) {
|
||||
case true:
|
||||
case "strict":
|
||||
str += "; SameSite=Strict";
|
||||
break;
|
||||
case "lax":
|
||||
str += "; SameSite=Lax";
|
||||
break;
|
||||
case "none":
|
||||
str += "; SameSite=None";
|
||||
break;
|
||||
default:
|
||||
throw new TypeError("option sameSite is invalid");
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
export {
|
||||
parse,
|
||||
serialize
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "@tinyhttp/cookie",
|
||||
"version": "2.1.0",
|
||||
"type": "module",
|
||||
"description": "HTTP cookie parser and serializer for Node.js",
|
||||
"homepage": "https://github.com/tinyhttp/tinyhttp/tree/master/packages/cookie#readme",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/cookie"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"node.js",
|
||||
"web framework",
|
||||
"web",
|
||||
"backend",
|
||||
"cookie"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"dependencies": {},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,73 @@
|
|||
<div align="center">
|
||||
|
||||
# @tinyhttp/cors
|
||||
|
||||
[![npm][npm-img]][npm-url] [![GitHub Workflow Status][gh-actions-img]][github-actions] [![Coverage][cov-img]][cov-url]
|
||||
|
||||
</div>
|
||||
|
||||
> A rewrite of [expressjs/cors](https://github.com/expressjs/cors) module.
|
||||
|
||||
HTTP cors header middleware.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/cors
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import { cors } from '@tinyhttp/cors'
|
||||
```
|
||||
|
||||
### `cors(options)`
|
||||
|
||||
Returns the CORS middleware with the settings specified in the parameters
|
||||
|
||||
#### Options
|
||||
|
||||
- `origin`: Can be a string defining the `Access-Control-Allow-Origin` value, a boolean which if set to true sets the header to `'*'`, a Regex type, an array (for multiple origins) or a function which contains the request and response as parameters and must return the value for the `Access-Control-Allow-Origin` header
|
||||
- `methods`: Array of method names which define the `Access-Control-Allow-Methods` header, default to all the most common methods (`GET`, `HEAD`, `PUT`, `PATCH`, `POST`, `DELETE`)
|
||||
- `allowedHeaders`: Configures the `Access-Control-Allow-Headers` CORS header. Expects an array (ex: [`'Content-Type'`, `'Authorization'`]).
|
||||
- `exposedHeaders`: Configures the `Access-Control-Expose-Headers` CORS header. If not specified, no custom headers are exposed
|
||||
- `credentials`: Configures the `Access-Control-Allow-Credentials` CORS header. Set to true to pass the header, otherwise it is omitted.
|
||||
- `maxAge`: Configures the `Access-Control-Max-Age` CORS header. Set to an integer to pass the header, otherwise it is omitted.
|
||||
- `optionsSuccessStatus`: Provides a status code to use for successful OPTIONS requests, since some legacy browsers (IE11, various SmartTVs) choke on 204.
|
||||
- `preflightContinue`: Set 204 and finish response if `true`, call `next` if false.
|
||||
|
||||
The default configuration is:
|
||||
|
||||
```json
|
||||
{
|
||||
"origin": "*",
|
||||
"methods": ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
|
||||
"optionsSuccessStatus": 204,
|
||||
"preflightContinue": false
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { App } from '@tinyhttp/app'
|
||||
import { cors } from '@tinyhttp/cors'
|
||||
|
||||
const app = new App()
|
||||
|
||||
app
|
||||
.use(cors({ origin: 'https://myfantastic.site/' }))
|
||||
.options('*', cors())
|
||||
.get('/', (req, res) => {
|
||||
res.send('The headers contained in my response are defined in the cors middleware')
|
||||
})
|
||||
.listen(3000)
|
||||
```
|
||||
|
||||
[npm-url]: https://npmjs.com/package/@tinyhttp/cors
|
||||
[github-actions]: https://github.com/tinyhttp/cors/actions
|
||||
[gh-actions-img]: https://img.shields.io/github/workflow/status/tinyhttp/cors/CI?style=for-the-badge&logo=github&label=&color=hotpink
|
||||
[cov-img]: https://img.shields.io/coveralls/github/tinyhttp/cors?style=for-the-badge&color=hotpink
|
||||
[cov-url]: https://coveralls.io/github/tinyhttp/cors
|
||||
[npm-img]: https://img.shields.io/npm/dt/@tinyhttp/cors?style=for-the-badge&color=hotpink
|
|
@ -0,0 +1,16 @@
|
|||
/// <reference types="node" />
|
||||
import { IncomingMessage as Request, ServerResponse as Response } from 'http';
|
||||
export interface AccessControlOptions {
|
||||
origin?: string | boolean | ((req: Request, res: Response) => string) | Array<string> | RegExp;
|
||||
methods?: string[];
|
||||
allowedHeaders?: string[];
|
||||
exposedHeaders?: string[];
|
||||
credentials?: boolean;
|
||||
maxAge?: number;
|
||||
optionsSuccessStatus?: number;
|
||||
preflightContinue?: boolean;
|
||||
}
|
||||
/**
|
||||
* CORS Middleware
|
||||
*/
|
||||
export declare const cors: (opts?: AccessControlOptions) => (req: Request, res: Response, next?: () => void) => void;
|
|
@ -0,0 +1,63 @@
|
|||
import { vary } from 'es-vary';
|
||||
|
||||
/**
|
||||
* CORS Middleware
|
||||
*/
|
||||
const cors = (opts = {}) => {
|
||||
const { origin = '*', methods = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'], allowedHeaders = ['content-type'], exposedHeaders, credentials, maxAge, optionsSuccessStatus = 204, preflightContinue = false } = opts;
|
||||
return (req, res, next) => {
|
||||
var _a, _b;
|
||||
// Checking the type of the origin property
|
||||
if (typeof origin === 'boolean' && origin === true) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
else if (typeof origin === 'string') {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
else if (typeof origin === 'function') {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin(req, res));
|
||||
}
|
||||
else if (typeof origin === 'object') {
|
||||
if (Array.isArray(origin) && (origin.indexOf(req.headers.origin) !== -1 || origin.indexOf('*') !== -1)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
|
||||
}
|
||||
else if (origin instanceof RegExp && origin.test(req.headers.origin)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
|
||||
}
|
||||
else {
|
||||
throw new TypeError('No other objects allowed. Allowed types is array of strings or RegExp');
|
||||
}
|
||||
}
|
||||
if ((typeof origin === 'string' && origin !== '*') || typeof origin === 'function')
|
||||
vary(res, 'Origin');
|
||||
// Setting the Access-Control-Allow-Methods header from the methods array
|
||||
res.setHeader('Access-Control-Allow-Methods', methods.join(', ').toUpperCase());
|
||||
// Setting the Access-Control-Allow-Headers header
|
||||
if (allowedHeaders)
|
||||
res.setHeader('Access-Control-Allow-Headers', allowedHeaders);
|
||||
// Setting the Access-Control-Expose-Headers header
|
||||
if (exposedHeaders)
|
||||
res.setHeader('Access-Control-Expose-Headers', exposedHeaders);
|
||||
// Setting the Access-Control-Allow-Credentials header
|
||||
if (credentials)
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
// Setting the Access-Control-Max-Age header
|
||||
if (maxAge)
|
||||
res.setHeader('Access-Control-Max-Age', maxAge);
|
||||
if (((_b = (_a = req.method) === null || _a === void 0 ? void 0 : _a.toUpperCase) === null || _b === void 0 ? void 0 : _b.call(_a)) === 'OPTIONS') {
|
||||
if (preflightContinue) {
|
||||
next === null || next === void 0 ? void 0 : next();
|
||||
}
|
||||
else {
|
||||
res.statusCode = optionsSuccessStatus;
|
||||
res.setHeader('Content-Length', '0');
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
else {
|
||||
next === null || next === void 0 ? void 0 : next();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export { cors };
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "@tinyhttp/cors",
|
||||
"version": "2.0.0",
|
||||
"type": "module",
|
||||
"description": "CORS middleware for modern Node.js ",
|
||||
"homepage": "https://github.com/tinyhttp/cors#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/cors.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.4 || 14.x || >=16"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"node.js",
|
||||
"web framework",
|
||||
"web",
|
||||
"backend"
|
||||
],
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"es-vary": "^0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "13.1.0",
|
||||
"@commitlint/config-conventional": "13.1.0",
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@tinyhttp/app": "1.3.15",
|
||||
"@types/node": "^16.7.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.2",
|
||||
"@typescript-eslint/parser": "^4.29.2",
|
||||
"c8": "^7.8.0",
|
||||
"esbuild-node-loader": "^0.3.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"expect": "^27.0.6",
|
||||
"husky": "^7.0.1",
|
||||
"prettier": "^2.3.2",
|
||||
"rollup": "^2.56.2",
|
||||
"supertest-fetch": "^1.4.3",
|
||||
"typescript": "^4.3.5",
|
||||
"uvu": "^0.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"test": "node --experimental-loader esbuild-node-loader node_modules/uvu/bin.js tests",
|
||||
"test:coverage": "c8 --include=src pnpm test",
|
||||
"test:report": "c8 report --reporter=text-lcov > coverage.lcov",
|
||||
"lint": "eslint . --ext=ts",
|
||||
"format": "prettier --check \"./**/*.{ts,md}\"",
|
||||
"format:fix": "prettier --write \"./**/*.{ts,md}\""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,15 @@
|
|||
# @tinyhttp/encode-url
|
||||
|
||||
> [`encode-url`](https://github.com/pillarjs/encodeurl) rewrite in TypeScript.
|
||||
|
||||
Encode a URL to a percent-encoded form, excluding already-encoded sequences
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/encode-url
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
## Example
|
|
@ -0,0 +1,2 @@
|
|||
export declare const encodeUrl: (url: string) => string;
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,SAAS,QAAS,MAAM,KAAG,MAIvC,CAAA"}
|
|
@ -0,0 +1,10 @@
|
|||
const ENCODE_CHARS_REGEXP = /(?:[^\x21\x25\x26-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7A\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g;
|
||||
const UNMATCHED_SURROGATE_PAIR_REGEXP = /(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g;
|
||||
const UNMATCHED_SURROGATE_PAIR_REPLACE = "$1<>$2";
|
||||
const encodeUrl = (url) => {
|
||||
return String(url).replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE).replace(ENCODE_CHARS_REGEXP, encodeURI);
|
||||
};
|
||||
export {
|
||||
encodeUrl
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["const ENCODE_CHARS_REGEXP =\n /(?:[^\\x21\\x25\\x26-\\x3B\\x3D\\x3F-\\x5B\\x5D\\x5F\\x61-\\x7A\\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g\n\nconst UNMATCHED_SURROGATE_PAIR_REGEXP = /(^|[^\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF]([^\\uDC00-\\uDFFF]|$)/g\n\nconst UNMATCHED_SURROGATE_PAIR_REPLACE = '$1\\uFFFD$2'\n\nexport const encodeUrl = (url: string): string => {\n return String(url)\n .replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE)\n .replace(ENCODE_CHARS_REGEXP, encodeURI)\n}\n"],"names":[],"mappings":"AAAA,MAAM,sBACJ;AAEF,MAAM,kCAAkC;AAExC,MAAM,mCAAmC;AAE5B,MAAA,YAAY,CAAC,QAAwB;AACzC,SAAA,OAAO,GAAG,EACd,QAAQ,iCAAiC,gCAAgC,EACzE,QAAQ,qBAAqB,SAAS;AAC3C;"}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "@tinyhttp/encode-url",
|
||||
"version": "2.1.1",
|
||||
"description": "encode-url rewrite in TypeScript",
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/cors"
|
||||
},
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"node.js",
|
||||
"web framework",
|
||||
"web",
|
||||
"backend",
|
||||
"cors"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,35 @@
|
|||
# @tinyhttp/etag
|
||||
|
||||
[![npm (scoped)](https://img.shields.io/npm/v/@tinyhttp/etag?style=flat-square)](https://npmjs.com/package/@tinyhttp/etag) [![npm](https://img.shields.io/npm/dt/@tinyhttp/etag?style=flat-square)](https://npmjs.com/package/@tinyhttp/etag) [![](https://img.shields.io/badge/website-visit-hotpink?style=flat-square)](https://tinyhttp.v1rtl.site/mw/etag)
|
||||
|
||||
> A rewrite of [etag](https://www.npmjs.com/package/etag) module.
|
||||
|
||||
This module generates HTTP ETags (as defined in RFC 7232) for use in HTTP responses.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/etag
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import { eTag } from '@tinyhttp/etag'
|
||||
```
|
||||
|
||||
`eTag(entity, [options])`
|
||||
|
||||
Generate a strong ETag for the given entity. This should be the complete body of the entity. Strings, `Buffer`s, and `fs.Stats` are accepted. By default, a strong ETag is generated except for `fs.Stats`, which will generate a weak ETag (this can be overwritten by options.weak).
|
||||
|
||||
```ts
|
||||
res.setHeader('ETag', eTag(body))
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
`eTag` accepts these properties in the options object.
|
||||
|
||||
#### `weak`
|
||||
|
||||
Specifies if the generated ETag will include the weak validator mark (that is, the leading `W/`). The actual entity tag is the same. The default value is `false`, unless the entity is `fs.Stats`, in which case it is `true`.
|
|
@ -0,0 +1,7 @@
|
|||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { Stats } from 'node:fs';
|
||||
export declare const eTag: (entity: string | Buffer | Stats, options?: {
|
||||
weak: boolean;
|
||||
}) => string;
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAuB/B,eAAO,MAAM,IAAI,WAAY,MAAM,GAAG,MAAM,GAAG,KAAK,YAAY;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,KAAG,MAUnF,CAAA"}
|
|
@ -0,0 +1,25 @@
|
|||
import { createHash } from "node:crypto";
|
||||
import { Stats } from "node:fs";
|
||||
const entityTag = (entity) => {
|
||||
if (entity.length === 0) {
|
||||
return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
|
||||
} else {
|
||||
const hash = createHash("sha1").update(entity, "utf8").digest("base64").substring(0, 27);
|
||||
const len = typeof entity === "string" ? Buffer.byteLength(entity, "utf8") : entity.length;
|
||||
return '"' + len.toString(16) + "-" + hash + '"';
|
||||
}
|
||||
};
|
||||
const statTag = ({ mtime, size }) => {
|
||||
return '"' + mtime.getTime().toString(16) + "-" + size.toString(16) + '"';
|
||||
};
|
||||
const eTag = (entity, options) => {
|
||||
if (entity == null)
|
||||
throw new TypeError("argument entity is required");
|
||||
const weak = (options == null ? void 0 : options.weak) || entity instanceof Stats;
|
||||
const tag = entity instanceof Stats ? statTag(entity) : entityTag(entity);
|
||||
return weak ? "W/" + tag : tag;
|
||||
};
|
||||
export {
|
||||
eTag
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// Original module: https://github.com/jshttp/etag/blob/master/index.js\n\nimport { createHash } from 'node:crypto'\nimport { Stats } from 'node:fs'\n\nconst entityTag = (entity: string | Buffer): string => {\n if (entity.length === 0) {\n // fast-path empty\n return '\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"'\n } else {\n // generate hash\n const hash = createHash('sha1')\n .update(entity as string, 'utf8')\n .digest('base64')\n .substring(0, 27)\n\n const len = typeof entity === 'string' ? Buffer.byteLength(entity, 'utf8') : entity.length\n\n return '\"' + len.toString(16) + '-' + hash + '\"'\n }\n}\n\nconst statTag = ({ mtime, size }: Stats): string => {\n return '\"' + mtime.getTime().toString(16) + '-' + size.toString(16) + '\"'\n}\n\nexport const eTag = (entity: string | Buffer | Stats, options?: { weak: boolean }): string => {\n if (entity == null) throw new TypeError('argument entity is required')\n\n const weak = options?.weak || entity instanceof Stats\n\n // generate entity tag\n\n const tag = entity instanceof Stats ? statTag(entity) : entityTag(entity)\n\n return weak ? 'W/' + tag : tag\n}\n"],"names":[],"mappings":";;AAKA,MAAM,YAAY,CAAC,WAAoC;AACjD,MAAA,OAAO,WAAW,GAAG;AAEhB,WAAA;AAAA,EAAA,OACF;AAEL,UAAM,OAAO,WAAW,MAAM,EAC3B,OAAO,QAAkB,MAAM,EAC/B,OAAO,QAAQ,EACf,UAAU,GAAG,EAAE;AAEZ,UAAA,MAAM,OAAO,WAAW,WAAW,OAAO,WAAW,QAAQ,MAAM,IAAI,OAAO;AAEpF,WAAO,MAAM,IAAI,SAAS,EAAE,IAAI,MAAM,OAAO;AAAA,EAC/C;AACF;AAEA,MAAM,UAAU,CAAC,EAAE,OAAO,WAA0B;AAC3C,SAAA,MAAM,MAAM,QAAA,EAAU,SAAS,EAAE,IAAI,MAAM,KAAK,SAAS,EAAE,IAAI;AACxE;AAEa,MAAA,OAAO,CAAC,QAAiC,YAAwC;AAC5F,MAAI,UAAU;AAAY,UAAA,IAAI,UAAU,6BAA6B;AAE/D,QAAA,QAAO,mCAAS,SAAQ,kBAAkB;AAIhD,QAAM,MAAM,kBAAkB,QAAQ,QAAQ,MAAM,IAAI,UAAU,MAAM;AAEjE,SAAA,OAAO,OAAO,MAAM;AAC7B;"}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "@tinyhttp/etag",
|
||||
"version": "2.1.1",
|
||||
"type": "module",
|
||||
"description": "entity tag module",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/etag"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tinyhttp/tinyhttp/issues"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"node.js",
|
||||
"web framework",
|
||||
"web",
|
||||
"backend",
|
||||
"etag"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,17 @@
|
|||
# @tinyhttp/forwarded
|
||||
|
||||
> [`forwarded`](https://github.com/jshttp/forwarded) rewrite in TypeScript
|
||||
|
||||
Determine address of a proxied request
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/forwarded
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import { forwarded } from '@tinyhttp/forwarded'
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
/// <reference types="node" />
|
||||
import { IncomingMessage } from 'node:http';
|
||||
/**
|
||||
* Get all addresses in the request, using the `X-Forwarded-For` header.
|
||||
*/
|
||||
export declare function forwarded(req: Pick<IncomingMessage, 'headers' | 'socket'>): string[];
|
||||
/**
|
||||
* Parse the X-Forwarded-For header.
|
||||
*/
|
||||
export declare function parse(header: string): string[];
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE3C;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,CAAC,GAAG,MAAM,EAAE,CAOpF;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CA6B9C"}
|
|
@ -0,0 +1,36 @@
|
|||
function forwarded(req) {
|
||||
const proxyAddrs = parse(req.headers["x-forwarded-for"] || "");
|
||||
const socketAddr = req.socket.remoteAddress;
|
||||
return [socketAddr].concat(proxyAddrs);
|
||||
}
|
||||
function parse(header) {
|
||||
let end = header.length;
|
||||
const list = [];
|
||||
let start = header.length;
|
||||
for (let i = header.length - 1; i >= 0; i--) {
|
||||
switch (header.charCodeAt(i)) {
|
||||
case 32:
|
||||
if (start === end) {
|
||||
start = end = i;
|
||||
}
|
||||
break;
|
||||
case 44:
|
||||
if (start !== end) {
|
||||
list.push(header.substring(start, end));
|
||||
}
|
||||
start = end = i;
|
||||
break;
|
||||
default:
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (start !== end)
|
||||
list.push(header.substring(start, end));
|
||||
return list;
|
||||
}
|
||||
export {
|
||||
forwarded,
|
||||
parse
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { IncomingMessage } from 'node:http'\n\n/**\n * Get all addresses in the request, using the `X-Forwarded-For` header.\n */\nexport function forwarded(req: Pick<IncomingMessage, 'headers' | 'socket'>): string[] {\n // simple header parsing\n const proxyAddrs = parse((req.headers['x-forwarded-for'] as string) || '')\n const socketAddr = req.socket.remoteAddress\n\n // return all addresses\n return [socketAddr].concat(proxyAddrs)\n}\n\n/**\n * Parse the X-Forwarded-For header.\n */\nexport function parse(header: string): string[] {\n let end = header.length\n const list: string[] = []\n let start = header.length\n\n // gather addresses, backwards\n for (let i = header.length - 1; i >= 0; i--) {\n switch (header.charCodeAt(i)) {\n case 0x20 /* */:\n if (start === end) {\n start = end = i\n }\n break\n case 0x2c /* , */:\n if (start !== end) {\n list.push(header.substring(start, end))\n }\n start = end = i\n break\n default:\n start = i\n break\n }\n }\n\n // final address\n if (start !== end) list.push(header.substring(start, end))\n\n return list\n}\n"],"names":[],"mappings":"AAKO,SAAS,UAAU,KAA4D;AAEpF,QAAM,aAAa,MAAO,IAAI,QAAQ,iBAAiB,KAAgB,EAAE;AACnE,QAAA,aAAa,IAAI,OAAO;AAG9B,SAAO,CAAC,UAAU,EAAE,OAAO,UAAU;AACvC;AAKO,SAAS,MAAM,QAA0B;AAC9C,MAAI,MAAM,OAAO;AACjB,QAAM,OAAiB,CAAA;AACvB,MAAI,QAAQ,OAAO;AAGnB,WAAS,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK;AACnC,YAAA,OAAO,WAAW,CAAC,GAAG;AAAA,MAC5B,KAAK;AACH,YAAI,UAAU,KAAK;AACjB,kBAAQ,MAAM;AAAA,QAChB;AACA;AAAA,MACF,KAAK;AACH,YAAI,UAAU,KAAK;AACjB,eAAK,KAAK,OAAO,UAAU,OAAO,GAAG,CAAC;AAAA,QACxC;AACA,gBAAQ,MAAM;AACd;AAAA,MACF;AACU,gBAAA;AACR;AAAA,IACJ;AAAA,EACF;AAGA,MAAI,UAAU;AAAK,SAAK,KAAK,OAAO,UAAU,OAAO,GAAG,CAAC;AAElD,SAAA;AACT;"}
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@tinyhttp/forwarded",
|
||||
"version": "2.1.2",
|
||||
"type": "module",
|
||||
"description": "forwarded rewrite with TypeScript and ESM support",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/forwarded"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"node.js",
|
||||
"web framework",
|
||||
"web",
|
||||
"backend",
|
||||
"forwarded",
|
||||
"headers",
|
||||
"header"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,17 @@
|
|||
# @tinyhttp/proxyaddr
|
||||
|
||||
> [`proxy-addr`](https://github.com/jshttp/proxy-addr) rewrite in TypeScript
|
||||
|
||||
Determine address of a proxied request
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/proxy-addr
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import { proxyaddr, all } from '@tinyhttp/proxy-addr'
|
||||
```
|
|
@ -0,0 +1,37 @@
|
|||
/// <reference types="node" />
|
||||
import type { IncomingMessage } from 'node:http';
|
||||
import { IPv6, IPv4 } from 'ipaddr.js';
|
||||
type Req = Pick<IncomingMessage, 'headers' | 'socket'>;
|
||||
type Trust = ((addr: string, i: number) => boolean) | number[] | string[] | string;
|
||||
/**
|
||||
* Get all addresses in the request, optionally stopping
|
||||
* at the first untrusted.
|
||||
*
|
||||
* @param request
|
||||
* @param trust
|
||||
*/
|
||||
declare function alladdrs(req: Req, trust: Trust): string[];
|
||||
/**
|
||||
* Compile argument into trust function.
|
||||
*
|
||||
* @param val
|
||||
*/
|
||||
declare function compile(val: string | string[] | number[]): (addr: string) => boolean;
|
||||
/**
|
||||
* Parse IP notation string into range subnet.
|
||||
*
|
||||
* @param {String} note
|
||||
* @private
|
||||
*/
|
||||
export declare function parseIPNotation(note: string): [IPv4 | IPv6, string | number];
|
||||
/**
|
||||
* Determine address of proxied request.
|
||||
*
|
||||
* @param request
|
||||
* @param trust
|
||||
* @public
|
||||
*/
|
||||
export declare function proxyaddr(req: Req, trust: Trust): string;
|
||||
export { alladdrs as all };
|
||||
export { compile };
|
||||
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAe,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE9C,KAAK,GAAG,GAAG,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,CAAC,CAAA;AAEtD,KAAK,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,CAAA;AAmBlF;;;;;;GAMG;AACH,iBAAS,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CAclD;AACD;;;;GAIG;AACH,iBAAS,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAiB7E;AAoBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,CA0B5E;AAWD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAIxD;AAoDD,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,CAAA;AAC1B,OAAO,EAAE,OAAO,EAAE,CAAA"}
|
|
@ -0,0 +1,136 @@
|
|||
import { forwarded } from "@tinyhttp/forwarded";
|
||||
import ipaddr from "ipaddr.js";
|
||||
const DIGIT_REGEXP = /^[0-9]+$/;
|
||||
const isip = ipaddr.isValid;
|
||||
const parseip = ipaddr.parse;
|
||||
const IP_RANGES = {
|
||||
linklocal: ["169.254.0.0/16", "fe80::/10"],
|
||||
loopback: ["127.0.0.1/8", "::1/128"],
|
||||
uniquelocal: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fc00::/7"]
|
||||
};
|
||||
const trustNone = () => false;
|
||||
function alladdrs(req, trust) {
|
||||
const addrs = forwarded(req);
|
||||
if (!trust)
|
||||
return addrs;
|
||||
if (typeof trust !== "function")
|
||||
trust = compile(trust);
|
||||
for (let i = 0; i < addrs.length - 1; i++) {
|
||||
if (trust(addrs[i], i))
|
||||
continue;
|
||||
addrs.length = i + 1;
|
||||
}
|
||||
return addrs;
|
||||
}
|
||||
function compile(val) {
|
||||
let trust;
|
||||
if (typeof val === "string")
|
||||
trust = [val];
|
||||
else if (Array.isArray(val))
|
||||
trust = val.slice();
|
||||
else
|
||||
throw new TypeError("unsupported trust argument");
|
||||
for (let i = 0; i < trust.length; i++) {
|
||||
val = trust[i];
|
||||
if (!Object.prototype.hasOwnProperty.call(IP_RANGES, val))
|
||||
continue;
|
||||
val = IP_RANGES[val];
|
||||
trust.splice.apply(trust, [i, 1].concat(val));
|
||||
i += val.length - 1;
|
||||
}
|
||||
return compileTrust(compileRangeSubnets(trust));
|
||||
}
|
||||
function compileRangeSubnets(arr) {
|
||||
const rangeSubnets = new Array(arr.length);
|
||||
for (let i = 0; i < arr.length; i++)
|
||||
rangeSubnets[i] = parseIPNotation(arr[i]);
|
||||
return rangeSubnets;
|
||||
}
|
||||
function compileTrust(rangeSubnets) {
|
||||
const len = rangeSubnets.length;
|
||||
return len === 0 ? trustNone : len === 1 ? trustSingle(rangeSubnets[0]) : trustMulti(rangeSubnets);
|
||||
}
|
||||
function parseIPNotation(note) {
|
||||
const pos = note.lastIndexOf("/");
|
||||
const str = pos !== -1 ? note.substring(0, pos) : note;
|
||||
if (!isip(str))
|
||||
throw new TypeError("invalid IP address: " + str);
|
||||
let ip = parseip(str);
|
||||
if (pos === -1 && ip.kind() === "ipv6") {
|
||||
ip = ip;
|
||||
if (ip.isIPv4MappedAddress())
|
||||
ip = ip.toIPv4Address();
|
||||
}
|
||||
const max = ip.kind() === "ipv6" ? 128 : 32;
|
||||
let range = pos !== -1 ? note.substring(pos + 1, note.length) : null;
|
||||
if (range === null)
|
||||
range = max;
|
||||
else if (DIGIT_REGEXP.test(range))
|
||||
range = parseInt(range, 10);
|
||||
else if (ip.kind() === "ipv4" && isip(range))
|
||||
range = parseNetmask(range);
|
||||
else
|
||||
range = null;
|
||||
if (typeof range === "number" && (range <= 0 || range > max))
|
||||
throw new TypeError("invalid range on address: " + note);
|
||||
return [ip, range];
|
||||
}
|
||||
function parseNetmask(netmask) {
|
||||
const ip = parseip(netmask);
|
||||
return ip.kind() === "ipv4" ? ip.prefixLengthFromSubnetMask() : null;
|
||||
}
|
||||
function proxyaddr(req, trust) {
|
||||
const addrs = alladdrs(req, trust);
|
||||
return addrs[addrs.length - 1];
|
||||
}
|
||||
function trustMulti(subnets) {
|
||||
return function trust(addr) {
|
||||
if (!isip(addr))
|
||||
return false;
|
||||
const ip = parseip(addr);
|
||||
let ipconv;
|
||||
const kind = ip.kind();
|
||||
for (let i = 0; i < subnets.length; i++) {
|
||||
const subnet = subnets[i];
|
||||
const subnetip = subnet[0];
|
||||
const subnetkind = subnetip.kind();
|
||||
const subnetrange = subnet[1];
|
||||
let trusted = ip;
|
||||
if (kind !== subnetkind) {
|
||||
if (subnetkind === "ipv4" && !ip.isIPv4MappedAddress())
|
||||
continue;
|
||||
if (!ipconv)
|
||||
ipconv = subnetkind === "ipv4" ? ip.toIPv4Address() : ip.toIPv4MappedAddress();
|
||||
trusted = ipconv;
|
||||
}
|
||||
if (trusted.match(subnetip, subnetrange))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
function trustSingle(subnet) {
|
||||
const subnetip = subnet[0];
|
||||
const subnetkind = subnetip.kind();
|
||||
const subnetisipv4 = subnetkind === "ipv4";
|
||||
const subnetrange = subnet[1];
|
||||
return function trust(addr) {
|
||||
if (!isip(addr))
|
||||
return false;
|
||||
let ip = parseip(addr);
|
||||
const kind = ip.kind();
|
||||
if (kind !== subnetkind) {
|
||||
if (subnetisipv4 && !ip.isIPv4MappedAddress())
|
||||
return false;
|
||||
ip = subnetisipv4 ? ip.toIPv4Address() : ip.toIPv4MappedAddress();
|
||||
}
|
||||
return ip.match(subnetip, subnetrange);
|
||||
};
|
||||
}
|
||||
export {
|
||||
alladdrs as all,
|
||||
compile,
|
||||
parseIPNotation,
|
||||
proxyaddr
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
長すぎる行があるためファイル差分は表示されません
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "@tinyhttp/proxy-addr",
|
||||
"version": "2.1.3",
|
||||
"type": "module",
|
||||
"description": "proxy-addr rewrite with TypeScript and ESM support",
|
||||
"homepage": "https://tinyhttp.v1rtl.site",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tinyhttp/tinyhttp.git",
|
||||
"directory": "packages/proxy-addr"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": "./dist/index.js",
|
||||
"keywords": [
|
||||
"tinyhttp",
|
||||
"node.js",
|
||||
"web framework",
|
||||
"web",
|
||||
"backend",
|
||||
"proxy-addr",
|
||||
"ip",
|
||||
"net",
|
||||
"network"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"author": "v1rtl",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ipaddr.js": "^2.1.0",
|
||||
"@tinyhttp/forwarded": "2.1.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 v 1 r t l
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,19 @@
|
|||
# @tinyhttp/req
|
||||
|
||||
[![npm (scoped)][npm-badge]](https://npmjs.com/package/@tinyhttp/req) [![npm][dl-badge]](https://npmjs.com/package/@tinyhttp/req) [![][web-badge]](https://tinyhttp.v1rtl.site/mw/req)
|
||||
|
||||
Request extensions for tinyhttp collected in one package.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pnpm i @tinyhttp/req
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
[tinyhttp Request docs](https://tinyhttp.v1rtl.site/docs#request).
|
||||
|
||||
[npm-badge]: https://img.shields.io/npm/v/@tinyhttp/req?style=flat-square
|
||||
[dl-badge]: https://img.shields.io/npm/dt/@tinyhttp/req?style=flat-square
|
||||
[web-badge]: https://img.shields.io/badge/website-visit-hotpink?style=flat-square
|
|
@ -0,0 +1,10 @@
|
|||
/// <reference types="node" />
|
||||
import { IncomingMessage } from 'node:http';
|
||||
type Request = Pick<IncomingMessage, 'headers'>;
|
||||
type AcceptReturns = string | boolean | string[];
|
||||
export declare const getAccepts: (req: Request) => (...types: string[]) => AcceptReturns;
|
||||
export declare const getAcceptsEncodings: (req: Request) => (...encodings: string[]) => AcceptReturns;
|
||||
export declare const getAcceptsCharsets: (req: Request) => (...charsets: string[]) => AcceptReturns;
|
||||
export declare const getAcceptsLanguages: (req: Request) => (...languages: string[]) => AcceptReturns;
|
||||
export {};
|
||||
//# sourceMappingURL=accepts.d.ts.map
|
変更されたファイルが多すぎるため、一部のファイルは表示されません さらに表示
読み込み中…
新しいイシューから参照