Skip to content

🔗 FBro协议处理与外部程序唤醒指南

完全控制网页链接协议处理,实现与外部应用程序的无缝集成


📋 概述

FBro浏览器提供了强大的协议处理功能,允许开发者控制当用户点击特定协议链接时的行为。通过重写OnProtocolExecution方法,可以决定是否允许操作系统执行外部程序,实现与各种应用程序的深度集成。

🎯 主要功能

  • 自定义协议处理:控制bdnetdisk://、mailto:、tel:等协议
  • 外部程序唤醒:安全地启动系统关联的应用程序
  • 协议拦截分析:记录和分析协议请求
  • 安全控制:防止恶意协议攻击

📱 常见协议示例

协议类型示例关联程序
bdnetdisk://百度网盘下载链接百度网盘客户端
thunder://迅雷下载链接迅雷下载器
mailto:邮件链接默认邮件客户端
tel:电话链接电话应用
skype:Skype通话Skype客户端
steam://Steam游戏Steam平台

🔧 核心实现

OnProtocolExecution方法详解

csharp
/// <summary>
/// 协议执行处理器
/// 当浏览器遇到自定义协议链接时触发
/// </summary>
/// <param name="browser">触发协议的浏览器实例</param>
/// <param name="frame">触发协议的框架</param>
/// <param name="request">协议请求信息</param>
/// <param name="allow_os_execution">是否允许操作系统执行外部程序</param>
public override void OnProtocolExecution(IFBroSharpBrowser browser, 
    IFBroSharpFrame frame, 
    IFBroSharpRequest request, 
    ref bool allow_os_execution)
{
    // 获取协议信息
    string url = request.Url;
    string protocol = GetProtocolFromUrl(url);
    
    Console.WriteLine($"协议执行请求 - URL: {url}");
    Console.WriteLine($"协议类型: {protocol}");
    Console.WriteLine($"来源框架: {frame?.Url ?? "Unknown"}");
    
    // 根据协议类型和安全策略决定是否允许执行
    bool shouldAllow = DetermineProtocolExecution(protocol, url, browser, frame);
    
    // 记录协议处理日志
    LogProtocolExecution(protocol, url, shouldAllow);
    
    // 设置是否允许操作系统执行
    allow_os_execution = shouldAllow;
    
    if (shouldAllow)
    {
        Console.WriteLine($"✅ 允许执行协议: {protocol}");
    }
    else
    {
        Console.WriteLine($"❌ 阻止执行协议: {protocol}");
    }
}

📖 参数说明

参数名类型说明
browserIFBroSharpBrowser触发协议的浏览器实例
frameIFBroSharpFrame触发协议的框架对象
requestIFBroSharpRequest包含协议URL和其他请求信息
allow_os_executionref bool控制是否允许操作系统执行外部程序

IFBroSharpRequest关键属性

csharp
/// <summary>
/// 获取请求的关键信息
/// </summary>
private void AnalyzeRequest(IFBroSharpRequest request)
{
    string url = request.Url;                    // 完整的协议URL
    string method = request.Method;              // 请求方法(通常为GET)
    string referrer = request.ReferrerUrl;       // 引荐来源URL
    
    Console.WriteLine($"请求URL: {url}");
    Console.WriteLine($"请求方法: {method}");
    Console.WriteLine($"引荐来源: {referrer}");
}

🎯 协议处理策略

1. 智能协议识别

csharp
/// <summary>
/// 从URL提取协议类型
/// </summary>
/// <param name="url">完整URL</param>
/// <returns>协议名称</returns>
private string GetProtocolFromUrl(string url)
{
    try
    {
        var uri = new Uri(url);
        return uri.Scheme.ToLower();
    }
    catch
    {
        // 如果URL格式不正确,尝试手动解析
        int colonIndex = url.IndexOf(':');
        if (colonIndex > 0)
        {
            return url.Substring(0, colonIndex).ToLower();
        }
        return "unknown";
    }
}

/// <summary>
/// 协议分类枚举
/// </summary>
public enum ProtocolCategory
{
    /// <summary>
    /// 下载协议
    /// </summary>
    Download,
    
    /// <summary>
    /// 通信协议
    /// </summary>
    Communication,
    
    /// <summary>
    /// 媒体协议
    /// </summary>
    Media,
    
