chore(all): resolve lint findings, update example plugin, refine docs and add LGPL licenses

This commit is contained in:
2026-10-07 00:57:35 +08:00
parent e8e9d77974
commit 8e8ddeddb6
388 changed files with 4638 additions and 3321 deletions
+23 -25
View File
@@ -12,13 +12,16 @@ namespace Bd2LocalIdentity;
internal sealed class LocalResourceServer : IDisposable
{
private static readonly string[] HeaderSeparators = ["\r\n"];
private static readonly char[] PathSeparators = ['/'];
private static readonly char[] QuerySeparators = ['?', '#'];
private const int MaximumRequestHeaderBytes = 16 * 1024;
private const int MaximumConcurrentRequests = 16;
private readonly ManualLogSource log;
private readonly TcpListener listener;
private readonly Thread acceptThread;
private readonly Semaphore requestSlots = new Semaphore(MaximumConcurrentRequests, MaximumConcurrentRequests);
private readonly Semaphore requestSlots = new(MaximumConcurrentRequests, MaximumConcurrentRequests);
private volatile bool disposed;
private LocalResourceServer(ManualLogSource log, string rootDirectory)
@@ -48,7 +51,7 @@ internal sealed class LocalResourceServer : IDisposable
string gameDataVersion)
{
string root = ValidateRoot(configuredDirectory, bundleVersion, gameDataVersion);
LocalResourceServer server = new LocalResourceServer(log, root);
var server = new LocalResourceServer(log, root);
log.LogInfo("Local resource HTTP server listening on " + server.Origin);
return server;
}
@@ -182,7 +185,7 @@ internal sealed class LocalResourceServer : IDisposable
return;
}
string[] lines = header.Split(new[] { "\r\n" }, StringSplitOptions.None);
string[] lines = header.Split(HeaderSeparators, StringSplitOptions.None);
string[] requestLine = lines[0].Split(' ');
if (requestLine.Length != 3 || (requestLine[2] != "HTTP/1.1" && requestLine[2] != "HTTP/1.0"))
{
@@ -204,7 +207,7 @@ internal sealed class LocalResourceServer : IDisposable
return;
}
using FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
using var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
long start = 0;
long end = file.Length - 1;
bool partial = false;
@@ -219,7 +222,7 @@ internal sealed class LocalResourceServer : IDisposable
}
long contentLength = file.Length == 0 ? 0 : end - start + 1;
StringBuilder response = new StringBuilder();
var response = new StringBuilder();
response.Append(partial ? "HTTP/1.1 206 Partial Content\r\n" : "HTTP/1.1 200 OK\r\n");
response.Append("Content-Length: ").Append(contentLength).Append("\r\n");
response.Append("Content-Type: ").Append(GetContentType(path)).Append("\r\n");
@@ -253,8 +256,8 @@ internal sealed class LocalResourceServer : IDisposable
private string ResolvePath(string requestTarget)
{
int query = requestTarget.IndexOfAny(new[] { '?', '#' });
string rawPath = query >= 0 ? requestTarget.Substring(0, query) : requestTarget;
int query = requestTarget.IndexOfAny(QuerySeparators);
string rawPath = query >= 0 ? requestTarget[..query] : requestTarget;
string decoded;
try
{
@@ -264,19 +267,19 @@ internal sealed class LocalResourceServer : IDisposable
{
return null;
}
if (decoded.IndexOf('\0') >= 0)
if (decoded.Contains('\0'))
{
return null;
}
string[] parts = decoded.Replace('\\', '/').Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
string[] parts = decoded.Replace('\\', '/').Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2 || (parts[0] != "ServerData" && parts[0] != "GameData"))
{
return null;
}
foreach (string part in parts)
{
if (part == "." || part == ".." || part.IndexOf(':') >= 0)
if (part == "." || part == ".." || part.Contains(':'))
{
return null;
}
@@ -320,7 +323,7 @@ internal sealed class LocalResourceServer : IDisposable
return false;
}
private static string ReadRequestHeader(Stream stream)
private static string ReadRequestHeader(NetworkStream stream)
{
byte[] bytes = new byte[MaximumRequestHeaderBytes];
int count = 0;
@@ -343,7 +346,7 @@ internal sealed class LocalResourceServer : IDisposable
private static Dictionary<string, string> ParseHeaders(string[] lines)
{
Dictionary<string, string> result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (int i = 1; i < lines.Length; i++)
{
int separator = lines[i].IndexOf(':');
@@ -351,7 +354,7 @@ internal sealed class LocalResourceServer : IDisposable
{
continue;
}
result[lines[i].Substring(0, separator).Trim()] = lines[i].Substring(separator + 1).Trim();
result[lines[i][..separator].Trim()] = lines[i][(separator + 1)..].Trim();
}
return result;
}
@@ -360,11 +363,11 @@ internal sealed class LocalResourceServer : IDisposable
{
start = 0;
end = length - 1;
if (length == 0 || !value.StartsWith("bytes=", StringComparison.OrdinalIgnoreCase) || value.IndexOf(',') >= 0)
if (length == 0 || !value.StartsWith("bytes=", StringComparison.OrdinalIgnoreCase) || value.Contains(','))
{
return false;
}
string[] bounds = value.Substring(6).Split('-');
string[] bounds = value[6..].Split('-');
if (bounds.Length != 2)
{
return false;
@@ -393,17 +396,12 @@ internal sealed class LocalResourceServer : IDisposable
private static string GetContentType(string path)
{
switch (Path.GetExtension(path).ToLowerInvariant())
return Path.GetExtension(path).ToLowerInvariant() switch
{
case ".json":
return "application/json; charset=utf-8";
case ".hash":
case ".info":
case ".version":
return "text/plain; charset=utf-8";
default:
return "application/octet-stream";
}
".json" => "application/json; charset=utf-8",
".hash" or ".info" or ".version" => "text/plain; charset=utf-8",
_ => "application/octet-stream",
};
}
private static void WriteError(Stream stream, int status, string reason, string additionalHeaders = "")