]> Lady’s Gitweb - Pisces/blob - base64.js
More comprehensive support for RFC 3986 & RFC 3987
[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 /**
11 * Returns an ArrayBuffer generated from the provided Base64.
12 *
13 * This function can also be used as a tag for a template literal. The
14 * literal will be interpreted akin to `String.raw`.
15 */
16 export const a2b = ($, ...$s) => {
17 const source = (
18 typeof $ == "string"
19 ? $
20 : Object.hasOwn($, "raw")
21 ? String.raw($, ...$s)
22 : `${$}`
23 ).replace(/[\t\n\f\r ]+/gu, "");
24 const u6s = Array.prototype.map.call(
25 source.length % 4 == 0 ? source.replace(/={1,2}$/u, "") : source,
26 (character) => {
27 const code = character.charCodeAt(0);
28 const result = code >= 0x41 && code <= 0x5A
29 ? code - 65
30 : code >= 0x61 && code <= 0x7A
31 ? code - 71
32 : code >= 0x30 && code <= 0x39
33 ? code + 4
34 : code == 0x2B
35 ? 62
36 : code == 0x2F
37 ? 63
38 : -1;
39 if (result < 0) {
40 throw new RangeError(
41 `Piscēs: Invalid character in Base64: ${character}.`,
42 );
43 } else {
44 return result;
45 }
46 },
47 );
48 const { length } = u6s;
49 const dataView = new DataView(
50 new ArrayBuffer(Math.floor(length * 3 / 4)),
51 );
52 for (let index = 0; index < length - 1;) {
53 const dataIndex = Math.ceil(index * 3 / 4);
54 const remainder = index % 3;
55 if (remainder == 0) {
56 dataView.setUint8(
57 dataIndex,
58 (u6s[index] << 2) + (u6s[++index] >> 4),
59 );
60 } else if (remainder == 1) {
61 dataView.setUint8(
62 dataIndex,
63 ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
64 );
65 } else {
66 dataView.setUint8(
67 dataIndex,
68 ((u6s[index] & 0x3) << 6) + u6s[++index],
69 );
70 }
71 }
72 return dataView.buffer;
73 };
74
75 /**
76 * Returns a (big‐endian) base64 string created from a typed array,
77 * buffer, or string.
78 *
79 * This function can also be used as a tag for a template literal. The
80 * literal will be interpreted akin to `String.raw`.
81 */
82 export const b2a = ($, ...$s) => {
83 const buffer = $ instanceof ArrayBuffer
84 ? $
85 : $ instanceof DataView ||
86 $ instanceof Object.getPrototypeOf(Uint8Array)
87 ? $.buffer
88 : ((string) =>
89 Array.prototype.reduce.call(
90 string,
91 (result, code·point, index) => (
92 result.setUint16(index * 2, code·point.charCodeAt(0)), result
93 ),
94 new DataView(new ArrayBuffer(string.length * 2)),
95 ).buffer)(
96 typeof $ == "string"
97 ? $
98 : Object.hasOwn($, "raw")
99 ? String.raw($, ...$s)
100 : `${$}`,
101 );
102 const dataView = new DataView(buffer);
103 const { byteLength } = buffer;
104 const minimumLengthOfResults = Math.ceil(byteLength * 4 / 3);
105 const resultingCode·points = new Array(
106 minimumLengthOfResults + (4 - (minimumLengthOfResults % 4)) % 4,
107 ).fill(0x3D);
108 for (let index = 0; index < byteLength;) {
109 const code·pointIndex = Math.ceil(index * 4 / 3);
110 const currentIndex = code·pointIndex + +(
111 index % 3 == 0 && resultingCode·points[code·pointIndex] != 0x3D
112 );
113 const remainder = currentIndex % 4;
114 const u6 = remainder == 0
115 ? dataView.getUint8(index) >> 2
116 : remainder == 1
117 ? ((dataView.getUint8(index++) & 0x3) << 4) +
118 (index < byteLength ? dataView.getUint8(index) >> 4 : 0)
119 : remainder == 2
120 ? ((dataView.getUint8(index++) & 0xF) << 2) +
121 (index < byteLength ? dataView.getUint8(index) >> 6 : 0)
122 : dataView.getUint8(index++) & 0x3F;
123 const result = u6 < 26
124 ? u6 + 65
125 : u6 < 52
126 ? u6 + 71
127 : u6 < 62
128 ? u6 - 4
129 : u6 < 63
130 ? 43
131 : u6 < 64
132 ? 47
133 : -1;
134 if (result < 0) {
135 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
136 } else {
137 resultingCode·points[currentIndex] = result;
138 }
139 }
140 return String.fromCodePoint(...resultingCode·points);
141 };
142
143 /**
144 * Returns whether the provided value is a Base64 string.
145 *
146 * Returns false if the provided value is not a string primitive.
147 */
148 export const isBase64 = ($) => {
149 if (typeof $ != "string") return false;
150 const source = $.replace(/[\t\n\f\r ]+/gu, "");
151 const trimmed = source.length % 4 == 0
152 ? source.replace(/={1,2}$/u, "")
153 : source;
154 return trimmed.length % 4 != 1 &&
155 !/[^0-9A-Za-z+\/]/u.test(trimmed);
156 };
This page took 0.06107 seconds and 5 git commands to generate.