    /// <summary>
    /// 游戏协议
    /// </summary>
    Gaming,
    
    /// <summary>
    /// 系统协议
    /// </summary>
    System,
    
    /// <summary>
    /// 未知协议
    /// </summary>
    Unknown
}

/// <summary>
/// 获取协议分类
/// </summary>
/// <param name="protocol">协议名称</param>
/// <returns>协议分类</returns>
private ProtocolCategory GetProtocolCategory(string protocol)
{
    switch (protocol.ToLower())
    {
        // 下载类协议
        case "bdnetdisk":
        case "thunder":
        case "flashget":
        case "qqdl":
            return ProtocolCategory.Download;
            
        // 通信类协议
        case "mailto":
        case "tel":
        case "sms":
        case "skype":
        case "zoom":
        case "teams":
            return ProtocolCategory.Communication;
            
        // 媒体类协议
        case "potplayer":
        case "vlc":
        case "spotify":
            return ProtocolCategory.Media;
            
        // 游戏类协议
        case "steam":
        case "origin":
        case "uplay":
            return ProtocolCategory.Gaming;
            
        // 系统类协议
        case "ms-settings":
        case "ms-windows-store":
            return ProtocolCategory.System;
            
        default:
            return ProtocolCategory.Unknown;
    }
}

2. 智能决策引擎

csharp
/// <summary>
/// 协议执行决策配置
/// </summary>
public class ProtocolExecutionConfig
{
    /// <summary>
    /// 是否启用协议处理
    /// </summary>
    public bool EnableProtocolHandling { get; set; } = true;
    
    /// <summary>
    /// 允许的协议白名单
    /// </summary>
    public List<string> AllowedProtocols { get; set; } = new List<string>
    {
        "bdnetdisk", "thunder", "mailto", "tel"
    };
    
    /// <summary>
    /// 禁止的协议黑名单
    /// </summary>
    public List<string> BlockedProtocols { get; set; } = new List<string>();
    
    /// <summary>
    /// 受信任的来源域名
    /// </summary>
    public List<string> TrustedDomains { get; set; } = new List<string>
    {
        "pan.baidu.com", "yunpan.360.cn", "cloud.189.cn"
    };
    
    /// <summary>
    /// 是否需要用户确认
    /// </summary>
    public bool RequireUserConfirmation { get; set; } = false;
    
    /// <summary>
    /// 是否记录协议执行日志
    /// </summary>
    public bool EnableLogging { get; set; } = true;
}

/// <summary>
/// 配置实例
/// </summary>
private ProtocolExecutionConfig config = new ProtocolExecutionConfig();

/// <summary>
/// 智能决策是否允许协议执行
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <param name="browser">浏览器实例</param>
/// <param name="frame">框架实例</param>
/// <returns>是否允许执行</returns>
private bool DetermineProtocolExecution(string protocol, string url, 
    IFBroSharpBrowser browser, IFBroSharpFrame frame)
{
    // 1. 检查是否启用协议处理
    if (!config.EnableProtocolHandling)
    {
        Console.WriteLine("协议处理已禁用");
        return false;
    }
    
    // 2. 检查黑名单
    if (config.BlockedProtocols.Contains(protocol))
    {
        Console.WriteLine($"协议 {protocol} 在黑名单中");
        return false;
    }
    
    // 3. 检查白名单
    if (config.AllowedProtocols.Any() && !config.AllowedProtocols.Contains(protocol))
    {
        Console.WriteLine($"协议 {protocol} 不在白名单中");
        return false;
    }
    
    // 4. 检查来源可信度
    if (frame != null && !IsTrustedSource(frame.Url))
    {
        Console.WriteLine($"来源不可信: {frame.Url}");
        return false;
    }
    
    // 5. 根据协议类型进行特殊判断
    if (!ValidateProtocolSafety(protocol, url))
    {
        Console.WriteLine($"协议安全验证失败: {protocol}");
        return false;
    }
    
    // 6. 用户确认(如果需要)
    if (config.RequireUserConfirmation)
    {
        return RequestUserConfirmation(protocol, url);
    }
    
    return true;
}

