1 /*
  2     Copyright 2008-2021
  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 <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true, bitwise: true*/
 35 
 36 /* depends:
 37  jxg
 38  utils/encoding
 39  */
 40 
 41 define(['jxg', 'utils/encoding'], function (JXG, Encoding) {
 42 
 43     "use strict";
 44 
 45     var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
 46         pad = '=';
 47 
 48     // Util namespace
 49     JXG.Util = JXG.Util || {};
 50 
 51     // Local helper functions
 52     /**
 53      * Extracts one byte from a string and ensures the result is less than or equal to 255.
 54      * @param {String} s
 55      * @param {Number} i
 56      * @returns {Number} <= 255
 57      * @private
 58      */
 59     function _getByte(s, i) {
 60         return s.charCodeAt(i) & 0xff;
 61     }
 62 
 63     /**
 64      * Determines the index of a base64 character in the base64 alphabet.
 65      * @param {String} s
 66      * @param {Number} i
 67      * @returns {Number}
 68      * @throws {Error} If the character can not be found in the alphabet.
 69      * @private
 70      */
 71     function _getIndex(s, i) {
 72         return alphabet.indexOf(s.charAt(i));
 73     }
 74 
 75     /**
 76      * Base64 routines
 77      * @namespace
 78      */
 79     JXG.Util.Base64 = {
 80         /**
 81          * Encode the given string.
 82          * @param {String} input
 83          * @returns {string} base64 encoded version of the input string.
 84          */
 85         encode : function (input) {
 86             var i, bin, len, padLen, encInput,
 87                 buffer = [];
 88 
 89             encInput =  Encoding.encode(input);
 90             len = encInput.length;
 91             padLen = len % 3;
 92 
 93             for (i = 0; i < len - padLen; i += 3) {
 94                 bin = (_getByte(encInput, i) << 16) | (_getByte(encInput, i + 1) << 8) | (_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 = _getByte(encInput, len - 1);
106                 buffer.push(alphabet.charAt(bin >> 2), alphabet.charAt((bin << 4) & 63), pad, pad);
107                 break;
108             case 2:
109                 bin = (_getByte(encInput, len - 2) << 8) | _getByte(encInput, len - 1);
110                 buffer.push(
111                     alphabet.charAt(bin >> 10),
112                     alphabet.charAt((bin >> 4) & 63),
113                     alphabet.charAt((bin << 2) & 63),
114                     pad
115                 );
116                 break;
117             }
118 
119             return buffer.join('');
120         },
121 
122         /**
123          * Decode from Base64
124          * @param {String} input Base64 encoded data
125          * @param {Boolean} utf8 In case this parameter is true {@link JXG.Util.UTF8.decode} will be applied to
126          * the result of the base64 decoder.
127          * @throws {Error} If the string has the wrong length.
128          * @returns {String}
129          */
130         decode : function (input, utf8) {
131             var encInput, i, len, padLen, bin, output,
132                 result = [],
133                 buffer = [];
134 
135             // deactivate regexp linting. Our regex is secure, because we replace everything with ''
136             /*jslint regexp:true*/
137             encInput = input.replace(/[^A-Za-z0-9\+\/=]/g, '');
138             /*jslint regexp:false*/
139 
140             len = encInput.length;
141 
142             if (len % 4 !== 0) {
143                 throw new Error('JSXGraph/utils/base64: Can\'t decode string (invalid input length).');
144             }
145 
146             if (encInput.charAt(len - 1) === pad) {
147                 padLen = 1;
148 
149                 if (encInput.charAt(len - 2) === pad) {
150                     padLen = 2;
151                 }
152 
153                 // omit the last four bytes (taken care of after the for loop)
154                 len -= 4;
155             }
156 
157             for (i = 0; i < len; i += 4) {
158                 bin = (_getIndex(encInput, i) << 18) | (_getIndex(encInput, i + 1) << 12) | (_getIndex(encInput, i + 2) << 6) | _getIndex(encInput, i + 3);
159                 buffer.push(bin >> 16, (bin >> 8) & 255, bin & 255);
160 
161                 // flush the buffer, if it gets too big fromCharCode will crash
162                 if (i % 10000 === 0) {
163                     result.push(String.fromCharCode.apply(null, buffer));
164                     buffer = [];
165                 }
166             }
167 
168             switch (padLen) {
169             case 1:
170                 bin = (_getIndex(encInput, len) << 12) | (_getIndex(encInput, len + 1) << 6) | (_getIndex(encInput, len + 2));
171                 buffer.push(bin >> 10, (bin >> 2) & 255);
172                 break;
173 
174             case 2:
175                 bin = (_getIndex(encInput, i) << 6) | (_getIndex(encInput, i + 1));
176                 buffer.push(bin >> 4);
177                 break;
178             }
179 
180             result.push(String.fromCharCode.apply(null, buffer));
181             output = result.join('');
182 
183             if (utf8) {
184                 output = Encoding.decode(output);
185             }
186 
187             return output;
188         },
189 
190         /**
191          * Decode the base64 input data as an array
192          * @param {string} input
193          * @returns {Array}
194          */
195         decodeAsArray: function (input) {
196             var i,
197                 dec = this.decode(input),
198                 ar = [],
199                 len = dec.length;
200 
201             for (i = 0; i < len; i++) {
202                 ar[i] = dec.charCodeAt(i);
203             }
204 
205             return ar;
206         }
207     };
208 
209     return JXG.Util.Base64;
210 });
211