From f17e70472f8cbd3121f01fd3fa40a33599f6ab08 Mon Sep 17 00:00:00 2001
From: Maddy <12754347+MaddyDev@users.noreply.github.com>
Date: Wed, 9 Feb 2022 13:36:23 -0800
Subject: [PATCH] check for encoded components (#18231)
* check for encoded components
* address comments
* relocate and simplify test
* move encodeUrl to notebookLinkHandler
* added comments
* add containsEncodedUri to network.ts
---
src/sql/base/common/network.ts | 14 ++++++++++
.../cellViews/markdownToolbar.component.ts | 4 +--
.../notebook/browser/notebookLinkHandler.ts | 19 +++++++++++++
.../contrib/notebook/browser/utils.ts | 1 -
.../test/browser/notebookLinkHandler.test.ts | 27 +++++++++++++++++++
5 files changed, 62 insertions(+), 3 deletions(-)
create mode 100644 src/sql/base/common/network.ts
diff --git a/src/sql/base/common/network.ts b/src/sql/base/common/network.ts
new file mode 100644
index 0000000000..2010f992c9
--- /dev/null
+++ b/src/sql/base/common/network.ts
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+/**
+* Checks if the specified URL is already URI-encoded by checking if there are any unencoded reserved URI component characters
+* (such as ?, =, &, /, etc.) in the URL. See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent for more details.
+* @returns true if the URL contains encoded URI component reserved characters
+*/
+export function containsEncodedUriComponentReservedCharacters(url: string): boolean {
+ // ie ?,=,&,/ etc
+ return (decodeURI(url) !== decodeURIComponent(url));
+}
diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts
index 1ae06dca39..d2f1540faf 100644
--- a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts
@@ -278,8 +278,8 @@ export class MarkdownToolbarComponent extends AngularDisposable {
// Otherwise, re-focus on the output element, and insert the link directly.
this.output?.nativeElement?.focus();
// Need to encode URI here in order for user to click the proper encoded link in WYSIWYG
- let encodedLinkURL = encodeURI(linkUrl);
- document.execCommand('insertHTML', false, `${escape(linkCalloutResult?.insertUnescapedLinkLabel)}`);
+ let encodedLinkURL = notebookLink.getEncodedLinkUrl();
+ document.execCommand('insertHTML', false, `${escape(linkCalloutResult?.insertUnescapedLinkLabel)}`);
return;
}
} else if (type === MarkdownButtonType.IMAGE_PREVIEW) {
diff --git a/src/sql/workbench/contrib/notebook/browser/notebookLinkHandler.ts b/src/sql/workbench/contrib/notebook/browser/notebookLinkHandler.ts
index a52920513e..4a3f81d8a4 100644
--- a/src/sql/workbench/contrib/notebook/browser/notebookLinkHandler.ts
+++ b/src/sql/workbench/contrib/notebook/browser/notebookLinkHandler.ts
@@ -8,6 +8,7 @@ import * as path from 'vs/base/common/path';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { replaceInvalidLinkPath } from 'sql/workbench/contrib/notebook/common/utils';
import { isWindows } from 'vs/base/common/platform';
+import { containsEncodedUriComponentReservedCharacters } from 'sql/base/common/network';
const useAbsolutePathConfigName = 'notebook.useAbsoluteFilePaths';
@@ -118,6 +119,24 @@ export class NotebookLinkHandler {
}
}
+ /**
+ * Function to get encoded url, Gets the link URI-encoded link URL
+ * (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
+ * @returns the encoded url
+ */
+ public getEncodedLinkUrl(): string | undefined {
+ if (typeof this._link === 'string') {
+ // Need to encode URI here in order for user to click the proper encoded link in WYSIWYG
+ // skip encoding it if it's already encoded
+ if (!containsEncodedUriComponentReservedCharacters(this._link)) {
+ return encodeURI(this._link);
+ }
+ return this._link;
+ }
+ // since we only handle strings that come from call out dialogs
+ return undefined;
+ }
+
/**
* Creates a URI for for a link with a anchor (#)
* @param node is the HTMLAnchorElement of the target notebook
diff --git a/src/sql/workbench/contrib/notebook/browser/utils.ts b/src/sql/workbench/contrib/notebook/browser/utils.ts
index 284265894a..a71d49b016 100644
--- a/src/sql/workbench/contrib/notebook/browser/utils.ts
+++ b/src/sql/workbench/contrib/notebook/browser/utils.ts
@@ -43,4 +43,3 @@ export function highlightSelectedText(): void {
document.execCommand('hiliteColor', false, 'Yellow');
}
}
-
diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookLinkHandler.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookLinkHandler.test.ts
index aaaf480c34..5f042f8e54 100644
--- a/src/sql/workbench/contrib/notebook/test/browser/notebookLinkHandler.test.ts
+++ b/src/sql/workbench/contrib/notebook/test/browser/notebookLinkHandler.test.ts
@@ -103,4 +103,31 @@ suite('Noteboook Link Handler', function (): void {
result = new NotebookLinkHandler(notebookUri, Object.assign(document.createElement('a'), { href: '/tmp/my%20stuff.png' }), configurationService);
assert.strictEqual(result.getLinkUrl(), `.${path.sep}my%2520stuff.png`, 'Basic link test with %20 filename failed');
});
+
+ test('Should return correctly encoded url/filePath', () => {
+ test('when given an already-encoded URL', () => {
+ let notebookLinkHandler = new NotebookLinkHandler(notebookUri, 'https://github.com/search/advanced?q=test&r=microsoft%2Fazuredatastudio&type=Code', configurationService);
+ assert.strictEqual(notebookLinkHandler.getEncodedLinkUrl(), `https://github.com/search/advanced?q=test&r=microsoft%2Fazuredatastudio&type=Code`, 'HTTPS link does not need encoding');
+ });
+
+ test('when given an already encoded URL with non-reserved characters', () => {
+ let notebookLinkHandler = new NotebookLinkHandler(notebookUri, 'https://github.com/search/advanced?q=test&r=(microsoft%2Fazuredatastudio)&type=Code', configurationService);
+ assert.strictEqual(notebookLinkHandler.getEncodedLinkUrl(), `https://github.com/search/advanced?q=test&r=(microsoft%2Fazuredatastudio)&type=Code`, '() in HTTP link should not be encoded');
+ });
+
+ test('when given an unencoded URL with a space', () => {
+ let notebookLinkHandler = new NotebookLinkHandler(notebookUri, 'https://github.com/search/advanced?q=test&r=(microsoft/azuredata studio)&type=Code', configurationService);
+ assert.strictEqual(notebookLinkHandler.getEncodedLinkUrl(), `https://github.com/search/advanced?q=test&r=(microsoft/azuredata%20studio)&type=Code`, 'space in the url failed to be encoded');
+ });
+
+ test('when given file path with a space', () => {
+ let notebookLinkHandler = new NotebookLinkHandler(notebookUri, '/Notebooks/Test_Paths/My File.ipynb', configurationService);
+ assert.strictEqual(notebookLinkHandler.getEncodedLinkUrl(), `/Notebooks/Test_Paths/My%20File.ipynb`, 'space in file path failed to be encoded');
+ });
+
+ test('when given file path has special characters such as %', () => {
+ let notebookLinkHandler = new NotebookLinkHandler(notebookUri, '/Notebooks/Test_Paths/My%20File.ipynb', configurationService);
+ assert.strictEqual(notebookLinkHandler.getEncodedLinkUrl(), `/Notebooks/Test_Paths/My%2520File.ipynb`, '% in file path failed to be encoded');
+ });
+ });
});