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