mhtml-stream 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,10 @@ Usage
17
17
  import { parseMhtml } from "mhtml-stream";
18
18
 
19
19
  for await (const { headers, content } of parseMhtml(...)) {
20
+ // ... : an async iterable of ArrayBuffers. This is very similar to the
21
+ // interface of a ReadableStream, but is a little more platform agnostic
22
+ // given that node handles streams significantly differently.
23
+
20
24
  // headers : a key-value object with the header information
21
25
 
22
26
  // content : a Uint8Array of the raw data, if you want as a string, `new
package/dist/headers.d.ts CHANGED
@@ -15,9 +15,9 @@ export interface MhtmlHeaders extends Iterable<[string, string]> {
15
15
  * iterate over all key-value pairs
16
16
  *
17
17
  * Multiple values for the same key will be joined in the order they were
18
- * added by `delim`.
18
+ * added by `delimiter`.
19
19
  */
20
- entries(delim?: string): IterableIterator<[string, string]>;
20
+ entries(delimiter?: string): IterableIterator<[string, string]>;
21
21
  /**
22
22
  * iterate over all key-value pairs
23
23
  *
@@ -29,9 +29,9 @@ export interface MhtmlHeaders extends Iterable<[string, string]> {
29
29
  * get the value for a key
30
30
  *
31
31
  * If the key is missing, null will be returned, if multiple values for the
32
- * key are present, they will be joined be `delim`.
32
+ * key are present, they will be joined be `delimiter`.
33
33
  */
34
- get(key: string, delim?: string): string | null;
34
+ get(key: string, delimiter?: string): string | null;
35
35
  /** get all values for a key */
36
36
  getAll(key: string): string[];
37
37
  /** whether key has any values associated with it */
@@ -41,9 +41,9 @@ export interface MhtmlHeaders extends Iterable<[string, string]> {
41
41
  /**
42
42
  * iterate over all values
43
43
  *
44
- * If a key has multiple values they will be joined by `delim`.
44
+ * If a key has multiple values they will be joined by `delimiter`.
45
45
  */
46
- values(delim?: string): IterableIterator<string>;
46
+ values(delimiter?: string): IterableIterator<string>;
47
47
  /** iterate over all values added */
48
48
  valuesAll(): IterableIterator<string>;
49
49
  }
package/dist/index.d.ts CHANGED
@@ -7,16 +7,12 @@ export interface MhtmlFile {
7
7
  /** the file's content */
8
8
  content: Uint8Array;
9
9
  }
10
- /** The decoder of any Content-Transfer-Encoding */
11
- export interface Decoder {
12
- /**
13
- * decode lines
14
- *
15
- * @param lines - each line of raw bytes
16
- * @returns data - decoded lines
17
- */
18
- (lines: AsyncIterable<Uint8Array>): AsyncIterable<Uint8Array>;
19
- }
10
+ /** The decoder of any Content-Transfer-Encoding
11
+ *
12
+ * @param lines - each line of raw bytes
13
+ * @returns data - decoded lines
14
+ */
15
+ export type Decoder = (lines: AsyncIterable<Uint8Array>) => AsyncIterable<Uint8Array>;
20
16
  /** options for mhtml parsing */
21
17
  export interface ParseOptions {
22
18
  /** custom decoders for other Content-Transfer-Encodings */
@@ -28,4 +24,4 @@ export interface ParseOptions {
28
24
  * decoderOverrides can be used to overwrite default encoders or specify your
29
25
  * own if a Content-Transfer-Encoding isn't handled properly
30
26
  */
31
- export declare function parseMhtml(stream: AsyncIterable<ArrayBuffer>, { decoderOverrides }?: ParseOptions): AsyncIterableIterator<MhtmlFile>;
27
+ export declare function parseMhtml(stream: AsyncIterable<Uint8Array>, { decoderOverrides }?: ParseOptions): AsyncIterableIterator<MhtmlFile>;
package/dist/index.js CHANGED
@@ -41,16 +41,12 @@ function decodeLine(line) {
41
41
  if (match) {
42
42
  const [, charset, encoding, text] = match;
43
43
  let buff;
44
- /* istanbul ignore else */
45
44
  if (encoding === "Q") {
46
45
  buff = decodeQEncoding(text);
47
46
  }
48
- else if (encoding === "B") {
49
- buff = toByteArray(text);
50
- }
51
47
  else {
52
- // NOTE should be impossible
53
- throw new Error(`unknown encoding for header value encoding: ${encoding}`);
48
+ // encoding === "B"
49
+ buff = toByteArray(text);
54
50
  }
55
51
  return new TextDecoder(charset).decode(buff);
56
52
  }
@@ -106,6 +102,7 @@ const defaultDecoders = new Map([
106
102
  ["8bit", decodeIdentity],
107
103
  ["binary", decodeBinary],
108
104
  ]);
105
+ // eslint-disable-next-line spellcheck/spell-checker
109
106
  /**
110
107
  * extract the boundary condition from a multipart header
111
108
  *
@@ -153,6 +150,7 @@ export async function* parseMhtml(stream, { decoderOverrides = new Map() } = {})
153
150
  const lines = splitStream(stream, crlf);
154
151
  let bound = null;
155
152
  let cont = true;
153
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
156
154
  while (cont) {
157
155
  // parse out headers and get encoding for content
158
156
  const headers = await parseHeaders(lines);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,248 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { parseMhtml } from ".";
3
+ import { Headers } from "./headers";
4
+ const encoder = new TextEncoder();
5
+ const decoder = new TextDecoder();
6
+ // from https://en.wikipedia.org/wiki/MIME#Multipart_messages
7
+ // eslint-disable-next-line spellcheck/spell-checker
8
+ const example = `MIME-Version: 1.0
9
+ Subject: =?iso-8859-1?Q?=A1Hola,_se=F1or!?=
10
+ Content-Type: multipart/mixed; boundary=frontier
11
+
12
+ This is a message with multiple parts in MIME format.
13
+ --frontier
14
+ Content-Type: text/plain
15
+
16
+ This is the body of the message.
17
+ --frontier
18
+ Content-Type: application/octet-stream
19
+ Content-Transfer-Encoding: base64
20
+
21
+ PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg
22
+ Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==
23
+ --frontier--
24
+ `;
25
+ const newYorker = `From: <Saved by Blink>
26
+ Snapshot-Content-Location: https://www.newyorker.com/culture/cultural-comment/what-the-twilight-zone-reveals-about-todays-prestige-tv
27
+ Subject: =?utf-8?Q?What=20=E2=80=9CThe=20Twilight=20Zone=E2=80=9D=20Reveals=20Abou?=
28
+ =?utf-8?Q?t=20Today=E2=80=99s=20Prestige=20TV=20|=20The=20New=20Yorker?=
29
+ Date: Sat, 16 Apr 2022 17:48:31 -0000
30
+ MIME-Version: 1.0
31
+ Content-Type: multipart/related;
32
+ type="text/html";
33
+ boundary="----MultipartBoundary--NYswbLinUCqE8KaJecg8DEV6giqFeyGLtHeT0qLB4h----"
34
+
35
+
36
+ ------MultipartBoundary--NYswbLinUCqE8KaJecg8DEV6giqFeyGLtHeT0qLB4h------
37
+ `;
38
+ // eslint-disable-next-line @typescript-eslint/require-await
39
+ async function* stringToStream(file) {
40
+ yield encoder.encode(file.replaceAll("\n", "\r\n"));
41
+ }
42
+ async function consume(iter) {
43
+ for await (const _ of iter) {
44
+ //
45
+ }
46
+ }
47
+ describe("parseMhtml()", () => {
48
+ test("example", async () => {
49
+ const files = [];
50
+ for await (const file of parseMhtml(stringToStream(example))) {
51
+ files.push(file);
52
+ }
53
+ const headers = files.map(({ headers }) => Object.fromEntries(headers));
54
+ const expectedHeaders = [
55
+ {
56
+ "MIME-Version": "1.0",
57
+ // eslint-disable-next-line spellcheck/spell-checker
58
+ Subject: "¡Hola, señor!",
59
+ "Content-Type": "multipart/mixed; boundary=frontier",
60
+ },
61
+ {
62
+ "Content-Type": "text/plain",
63
+ },
64
+ {
65
+ "Content-Type": "application/octet-stream",
66
+ "Content-Transfer-Encoding": "base64",
67
+ },
68
+ ];
69
+ expect(headers).toEqual(expectedHeaders);
70
+ const text = files
71
+ .slice(0, 2)
72
+ .map(({ content }) => decoder.decode(content));
73
+ const expectedText = [
74
+ "This is a message with multiple parts in MIME format.",
75
+ "This is the body of the message.",
76
+ ];
77
+ expect(text).toEqual(expectedText);
78
+ });
79
+ test("other headers", async () => {
80
+ // eslint-disable-next-line spellcheck/spell-checker
81
+ const content = `MIME-Version: 1.0
82
+ From: this is a wrapped: header
83
+ with an extra delimiter in: both sections
84
+ Subject: =?utf-8?B?QmFzZSA2NCDigJQgTW96aWxsYSBEZXZlbG9wZXIgTmV0d29yaw==?=
85
+ Content-Type: multipart/mixed; boundary="quoted-frontier"
86
+
87
+ This is a message with multiple parts in MIME format.
88
+ --quoted-frontier--
89
+ `;
90
+ const files = [];
91
+ for await (const file of parseMhtml(stringToStream(content))) {
92
+ files.push(file);
93
+ }
94
+ const headers = files.map(({ headers }) => Object.fromEntries(headers));
95
+ const expectedHeaders = [
96
+ {
97
+ "MIME-Version": "1.0",
98
+ From: "this is a wrapped: header with an extra delimiter in: both sections",
99
+ Subject: "Base 64 \u2014 Mozilla Developer Network",
100
+ "Content-Type": 'multipart/mixed; boundary="quoted-frontier"',
101
+ },
102
+ ];
103
+ expect(headers).toEqual(expectedHeaders);
104
+ const text = files.map(({ content }) => decoder.decode(content));
105
+ const expectedText = [
106
+ "This is a message with multiple parts in MIME format.",
107
+ ];
108
+ expect(text).toEqual(expectedText);
109
+ });
110
+ test("escaped multi-line headers", async () => {
111
+ const files = [];
112
+ for await (const file of parseMhtml(stringToStream(newYorker))) {
113
+ files.push(file);
114
+ }
115
+ const headers = files.map(({ headers }) => Object.fromEntries(headers));
116
+ const expectedHeaders = [
117
+ {
118
+ // eslint-disable-next-line spellcheck/spell-checker
119
+ "Content-Type": `multipart/related;type="text/html";boundary="----MultipartBoundary--NYswbLinUCqE8KaJecg8DEV6giqFeyGLtHeT0qLB4h----"`,
120
+ Date: "Sat, 16 Apr 2022 17:48:31 -0000",
121
+ From: "<Saved by Blink>",
122
+ "MIME-Version": "1.0",
123
+ "Snapshot-Content-Location": "https://www.newyorker.com/culture/cultural-comment/what-the-twilight-zone-reveals-about-todays-prestige-tv",
124
+ Subject:
125
+ // eslint-disable-next-line spellcheck/spell-checker
126
+ "What “The Twilight Zone” Reveals About Today’s Prestige TV | The New Yorker",
127
+ },
128
+ ];
129
+ expect(headers).toEqual(expectedHeaders);
130
+ const text = files.map(({ content }) => decoder.decode(content));
131
+ const expectedText = [""];
132
+ expect(text).toEqual(expectedText);
133
+ });
134
+ test("fails non-ascii in q-encoding", async () => {
135
+ // eslint-disable-next-line spellcheck/spell-checker
136
+ const content = `MIME-Version: 1.0
137
+ Subject: =?iso-8859-1?Q?=A1Hola,\xffse=F1or!?=
138
+ `;
139
+ const parser = parseMhtml(stringToStream(content));
140
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
141
+ await expect(consume(parser)).rejects.toThrow("got non-ascii character when decoding q-quoted word");
142
+ });
143
+ test("fails without empty header delimiter", async () => {
144
+ const content = `MIME-Version: 1.0`;
145
+ const parser = parseMhtml(stringToStream(content));
146
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
147
+ await expect(consume(parser)).rejects.toThrow("didn't find an empty line to signify the end of header parsing");
148
+ });
149
+ test("fails with invalid header", async () => {
150
+ const content = `MIME-Version: 1.0
151
+ invalid header
152
+
153
+ `;
154
+ const parser = parseMhtml(stringToStream(content));
155
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
156
+ await expect(consume(parser)).rejects.toThrow("header line didn't have key-value delimiter");
157
+ });
158
+ test("fails with missing Content-Type", async () => {
159
+ const content = `MIME-Version: 1.0
160
+
161
+ `;
162
+ const parser = parseMhtml(stringToStream(content));
163
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
164
+ await expect(consume(parser)).rejects.toThrow("first headers didn't contain a content type");
165
+ });
166
+ test("fails with missing multipart", async () => {
167
+ const content = `MIME-Version: 1.0
168
+ Content-Type: text/plain; boundary=frontier
169
+
170
+ `;
171
+ const parser = parseMhtml(stringToStream(content));
172
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
173
+ await expect(consume(parser)).rejects.toThrow("first content type header didn't contain");
174
+ });
175
+ test("fails with missing boundary", async () => {
176
+ const content = `MIME-Version: 1.0
177
+ Content-Type: multipart/mixed
178
+
179
+ `;
180
+ const parser = parseMhtml(stringToStream(content));
181
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
182
+ await expect(consume(parser)).rejects.toThrow("first content type header didn't contain");
183
+ });
184
+ test("fails with missing decoder", async () => {
185
+ const content = `MIME-Version: 1.0
186
+ Content-Transfer-Encoding: unknown
187
+ Content-Type: multipart/mixed; boundary=frontier
188
+
189
+ `;
190
+ const parser = parseMhtml(stringToStream(content));
191
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
192
+ await expect(consume(parser)).rejects.toThrow("unhandled encoding type: unknown");
193
+ });
194
+ test("fails without terminus", async () => {
195
+ const content = `MIME-Version: 1.0
196
+ Content-Type: multipart/mixed; boundary=frontier
197
+
198
+ This is a message with multiple parts in MIME format.
199
+ `;
200
+ const parser = parseMhtml(stringToStream(content));
201
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
202
+ await expect(consume(parser)).rejects.toThrow("stream didn't end with the appropriate termination boundary");
203
+ });
204
+ });
205
+ describe("MhtmlHeaders", () => {
206
+ const headers = new Headers();
207
+ headers.append("a", "b");
208
+ headers.append("a", "c");
209
+ headers.append("d", "e");
210
+ test("entries()", () => {
211
+ const entries = Object.fromEntries(headers.entries());
212
+ expect(entries).toEqual({ a: "b, c", d: "e" });
213
+ });
214
+ test("entriesAll()", () => {
215
+ const entriesAll = [...headers.entriesAll()].sort();
216
+ expect(entriesAll).toEqual([
217
+ ["a", "b"],
218
+ ["a", "c"],
219
+ ["d", "e"],
220
+ ]);
221
+ });
222
+ test("get()", () => {
223
+ expect(headers.get("a")).toStrictEqual("b, c");
224
+ expect(headers.get("d")).toStrictEqual("e");
225
+ expect(headers.get("f")).toStrictEqual(null);
226
+ });
227
+ test("getAll()", () => {
228
+ expect(headers.getAll("a")).toEqual(["b", "c"]);
229
+ expect(headers.getAll("d")).toEqual(["e"]);
230
+ expect(headers.getAll("f")).toEqual([]);
231
+ });
232
+ test("has()", () => {
233
+ expect(headers.has("a")).toBe(true);
234
+ expect(headers.has("f")).toBe(false);
235
+ });
236
+ test("keys()", () => {
237
+ const keys = [...headers.keys()].sort();
238
+ expect(keys).toEqual(["a", "d"]);
239
+ });
240
+ test("values()", () => {
241
+ const values = [...headers.values()].sort();
242
+ expect(values).toEqual(["b, c", "e"]);
243
+ });
244
+ test("valuesAll()", () => {
245
+ const valuesAll = [...headers.valuesAll()].sort();
246
+ expect(valuesAll).toEqual(["b", "c", "e"]);
247
+ });
248
+ });
@@ -0,0 +1 @@
1
+ var f=function(M){var Y=M.length;if(Y%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var W=M.indexOf("=");if(W===-1)W=Y;var z=W===Y?0:4-W%4;return[W,z]};var h=function(M,Y,W){return(Y+W)*3/4-W},v=function(M){var Y,W=f(M),z=W[0],G=W[1],J=new m(h(M,z,G)),K=0,R=G>0?z-4:z,U;for(U=0;U<R;U+=4)Y=V[M.charCodeAt(U)]<<18|V[M.charCodeAt(U+1)]<<12|V[M.charCodeAt(U+2)]<<6|V[M.charCodeAt(U+3)],J[K++]=Y>>16&255,J[K++]=Y>>8&255,J[K++]=Y&255;if(G===2)Y=V[M.charCodeAt(U)]<<2|V[M.charCodeAt(U+1)]>>4,J[K++]=Y&255;if(G===1)Y=V[M.charCodeAt(U)]<<10|V[M.charCodeAt(U+1)]<<4|V[M.charCodeAt(U+2)]>>2,J[K++]=Y>>8&255,J[K++]=Y&255;return J};var j=v;var g=[],V=[],m=typeof Uint8Array!=="undefined"?Uint8Array:Array,E="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(X=0,_=E.length;X<_;++X)g[X]=E[X],V[E.charCodeAt(X)]=X;var X,_;V["-".charCodeAt(0)]=62;V["_".charCodeAt(0)]=63;class F{#M=new Map;[Symbol.iterator](){return this.entries()}append(M,Y){const W=this.#M.get(M);if(W===void 0)this.#M.set(M,[Y]);else W.push(Y)}*entries(M=", "){for(let[Y,W]of this.#M.entries())yield[Y,W.join(M)]}*entriesAll(){for(let[M,Y]of this.#M.entries())for(let W of Y)yield[M,W]}get(M,Y=", "){const W=this.#M.get(M);if(W===void 0)return null;else return W.join(Y)}getAll(M){return this.#M.get(M)??[]}has(M){return this.#M.has(M)}keys(){return this.#M.keys()}*values(M=", "){for(let Y of this.#M.values())yield Y.join(M)}*valuesAll(){for(let M of this.#M.values())yield*M}}function N(M,Y){if(M.length!==Y.length)return!1;else if(M.byteOffset%4===Y.byteOffset%4){const W=(4-M.byteOffset%4)%4,z=Math.floor((M.byteLength-W)/4),G=W+z*4,J=new Uint32Array(M.buffer,M.byteOffset+W,z),K=new Uint32Array(Y.buffer,Y.byteOffset+W,z);for(let R=0;R<W;++R)if(M[R]!==Y[R])return!1;for(let R=0;R<z;++R)if(J[R]!==K[R])return!1;for(let R=G;R<M.length;++R)if(M[R]!==Y[R])return!1;return!0}else{for(let W of M.keys())if(M[W]!==Y[W])return!1;return!0}}function y(M,Y){return M.findIndex((W,z)=>{if(W!==Y[0]||z+Y.length>M.length)return!1;else{for(let G=1;G<Y.length;++G)if(M[z+G]!==Y[G])return!1;return!0}})}async function*q(M,Y){let W=new Uint8Array(0);for await(let z of M){W=W.length?A([W,z]):z;let G;while((G=y(W,Y))!==-1)yield W.subarray(0,G),W=W.subarray(G+Y.length)}yield W}var A=function(M){const Y=M.reduce((G,J)=>G+J.length,0),W=new Uint8Array(Y);let z=0;for(let G of M)W.set(G,z),z+=G.length;return W};async function C(M){const Y=[];for await(let W of M)Y.push(W);return A(Y)}async function*O(M,Y=b){for await(let W of M){const z=new Uint8Array(W.length+Y.length);let G=!1,J=0;for(let K=0;K<W.length;++K){const R=W[K];if(R>=128)throw new Error(`got non-ascii character when decoding quoted printable: ${R}`);if(R!==61)z[J++]=R;else{const U=W[++K];if(U===void 0)G=!0;else{const $=W[++K];if($===void 0)throw new Error("quoted printable escape (=) was not followed by two bytes");let Z=parseInt(String.fromCharCode(U),16);Z*=16,Z+=parseInt(String.fromCharCode($),16),z[J++]=Z}}}if(!G)z.set(Y,J),J+=Y.length;yield z.subarray(0,J)}}async function*Q(M){for await(let Y of M)yield j(L.decode(Y))}async function*P(M){for await(let Y of M)yield Y}function T(){throw new Error("binary transfer-encoding is explicitly not supported and trying to add an implementation will likely result in unexpected results, but if you want to ignore anyway, set binary to `decode8bit`")}var b=new Uint8Array([10]),L=new TextDecoder;var k=function(M){const Y=new Uint8Array(M.length);let W=0;for(let z=0;z<M.length;++z){const G=M.charCodeAt(z);let J;if(G>=128)throw new Error(`got non-ascii character when decoding q-quoted word: "${M}"`);else if(G===95)J=32;else if(G===61)J=parseInt(M[++z],16),J*=16,J+=parseInt(M[++z],16);else J=G;Y[W++]=J}return Y.subarray(0,W)},H=function(M){const Y=p.exec(M);if(Y){const[,W,z,G]=Y;let J;if(z==="Q")J=k(G);else J=j(G);return new TextDecoder(W).decode(J)}else return M};async function c(M){const Y=new F;let W="",z="";for(;;){const{done:G,value:J}=await M.next();if(G)throw new Error("didn't find an empty line to signify the end of header parsing");const K=I.decode(J);if(/^\s/.test(K))z+=H(K.substr(1));else{if(W)Y.append(W,z);if(K){const R=K.indexOf(": ");if(R===-1)throw new Error(`header line didn't have key-value delimiter: "${K}"`);W=K.slice(0,R),z=H(K.slice(R+2))}else return Y}}}var s=function(M){const Y=M.get("Content-Type");if(Y===null)throw new Error(`first headers didn't contain a content type: ${JSON.stringify(Object.fromEntries(M))}`);let W=void 0,z=!1;for(let K of Y.split(/;\s*/))if(K.startsWith("multipart/"))z=!0;else if(K.startsWith("boundary=")){if(W=K.slice(9),W.startsWith('"')&&W.endsWith('"'))W=W.slice(1,-1)}if(!z||W===void 0)throw new Error("first content type header didn't contain 'multipart/...' and a boundary string");const G=w.encode(`--${W}`),J=w.encode(`--${W}--`);return[G,J]};async function*e(M,{decoderOverrides:Y=new Map}={}){const W=new Map([...a.entries(),...Y.entries()]),z=new Uint8Array([13,10]),G=q(M,z);let J=null,K=!0;while(K){const R=await c(G),[U,$]=J??(J=s(R)),Z=R.get("Content-Transfer-Encoding")??"7bit",S=W.get(Z);if(S===void 0)throw new Error(`unhandled encoding type: ${Z}`);const B=await C(S({[Symbol.asyncIterator](){return{async next(){const{done:x,value:D}=await G.next();if(x)throw new Error(`stream didn't end with the appropriate termination boundary: ${I.decode($)}`);else if(N(D,U))return{done:!0,value:void 0};else if(N(D,$))return K=!1,{done:!0,value:void 0};else return{value:D}}}}}));yield{headers:R,content:B}}}var w=new TextEncoder,I=new TextDecoder,p=/^=\?([^?\s]+)\?([BQ])\?([^?\s]+)\?=$/,a=new Map([["7bit",P],["base64",Q],["quoted-printable",O],["8bit",P],["binary",T]]);export{e as parseMhtml};
package/dist/utils.d.ts CHANGED
@@ -1,10 +1,3 @@
1
- /**
2
- * create a Uint8Array from a buffer
3
- *
4
- * This method is necessary since creating a Uint8Array from a view will copy
5
- * the elements instead of creating a view of the underling buffer.
6
- */
7
- export declare function toBytes(buff: ArrayBuffer): Uint8Array;
8
1
  /**
9
2
  * Compare two byte arrays for equality
10
3
  */
@@ -20,7 +13,7 @@ export declare function indexOf(haystack: Uint8Array, needle: Uint8Array): numbe
20
13
  * compatibility between node and web, and splits it into an async iterator
21
14
  * where each value is delimited by the split sequence.
22
15
  */
23
- export declare function splitStream(iter: AsyncIterable<ArrayBuffer>, split: Uint8Array): AsyncIterableIterator<Uint8Array>;
16
+ export declare function splitStream(iter: AsyncIterable<Uint8Array>, split: Uint8Array): AsyncIterableIterator<Uint8Array>;
24
17
  /**
25
18
  * collect an async iterable of buffers into one
26
19
  */
package/dist/utils.js CHANGED
@@ -8,20 +8,6 @@
8
8
  * @packageDocumentation
9
9
  */
10
10
  import { toByteArray } from "base64-js";
11
- /**
12
- * create a Uint8Array from a buffer
13
- *
14
- * This method is necessary since creating a Uint8Array from a view will copy
15
- * the elements instead of creating a view of the underling buffer.
16
- */
17
- export function toBytes(buff) {
18
- if (ArrayBuffer.isView(buff)) {
19
- return new Uint8Array(buff.buffer, buff.byteOffset, buff.byteLength);
20
- }
21
- else {
22
- return new Uint8Array(buff);
23
- }
24
- }
25
11
  /**
26
12
  * Compare two byte arrays for equality
27
13
  */
@@ -91,8 +77,7 @@ export function indexOf(haystack, needle) {
91
77
  export async function* splitStream(iter, split) {
92
78
  let current = new Uint8Array(0);
93
79
  for await (const chunk of iter) {
94
- const next = toBytes(chunk);
95
- current = current.length ? concat([current, next]) : next;
80
+ current = current.length ? concat([current, chunk]) : chunk;
96
81
  let nextInd;
97
82
  while ((nextInd = indexOf(current, split)) !== -1) {
98
83
  yield current.subarray(0, nextInd);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,148 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { bytesEqual, collect, decodeBinary, decodeIdentity, decodeQuotedPrintable, indexOf, splitStream, } from "./utils";
3
+ const encoder = new TextEncoder();
4
+ const decoder = new TextDecoder();
5
+ // eslint-disable-next-line @typescript-eslint/require-await
6
+ async function* toAsyncIterable(items) {
7
+ for (const item of items) {
8
+ yield item;
9
+ }
10
+ }
11
+ describe("bytesEqual()", () => {
12
+ test("length failure", () => {
13
+ const left = new Uint8Array([1, 2, 3]);
14
+ const right = new Uint8Array([1, 2, 3, 4]);
15
+ expect(bytesEqual(left, right)).toBe(false);
16
+ });
17
+ test("simple success", () => {
18
+ const left = new Uint8Array([1, 2, 3]);
19
+ const right = new Uint8Array([1, 2, 3]);
20
+ expect(bytesEqual(left, right)).toBe(true);
21
+ });
22
+ test("simple failure", () => {
23
+ const left = new Uint8Array([1, 2, 3]);
24
+ const right = new Uint8Array([1, 2, 4]);
25
+ expect(bytesEqual(left, right)).toBe(false);
26
+ });
27
+ test("fast path success", () => {
28
+ const left = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).subarray(1);
29
+ const right = new Uint8Array([0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7]).subarray(5);
30
+ expect(bytesEqual(left, right)).toBe(true);
31
+ });
32
+ test("fast path failure start", () => {
33
+ const left = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).subarray(1);
34
+ const right = new Uint8Array([0, 0, 0, 0, 0, 2, 2, 3, 4, 5, 6, 7]).subarray(5);
35
+ expect(bytesEqual(left, right)).toBe(false);
36
+ });
37
+ test("fast path failure middle", () => {
38
+ const left = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).subarray(1);
39
+ const right = new Uint8Array([0, 0, 0, 0, 0, 1, 2, 4, 4, 5, 6, 7]).subarray(5);
40
+ expect(bytesEqual(left, right)).toBe(false);
41
+ });
42
+ test("fast path failure end", () => {
43
+ const left = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).subarray(1);
44
+ const right = new Uint8Array([0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8]).subarray(5);
45
+ expect(bytesEqual(left, right)).toBe(false);
46
+ });
47
+ test("slow path success", () => {
48
+ const left = new Uint8Array([1, 2, 3, 4, 5]);
49
+ const right = new Uint8Array([0, 1, 2, 3, 4, 5]).subarray(1);
50
+ expect(bytesEqual(left, right)).toBe(true);
51
+ });
52
+ test("slow path failure", () => {
53
+ const left = new Uint8Array([1, 2, 3, 4, 5]);
54
+ const right = new Uint8Array([0, 1, 2, 3, 4, 6]).subarray(1);
55
+ expect(bytesEqual(left, right)).toBe(false);
56
+ });
57
+ });
58
+ describe("indexOf()", () => {
59
+ test("simple success", () => {
60
+ const haystack = new Uint8Array([0, 1, 2]);
61
+ const needle = new Uint8Array([1]);
62
+ expect(indexOf(haystack, needle)).toEqual(1);
63
+ });
64
+ test("simple failure", () => {
65
+ const haystack = new Uint8Array([0, 1, 2]);
66
+ const needle = new Uint8Array([3]);
67
+ expect(indexOf(haystack, needle)).toEqual(-1);
68
+ });
69
+ test("complex success", () => {
70
+ const haystack = new Uint8Array([0, 1, 3, 1, 2, 3]);
71
+ const needle = new Uint8Array([1, 2]);
72
+ expect(indexOf(haystack, needle)).toEqual(3);
73
+ });
74
+ test("complex failure", () => {
75
+ const haystack = new Uint8Array([0, 1, 3, 1, 4, 3]);
76
+ const needle = new Uint8Array([1, 2]);
77
+ expect(indexOf(haystack, needle)).toEqual(-1);
78
+ });
79
+ });
80
+ test("splitStream()", async () => {
81
+ const split = new Uint8Array([1, 2]);
82
+ const input = [
83
+ new Uint8Array([255, 1, 2, 3, 4, 1, 2, 1]),
84
+ new Uint8Array([2, 5, 6, 7, 8]),
85
+ new Uint8Array([9, 10, 11, 2, 1, 12, 13]),
86
+ new Uint8Array([14, 15, 1, 2, 16]),
87
+ ];
88
+ const parts = [];
89
+ for await (const part of splitStream(toAsyncIterable(input), split)) {
90
+ parts.push(part);
91
+ }
92
+ const expected = [
93
+ new Uint8Array([255]),
94
+ new Uint8Array([3, 4]),
95
+ new Uint8Array(0),
96
+ new Uint8Array([5, 6, 7, 8, 9, 10, 11, 2, 1, 12, 13, 14, 15]),
97
+ new Uint8Array([16]),
98
+ ];
99
+ expect(parts.length).toEqual(expected.length);
100
+ for (const [i, part] of parts.entries()) {
101
+ expect(bytesEqual(part, expected[i])).toBe(true);
102
+ }
103
+ });
104
+ test("collect()", async () => {
105
+ const res = await collect(toAsyncIterable([
106
+ new Uint8Array([0, 1]),
107
+ new Uint8Array([2, 3, 4]),
108
+ new Uint8Array([5, 6]),
109
+ ]));
110
+ const expected = new Uint8Array([0, 1, 2, 3, 4, 5, 6]);
111
+ expect(bytesEqual(res, expected)).toBe(true);
112
+ });
113
+ describe("decodeQuotedPrintable()", () => {
114
+ test("success", async () => {
115
+ // eslint-disable-next-line spellcheck/spell-checker
116
+ const input = ["key=3Dvalue", "this line continues =", "on the next line"];
117
+ const res = decoder.decode(await collect(decodeQuotedPrintable(toAsyncIterable(input.map((l) => encoder.encode(l))))));
118
+ expect(res).toStrictEqual("key=value\nthis line continues on the next line\n");
119
+ });
120
+ test("ascii failure", async () => {
121
+ const input = ["\xff"];
122
+ const decoded = decodeQuotedPrintable(toAsyncIterable(input.map((l) => encoder.encode(l))));
123
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
124
+ await expect((async () => {
125
+ for await (const _ of decoded) {
126
+ //
127
+ }
128
+ })()).rejects.toThrow("non-ascii");
129
+ });
130
+ test("encoding failure", async () => {
131
+ const input = ["a=0"];
132
+ const decoded = decodeQuotedPrintable(toAsyncIterable(input.map((l) => encoder.encode(l))));
133
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression,@typescript-eslint/await-thenable
134
+ await expect((async () => {
135
+ for await (const _ of decoded) {
136
+ //
137
+ }
138
+ })()).rejects.toThrow("quoted printable escape");
139
+ });
140
+ });
141
+ test("decodeIdentity()", async () => {
142
+ const input = ["line ", "not ascii \xff"];
143
+ const res = decoder.decode(await collect(decodeIdentity(toAsyncIterable(input.map((l) => encoder.encode(l))))));
144
+ expect(res).toStrictEqual("line not ascii \xff");
145
+ });
146
+ test("decodeBinary()", () => {
147
+ expect(decodeBinary).toThrow("explicitly");
148
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mhtml-stream",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
4
  "description": "Zero dependency stream MHTML parser",
5
5
  "keywords": [
6
6
  "mhtml",
@@ -12,104 +12,37 @@
12
12
  },
13
13
  "author": "Erik Brinkman <erik.brinkman@gmail.com>",
14
14
  "license": "MIT",
15
+ "type": "module",
15
16
  "types": "dist/index.d.ts",
16
- "module": "bundle/mhtml.esm.min.js",
17
- "main": "bundle/mhtml.cjs.min.js",
18
- "unpkg": "bundle/mhtml.iife.min.js",
17
+ "module": "dist/mhtml.esm.min.js",
19
18
  "files": [
20
- "/bundle/*.js",
21
19
  "/dist/**/*.js",
22
20
  "/dist/**/*.d.ts"
23
21
  ],
24
- "type": "module",
25
22
  "scripts": {
26
- "fmt": "prettier --cache --write 'src/*.ts' '*.json' bundle.mjs",
27
- "lint:tsc": "pnpify tsc",
28
- "lint:es": "pnpify eslint --cache 'src/*.ts'",
29
- "lint:doc": "pnpify typedoc --emit none",
30
- "lint": "yarn lint:tsc && yarn lint:es && yarn lint:doc",
31
- "test": "jest --coverage",
32
- "doc": "pnpify typedoc",
33
- "build": "pnpify tsc -p tsconfig.build.json && node bundle.mjs",
34
- "prepack": "yarn lint && yarn test && yarn build"
23
+ "fmt": "prettier --cache --write 'src/*.ts' '*.{js,json}'",
24
+ "lint": "tsc && eslint --cache 'src/*.ts' && typedoc --emit none",
25
+ "doc": "typedoc",
26
+ "export": "tsc -p tsconfig.build.json && bun build src/index.ts --minify --outfile dist/mhtml.esm.min.js",
27
+ "prepack": "bun lint && bun test --coverage && bun export"
35
28
  },
36
29
  "dependencies": {
37
30
  "base64-js": "^1.5.1"
38
31
  },
39
32
  "devDependencies": {
40
- "@babel/core": "^7.22.5",
41
- "@babel/preset-env": "^7.22.5",
42
- "@babel/preset-typescript": "^7.22.5",
43
- "@types/base64-js": "^1.3.0",
44
- "@types/jest": "^29.5.2",
45
- "@types/node": "^20.3.3",
46
- "@typescript-eslint/eslint-plugin": "^5.60.1",
47
- "@typescript-eslint/parser": "^5.60.1",
48
- "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.15",
49
- "@yarnpkg/pnpify": "^4.0.0-rc.47",
50
- "babel-jest": "^29.5.0",
51
- "chalk": "5.3.0",
52
- "esbuild": "^0.18.10",
53
- "eslint": "^8.44.0",
54
- "eslint-config-prettier": "^8.8.0",
55
- "eslint-plugin-jest": "^27.2.2",
33
+ "@types/base64-js": "^1.3.2",
34
+ "@types/bun": "^1.1.0",
35
+ "eslint": "^9.0.0",
36
+ "eslint-config-prettier": "^9.1.0",
56
37
  "eslint-plugin-spellcheck": "^0.0.20",
57
38
  "eslint-plugin-tsdoc": "^0.2.17",
58
- "jest": "^29.5.0",
59
- "prettier": "^2.8.8",
60
- "prettier-plugin-organize-imports": "^3.2.2",
61
- "typedoc": "^0.24.8",
62
- "typescript": "5.1.6"
63
- },
64
- "resolutions": {
65
- "minimist": "1.2.6",
66
- "minimatch": "^3.0.5"
67
- },
68
- "prettier": {
69
- "plugins": [
70
- "prettier-plugin-organize-imports"
71
- ]
72
- },
73
- "jest": {
74
- "testEnvironment": "node"
75
- },
76
- "babel": {
77
- "presets": [
78
- [
79
- "@babel/preset-env",
80
- {
81
- "targets": {
82
- "node": "current"
83
- }
84
- }
85
- ],
86
- "@babel/preset-typescript"
87
- ]
39
+ "prettier": "^3.2.5",
40
+ "prettier-plugin-organize-imports": "^3.2.4",
41
+ "typedoc": "^0.25.8",
42
+ "typescript": "^5.3.3",
43
+ "typescript-eslint": "^7.7.0"
88
44
  },
89
45
  "eslintConfig": {
90
- "root": true,
91
- "parser": "@typescript-eslint/parser",
92
- "plugins": [
93
- "@typescript-eslint",
94
- "jest",
95
- "spellcheck",
96
- "eslint-plugin-tsdoc"
97
- ],
98
- "extends": [
99
- "eslint:recommended",
100
- "plugin:@typescript-eslint/recommended",
101
- "plugin:@typescript-eslint/recommended-requiring-type-checking",
102
- "plugin:jest/recommended",
103
- "prettier"
104
- ],
105
- "parserOptions": {
106
- "project": [
107
- "./tsconfig.json"
108
- ]
109
- },
110
- "env": {
111
- "node": true
112
- },
113
46
  "rules": {
114
47
  "@typescript-eslint/no-inferrable-types": "off",
115
48
  "@typescript-eslint/no-non-null-assertion": "off",
@@ -158,6 +91,5 @@
158
91
  }
159
92
  ]
160
93
  }
161
- },
162
- "packageManager": "yarn@3.6.0"
163
- }
94
+ }
95
+ }
@@ -1 +0,0 @@
1
- var j=Object.create;var h=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var N=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var R=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),V=(t,e)=>{for(var r in e)h(t,r,{get:e[r],enumerable:!0})},x=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of W(e))!J.call(t,o)&&o!==r&&h(t,o,{get:()=>e[o],enumerable:!(n=Q(e,o))||n.enumerable});return t};var U=(t,e,r)=>(r=t!=null?j(N(t)):{},x(e||!t||!t.__esModule?h(r,"default",{value:t,enumerable:!0}):r,t)),_=t=>x(h({},"__esModule",{value:!0}),t);var w=R(g=>{"use strict";g.byteLength=G;g.toByteArray=X;g.fromByteArray=ee;var c=[],d=[],z=typeof Uint8Array<"u"?Uint8Array:Array,b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(f=0,F=b.length;f<F;++f)c[f]=b[f],d[b.charCodeAt(f)]=f;var f,F;d["-".charCodeAt(0)]=62;d["_".charCodeAt(0)]=63;function B(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");r===-1&&(r=e);var n=r===e?0:4-r%4;return[r,n]}function G(t){var e=B(t),r=e[0],n=e[1];return(r+n)*3/4-n}function K(t,e,r){return(e+r)*3/4-r}function X(t){var e,r=B(t),n=r[0],o=r[1],a=new z(K(t,n,o)),s=0,i=o>0?n-4:n,l;for(l=0;l<i;l+=4)e=d[t.charCodeAt(l)]<<18|d[t.charCodeAt(l+1)]<<12|d[t.charCodeAt(l+2)]<<6|d[t.charCodeAt(l+3)],a[s++]=e>>16&255,a[s++]=e>>8&255,a[s++]=e&255;return o===2&&(e=d[t.charCodeAt(l)]<<2|d[t.charCodeAt(l+1)]>>4,a[s++]=e&255),o===1&&(e=d[t.charCodeAt(l)]<<10|d[t.charCodeAt(l+1)]<<4|d[t.charCodeAt(l+2)]>>2,a[s++]=e>>8&255,a[s++]=e&255),a}function Y(t){return c[t>>18&63]+c[t>>12&63]+c[t>>6&63]+c[t&63]}function Z(t,e,r){for(var n,o=[],a=e;a<r;a+=3)n=(t[a]<<16&16711680)+(t[a+1]<<8&65280)+(t[a+2]&255),o.push(Y(n));return o.join("")}function ee(t){for(var e,r=t.length,n=r%3,o=[],a=16383,s=0,i=r-n;s<i;s+=a)o.push(Z(t,s,s+a>i?i:s+a));return n===1?(e=t[r-1],o.push(c[e>>2]+c[e<<4&63]+"==")):n===2&&(e=(t[r-2]<<8)+t[r-1],o.push(c[e>>10]+c[e>>4&63]+c[e<<2&63]+"=")),o.join("")}});var fe={};V(fe,{parseMhtml:()=>ce});module.exports=_(fe);var q=U(w());var p=class{#e=new Map;[Symbol.iterator](){return this.entries()}append(e,r){let n=this.#e.get(e);n===void 0?this.#e.set(e,[r]):n.push(r)}*entries(e=", "){for(let[r,n]of this.#e.entries())yield[r,n.join(e)]}*entriesAll(){for(let[e,r]of this.#e.entries())for(let n of r)yield[e,n]}get(e,r=", "){let n=this.#e.get(e);return n===void 0?null:n.join(r)}getAll(e){return this.#e.get(e)??[]}has(e){return this.#e.has(e)}keys(){return this.#e.keys()}*values(e=", "){for(let r of this.#e.values())yield r.join(e)}*valuesAll(){for(let e of this.#e.values())yield*e}};var C=U(w());function te(t){return ArrayBuffer.isView(t)?new Uint8Array(t.buffer,t.byteOffset,t.byteLength):new Uint8Array(t)}function v(t,e){if(t.length!==e.length)return!1;if(t.byteOffset%4===e.byteOffset%4){let r=(4-t.byteOffset%4)%4,n=Math.floor((t.byteLength-r)/4),o=r+n*4,a=new Uint32Array(t.buffer,t.byteOffset+r,n),s=new Uint32Array(e.buffer,e.byteOffset+r,n);for(let i=0;i<r;++i)if(t[i]!==e[i])return!1;for(let i=0;i<n;++i)if(a[i]!==s[i])return!1;for(let i=o;i<t.length;++i)if(t[i]!==e[i])return!1;return!0}else{for(let r of t.keys())if(t[r]!==e[r])return!1;return!0}}function re(t,e){return t.findIndex((r,n)=>{if(r!==e[0]||n+e.length>t.length)return!1;for(let o=1;o<e.length;++o)if(t[n+o]!==e[o])return!1;return!0})}async function*k(t,e){let r=new Uint8Array(0);for await(let n of t){let o=te(n);r=r.length?E([r,o]):o;let a;for(;(a=re(r,e))!==-1;)yield r.subarray(0,a),r=r.subarray(a+e.length)}yield r}function E(t){let e=t.reduce((o,a)=>o+a.length,0),r=new Uint8Array(e),n=0;for(let o of t)r.set(o,n),n+=o.length;return r}async function M(t){let e=[];for await(let r of t)e.push(r);return E(e)}var ne=new Uint8Array([10]);async function*L(t,e=ne){for await(let r of t){let n=new Uint8Array(r.length+e.length),o=!1,a=0;for(let s=0;s<r.length;++s){let i=r[s];if(i>=128)throw new Error(`got non-ascii character when decoding quoted printable: ${i}`);if(i!==61)n[a++]=i;else{let l=r[++s];if(l===void 0)o=!0;else{let u=r[++s];if(u===void 0)throw new Error("quoted printable escape (=) was not followed by two bytes");let y=parseInt(String.fromCharCode(l),16);y*=16,y+=parseInt(String.fromCharCode(u),16),n[a++]=y}}}o||(n.set(e,a),a+=e.length),yield n.subarray(0,a)}}var oe=new TextDecoder;async function*O(t){for await(let e of t)yield(0,C.toByteArray)(oe.decode(e))}async function*m(t){for await(let e of t)yield e}function H(){throw new Error("binary transfer-encoding is explicitly not supported and trying to add an implementation will likely result in unexpected results, but if you want to ignore anyway, set binary to `decode8bit`")}var $=new TextEncoder,D=new TextDecoder;function ae(t){let e=new Uint8Array(t.length),r=0;for(let n=0;n<t.length;++n){let o=t.charCodeAt(n),a;if(o>=128)throw new Error(`got non-ascii character when decoding q-quoted word: "${t}"`);o===95?a=32:o===61?(a=parseInt(t[++n],16),a*=16,a+=parseInt(t[++n],16)):a=o,e[r++]=a}return e.subarray(0,r)}var se=/^=\?([^?\s]+)\?([BQ])\?([^?\s]+)\?=$/;function T(t){let e=se.exec(t);if(e){let[,r,n,o]=e,a;if(n==="Q")a=ae(o);else if(n==="B")a=(0,q.toByteArray)(o);else throw new Error(`unknown encoding for header value encoding: ${n}`);return new TextDecoder(r).decode(a)}else return t}async function ie(t){let e=new p,r="",n="";for(;;){let{done:o,value:a}=await t.next();if(o)throw new Error("didn't find an empty line to signify the end of header parsing");let s=D.decode(a);if(/^\s/.test(s))n+=T(s.substr(1));else if(r&&e.append(r,n),s){let i=s.indexOf(": ");if(i===-1)throw new Error(`header line didn't have key-value delimiter: "${s}"`);r=s.slice(0,i),n=T(s.slice(i+2))}else return e}}var le=new Map([["7bit",m],["base64",O],["quoted-printable",L],["8bit",m],["binary",H]]);function de(t){let e=t.get("Content-Type");if(e===null)throw new Error(`first headers didn't contain a content type: ${JSON.stringify(Object.fromEntries(t))}`);let r,n=!1;for(let s of e.split(/;\s*/))s.startsWith("multipart/")?n=!0:s.startsWith("boundary=")&&(r=s.slice(9),r.startsWith('"')&&r.endsWith('"')&&(r=r.slice(1,-1)));if(!n||r===void 0)throw new Error("first content type header didn't contain 'multipart/...' and a boundary string");let o=$.encode(`--${r}`),a=$.encode(`--${r}--`);return[o,a]}async function*ce(t,{decoderOverrides:e=new Map}={}){let r=new Map([...le.entries(),...e.entries()]),n=new Uint8Array([13,10]),o=k(t,n),a=null,s=!0;for(;s;){let i=await ie(o),[l,u]=a??(a=de(i)),y=i.get("Content-Transfer-Encoding")??"7bit",I=r.get(y);if(I===void 0)throw new Error(`unhandled encoding type: ${y}`);let P=await M(I({[Symbol.asyncIterator](){return{async next(){let{done:S,value:A}=await o.next();if(S)throw new Error(`stream didn't end with the appropriate termination boundary: ${D.decode(u)}`);return v(A,l)?{done:!0,value:void 0}:v(A,u)?(s=!1,{done:!0,value:void 0}):{value:A}}}}}));yield{headers:i,content:P}}}0&&(module.exports={parseMhtml});
@@ -1 +0,0 @@
1
- var S=Object.create;var I=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var J=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var R=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Q(e))!N.call(t,o)&&o!==r&&I(t,o,{get:()=>e[o],enumerable:!(n=j(e,o))||n.enumerable});return t};var x=(t,e,r)=>(r=t!=null?S(W(t)):{},R(e||!t||!t.__esModule?I(r,"default",{value:t,enumerable:!0}):r,t));var b=J(h=>{"use strict";h.byteLength=_;h.toByteArray=G;h.fromByteArray=Y;var c=[],d=[],V=typeof Uint8Array<"u"?Uint8Array:Array,A="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(f=0,U=A.length;f<U;++f)c[f]=A[f],d[A.charCodeAt(f)]=f;var f,U;d["-".charCodeAt(0)]=62;d["_".charCodeAt(0)]=63;function F(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");r===-1&&(r=e);var n=r===e?0:4-r%4;return[r,n]}function _(t){var e=F(t),r=e[0],n=e[1];return(r+n)*3/4-n}function z(t,e,r){return(e+r)*3/4-r}function G(t){var e,r=F(t),n=r[0],o=r[1],a=new V(z(t,n,o)),s=0,i=o>0?n-4:n,l;for(l=0;l<i;l+=4)e=d[t.charCodeAt(l)]<<18|d[t.charCodeAt(l+1)]<<12|d[t.charCodeAt(l+2)]<<6|d[t.charCodeAt(l+3)],a[s++]=e>>16&255,a[s++]=e>>8&255,a[s++]=e&255;return o===2&&(e=d[t.charCodeAt(l)]<<2|d[t.charCodeAt(l+1)]>>4,a[s++]=e&255),o===1&&(e=d[t.charCodeAt(l)]<<10|d[t.charCodeAt(l+1)]<<4|d[t.charCodeAt(l+2)]>>2,a[s++]=e>>8&255,a[s++]=e&255),a}function K(t){return c[t>>18&63]+c[t>>12&63]+c[t>>6&63]+c[t&63]}function X(t,e,r){for(var n,o=[],a=e;a<r;a+=3)n=(t[a]<<16&16711680)+(t[a+1]<<8&65280)+(t[a+2]&255),o.push(K(n));return o.join("")}function Y(t){for(var e,r=t.length,n=r%3,o=[],a=16383,s=0,i=r-n;s<i;s+=a)o.push(X(t,s,s+a>i?i:s+a));return n===1?(e=t[r-1],o.push(c[e>>2]+c[e<<4&63]+"==")):n===2&&(e=(t[r-2]<<8)+t[r-1],o.push(c[e>>10]+c[e>>4&63]+c[e<<2&63]+"=")),o.join("")}});var T=x(b());var g=class{#e=new Map;[Symbol.iterator](){return this.entries()}append(e,r){let n=this.#e.get(e);n===void 0?this.#e.set(e,[r]):n.push(r)}*entries(e=", "){for(let[r,n]of this.#e.entries())yield[r,n.join(e)]}*entriesAll(){for(let[e,r]of this.#e.entries())for(let n of r)yield[e,n]}get(e,r=", "){let n=this.#e.get(e);return n===void 0?null:n.join(r)}getAll(e){return this.#e.get(e)??[]}has(e){return this.#e.has(e)}keys(){return this.#e.keys()}*values(e=", "){for(let r of this.#e.values())yield r.join(e)}*valuesAll(){for(let e of this.#e.values())yield*e}};var B=x(b());function Z(t){return ArrayBuffer.isView(t)?new Uint8Array(t.buffer,t.byteOffset,t.byteLength):new Uint8Array(t)}function w(t,e){if(t.length!==e.length)return!1;if(t.byteOffset%4===e.byteOffset%4){let r=(4-t.byteOffset%4)%4,n=Math.floor((t.byteLength-r)/4),o=r+n*4,a=new Uint32Array(t.buffer,t.byteOffset+r,n),s=new Uint32Array(e.buffer,e.byteOffset+r,n);for(let i=0;i<r;++i)if(t[i]!==e[i])return!1;for(let i=0;i<n;++i)if(a[i]!==s[i])return!1;for(let i=o;i<t.length;++i)if(t[i]!==e[i])return!1;return!0}else{for(let r of t.keys())if(t[r]!==e[r])return!1;return!0}}function ee(t,e){return t.findIndex((r,n)=>{if(r!==e[0]||n+e.length>t.length)return!1;for(let o=1;o<e.length;++o)if(t[n+o]!==e[o])return!1;return!0})}async function*C(t,e){let r=new Uint8Array(0);for await(let n of t){let o=Z(n);r=r.length?k([r,o]):o;let a;for(;(a=ee(r,e))!==-1;)yield r.subarray(0,a),r=r.subarray(a+e.length)}yield r}function k(t){let e=t.reduce((o,a)=>o+a.length,0),r=new Uint8Array(e),n=0;for(let o of t)r.set(o,n),n+=o.length;return r}async function E(t){let e=[];for await(let r of t)e.push(r);return k(e)}var te=new Uint8Array([10]);async function*M(t,e=te){for await(let r of t){let n=new Uint8Array(r.length+e.length),o=!1,a=0;for(let s=0;s<r.length;++s){let i=r[s];if(i>=128)throw new Error(`got non-ascii character when decoding quoted printable: ${i}`);if(i!==61)n[a++]=i;else{let l=r[++s];if(l===void 0)o=!0;else{let u=r[++s];if(u===void 0)throw new Error("quoted printable escape (=) was not followed by two bytes");let y=parseInt(String.fromCharCode(l),16);y*=16,y+=parseInt(String.fromCharCode(u),16),n[a++]=y}}}o||(n.set(e,a),a+=e.length),yield n.subarray(0,a)}}var re=new TextDecoder;async function*L(t){for await(let e of t)yield(0,B.toByteArray)(re.decode(e))}async function*v(t){for await(let e of t)yield e}function O(){throw new Error("binary transfer-encoding is explicitly not supported and trying to add an implementation will likely result in unexpected results, but if you want to ignore anyway, set binary to `decode8bit`")}var H=new TextEncoder,q=new TextDecoder;function ne(t){let e=new Uint8Array(t.length),r=0;for(let n=0;n<t.length;++n){let o=t.charCodeAt(n),a;if(o>=128)throw new Error(`got non-ascii character when decoding q-quoted word: "${t}"`);o===95?a=32:o===61?(a=parseInt(t[++n],16),a*=16,a+=parseInt(t[++n],16)):a=o,e[r++]=a}return e.subarray(0,r)}var oe=/^=\?([^?\s]+)\?([BQ])\?([^?\s]+)\?=$/;function $(t){let e=oe.exec(t);if(e){let[,r,n,o]=e,a;if(n==="Q")a=ne(o);else if(n==="B")a=(0,T.toByteArray)(o);else throw new Error(`unknown encoding for header value encoding: ${n}`);return new TextDecoder(r).decode(a)}else return t}async function ae(t){let e=new g,r="",n="";for(;;){let{done:o,value:a}=await t.next();if(o)throw new Error("didn't find an empty line to signify the end of header parsing");let s=q.decode(a);if(/^\s/.test(s))n+=$(s.substr(1));else if(r&&e.append(r,n),s){let i=s.indexOf(": ");if(i===-1)throw new Error(`header line didn't have key-value delimiter: "${s}"`);r=s.slice(0,i),n=$(s.slice(i+2))}else return e}}var se=new Map([["7bit",v],["base64",L],["quoted-printable",M],["8bit",v],["binary",O]]);function ie(t){let e=t.get("Content-Type");if(e===null)throw new Error(`first headers didn't contain a content type: ${JSON.stringify(Object.fromEntries(t))}`);let r,n=!1;for(let s of e.split(/;\s*/))s.startsWith("multipart/")?n=!0:s.startsWith("boundary=")&&(r=s.slice(9),r.startsWith('"')&&r.endsWith('"')&&(r=r.slice(1,-1)));if(!n||r===void 0)throw new Error("first content type header didn't contain 'multipart/...' and a boundary string");let o=H.encode(`--${r}`),a=H.encode(`--${r}--`);return[o,a]}async function*ge(t,{decoderOverrides:e=new Map}={}){let r=new Map([...se.entries(),...e.entries()]),n=new Uint8Array([13,10]),o=C(t,n),a=null,s=!0;for(;s;){let i=await ae(o),[l,u]=a??(a=ie(i)),y=i.get("Content-Transfer-Encoding")??"7bit",m=r.get(y);if(m===void 0)throw new Error(`unhandled encoding type: ${y}`);let D=await E(m({[Symbol.asyncIterator](){return{async next(){let{done:P,value:p}=await o.next();if(P)throw new Error(`stream didn't end with the appropriate termination boundary: ${q.decode(u)}`);return w(p,l)?{done:!0,value:void 0}:w(p,u)?(s=!1,{done:!0,value:void 0}):{value:p}}}}}));yield{headers:i,content:D}}}export{ge as parseMhtml};
@@ -1 +0,0 @@
1
- var mhtml=(()=>{var j=Object.create;var h=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var N=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var R=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),V=(t,e)=>{for(var r in e)h(t,r,{get:e[r],enumerable:!0})},x=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of W(e))!J.call(t,o)&&o!==r&&h(t,o,{get:()=>e[o],enumerable:!(n=Q(e,o))||n.enumerable});return t};var U=(t,e,r)=>(r=t!=null?j(N(t)):{},x(e||!t||!t.__esModule?h(r,"default",{value:t,enumerable:!0}):r,t)),_=t=>x(h({},"__esModule",{value:!0}),t);var w=R(g=>{"use strict";g.byteLength=G;g.toByteArray=X;g.fromByteArray=ee;var c=[],d=[],z=typeof Uint8Array<"u"?Uint8Array:Array,b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(f=0,F=b.length;f<F;++f)c[f]=b[f],d[b.charCodeAt(f)]=f;var f,F;d["-".charCodeAt(0)]=62;d["_".charCodeAt(0)]=63;function B(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");r===-1&&(r=e);var n=r===e?0:4-r%4;return[r,n]}function G(t){var e=B(t),r=e[0],n=e[1];return(r+n)*3/4-n}function K(t,e,r){return(e+r)*3/4-r}function X(t){var e,r=B(t),n=r[0],o=r[1],a=new z(K(t,n,o)),s=0,i=o>0?n-4:n,l;for(l=0;l<i;l+=4)e=d[t.charCodeAt(l)]<<18|d[t.charCodeAt(l+1)]<<12|d[t.charCodeAt(l+2)]<<6|d[t.charCodeAt(l+3)],a[s++]=e>>16&255,a[s++]=e>>8&255,a[s++]=e&255;return o===2&&(e=d[t.charCodeAt(l)]<<2|d[t.charCodeAt(l+1)]>>4,a[s++]=e&255),o===1&&(e=d[t.charCodeAt(l)]<<10|d[t.charCodeAt(l+1)]<<4|d[t.charCodeAt(l+2)]>>2,a[s++]=e>>8&255,a[s++]=e&255),a}function Y(t){return c[t>>18&63]+c[t>>12&63]+c[t>>6&63]+c[t&63]}function Z(t,e,r){for(var n,o=[],a=e;a<r;a+=3)n=(t[a]<<16&16711680)+(t[a+1]<<8&65280)+(t[a+2]&255),o.push(Y(n));return o.join("")}function ee(t){for(var e,r=t.length,n=r%3,o=[],a=16383,s=0,i=r-n;s<i;s+=a)o.push(Z(t,s,s+a>i?i:s+a));return n===1?(e=t[r-1],o.push(c[e>>2]+c[e<<4&63]+"==")):n===2&&(e=(t[r-2]<<8)+t[r-1],o.push(c[e>>10]+c[e>>4&63]+c[e<<2&63]+"=")),o.join("")}});var fe={};V(fe,{parseMhtml:()=>ce});var q=U(w());var p=class{#e=new Map;[Symbol.iterator](){return this.entries()}append(e,r){let n=this.#e.get(e);n===void 0?this.#e.set(e,[r]):n.push(r)}*entries(e=", "){for(let[r,n]of this.#e.entries())yield[r,n.join(e)]}*entriesAll(){for(let[e,r]of this.#e.entries())for(let n of r)yield[e,n]}get(e,r=", "){let n=this.#e.get(e);return n===void 0?null:n.join(r)}getAll(e){return this.#e.get(e)??[]}has(e){return this.#e.has(e)}keys(){return this.#e.keys()}*values(e=", "){for(let r of this.#e.values())yield r.join(e)}*valuesAll(){for(let e of this.#e.values())yield*e}};var C=U(w());function te(t){return ArrayBuffer.isView(t)?new Uint8Array(t.buffer,t.byteOffset,t.byteLength):new Uint8Array(t)}function v(t,e){if(t.length!==e.length)return!1;if(t.byteOffset%4===e.byteOffset%4){let r=(4-t.byteOffset%4)%4,n=Math.floor((t.byteLength-r)/4),o=r+n*4,a=new Uint32Array(t.buffer,t.byteOffset+r,n),s=new Uint32Array(e.buffer,e.byteOffset+r,n);for(let i=0;i<r;++i)if(t[i]!==e[i])return!1;for(let i=0;i<n;++i)if(a[i]!==s[i])return!1;for(let i=o;i<t.length;++i)if(t[i]!==e[i])return!1;return!0}else{for(let r of t.keys())if(t[r]!==e[r])return!1;return!0}}function re(t,e){return t.findIndex((r,n)=>{if(r!==e[0]||n+e.length>t.length)return!1;for(let o=1;o<e.length;++o)if(t[n+o]!==e[o])return!1;return!0})}async function*k(t,e){let r=new Uint8Array(0);for await(let n of t){let o=te(n);r=r.length?E([r,o]):o;let a;for(;(a=re(r,e))!==-1;)yield r.subarray(0,a),r=r.subarray(a+e.length)}yield r}function E(t){let e=t.reduce((o,a)=>o+a.length,0),r=new Uint8Array(e),n=0;for(let o of t)r.set(o,n),n+=o.length;return r}async function M(t){let e=[];for await(let r of t)e.push(r);return E(e)}var ne=new Uint8Array([10]);async function*L(t,e=ne){for await(let r of t){let n=new Uint8Array(r.length+e.length),o=!1,a=0;for(let s=0;s<r.length;++s){let i=r[s];if(i>=128)throw new Error(`got non-ascii character when decoding quoted printable: ${i}`);if(i!==61)n[a++]=i;else{let l=r[++s];if(l===void 0)o=!0;else{let u=r[++s];if(u===void 0)throw new Error("quoted printable escape (=) was not followed by two bytes");let y=parseInt(String.fromCharCode(l),16);y*=16,y+=parseInt(String.fromCharCode(u),16),n[a++]=y}}}o||(n.set(e,a),a+=e.length),yield n.subarray(0,a)}}var oe=new TextDecoder;async function*O(t){for await(let e of t)yield(0,C.toByteArray)(oe.decode(e))}async function*m(t){for await(let e of t)yield e}function H(){throw new Error("binary transfer-encoding is explicitly not supported and trying to add an implementation will likely result in unexpected results, but if you want to ignore anyway, set binary to `decode8bit`")}var $=new TextEncoder,D=new TextDecoder;function ae(t){let e=new Uint8Array(t.length),r=0;for(let n=0;n<t.length;++n){let o=t.charCodeAt(n),a;if(o>=128)throw new Error(`got non-ascii character when decoding q-quoted word: "${t}"`);o===95?a=32:o===61?(a=parseInt(t[++n],16),a*=16,a+=parseInt(t[++n],16)):a=o,e[r++]=a}return e.subarray(0,r)}var se=/^=\?([^?\s]+)\?([BQ])\?([^?\s]+)\?=$/;function T(t){let e=se.exec(t);if(e){let[,r,n,o]=e,a;if(n==="Q")a=ae(o);else if(n==="B")a=(0,q.toByteArray)(o);else throw new Error(`unknown encoding for header value encoding: ${n}`);return new TextDecoder(r).decode(a)}else return t}async function ie(t){let e=new p,r="",n="";for(;;){let{done:o,value:a}=await t.next();if(o)throw new Error("didn't find an empty line to signify the end of header parsing");let s=D.decode(a);if(/^\s/.test(s))n+=T(s.substr(1));else if(r&&e.append(r,n),s){let i=s.indexOf(": ");if(i===-1)throw new Error(`header line didn't have key-value delimiter: "${s}"`);r=s.slice(0,i),n=T(s.slice(i+2))}else return e}}var le=new Map([["7bit",m],["base64",O],["quoted-printable",L],["8bit",m],["binary",H]]);function de(t){let e=t.get("Content-Type");if(e===null)throw new Error(`first headers didn't contain a content type: ${JSON.stringify(Object.fromEntries(t))}`);let r,n=!1;for(let s of e.split(/;\s*/))s.startsWith("multipart/")?n=!0:s.startsWith("boundary=")&&(r=s.slice(9),r.startsWith('"')&&r.endsWith('"')&&(r=r.slice(1,-1)));if(!n||r===void 0)throw new Error("first content type header didn't contain 'multipart/...' and a boundary string");let o=$.encode(`--${r}`),a=$.encode(`--${r}--`);return[o,a]}async function*ce(t,{decoderOverrides:e=new Map}={}){let r=new Map([...le.entries(),...e.entries()]),n=new Uint8Array([13,10]),o=k(t,n),a=null,s=!0;for(;s;){let i=await ie(o),[l,u]=a??(a=de(i)),y=i.get("Content-Transfer-Encoding")??"7bit",I=r.get(y);if(I===void 0)throw new Error(`unhandled encoding type: ${y}`);let P=await M(I({[Symbol.asyncIterator](){return{async next(){let{done:S,value:A}=await o.next();if(S)throw new Error(`stream didn't end with the appropriate termination boundary: ${D.decode(u)}`);return v(A,l)?{done:!0,value:void 0}:v(A,u)?(s=!1,{done:!0,value:void 0}):{value:A}}}}}));yield{headers:i,content:P}}}return _(fe);})();