UNPKG

rx-player

Version:
383 lines (382 loc) 13.6 kB
"use strict"; /** * Copyright 2015 CANAL+ Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.bytesToHex = bytesToHex; exports.hexToBytes = hexToBytes; exports.strToUtf8 = strToUtf8; exports.utf8ToStr = utf8ToStr; exports.strToUtf16LE = strToUtf16LE; exports.utf16LEToStr = utf16LEToStr; exports.strToBeUtf16 = strToBeUtf16; exports.beUtf16ToStr = beUtf16ToStr; exports.guidToUuid = guidToUuid; exports.readNullTerminatedString = readNullTerminatedString; var log_1 = require("../log"); var assert_1 = require("./assert"); var global_scope_1 = require("./global_scope"); var hasTextDecoder = typeof global_scope_1.default === "object" && typeof global_scope_1.default.TextDecoder === "function"; var hasTextEncoder = typeof global_scope_1.default === "object" && typeof global_scope_1.default.TextEncoder === "function"; /** * Convert a string to an Uint8Array containing the corresponding UTF-16 code * units in little-endian. * @param {string} str * @returns {Uint8Array} */ function strToUtf16LE(str) { var buffer = new ArrayBuffer(str.length * 2); var res = new Uint8Array(buffer); for (var i = 0; i < res.length; i += 2) { var value = str.charCodeAt(i / 2); res[i] = value & 0xff; res[i + 1] = (value >> 8) & 0xff; } return res; } /** * Convert a string to an Uint8Array containing the corresponding UTF-16 code * units in big-endian. * @param {string} str * @returns {Uint8Array} */ function strToBeUtf16(str) { var buffer = new ArrayBuffer(str.length * 2); var res = new Uint8Array(buffer); for (var i = 0; i < res.length; i += 2) { var value = str.charCodeAt(i / 2); res[i + 1] = value & 0xff; res[i] = (value >> 8) & 0xff; } return res; } /** * Construct string from the little-endian UTF-16 code units given. * @param {Uint8Array} bytes * @returns {string} */ function utf16LEToStr(bytes) { if (hasTextDecoder) { try { // instanciation throws if the encoding is unsupported var decoder = new TextDecoder("utf-16le"); return decoder.decode(bytes); } catch (e) { var err = e instanceof Error ? e : "Unknown Error"; log_1.default.warn("utils", "could not use TextDecoder to parse UTF-16LE, " + "fallbacking to another implementation", err); } } var str = ""; for (var i = 0; i < bytes.length; i += 2) { str += String.fromCharCode((bytes[i + 1] << 8) + bytes[i]); } return str; } /** * Construct string from the little-endian UTF-16 code units given. * @param {Uint8Array} bytes * @returns {string} */ function beUtf16ToStr(bytes) { if (hasTextDecoder) { try { // instanciation throws if the encoding is unsupported var decoder = new TextDecoder("utf-16be"); return decoder.decode(bytes); } catch (e) { var err = e instanceof Error ? e : "Unknown Error"; log_1.default.warn("utils", "Could not use TextDecoder to parse UTF-16BE, " + "fallbacking to another implementation", err); } } var str = ""; for (var i = 0; i < bytes.length; i += 2) { str += String.fromCharCode((bytes[i] << 8) + bytes[i + 1]); } return str; } /** * Convert a string to an Uint8Array containing the corresponding UTF-8 code * units. * @param {string} str * @returns {Uint8Array} */ function strToUtf8(str) { if (hasTextEncoder) { try { var encoder = new TextEncoder(); return encoder.encode(str); } catch (e) { var err = e instanceof Error ? e : "Unknown Error"; log_1.default.warn("utils", "Could not use TextEncoder to encode string into UTF-8, " + "fallbacking to another implementation", err); } } // http://stackoverflow.com/a/13691499 provides an ugly but functional solution. // (Note you have to dig deeper to understand it but I have more faith in // stackoverflow not going down in the future so I leave that link.) // Briefly said, `utf8Str` will contain a version of `str` where every // non-ASCII characters will be replaced by an escape sequence of the // corresponding representation of those characters in UTF-8. // It does sound weird and unnecessarily complicated, but it works! // // Here is actually what happens with more words. We will rely on two browser // APIs: // // - `encodeURIComponent` will take a string and convert the non-ASCII // characters in it into the percent-encoded version of the corresponding // UTF-8 bytes // Example: encodeURIComponent("é") => 0xC3 0xA9 => `"%C3%A9"` // // - `unescape` unescapes (so far so good) a percent-encoded string. But it // does it in a really simple way: percent-encoded byte by percent-encoded // byte into the corresponding extended ASCII representation on 8 bits. // As a result, we end-up with a string which actually contains instead of // each of its original characters, the UTF-8 code units (8 bits) of // those characters. // Let's take our previous `"é" => "%C3%A9"` example. Here we would get: // unecape("%C3%A9") => "\u00c3\u00a9" === "é" (in extended ASCII) // // By iterating on the resulting string, we will then be able to generate a // Uint8Array containing the UTF-8 representation of that original string, by // just calling the charCodeAt API on it. var utf8Str; var pcStr = encodeURIComponent(str); // As "unescape" is a deprecated function we want to declare a fallback in the // case a browser decide to not implement it. if (typeof unescape === "function") { utf8Str = unescape(pcStr); } else { // Let's implement a simple unescape function (got to admit it was for the challenge) // http://ecma-international.org/ecma-262/9.0/#sec-unescape-string var isHexChar = /[0-9a-fA-F]/; var pcStrLen = pcStr.length; utf8Str = ""; for (var i = 0; i < pcStr.length; i++) { var wasPercentEncoded = false; if (pcStr[i] === "%") { if (i <= pcStrLen - 6 && pcStr[i + 1] === "u" && isHexChar.test(pcStr[i + 2]) && isHexChar.test(pcStr[i + 3]) && isHexChar.test(pcStr[i + 4]) && isHexChar.test(pcStr[i + 5])) { var charCode = parseInt(pcStr.substring(i + 1, i + 6), 16); utf8Str += String.fromCharCode(charCode); wasPercentEncoded = true; i += 5; // Skip the next 5 chars } else if (i <= pcStrLen - 3 && isHexChar.test(pcStr[i + 1]) && isHexChar.test(pcStr[i + 2])) { var charCode = parseInt(pcStr.substring(i + 1, i + 3), 16); utf8Str += String.fromCharCode(charCode); wasPercentEncoded = true; i += 2; // Skip the next 2 chars } } if (!wasPercentEncoded) { utf8Str += pcStr[i]; } } } // Now let's just build our array from every other bytes of that string's // UTF-16 representation var res = new Uint8Array(utf8Str.length); for (var i = 0; i < utf8Str.length; i++) { res[i] = utf8Str.charCodeAt(i) & 0xff; // first byte should be 0x00 anyway } return res; } /** * Creates a new string from the given array of char codes. * @param {Uint8Array} args * @returns {string} */ function stringFromCharCodes(args) { var max = 16000; var ret = ""; for (var i = 0; i < args.length; i += max) { var subArray = args.subarray(i, i + max); // NOTE: ugly I know, but TS is problematic here (you can try) ret += String.fromCharCode.apply(null, subArray); } return ret; } /** * Transform an integer into an hexadecimal string of the given length, padded * to the left with `0` if needed. * @example * ``` * intToHex(5, 4); // => "0005" * intToHex(5, 2); // => "05" * intToHex(10, 1); // => "a" * intToHex(268, 3); // => "10c" * intToHex(4584, 6) // => "0011e8" * intToHex(123456, 4); // => "1e240" (we do nothing when going over 4 chars) * ``` * @param {number} num * @param {number} size * @returns {string} */ function intToHex(num, size) { var toStr = num.toString(16); return toStr.length >= size ? toStr : new Array(size - toStr.length + 1).join("0") + toStr; } /** * Creates a string from the given Uint8Array containing utf-8 code units. * @param {Uint8Array} data * @returns {string} */ function utf8ToStr(data) { if (hasTextDecoder) { try { // TextDecoder use UTF-8 by default var decoder = new TextDecoder(); return decoder.decode(data); } catch (e) { var err = e instanceof Error ? e : "Unknown Error"; log_1.default.warn("utils", "could not use TextDecoder to parse UTF-8, " + "fallbacking to another implementation", err); } } var uint8 = data; // If present, strip off the UTF-8 BOM. if (uint8[0] === 0xef && uint8[1] === 0xbb && uint8[2] === 0xbf) { uint8 = uint8.subarray(3); } // We're basically doing strToUtf8 in reverse. // You can look at that other function for the whole story. // Generate string containing escaped UTF-8 code units var utf8Str = stringFromCharCodes(uint8); var escaped; if (typeof escape === "function") { // Transform UTF-8 escape sequence into percent-encoded escape sequences. escaped = escape(utf8Str); } else { // Let's implement a simple escape function // http://ecma-international.org/ecma-262/9.0/#sec-escape-string var nonEscapedChar = /[A-Za-z0-9*_+-./]/; escaped = ""; for (var i = 0; i < utf8Str.length; i++) { if (nonEscapedChar.test(utf8Str[i])) { escaped += utf8Str[i]; } else { var charCode = utf8Str.charCodeAt(i); escaped += charCode >= 256 ? "%u" + intToHex(charCode, 4) : "%" + intToHex(charCode, 2); } } } // Decode the percent-encoded UTF-8 string into the proper JS string. // Example: "g#%E3%82%AC" -> "g#€" return decodeURIComponent(escaped); } /** * Convert hex codes in a string form into the corresponding bytes. * @param {string} str * @returns {Uint8Array} * @throws TypeError - str.length is odd */ function hexToBytes(str) { var len = str.length; var arr = new Uint8Array(len / 2); for (var i = 0, j = 0; i < len; i += 2, j++) { arr[j] = parseInt(str.substring(i, i + 2), 16) & 0xff; } return arr; } /** * Convert bytes into the corresponding hex string, with the possibility * to add a separator. * @param {Uint8Array} bytes * @param {string} [sep=""] - separator. Separate each two hex character. * @returns {string} */ function bytesToHex(bytes, sep) { if (sep === void 0) { sep = ""; } var hex = ""; for (var i = 0; i < bytes.byteLength; i++) { hex += (bytes[i] >>> 4).toString(16); hex += (bytes[i] & 0xf).toString(16); if (sep.length > 0 && i < bytes.byteLength - 1) { hex += sep; } } return hex; } /** * Convert little-endian GUID into big-endian UUID. * @param {Uint8Array} guid * @returns {Uint8Array} - uuid * @throws AssertionError - The guid length is not 16 */ function guidToUuid(guid) { (0, assert_1.default)(guid.length === 16, "GUID length should be 16"); var p1A = guid[0]; var p1B = guid[1]; var p1C = guid[2]; var p1D = guid[3]; var p2A = guid[4]; var p2B = guid[5]; var p3A = guid[6]; var p3B = guid[7]; var uuid = new Uint8Array(16); // swapping byte endian on 4 bytes // [1, 2, 3, 4] => [4, 3, 2, 1] uuid[0] = p1D; uuid[1] = p1C; uuid[2] = p1B; uuid[3] = p1A; // swapping byte endian on 2 bytes // [5, 6] => [6, 5] uuid[4] = p2B; uuid[5] = p2A; // swapping byte endian on 2 bytes // [7, 8] => [8, 7] uuid[6] = p3B; uuid[7] = p3A; uuid.set(guid.subarray(8, 16), 8); return uuid; } /** * Decode string from bytes (UTF-8). * Keeps reading until it reaches a byte that equals to zero. * @param {Uint8Array} buffer * @param {number} offset * @returns {Object} */ function readNullTerminatedString(buffer, offset) { var position = offset; while (position < buffer.length) { var value = buffer[position]; if (value === 0) { break; } position += 1; } var bytes = buffer.subarray(offset, position); return { end: position + 1, string: utf8ToStr(bytes) }; }