深入解析 Suricata 内置的 LibHTP:安全感知 HTTP 解析库的用法、FFI 与源码结构
网络安全【免费下载链接】suricataSuricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community.项目地址https://gitcode.com/gh_mirrors/su/suricata点击查看免费下载导读LibHTP 是 Suricata 项目中用于解析 HTTP 协议及其相关细节的安全感知解析库security-aware parser其核心目标就是支撑 Suricata 的 HTTP 应用层检测场景。本文以仓库内的 rust/htp/README.md 为主体结合 rust/htp/src 下的 Rust 源码、rust/htp/Cargo.toml.in 与 rust/htp/cbindgen.toml完整讲解 LibHTP 的定位、Rust 依赖集成方式、C/C 项目的 FFI 桥接步骤、许可证以及其内部核心抽象连接、事务、配置、解压与压缩炸弹防护的源码级实现。读完本文你将能够在自己的 Rust 或 C/C 项目中正确引入并构建 LibHTP同时理解它如何以安全为先的防御性设计解析 HTTP 流量。LibHTP 是什么根据 rust/htp/README.md 的定位描述LibHTP 是一个面向安全的 HTTP 协议解析器security-aware parser负责解析 HTTP 协议以及与之相关的“边角料”内容the related bits and pieces。它的首要目标goal of the project is mainly to support the Suricata use case是服务 Suricata 的入侵检测与网络监控场景文档同时说明其他使用场景可能不会被完全支持并鼓励使用者自行覆盖这些场景。从仓库结构看LibHTP 以 Rust 实现并内嵌于 Suricata 仓库的 rust/htp 目录中。其 crate 名为suricata-htp见 Cargo.toml.in描述为 “Security Aware HTP Protocol parsing library”发布形态同时提供staticlib、rlib与cdylib三种 crate 类型见 Cargo.toml.in为静态链接、纯 Rust 集成和动态库调用三种使用方式都预留了通道。历史上LibHTP 由 Open Information Security FoundationOISF2009–2010 版权与 Qualys, Inc.2010–2013 版权共同维护这段版权信息在 README 开头被明确保留。在 Rust 项目中集成 LibHTP依赖声明README 给出的使用方式非常简洁将 LibHTP 加入项目的Cargo.toml依赖即可开始使用。同时它特别强调使用公共类型common types时还需要同时引入基础库base library。[dependencies] htp 2.0.0需要注意两点MSRV 约束README 明确声明rustc的最低支持版本为1.58.1The minimum supported version of rustc is 1.58.1。这意味着引入依赖时你的工具链不能低于该版本否则无法编译。crate 名称README 中的示例写法htp 2.0.0对应 crates.io 上的发布名而当前仓库内 rust/htp/Cargo.toml.in 中 crate 名写为suricata-htp版本号由 autoconf 构建系统在生成Cargo.toml时以PACKAGE_VERSION注入。也就是说直接在 Suricata 源码树内构建时使用的是suricata-htp这个名字。依赖树一览当前版本 LibHTP 的依赖见 rust/htp/Cargo.toml.in体现了它在解析链路中的真实需求依赖用途推断结合源码nom8.0.0提供组合子解析器框架用于请求行、响应行、头部等语法解析bstr1.12.0提供面向字节的字符串类型用于 HTTP 原始字节数据的安全处理base640.22.1处理 HTTP 中出现的 Base64 编码内容libc0.2FFI 层所需的 C 类型与函数绑定lzma-rs、flate2、brotli、zstd四种压缩格式的流式解压对应 HTTP 内容编码见下文“解压与压缩炸弹防护”time0.3.x连接打开/关闭时间戳管理lazy_static惰性初始化的全局数据rstestdev-dependencies测试辅助用于参数化测试用例其中cdylib-link-lines作为 build-dependencies 出现服务于动态库cdylib构建时的链接行生成——这与 README 中“生成 shared objects”的 FFI 流程相互印证。FFI 支持让 C/C 项目使用 LibHTPREADME 明确指出 LibHTP 提供了面向 C/C 项目的外部函数接口Foreign Function Interface, FFI并可通过启用cbindgenfeature 来构建。启用与构建步骤README 给出的完整流程如下# 安装 cbindgen它用于生成 C 头文件 cargo install --force cbindgen # 生成头文件与共享库 make第一步通过cargo install --force cbindgen安装头文件生成器 cbindgen。--force确保即使本地已有旧版本也会强制更新到最新。第二步执行make该命令会一次性产出 C 头文件与共享对象shared objects。这里的make由仓库的 automake 体系驱动rust/htp/Makefile.am 中声明了all-local: Cargo.toml目标而Cargo.toml本身由Cargo.toml.in经 configure 生成EXTRA_DIST Cargo.toml表明它属于发布产物。cbindgen 配置Rust 类型到 C 类型的安全映射rust/htp/cbindgen.toml 详细定义了头文件生成规则是理解 FFI 的关键配置文件输出语言与格式language C启用cpp_compat true兼容 C 编译style both同时生成上驼峰与下划线两种风格别名头文件使用include_guard _HTP_H并自动附带 “autogenerated by cbindgen, Do NOT modify manually” 警告注释。类型重命名映射通过[export.rename]将 Rust 内部类型导出为带htp_前缀的 C 类型这是 C API 的命名规范核心。映射表摘录如下Rust 类型C 导出类型Confightp_cfg_tConnectionhtp_conn_tConnectionParserhtp_connp_tHeader/Headershtp_header_t/htp_headers_tParamhtp_param_tDatahtp_tx_data_tTransaction/Transactionshtp_tx_t/htp_txs_tUrihtp_uri_tBstrbstrLog/Logshtp_log_t/htp_logs_ttimevalstruct timeval枚举与宏导出[export] include明确只导出HtpUrlEncodingHandling、HtpLogCode、HtpFlags三类符号枚举变体以QualifiedScreamingSnakeCase风格重命名[macro_expansion] bitflags true允许将 Rust 的 bitflags 位标志类型展开为 C 宏。after_includes中还补充了两个向后兼容宏htp_url_encoding_handling_t与htp_log_code_t方便旧代码平滑迁移。对应地rust/htp/src/c_api 目录含bstr.rs、config.rs、connection.rs、connection_parser.rs、header.rs、log.rs、transaction.rs、uri.rs正是面向 C 的 API 层实现与 cbindgen 配置一一对应可视为 FFI 能力在源码侧的完整落地。源码级核心抽象状态机、连接与事务要真正理解 LibHTP 的“安全感知”属性需要进入 rust/htp/src/lib.rs 观察其核心抽象。HtpStatus解析器的统一返回协议HtpStatus见 src/lib.rs是 LibHTP 内部使用的状态码枚举它构成了所有解析函数与回调的统一“协议语言”ERROR_RESERVED -1000/STATUS_RESERVED 1000内部使用的取值边界ERROR -1通用错误DECLINED 0没有做任何处理通常由回调返回表示对该上下文不感兴趣OK 1工作成功完成DATA 2流式处理中已消费完本次提供的全部数据调用方应继续提供更多数据DATA_OTHER 3需要在对侧流如入站解析器需要观察出站数据上继续处理本次数据未被完全消费可用request_data_consumed()/response_data_consumed()查询实际消费量STOP 4回调要求停止处理例如连接级回调返回STOP表示 LibHTP 应放弃跟踪该连接DATA_BUFFER 5与DATA类似但未消费部分应被保留缓冲供后续使用。这套状态码设计直接服务于流式stream-based解析模型HTTP 数据在 TCP 上被分成多个报文段到达解析器必须能在“数据不足”“数据跨包”“需要双向观察”等场景下与调用方精确协作。Connection会话级信息载体src/connection.rs 中的Connection保存一次会话的元信息客户端/服务端的 IP 与端口、消息日志通道log_channel、解析标志、打开/关闭时间戳以及入站request与出站response数据计数器。ConnectionFlags则记录了会话级解析标志PIPELINED见到过流水线化请求与HTTP_0_9_EXTRAHTTP/0.9 通信之后出现额外数据——这两个标志暗示 LibHTP 对 HTTP 管线化与老版本协议边界都有专门处理。事务Transaction与解析钩子HTTP 的请求-响应对在 LibHTP 中建模为事务Transaction并由ConnectionParser驱动解析。解析过程的每个关键节点都通过**钩子hook**暴露给调用方。在 src/config.rs 的Config结构中可以看到一整套生命周期钩子按顺序覆盖了事务创建/销毁hook_tx_create、hook_tx_destroy注释明确说明 Suricata 用它来分配/释放自己的事务用户数据甚至能让 Rust 侧事务创建在 C 分配失败时返回None请求侧hook_request_start收到新请求首字节兼作事务开始、hook_request_line、hook_request_header_data原始头部字节含终止空行、hook_request_body_data块传输会先解码数据结束时以None调用一次、hook_request_trailer_data、hook_request_trailer、hook_request_complete响应侧hook_response_start、hook_response_line、hook_response_header_data、hook_response_body_data默认会解压压缩内容、hook_response_trailer_data、hook_response_trailer、hook_response_complete事务收尾hook_transaction_complete文档注释特别提醒由于服务器可能在请求尚未收全时就开始响应response_complete有可能先于request_complete触发。这一整套钩子构成了 Suricata 应用层 HTTP 检测的基础IDS 引擎在事务边界上挂载检测逻辑既能拿到逐字节的原始数据也能拿到结构化的事件点。配置项与默认值安全边界的来源src/config.rs 中Config的Default实现直接揭示了“安全感知”的具体参数化手段配置字段默认值含义field_limit18000当输入块不含全部数据如头部行跨多个包时内部缓冲的最大尺寸server_personalityHtpServerPersonality::MINIMAL服务器个性标识影响解析的宽容度策略requestline_leading_whitespace_unwantedIgnore请求行前导空白字符的处理策略request_decompression_enabledfalse是否解压请求体响应体默认解压可单独禁用max_tx512单个连接上允许的最大事务数number_headers_limit1024单个消息允许的最大头部数量其中max_tx与number_headers_limit这类上限是典型的资源耗尽防护HTTP 走私、头部洪泛等攻击正是通过无限制的事务/头部数量来拖垮解析器LibHTP 用可配置上限将其钳制住。field_limit则防止跨包拼接时的无界缓冲。此外Config中还有decoder_cfgURL 路径解码配置与compression_options解压选项后者直接关联下一节的压缩炸弹防护。解压支持与压缩炸弹防护现代 HTTP 流量大量使用内容编码LibHTP 在 src/decompressors.rs 中实现了流式解压支持的编码格式与底层依赖对应为gzip/zlibflate2、brotlibrotlicrate、zstdzstdcrate、LZMAlzma-rs采用流模式。解压是一把双刃剑恶意流量可以构造“压缩炸弹”decompression bomb用极小的压缩数据膨胀出海量输出打爆内存与 CPU。LibHTP 在 decompressors.rs 中定义了完整的防护默认值DEFAULT_LZMA_MEMLIMIT 1_048_5761 MBLZMA 解压的字典内存上限设为 0 可完全禁用 LZMA见set_lzma_memlimit的实现DEFAULT_BOMB_LIMIT 1_048_5761 MB单次解压的最大输出尺寸上限DEFAULT_BOMB_RATIO 2048允许的压缩比上限压缩后:解压后DEFAULT_TIME_LIMIT 100_000微秒单次解压的时间预算DEFAULT_TIME_FREQ_TEST 256每迭代多少次检查一次时间预算DEFAULT_LAYER_LIMIT 2最多解压的压缩层数应对多层嵌套压缩DEFAULT_BOMB_NB_LIMIT 3一个流中累计触发炸弹上限的次数超过后该流跳过解压。从源码结构看Options把以上参数bomb_limit、bomb_ratio、time_limit、layer_limit等统一管理并允许通过set_lzma_memlimit等 setter 在配置层调整。这些参数组合在一起构成了对内存、CPU、压缩比、层数与次数五个维度的立体防御是“安全感知解析器”最直接的体现。测试与模糊测试LibHTP 的可靠性由多层测试保障rust/htp/src/test 目录包含单元/集成测试模块common.rs、gunzip.rs、hybrid.rs、main.rs其中gunzip.rs专门覆盖 gzip 解压路径hybrid.rs则从命名推断覆盖“混合”解析场景另有files/目录存放测试数据文件。rust/htp/fuzz 目录含Cargo.toml与fuzz_targets/表明该项目针对 HTTP 解析器构建了模糊测试目标。对于面向攻击者输入的安全解析器而言模糊测试是发现解析漏洞的标准手段这也解释了为何 README 强调它是 “security-aware” 解析器。依赖中的rstest用于参数化驱动大量输入样例覆盖解析边界情况。与 Suricata 引擎的协作关系回到 README 的核心定位——“The goal of the project is mainly to support the Suricata use case”项目目标主要是支持 Suricata 用例。从源码结构可以推断出这种协作的具体形态Suricata 通过hook_tx_create/hook_tx_destroy钩子在每个 LibHTP 事务上挂载自己的 C 侧事务对象实现解析与检测的松耦合见 src/config.rs 的注释说明LibHTP 以rlib形式静态链接进 Suricata 的 Rust 侧同时以staticlib/cdylib支持 C 侧调用见 Cargo.toml.in上游 Suricata 仓库将 LibHTP 以子目录形式内嵌本仓库即 rust/htp通过 automakeMakefile.am与 autoconfCargo.toml.in的PACKAGE_VERSION注入统一纳入构建系统。许可证LibHTP 采用BSD 3-Clause许可证即 “BSD New” / “BSD Simplified”。README 明确要求使用者查阅随发布物附带的 rust/htp/LICENSE 文件以获取完整的许可、复制与版权信息。BSD 3-Clause 属于宽松许可证这也是它能被广泛嵌入 Suricata 这类开源安全产品的前提之一。仓库根目录的 COPYING 与 LICENSE 则覆盖了整个 Suricata 项目的整体许可条款。小结LibHTP 是 Suricata 的 HTTP 应用层解析基石它以 Rust 实现、以suricata-htpcrate 发布支持rlib/staticlib/cdylib三种集成形态对 Rust 项目只需在Cargo.toml中声明依赖注意 rustc ≥ 1.58.1 的 MSRV 约束对 C/C 项目则通过启用cbindgenfeature、安装 cbindgen 并执行make生成头文件与共享库。在源码层面HtpStatus状态码、Connection会话模型、Config中一整套事务生命周期钩子与安全上限以及decompressors中针对 gzip/brotli/zstd/LZMA 的多维压缩炸弹防护共同构成了其“安全感知”的完整内涵。无论是阅读 Suricata 的 HTTP 检测实现还是在自己的项目里复用这套经过生产级考验的解析逻辑本文梳理的集成路径与源码地图都能作为直接入口。赞分享网络安全【免费下载链接】suricataSuricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community.项目地址https://gitcode.com/gh_mirrors/su/suricata点击查看免费下载相关推荐Dolibarr 内置 sabre/http 库演进全解析从 CHANGELOG 到源码的 HTTP 工具箱深度拆解Dolibarr 内置 sabre/http 库演进全解析从 CHANGELOG 到源码的 HTTP 工具箱深度拆解 Dolibarr ERP/CRM 在 h企业应用后端深入解析 xmrig 内置的 {fmt} 格式化库特性、实战用法与源码级剖析深入解析 xmrig 内置的 {fmt} 格式化库特性、实战用法与源码级剖析 {fmt} 是一个开源 C 格式化库为 C 标准库的 printf 家族和区块链高性能计算OpenPDF源码架构分析深入理解PDF文档的内部结构OpenPDF源码架构分析深入理解PDF文档的内部结构 OpenPDF是一个功能强大的Java开源库专门用于创建和编辑PDF文件。作为iText的一个分支后端上一篇Agent Skills vs 提示词 vs MCP3种AI智能体扩展机制深度对比如何选型不踩坑下一篇嵌套插件用 NoneBot 2 将大型插件拆分为可维护的子插件体系创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Linux下BIND9权威DNS服务器实战配置与排错

