[Hosts] Handle hidden hosts file (#34308)

* handle hidden hosts file

* don't remove hidden attribute
This commit is contained in:
Davide Giacometti
2024-08-16 20:36:26 +02:00
committed by GitHub
parent 1cbf512ed0
commit 9af757f5ce
4 changed files with 50 additions and 11 deletions

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.IO;
using System.IO.Abstractions.TestingHelpers; using System.IO.Abstractions.TestingHelpers;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -248,25 +249,53 @@ namespace Hosts.Tests
{ {
var fileSystem = new CustomMockFileSystem(); var fileSystem = new CustomMockFileSystem();
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
var hostsFile = new MockFileData(string.Empty);
hostsFile.Attributes = System.IO.FileAttributes.ReadOnly; var hostsFile = new MockFileData(string.Empty)
{
Attributes = FileAttributes.ReadOnly,
};
fileSystem.AddFile(service.HostsFilePath, hostsFile); fileSystem.AddFile(service.HostsFilePath, hostsFile);
await Assert.ThrowsExceptionAsync<ReadOnlyHostsException>(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>())); await Assert.ThrowsExceptionAsync<ReadOnlyHostsException>(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>()));
} }
[TestMethod] [TestMethod]
public void Remove_ReadOnly() public void Remove_ReadOnly_Attribute()
{ {
var fileSystem = new CustomMockFileSystem(); var fileSystem = new CustomMockFileSystem();
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object); var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
var hostsFile = new MockFileData(string.Empty);
hostsFile.Attributes = System.IO.FileAttributes.ReadOnly; var hostsFile = new MockFileData(string.Empty)
{
Attributes = FileAttributes.ReadOnly,
};
fileSystem.AddFile(service.HostsFilePath, hostsFile); fileSystem.AddFile(service.HostsFilePath, hostsFile);
service.RemoveReadOnly(); service.RemoveReadOnlyAttribute();
var readOnly = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(System.IO.FileAttributes.ReadOnly);
var readOnly = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(FileAttributes.ReadOnly);
Assert.IsFalse(readOnly); Assert.IsFalse(readOnly);
} }
[TestMethod]
public async Task Save_Hidden_Hosts()
{
var fileSystem = new CustomMockFileSystem();
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
var hostsFile = new MockFileData(string.Empty)
{
Attributes = FileAttributes.Hidden,
};
fileSystem.AddFile(service.HostsFilePath, hostsFile);
await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>());
var hidden = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(FileAttributes.Hidden);
Assert.IsTrue(hidden);
}
} }
} }

View File

@@ -23,6 +23,7 @@ namespace HostsUILib.Helpers
public class HostsService : IHostsService, IDisposable public class HostsService : IHostsService, IDisposable
{ {
private const string _backupSuffix = $"_PowerToysBackup_"; private const string _backupSuffix = $"_PowerToysBackup_";
private const int _defaultBufferSize = 4096; // From System.IO.File source code
private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@@ -197,7 +198,16 @@ namespace HostsUILib.Helpers
_backupDone = true; _backupDone = true;
} }
await _fileSystem.File.WriteAllLinesAsync(HostsFilePath, lines, Encoding); // FileMode.OpenOrCreate is necessary to prevent UnauthorizedAccessException when the hosts file is hidden
using var stream = _fileSystem.FileStream.Create(HostsFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, _defaultBufferSize, FileOptions.Asynchronous);
using var writer = new StreamWriter(stream, Encoding);
foreach (var line in lines)
{
await writer.WriteLineAsync(line.AsMemory());
}
stream.SetLength(stream.Position);
await writer.FlushAsync();
} }
finally finally
{ {
@@ -292,7 +302,7 @@ namespace HostsUILib.Helpers
} }
} }
public void RemoveReadOnly() public void RemoveReadOnlyAttribute()
{ {
var fileInfo = _fileSystem.FileInfo.FromFileName(HostsFilePath); var fileInfo = _fileSystem.FileInfo.FromFileName(HostsFilePath);
if (fileInfo.IsReadOnly) if (fileInfo.IsReadOnly)

View File

@@ -25,6 +25,6 @@ namespace HostsUILib.Helpers
void OpenHostsFile(); void OpenHostsFile();
void RemoveReadOnly(); void RemoveReadOnlyAttribute();
} }
} }

View File

@@ -283,7 +283,7 @@ namespace HostsUILib.ViewModels
[RelayCommand] [RelayCommand]
public void OverwriteHosts() public void OverwriteHosts()
{ {
_hostsService.RemoveReadOnly(); _hostsService.RemoveReadOnlyAttribute();
_ = Task.Run(SaveAsync); _ = Task.Run(SaveAsync);
} }