/// <summary>
/// 检查来源是否可信
/// </summary>
/// <param name="sourceUrl">来源URL</param>
/// <returns>是否可信</returns>
private bool IsTrustedSource(string sourceUrl)
{
    if (string.IsNullOrEmpty(sourceUrl))
        return false;
        
    try
    {
        var uri = new Uri(sourceUrl);
        string domain = uri.Host.ToLower();
        
        return config.TrustedDomains.Any(trustedDomain => 
            domain.Equals(trustedDomain, StringComparison.OrdinalIgnoreCase) ||
            domain.EndsWith("." + trustedDomain, StringComparison.OrdinalIgnoreCase));
    }
    catch
    {
        return false;
    }
}

/// <summary>
/// 协议安全验证
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <returns>是否安全</returns>
private bool ValidateProtocolSafety(string protocol, string url)
{
    switch (protocol.ToLower())
    {
        case "bdnetdisk":
            return ValidateBaiduNetdiskUrl(url);
            
        case "thunder":
            return ValidateThunderUrl(url);
            
        case "mailto":
            return ValidateMailtoUrl(url);
            
        case "tel":
            return ValidateTelUrl(url);
            
        default:
            // 对于未知协议,采用保守策略
            return config.AllowedProtocols.Contains(protocol);
    }
}

/// <summary>
/// 验证百度网盘协议URL
/// </summary>
/// <param name="url">百度网盘URL</param>
/// <returns>是否有效</returns>
private bool ValidateBaiduNetdiskUrl(string url)
{
    // 简单验证:确保URL格式正确
    return url.StartsWith("bdnetdisk://") && url.Length > 20;
}

/// <summary>
/// 验证迅雷协议URL
/// </summary>
/// <param name="url">迅雷URL</param>
/// <returns>是否有效</returns>
private bool ValidateThunderUrl(string url)
{
    return url.StartsWith("thunder://") && url.Length > 15;
}

/// <summary>
/// 验证邮件协议URL
/// </summary>
/// <param name="url">邮件URL</param>
/// <returns>是否有效</returns>
private bool ValidateMailtoUrl(string url)
{
    try
    {
        // 检查是否包含有效的邮件地址
        return url.StartsWith("mailto:") && url.Contains("@");
    }
    catch
    {
        return false;
    }
}

/// <summary>
/// 验证电话协议URL
/// </summary>
/// <param name="url">电话URL</param>
/// <returns>是否有效</returns>
private bool ValidateTelUrl(string url)
{
    // 简单验证:确保包含数字
    var phoneNumber = url.Substring(4); // 移除 "tel:"
    return System.Text.RegularExpressions.Regex.IsMatch(phoneNumber, @"[\d\-\+\(\)\s]+");
}

3. 用户交互确认

csharp
/// <summary>
/// 请求用户确认是否执行协议
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <returns>用户是否确认</returns>
private bool RequestUserConfirmation(string protocol, string url)
{
    try
    {
        string message = GenerateConfirmationMessage(protocol, url);
        
        var result = System.Windows.Forms.MessageBox.Show(
            message,
            "协议执行确认",
            System.Windows.Forms.MessageBoxButtons.YesNo,
            System.Windows.Forms.MessageBoxIcon.Question,
            System.Windows.Forms.MessageBoxDefaultButton.Button2 // 默认选择"否"
        );
        
        bool confirmed = result == System.Windows.Forms.DialogResult.Yes;
        Console.WriteLine($"用户确认结果: {(confirmed ? "允许" : "拒绝")}");
        
        return confirmed;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"用户确认失败: {ex.Message}");
        return false; // 出错时默认拒绝
    }
}

/// <summary>
/// 生成确认消息
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">完整URL</param>
/// <returns>确认消息</returns>
private string GenerateConfirmationMessage(string protocol, string url)
{
    var category = GetProtocolCategory(protocol);
    
    string protocolDescription = GetProtocolDescription(protocol);
    string safeUrl = url.Length > 100 ? url.Substring(0, 100) + "..." : url;
    
    return $"网页正在尝试打开外部应用程序:\n\n" +
           $"协议类型:{protocolDescription}\n" +
           $"目标链接:{safeUrl}\n\n" +
           $"是否允许执行此操作?\n\n" +
           $"注意:只有信任的网站才应该被允许执行此类操作。";
}

