From 16e26a200e7f1349ba4bc4c16dc0fbee8fb51760 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Fri, 3 Nov 2023 17:10:26 +0100 Subject: [PATCH] [Hosts]Handle read-only hosts file (#29562) * handle read-only hosts file --- .../Hosts/Hosts.Tests/HostsServiceTest.cs | 55 +++++++++++++------ .../NotRunningElevatedException.cs | 2 +- .../Exceptions/ReadOnlyHostsException.cs | 12 ++++ .../Hosts/Hosts/Helpers/HostsService.cs | 15 +++++ .../Hosts/Hosts/Helpers/IHostsService.cs | 2 + .../Hosts/Hosts/HostsXAML/Views/MainPage.xaml | 15 ++++- .../Hosts/Hosts/Strings/en-us/Resources.resw | 7 +++ .../Hosts/Hosts/ViewModels/MainViewModel.cs | 19 +++++++ 8 files changed, 106 insertions(+), 21 deletions(-) rename src/modules/Hosts/Hosts/{Helpers => Exceptions}/NotRunningElevatedException.cs (91%) create mode 100644 src/modules/Hosts/Hosts/Exceptions/ReadOnlyHostsException.cs diff --git a/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs b/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs index c75c0ce1b6..d76beebd60 100644 --- a/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs +++ b/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs @@ -5,6 +5,7 @@ using System.IO.Abstractions.TestingHelpers; using System.Linq; using System.Threading.Tasks; +using Hosts.Exceptions; using Hosts.Helpers; using Hosts.Models; using Hosts.Settings; @@ -18,11 +19,13 @@ namespace Hosts.Tests [TestClass] public class HostsServiceTest { + private static Mock _userSettings; private static Mock _elevationHelper; [ClassInitialize] public static void ClassInitialize(TestContext context) { + _userSettings = new Mock(); _elevationHelper = new Mock(); _elevationHelper.Setup(m => m.IsElevated).Returns(true); } @@ -31,8 +34,7 @@ namespace Hosts.Tests public void Hosts_Exists() { var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty)); var result = service.Exists(); @@ -43,8 +45,7 @@ namespace Hosts.Tests public void Hosts_Not_Exists() { var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); var result = service.Exists(); Assert.IsFalse(result); @@ -65,8 +66,7 @@ namespace Hosts.Tests "; var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); fileSystem.AddFile(service.HostsFilePath, new MockFileData(content)); var data = await service.ReadAsync(); @@ -91,8 +91,7 @@ namespace Hosts.Tests "; var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); fileSystem.AddFile(service.HostsFilePath, new MockFileData(content)); var data = await service.ReadAsync(); @@ -118,8 +117,7 @@ namespace Hosts.Tests "; var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); fileSystem.AddFile(service.HostsFilePath, new MockFileData(content)); var data = await service.ReadAsync(); @@ -138,9 +136,7 @@ namespace Hosts.Tests public async Task Empty_Hosts() { var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); - - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty)); await service.WriteAsync(string.Empty, Enumerable.Empty()); @@ -203,7 +199,6 @@ namespace Hosts.Tests var fileSystem = new CustomMockFileSystem(); var userSettings = new Mock(); userSettings.Setup(m => m.AdditionalLinesPosition).Returns(HostsAdditionalLinesPosition.Bottom); - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); fileSystem.AddFile(service.HostsFilePath, new MockFileData(content)); @@ -228,8 +223,7 @@ namespace Hosts.Tests "; var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); - var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); fileSystem.AddFile(service.HostsFilePath, new MockFileData(content)); var data = await service.ReadAsync(); @@ -243,12 +237,37 @@ namespace Hosts.Tests public async Task Save_NotRunningElevatedException() { var fileSystem = new CustomMockFileSystem(); - var userSettings = new Mock(); var elevationHelper = new Mock(); elevationHelper.Setup(m => m.IsElevated).Returns(false); - var service = new HostsService(fileSystem, userSettings.Object, elevationHelper.Object); + var service = new HostsService(fileSystem, _userSettings.Object, elevationHelper.Object); await Assert.ThrowsExceptionAsync(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty())); } + + [TestMethod] + public async Task Save_ReadOnlyHostsException() + { + var fileSystem = new CustomMockFileSystem(); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); + var hostsFile = new MockFileData(string.Empty); + hostsFile.Attributes = System.IO.FileAttributes.ReadOnly; + fileSystem.AddFile(service.HostsFilePath, hostsFile); + + await Assert.ThrowsExceptionAsync(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty())); + } + + [TestMethod] + public void Remove_ReadOnly() + { + var fileSystem = new CustomMockFileSystem(); + var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); + var hostsFile = new MockFileData(string.Empty); + hostsFile.Attributes = System.IO.FileAttributes.ReadOnly; + fileSystem.AddFile(service.HostsFilePath, hostsFile); + + service.RemoveReadOnly(); + var readOnly = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(System.IO.FileAttributes.ReadOnly); + Assert.IsFalse(readOnly); + } } } diff --git a/src/modules/Hosts/Hosts/Helpers/NotRunningElevatedException.cs b/src/modules/Hosts/Hosts/Exceptions/NotRunningElevatedException.cs similarity index 91% rename from src/modules/Hosts/Hosts/Helpers/NotRunningElevatedException.cs rename to src/modules/Hosts/Hosts/Exceptions/NotRunningElevatedException.cs index 54d2aee710..4aa428a270 100644 --- a/src/modules/Hosts/Hosts/Helpers/NotRunningElevatedException.cs +++ b/src/modules/Hosts/Hosts/Exceptions/NotRunningElevatedException.cs @@ -4,7 +4,7 @@ using System; -namespace Hosts.Helpers +namespace Hosts.Exceptions { public class NotRunningElevatedException : Exception { diff --git a/src/modules/Hosts/Hosts/Exceptions/ReadOnlyHostsException.cs b/src/modules/Hosts/Hosts/Exceptions/ReadOnlyHostsException.cs new file mode 100644 index 0000000000..76685aac4a --- /dev/null +++ b/src/modules/Hosts/Hosts/Exceptions/ReadOnlyHostsException.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Hosts.Exceptions +{ + public class ReadOnlyHostsException : Exception + { + } +} diff --git a/src/modules/Hosts/Hosts/Helpers/HostsService.cs b/src/modules/Hosts/Hosts/Helpers/HostsService.cs index c6eaada406..ec58c149de 100644 --- a/src/modules/Hosts/Hosts/Helpers/HostsService.cs +++ b/src/modules/Hosts/Hosts/Helpers/HostsService.cs @@ -13,6 +13,7 @@ using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Threading.Tasks; +using Hosts.Exceptions; using Hosts.Models; using Hosts.Settings; using ManagedCommon; @@ -129,6 +130,11 @@ namespace Hosts.Helpers throw new NotRunningElevatedException(); } + if (_fileSystem.FileInfo.FromFileName(HostsFilePath).IsReadOnly) + { + throw new ReadOnlyHostsException(); + } + var lines = new List(); if (entries.Any()) @@ -288,6 +294,15 @@ namespace Hosts.Helpers } } + public void RemoveReadOnly() + { + var fileInfo = _fileSystem.FileInfo.FromFileName(HostsFilePath); + if (fileInfo.IsReadOnly) + { + fileInfo.IsReadOnly = false; + } + } + public void Dispose() { Dispose(disposing: true); diff --git a/src/modules/Hosts/Hosts/Helpers/IHostsService.cs b/src/modules/Hosts/Hosts/Helpers/IHostsService.cs index b9a14d1de8..cf35db07a1 100644 --- a/src/modules/Hosts/Hosts/Helpers/IHostsService.cs +++ b/src/modules/Hosts/Hosts/Helpers/IHostsService.cs @@ -24,5 +24,7 @@ namespace Hosts.Helpers void CleanupBackup(); void OpenHostsFile(); + + void RemoveReadOnly(); } } diff --git a/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml b/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml index 2486b708c0..27ddd09a71 100644 --- a/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml +++ b/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml @@ -377,7 +377,15 @@ IsOpen="{x:Bind ViewModel.Error, Mode=TwoWay}" Message="{x:Bind ViewModel.ErrorMessage, Mode=TwoWay}" Severity="Error" - Visibility="{x:Bind ViewModel.Error, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}}" /> + Visibility="{x:Bind ViewModel.Error, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}}"> + +