Skip to content

Commit d61802f

Browse files
committed
Escape path names during import/export. Skip redundant chdirs (#5770)
* Fix security issues. Change to using less instead of sass (as it has a tar dependency) * Fix import/export paths and remove redundant chdirs.
1 parent abff373 commit d61802f

5 files changed

Lines changed: 58 additions & 27 deletions

File tree

news/2 Fixes/5386.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix import/export paths to be escaped on windows.

src/client/datascience/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ export namespace Settings {
122122
}
123123

124124
export namespace CodeSnippits {
125-
export const ChangeDirectory = ['{0}', 'import os', 'try:', '\tos.chdir(os.path.join(os.getcwd(), \'{1}\'))', '\tprint(os.getcwd())', 'except:', '\tpass', ''];
125+
export const ChangeDirectory = ['{0}', '{1}', 'import os', 'try:', '\tos.chdir(os.path.join(os.getcwd(), \'{2}\'))', '\tprint(os.getcwd())', 'except:', '\tpass', ''];
126+
export const ChangeDirectoryCommentIdentifier = '# ms-python.python added'; // Not translated so can compare.
126127
}
127128

128129
export namespace Identifiers {

src/client/datascience/jupyter/jupyterExporter.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import * as path from 'path';
88
import * as uuid from 'uuid/v4';
99

1010
import { IWorkspaceService } from '../../common/application/types';
11-
import { IFileSystem } from '../../common/platform/types';
11+
import { IFileSystem, IPlatformService } from '../../common/platform/types';
1212
import { IConfigurationService, ILogger } from '../../common/types';
1313
import * as localize from '../../common/utils/localize';
1414
import { noop } from '../../common/utils/misc';
1515
import { CellMatcher } from '../cellMatcher';
16+
import { concatMultilineString } from '../common';
1617
import { CodeSnippits, Identifiers } from '../constants';
1718
import { CellState, ICell, IJupyterExecution, INotebookExporter, ISysInfo } from '../types';
1819

@@ -24,7 +25,9 @@ export class JupyterExporter implements INotebookExporter {
2425
@inject(ILogger) private logger: ILogger,
2526
@inject(IWorkspaceService) private workspaceService: IWorkspaceService,
2627
@inject(IConfigurationService) private configService: IConfigurationService,
27-
@inject(IFileSystem) private fileSystem: IFileSystem) {
28+
@inject(IFileSystem) private fileSystem: IFileSystem,
29+
@inject(IPlatformService) private readonly platform: IPlatformService
30+
) {
2831
}
2932

3033
public dispose() {
@@ -75,7 +78,7 @@ export class JupyterExporter implements INotebookExporter {
7578
const changeDirectory = await this.calculateDirectoryChange(file, cells);
7679

7780
if (changeDirectory) {
78-
const exportChangeDirectory = CodeSnippits.ChangeDirectory.join(os.EOL).format(localize.DataScience.exportChangeDirectoryComment(), changeDirectory);
81+
const exportChangeDirectory = CodeSnippits.ChangeDirectory.join(os.EOL).format(localize.DataScience.exportChangeDirectoryComment(), CodeSnippits.ChangeDirectoryCommentIdentifier, changeDirectory);
7982

8083
const cell: ICell = {
8184
data: {
@@ -117,21 +120,30 @@ export class JupyterExporter implements INotebookExporter {
117120
}
118121

119122
private calculateDirectoryChange = async (notebookFile: string, cells: ICell[]): Promise<string | undefined> => {
123+
// Make sure we don't already have a cell with a ChangeDirectory comment in it.
120124
let directoryChange: string | undefined;
121-
const notebookFilePath = path.dirname(notebookFile);
122-
// First see if we have a workspace open, this only works if we have a workspace root to be relative to
123-
if (this.workspaceService.hasWorkspaceFolders) {
124-
const workspacePath = await this.firstWorkspaceFolder(cells);
125-
126-
// Make sure that we have everything that we need here
127-
if (workspacePath && path.isAbsolute(workspacePath) && notebookFilePath && path.isAbsolute(notebookFilePath)) {
128-
directoryChange = path.relative(notebookFilePath, workspacePath);
125+
const haveChangeAlready = cells.find(c => concatMultilineString(c.data.source).includes(CodeSnippits.ChangeDirectoryCommentIdentifier));
126+
if (!haveChangeAlready) {
127+
const notebookFilePath = path.dirname(notebookFile);
128+
// First see if we have a workspace open, this only works if we have a workspace root to be relative to
129+
if (this.workspaceService.hasWorkspaceFolders) {
130+
const workspacePath = await this.firstWorkspaceFolder(cells);
131+
132+
// Make sure that we have everything that we need here
133+
if (workspacePath && path.isAbsolute(workspacePath) && notebookFilePath && path.isAbsolute(notebookFilePath)) {
134+
directoryChange = path.relative(notebookFilePath, workspacePath);
135+
}
129136
}
130137
}
131138

132139
// If path.relative can't calculate a relative path, then it just returns the full second path
133140
// so check here, we only want this if we were able to calculate a relative path, no network shares or drives
134141
if (directoryChange && !path.isAbsolute(directoryChange)) {
142+
// Escape windows path chars so they end up in the source escaped
143+
if (this.platform.isWindows) {
144+
directoryChange = directoryChange.replace('\\', '\\\\');
145+
}
146+
135147
return directoryChange;
136148
} else {
137149
return undefined;

src/client/datascience/jupyter/jupyterImporter.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as os from 'os';
77
import * as path from 'path';
88

99
import { IWorkspaceService } from '../../common/application/types';
10-
import { IFileSystem } from '../../common/platform/types';
10+
import { IFileSystem, IPlatformService } from '../../common/platform/types';
1111
import { IConfigurationService, IDisposableRegistry } from '../../common/types';
1212
import * as localize from '../../common/utils/localize';
1313
import { noop } from '../../common/utils/misc';
@@ -38,7 +38,9 @@ export class JupyterImporter implements INotebookImporter {
3838
@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry,
3939
@inject(IConfigurationService) private configuration: IConfigurationService,
4040
@inject(IJupyterExecution) private jupyterExecution: IJupyterExecution,
41-
@inject(IWorkspaceService) private workspaceService: IWorkspaceService) {
41+
@inject(IWorkspaceService) private workspaceService: IWorkspaceService,
42+
@inject(IPlatformService) private readonly platform: IPlatformService
43+
) {
4244
this.templatePromise = this.createTemplateFile();
4345
}
4446

@@ -49,7 +51,7 @@ export class JupyterImporter implements INotebookImporter {
4951
const settings = this.configuration.getSettings();
5052
let directoryChange: string | undefined;
5153
if (settings.datascience.changeDirOnImportExport) {
52-
directoryChange = this.calculateDirectoryChange(file);
54+
directoryChange = await this.calculateDirectoryChange(file);
5355
}
5456

5557
// Use the jupyter nbconvert functionality to turn the notebook into a python file
@@ -70,27 +72,39 @@ export class JupyterImporter implements INotebookImporter {
7072
}
7173

7274
private addDirectoryChange = (pythonOutput: string, directoryChange: string): string => {
73-
const newCode = CodeSnippits.ChangeDirectory.join(os.EOL).format(localize.DataScience.importChangeDirectoryComment(), directoryChange);
75+
const newCode = CodeSnippits.ChangeDirectory.join(os.EOL).format(localize.DataScience.importChangeDirectoryComment(), CodeSnippits.ChangeDirectoryCommentIdentifier, directoryChange);
7476
return newCode.concat(pythonOutput);
7577
}
7678

7779
// When importing a file, calculate if we can create a %cd so that the relative paths work
78-
private calculateDirectoryChange = (notebookFile: string): string | undefined => {
80+
private async calculateDirectoryChange(notebookFile: string): Promise<string | undefined> {
7981
let directoryChange: string | undefined;
80-
const notebookFilePath = path.dirname(notebookFile);
81-
// First see if we have a workspace open, this only works if we have a workspace root to be relative to
82-
if (this.workspaceService.hasWorkspaceFolders) {
83-
const workspacePath = this.workspaceService.workspaceFolders![0].uri.fsPath;
84-
85-
// Make sure that we have everything that we need here
86-
if (workspacePath && path.isAbsolute(workspacePath) && notebookFilePath && path.isAbsolute(notebookFilePath)) {
87-
directoryChange = path.relative(workspacePath, notebookFilePath);
82+
// Make sure we don't already have an import/export comment in the file
83+
const contents = await this.fileSystem.readFile(notebookFile);
84+
const haveChangeAlready = contents.includes(CodeSnippits.ChangeDirectoryCommentIdentifier);
85+
86+
if (!haveChangeAlready) {
87+
const notebookFilePath = path.dirname(notebookFile);
88+
// First see if we have a workspace open, this only works if we have a workspace root to be relative to
89+
if (this.workspaceService.hasWorkspaceFolders) {
90+
const workspacePath = this.workspaceService.workspaceFolders![0].uri.fsPath;
91+
92+
// Make sure that we have everything that we need here
93+
if (workspacePath && path.isAbsolute(workspacePath) && notebookFilePath && path.isAbsolute(notebookFilePath)) {
94+
directoryChange = path.relative(workspacePath, notebookFilePath);
95+
}
8896
}
8997
}
9098

9199
// If path.relative can't calculate a relative path, then it just returns the full second path
92100
// so check here, we only want this if we were able to calculate a relative path, no network shares or drives
93101
if (directoryChange && !path.isAbsolute(directoryChange)) {
102+
103+
// Escape windows path chars so they end up in the source escaped
104+
if (this.platform.isWindows) {
105+
directoryChange = directoryChange.replace('\\', '\\\\');
106+
}
107+
94108
return directoryChange;
95109
} else {
96110
return undefined;

src/test/datascience/notebook.functional.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,11 @@ suite('DataScience notebook tests', () => {
354354
// Try importing this. This should verify export works and that importing is possible
355355
const results = await importer.importFromFile(temp.filePath);
356356

357-
// Make sure we added a chdir into our results
358-
assert.ok(results.includes('os.chdir'));
357+
// Make sure we have a single chdir in our results
358+
const first = results.indexOf('os.chdir');
359+
assert.ok(first >= 0, 'No os.chdir in import');
360+
const second = results.indexOf('os.chdir', first + 1);
361+
assert.equal(second, -1, 'More than one chdir in the import. It should be skipped');
359362

360363
// Make sure we have a cell in our results
361364
assert.ok(/#\s*%%/.test(results), 'No cells in returned import');

0 commit comments

Comments
 (0)