/// <summary>
/// 获取协议描述
/// </summary>
/// <param name="protocol">协议类型</param>
/// <returns>协议描述</returns>
private string GetProtocolDescription(string protocol)
{
    switch (protocol.ToLower())
    {
        case "bdnetdisk": return "百度网盘下载";
        case "thunder": return "迅雷下载";
        case "mailto": return "邮件客户端";
        case "tel": return "电话应用";
        case "skype": return "Skype通话";
        case "steam": return "Steam游戏平台";
        default: return $"{protocol.ToUpper()} 协议";
    }
}

📝 日志记录系统

协议执行日志

csharp
/// <summary>
/// 协议执行日志条目
/// </summary>
public class ProtocolExecutionLogEntry
{
    public DateTime Timestamp { get; set; }
    public string Protocol { get; set; }
    public string Url { get; set; }
    public string SourceUrl { get; set; }
    public bool Allowed { get; set; }
    public string Reason { get; set; }
    public ProtocolCategory Category { get; set; }
}

/// <summary>
/// 协议执行日志列表
/// </summary>
private readonly List<ProtocolExecutionLogEntry> protocolLogs = 
    new List<ProtocolExecutionLogEntry>();

/// <summary>
/// 记录协议执行日志
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="url">协议URL</param>
/// <param name="allowed">是否允许执行</param>
/// <param name="sourceUrl">来源URL</param>
/// <param name="reason">决策原因</param>
private void LogProtocolExecution(string protocol, string url, bool allowed, 
    string sourceUrl = null, string reason = null)
{
    if (!config.EnableLogging)
        return;
        
    var logEntry = new ProtocolExecutionLogEntry
    {
        Timestamp = DateTime.Now,
        Protocol = protocol,
        Url = url,
        SourceUrl = sourceUrl ?? "Unknown",
        Allowed = allowed,
        Reason = reason ?? (allowed ? "Policy allowed" : "Policy blocked"),
        Category = GetProtocolCategory(protocol)
    };
    
    // 记录到控制台
    Console.WriteLine($"[{logEntry.Timestamp:yyyy-MM-dd HH:mm:ss}] " +
                     $"协议: {protocol} | 状态: {(allowed ? "允许" : "阻止")} | " +
                     $"来源: {GetDomainFromUrl(sourceUrl)}");
    
    // 记录到内存列表
    protocolLogs.Add(logEntry);
    
    // 记录到文件
    WriteProtocolLogToFile(logEntry);
    
    // 定期清理旧日志
    if (protocolLogs.Count % 100 == 0)
    {
        CleanupOldLogs();
    }
}

/// <summary>
/// 将协议日志写入文件
/// </summary>
/// <param name="logEntry">日志条目</param>
private void WriteProtocolLogToFile(ProtocolExecutionLogEntry logEntry)
{
    try
    {
        var logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "FBroLogs");
        if (!Directory.Exists(logDir))
        {
            Directory.CreateDirectory(logDir);
        }
        
        var logFile = Path.Combine(logDir, $"ProtocolExecution_{DateTime.Now:yyyyMMdd}.log");
        var logLine = $"{logEntry.Timestamp:yyyy-MM-dd HH:mm:ss}|{logEntry.Protocol}|" +
                     $"{(logEntry.Allowed ? "ALLOWED" : "BLOCKED")}|{logEntry.Category}|" +
                     $"{logEntry.Url}|{logEntry.SourceUrl}|{logEntry.Reason}";
        
        File.AppendAllText(logFile, logLine + Environment.NewLine, System.Text.Encoding.UTF8);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"写入协议日志失败: {ex.Message}");
    }
}

/// <summary>
/// 清理旧日志
/// </summary>
/// <param name="daysToKeep">保留天数</param>
private void CleanupOldLogs(int daysToKeep = 30)
{
    var cutoffDate = DateTime.Now.AddDays(-daysToKeep);
    protocolLogs.RemoveAll(log => log.Timestamp < cutoffDate);
}

