mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
Fix UTF-8 BOM in dnsmasq config/hosts writes so dnsmasq does not fail
- Add DnsmasqFileEncoding.Utf8NoBom for all config and hosts file writes - Strip BOM from first line when reading main config and managed config - Prevents 'bad option at line 1' when dnsmasq loads files written by the app
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DnsmasqWebUI.Infrastructure;
|
||||||
|
|
||||||
|
/// <summary>Encoding for dnsmasq config and hosts files. UTF-8 without BOM so dnsmasq does not see a BOM as "bad option" on line 1.</summary>
|
||||||
|
public static class DnsmasqFileEncoding
|
||||||
|
{
|
||||||
|
public static readonly Encoding Utf8NoBom = new UTF8Encoding(false);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using DnsmasqWebUI.Infrastructure;
|
||||||
using DnsmasqWebUI.Models.Config;
|
using DnsmasqWebUI.Models.Config;
|
||||||
using DnsmasqWebUI.Models.Dhcp;
|
using DnsmasqWebUI.Models.Dhcp;
|
||||||
using DnsmasqWebUI.Models.EffectiveConfig;
|
using DnsmasqWebUI.Models.EffectiveConfig;
|
||||||
@@ -82,7 +83,7 @@ public class DnsmasqConfigService : IDnsmasqConfigService
|
|||||||
var dir = Path.GetDirectoryName(managedHostsPath);
|
var dir = Path.GetDirectoryName(managedHostsPath);
|
||||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||||
Directory.CreateDirectory(dir);
|
Directory.CreateDirectory(dir);
|
||||||
File.WriteAllText(managedHostsPath, "");
|
File.WriteAllText(managedHostsPath, "", DnsmasqFileEncoding.Utf8NoBom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteDhcpHostsAsync(IReadOnlyList<DhcpHostEntry> entries, CancellationToken ct = default)
|
public async Task WriteDhcpHostsAsync(IReadOnlyList<DhcpHostEntry> entries, CancellationToken ct = default)
|
||||||
@@ -110,7 +111,10 @@ public class DnsmasqConfigService : IDnsmasqConfigService
|
|||||||
|
|
||||||
IReadOnlyList<string> rawLines;
|
IReadOnlyList<string> rawLines;
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
rawLines = await File.ReadAllLinesAsync(path, Encoding.UTF8, ct);
|
{
|
||||||
|
var read = await File.ReadAllLinesAsync(path, DnsmasqFileEncoding.Utf8NoBom, ct);
|
||||||
|
rawLines = StripBomFromFirstLine(read);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
rawLines = Array.Empty<string>();
|
rawLines = Array.Empty<string>();
|
||||||
|
|
||||||
@@ -136,7 +140,7 @@ public class DnsmasqConfigService : IDnsmasqConfigService
|
|||||||
output.Add(DnsmasqConfDhcpHostLineParser.ToLine(entry));
|
output.Add(DnsmasqConfDhcpHostLineParser.ToLine(entry));
|
||||||
|
|
||||||
var tmpPath = path + ".tmp";
|
var tmpPath = path + ".tmp";
|
||||||
await File.WriteAllLinesAsync(tmpPath, output, Encoding.UTF8, ct);
|
await File.WriteAllLinesAsync(tmpPath, output, DnsmasqFileEncoding.Utf8NoBom, ct);
|
||||||
File.Move(tmpPath, path, overwrite: true);
|
File.Move(tmpPath, path, overwrite: true);
|
||||||
EnsureManagedHostsFileExists(set.ManagedHostsFilePath);
|
EnsureManagedHostsFileExists(set.ManagedHostsFilePath);
|
||||||
var effectiveHostsPathDhcp = configLines.OfType<AddnHostsLine>().FirstOrDefault()?.AddnHostsPath ?? "";
|
var effectiveHostsPathDhcp = configLines.OfType<AddnHostsLine>().FirstOrDefault()?.AddnHostsPath ?? "";
|
||||||
@@ -152,7 +156,7 @@ public class DnsmasqConfigService : IDnsmasqConfigService
|
|||||||
foreach (var file in set.Files.Where(f => !f.IsManaged && !string.Equals(f.Path, managedPath, StringComparison.Ordinal)))
|
foreach (var file in set.Files.Where(f => !f.IsManaged && !string.Equals(f.Path, managedPath, StringComparison.Ordinal)))
|
||||||
{
|
{
|
||||||
if (!File.Exists(file.Path)) continue;
|
if (!File.Exists(file.Path)) continue;
|
||||||
var lines = await File.ReadAllLinesAsync(file.Path, Encoding.UTF8, ct);
|
var lines = await File.ReadAllLinesAsync(file.Path, DnsmasqFileEncoding.Utf8NoBom, ct);
|
||||||
var configLines = DnsmasqConfFileLineParser.ParseFile(lines);
|
var configLines = DnsmasqConfFileLineParser.ParseFile(lines);
|
||||||
foreach (var dhcp in configLines.OfType<DhcpHostLine>().Select(c => c.DhcpHost))
|
foreach (var dhcp in configLines.OfType<DhcpHostLine>().Select(c => c.DhcpHost))
|
||||||
{
|
{
|
||||||
@@ -191,11 +195,25 @@ public class DnsmasqConfigService : IDnsmasqConfigService
|
|||||||
var output = list.Select(DnsmasqConfFileLineParser.ToLine).ToList();
|
var output = list.Select(DnsmasqConfFileLineParser.ToLine).ToList();
|
||||||
|
|
||||||
var tmpPath = path + ".tmp";
|
var tmpPath = path + ".tmp";
|
||||||
await File.WriteAllLinesAsync(tmpPath, output, Encoding.UTF8, ct);
|
await File.WriteAllLinesAsync(tmpPath, output, DnsmasqFileEncoding.Utf8NoBom, ct);
|
||||||
File.Move(tmpPath, path, overwrite: true);
|
File.Move(tmpPath, path, overwrite: true);
|
||||||
EnsureManagedHostsFileExists(set.ManagedHostsFilePath);
|
EnsureManagedHostsFileExists(set.ManagedHostsFilePath);
|
||||||
var effectiveHostsPath = list.OfType<AddnHostsLine>().FirstOrDefault()?.AddnHostsPath ?? "";
|
var effectiveHostsPath = list.OfType<AddnHostsLine>().FirstOrDefault()?.AddnHostsPath ?? "";
|
||||||
_configSetCache.NotifyWeWroteManagedConfig(new ManagedConfigContent(list, effectiveHostsPath));
|
_configSetCache.NotifyWeWroteManagedConfig(new ManagedConfigContent(list, effectiveHostsPath));
|
||||||
_logger.LogInformation("Wrote managed config file: {Path}", path);
|
_logger.LogInformation("Wrote managed config file: {Path}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Strip UTF-8 BOM from first line if present.</summary>
|
||||||
|
private static IReadOnlyList<string> StripBomFromFirstLine(IReadOnlyList<string> lines)
|
||||||
|
{
|
||||||
|
if (lines.Count == 0) return lines;
|
||||||
|
var first = lines[0];
|
||||||
|
if (first.Length > 0 && first[0] == '\uFEFF')
|
||||||
|
{
|
||||||
|
var list = lines.ToList();
|
||||||
|
list[0] = first[1..];
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using DnsmasqWebUI.Infrastructure;
|
||||||
using DnsmasqWebUI.Models.Config;
|
using DnsmasqWebUI.Models.Config;
|
||||||
using DnsmasqWebUI.Models.EffectiveConfig;
|
using DnsmasqWebUI.Models.EffectiveConfig;
|
||||||
using DnsmasqWebUI.Configuration;
|
using DnsmasqWebUI.Configuration;
|
||||||
@@ -48,8 +49,9 @@ public class EnsureManagedConfigHostedService : IApplicationHostedService
|
|||||||
var confFileLine = "conf-file=" + Path.GetRelativePath(mainDir, managedPath);
|
var confFileLine = "conf-file=" + Path.GetRelativePath(mainDir, managedPath);
|
||||||
|
|
||||||
var lines = File.Exists(mainFull)
|
var lines = File.Exists(mainFull)
|
||||||
? (await File.ReadAllLinesAsync(mainFull, Encoding.UTF8, cancellationToken)).ToList()
|
? (await File.ReadAllLinesAsync(mainFull, DnsmasqFileEncoding.Utf8NoBom, cancellationToken)).ToList()
|
||||||
: new List<string>();
|
: new List<string>();
|
||||||
|
StripBomFromFirstLine(lines);
|
||||||
|
|
||||||
// Remove any conf-file= line that points to our managed path (we will add it as the last line).
|
// Remove any conf-file= line that points to our managed path (we will add it as the last line).
|
||||||
for (var i = lines.Count - 1; i >= 0; i--)
|
for (var i = lines.Count - 1; i >= 0; i--)
|
||||||
@@ -75,12 +77,12 @@ public class EnsureManagedConfigHostedService : IApplicationHostedService
|
|||||||
if (lines.Count > 0 && !string.IsNullOrWhiteSpace(lines[^1]))
|
if (lines.Count > 0 && !string.IsNullOrWhiteSpace(lines[^1]))
|
||||||
lines.Add("");
|
lines.Add("");
|
||||||
lines.Add(confFileLine);
|
lines.Add(confFileLine);
|
||||||
await File.WriteAllLinesAsync(mainFull, lines, Encoding.UTF8, cancellationToken);
|
await File.WriteAllLinesAsync(mainFull, lines, DnsmasqFileEncoding.Utf8NoBom, cancellationToken);
|
||||||
_logger.LogInformation("Set {Line} as the last line of main config {Path} so the managed file is included only by conf-file=.", confFileLine, mainFull);
|
_logger.LogInformation("Set {Line} as the last line of main config {Path} so the managed file is included only by conf-file=.", confFileLine, mainFull);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await File.WriteAllLinesAsync(mainFull, lines, Encoding.UTF8, cancellationToken);
|
await File.WriteAllLinesAsync(mainFull, lines, DnsmasqFileEncoding.Utf8NoBom, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(managedDir);
|
Directory.CreateDirectory(managedDir);
|
||||||
@@ -92,5 +94,14 @@ public class EnsureManagedConfigHostedService : IApplicationHostedService
|
|||||||
await configService.WriteManagedConfigAsync(Array.Empty<DnsmasqConfLine>(), cancellationToken);
|
await configService.WriteManagedConfigAsync(Array.Empty<DnsmasqConfLine>(), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Strip UTF-8 BOM from first line if present, so we do not perpetuate it when writing.</summary>
|
||||||
|
private static void StripBomFromFirstLine(List<string> lines)
|
||||||
|
{
|
||||||
|
if (lines.Count == 0) return;
|
||||||
|
var first = lines[0];
|
||||||
|
if (first.Length > 0 && first[0] == '\uFEFF')
|
||||||
|
lines[0] = first[1..];
|
||||||
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Text;
|
using DnsmasqWebUI.Infrastructure;
|
||||||
using DnsmasqWebUI.Models.Hosts;
|
using DnsmasqWebUI.Models.Hosts;
|
||||||
using DnsmasqWebUI.Parsers;
|
using DnsmasqWebUI.Parsers;
|
||||||
using DnsmasqWebUI.Services.Abstractions;
|
using DnsmasqWebUI.Services.Abstractions;
|
||||||
@@ -36,7 +36,7 @@ public class HostsFileService : IHostsFileService
|
|||||||
|
|
||||||
var tmpPath = path + ".tmp";
|
var tmpPath = path + ".tmp";
|
||||||
var lines = entries.Select(HostsFileLineParser.ToLine).ToList();
|
var lines = entries.Select(HostsFileLineParser.ToLine).ToList();
|
||||||
await File.WriteAllLinesAsync(tmpPath, lines, Encoding.UTF8, ct);
|
await File.WriteAllLinesAsync(tmpPath, lines, DnsmasqFileEncoding.Utf8NoBom, ct);
|
||||||
File.Move(tmpPath, path, overwrite: true);
|
File.Move(tmpPath, path, overwrite: true);
|
||||||
_hostsCache.NotifyWeWroteManagedHosts(entries);
|
_hostsCache.NotifyWeWroteManagedHosts(entries);
|
||||||
_logger.LogInformation("Wrote managed hosts file: {Path}", path);
|
_logger.LogInformation("Wrote managed hosts file: {Path}", path);
|
||||||
|
|||||||
Reference in New Issue
Block a user