From 8c3ef51fbda9c4ddbaa232916ba82cd211315cef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Dec 2021 14:28:38 -0800 Subject: [PATCH] Do not throw for user paths that contain a quote character in them. --- .../AbstractPersistentStorageTests.cs | 37 ++++++++++++++++--- .../SQLiteV2PersistentStorageTests.cs | 2 +- .../SQLite/v2/Interop/SqlConnection.cs | 8 +++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index df60f4ed036e7..e66d827f69592 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -129,6 +129,30 @@ public async Task TestNullFilePaths() Assert.Null(await storage.ReadStreamAsync(document, streamName)); } + [Theory, CombinatorialData, WorkItem(1436188, "https://devdiv.visualstudio.com/DevDiv/_queries/edit/1436188")] + public async Task CacheDirectoryInPathWithSingleQuote(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration) + { + _ = iteration; + using var folderRoot = new DisposableDirectory(new TempRoot()); + var folder = folderRoot.CreateDirectory(PersistentFolderPrefix + "'" + Guid.NewGuid()); + var solution = CreateOrOpenSolution(folder); + + var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1"; + var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2"; + + await using (var storage = await GetStorageAsync(solution, folder)) + { + Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); + Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); + } + + await using (var storage = await GetStorageAsync(solution, folder)) + { + Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); + Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); + } + } + [Theory, CombinatorialData] public async Task PersistentService_Solution_WriteReadDifferentInstances(Size size, bool withChecksum, [CombinatorialRange(0, Iterations)] int iteration) { @@ -911,9 +935,10 @@ private void DoSimultaneousWrites(Func write) Assert.Empty(exceptions); } - protected Solution CreateOrOpenSolution(bool nullPaths = false) + protected Solution CreateOrOpenSolution(TempDirectory? persistentFolder = null, bool nullPaths = false) { - var solutionFile = _persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText(""); + persistentFolder ??= _persistentFolder; + var solutionFile = persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText(""); var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(), solutionFile.Path); @@ -922,12 +947,12 @@ protected Solution CreateOrOpenSolution(bool nullPaths = false) var solution = workspace.CurrentSolution; - var projectFile = _persistentFolder.CreateOrOpenFile("Project1.csproj").WriteAllText(""); + var projectFile = persistentFolder.CreateOrOpenFile("Project1.csproj").WriteAllText(""); solution = solution.AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "Project1", "Project1", LanguageNames.CSharp, filePath: nullPaths ? null : projectFile.Path)); var project = solution.Projects.Single(); - var documentFile = _persistentFolder.CreateOrOpenFile("Document1.cs").WriteAllText(""); + var documentFile = persistentFolder.CreateOrOpenFile("Document1.cs").WriteAllText(""); solution = solution.AddDocument(DocumentInfo.Create(DocumentId.CreateNewId(project.Id), "Document1", filePath: nullPaths ? null : documentFile.Path)); @@ -939,12 +964,14 @@ protected Solution CreateOrOpenSolution(bool nullPaths = false) internal async Task GetStorageAsync( Solution solution, + TempDirectory? persistentFolder = null, IPersistentStorageFaultInjector? faultInjector = null, bool throwOnFailure = true) { // If we handed out one for a previous test, we need to shut that down first + persistentFolder ??= _persistentFolder; _storageService?.GetTestAccessor().Shutdown(); - var configuration = new MockPersistentStorageConfiguration(solution.Id, _persistentFolder.Path, throwOnFailure); + var configuration = new MockPersistentStorageConfiguration(solution.Id, persistentFolder.Path, throwOnFailure); _storageService = GetStorageService((IMefHostExportProvider)solution.Workspace.Services.HostServices, configuration, faultInjector, _persistentFolder.Path); var storage = await _storageService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None); diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs index d6845be5adaea..ef6a87a205418 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs @@ -46,7 +46,7 @@ public async Task TestCrashInNewConnection() // Because instantiating the connection will fail, we will not get back // a working persistent storage. We are testing a fault recovery code path. - await using (var storage = await GetStorageAsync(solution, faultInjector, throwOnFailure: false)) + await using (var storage = await GetStorageAsync(solution, faultInjector: faultInjector, throwOnFailure: false)) using (var memStream = new MemoryStream()) using (var streamWriter = new StreamWriter(memStream)) { diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs index 7a191298ed363..3d69836813508 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs @@ -126,8 +126,14 @@ public static SqlConnection Create(IPersistentStorageFaultInjector? faultInjecto // uri of the DB on disk we're associating this in-memory cache with. This throws on at least OSX for // reasons that aren't fully understood yet. If more details/fixes emerge in that linked issue, we can // ideally remove this and perform the attachment uniformly on all platforms. + + // From: https://www.sqlite.org/lang_expr.html + // + // A string constant is formed by enclosing the string in single quotes ('). A single quote within the + // string can be encoded by putting two single quotes in a row - as in Pascal. C-style escapes using the + // backslash character are not supported because they are not standard SQL. var attachString = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? $"attach database '{new Uri(databasePath).AbsoluteUri}?mode=memory&cache=shared' as {Database.WriteCache.GetName()};" + ? $"attach database '{new Uri(databasePath.Replace("'", "''")).AbsoluteUri}?mode=memory&cache=shared' as {Database.WriteCache.GetName()};" : $"attach database 'file::memory:?cache=shared' as {Database.WriteCache.GetName()};"; connection.ExecuteCommand(attachString);