Skip to content

Commit

Permalink
fix: validate against trailing dot + reserved name
Browse files Browse the repository at this point in the history
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta committed Feb 13, 2023
1 parent ccbca88 commit fef3b6a
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 1 deletion.
1 change: 1 addition & 0 deletions arduino-ide-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"electron-updater": "^4.6.5",
"fast-json-stable-stringify": "^2.1.0",
"fast-safe-stringify": "^2.1.1",
"filename-reserved-regex": "^2.0.0",
"glob": "^7.1.6",
"google-protobuf": "^3.20.1",
"hash.js": "^1.1.7",
Expand Down
52 changes: 52 additions & 0 deletions arduino-ide-extension/src/common/protocol/sketches-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ApplicationError } from '@theia/core/lib/common/application-error';
import { nls } from '@theia/core/lib/common/nls';
import URI from '@theia/core/lib/common/uri';
import * as dateFormat from 'dateformat';
const filenameReservedRegex = require('filename-reserved-regex');

export namespace SketchesError {
export const Codes = {
Expand Down Expand Up @@ -160,6 +161,19 @@ export namespace Sketch {
// (non-API) exported for the tests
export const defaultFallbackChar = '_';
// (non-API) exported for the tests
export function reservedFilename(name: string): string {
return nls.localize(
'arduino/sketch/reservedFilename',
"'{0}' is a reserved filename.",
name
);
}
// (non-API) exported for the tests
export const noTrailingPeriod = nls.localize(
'arduino/sketch/noTrailingPeriod',
'A filename cannot end with a dot'
);
// (non-API) exported for the tests
export const invalidSketchFolderNameMessage = nls.localize(
'arduino/sketch/invalidSketchName',
'The name must start with a letter or number, followed by letters, numbers, dashes, dots and underscores. Maximum length is 63 characters.'
Expand All @@ -175,6 +189,10 @@ export namespace Sketch {
export function validateSketchFolderName(
candidate: string
): string | undefined {
const validFilenameError = isValidFilename(candidate);
if (validFilenameError) {
return validFilenameError;
}
return /^[0-9a-zA-Z]{1}[0-9a-zA-Z_\.-]{0,62}$/.test(candidate)
? undefined
: invalidSketchFolderNameMessage;
Expand All @@ -186,11 +204,36 @@ export namespace Sketch {
export function validateCloudSketchFolderName(
candidate: string
): string | undefined {
const validFilenameError = isValidFilename(candidate);
if (validFilenameError) {
return validFilenameError;
}
return /^[0-9a-zA-Z]{1}[0-9a-zA-Z_\.-]{0,35}$/.test(candidate)
? undefined
: invalidCloudSketchFolderNameMessage;
}

function isValidFilename(candidate: string): string | undefined {
if (isReservedFilename(candidate)) {
return reservedFilename(candidate);
}
if (endsWithPeriod(candidate)) {
return noTrailingPeriod;
}
return undefined;
}

function endsWithPeriod(candidate: string): boolean {
return candidate.length > 1 && candidate[candidate.length - 1] === '.';
}

function isReservedFilename(candidate: string): boolean {
return (
filenameReservedRegex().test(candidate) ||
filenameReservedRegex.windowsNames().test(candidate)
);
}

/**
* Transforms the `candidate` argument into a valid sketch folder name by replacing all invalid characters with underscore (`_`) and trimming the string after 63 characters.
* If the argument is falsy, returns with `"sketch"`.
Expand All @@ -202,6 +245,12 @@ export namespace Sketch {
*/
appendTimestampSuffix: boolean | Date = false
): string {
if (
!appendTimestampSuffix &&
filenameReservedRegex.windowsNames().test(candidate)
) {
return defaultSketchFolderName;
}
const validName = candidate
? candidate
.replace(/^[^0-9a-zA-Z]{1}/g, defaultFallbackFirstChar)
Expand Down Expand Up @@ -230,6 +279,9 @@ export namespace Sketch {
* Transforms the `candidate` argument into a valid cloud sketch folder name by replacing all invalid characters with underscore and trimming the string after 36 characters.
*/
export function toValidCloudSketchFolderName(candidate: string): string {
if (filenameReservedRegex.windowsNames().test(candidate)) {
return defaultSketchFolderName;
}
return candidate
? candidate
.replace(/^[^0-9a-zA-Z]{1}/g, defaultFallbackFirstChar)
Expand Down
42 changes: 41 additions & 1 deletion arduino-ide-extension/src/test/common/sketches-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
import { expect } from 'chai';
import { Sketch } from '../../common/protocol';

const windowsReservedFileNames = [
'CON',
'PRN',
'AUX',
'NUL',
'COM1',
'COM2',
'COM3',
'COM4',
'COM5',
'COM6',
'COM7',
'COM8',
'COM9',
'LPT1',
'LPT2',
'LPT3',
'LPT4',
'LPT5',
'LPT6',
'LPT7',
'LPT8',
'LPT9',
];
const windowsInvalidFilenames = ['trailingPeriod.', 'trailingSpace '];
const invalidFilenames = [
...windowsInvalidFilenames,
...windowsReservedFileNames,
].map((name) => <[string, boolean]>[name, false]);

describe('sketch', () => {
describe('validateSketchFolderName', () => {
(
[
...invalidFilenames,
['com1', false], // Do not assume case sensitivity. (https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions)
['sketch', true],
['can-contain-slash-and-dot.ino', true],
['regex++', false],
['dots...', true],
['trailing.dots...', false],
['no.trailing.dots.._', true],
['No Spaces', false],
['_invalidToStartWithUnderscore', false],
['Invalid+Char.ino', false],
Expand Down Expand Up @@ -42,6 +75,7 @@ describe('sketch', () => {
describe('validateCloudSketchFolderName', () => {
(
[
...invalidFilenames,
['sketch', true],
['can-contain-dashes', true],
['can.contain.dots', true],
Expand Down Expand Up @@ -83,6 +117,9 @@ describe('sketch', () => {
['foo bar', 'foo_bar'],
['_foobar', '0foobar'],
['vAlid', 'vAlid'],
['COM1', Sketch.defaultSketchFolderName],
['COM1.', 'COM1_'],
['period.', 'period_'],
].map(([input, expected]) =>
toMapIt(input, expected, Sketch.toValidSketchFolderName)
);
Expand Down Expand Up @@ -111,6 +148,9 @@ describe('sketch', () => {
['fooBar-', 'fooBar_' + epochSuffix],
['fooBar+', 'fooBar_' + epochSuffix],
['vAlid', 'vAlid' + epochSuffix],
['COM1', 'COM1' + epochSuffix],
['COM1.', 'COM1_' + epochSuffix],
['period.', 'period_' + epochSuffix],
].map(([input, expected]) =>
toMapIt(input, expected, (input: string) =>
Sketch.toValidSketchFolderName(input, epoch)
Expand Down
2 changes: 2 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,11 @@
"moving": "Moving",
"movingMsg": "The file \"{0}\" needs to be inside a sketch folder named \"{1}\".\nCreate this folder, move the file, and continue?",
"new": "New Sketch",
"noTrailingPeriod": "A filename cannot end with a dot",
"openFolder": "Open Folder",
"openRecent": "Open Recent",
"openSketchInNewWindow": "Open Sketch in New Window",
"reservedFilename": "'{0}' is a reserved filename.",
"saveFolderAs": "Save sketch folder as...",
"saveSketch": "Save your sketch to open it again later.",
"saveSketchAs": "Save sketch folder as...",
Expand Down

0 comments on commit fef3b6a

Please sign in to comment.