/// <summary>
/// 获取协议统计报告
/// </summary>
/// <returns>统计报告</returns>
public string GetProtocolStatisticsReport()
{
    var report = new System.Text.StringBuilder();
    report.AppendLine("=== 协议执行统计报告 ===");
    report.AppendLine($"统计时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
    report.AppendLine($"总协议请求数: {protocolLogs.Count}");
    
    if (protocolLogs.Any())
    {
        var allowedCount = protocolLogs.Count(log => log.Allowed);
        var blockedCount = protocolLogs.Count - allowedCount;
        
        report.AppendLine($"允许执行: {allowedCount} ({(double)allowedCount / protocolLogs.Count * 100:F1}%)");
        report.AppendLine($"阻止执行: {blockedCount} ({(double)blockedCount / protocolLogs.Count * 100:F1}%)");
        
        report.AppendLine("\n--- 按协议类型统计 ---");
        var protocolStats = protocolLogs.GroupBy(log => log.Protocol)
            .OrderByDescending(g => g.Count())
            .Take(10);
            
        foreach (var group in protocolStats)
        {
            var allowed = group.Count(log => log.Allowed);
            report.AppendLine($"{group.Key}: {group.Count()}次 (允许{allowed}次)");
        }
        
        report.AppendLine("\n--- 按协议分类统计 ---");
        var categoryStats = protocolLogs.GroupBy(log => log.Category)
            .OrderByDescending(g => g.Count());
            
        foreach (var group in categoryStats)
        {
            var allowed = group.Count(log => log.Allowed);
            report.AppendLine($"{group.Key}: {group.Count()}次 (允许{allowed}次)");
        }
    }
    
    report.AppendLine("================================");
    return report.ToString();
}

🎯 实际应用场景

场景1:下载工具集成

csharp
/// <summary>
/// 下载工具集成处理器
/// 专门处理各种下载协议
/// </summary>
public class DownloadProtocolHandler
{
    private readonly Dictionary<string, string> downloadClients = new Dictionary<string, string>
    {
        { "bdnetdisk", "百度网盘" },
        { "thunder", "迅雷" },
        { "flashget", "快车" },
        { "qqdl", "QQ旋风" }
    };
    
    public override void OnProtocolExecution(IFBroSharpBrowser browser, 
        IFBroSharpFrame frame, 
        IFBroSharpRequest request, 
        ref bool allow_os_execution)
    {
        string protocol = GetProtocolFromUrl(request.Url);
        
        if (downloadClients.ContainsKey(protocol))
        {
            // 记录下载请求
            LogDownloadRequest(protocol, request.Url, frame?.Url);
            
            // 检查下载客户端是否已安装
            if (IsDownloadClientInstalled(protocol))
            {
                allow_os_execution = true;
                Console.WriteLine($"✅ 启动{downloadClients[protocol]}处理下载");
            }
            else
            {
                allow_os_execution = false;
                Console.WriteLine($"❌ {downloadClients[protocol]}未安装,无法处理下载");
                
                // 可选:提示用户安装下载客户端
                NotifyClientNotInstalled(downloadClients[protocol]);
            }
        }
        else
        {
            // 非下载协议,使用默认处理
            allow_os_execution = false;
        }
    }
    
    /// <summary>
    /// 检查下载客户端是否已安装
    /// </summary>
    /// <param name="protocol">协议类型</param>
    /// <returns>是否已安装</returns>
    private bool IsDownloadClientInstalled(string protocol)
    {
        try
        {
            // 检查注册表中的协议关联
            using (var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(protocol))
            {
                return key != null;
            }
        }
        catch
        {
            return false;
        }
    }
    
    /// <summary>
    /// 记录下载请求
    /// </summary>
    private void LogDownloadRequest(string protocol, string url, string sourceUrl)
    {
        Console.WriteLine($"下载请求 - 协议: {protocol}, 来源: {GetDomainFromUrl(sourceUrl)}");
    }
    
    /// <summary>
    /// 通知客户端未安装
    /// </summary>
    private void NotifyClientNotInstalled(string clientName)
    {
        // 可以显示提示消息或引导用户下载
        Console.WriteLine($"提示: {clientName}未安装,请先安装相应的下载客户端");
    }
}

场景2:通信应用集成

csharp
/// <summary>
/// 通信应用集成处理器
/// 处理邮件、电话、即时通信等协议
/// </summary>
public class CommunicationProtocolHandler
{
    public override void OnProtocolExecution(IFBroSharpBrowser browser, 
        IFBroSharpFrame frame, 
        IFBroSharpRequest request, 
        ref bool allow_os_execution)
    {
        string protocol = GetProtocolFromUrl(request.Url);
        string url = request.Url;
        
        switch (protocol.ToLower())
        {
            case "mailto":
                allow_os_execution = HandleMailtoProtocol(url, frame?.Url);
                break;
                
            case "tel":
                allow_os_execution = HandleTelProtocol(url, frame?.Url);
                break;
                
            case "skype":
                allow_os_execution = HandleSkypeProtocol(url, frame?.Url);
                break;
                
            case "zoom":
                allow_os_execution = HandleZoomProtocol(url, frame?.Url);
                break;
                
            default:
                allow_os_execution = false;
                break;
        }
    }
    
    /// <summary>
    /// 处理邮件协议
    /// </summary>
    private bool HandleMailtoProtocol(string url, string sourceUrl)
    {
        try
        {
            // 解析邮件地址和主题
            var mailInfo = ParseMailtoUrl(url);
            
            Console.WriteLine($"邮件协议 - 收件人: {mailInfo.To}, 主题: {mailInfo.Subject}");
            
            // 检查是否为可信来源
            if (IsTrustedEmailSource(sourceUrl))
            {
                Console.WriteLine("✅ 允许打开邮件客户端");
                return true;
            }
            else
            {
                Console.WriteLine("❌ 来源不可信,拒绝打开邮件客户端");
                return false;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理邮件协议失败: {ex.Message}");
            return false;
        }
    }
    
    /// <summary>
    /// 处理电话协议
    /// </summary>
    private bool HandleTelProtocol(string url, string sourceUrl)
    {
        try
        {
            string phoneNumber = url.Substring(4); // 移除 "tel:"
            Console.WriteLine($"电话协议 - 号码: {phoneNumber}");
            
            // 验证电话号码格式
            if (IsValidPhoneNumber(phoneNumber))
            {
                Console.WriteLine("✅ 允许拨打电话");
                return true;
            }
            else
            {
                Console.WriteLine("❌ 电话号码格式无效");
                return false;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理电话协议失败: {ex.Message}");
            return false;
        }
    }
    
    /// <summary>
    /// 解析邮件URL
    /// </summary>
    private (string To, string Subject) ParseMailtoUrl(string url)
    {
        var uri = new Uri(url);
        string to = uri.AbsolutePath;
        string subject = "";
        
        if (!string.IsNullOrEmpty(uri.Query))
        {
            var queryParams = System.Web.HttpUtility.ParseQueryString(uri.Query);
            subject = queryParams["subject"] ?? "";
        }
        
        return (to, subject);
    }
    
    /// <summary>
    /// 验证电话号码格式
    /// </summary>
    private bool IsValidPhoneNumber(string phoneNumber)
    {
        // 简单的电话号码验证
        var cleanNumber = System.Text.RegularExpressions.Regex.Replace(phoneNumber, @"[^\d\+]", "");
        return cleanNumber.Length >= 7 && cleanNumber.Length <= 15;
    }
    
    /// <summary>
    /// 检查是否为可信的邮件来源
    /// </summary>
    private bool IsTrustedEmailSource(string sourceUrl)
    {
        if (string.IsNullOrEmpty(sourceUrl))
            return false;
            
        // 检查是否来自知名邮件服务或企业网站
        string[] trustedEmailSources = {
            "gmail.com", "outlook.com", "qq.com", "163.com",
            "company.com" // 可以添加企业域名
        };
        
        try
        {
            var uri = new Uri(sourceUrl);
            string domain = uri.Host.ToLower();
            
            return trustedEmailSources.Any(trusted => 
                domain.Equals(trusted) || domain.EndsWith("." + trusted));
        }
        catch
        {
            return false;
        }
    }
}

场景3:安全控制模式

csharp
/// <summary>
/// 安全优先的协议处理器
/// 严格控制协议执行,防止恶意攻击
/// </summary>
public class SecureProtocolHandler
{
    private readonly HashSet<string> secureProtocols = new HashSet<string>
    {
        "mailto", "tel" // 只允许这些基础协议
    };
    
    private readonly Dictionary<string, DateTime> protocolExecutionHistory = 
        new Dictionary<string, DateTime>();
    
    private readonly TimeSpan rateLimitInterval = TimeSpan.FromMinutes(1);
    private readonly int maxExecutionsPerInterval = 5;
    
    public override void OnProtocolExecution(IFBroSharpBrowser browser, 
        IFBroSharpFrame frame, 
        IFBroSharpRequest request, 
        ref bool allow_os_execution)
    {
        string protocol = GetProtocolFromUrl(request.Url);
        string sourceUrl = frame?.Url ?? "Unknown";
        
        // 1. 协议白名单检查
        if (!secureProtocols.Contains(protocol))
        {
            LogSecurityEvent($"阻止未授权协议: {protocol}", sourceUrl, request.Url);
            allow_os_execution = false;
            return;
        }
        
        // 2. 频率限制检查
        if (IsRateLimited(protocol))
        {
            LogSecurityEvent($"协议执行频率过高: {protocol}", sourceUrl, request.Url);
            allow_os_execution = false;
            return;
        }
        
        // 3. 来源验证
        if (!IsSecureSource(sourceUrl))
        {
            LogSecurityEvent($"不安全的来源: {sourceUrl}", sourceUrl, request.Url);
            allow_os_execution = false;
            return;
        }
        
        // 4. URL内容验证
        if (!IsSecureProtocolUrl(protocol, request.Url))
        {
            LogSecurityEvent($"协议URL验证失败: {protocol}", sourceUrl, request.Url);
            allow_os_execution = false;
            return;
        }
        
        // 通过所有安全检查
        RecordProtocolExecution(protocol);
        allow_os_execution = true;
        
        Console.WriteLine($"✅ 安全协议执行: {protocol}");
    }
    
    /// <summary>
    /// 检查是否达到频率限制
    /// </summary>
    private bool IsRateLimited(string protocol)
    {
        var key = $"{protocol}_{DateTime.Now:yyyyMMddHHmm}";
        
        if (protocolExecutionHistory.ContainsKey(key))
        {
            var executionTime = protocolExecutionHistory[key];
            if (DateTime.Now - executionTime < rateLimitInterval)
            {
                // 检查执行次数
                var recentExecutions = protocolExecutionHistory.Keys
                    .Count(k => k.StartsWith(protocol) && 
                           DateTime.Now - protocolExecutionHistory[k] < rateLimitInterval);
                
                return recentExecutions >= maxExecutionsPerInterval;
            }
        }
        
        return false;
    }
    
    /// <summary>
    /// 记录协议执行
    /// </summary>
    private void RecordProtocolExecution(string protocol)
    {
        var key = $"{protocol}_{DateTime.Now:yyyyMMddHHmmss}";
        protocolExecutionHistory[key] = DateTime.Now;
        
        // 清理旧记录
        var oldKeys = protocolExecutionHistory.Keys
            .Where(k => DateTime.Now - protocolExecutionHistory[k] > TimeSpan.FromHours(1))
            .ToList();
            
        foreach (var oldKey in oldKeys)
        {
            protocolExecutionHistory.Remove(oldKey);
        }
    }
    
    /// <summary>
    /// 检查来源是否安全
    /// </summary>
    private bool IsSecureSource(string sourceUrl)
    {
        if (string.IsNullOrEmpty(sourceUrl) || sourceUrl == "Unknown")
            return false;
            
        try
        {
            var uri = new Uri(sourceUrl);
            
            // 必须是HTTPS
            if (uri.Scheme != "https")
                return false;
                
            // 检查域名是否在可信列表中
            // 这里可以实现更复杂的域名验证逻辑
            
            return true;
        }
        catch
        {
            return false;
        }
    }
    
    /// <summary>
    /// 验证协议URL是否安全
    /// </summary>
    private bool IsSecureProtocolUrl(string protocol, string url)
    {
        switch (protocol)
        {
            case "mailto":
                // 检查邮件地址是否包含恶意脚本
                return !url.Contains("<script") && !url.Contains("javascript:");
                
            case "tel":
                // 检查电话号码是否合理
                var phoneNumber = url.Substring(4);
                return System.Text.RegularExpressions.Regex.IsMatch(phoneNumber, @"^[\d\-\+\(\)\s]+$");
                
            default:
                return false;
        }
    }
    
    /// <summary>
    /// 记录安全事件
    /// </summary>
    private void LogSecurityEvent(string message, string sourceUrl, string protocolUrl)
    {
        var logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] SECURITY: {message}";
        logEntry += $" | Source: {sourceUrl} | Protocol: {protocolUrl}";
        
        Console.WriteLine(logEntry);
        
        // 写入安全日志文件
        try
        {
            var logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "FBroLogs");
            if (!Directory.Exists(logDir))
                Directory.CreateDirectory(logDir);
                
            var logFile = Path.Combine(logDir, $"Security_{DateTime.Now:yyyyMMdd}.log");
            File.AppendAllText(logFile, logEntry + Environment.NewLine, System.Text.Encoding.UTF8);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"写入安全日志失败: {ex.Message}");
        }
    }
}

