├── .vscode └── settings.json ├── LICENSE ├── README.md ├── makefile └── polyfill ├── README.md ├── mod.mjs ├── mod_test.js ├── polyfill.min.js └── polyfill.mjs /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": false 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © Luca Casonato 2 | 3 | This work is licensed under a Creative Commons Attribution 4.0 International 4 | License. To the extent portions of it are incorporated into source code, 5 | such portions in the source code are licensed under the BSD 3-Clause License instead. 6 | 7 | - - - - 8 | 9 | Creative Commons Attribution 4.0 International Public License 10 | 11 | By exercising the Licensed Rights (defined below), You accept and agree 12 | to be bound by the terms and conditions of this Creative Commons 13 | Attribution 4.0 International Public License ("Public License"). To the 14 | extent this Public License may be interpreted as a contract, You are 15 | granted the Licensed Rights in consideration of Your acceptance of 16 | these terms and conditions, and the Licensor grants You such rights in 17 | consideration of benefits the Licensor receives from making the 18 | Licensed Material available under these terms and conditions. 19 | 20 | 21 | Section 1 -- Definitions. 22 | 23 | a. Adapted Material means material subject to Copyright and Similar 24 | Rights that is derived from or based upon the Licensed Material 25 | and in which the Licensed Material is translated, altered, 26 | arranged, transformed, or otherwise modified in a manner requiring 27 | permission under the Copyright and Similar Rights held by the 28 | Licensor. For purposes of this Public License, where the Licensed 29 | Material is a musical work, performance, or sound recording, 30 | Adapted Material is always produced where the Licensed Material is 31 | synched in timed relation with a moving image. 32 | 33 | b. Adapter's License means the license You apply to Your Copyright 34 | and Similar Rights in Your contributions to Adapted Material in 35 | accordance with the terms and conditions of this Public License. 36 | 37 | c. Copyright and Similar Rights means copyright and/or similar rights 38 | closely related to copyright including, without limitation, 39 | performance, broadcast, sound recording, and Sui Generis Database 40 | Rights, without regard to how the rights are labeled or 41 | categorized. For purposes of this Public License, the rights 42 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 43 | Rights. 44 | 45 | d. Effective Technological Measures means those measures that, in the 46 | absence of proper authority, may not be circumvented under laws 47 | fulfilling obligations under Article 11 of the WIPO Copyright 48 | Treaty adopted on December 20, 1996, and/or similar international 49 | agreements. 50 | 51 | e. Exceptions and Limitations means fair use, fair dealing, and/or 52 | any other exception or limitation to Copyright and Similar Rights 53 | that applies to Your use of the Licensed Material. 54 | 55 | f. Licensed Material means the artistic or literary work, database, 56 | or other material to which the Licensor applied this Public 57 | License. 58 | 59 | g. Licensed Rights means the rights granted to You subject to the 60 | terms and conditions of this Public License, which are limited to 61 | all Copyright and Similar Rights that apply to Your use of the 62 | Licensed Material and that the Licensor has authority to license. 63 | 64 | h. Licensor means the individual(s) or entity(ies) granting rights 65 | under this Public License. 66 | 67 | i. Share means to provide material to the public by any means or 68 | process that requires permission under the Licensed Rights, such 69 | as reproduction, public display, public performance, distribution, 70 | dissemination, communication, or importation, and to make material 71 | available to the public including in ways that members of the 72 | public may access the material from a place and at a time 73 | individually chosen by them. 74 | 75 | j. Sui Generis Database Rights means rights other than copyright 76 | resulting from Directive 96/9/EC of the European Parliament and of 77 | the Council of 11 March 1996 on the legal protection of databases, 78 | as amended and/or succeeded, as well as other essentially 79 | equivalent rights anywhere in the world. 80 | 81 | k. You means the individual or entity exercising the Licensed Rights 82 | under this Public License. Your has a corresponding meaning. 83 | 84 | 85 | Section 2 -- Scope. 86 | 87 | a. License grant. 88 | 89 | 1. Subject to the terms and conditions of this Public License, 90 | the Licensor hereby grants You a worldwide, royalty-free, 91 | non-sublicensable, non-exclusive, irrevocable license to 92 | exercise the Licensed Rights in the Licensed Material to: 93 | 94 | a. reproduce and Share the Licensed Material, in whole or 95 | in part; and 96 | 97 | b. produce, reproduce, and Share Adapted Material. 98 | 99 | 2. Exceptions and Limitations. For the avoidance of doubt, where 100 | Exceptions and Limitations apply to Your use, this Public 101 | License does not apply, and You do not need to comply with 102 | its terms and conditions. 103 | 104 | 3. Term. The term of this Public License is specified in Section 105 | 6(a). 106 | 107 | 4. Media and formats; technical modifications allowed. The 108 | Licensor authorizes You to exercise the Licensed Rights in 109 | all media and formats whether now known or hereafter created, 110 | and to make technical modifications necessary to do so. The 111 | Licensor waives and/or agrees not to assert any right or 112 | authority to forbid You from making technical modifications 113 | necessary to exercise the Licensed Rights, including 114 | technical modifications necessary to circumvent Effective 115 | Technological Measures. For purposes of this Public License, 116 | simply making modifications authorized by this Section 2(a) 117 | (4) never produces Adapted Material. 118 | 119 | 5. Downstream recipients. 120 | 121 | a. Offer from the Licensor -- Licensed Material. Every 122 | recipient of the Licensed Material automatically 123 | receives an offer from the Licensor to exercise the 124 | Licensed Rights under the terms and conditions of this 125 | Public License. 126 | 127 | b. No downstream restrictions. You may not offer or impose 128 | any additional or different terms or conditions on, or 129 | apply any Effective Technological Measures to, the 130 | Licensed Material if doing so restricts exercise of the 131 | Licensed Rights by any recipient of the Licensed 132 | Material. 133 | 134 | 6. No endorsement. Nothing in this Public License constitutes or 135 | may be construed as permission to assert or imply that You 136 | are, or that Your use of the Licensed Material is, connected 137 | with, or sponsored, endorsed, or granted official status by, 138 | the Licensor or others designated to receive attribution as 139 | provided in Section 3(a)(1)(A)(i). 140 | 141 | b. Other rights. 142 | 143 | 1. Moral rights, such as the right of integrity, are not 144 | licensed under this Public License, nor are publicity, 145 | privacy, and/or other similar personality rights; however, to 146 | the extent possible, the Licensor waives and/or agrees not to 147 | assert any such rights held by the Licensor to the limited 148 | extent necessary to allow You to exercise the Licensed 149 | Rights, but not otherwise. 150 | 151 | 2. Patent and trademark rights are not licensed under this 152 | Public License. 153 | 154 | 3. To the extent possible, the Licensor waives any right to 155 | collect royalties from You for the exercise of the Licensed 156 | Rights, whether directly or through a collecting society 157 | under any voluntary or waivable statutory or compulsory 158 | licensing scheme. In all other cases the Licensor expressly 159 | reserves any right to collect such royalties. 160 | 161 | 162 | Section 3 -- License Conditions. 163 | 164 | Your exercise of the Licensed Rights is expressly made subject to the 165 | following conditions. 166 | 167 | a. Attribution. 168 | 169 | 1. If You Share the Licensed Material (including in modified 170 | form), You must: 171 | 172 | a. retain the following if it is supplied by the Licensor 173 | with the Licensed Material: 174 | 175 | i. identification of the creator(s) of the Licensed 176 | Material and any others designated to receive 177 | attribution, in any reasonable manner requested by 178 | the Licensor (including by pseudonym if 179 | designated); 180 | 181 | ii. a copyright notice; 182 | 183 | iii. a notice that refers to this Public License; 184 | 185 | iv. a notice that refers to the disclaimer of 186 | warranties; 187 | 188 | v. a URI or hyperlink to the Licensed Material to the 189 | extent reasonably practicable; 190 | 191 | b. indicate if You modified the Licensed Material and 192 | retain an indication of any previous modifications; and 193 | 194 | c. indicate the Licensed Material is licensed under this 195 | Public License, and include the text of, or the URI or 196 | hyperlink to, this Public License. 197 | 198 | 2. You may satisfy the conditions in Section 3(a)(1) in any 199 | reasonable manner based on the medium, means, and context in 200 | which You Share the Licensed Material. For example, it may be 201 | reasonable to satisfy the conditions by providing a URI or 202 | hyperlink to a resource that includes the required 203 | information. 204 | 205 | 3. If requested by the Licensor, You must remove any of the 206 | information required by Section 3(a)(1)(A) to the extent 207 | reasonably practicable. 208 | 209 | 4. If You Share Adapted Material You produce, the Adapter's 210 | License You apply must not prevent recipients of the Adapted 211 | Material from complying with this Public License. 212 | 213 | 214 | Section 4 -- Sui Generis Database Rights. 215 | 216 | Where the Licensed Rights include Sui Generis Database Rights that 217 | apply to Your use of the Licensed Material: 218 | 219 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 220 | to extract, reuse, reproduce, and Share all or a substantial 221 | portion of the contents of the database; 222 | 223 | b. if You include all or a substantial portion of the database 224 | contents in a database in which You have Sui Generis Database 225 | Rights, then the database in which You have Sui Generis Database 226 | Rights (but not its individual contents) is Adapted Material; and 227 | 228 | c. You must comply with the conditions in Section 3(a) if You Share 229 | all or a substantial portion of the contents of the database. 230 | 231 | For the avoidance of doubt, this Section 4 supplements and does not 232 | replace Your obligations under this Public License where the Licensed 233 | Rights include other Copyright and Similar Rights. 234 | 235 | 236 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 237 | 238 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 239 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 240 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 241 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 242 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 243 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 244 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 245 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 246 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 247 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 248 | 249 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 250 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 251 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 252 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 253 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 254 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 255 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 256 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 257 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 258 | 259 | c. The disclaimer of warranties and limitation of liability provided 260 | above shall be interpreted in a manner that, to the extent 261 | possible, most closely approximates an absolute disclaimer and 262 | waiver of all liability. 263 | 264 | 265 | Section 6 -- Term and Termination. 266 | 267 | a. This Public License applies for the term of the Copyright and 268 | Similar Rights licensed here. However, if You fail to comply with 269 | this Public License, then Your rights under this Public License 270 | terminate automatically. 271 | 272 | b. Where Your right to use the Licensed Material has terminated under 273 | Section 6(a), it reinstates: 274 | 275 | 1. automatically as of the date the violation is cured, provided 276 | it is cured within 30 days of Your discovery of the 277 | violation; or 278 | 279 | 2. upon express reinstatement by the Licensor. 280 | 281 | For the avoidance of doubt, this Section 6(b) does not affect any 282 | right the Licensor may have to seek remedies for Your violations 283 | of this Public License. 284 | 285 | c. For the avoidance of doubt, the Licensor may also offer the 286 | Licensed Material under separate terms or conditions or stop 287 | distributing the Licensed Material at any time; however, doing so 288 | will not terminate this Public License. 289 | 290 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 291 | License. 292 | 293 | 294 | Section 7 -- Other Terms and Conditions. 295 | 296 | a. The Licensor shall not be bound by any additional or different 297 | terms or conditions communicated by You unless expressly agreed. 298 | 299 | b. Any arrangements, understandings, or agreements regarding the 300 | Licensed Material not stated herein are separate from and 301 | independent of the terms and conditions of this Public License. 302 | 303 | 304 | Section 8 -- Interpretation. 305 | 306 | a. For the avoidance of doubt, this Public License does not, and 307 | shall not be interpreted to, reduce, limit, restrict, or impose 308 | conditions on any use of the Licensed Material that could lawfully 309 | be made without permission under this Public License. 310 | 311 | b. To the extent possible, if any provision of this Public License is 312 | deemed unenforceable, it shall be automatically reformed to the 313 | minimum extent necessary to make it enforceable. If the provision 314 | cannot be reformed, it shall be severed from this Public License 315 | without affecting the enforceability of the remaining terms and 316 | conditions. 317 | 318 | c. No term or condition of this Public License will be waived and no 319 | failure to comply consented to unless expressly agreed to by the 320 | Licensor. 321 | 322 | d. Nothing in this Public License constitutes or may be interpreted 323 | as a limitation upon, or waiver of, any privileges and immunities 324 | that apply to the Licensor or You, including from the legal 325 | processes of any jurisdiction or authority. 326 | 327 | - - - - 328 | 329 | BSD 3-Clause License 330 | 331 | Redistribution and use in source and binary forms, with or without 332 | modification, are permitted provided that the following conditions are met: 333 | 334 | 1. Redistributions of source code must retain the above copyright notice, this 335 | list of conditions and the following disclaimer. 336 | 337 | 2. Redistributions in binary form must reproduce the above copyright notice, 338 | this list of conditions and the following disclaimer in the documentation 339 | and/or other materials provided with the distribution. 340 | 341 | 3. Neither the name of the copyright holder nor the names of its 342 | contributors may be used to endorse or promote products derived from 343 | this software without specific prior written permission. 344 | 345 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 346 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 347 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 348 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 349 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 350 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 351 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 352 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 353 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 354 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 355 | 356 | - - - - -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proposal-binary-encoding 2 | 3 | A proposal to add modern, easy to use binary encoders to the web platform. This 4 | is proposed as an addition to the HTML spec, not the ECMAScript language 5 | specification. 6 | 7 | - [Problem](#problem) 8 | - [Implementations in other environments](#implementations-in-other-environments) 9 | - [Node.js](#nodejs) 10 | - [Deno standard library](#deno-standard-library) 11 | - [Dart](#dart) 12 | - [Go](#go) 13 | - [Proposal](#proposal) 14 | - [Examples](#examples) 15 | - [FAQ](#faq) 16 | 17 | ## Problem 18 | 19 | Many protocols, APIs, and algorithms require that some binary data (byte array) 20 | is serialized into a string that represents that binary data losslessly. Common 21 | formats for this are for example base64 encoding and hex encoding. Often the 22 | reverse - so deserializing the string back into the original data - is required 23 | too. 24 | 25 | Here are some (common) usecases that require base64 or hex encoding / decoding 26 | some binary data: 27 | 28 | - Encoding a png image into a data URL (base64 encoding the png) 29 | - Creating a hex string from a cryptographic digest (hash) 30 | - Generating a random ID from `crypto.getRandomValues` (hex encoding a random 31 | byte array) 32 | - Send binary data over transports that only supports string values (base64 33 | {de/en}coding) 34 | - Parsing PEM files (binary data is stored as base64 encoded strings) 35 | 36 | The web platform does not provide a fast an easy approach to base64 / hex encode 37 | and decode. These are currently the most common ways to do hex encoding and 38 | decoding: 39 | 40 | ```js 41 | /** 42 | * @param {Uint8Array} bytes 43 | * @returns {string} 44 | */ 45 | function base64Encode(bytes) { 46 | var binary = ""; 47 | var bytes = new Uint8Array(buffer); 48 | var len = bytes.byteLength; 49 | for (var i = 0; i < len; i++) { 50 | binary += String.fromCharCode(bytes[i]); 51 | } 52 | return globalThis.btoa(binary); 53 | } 54 | 55 | /** 56 | * @param {string} str 57 | * @returns {Uint8Array} 58 | */ 59 | function base64Decode(str) { 60 | var binaryStr = globalThis.atob(str); 61 | var bytes = new Uint8Array(binaryStr.length); 62 | for (var i = 0; i < binaryStr.length; i++) { 63 | bytes[i] = binaryStr.charCodeAt(i); 64 | } 65 | return bytes; 66 | } 67 | 68 | /** 69 | * @param {Uint8Array} bytes 70 | * @returns {string} 71 | */ 72 | function hexEncode(bytes) { 73 | return [...bytes].map((x) => x.toString(16).padStart(2, "0")).join(""); 74 | } 75 | 76 | /** 77 | * @param {string} str 78 | * @returns {Uint8Array} 79 | */ 80 | function hexDecode(str) { 81 | var bytes = new Uint8Array(str.length); 82 | for (var i = 0; i < str.length; i += 2) { 83 | bytes[i] = parseInt(hex.substr(n, 2), 16); 84 | } 85 | return bytes; 86 | } 87 | ``` 88 | 89 | All of the above decoders don't handle errors correctly, don't validate the 90 | input, and the encoders do a lot of string concatenations (`join("")` is also 91 | concatenation). These are the first implementations that users will see when 92 | looking up "arraybuffer to base64 javascript" or "arraybuffer to hex javascript" 93 | on Google: 94 | 95 | Top search result for "arraybuffer to base64 javascript" on StackOverflow has 96 | 252 upvotes and is very inefficient due to the excessive string concat: 97 | https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string 98 | 99 | Top search result for "arraybuffer to hex javascript" on StackOverflow has 76 100 | upvotes and is pretty inefficient due to an excessive amount of function calls 101 | and string `join` which is incredibly slow in V8: 102 | https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex 103 | 104 | These many suboptimal custom implementations lead to a bunch of extranious code 105 | that is shipped to clients for trivial encodings that are already present in 106 | browser binaries. This is especially bad when users use Node's `Buffer` for the 107 | sole purpose of hex and base64 encoding / decoding and then bundle that for the 108 | browser which pulls in a large browserify polyfill. 109 | 110 | Through some analysis with sourcegraph, it looks that a large number of JS devs 111 | use 112 | [`Buffer.toString("hex")`](https://sourcegraph.com/search?q=context:global+%28lang:javascript+OR+lang:typescript%29+count:all+toString%28%22hex%22%29&patternType=literal) 113 | or 114 | [`Buffer.toString("base64")`](https://sourcegraph.com/search?q=context:global+%28lang:javascript+OR+lang:typescript%29+count:all+toString%28%22hex%22%29&patternType=literal) 115 | for encoding / decoding base64 or hex: combined there are almost 10k uses in 116 | 366k public repos. 117 | 118 | Additionally the NPM [`base-64`](https://www.npmjs.com/package/base-64) packages 119 | which provides byte array -> base64 string encodings, has 600k weekly downloads. 120 | Again wouldn't be needed if the platform shipped this primitive. 121 | 122 | When thinking about implementing native binary encodeers, the question of which 123 | alphabet to use is bound to come up. Whatever the final proposal, the most 124 | common encoding alphabets should be supported. The standard base64 algorithm is 125 | defined by RFC 4648 and is already available in 126 | https://infra.spec.whatwg.org/#forgiving-base64. An alternative url safe base64 127 | encoding is also specified by RFC 4648 and is often used in the context of web 128 | applications. The only variation you get for hex encoding is upper vs lower 129 | case, but this can easially be changed by the user using a `.toUpperCase` or 130 | `.toLowerCase`. The default should be lower case to match existing 131 | implementations in Node, Go, and `Number.toString(16)`. 132 | 133 | ## Implementations in other environments 134 | 135 | ### Node.js 136 | 137 | In Node, base64 and hex encoding of byte slices can be done via the `Buffer` 138 | primitive. Buffers have a `.toString` method that takes an optional argument 139 | defining the type of encoding to use. For this proposal only `"hex"` and 140 | `"base64"`, and `"base64url"` are relevant. Streaming is not supported. 141 | Alternative alphabets are not supported. Disabling of padding is not supported. 142 | Usage example: 143 | 144 | ```js 145 | const buf = Buffer.from("hello world", "utf8"); 146 | buf.toString("hex"); 147 | buf.toString("base64"); 148 | 149 | const buf2 = Buffer.from("68656c6c6f20776f726c64", "hex"); 150 | buf2.toString("utf8"); 151 | ``` 152 | 153 | ### Deno standard library 154 | 155 | Deno does not include a base64 or hex decoder in the runtime natively, but it 156 | does include one in the standard library. It is capable of `base64`, 157 | `base64url`, and `hex`. Streaming is not supported. Alternative alphabets are 158 | not supported. Disabling of padding is not supported. 159 | 160 | Usage example: 161 | 162 | ```js 163 | import * as base64 from "https://deno.land/std@0.98.0/encoding/base64.ts"; 164 | import * as base64url from "https://deno.land/std@0.98.0/encoding/base64url.ts"; 165 | import * as hex from "https://deno.land/std@0.98.0/encoding/hex.ts"; 166 | 167 | const message = new TextEncoder.encode("hello world"); 168 | 169 | base64.encode(message); // takes uint8array 170 | base64.decode("aGVsbG8gd29ybGQ="); // returns uin8array 171 | 172 | base64url.encode(message); // takes uint8array 173 | base64url.decode("aGVsbG8gd29ybGQ="); // returns uin8array 174 | 175 | hex.encode(message); // takes uint8array 176 | hex.decode("68656c6c6f20776f726c64"); // returns uin8array 177 | ``` 178 | 179 | ### Dart 180 | 181 | Dart supports base64 and base64url encoding via the standard library. Hex 182 | encoding is not supported natively, instead the `hex` package on pub.dev is 183 | recommended. Streaming is supported for base64. Alternative alphabets are not 184 | supported. Disabling of padding is not supported. 185 | 186 | Usage example; 187 | 188 | ```dart 189 | import "dart:convert"; 190 | import "package:hex/hex.dart"; 191 | 192 | base64.encode([0x62, 0x6c, 0xc3, 0xa5, 0x62, 0xc3, 0xa6, 193 | 0x72, 0x67, 0x72, 0xc3, 0xb8, 0x64]); 194 | base64.decode("YmzDpWLDpnJncsO4ZAo="); 195 | 196 | base64Url.encode([0x62, 0x6c, 0xc3, 0xa5, 0x62, 0xc3, 0xa6, 197 | 0x72, 0x67, 0x72, 0xc3, 0xb8, 0x64]); 198 | base64Url.decode("YmzDpWLDpnJncsO4ZAo="); 199 | 200 | HEX.encode([1, 2, 3]); // "010203" 201 | HEX.decode("010203"); // [1, 2, 3] 202 | 203 | // Streaming for base64 is supported via the Base64Encoder and Base64Decoder 204 | // classes. These are stream combinators for the Dart native streams (we would 205 | // call them transform streams). 206 | ``` 207 | 208 | ### Go 209 | 210 | In Go base64 is implemented with the Go native streaming API (`io.Reader` / 211 | `io.Writer`). There are two functions, `base64.NewEncoder` and 212 | `base64.NewDecoder` which can be used to create what we would call transform 213 | streams. In Go all code is concurrent code (what we would call async), so this 214 | API can be used with the same versatility as a synchronous encoder / decoder in 215 | JS. The encoders and decoders take a `Encoding` parameter which specifies the 216 | alphabet to use. Padding can be enabled and disabled for each encoding. 217 | 218 | Usage example: 219 | 220 | ```go 221 | // Open the input and output files (these are io.Reader and io.Writer streams) 222 | in, _ := os.Open("in.txt") 223 | out, _ := os.Open("out.txt") 224 | 225 | // Create a new encoder that outputs to out with the standard base64 encoding 226 | encoder := base64.NewEncoder(base64.StdEncoding, out) 227 | 228 | // Copy the input data into the encoder 229 | io.Copy(encoder, in) 230 | 231 | // The decoder works the same, just with input and output reversed and 232 | // `base64.NewDecoder` used instead. 233 | ``` 234 | 235 | ## Proposal 236 | 237 | This proposal introduces a new `BinaryEncoder` and `BinaryDecoder` API that can 238 | be used to serialize byte arrays into base64 or hex strings, and deserialize 239 | these strings back into byte arrays. 240 | 241 | ### Binary encodings 242 | 243 | This proposal allows for encoding and decoding `base64`, `base64url`, and `hex` 244 | data. It does not implement streaming support, as this could be later 245 | implemented in a `BinaryEncoderStream` / `BinaryDecoderStream`, or using the 246 | same synchronous API as text encoding, using a `stream: true` option on the 247 | `encode` / `decode` methods. This proposal also does not allow disabling of 248 | padding for base64, or alternative alphabets. 249 | 250 | ```webidl 251 | enum BinaryEncoding { 252 | "base64", 253 | "base64url", 254 | "hex" 255 | } 256 | ``` 257 | 258 | ### `BinaryEncoder` 259 | 260 | A second argument in the constructor can be used for an option bag if additional 261 | fields for BinaryEncoder are required in the future. 262 | 263 | ```webidl 264 | [Exposed=(Window,Worker)] 265 | interface BinaryEncoder { 266 | constructor(BinaryEncoding encoding); 267 | 268 | readonly attribute BinaryEncoding encoding; 269 | readonly attribute boolean padding; 270 | 271 | USVString encode([AllowShared] BufferSource input); 272 | }; 273 | ``` 274 | 275 | A `BinaryEncoder` object has an associated **encoding**, which is a 276 | `BinaryEncoding`. 277 | 278 | The `new BinaryEncoder(encoding)` constructor steps are: 279 | 280 | 1. Set this's encoding to _encoding_. 281 | 282 | The `encode(input)` method steps are: 283 | 284 | 1. Switch on this's encoding and run associated steps: 285 | - "base64": return the output of running **forgiving-base64 encode** on 286 | input. TODO: handle failure case (throw TypeError or DOMException?) 287 | - "base64url": return the output of running **forgiving-base64 encode** on 288 | input, with alternative base64 table from RFC 4648. NOTE: forgiving-base64 289 | encode does not have an argument for base64 table. TODO: handle failure 290 | case (throw TypeError or DOMException?) 291 | - "hex": return the output of running **hex encode** on input. TODO: handle 292 | failure case (throw TypeError or DOMException?) 293 | 294 | To **hex encode** given a byte sequence data, run these steps: 295 | 296 | 1. TODO 297 | 298 | ### `BinaryDecoder` 299 | 300 | A second argument in the constructor can be used for an option bag if additional 301 | fields for BinaryDecoder are required in the future. 302 | 303 | ```webidl 304 | [Exposed=(Window,Worker)] 305 | interface BinaryDecoder { 306 | constructor(BinaryEncoding encoding); 307 | 308 | readonly attribute BinaryEncoding encoding; 309 | 310 | [NewObject] Uint8Array decode(DOMString input); 311 | }; 312 | ``` 313 | 314 | A `BinaryDecoder` object has an associated **encoding**, which is a 315 | `BinaryEncoding`. 316 | 317 | The `new BinaryDecoder(encoding)` constructor steps are: 318 | 319 | 1. Set this's encoding to _encoding_. 320 | 321 | The `decode(input)` method steps are: 322 | 323 | 1. Switch on this's encoding and run associated steps: 324 | - "base64": return the output of running **forgiving-base64 decode** on 325 | input. TODO: handle failure case (throw TypeError or DOMException?) 326 | - "base64url": return the output of running **forgiving-base64 decode** on 327 | input, with alternative base64 table from RFC 4648. NOTE: forgiving-base64 328 | encode does not have an argument for base64 table. TODO: handle failure 329 | case (throw TypeError or DOMException?) 330 | - "hex": return the output of running **hex decode** on input. TODO: handle 331 | failure case (throw TypeError or DOMException?) 332 | 333 | To **hex decode** given a string data, run these steps: 334 | 335 | 1. TODO 336 | 337 | ### Future extensions 338 | 339 | This proposal leaves many extension points for future API additions. For example 340 | the addition of a way to disable padding for the encoder, and support for base32 341 | and base62 encoding. 342 | 343 | ### Examples 344 | 345 | Some examples demonstrating how this API can be used. 346 | 347 | #### Calculate a hex sha256 digest of a file 348 | 349 | ```js 350 | const file = new Uint8Array([/** populated with some data */]); 351 | const digestBytes = await crypto.subtle.digest("sha-256", file); 352 | const digest = new BinaryEncoder("hex").encode(digestBytes); 353 | console.log(digest); 354 | ``` 355 | 356 | #### Parse a PEM file 357 | 358 | ```js 359 | const BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; 360 | const END_CERT = "-----END CERTIFICATE-----"; 361 | 362 | const certificate = ` 363 | -----BEGIN CERTIFICATE----- 364 | MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw 365 | CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg 366 | R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 367 | MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT 368 | ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw 369 | EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW 370 | +1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 371 | ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T 372 | AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI 373 | zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW 374 | tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 375 | /q4AaOeMSQ+2b1tbFfLn 376 | -----END CERTIFICATE----- 377 | `.trim(); 378 | 379 | if (!certificate.startsWith(BEGIN_CERT)) { 380 | throw new Error("certificate doesn't start with BEGIN CERTIFICATE"); 381 | } 382 | if (!certificate.endsWith(END_CERT)) { 383 | throw new Error("certificate doesn't end with END CERTIFICATE"); 384 | } 385 | 386 | const inner = certificate.substring( 387 | BEGIN_CERT.length, 388 | certificate.length - END_CERT.length, 389 | ).trim(); 390 | 391 | const der = new BinaryDecoder("base64").decode(inner); 392 | ``` 393 | 394 | ## FAQ 395 | 396 | ### I want streams! 397 | 398 | Streams are interesting, but the most common usecase is not streaming. This 399 | proposal tries to get consensus for the least controversial and most common 400 | usecase first, and can then be expanded to streaming later. This could be done 401 | in a non breaking way in the same way as streaming support for text encoder: 402 | `BinaryEncoderStream` / `BinaryDecoderStream`, or using the same synchronous API 403 | as text encoding, using a `stream: true` option on the `encode` / `decode` 404 | methods. 405 | 406 | ### Can this be combined into the TextEncoding / TextDecoding interfaces? 407 | 408 | In theory yes, but in practice it doesn't make much sense. In text encoding the 409 | binary representation is the "encoded" form, while in binary encoding the text 410 | form is the "encoded" form. Because of this, encoding some binary data to a 411 | base64 string would actually use the text decoder interface as it is the one 412 | that translates **from** byte array **to** string. This is not intuitive. 413 | 414 | ### Why is X encoding not supported? 415 | 416 | This is a first pass with just the 3 most common encodings. Support for 417 | "base62", "base32", and various other encodings can be added after initial 418 | consensus and implementation. 419 | 420 | ### Is this feature poly-fillable? 421 | 422 | Yes! In fact there is a polyfill in this repo in the polyfill/ folder. The 423 | polyfill is 1.3 kb gzipped and could likely be made a lot smaller. 424 | 425 | ### `hex` or `base16`? 426 | 427 | `hex` is definitely the common name. See 428 | [Java](https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html#toHexString-long-), 429 | [Go](https://golang.org/pkg/encoding/hex/), 430 | [Python](https://docs.python.org/3/library/functions.html#hex). Outside of 431 | [RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-8) it is not 432 | commonly called "base 16". 433 | 434 | Some real world data from Sourcegraph to back this up: 435 | 436 | - [123.4k results](https://sourcegraph.com/search?q=context:global+lang:javascript+%22hex%22+count:all&patternType=literal) 437 | for "hex". 438 | - [73 results](https://sourcegraph.com/search?q=context:global+lang:javascript+%22base16%22+count:all&patternType=literal) 439 | for "base16". 440 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | bundle: 2 | esbuild --target=es2020,chrome89,firefox88,safari13 --minify --bundle --legal-comments=eof polyfill/polyfill.mjs > polyfill/polyfill.min.js -------------------------------------------------------------------------------- /polyfill/README.md: -------------------------------------------------------------------------------- 1 | This is a polyfill for the API outlined in /README.md 2 | 3 | To use it in the browser, import "polyfill.min.js"; NOTE: API may change at any 4 | time. Do not use in production. 5 | 6 | The polyfill is 1.3 kb gzipped. 7 | -------------------------------------------------------------------------------- /polyfill/mod.mjs: -------------------------------------------------------------------------------- 1 | //! Copyright 2021 Luca Casonato. All rights reserved. BSD 3-Clause License. 2 | //! Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. 3 | /*! Portions of the below code are ported from Go 4 | https://github.com/golang/go/blob/go1.12.5/src/encoding/hex/hex.go 5 | Copyright 2009 The Go Authors. All rights reserved. 6 | Use of this source code is governed by a BSD-style 7 | license that can be found in the LICENSE file. */ 8 | 9 | const encodings = ["base64", "base64url", "hex"]; 10 | 11 | const hexTable = new TextEncoder().encode("0123456789abcdef"); 12 | 13 | // deno-fmt-ignore 14 | const base64abc = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", 15 | "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", 16 | "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", 17 | "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", 18 | "5", "6", "7", "8", "9", "+", "/"]; 19 | 20 | function base64encode(uint8) { 21 | const l = uint8.length; 22 | let result = ""; 23 | let i; 24 | for (i = 2; i < l; i += 3) { 25 | result += base64abc[uint8[i - 2] >> 2] + 26 | base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)] + 27 | base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)] + 28 | base64abc[uint8[i] & 0x3f]; 29 | } 30 | if (i === l + 1) { 31 | // 1 octet yet to write 32 | result += base64abc[uint8[i - 2] >> 2] + 33 | base64abc[(uint8[i - 2] & 0x03) << 4] + "=="; 34 | } 35 | if (i === l) { 36 | // 2 octets yet to write 37 | result += base64abc[uint8[i - 2] >> 2] + 38 | base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)] + 39 | base64abc[(uint8[i - 1] & 0x0f) << 2] + 40 | "="; 41 | } 42 | return result; 43 | } 44 | 45 | class BinaryEncoder { 46 | _encoding; 47 | constructor(encoding) { 48 | if (arguments.length < 1) { 49 | throw TypeError("1 argument required, but only 0 present."); 50 | } 51 | this._encoding = String(encoding).trim(); 52 | if (!encodings.includes(this._encoding)) { 53 | throw new RangeError("Invalid encoding " + this._encoding); 54 | } 55 | } 56 | get encoding() { 57 | return this._encoding; 58 | } 59 | encode(input) { 60 | if (arguments.length < 1) { 61 | throw TypeError("1 argument required, but only 0 present."); 62 | } 63 | if (input instanceof ArrayBuffer) { 64 | input = new Uint8Array(input); 65 | } else if (ArrayBuffer.isView(input)) { 66 | input = new Uint8Array(input.buffer, input.byteOffset, input.byteLength); 67 | } else { 68 | throw TypeError("Argument 1 must be an array buffer, or a view on one."); 69 | } 70 | switch (this._encoding) { 71 | case "base64": 72 | return base64encode(input); 73 | case "base64url": 74 | return base64encode(input).replace(/=/g, "").replace(/\+/g, "-") 75 | .replace(/\//g, "_"); 76 | case "hex": 77 | const dst = new Uint8Array(input.length * 2); 78 | for (let i = 0; i < dst.length; i++) { 79 | const v = input[i]; 80 | dst[i * 2] = hexTable[v >> 4]; 81 | dst[i * 2 + 1] = hexTable[v & 0x0f]; 82 | } 83 | return new TextDecoder().decode(dst); 84 | } 85 | } 86 | } 87 | 88 | export function addPaddingToBase64url(base64url) { 89 | if (base64url.length % 4 === 2) return base64url + "=="; 90 | if (base64url.length % 4 === 3) return base64url + "="; 91 | if (base64url.length % 4 === 1) { 92 | throw new TypeError("Illegal base64url string."); 93 | } 94 | return base64url; 95 | } 96 | 97 | function base64decode(input) { 98 | const data = atob(input); 99 | const bytes = new Uint8Array(data.length); 100 | for (let i = 0; i < data.length; i++) { 101 | bytes[i] = data.charCodeAt(i); 102 | } 103 | return bytes; 104 | } 105 | 106 | function fromHexChar(byte) { 107 | // '0' <= byte && byte <= '9' 108 | if (48 <= byte && byte <= 57) return byte - 48; 109 | // 'a' <= byte && byte <= 'f' 110 | if (97 <= byte && byte <= 102) return byte - 97 + 10; 111 | // 'A' <= byte && byte <= 'F' 112 | if (65 <= byte && byte <= 70) return byte - 65 + 10; 113 | throw new TypeError("Illegal hex string."); 114 | } 115 | 116 | class BinaryDecoder { 117 | _encoding; 118 | constructor(encoding) { 119 | if (arguments.length < 1) { 120 | throw TypeError("1 argument required, but only 0 present."); 121 | } 122 | this._encoding = String(encoding).trim(); 123 | if (!encodings.includes(this._encoding)) { 124 | throw new RangeError("Invalid encoding " + this._encoding); 125 | } 126 | } 127 | get encoding() { 128 | return this._encoding; 129 | } 130 | decode(input) { 131 | if (arguments.length < 1) { 132 | throw TypeError("1 argument required, but only 0 present."); 133 | } 134 | input = String(input); 135 | switch (this._encoding) { 136 | case "base64": 137 | return base64decode(input); 138 | case "base64url": 139 | return base64decode( 140 | addPaddingToBase64url(input).replace(/\-/g, "+") 141 | .replace(/_/g, "/"), 142 | ); 143 | case "hex": 144 | const dst = new Uint8Array(input.length >>> 1); 145 | for (let i = 0; i < dst.length; i++) { 146 | const a = fromHexChar(input[i * 2].charCodeAt(0)); 147 | const b = fromHexChar(input[i * 2 + 1].charCodeAt(0)); 148 | dst[i] = (a << 4) | b; 149 | } 150 | 151 | if (input.length % 2 == 1) { 152 | // Check for invalid char before reporting bad length, 153 | // since the invalid char (if present) is an earlier problem. 154 | fromHexChar(input[dst.length * 2].charCodeAt(0)); 155 | throw new TypeError("Illegal hex string."); 156 | } 157 | 158 | return dst; 159 | } 160 | } 161 | } 162 | 163 | export { BinaryDecoder, BinaryEncoder }; 164 | -------------------------------------------------------------------------------- /polyfill/mod_test.js: -------------------------------------------------------------------------------- 1 | import "./polyfill.mjs"; 2 | 3 | import { 4 | assertEquals, 5 | assertThrows, 6 | } from "https://deno.land/std@0.98.0/testing/asserts.ts"; 7 | 8 | Deno.test("BinaryDecoder no args", () => { 9 | assertThrows(() => new BinaryDecoder()); 10 | }); 11 | 12 | Deno.test("BinaryEncoder no args", () => { 13 | assertThrows(() => new BinaryEncoder()); 14 | }); 15 | 16 | const invalidEncodings = [ 17 | "foo", 18 | "heex", 19 | "he x", 20 | "base-64", 21 | "base42", 22 | 42, 23 | {}, 24 | ]; 25 | 26 | for (const encoding of invalidEncodings) { 27 | Deno.test(`BinaryDecoder invalid encoding ${JSON.stringify(encoding)}`, () => { 28 | assertThrows(() => new BinaryDecoder(encoding)); 29 | }); 30 | 31 | Deno.test(`BinaryEncoder invalid encoding ${JSON.stringify(encoding)}`, () => { 32 | assertThrows(() => new BinaryEncoder(encoding)); 33 | }); 34 | } 35 | 36 | const validEncodings = { 37 | "base64": "base64", 38 | "base64url": "base64url", 39 | "hex": "hex", 40 | "base64 ": "base64", 41 | " base64 ": "base64", 42 | }; 43 | for (const encoding in validEncodings) { 44 | const expected = validEncodings[encoding]; 45 | Deno.test(`BinaryDecoder valid encoding ${JSON.stringify(encoding)}`, () => { 46 | const decoder = new BinaryDecoder(encoding); 47 | assertEquals(decoder.encoding, expected); 48 | }); 49 | 50 | Deno.test(`BinaryEncoder valid encoding ${JSON.stringify(encoding)}`, () => { 51 | const encoder = new BinaryEncoder(encoding); 52 | assertEquals(encoder.encoding, expected); 53 | }); 54 | } 55 | 56 | const validEncodeTests = [ 57 | ["base64", "", ""], 58 | [ 59 | "base64", 60 | new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]), 61 | "FPucA9l+", 62 | ], 63 | ["base64", new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9]), "FPucA9k="], 64 | ["base64", new Uint8Array([0x14, 0xfb, 0x9c, 0x03]), "FPucAw=="], 65 | ["base64", "", ""], 66 | ["base64", "f", "Zg=="], 67 | ["base64", "fo", "Zm8="], 68 | ["base64", "foo", "Zm9v"], 69 | ["base64", "<>", "PDw/Pz8+Pg=="], 70 | [ 71 | "base64url", 72 | new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]), 73 | "FPucA9l-", 74 | ], 75 | ["base64url", "<>", "PDw_Pz8-Pg"], 76 | ["hex", "abc", "616263"], 77 | ["hex", new Uint8Array([1, 2, 3]), "010203"], 78 | ["hex", new Uint8Array([255, 0, 128]), "ff0080"], 79 | ]; 80 | 81 | for (const [encoding, input, output] of validEncodeTests) { 82 | Deno.test(`BinaryDecoder decode ${encoding} ${JSON.stringify(output)}`, () => { 83 | const decoder = new BinaryDecoder(encoding); 84 | assertEquals( 85 | decoder.decode(output), 86 | typeof input === "string" ? new TextEncoder().encode(input) : input, 87 | ); 88 | }); 89 | 90 | Deno.test(`BinaryEncoder encode ${encoding} ${JSON.stringify(output)}`, () => { 91 | const encoder = new BinaryEncoder(encoding); 92 | assertEquals( 93 | encoder.encode( 94 | typeof input === "string" ? new TextEncoder().encode(input) : input, 95 | ), 96 | output, 97 | ); 98 | }); 99 | } 100 | 101 | // TODO(lucacasonato) add invalid decode tests 102 | -------------------------------------------------------------------------------- /polyfill/polyfill.min.js: -------------------------------------------------------------------------------- 1 | (()=>{var y=Object.defineProperty;var x=(r,e,o)=>e in r?y(r,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):r[e]=o;var a=(r,e,o)=>(x(r,typeof e!="symbol"?e+"":e,o),o);var l=["base64","base64url","hex"],d=new TextEncoder().encode("0123456789abcdef"),t=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","+","/"];function h(r){let e=r.length,o="",n;for(n=2;n>2]+t[(r[n-2]&3)<<4|r[n-1]>>4]+t[(r[n-1]&15)<<2|r[n]>>6]+t[r[n]&63];return n===e+1&&(o+=t[r[n-2]>>2]+t[(r[n-2]&3)<<4]+"=="),n===e&&(o+=t[r[n-2]>>2]+t[(r[n-2]&3)<<4|r[n-1]>>4]+t[(r[n-1]&15)<<2]+"="),o}var s=class{constructor(e){a(this,"_encoding");if(arguments.length<1)throw TypeError("1 argument required, but only 0 present.");if(this._encoding=String(e).trim(),!l.includes(this._encoding))throw new RangeError("Invalid encoding "+this._encoding)}get encoding(){return this._encoding}encode(e){if(arguments.length<1)throw TypeError("1 argument required, but only 0 present.");if(e instanceof ArrayBuffer)e=new Uint8Array(e);else if(ArrayBuffer.isView(e))e=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);else throw TypeError("Argument 1 must be an array buffer, or a view on one.");switch(this._encoding){case"base64":return h(e);case"base64url":return h(e).replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_");case"hex":let o=new Uint8Array(e.length*2);for(let n=0;n>4],o[n*2+1]=d[c&15]}return new TextDecoder().decode(o)}}};function m(r){if(r.length%4==2)return r+"==";if(r.length%4==3)return r+"=";if(r.length%4==1)throw new TypeError("Illegal base64url string.");return r}function f(r){let e=atob(r),o=new Uint8Array(e.length);for(let n=0;n>>1);for(let n=0;n