added doga

This commit is contained in:
szabomarton
2025-02-25 09:55:29 +01:00
parent 5174ab4cc4
commit 13254e5623
1149 changed files with 80161 additions and 0 deletions

View File

@@ -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.

View File

@@ -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))
})
```

View File

@@ -0,0 +1,21 @@
export declare class ContentDisposition {
type: string;
parameters: Record<string, unknown>;
constructor(type: string, parameters: Record<string, string | undefined | boolean>);
}
/**
* 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;
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA8BA,qBAAa,kBAAkB;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBACvB,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;CAInF;AAoFD;;;;;GAKG;AAEH,wBAAgB,kBAAkB,CAChC,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,GAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CAC3B,CAAM,GACN,MAAM,CAGR;AA2BD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAuDxD"}

View File

@@ -0,0 +1,162 @@
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation>
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;
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation>
const QESC_REGEXP = /\\([\u0000-\u007f])/g;
const QUOTE_REGEXP = /([\\"])/g;
const PARAM_REGEXP =
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation>
/;[\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!#$&+.^_`|~-])+)$/;
// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation>
const DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/;
const getlatin1 = (val) => {
// simple Unicode -> ISO-8859-1 transformation
return String(val).replace(NON_LATIN1_REGEXP, '?');
};
export 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);
// percent encode as UTF-8
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');
}
// start with normalized type
let string = String(type).toLowerCase();
// append parameters
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 === undefined)
return {};
const params = {};
// fallback defaults to true
if (!fallback)
fallback = true;
if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {
throw new TypeError('fallback must be ISO-8859-1 string');
}
// restrict to file base name
const name = basename(filename);
// determine if name is suitable for quoted string
const isQuotedString = TEXT_REGEXP.test(name);
// generate fallback name
const fallbackName = typeof fallback !== 'string' ? fallback && getlatin1(name) : basename(fallback);
const hasFallback = typeof fallbackName === 'string' && fallbackName !== name;
// set extended filename parameter
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
params['filename*'] = name;
}
// set filename parameter
if (isQuotedString || hasFallback) {
params.filename = hasFallback ? fallbackName : name;
}
return params;
}
const pdecode = (_str, hex) => String.fromCharCode(Number.parseInt(hex, 16));
/**
* Create an attachment Content-Disposition header.
*
* @param filename file name
* @param options
*/
export function contentDisposition(filename, options = {}) {
// format into string
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;
}
/**
* Parse Content-Disposition header string.
* @param header string
*/
export function parse(header) {
let match = DISPOSITION_TYPE_REGEXP.exec(header);
if (!match)
throw new TypeError('invalid type format');
// normalize type
let index = match[0].length;
const type = match[1].toLowerCase();
let key;
const names = [];
const params = {};
let value;
// calculate index to start at
index = PARAM_REGEXP.lastIndex = match[0].slice(-1) === ';' ? index - 1 : index;
// match parameters
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) {
// decode extended value
key = key.slice(0, -1);
value = decodefield(value);
// overwrite existing 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);
}
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
{
"name": "@tinyhttp/content-disposition",
"description": "content-disposition rewrite in TypeScript",
"version": "2.2.2",
"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"
],
"scripts": {
"build": "tsc"
}
}