]> Lady’s Gitweb - Pisces/blob - base64.js
Treat object function args more consistently
[Pisces] / base64.js
1 // ♓🌟 Piscēs ∷ base64.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 base64CodeUnitIterablePrototype = {
49 [iteratorSymbol]() {
50 return {
51 next: bind(
52 arrayIteratorNext,
53 call(arrayIterator, this, []),
54 [],
55 ),
56 };
57 },
58 };
59
60 /**
61 * Returns an ArrayBuffer generated from the provided Base64.
62 *
63 * This function can also be used as a tag for a template literal. The
64 * literal will be interpreted akin to `String.raw`.
65 */
66 export const a2b = ($, ...$s) => {
67 const source = stringReplace(
68 typeof $ == "string"
69 ? $
70 : hasOwnProperty($, "raw")
71 ? rawString($, ...$s)
72 : `${$}`,
73 /[\t\n\f\r ]+/gu,
74 "",
75 );
76 const u6s = map(
77 source.length % 4 == 0
78 ? stringReplace(source, /={1,2}$/u, "")
79 : source,
80 (ucsCharacter) => {
81 const code = getCodeUnit(ucsCharacter, 0);
82 const result = code >= 0x41 && code <= 0x5A
83 ? code - 65
84 : code >= 0x61 && code <= 0x7A
85 ? code - 71
86 : code >= 0x30 && code <= 0x39
87 ? code + 4
88 : code == 0x2B
89 ? 62
90 : code == 0x2F
91 ? 63
92 : -1;
93 if (result < 0) {
94 throw new RangeError(
95 `Piscēs: Invalid character in Base64: ${character}.`,
96 );
97 } else {
98 return result;
99 }
100 },
101 );
102 const { length } = u6s;
103 const dataView = new View(new Buffer(floor(length * 3 / 4)));
104 for (let index = 0; index < length - 1;) {
105 const dataIndex = ceil(index * 3 / 4);
106 const remainder = index % 3;
107 if (remainder == 0) {
108 call(viewSetUint8, dataView, [
109 dataIndex,
110 (u6s[index] << 2) + (u6s[++index] >> 4),
111 ]);
112 } else if (remainder == 1) {
113 call(viewSetUint8, dataView, [
114 dataIndex,
115 ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
116 ]);
117 } else {
118 call(viewSetUint8, dataView, [
119 dataIndex,
120 ((u6s[index] & 0x3) << 6) + u6s[++index],
121 ]);
122 }
123 }
124 return call(getViewBuffer, dataView, []);
125 };
126
127 /**
128 * Returns a (big‐endian) base64 string created from a typed array,
129 * buffer, or string.
130 *
131 * This function can also be used as a tag for a template literal. The
132 * literal will be interpreted akin to `String.raw`.
133 */
134 export const b2a = ($, ...$s) => {
135 const buffer = $ instanceof Buffer
136 ? $
137 : $ instanceof View
138 ? call(getViewBuffer, $, [])
139 : $ instanceof TypedArray
140 ? call(getTypedArrayBuffer, $, [])
141 : ((string) =>
142 call(
143 getViewBuffer,
144 reduce(
145 string,
146 (result, ucsCharacter, index) => (
147 call(viewSetUint16, result, [
148 index * 2,
149 getCodeUnit(ucsCharacter, 0),
150 ]), result
151 ),
152 new View(new Buffer(string.length * 2)),
153 ),
154 [],
155 ))(
156 typeof $ == "string"
157 ? $
158 : hasOwnProperty($, "raw")
159 ? rawString($, ...$s)
160 : `${$}`,
161 );
162 const dataView = new View(buffer);
163 const byteLength = call(getBufferByteLength, buffer, []);
164 const minimumLengthOfResults = ceil(byteLength * 4 / 3);
165 const resultingCodeUnits = fill(
166 objectCreate(
167 base64CodeUnitIterablePrototype,
168 {
169 length: {
170 value: minimumLengthOfResults +
171 (4 - (minimumLengthOfResults % 4)) % 4,
172 },
173 },
174 ),
175 0x3D,
176 );
177 for (let index = 0; index < byteLength;) {
178 const codeUnitIndex = ceil(index * 4 / 3);
179 const currentIndex = codeUnitIndex + +(
180 index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
181 );
182 const remainder = currentIndex % 4;
183 const u6 = remainder == 0
184 ? call(viewGetUint8, dataView, [index]) >> 2
185 : remainder == 1
186 ? ((call(viewGetUint8, dataView, [index++]) & 0x3) << 4) +
187 (index < byteLength
188 ? call(viewGetUint8, dataView, [index]) >> 4
189 : 0)
190 : remainder == 2
191 ? ((call(viewGetUint8, dataView, [index++]) & 0xF) << 2) +
192 (index < byteLength
193 ? call(viewGetUint8, dataView, [index]) >> 6
194 : 0)
195 : call(viewGetUint8, dataView, [index++]) & 0x3F;
196 const result = u6 < 26
197 ? u6 + 65
198 : u6 < 52
199 ? u6 + 71
200 : u6 < 62
201 ? u6 - 4
202 : u6 < 63
203 ? 43
204 : u6 < 64
205 ? 47
206 : -1;
207 if (result < 0) {
208 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
209 } else {
210 resultingCodeUnits[currentIndex] = result;
211 }
212 }
213 return stringFromCodeUnits(...resultingCodeUnits);
214 };
215
216 /**
217 * Returns whether the provided value is a Base64 string.
218 *
219 * Returns false if the provided value is not a string primitive.
220 */
221 export const isBase64 = ($) => {
222 if (typeof $ !== "string") {
223 return false;
224 } else {
225 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
226 const trimmed = source.length % 4 == 0
227 ? stringReplace(source, /={1,2}$/u, "")
228 : source;
229 return trimmed.length % 4 != 1 &&
230 call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) == null;
231 }
232 };
This page took 0.194327 seconds and 5 git commands to generate.