From d42f6ba07aa1a4e4f7ffe09acc812c0aba571120 Mon Sep 17 00:00:00 2001 From: RMBGAME Date: Tue, 10 Mar 2026 20:55:31 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=98=AD=20=E6=B2=A1=E6=9C=89=E5=8A=9E?= =?UTF-8?q?=E6=B3=95=E5=8F=AA=E8=83=BD=E7=89=B9=E6=AE=8A=E5=A4=84=E7=90=86?= =?UTF-8?q?github=E8=84=9A=E6=9C=AC=E6=B3=A8=E5=85=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ddleware.FindScriptInjectInsertPosition.cs | 118 ++++++++++++++++++ .../Middleware/HttpReverseProxyMiddleware.cs | 16 ++- .../HttpReverseProxyMiddlewareUnitTest.cs | 51 ++++++++ 3 files changed, 184 insertions(+), 1 deletion(-) diff --git a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.FindScriptInjectInsertPosition.cs b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.FindScriptInjectInsertPosition.cs index bf334d4cd..5704113ac 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.FindScriptInjectInsertPosition.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.FindScriptInjectInsertPosition.cs @@ -90,4 +90,122 @@ partial class HttpReverseProxyMiddleware notfound: insertPosition = -1; return false; } + + internal static bool FindScriptInjectInsertPositionForGithub(byte[] buffer_, Encoding encoding, out ReadOnlyMemory buffer, out int insertPosition) + { + buffer = buffer_.AsMemory(); + + ReadOnlySpan mark = "= mark.Length) + { + for (var i = 0; i <= span.Length - mark.Length; i++) + { + if (!EqualsAsciiIgnoreCase(span.Slice(i, mark.Length), mark)) + { + continue; + } + + var afterMarkIndex = i + mark.Length; + if (afterMarkIndex < span.Length && IsHtmlAttributeNameChar(span[afterMarkIndex])) + { + continue; + } + + var tagEndOffset = span.Slice(afterMarkIndex).IndexOf((byte)'>'); + if (tagEndOffset < 0) + { + break; + } + + var tagEndIndex = afterMarkIndex + tagEndOffset; + var scriptTag = span.Slice(i, tagEndIndex - i + 1); + if (ScriptTagHasSrcAttribute(scriptTag)) + { + lastScriptWithSrcStart = i; + } + + i = tagEndIndex; + } + } + + if (lastScriptWithSrcStart >= 0) + { + insertPosition = lastScriptWithSrcStart; + return true; + } + + return FindScriptInjectInsertPosition(buffer_, encoding, out buffer, out insertPosition); + } + + static bool ScriptTagHasSrcAttribute(ReadOnlySpan scriptTag) + { + ReadOnlySpan src = "src"u8; + + if (scriptTag.Length < src.Length) + return false; + + for (var i = 0; i <= scriptTag.Length - src.Length; i++) + { + if (!EqualsAsciiIgnoreCase(scriptTag.Slice(i, src.Length), src)) + continue; + + if (i > 0 && IsHtmlAttributeNameChar(scriptTag[i - 1])) + continue; + + var j = i + src.Length; + while (j < scriptTag.Length && IsAsciiWhitespace(scriptTag[j])) + { + j++; + } + + if (j < scriptTag.Length && scriptTag[j] == (byte)'=') + { + return true; + } + } + + return false; + } + + static bool EqualsAsciiIgnoreCase(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + return false; + + for (var i = 0; i < left.Length; i++) + { + var l = ToUpperAscii(left[i]); + var r = ToUpperAscii(right[i]); + if (l != r) + return false; + } + + return true; + } + + static byte ToUpperAscii(byte value) + { + if (value is >= (byte)'a' and <= (byte)'z') + return (byte)(value - 32); + return value; + } + + static bool IsAsciiWhitespace(byte value) => + value == (byte)' ' || + value == (byte)'\t' || + value == (byte)'\r' || + value == (byte)'\n' || + value == (byte)'\f'; + + static bool IsHtmlAttributeNameChar(byte value) => + (value is >= (byte)'a' and <= (byte)'z') || + (value is >= (byte)'A' and <= (byte)'Z') || + (value is >= (byte)'0' and <= (byte)'9') || + value == (byte)'-' || + value == (byte)'_' || + value == (byte)':' || + value == (byte)'.'; } \ No newline at end of file diff --git a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.cs b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.cs index f836eb2f4..5770f6c4c 100644 --- a/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.cs +++ b/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/HttpReverseProxyMiddleware.cs @@ -327,7 +327,12 @@ Actual value was {actualValue}. } } - if (FindScriptInjectInsertPosition(buffer_, encoding, out var buffer, out var position)) + var isGithubHost = IsGithubHost(context.Request.Host.Host); + var isFindPosition = isGithubHost + ? FindScriptInjectInsertPositionForGithub(buffer_, encoding, out var buffer, out var position) + : FindScriptInjectInsertPosition(buffer_, encoding, out buffer, out position); + + if (isFindPosition) { using var bodyWriter = new MemoryStream(); using Stream? bodyCompress = GetStreamByContentCompression(bodyWriter, contentCompression, CompressionMode.Compress, true); @@ -398,6 +403,15 @@ Actual value was {actualValue}. context.Response.Body = originalBody; } } + + static bool IsGithubHost(string? host) + { + if (string.IsNullOrWhiteSpace(host)) + return false; + + return host.Equals("github.com", StringComparison.OrdinalIgnoreCase) || + host.EndsWith(".github.com", StringComparison.OrdinalIgnoreCase); + } } static Stream? GetStreamByContentCompression(Stream stream, string contentCompression, CompressionMode mode, bool leaveOpen) => contentCompression switch diff --git a/src/BD.WTTS.UnitTest/HttpReverseProxyMiddlewareUnitTest.cs b/src/BD.WTTS.UnitTest/HttpReverseProxyMiddlewareUnitTest.cs index 41812a36c..6b8a2de4e 100644 --- a/src/BD.WTTS.UnitTest/HttpReverseProxyMiddlewareUnitTest.cs +++ b/src/BD.WTTS.UnitTest/HttpReverseProxyMiddlewareUnitTest.cs @@ -46,4 +46,55 @@ public sealed class HttpReverseProxyMiddlewareUnitTest var new_html = encoding.GetString(s.ToArray()); TestContext.WriteLine(new_html); } + + [Test] + public void TestFindScriptInjectInsertPositionForGithub_BeforeLastScript() + { + var html = """ + + + + + demo + + + + + + + """; + + var buffer = Encoding.UTF8.GetBytes(html); + var ok = HttpReverseProxyMiddleware.FindScriptInjectInsertPositionForGithub(buffer, Encoding.UTF8, out _, out var position); + + Assert.That(ok, Is.True); + var expected = html.LastIndexOf(" + + + + + + """; + + var buffer = Encoding.UTF8.GetBytes(html); + var ok = HttpReverseProxyMiddleware.FindScriptInjectInsertPositionForGithub(buffer, Encoding.UTF8, out _, out var position); + + Assert.That(ok, Is.True); + var expected = html.LastIndexOf("