ngx_http_validate_host

ngx_http_validate_host 1 定义ngx_http_validate_host 函数 定义在 ./nginx-1.24.0/src/http/ngx_http_request.cstaticngx_int_tngx_http_validate_host(ngx_str_t*host,ngx_pool_t*pool,ngx_uint_talloc){u_char*h,ch;size_ti,dot_pos,host_len;enum{sw_usual0,sw_literal,sw_rest}state;dot_poshost-len;host_lenhost-len;hhost-data;statesw_usual;for(i0;ihost-len;i){chh[i];switch(ch){case.:if(dot_posi-1){returnNGX_DECLINED;}dot_posi;break;case::if(statesw_usual){host_leni;statesw_rest;}break;case[:if(i0){statesw_literal;}break;case]:if(statesw_literal){host_leni1;statesw_rest;}break;default:if(ngx_path_separator(ch)){returnNGX_DECLINED;}if(ch0x20||ch0x7f){returnNGX_DECLINED;}if(chAchZ){alloc1;}break;}}if(dot_poshost_len-1){host_len--;}if(host_len0){returnNGX_DECLINED;}if(alloc){host-datangx_pnalloc(pool,host_len);if(host-dataNULL){returnNGX_ERROR;}ngx_strlow(host-data,h,host_len);}host-lenhost_len;returnNGX_OK;}ngx_http_validate_host 函数 用于验证 HTTP 请求中 Host 头的合法性并对其进行规范化。 它会去除端口部分和末尾多余的点 拒绝连续点、控制字符、路径分隔符等非法输入 同时可将主机名转换为小写。 验证通过返回 NGX_OK 非法返回 NGX_DECLINED 内存分配失败返回 NGX_ERROR。2 详解1 函数签名staticngx_int_tngx_http_validate_host(ngx_str_t*host,ngx_pool_t*pool,ngx_uint_talloc)返回值 NGX_OK —— 验证成功主机名合法且规范化操作如小写转换顺利完成。 NGX_DECLINED —— 主机名格式非法 NGX_ERROR —— 内部内存分配失败参数1 ngx_str_t *host 既是输入也是输出 输入 host-data 指向待验证的原始主机名字符串 host-len 为该字符串的字节长度。 输出 验证成功后函数会修改此结构体 host-len 被截短为实际的主机名长度去除端口号、IPv6 地址后的额外字符以及末尾多余的点。 若 alloc 条件成立host-data 会被重新指向内存池中新分配的缓冲区原数据保持不变 其中存放已经过小写转换的有效主机名否则 data 保持原指针不变。参数2 ngx_pool_t *pool 指向 Nginx 内存池的指针 仅在需要分配新内存时使用参数3 ngx_uint_t alloc 用作内存分配与小写转换的控制标志 调用者传入的值 传入 0 表示“除非绝对必要否则不分配内存” 但若主机名中包含大写字母函数内部会将 alloc 局部值覆盖为 1 随后仍会分配并转换为小写因为域名规范要求不区分大小写小写是标准形式。 传入 1 表示“无条件分配新缓冲区 并将主机名转换为小写”即使原本全是小写也会分配确保字符串是独立副本且小写。 函数内部逻辑 遍历字符时若发现 A~Z强制 alloc 1。 函数末尾根据 alloc 是否为 1 决定是否执行 ngx_pnalloc 和 ngx_strlow。 实际效果调用者可通过传 0 避免不必要的内存拷贝 但当存在大写字符时函数会自动分配并转换 保证最终 host 指向规范化的、全小写的主机名。 若传 1 则总是得到一份新的全小写副本。2 逻辑流程1 局部变量 2 遍历 3 去除末尾多余的点号 4 检查最终主机名长度 5 内存分配与小写转换 6 更新长度 7 返回成功状态码1 局部变量{u_char*h,ch;size_ti,dot_pos,host_len;enum{sw_usual0,sw_literal,sw_rest}state;dot_poshost-len;host_lenhost-len;hhost-data;statesw_usual;h指向主机名字符串首字符 ch临时保存当前字符。 h 用于保留原始数据指针后续小写转换时需用原数据 ch 在循环中每次取得当前字符便于判断。声明循环索引 i 记录最近一个点位置的 dot_pos 以及实际主机部分长度 host_len。 dot_pos 初始化为 host-len表示未找到点 host_len 初始为总长度之后会剔除端口和多余部分。定义状态机变量 state包含三种状态 sw_usual0常规主机名部分。 sw_literalIPv6 地址括号内。 sw_rest已遇到端口分隔符 : 或右括号 ]后续字符不计入主机名。 通过状态标识当前解析上下文用于正确处理 IPv6 地址中的冒号和端口分隔符。初始化 dot_pos 为字符串总长度 避免误判第一个字符就是点的情况 初始化 host_len 为原始字符串长度 之后会根据状态遇到冒号或右括号缩短此长度 h 指向主机名字符串的首字符 保存原始数据指针 即使在后续分配新缓冲区时 原字符串地址仍被保留用于小写转换 初始状态设为常规主机名解析2 遍历for(i0;ihost-len;i){chh[i];开始循环遍历整个输入字符串的每个字符 获取当前字符存入 chswitch(ch){case.:if(dot_posi-1){returnNGX_DECLINED;}dot_posi;break;当前字符为点 . 处理 点用于分隔域名标签需检查连续点 若前一个字符也是点即 dot_pos 是上一个点的位置等于 i-1 则说明出现连续点如 ..返回 NGX_DECLINED 拒绝。 域名中不允许连续的点尽早返回错误 更新 dot_pos 为当前点的位置case::if(statesw_usual){host_leni;statesw_rest;}break;当前字符为冒号的处理 仅在常规状态下遇到冒号才认为是端口分隔符 此时将主机名长度截断为当前索引即冒号前部分 并进入 sw_rest 状态忽略后续字符。case[:if(i0){statesw_literal;}break;当前字符为左方括号的处理 仅当左括号在字符串起始位置时才视为 IPv6 地址开始状态转为 sw_literalcase]:if(statesw_literal){host_leni1;statesw_rest;}break;如果正处于 IPv6 地址解析状态右括号表示地址结束 主机名长度更新为 i1包含右括号 状态转为 sw_rest后续字符忽略 IPv6 地址必须用 [...] 包裹右括号之后不能再有主机名字符只能跟端口default:if(ngx_path_separator(ch)){returnNGX_DECLINED;}if(ch0x20||ch0x7f){returnNGX_DECLINED;}if(chAchZ){alloc1;}break;}}除 .、:、[、] 外的所有其他字符均在此处理。#1 若字符是路径分隔符Unix 为 /直接拒绝。 主机名中不应包含路径分隔符防止 URL 注入混淆。#2 过滤控制字符和空格 ASCII 码 0x00~0x20含空格、制表符等以及 DEL0x7F均非法。 这些字符不可打印且可能引发安全问题如 HTTP 头注入必须拒绝。#3 若字符为大写字母 A~Z将局部变量 alloc 置为 1。 域名大小写不敏感通常规范化要求小写。 检测到大写后强制后续分配内存并转为小写3 去除末尾多余的点号if(dot_poshost_len-1){host_len--;}如果最后一个有效字符是点即 dot_pos 等于 host_len - 1 将 host_len 减一去除末尾点。 域名末尾允许有一个点表示绝对域名 但在 Nginx 内部处理时通常去掉以利匹配。4 检查最终主机名长度if(host_len0){returnNGX_DECLINED;}如果去除末尾点后主机名长度为 0如空字符串或只有一个点返回无效。 主机名不能为空。5 内存分配与小写转换if(alloc){host-datangx_pnalloc(pool,host_len);if(host-dataNULL){returnNGX_ERROR;}ngx_strlow(host-data,h,host_len);}判断是否需要分配新内存并转小写。 alloc 可能由调用者传入 1 要求分配 也可能因检测到大写字母而被置 1。在内存池中分配 host_len 字节的新缓冲区并让 host-data 指向它。 若分配失败返回 NGX_ERROR 告知调用者系统错误。 内存不足无法继续必须退出。将原始字符串 h 的前 host_len 个字符 转换为小写后 复制到新分配的缓冲区6 更新长度host-lenhost_len;更新 host 结构体的长度字段为最终主机名长度。7 返回成功状态码returnNGX_OK;}