⚙️ 配置管理系统

配置文件管理

csharp
/// <summary>
/// 加载协议处理配置
/// </summary>
public void LoadConfig(string configPath = null)
{
    try
    {
        configPath = configPath ?? Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            "FBro", "ProtocolConfig.json");
            
        if (File.Exists(configPath))
        {
            var json = File.ReadAllText(configPath, System.Text.Encoding.UTF8);
            config = Newtonsoft.Json.JsonConvert.DeserializeObject<ProtocolExecutionConfig>(json) 
                ?? new ProtocolExecutionConfig();
            Console.WriteLine($"协议配置已加载: {configPath}");
        }
        else
        {
            SaveConfig(configPath);
            Console.WriteLine($"创建默认协议配置: {configPath}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"加载协议配置失败: {ex.Message},使用默认配置");
        config = new ProtocolExecutionConfig();
    }
}

/// <summary>
/// 保存协议处理配置
/// </summary>
public void SaveConfig(string configPath = null)
{
    try
    {
        configPath = configPath ?? Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            "FBro", "ProtocolConfig.json");
            
        var directory = Path.GetDirectoryName(configPath);
        if (!Directory.Exists(directory))
            Directory.CreateDirectory(directory);
            
        var json = Newtonsoft.Json.JsonConvert.SerializeObject(config, Newtonsoft.Json.Formatting.Indented);
        File.WriteAllText(configPath, json, System.Text.Encoding.UTF8);
        
        Console.WriteLine($"协议配置已保存: {configPath}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"保存协议配置失败: {ex.Message}");
    }
}

⚠️ 注意事项

1. 安全考虑

  • 严格验证:对所有协议URL进行格式和内容验证
  • 来源检查:确保协议请求来自可信的网站
  • 用户确认:对敏感操作要求用户明确确认
  • 频率限制:防止恶意网站频繁触发协议执行

2. 用户体验

  • 合理提示:当阻止协议执行时,给用户明确的提示信息
  • 配置灵活:允许用户自定义协议处理策略
  • 日志透明:提供详细的日志记录供用户查看

3. 兼容性

  • 注册表检查:验证外部程序是否已正确安装
  • 协议标准:遵循标准的协议格式规范
  • 错误处理:妥善处理协议执行失败的情况

4. 性能优化

  • 缓存机制:缓存协议验证结果,避免重复计算
  • 异步处理:对于复杂的验证逻辑,考虑异步执行
  • 日志管理:定期清理旧的日志文件,避免占用过多磁盘空间

🏆 最佳实践

1. 分层安全策略

csharp
// 实现多层安全验证
if (!IsProtocolInWhitelist(protocol)) return false;
if (!IsTrustedSource(sourceUrl)) return false;
if (!ValidateProtocolFormat(url)) return false;
if (!CheckUserPermission(protocol)) return false;

2. 详细的日志记录

csharp
// 记录所有关键信息
LogProtocolExecution(protocol, url, allowed, sourceUrl, decisionReason);

3. 用户友好的错误处理

csharp
// 提供明确的错误信息和解决建议
if (!IsClientInstalled(protocol))
{
    ShowInstallationGuide(protocol);
}

4. 配置化的处理策略

csharp
// 使用配置文件控制行为,便于调整
if (config.RequireUserConfirmation)
{
    allow_os_execution = RequestUserConfirmation(protocol, url);
}

通过以上完整的实现方案,您可以安全、灵活地控制FBro浏览器中的协议处理行为,实现与各种外部应用程序的无缝集成,同时确保用户的安全和良好的使用体验。

如果文档对您有帮助,欢迎 请喝咖啡 ☕ | 软件发布 | 源码购买