xref: /aosp_15_r20/external/perfetto/ui/src/base/string_utils.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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