Linux下BIND9权威DNS服务器实战配置与排错

简介:本资源是一份完整的Linux环境下DNS服务器配置实验报告,面向网络工程、系统运维及计算机相关专业的初学者与实践者,解决DNS服务部署、BIND软件安装、正反向区域配置及服务验证等核心实操问题。文档以Red Hat Enterprise Linux为平台&…

2026/10/9 5:31:37 阅读更多 →
60kph-9%-52°C热管理数据图

60kph-9%-52°C热管理数据图

一、车速二、轮边力三、前格栅四、车外温度(后视镜下)五、冷凝器进风温度六、辅助散热器

2026/10/9 5:31:37 阅读更多 →
数字化转型实践方法⑬|数字化转型7类核心人才,你的团队配齐了吗?

数字化转型实践方法⑬|数字化转型7类核心人才,你的团队配齐了吗?

梳理推进数字化转型不可或缺7类人才,包含项目经理、商业设计师、技术指导、数据科学家、前沿技术工程师、UI/UX设计师、工程师程序员,明确每类人才的职责定位。上一篇我们聊了管理层共识——顶层定调。但共识再高、决心再大,落地的时候还是要…

2026/10/9 5:31:37 阅读更多 →

最新新闻

如何打造无可挑剔的代码品质:从命名到文档的完整检查清单

