From e84558d908f0dfacbf7658aaa7191a237b011aae Mon Sep 17 00:00:00 2001 From: nilaoda Date: Tue, 20 Sep 2022 22:51:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=A3=E7=90=86=E3=80=81?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89HLS=20KEY=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/N_m3u8DL-RE.Common/Resource/ResString.cs | 4 ++ src/N_m3u8DL-RE.Common/Resource/StaticText.cs | 56 +++++++++++++------ src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs | 7 ++- src/N_m3u8DL-RE.Common/Util/HexUtil.cs | 19 +++++++ .../Extractor/HLSExtractor.cs | 12 ---- .../Processor/HLS/DefaultHLSKeyProcessor.cs | 15 ++++- src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs | 44 ++++++++++++++- src/N_m3u8DL-RE/CommandLine/MyOption.cs | 19 ++++++- src/N_m3u8DL-RE/Program.cs | 10 +++- src/N_m3u8DL-RE/Util/DownloadUtil.cs | 11 +--- src/N_m3u8DL-RE/Util/OtherUtil.cs | 4 +- 11 files changed, 153 insertions(+), 48 deletions(-) diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index 50610ca..01b7f9b 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -36,6 +36,9 @@ namespace N_m3u8DL_RE.Common.Resource public static string cmd_selectAudio_more { get => GetText("cmd_selectAudio_more"); } public static string cmd_selectSubtitle { get => GetText("cmd_selectSubtitle"); } public static string cmd_selectSubtitle_more { get => GetText("cmd_selectSubtitle_more"); } + public static string cmd_customHLSMethod { get => GetText("cmd_customHLSMethod"); } + public static string cmd_customHLSKey { get => GetText("cmd_customHLSKey"); } + public static string cmd_customHLSIv { get => GetText("cmd_customHLSIv"); } public static string cmd_Input { get => GetText("cmd_Input"); } public static string cmd_keys { get => GetText("cmd_keys"); } public static string cmd_keyText { get => GetText("cmd_keyText"); } @@ -57,6 +60,7 @@ namespace N_m3u8DL_RE.Common.Resource public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); } public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); } public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); } + public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); } public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); } public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); } public static string cmd_liveRealTimeMerge { get => GetText("cmd_liveRealTimeMerge"); } diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index ca22b31..674bcdf 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -244,17 +244,41 @@ namespace N_m3u8DL_RE.Common.Resource zhTW: "錄製直播時即時合併", enUS: "Real-time merge into file when recording live" ), + ["cmd_useSystemProxy"] = new TextContainer + ( + zhCN: "使用系统默认代理", + zhTW: "使用系統默認代理", + enUS: "Use system default proxy" + ), ["cmd_livePerformAsVod"] = new TextContainer ( zhCN: "以点播方式下载直播流", zhTW: "以點播方式下載直播流", enUS: "Download live streams as vod" ), + ["cmd_customHLSMethod"] = new TextContainer + ( + zhCN: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)", + zhTW: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)", + enUS: "Set HLS encryption method (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)" + ), + ["cmd_customHLSKey"] = new TextContainer + ( + zhCN: "指定HLS解密KEY. 可以是文件, HEX或Base64", + zhTW: "指定HLS解密KEY. 可以是文件, HEX或Base64", + enUS: "Set the HLS decryption key. Can be file, HEX or Base64" + ), + ["cmd_customHLSIv"] = new TextContainer + ( + zhCN: "指定HLS解密IV. 可以是文件, HEX或Base64", + zhTW: "指定HLS解密IV. 可以是文件, HEX或Base64", + enUS: "Set the HLS decryption iv. Can be file, HEX or Base64" + ), ["cmd_liveKeepSegments"] = new TextContainer ( zhCN: "录制直播并开启实时合并时依然保留分片", zhTW: "錄製直播並開啟即時合併時依然保留分片", - enUS: "Keep segments when recording a live broadcast and enable liveRealTimeMerge" + enUS: "Keep segments when recording a live (liveRealTimeMerge enabled)" ), ["cmd_liveRecordLimit"] = new TextContainer ( @@ -276,9 +300,9 @@ namespace N_m3u8DL_RE.Common.Resource ), ["cmd_selectVideo"] = new TextContainer ( - zhCN: "通过正则表达式选择符合要求的视频流. 输入 \"--morehelp select-video\" 以查看详细信息.", - zhTW: "通過正則表達式選擇符合要求的影片軌. 輸入 \"--morehelp select-video\" 以查看詳細訊息.", - enUS: "Select video streams by regular expressions. Use \"--morehelp select-video\" for more details." + zhCN: "通过正则表达式选择符合要求的视频流. 输入 \"--morehelp select-video\" 以查看详细信息", + zhTW: "通過正則表達式選擇符合要求的影片軌. 輸入 \"--morehelp select-video\" 以查看詳細訊息", + enUS: "Select video streams by regular expressions. Use \"--morehelp select-video\" for more details" ), ["cmd_selectVideo_more"] = new TextContainer ( @@ -312,9 +336,9 @@ namespace N_m3u8DL_RE.Common.Resource ), ["cmd_selectAudio"] = new TextContainer ( - zhCN: "通过正则表达式选择符合要求的音频流. 输入 \"--morehelp select-audio\" 以查看详细信息.", - zhTW: "通過正則表達式選擇符合要求的音軌. 輸入 \"--morehelp select-audio\" 以查看詳細訊息.", - enUS: "Select audio streams by regular expressions. Use \"--morehelp select-audio\" for more details." + zhCN: "通过正则表达式选择符合要求的音频流. 输入 \"--morehelp select-audio\" 以查看详细信息", + zhTW: "通過正則表達式選擇符合要求的音軌. 輸入 \"--morehelp select-audio\" 以查看詳細訊息", + enUS: "Select audio streams by regular expressions. Use \"--morehelp select-audio\" for more details" ), ["cmd_selectAudio_more"] = new TextContainer ( @@ -345,9 +369,9 @@ namespace N_m3u8DL_RE.Common.Resource ), ["cmd_selectSubtitle"] = new TextContainer ( - zhCN: "通过正则表达式选择符合要求的字幕流. 输入 \"--morehelp select-subtitle\" 以查看详细信息.", - zhTW: "通過正則表達式選擇符合要求的字幕流. 輸入 \"--morehelp select-subtitle\" 以查看詳細訊息.", - enUS: "Select subtitle streams by regular expressions. Use \"--morehelp select-subtitle\" for more details." + zhCN: "通过正则表达式选择符合要求的字幕流. 输入 \"--morehelp select-subtitle\" 以查看详细信息", + zhTW: "通過正則表達式選擇符合要求的字幕流. 輸入 \"--morehelp select-subtitle\" 以查看詳細訊息", + enUS: "Select subtitle streams by regular expressions. Use \"--morehelp select-subtitle\" for more details" ), ["cmd_selectSubtitle_more"] = new TextContainer ( @@ -411,15 +435,15 @@ namespace N_m3u8DL_RE.Common.Resource ), ["cmd_muxAfterDone"] = new TextContainer ( - zhCN: "所有工作完成时尝试混流分离的音视频. 输入 \"--morehelp mux-after-done\" 以查看详细信息.", - zhTW: "所有工作完成時嘗試混流分離的影音. 輸入 \"--morehelp mux-after-done\" 以查看詳細訊息.", - enUS: "When all works is done, try to mux the downloaded streams. Use \"--morehelp mux-after-done\" for more details." + zhCN: "所有工作完成时尝试混流分离的音视频. 输入 \"--morehelp mux-after-done\" 以查看详细信息", + zhTW: "所有工作完成時嘗試混流分離的影音. 輸入 \"--morehelp mux-after-done\" 以查看詳細訊息", + enUS: "When all works is done, try to mux the downloaded streams. Use \"--morehelp mux-after-done\" for more details" ), ["cmd_muxImport"] = new TextContainer ( - zhCN: "混流时引入外部媒体文件. 输入 \"--morehelp mux-import\" 以查看详细信息.", - zhTW: "混流時引入外部媒體檔案. 輸入 \"--morehelp mux-import\" 以查看詳細訊息.", - enUS: "When MuxAfterDone enabled, allow to import local media files. Use \"--morehelp mux-import\" for more details." + zhCN: "混流时引入外部媒体文件. 输入 \"--morehelp mux-import\" 以查看详细信息", + zhTW: "混流時引入外部媒體檔案. 輸入 \"--morehelp mux-import\" 以查看詳細訊息", + enUS: "When MuxAfterDone enabled, allow to import local media files. Use \"--morehelp mux-import\" for more details" ), ["cmd_muxImport_more"] = new TextContainer ( diff --git a/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs b/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs index 46ee563..70098db 100644 --- a/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs +++ b/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs @@ -23,13 +23,14 @@ namespace N_m3u8DL_RE.Common.Util { public class HTTPUtil { - - public static readonly HttpClient AppHttpClient = new(new HttpClientHandler + public static readonly HttpClientHandler HttpClientHandler = new() { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.All, ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true - }) + }; + + public static readonly HttpClient AppHttpClient = new(HttpClientHandler) { Timeout = TimeSpan.FromMinutes(2) }; diff --git a/src/N_m3u8DL-RE.Common/Util/HexUtil.cs b/src/N_m3u8DL-RE.Common/Util/HexUtil.cs index e5f0b0b..20944d9 100644 --- a/src/N_m3u8DL-RE.Common/Util/HexUtil.cs +++ b/src/N_m3u8DL-RE.Common/Util/HexUtil.cs @@ -13,6 +13,25 @@ namespace N_m3u8DL_RE.Common.Util return BitConverter.ToString(data).Replace("-", split); } + /// + /// 判断是不是HEX字符串 + /// + /// + /// + public static bool TryParseHexString(string input, out byte[]? bytes) + { + bytes = null; + input = input.ToUpper(); + if (input.StartsWith("0X")) + input = input[2..]; + if (input.Length % 2 != 0) + return false; + if (input.Any(c => !"0123456789ABCDEF".Contains(c))) + return false; + bytes = HexToBytes(input); + return true; + } + public static byte[] HexToBytes(string hex) { hex = hex.Trim(); diff --git a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs index 2073674..95eae91 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs @@ -318,18 +318,6 @@ namespace N_m3u8DL_RE.Parser.Extractor //解析KEY else if (line.StartsWith(HLSTags.ext_x_key)) { - //自定义KEY情况 不读取当前行的KEY信息. - //对于IV,没自定义且当前行有IV的话 就用 - if (ParserConfig.CustomeKey != null) - { - currentEncryptInfo.Key = ParserConfig.CustomeKey; - if (ParserConfig.CustomeIV == null && line.Contains("IV=0x")) - currentEncryptInfo.IV = HexUtil.HexToBytes(ParserUtil.GetAttribute(line, "IV")); - continue; - } - - var iv = ParserUtil.GetAttribute(line, "IV"); - var method = ParserUtil.GetAttribute(line, "METHOD"); var uri = ParserUtil.GetAttribute(line, "URI"); var uri_last = ParserUtil.GetAttribute(lastKeyLine, "URI"); diff --git a/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs b/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs index 68d72f8..d1c7db7 100644 --- a/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs @@ -28,12 +28,21 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS Logger.Debug("METHOD:{},URI:{},IV:{}", method, uri, iv); var encryptInfo = new EncryptInfo(method); + + //处理自定义加密方式 + if (parserConfig.CustomMethod != null) + { + encryptInfo.Method = parserConfig.CustomMethod.Value; + Logger.Warn("METHOD changed to {}", method, encryptInfo.Method); + } + //IV if (!string.IsNullOrEmpty(iv)) { encryptInfo.IV = HexUtil.HexToBytes(iv); } - if (parserConfig.CustomeIV != null) + //自定义IV + if (parserConfig.CustomeIV != null && parserConfig.CustomeIV.Length > 0) { encryptInfo.IV = parserConfig.CustomeIV; } @@ -41,7 +50,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS //KEY try { - if (parserConfig.CustomeKey != null) + if (parserConfig.CustomeKey != null && parserConfig.CustomeKey.Length > 0) { encryptInfo.Key = parserConfig.CustomeKey; } @@ -75,7 +84,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS { Logger.WarnMarkUp($"[grey]{_ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]"); Thread.Sleep(1000); - if (retryCount > 0) goto getHttpKey; + if (retryCount-- > 0) goto getHttpKey; else throw; } } diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 4a8e8b3..d75a850 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -1,5 +1,7 @@ -using N_m3u8DL_RE.Common.Log; +using N_m3u8DL_RE.Common.Enum; +using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Resource; +using N_m3u8DL_RE.Common.Util; using N_m3u8DL_RE.Entity; using N_m3u8DL_RE.Enum; using N_m3u8DL_RE.Util; @@ -48,9 +50,18 @@ namespace N_m3u8DL_RE.CommandLine private readonly static Option BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl); private readonly static Option ConcurrentDownload = new(new string[] { "-mt", "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false); + //代理选项 + private readonly static Option UseSystemProxy = new(new string[] { "--use-system-proxy" }, description: ResString.cmd_useSystemProxy, getDefaultValue: () => true); + //morehelp private readonly static Option MoreHelp = new(new string[] { "--morehelp" }, description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" }; + //自定义KEY等 + private readonly static Option CustomHLSMethod = new(name: "--custom-hls-method", description: ResString.cmd_customHLSMethod) { ArgumentHelpName = "METHOD" }; + private readonly static Option CustomHLSKey = new(name: "--custom-hls-key", description: ResString.cmd_customHLSKey, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" }; + private readonly static Option CustomHLSIv = new(name: "--custom-hls-iv", description: ResString.cmd_customHLSIv, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" }; + + //直播相关 private readonly static Option LivePerformAsVod = new(new string[] { "--live-perform-as-vod" }, description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false); private readonly static Option LiveRealTimeMerge = new(new string[] { "--live-real-time-merge" }, description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false); @@ -65,6 +76,32 @@ namespace N_m3u8DL_RE.CommandLine private readonly static Option AudioFilter = new(new string[] { "-sa", "--select-audio" }, description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; private readonly static Option SubtitleFilter = new(new string[] { "-ss", "--select-subtitle" }, description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; + /// + /// 解析自定义KEY + /// + /// + /// + private static byte[]? ParseHLSCustomKey(ArgumentResult result) + { + var input = result.Tokens.First().Value; + try + { + if (string.IsNullOrEmpty(input)) + return null; + if (File.Exists(input)) + return File.ReadAllBytes(input); + else if (HexUtil.TryParseHexString(input, out byte[]? bytes)) + return bytes; + else + return Convert.FromBase64String(input); + } + catch (Exception) + { + result.ErrorMessage = "error in parse hls custom key: " + input; + return null; + } + } + /// /// 解析录制直播时长限制 /// @@ -302,6 +339,10 @@ namespace N_m3u8DL_RE.CommandLine LiveKeepSegments = bindingContext.ParseResult.GetValueForOption(LiveKeepSegments), LiveRecordLimit = bindingContext.ParseResult.GetValueForOption(LiveRecordLimit), LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod), + UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy), + CustomHLSMethod = bindingContext.ParseResult.GetValueForOption(CustomHLSMethod), + CustomHLSKey = bindingContext.ParseResult.GetValueForOption(CustomHLSKey), + CustomHLSIv = bindingContext.ParseResult.GetValueForOption(CustomHLSIv), }; var parsedHeaders = bindingContext.ParseResult.GetValueForOption(Headers); @@ -362,6 +403,7 @@ namespace N_m3u8DL_RE.CommandLine FFmpegBinaryPath, LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, MuxAfterDone, + CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LiveRecordLimit, MuxImports, VideoFilter, AudioFilter, SubtitleFilter, MoreHelp }; diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs index dc05bea..1b23b58 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -1,4 +1,5 @@ -using N_m3u8DL_RE.Common.Log; +using N_m3u8DL_RE.Common.Enum; +using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Entity; using N_m3u8DL_RE.Enum; @@ -123,6 +124,10 @@ namespace N_m3u8DL_RE.CommandLine /// public bool LivePerformAsVod { get; set; } /// + /// See: . + /// + public bool UseSystemProxy { get; set; } + /// /// See: . /// public SubtitleFormat SubtitleFormat { get; set; } @@ -174,6 +179,18 @@ namespace N_m3u8DL_RE.CommandLine /// See: . /// public StreamFilter? SubtitleFilter { get; set; } + /// + /// See: . + /// + public EncryptMethod? CustomHLSMethod { get; set; } + /// + /// See: . + /// + public byte[]? CustomHLSKey { get; set; } + /// + /// See: . + /// + public byte[]? CustomHLSIv { get; set; } public bool MuxKeepFiles { get; set; } } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index 646a59c..d4730b7 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -58,6 +58,11 @@ namespace N_m3u8DL_RE { Logger.LogLevel = option.LogLevel; + if (option.UseSystemProxy == false) + { + HTTPUtil.HttpClientHandler.UseProxy = false; + } + try { //检查互斥的选项 @@ -130,7 +135,10 @@ namespace N_m3u8DL_RE AppendUrlParams = option.AppendUrlParams, UrlProcessorArgs = option.UrlProcessorArgs, BaseUrl = option.BaseUrl!, - Headers = headers + Headers = headers, + CustomMethod = option.CustomHLSMethod, + CustomeKey = option.CustomHLSKey, + CustomeIV = option.CustomHLSIv, }; //demo1 diff --git a/src/N_m3u8DL-RE/Util/DownloadUtil.cs b/src/N_m3u8DL-RE/Util/DownloadUtil.cs index 8d1a4ea..70b2dae 100644 --- a/src/N_m3u8DL-RE/Util/DownloadUtil.cs +++ b/src/N_m3u8DL-RE/Util/DownloadUtil.cs @@ -1,5 +1,6 @@ using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Resource; +using N_m3u8DL_RE.Common.Util; using N_m3u8DL_RE.Entity; using System; using System.Collections.Generic; @@ -14,15 +15,7 @@ namespace N_m3u8DL_RE.Util { internal class DownloadUtil { - private static readonly HttpClient AppHttpClient = new(new HttpClientHandler - { - AllowAutoRedirect = false, - AutomaticDecompression = DecompressionMethods.All, - ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true - }) - { - Timeout = TimeSpan.FromMinutes(2) - }; + private static readonly HttpClient AppHttpClient = HTTPUtil.AppHttpClient; public static async Task DownloadToFileAsync(string url, string path, SpeedContainer speedContainer, Dictionary? headers = null, long? fromPosition = null, long? toPosition = null) { diff --git a/src/N_m3u8DL-RE/Util/OtherUtil.cs b/src/N_m3u8DL-RE/Util/OtherUtil.cs index f7091a8..59b729c 100644 --- a/src/N_m3u8DL-RE/Util/OtherUtil.cs +++ b/src/N_m3u8DL-RE/Util/OtherUtil.cs @@ -8,9 +8,9 @@ namespace N_m3u8DL_RE.Util { internal class OtherUtil { - public static Dictionary SplitHeaderArrayToDic(string[]? headers) + public static Dictionary SplitHeaderArrayToDic(string[]? headers) { - Dictionary dic = new(); + Dictionary dic = new(); if (headers != null) {