using System.Text.RegularExpressions; using CaddyManager.Contracts.Caddy; namespace CaddyManager.Services.Caddy; /// public partial class CaddyConfigurationParsingService: ICaddyConfigurationParsingService { /// /// Regex to help parse hostnames from a Caddyfile. /// This regex only matches hostname declarations at the beginning of lines (column 1 after optional whitespace) /// and excludes nested directives like "reverse_proxy target {". /// /// [GeneratedRegex(@"(?m)^([^\s\{\r\n][^\{\r\n]*?)\s*\{", RegexOptions.Multiline)] private static partial Regex HostnamesRegex(); /// /// Regex to help parse hostnames being used in reverse proxy directives. /// /// [GeneratedRegex(@"(?m)reverse_proxy\s+([^\s\{\}]+)(?:\s*\{)?", RegexOptions.Multiline)] private static partial Regex ReverseProxyRegex(); /// public List GetHostnamesFromCaddyfileContent(string caddyfileContent) { var hostnamesRegex = HostnamesRegex(); var matches = hostnamesRegex.Matches(caddyfileContent); var hostnames = new List(); foreach (Match match in matches) { // Split the matched string by commas and trim whitespace var splitHostnames = match.Groups[1].Value.Split(',') .Select(h => h.Trim()) .Where(h => !string.IsNullOrWhiteSpace(h)) .ToList(); hostnames.AddRange(splitHostnames); } // Remove duplicates and return the list return [.. hostnames.Distinct()]; } /// public string GetReverseProxyTargetFromCaddyfileContent(string caddyfileContent) { var reverseProxyRegex = ReverseProxyRegex(); var match = reverseProxyRegex.Match(caddyfileContent); if (!match.Success) return string.Empty; // Use the captured group which contains the target (e.g., pikachu:3011) var targetPart = match.Groups[1].Value.Trim(); if (string.IsNullOrEmpty(targetPart)) return string.Empty; var targetComponents = targetPart.Split(':'); if (targetComponents.Length <= 1) return targetPart; // Handle cases like http://backend:9000, 192.168.1.100:3000 return targetComponents[0]; } /// public List GetReverseProxyPortsFromCaddyfileContent(string caddyfileContent) { var reverseProxyRegex = ReverseProxyRegex(); var matches = reverseProxyRegex.Matches(caddyfileContent); var results = new List(); foreach (Match match in matches) { // Use the captured group which contains the target (e.g., pikachu:3011) var targetPart = match.Groups[1].Value.Trim(); if (string.IsNullOrEmpty(targetPart)) continue; var targetComponents = targetPart.Split(':'); if (targetComponents.Length > 1 && int.TryParse(targetComponents.Last(), out int port)) { results.Add(port); } } return [.. results.Distinct()]; } /// public List GetTagsFromCaddyfileContent(string caddyfileContent) { // Split the content into lines and look for the tags line var lines = caddyfileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { var trimmedLine = line.Trim(); if (trimmedLine.StartsWith("#")) { // Remove the # and any leading whitespace, then check if it starts with "tags:" var afterHash = trimmedLine.Substring(1).TrimStart(); if (afterHash.StartsWith("tags:", StringComparison.OrdinalIgnoreCase)) { // Extract the part after "tags:" var tagsString = afterHash.Substring(5).Trim(); // 5 = length of "tags:" if (string.IsNullOrWhiteSpace(tagsString)) return []; // Split by semicolon and clean up each tag return [.. tagsString.Split(';') .Select(tag => tag.Trim()) .Where(tag => !string.IsNullOrWhiteSpace(tag)) .Distinct()]; } } } // No tags line found return []; } }