如何打造无可挑剔的代码品质:从命名到文档的完整检查清单

1. 一个词撬动的品质革命:为什么“impeccable”值得单独拿出来讲第一次看到“impeccable”这个词被单独拎出来当作项目标题,我的反应是:这要么是个极简主义的个人品牌实验,要么是一个对“品质”有执念的人在做一件很较真的事。后来…

2026/10/9 8:00:27 阅读更多 →
深度学习期货交易入门:从CTP接口到数据落库的完整实战

深度学习期货交易入门:从CTP接口到数据落库的完整实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 8:00:27 阅读更多 →
SpringBoot与Android电子书阅读系统全栈项目实战解析

SpringBoot与Android电子书阅读系统全栈项目实战解析

如果你的简历上还缺一个能拿得出手的Java全栈项目,这套基于Java SpringBoot和Android的电子书阅读系统,是一个值得花两周时间认真啃下来的选择。它不是一个PPT项目,而是包含了Android客户端、SpringBoot后端、MySQL数据库的完整可运行系统&am…

2026/10/9 8:00:27 阅读更多 →
image 组件用法

image 组件用法

一、image 组件基础image 是小程序的图片组件,负责把一张图片按指定方式显示在页面里。它支持 JPG、PNG、SVG、WEBP、GIF 等格式,从基础库 2.3.0 起也支持直接使用云文件 ID。它的用法非常简单,只有一个必须关心的属性 src 和一个真正决定效果…

2026/10/9 8:00:27 阅读更多 →
基于3893张图像的手套与徒手检测:YOLO训练调参及产线落地实战

基于3893张图像的手套与徒手检测:YOLO训练调参及产线落地实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 8:00:27 阅读更多 →
『项目管理精要』第 7 章 团队演进与冲突解决:从单打独斗到带队攻坚

『项目管理精要』第 7 章 团队演进与冲突解决:从单打独斗到带队攻坚

从一名卓越的个人贡献者(Individual Contributor, IC)成长为优秀的技术主管(TL),最大挑战在于“如何带出一支高效能的自组织团队”。在平衡矩阵或弱矩阵组织中,成员往往来自不同的职能部门,兼顾多个项目,团队容易陷入推诿扯皮或效率低下的泥潭。TL 需要理解塔克曼团队演…

2026/10/9 7:59:27 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →