Skip to content

Commit

Permalink
frontend: fix base64 decoding of 'utf8WithControlChars' (#825)
Browse files Browse the repository at this point in the history
  • Loading branch information
bojand authored Aug 31, 2023
1 parent 5b5ecae commit 68d727f
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 54 deletions.
6 changes: 6 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"antd": "^4.21",
"array-move": "^4",
"framer-motion": "^7",
"js-base64": "^3.7.5",
"mobx": "^6.6",
"mobx-react": "^7.5",
"moment": "^2.29.4",
Expand Down
42 changes: 6 additions & 36 deletions frontend/src/state/backendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import fetchWithTimeout from '../utils/fetchWithTimeout';
import { toJson } from '../utils/jsonUtils';
import { LazyMap } from '../utils/LazyMap';
import { ObjToKv } from '../utils/tsxUtils';
import { decodeBase64, TimeSince } from '../utils/utils';
import { base64ToHexString, decodeBase64, TimeSince } from '../utils/utils';
import { appGlobal } from './appGlobal';
import {
GetAclsRequest, AclRequestDefault, GetAclOverviewResponse, AdminInfo,
Expand Down Expand Up @@ -369,31 +369,18 @@ const apiStore = {
case 'message':
const m = msg.message as TopicMessage;

const keyData = m.key.payload;
if (keyData != null && keyData != undefined && keyData != '' && m.key.encoding == 'binary') {
try {
m.key.payload = decodeBase64(m.key.payload); // unpack base64 encoded key
} catch (error) {
// Empty
// Only unpack if the key is base64 based
}
}

m.keyJson = JSON.stringify(m.key.payload);
m.valueJson = JSON.stringify(m.value.payload);

if (m.key.encoding == 'binary' || m.key.encoding == 'utf8WithControlChars') {
m.key.payload = decodeBase64(m.key.payload);
m.keyBinHexPreview = this.base64ToHexString(m.key.payload);
m.keyBinHexPreview = base64ToHexString(m.key.payload);
}

if (m.value.encoding == 'binary' || m.key.encoding == 'utf8WithControlChars') {
if (m.value.encoding == 'binary' || m.value.encoding == 'utf8WithControlChars') {
m.value.payload = decodeBase64(m.value.payload);
m.valueBinHexPreview = this.base64ToHexString(m.value.payload);
m.valueBinHexPreview = base64ToHexString(m.value.payload);
}


//m = observable.object(m, undefined, { deep: false });
m.keyJson = JSON.stringify(m.key.payload);
m.valueJson = JSON.stringify(m.value.payload);

this.messages.push(m);
break;
Expand All @@ -402,24 +389,7 @@ const apiStore = {
currentWS.onmessage = onMessageHandler;
},

base64ToHexString(base64: string): string {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}

let hex = '';
for (let i = 0; i < bytes.length; i++) {
const b = bytes[i].toString(16);
hex += b.length === 1 ? '0' + b : b;

if (i < bytes.length - 1)
hex += ' ';
}

return hex;
},

stopMessageSearch() {
if (currentWS) {
Expand Down
48 changes: 30 additions & 18 deletions frontend/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import prettyMillisecondsOriginal from 'pretty-ms';
import { message } from 'antd';
import { MessageType } from 'antd/lib/message';
import { TopicMessage } from '../state/restInterfaces';

import { Base64 } from 'js-base64';

// Note: Making a <Memo> component is not possible, the container JSX will always render children first so they can be passed as props
export const nameof = <T>(name: Extract<keyof T, string>): string => name;
Expand Down Expand Up @@ -485,7 +485,7 @@ export function groupConsecutive(ar: number[]): number[][] {
return groups;
}

export const prettyBytesOrNA = function(n: number) {
export const prettyBytesOrNA = function (n: number) {
if (!isFinite(n) || n < 0) return 'N/A';
return prettyBytes(n);
}
Expand All @@ -505,7 +505,7 @@ function isUInt64Maximum(str: string) {
return false;
}

export const prettyBytes = function(n: number | string | null | undefined, options?: PrettyValueOptions) {
export const prettyBytes = function (n: number | string | null | undefined, options?: PrettyValueOptions) {
if (typeof n === 'undefined' || n === null)
return options?.showNullAs ?? 'N/A'; // null, undefined -> N/A

Expand Down Expand Up @@ -535,7 +535,7 @@ export const prettyBytes = function(n: number | string | null | undefined, optio
return prettyBytesOriginal(n, { binary: true });
}

export const prettyMilliseconds = function(n: number | string, options?: prettyMillisecondsOriginal.Options & PrettyValueOptions) {
export const prettyMilliseconds = function (n: number | string, options?: prettyMillisecondsOriginal.Options & PrettyValueOptions) {
if (typeof n === 'undefined' || n === null)
return options?.showNullAs ?? 'N/A'; // null, undefined -> N/A

Expand Down Expand Up @@ -711,23 +711,35 @@ export function scrollTo(targetId: string, anchor: 'start' | 'end' | 'center' =

// See: https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
export function decodeBase64(base64: string) {
const data = atob(base64);
const length = data.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++)
bytes[i] = data.charCodeAt(i);

const decoder = new TextDecoder(); // default is utf-8
return decoder.decode(bytes);
return Base64.decode(base64);
}

export function encodeBase64(rawData: string) {
// first we use encodeURIComponent to get percent-encoded UTF-8,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
return btoa(encodeURIComponent(rawData).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode(parseInt(p1, 16))
}))
return Base64.encode(rawData);
}

export function base64ToHexString(base64: string): string {
try {

const binary = Base64.decode(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
let hex = '';
for (let i = 0; i < bytes.length; i++) {
const b = bytes[i].toString(16);
hex += b.length === 1 ? '0' + b : b;

if (i < bytes.length - 1)
hex += ' ';
}

return hex;
}
catch (err) {
return '<<Unable to decode message>>';
}
}

export function delay(timeoutMs: number): Promise<void> {
Expand Down

0 comments on commit 68d727f

Please sign in to comment.