1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2019 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Workerimport { 16*6dbdd20aSAndroid Build Coastguard Worker decode as b64Decode, 17*6dbdd20aSAndroid Build Coastguard Worker encode as b64Encode, 18*6dbdd20aSAndroid Build Coastguard Worker length as b64Len, 19*6dbdd20aSAndroid Build Coastguard Worker} from '@protobufjs/base64'; 20*6dbdd20aSAndroid Build Coastguard Workerimport {assertTrue} from './logging'; 21*6dbdd20aSAndroid Build Coastguard Worker 22*6dbdd20aSAndroid Build Coastguard Worker// Lazy initialize at first use. 23*6dbdd20aSAndroid Build Coastguard Workerlet textDecoder: TextDecoder | undefined = undefined; 24*6dbdd20aSAndroid Build Coastguard Workerlet textEncoder: TextEncoder | undefined = undefined; 25*6dbdd20aSAndroid Build Coastguard Worker 26*6dbdd20aSAndroid Build Coastguard Workerexport function base64Encode(buffer: Uint8Array): string { 27*6dbdd20aSAndroid Build Coastguard Worker return b64Encode(buffer, 0, buffer.length); 28*6dbdd20aSAndroid Build Coastguard Worker} 29*6dbdd20aSAndroid Build Coastguard Worker 30*6dbdd20aSAndroid Build Coastguard Workerexport function base64Decode(str: string): Uint8Array { 31*6dbdd20aSAndroid Build Coastguard Worker // if the string is in base64url format, convert to base64 32*6dbdd20aSAndroid Build Coastguard Worker const b64 = str.replaceAll('-', '+').replaceAll('_', '/'); 33*6dbdd20aSAndroid Build Coastguard Worker const arr = new Uint8Array(b64Len(b64)); 34*6dbdd20aSAndroid Build Coastguard Worker const written = b64Decode(b64, arr, 0); 35*6dbdd20aSAndroid Build Coastguard Worker assertTrue(written === arr.length); 36*6dbdd20aSAndroid Build Coastguard Worker return arr; 37*6dbdd20aSAndroid Build Coastguard Worker} 38*6dbdd20aSAndroid Build Coastguard Worker 39*6dbdd20aSAndroid Build Coastguard Worker// encode binary array to hex string 40*6dbdd20aSAndroid Build Coastguard Workerexport function hexEncode(bytes: Uint8Array): string { 41*6dbdd20aSAndroid Build Coastguard Worker return bytes.reduce( 42*6dbdd20aSAndroid Build Coastguard Worker (prev, cur) => prev + ('0' + cur.toString(16)).slice(-2), 43*6dbdd20aSAndroid Build Coastguard Worker '', 44*6dbdd20aSAndroid Build Coastguard Worker ); 45*6dbdd20aSAndroid Build Coastguard Worker} 46*6dbdd20aSAndroid Build Coastguard Worker 47*6dbdd20aSAndroid Build Coastguard Workerexport function utf8Encode(str: string): Uint8Array { 48*6dbdd20aSAndroid Build Coastguard Worker textEncoder = textEncoder ?? new TextEncoder(); 49*6dbdd20aSAndroid Build Coastguard Worker return textEncoder.encode(str); 50*6dbdd20aSAndroid Build Coastguard Worker} 51*6dbdd20aSAndroid Build Coastguard Worker 52*6dbdd20aSAndroid Build Coastguard Worker// Note: not all byte sequences can be converted to<>from UTF8. This can be 53*6dbdd20aSAndroid Build Coastguard Worker// used only with valid unicode strings, not arbitrary byte buffers. 54*6dbdd20aSAndroid Build Coastguard Workerexport function utf8Decode(buffer: Uint8Array | ArrayBuffer): string { 55*6dbdd20aSAndroid Build Coastguard Worker textDecoder = textDecoder ?? new TextDecoder(); 56*6dbdd20aSAndroid Build Coastguard Worker return textDecoder.decode(buffer); 57*6dbdd20aSAndroid Build Coastguard Worker} 58*6dbdd20aSAndroid Build Coastguard Worker 59*6dbdd20aSAndroid Build Coastguard Worker// The binaryEncode/Decode functions below allow to encode an arbitrary binary 60*6dbdd20aSAndroid Build Coastguard Worker// buffer into a string that can be JSON-encoded. binaryEncode() applies 61*6dbdd20aSAndroid Build Coastguard Worker// UTF-16 encoding to each byte individually. 62*6dbdd20aSAndroid Build Coastguard Worker// Unlike utf8Encode/Decode, any arbitrary byte sequence can be converted into a 63*6dbdd20aSAndroid Build Coastguard Worker// valid string, and viceversa. 64*6dbdd20aSAndroid Build Coastguard Worker// This should be only used when a byte array needs to be transmitted over an 65*6dbdd20aSAndroid Build Coastguard Worker// interface that supports only JSON serialization (e.g., postmessage to a 66*6dbdd20aSAndroid Build Coastguard Worker// chrome extension). 67*6dbdd20aSAndroid Build Coastguard Worker 68*6dbdd20aSAndroid Build Coastguard Workerexport function binaryEncode(buf: Uint8Array): string { 69*6dbdd20aSAndroid Build Coastguard Worker let str = ''; 70*6dbdd20aSAndroid Build Coastguard Worker for (let i = 0; i < buf.length; i++) { 71*6dbdd20aSAndroid Build Coastguard Worker str += String.fromCharCode(buf[i]); 72*6dbdd20aSAndroid Build Coastguard Worker } 73*6dbdd20aSAndroid Build Coastguard Worker return str; 74*6dbdd20aSAndroid Build Coastguard Worker} 75*6dbdd20aSAndroid Build Coastguard Worker 76*6dbdd20aSAndroid Build Coastguard Workerexport function binaryDecode(str: string): Uint8Array { 77*6dbdd20aSAndroid Build Coastguard Worker const buf = new Uint8Array(str.length); 78*6dbdd20aSAndroid Build Coastguard Worker const strLen = str.length; 79*6dbdd20aSAndroid Build Coastguard Worker for (let i = 0; i < strLen; i++) { 80*6dbdd20aSAndroid Build Coastguard Worker buf[i] = str.charCodeAt(i); 81*6dbdd20aSAndroid Build Coastguard Worker } 82*6dbdd20aSAndroid Build Coastguard Worker return buf; 83*6dbdd20aSAndroid Build Coastguard Worker} 84*6dbdd20aSAndroid Build Coastguard Worker 85*6dbdd20aSAndroid Build Coastguard Worker// A function used to interpolate strings into SQL query. The only replacement 86*6dbdd20aSAndroid Build Coastguard Worker// is done is that single quote replaced with two single quotes, according to 87*6dbdd20aSAndroid Build Coastguard Worker// SQLite documentation: 88*6dbdd20aSAndroid Build Coastguard Worker// https://www.sqlite.org/lang_expr.html#literal_values_constants_ 89*6dbdd20aSAndroid Build Coastguard Worker// 90*6dbdd20aSAndroid Build Coastguard Worker// The purpose of this function is to use in simple comparisons, to escape 91*6dbdd20aSAndroid Build Coastguard Worker// strings used in GLOB clauses see escapeQuery function. 92*6dbdd20aSAndroid Build Coastguard Workerexport function sqliteString(str: string): string { 93*6dbdd20aSAndroid Build Coastguard Worker return `'${str.replaceAll("'", "''")}'`; 94*6dbdd20aSAndroid Build Coastguard Worker} 95*6dbdd20aSAndroid Build Coastguard Worker 96*6dbdd20aSAndroid Build Coastguard Worker// Makes a string safe to be used as a SQL table/view/function name. 97*6dbdd20aSAndroid Build Coastguard Workerexport function sqlNameSafe(str: string): string { 98*6dbdd20aSAndroid Build Coastguard Worker return str.replace(/[^a-zA-Z0-9_]+/g, '_'); 99*6dbdd20aSAndroid Build Coastguard Worker} 100*6dbdd20aSAndroid Build Coastguard Worker 101*6dbdd20aSAndroid Build Coastguard Worker// Chat apps (including G Chat) sometimes replace ASCII characters with similar 102*6dbdd20aSAndroid Build Coastguard Worker// looking unicode characters that break code snippets. 103*6dbdd20aSAndroid Build Coastguard Worker// This function attempts to undo these replacements. 104*6dbdd20aSAndroid Build Coastguard Workerexport function undoCommonChatAppReplacements(str: string): string { 105*6dbdd20aSAndroid Build Coastguard Worker // Replace non-breaking spaces with normal spaces. 106*6dbdd20aSAndroid Build Coastguard Worker return str.replaceAll('\u00A0', ' '); 107*6dbdd20aSAndroid Build Coastguard Worker} 108*6dbdd20aSAndroid Build Coastguard Worker 109*6dbdd20aSAndroid Build Coastguard Workerexport function cropText(str: string, charWidth: number, rectWidth: number) { 110*6dbdd20aSAndroid Build Coastguard Worker let displayText = ''; 111*6dbdd20aSAndroid Build Coastguard Worker const maxLength = Math.floor(rectWidth / charWidth) - 1; 112*6dbdd20aSAndroid Build Coastguard Worker if (str.length <= maxLength) { 113*6dbdd20aSAndroid Build Coastguard Worker displayText = str; 114*6dbdd20aSAndroid Build Coastguard Worker } else { 115*6dbdd20aSAndroid Build Coastguard Worker let limit = maxLength; 116*6dbdd20aSAndroid Build Coastguard Worker let maybeTripleDot = ''; 117*6dbdd20aSAndroid Build Coastguard Worker if (maxLength > 1) { 118*6dbdd20aSAndroid Build Coastguard Worker limit = maxLength - 1; 119*6dbdd20aSAndroid Build Coastguard Worker maybeTripleDot = '\u2026'; 120*6dbdd20aSAndroid Build Coastguard Worker } 121*6dbdd20aSAndroid Build Coastguard Worker // Javascript strings are UTF-16. |limit| could point in the middle of a 122*6dbdd20aSAndroid Build Coastguard Worker // 32-bit double-wchar codepoint (e.g., an emoji). Here we detect if the 123*6dbdd20aSAndroid Build Coastguard Worker // |limit|-th wchar is a leading surrogate and attach the trailing one. 124*6dbdd20aSAndroid Build Coastguard Worker const lastCharCode = str.charCodeAt(limit - 1); 125*6dbdd20aSAndroid Build Coastguard Worker limit += lastCharCode >= 55296 && lastCharCode < 56320 ? 1 : 0; 126*6dbdd20aSAndroid Build Coastguard Worker displayText = str.substring(0, limit) + maybeTripleDot; 127*6dbdd20aSAndroid Build Coastguard Worker } 128*6dbdd20aSAndroid Build Coastguard Worker return displayText; 129*6dbdd20aSAndroid Build Coastguard Worker} 130