      1 /*
      2     Copyright 2008-2023
      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";
     36 import Encoding from "./encoding";
     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