]> Lady’s Gitweb - Pisces/blob - binary.js
dc4b6120ebfffb0e5ed845de73808b40c937b90a
[Pisces] / binary.js
1 // ♓🌟 Piscēs ∷ binary.js
2 // ====================================================================
3 //
4 // Copyright © 2020–2022 Lady [@ Lady’s Computer].
5 //
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
9
10 import { fill, map, reduce } from "./collection.js";
11 import { bind, call } from "./function.js";
12 import { ceil, floor } from "./numeric.js";
13 import { hasOwnProperty, objectCreate } from "./object.js";
14 import {
15 getCodeUnit,
16 rawString,
17 stringFromCodeUnits,
18 stringReplace,
19 } from "./string.js";
20
21 const Buffer = ArrayBuffer;
22 const View = DataView;
23 const TypedArray = Object.getPrototypeOf(Uint8Array);
24 const { prototype: arrayPrototype } = Array;
25 const { prototype: bufferPrototype } = Buffer;
26 const { iterator: iteratorSymbol } = Symbol;
27 const { prototype: rePrototype } = RegExp;
28 const { prototype: typedArrayPrototype } = TypedArray;
29 const { prototype: viewPrototype } = View;
30
31 const { [iteratorSymbol]: arrayIterator } = arrayPrototype;
32 const {
33 next: arrayIteratorNext,
34 } = Object.getPrototypeOf([][iteratorSymbol]());
35 const getBufferByteLength =
36 Object.getOwnPropertyDescriptor(bufferPrototype, "byteLength").get;
37 const getTypedArrayBuffer =
38 Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
39 const getViewBuffer =
40 Object.getOwnPropertyDescriptor(viewPrototype, "buffer").get;
41 const { exec: reExec } = rePrototype;
42 const {
43 getUint8: viewGetUint8,
44 setUint8: viewSetUint8,
45 setUint16: viewSetUint16,
46 } = viewPrototype;
47
48 const bufferFromArgs = ($, $s) =>
49 $ instanceof Buffer
50 ? $
51 : $ instanceof View
52 ? call(getViewBuffer, $, [])
53 : $ instanceof TypedArray
54 ? call(getTypedArrayBuffer, $, [])
55 : ((string) =>
56 call(
57 getViewBuffer,
58 reduce(
59 string,
60 (result, ucsCharacter, index) => (
61 call(viewSetUint16, result, [
62 index * 2,
63 getCodeUnit(ucsCharacter, 0),
64 ]), result
65 ),
66 new View(new Buffer(string.length * 2)),
67 ),
68 [],
69 ))(
70 typeof $ == "string"
71 ? $
72 : hasOwnProperty($, "raw")
73 ? rawString($, ...$s)
74 : `${$}`,
75 );
76
77 const binaryCodeUnitIterablePrototype = {
78 [iteratorSymbol]() {
79 return {
80 next: bind(
81 arrayIteratorNext,
82 call(arrayIterator, this, []),
83 [],
84 ),
85 };
86 },
87 };
88
89 const decodeBase64 = (source, safe = false) => {
90 const u6s = map(
91 source.length % 4 == 0
92 ? stringReplace(source, /={1,2}$/u, "")
93 : source,
94 (ucsCharacter) => {
95 const code = getCodeUnit(ucsCharacter, 0);
96 const result = code >= 0x41 && code <= 0x5A
97 ? code - 65
98 : code >= 0x61 && code <= 0x7A
99 ? code - 71
100 : code >= 0x30 && code <= 0x39
101 ? code + 4
102 : code == (safe ? 0x2D : 0x2B)
103 ? 62
104 : code == (safe ? 0x5F : 0x2F)
105 ? 63
106 : -1;
107 if (result < 0) {
108 throw new RangeError(
109 `Piscēs: Invalid character in Base64: ${character}.`,
110 );
111 } else {
112 return result;
113 }
114 },
115 );
116 const { length } = u6s;
117 const dataView = new View(new Buffer(floor(length * 3 / 4)));
118 for (let index = 0; index < length - 1;) {
119 const dataIndex = ceil(index * 3 / 4);
120 const remainder = index % 3;
121 if (remainder == 0) {
122 call(viewSetUint8, dataView, [
123 dataIndex,
124 (u6s[index] << 2) + (u6s[++index] >> 4),
125 ]);
126 } else if (remainder == 1) {
127 call(viewSetUint8, dataView, [
128 dataIndex,
129 ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
130 ]);
131 } else {
132 call(viewSetUint8, dataView, [
133 dataIndex,
134 ((u6s[index] & 0x3) << 6) + u6s[++index],
135 ]);
136 }
137 }
138 return call(getViewBuffer, dataView, []);
139 };
140
141 const encodeBase64 = (buffer, safe = false) => {
142 const dataView = new View(buffer);
143 const byteLength = call(getBufferByteLength, buffer, []);
144 const minimumLengthOfResults = ceil(byteLength * 4 / 3);
145 const resultingCodeUnits = fill(
146 objectCreate(
147 binaryCodeUnitIterablePrototype,
148 {
149 length: {
150 value: minimumLengthOfResults +
151 (4 - (minimumLengthOfResults % 4)) % 4,
152 },
153 },
154 ),
155 0x3D,
156 );
157 for (let index = 0; index < byteLength;) {
158 const codeUnitIndex = ceil(index * 4 / 3);
159 const currentIndex = codeUnitIndex + +(
160 index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
161 );
162 const remainder = currentIndex % 4;
163 const u6 = remainder == 0
164 ? call(viewGetUint8, dataView, [index]) >> 2
165 : remainder == 1
166 ? ((call(viewGetUint8, dataView, [index++]) & 0x3) << 4) +
167 (index < byteLength
168 ? call(viewGetUint8, dataView, [index]) >> 4
169 : 0)
170 : remainder == 2
171 ? ((call(viewGetUint8, dataView, [index++]) & 0xF) << 2) +
172 (index < byteLength
173 ? call(viewGetUint8, dataView, [index]) >> 6
174 : 0)
175 : call(viewGetUint8, dataView, [index++]) & 0x3F;
176 const result = u6 < 26
177 ? u6 + 65
178 : u6 < 52
179 ? u6 + 71
180 : u6 < 62
181 ? u6 - 4
182 : u6 < 63
183 ? (safe ? 0x2D : 0x2B)
184 : u6 < 64
185 ? (safe ? 0x5F : 0x2F)
186 : -1;
187 if (result < 0) {
188 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
189 } else {
190 resultingCodeUnits[currentIndex] = result;
191 }
192 }
193 return stringFromCodeUnits(...resultingCodeUnits);
194 };
195
196 const sourceFromArgs = ($, $s) =>
197 stringReplace(
198 typeof $ == "string"
199 ? $
200 : hasOwnProperty($, "raw")
201 ? rawString($, ...$s)
202 : `${$}`,
203 /[\t\n\f\r ]+/gu,
204 "",
205 );
206
207 /**
208 * Returns an ArrayBuffer generated from the provided Base64.
209 *
210 * This function can also be used as a tag for a template literal. The
211 * literal will be interpreted akin to `String.raw`.
212 */
213 export const base64Binary = ($, ...$s) =>
214 decodeBase64(sourceFromArgs($, $s));
215
216 /**
217 * Returns a (big‐endian) base64 string created from a typed array,
218 * buffer, or (16‐bit) string.
219 *
220 * This function can also be used as a tag for a template literal. The
221 * literal will be interpreted akin to `String.raw`.
222 */
223 export const base64String = ($, ...$s) =>
224 encodeBase64(bufferFromArgs($, $s));
225
226 /**
227 * Returns an ArrayBuffer generated from the provided filename‐safe
228 * Base64.
229 *
230 * This function can also be used as a tag for a template literal. The
231 * literal will be interpreted akin to `String.raw`.
232 */
233 export const filenameSafeBase64Binary = ($, ...$s) =>
234 decodeBase64(sourceFromArgs($, $s), true);
235
236 /**
237 * Returns a (big‐endian) filename‐safe base64 string created from a
238 * typed array, buffer, or (16‐bit) string.
239 *
240 * This function can also be used as a tag for a template literal. The
241 * literal will be interpreted akin to `String.raw`.
242 */
243 export const filenameSafeBase64String = ($, ...$s) =>
244 encodeBase64(bufferFromArgs($, $s), true);
245
246 /**
247 * Returns whether the provided value is a Base64 string.
248 *
249 * Returns false if the provided value is not a string primitive.
250 */
251 export const isBase64 = ($) => {
252 if (typeof $ !== "string") {
253 return false;
254 } else {
255 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
256 const trimmed = source.length % 4 == 0
257 ? stringReplace(source, /={1,2}$/u, "")
258 : source;
259 return trimmed.length % 4 != 1 &&
260 call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) == null;
261 }
262 };
263
264 /**
265 * Returns whether the provided value is a filename‐safe base64 string.
266 *
267 * Returns false if the provided value is not a string primitive.
268 */
269 export const isFilenameSafeBase64 = ($) => {
270 if (typeof $ !== "string") {
271 return false;
272 } else {
273 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
274 const trimmed = source.length % 4 == 0
275 ? stringReplace(source, /={1,2}$/u, "")
276 : source;
277 return trimmed.length % 4 != 1 &&
278 call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) == null;
279 }
280 };
This page took 0.121823 seconds and 3 git commands to generate.