1 /*
  2     Copyright 2008-2024
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true*/
 33 /*jslint nomen: true, plusplus: true, bitwise: true*/
 34 
 35 import JXG from "../jxg.js";
 36 import Encoding from "./encoding.js";
 37 
 38 var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
 39     pad = "=";
 40 
 41 // Util namespace
 42 JXG.Util = JXG.Util || {};
 43 
 44 /**
 45  * Base64 routines
 46  * @namespace
 47  */
 48 JXG.Util.Base64 = {
 49     // Local helper functions
 50     /**
 51      * Extracts one byte from a string and ensures the result is less than or equal to 255.
 52      * @param {String} s
 53      * @param {Number} i
 54      * @returns {Number} <= 255
 55      * @private
 56      */
 57     _getByte: function(s, i) {
 58         return s.charCodeAt(i) & 0xff;
 59     },
 60 
 61     /**
 62      * Determines the index of a base64 character in the base64 alphabet.
 63      * @param {String} s
 64      * @param {Number} i
 65      * @returns {Number}
 66      * @throws {Error} If the character can not be found in the alphabet.
 67      * @private
 68      */
 69     _getIndex: function(s, i) {
 70         return alphabet.indexOf(s.charAt(i));
 71     },
 72 
 73     /**
 74      * Encode the given string.
 75      * @param {String} input
 76      * @returns {string} base64 encoded version of the input string.
 77      */
 78     encode: function (input) {
 79         var i,
 80             bin,
 81             len,
 82             padLen,
 83             encInput,
 84             buffer = [];
 85 
 86         encInput = Encoding.encode(input);
 87         len = encInput.length;
 88         padLen = len % 3;
 89 
 90         for (i = 0; i < len - padLen; i += 3) {
 91             bin =
 92                 (this._getByte(encInput, i) << 16) |
 93                 (this._getByte(encInput, i + 1) << 8) |
 94                 this._getByte(encInput, i + 2);
 95             buffer.push(
 96                 alphabet.charAt(bin >> 18),
 97                 alphabet.charAt((bin >> 12) & 63),
 98                 alphabet.charAt((bin >> 6) & 63),
 99                 alphabet.charAt(bin & 63)
100             );
101         }
102 
103         switch (padLen) {
104             case 1:
105                 bin = this._getByte(encInput, len - 1);
106                 buffer.push(
107                     alphabet.charAt(bin >> 2),
108                     alphabet.charAt((bin << 4) & 63),
109                     pad,
110                     pad
111                 );
112                 break;
113             case 2:
114                 bin = (this._getByte(encInput, len - 2) << 8) | this._getByte(encInput, len - 1);
115                 buffer.push(
116                     alphabet.charAt(bin >> 10),
117                     alphabet.charAt((bin >> 4) & 63),
118                     alphabet.charAt((bin << 2) & 63),
119                     pad
120                 );
121                 break;
122         }
123 
124         return buffer.join("");
125     },
126 
127     /**
128      * Decode from Base64
129      * @param {String} input Base64 encoded data
130      * @param {Boolean} utf8 In case this parameter is true {@link JXG.Util.UTF8.decode} will be applied to
131      * the result of the base64 decoder.
132      * @throws {Error} If the string has the wrong length.
133      * @returns {String}
134      */
135     decode: function (input, utf8) {
136         var encInput,
137             i,
138             len,
139             padLen,
140             bin,
141             output,
142             result = [],
143             buffer = [];
144 
145         // deactivate regexp linting. Our regex is secure, because we replace everything with ''
146         /*jslint regexp:true*/
147         encInput = input.replace(/[^A-Za-z0-9+/=]/g, "");
148         /*jslint regexp:false*/
149 
150         len = encInput.length;
151 
152         if (len % 4 !== 0) {
153             throw new Error(
154                 "JSXGraph/utils/base64: Can't decode string (invalid input length)."
155             );
156         }
157 
158         if (encInput.charAt(len - 1) === pad) {
159             padLen = 1;
160 
161             if (encInput.charAt(len - 2) === pad) {
162                 padLen = 2;
163             }
164 
165             // omit the last four bytes (taken care of after the for loop)
166             len -= 4;
167         }
168 
169         for (i = 0; i < len; i += 4) {
170             bin =
171                 (this._getIndex(encInput, i) << 18) |
172                 (this._getIndex(encInput, i + 1) << 12) |
173                 (this._getIndex(encInput, i + 2) << 6) |
174                 this._getIndex(encInput, i + 3);
175             buffer.push(bin >> 16, (bin >> 8) & 255, bin & 255);
176 
177             // flush the buffer, if it gets too big fromCharCode will crash
178             if (i % 10000 === 0) {
179                 result.push(String.fromCharCode.apply(null, buffer));
180                 buffer = [];
181             }
182         }
183 
184         switch (padLen) {
185             case 1:
186                 bin =
187                     (this._getIndex(encInput, len) << 12) |
188                     (this._getIndex(encInput, len + 1) << 6) |
189                     this._getIndex(encInput, len + 2);
190                 buffer.push(bin >> 10, (bin >> 2) & 255);
191                 break;
192 
193             case 2:
194                 bin = (this._getIndex(encInput, i) << 6) | this._getIndex(encInput, i + 1);
195                 buffer.push(bin >> 4);
196                 break;
197         }
198 
199         result.push(String.fromCharCode.apply(null, buffer));
200         output = result.join("");
201 
202         if (utf8) {
203             output = Encoding.decode(output);
204         }
205 
206         return output;
207     },
208 
209     /**
210      * Decode the base64 input data as an array
211      * @param {string} input
212      * @returns {Array}
213      */
214     decodeAsArray: function (input) {
215         var i,
216             dec = this.decode(input),
217             ar = [],
218             len = dec.length;
219 
220         for (i = 0; i < len; i++) {
221             ar[i] = dec.charCodeAt(i);
222         }
223 
224         return ar;
225     }
226 };
227 
228 export default JXG.Util.Base64;
229