Skip to content

Commit

Permalink
fix: encoding when reading a cloud sketch
Browse files Browse the repository at this point in the history
Closes #449
Closes #634

Signed-off-by: Akos Kitta <[email protected]>
  • Loading branch information
Akos Kitta committed Feb 23, 2023
1 parent f9c5888 commit 23906d9
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 57 deletions.
53 changes: 5 additions & 48 deletions arduino-ide-extension/src/browser/create/create-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,24 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { inject, injectable } from '@theia/core/shared/inversify';
import { fetch } from 'cross-fetch';
import { SketchesService } from '../../common/protocol';
import { unit8ArrayToString } from '../../common/utils';
import { ArduinoPreferences } from '../arduino-preferences';
import { AuthenticationClientService } from '../auth/authentication-client-service';
import { SketchCache } from '../widgets/cloud-sketchbook/cloud-sketch-cache';
import * as createPaths from './create-paths';
import { posix } from './create-paths';
import { Create, CreateError } from './typings';

export interface ResponseResultProvider {
interface ResponseResultProvider {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(response: Response): Promise<any>;
}
export namespace ResponseResultProvider {
namespace ResponseResultProvider {
export const NOOP: ResponseResultProvider = async () => undefined;
export const TEXT: ResponseResultProvider = (response) => response.text();
export const JSON: ResponseResultProvider = (response) => response.json();
}

// TODO: check if this is still needed: https://github.com/electron/electron/issues/18733
// The original issue was reported for Electron 5.x and 6.x. Theia uses 15.x
export function Utf8ArrayToStr(array: Uint8Array): string {
let out, i, c;
let char2, char3;

out = '';
const len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
);
break;
}
}

return out;
}

type ResourceType = 'f' | 'd';

@injectable()
Expand Down Expand Up @@ -330,10 +288,9 @@ export class CreateApi {
if (sketch) {
const url = new URL(`${this.domain()}/sketches/${sketch.id}`);
const headers = await this.headers();

// parse the secret file
const secrets = (
typeof content === 'string' ? content : Utf8ArrayToStr(content)
typeof content === 'string' ? content : unit8ArrayToString(content)
)
.split(/\r?\n/)
.reduce((prev, curr) => {
Expand Down Expand Up @@ -397,7 +354,7 @@ export class CreateApi {
const headers = await this.headers();

let data: string =
typeof content === 'string' ? content : Utf8ArrayToStr(content);
typeof content === 'string' ? content : unit8ArrayToString(content);
data = await this.toggleSecretsInclude(posixPath, data, 'remove');

const payload = { data: btoa(data) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { CreateUri } from './create-uri';
import { SketchesService } from '../../common/protocol';
import { ArduinoPreferences } from '../arduino-preferences';
import { Create } from './typings';
import { stringToUint8Array } from '../../common/utils';

@injectable()
export class CreateFsProvider
Expand Down Expand Up @@ -154,7 +155,7 @@ export class CreateFsProvider

async readFile(uri: URI): Promise<Uint8Array> {
const content = await this.getCreateApi.readFile(uri.path.toString());
return new TextEncoder().encode(content);
return stringToUint8Array(content);
}

async writeFile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import {
} from '@theia/core/shared/inversify';
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
import { ArduinoDaemon } from '../../../common/protocol';
import { assertUnreachable } from '../../../common/utils';
import { CreateFeatures } from '../../create/create-features';
import { NotificationCenter } from '../../notification-center';
import debounce = require('lodash.debounce');
import isOnline = require('is-online');
import { CreateFeatures } from '../../create/create-features';

@injectable()
export class IsOnline implements FrontendApplicationContribution {
Expand Down Expand Up @@ -321,8 +322,3 @@ function getOfflineTooltip(statusType: OfflineConnectionStatus): string {
assertUnreachable(statusType);
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function assertUnreachable(_: never): never {
throw new Error();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,19 @@ import {
import { CloudSketchbookCommands } from './cloud-sketchbook-commands';
import { DoNotAskAgainConfirmDialog } from '../../dialogs/do-not-ask-again-dialog';
import { SketchbookTree } from '../sketchbook/sketchbook-tree';
import { firstToUpperCase } from '../../../common/utils';
import { assertUnreachable } from '../../../common/utils';
import { FileStat } from '@theia/filesystem/lib/common/files';
import { WorkspaceNode } from '@theia/navigator/lib/browser/navigator-tree';
import { posix, splitSketchPath } from '../../create/create-paths';
import { Create } from '../../create/typings';
import { nls } from '@theia/core/lib/common';
import { ApplicationConnectionStatusContribution } from '../../theia/core/connection-status-service';
import { ExecuteWithProgress } from '../../../common/protocol/progressible';
import {
synchronizingSketchbook,
pullingSketch,
pushingSketch,
} from '../../contributions/cloud-contribution';

const MESSAGE_TIMEOUT = 5 * 1000;
const deepmerge = require('deepmerge').default;
Expand Down Expand Up @@ -327,7 +332,7 @@ export class CloudSketchbookTree extends SketchbookTree {
): Promise<T> {
const name = node.uri.path.name;
return ExecuteWithProgress.withProgress(
`${firstToUpperCase(state)} '${name}'`,
this.taskMessage(state, name),
this.messageService,
async (progress) => {
progress.report({ work: { done: 0, total: NaN } });
Expand All @@ -336,6 +341,22 @@ export class CloudSketchbookTree extends SketchbookTree {
);
}

private taskMessage(
state: CloudSketchbookTree.CloudSketchDirNode.State,
input: string
): string {
switch (state) {
case 'syncing':
return synchronizingSketchbook;
case 'pulling':
return pullingSketch(input);
case 'pushing':
return pushingSketch(input);
default:
assertUnreachable(state);
}
}

private async sync(source: URI, dest: URI): Promise<void> {
const { filesToWrite, filesToDelete } = await this.treeDiff(source, dest);
await Promise.all(
Expand Down
18 changes: 18 additions & 0 deletions arduino-ide-extension/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,21 @@ export function startsWithUpperCase(what: string): boolean {
export function isNullOrUndefined(what: unknown): what is undefined | null {
return what === undefined || what === null;
}

// Use it for and exhaustive `switch` statements
// https://stackoverflow.com/a/39419171/5529090
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function assertUnreachable(_: never): never {
throw new Error();
}

// Text encoder can crash in electron browser: https://github.com/arduino/arduino-ide/issues/634#issuecomment-1440039171
export function unit8ArrayToString(uint8Array: Uint8Array): string {
return uint8Array.reduce(
(text, byte) => text + String.fromCharCode(byte),
''
);
}
export function stringToUint8Array(text: string): Uint8Array {
return Uint8Array.from(text, (char) => char.charCodeAt(0));
}

0 comments on commit 23906d9

Please sign in to comment.