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