generated from sapphiredev/sapphire-template
-
-
Notifications
You must be signed in to change notification settings - Fork 35
/
MediaParser.ts
108 lines (94 loc) · 3.07 KB
/
MediaParser.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { Piece } from '@sapphire/pieces';
import type { Awaitable } from '@sapphire/utilities';
import { createBrotliDecompress, createGunzip, createInflate } from 'zlib';
import type { ApiRequest } from './api/ApiRequest';
import type { MimeTypeWithoutParameters } from './http/Server';
import type { Route } from './Route';
/**
* A media parser
* @since 1.3.0
*/
export abstract class MediaParser extends Piece {
/**
* Parses the body data from an API request.
* @since 1.3.0
*/
public abstract run(request: ApiRequest): Awaitable<unknown>;
/**
* Checks if a route accepts the media type from this parser.
* @since 1.3.0
* @param route The route to be checked.
*/
public accepts(route: Route): boolean {
return route.acceptedContentMimeTypes === null || route.acceptedContentMimeTypes.includes(this.name as MimeTypeWithoutParameters);
}
/**
* Reads the content body as a string, this is useful for parsing/reading plain-text data.
* @since 1.3.0
* @param request The request to read the body from.
*/
protected async readString(request: ApiRequest): Promise<string> {
const stream = this.contentStream(request);
if (stream === null) return '';
let body = '';
for await (const chunk of stream) body += chunk;
return body;
}
/**
* Reads the content body as a buffer, this is useful for parsing/reading binary data.
* @since 1.3.0
* @param request The request to read the body from.
*/
protected async readBuffer(request: ApiRequest): Promise<Buffer> {
const stream = this.contentStream(request);
if (stream === null) return Buffer.alloc(0);
const bodies: Buffer[] = [];
for await (const chunk of stream) bodies.push(chunk);
return Buffer.concat(bodies);
}
/**
* Reads the content stream from a request, piping the data through a transformer stream.
* @since 1.3.0
* @param request The request to read the body from.
*/
protected contentStream(request: ApiRequest) {
switch ((request.headers['content-encoding'] ?? 'identity').toLowerCase()) {
// RFC 7230 4.2.2:
//
// The "deflate" coding is a "zlib" data format (RFC 1950) containing a "deflate" compressed data stream
// (RFC 1951) that uses a combination of the Lempel-Ziv (LZ77) compression algorithm and Huffman coding.
case 'deflate': {
const stream = createInflate();
request.pipe(stream);
return stream;
}
// RFC 7230 4.2.3
//
// The "gzip" coding is an LZ77 coding with a 32-bit Cyclic Redundancy Check (CRC) that is commonly produced
// by the gzip file compression program (RFC 1952).
case 'x-gzip':
case 'gzip': {
const stream = createGunzip();
request.pipe(stream);
return stream;
}
// RFC 7932
//
// A format using the Brotli algorithm.
case 'br': {
const stream = createBrotliDecompress();
request.pipe(stream);
return stream;
}
// An "identity" token is used as a synonym for "no encoding" in order to communicate when no encoding is
// preferred.
case 'identity': {
return request;
}
}
return null;
}
}
export namespace MediaParser {
export type Context = Piece.Context;
}