Change sanitized characters to support Windows paths (#1529)

Fixed #1526
This commit is contained in:
Riccardo
2025-10-03 13:21:08 +02:00
committed by GitHub
parent 40740db416
commit 54a6ffdf01
4 changed files with 110 additions and 13 deletions

View File

@@ -1,6 +1,28 @@
import { asAbsolutePaths } from './path';
import { asAbsolutePaths, fromFsPath } from './path';
describe('path utils', () => {
describe('fromFsPath', () => {
it('should normalize backslashes in relative paths', () => {
const [path] = fromFsPath('areas\\dailies\\2024\\file.md');
expect(path).toBe('areas/dailies/2024/file.md');
});
it('should handle mixed separators in relative paths', () => {
const [path] = fromFsPath('areas/dailies\\2024/file.md');
expect(path).toBe('areas/dailies/2024/file.md');
});
it('should preserve forward slashes in relative paths', () => {
const [path] = fromFsPath('areas/dailies/2024/file.md');
expect(path).toBe('areas/dailies/2024/file.md');
});
it('should normalize backslashes in Windows absolute paths', () => {
const [path] = fromFsPath('C:\\workspace\\file.md');
expect(path).toBe('/C:/workspace/file.md');
});
});
describe('asAbsolutePaths', () => {
it('returns the path if already absolute', () => {
const paths = asAbsolutePaths('/path/to/test', [

View File

@@ -16,13 +16,16 @@ export function fromFsPath(path: string): [string, string] {
let authority: string;
if (isUNCShare(path)) {
[path, authority] = parseUNCShare(path);
path = path.replace(/\\/g, '/');
} else if (hasDrive(path)) {
path = '/' + path[0].toUpperCase() + path.substr(1).replace(/\\/g, '/');
path = '/' + path[0].toUpperCase() + path.substr(1);
} else if (path[0] === '/' && hasDrive(path, 1)) {
// POSIX representation of a Windows path: just normalize drive letter case
path = '/' + path[1].toUpperCase() + path.substr(2);
}
// Always normalize backslashes to forward slashes (filesystem → POSIX)
path = path.replace(/\\/g, '/');
return [path, authority];
}

View File

@@ -493,17 +493,17 @@ foam_template:
'foam-vscode.create-note'
);
// Title with many invalid characters (excluding / which is preserved for directories): \#%&{}<>?*$!'":@+`|=
// Title with many invalid characters
const resolver = new Resolver(new Map(), new Date());
resolver.define('FOAM_TITLE', 'Test\\#%&{}<>?*$!\'"Title:@+`|=');
resolver.define('FOAM_TITLE', 'Test#%&{}<>?*$!\'"Title@+`|=');
const result = await engine.processTemplate(trigger, template, resolver);
// All invalid characters should become dashes: Test + 14 invalid chars + Title + : + @+`|= (6 more total)
expect(result.filepath.path).toBe('Test--------------Title------.md');
// All invalid characters should become dashes
expect(result.filepath.path).toBe('Test-------------Title-----.md');
// Content should remain unchanged
expect(result.content).toContain('# Test\\#%&{}<>?*$!\'"Title:@+`|=');
expect(result.content).toContain('# Test#%&{}<>?*$!\'"Title@+`|=');
});
it('should not affect FOAM_TITLE when not used in filepath', async () => {
@@ -569,7 +569,7 @@ Date and title combination.`,
const result = await engine.processTemplate(trigger, template, resolver);
// Entire resolved filepath should be sanitized
expect(result.filepath.path).toBe('2024/03/Note-With-Invalid-Chars.md');
expect(result.filepath.path).toBe('2024/03/Note:With-Invalid-Chars.md');
// Content should use original FOAM_TITLE
expect(result.content).toContain('# Note:With|Invalid*Chars');
@@ -601,5 +601,78 @@ foam_template:
expect(result.filepath.path).toBe('notes/ValidTitle123.md');
expect(result.content).toContain('# ValidTitle123');
});
it('should preserve backslashes as directory separators (Windows-style paths)', async () => {
const { engine } = await setupFoamEngine();
// Simulate a resolved filepath with Windows-style backslash separators
const template: Template = {
type: 'markdown',
content: `# MyNote`,
metadata: new Map([
['filepath', 'areas\\dailies\\2024\\MyNote.md'], // Already resolved, has backslashes
]),
};
const trigger = TriggerFactory.createCommandTrigger(
'foam-vscode.create-note'
);
const resolver = new Resolver(new Map(), new Date());
const result = await engine.processTemplate(trigger, template, resolver);
// Backslashes should be normalized to forward slashes
expect(result.filepath.path).toBe('areas/dailies/2024/MyNote.md');
expect(result.content).toContain('# MyNote');
});
it('should normalize mixed forward and backslashes', async () => {
const { engine } = await setupFoamEngine();
// Simulate a resolved filepath with mixed separators
const template: Template = {
type: 'markdown',
content: `# MyNote`,
metadata: new Map([
['filepath', 'areas/dailies\\2024/MyNote.md'], // Mixed separators
]),
};
const trigger = TriggerFactory.createCommandTrigger(
'foam-vscode.create-note'
);
const resolver = new Resolver(new Map(), new Date());
const result = await engine.processTemplate(trigger, template, resolver);
// Both separators should be normalized to forward slashes
expect(result.filepath.path).toBe('areas/dailies/2024/MyNote.md');
expect(result.content).toContain('# MyNote');
});
it('should sanitize invalid characters while normalizing backslash separators', async () => {
const { engine } = await setupFoamEngine();
// Simulate a resolved filepath with backslash separator and invalid chars
const template: Template = {
type: 'markdown',
content: `# Note:With*Invalid`,
metadata: new Map([['filepath', 'areas\\Note:With*Invalid.md']]), // Backslash + invalid chars
};
const trigger = TriggerFactory.createCommandTrigger(
'foam-vscode.create-note'
);
const resolver = new Resolver(new Map(), new Date());
const result = await engine.processTemplate(trigger, template, resolver);
// Backslash normalized to forward slash, invalid chars sanitized
expect(result.filepath.path).toBe('areas/Note:With-Invalid.md');
expect(result.content).toContain('# Note:With*Invalid');
});
});
});

View File

@@ -14,14 +14,13 @@ import { URI } from '../core/model/uri';
/**
* Characters that are invalid in file names
* Based on UNALLOWED_CHARS from variable-resolver.ts but excluding forward slash
* which is needed for directory separators in filepaths
* Based on UNALLOWED_CHARS from variable-resolver.ts but excluding filepaths
* related chars
*/
const FILEPATH_UNALLOWED_CHARS = '\\#%&{}<>?*$!\'":@+`|=';
const FILEPATH_UNALLOWED_CHARS = '#%&{}<>?*$!\'"@+`|=';
/**
* Sanitizes a filepath by replacing invalid characters with dashes
* Note: Forward slashes (/) are preserved for directory separators
* @param filepath The filepath to sanitize
* @returns The sanitized filepath
*/