Nginx系列(⼗九):URI转义机制(部分转载)
⼀、URI和特殊字符
1. URI
2. URI 的组成
完整的 URI,由四个主要的部分构成:
scheme 表⽰协议,⽐如 http,ftp 等等,详细介绍可以参考 rfc2396#section-3.1。
authority,⽤:// 来和 scheme 区分。从字⾯意思看就是“认证”,“鉴权”的意思,引⽤ rfc2396#secion-3.2 的⼀句话:
This authority component is typically defined by an Internet-based
server or a scheme-specific registry of naming authorities.
这个“认证”部分,由⼀个基于 Internet 的服务器定义或者由命名机关注册登记(和具体的协议有关)。
⽽常见的 authority 则是:“由基于 Internet 的服务器定义”,其格式如下:
userinfo 这个域⽤于填写⼀些⽤户相关的信息,⽐如可能会填写 “user:password”,当然这是不被建议的。抛开这个不讲,后⾯
的“host:port”则是被熟知的服务器地址了,host 可以是域名,也可以是对应的 IP 地址,port 表⽰端⼝,这是⼀个可选项,如果不填写,会使⽤默认端⼝(也是和协议相关,⽐如 http 协议默认端⼝是 80)。
path,在 scheme 和 authority 确定下来的情况下标识资源,path 由⼏个段组成,每个段⽤ / 来分隔。注意,path 不等同于⽂件系统定义的路径。
query,查询串(或者说参数串),⽤ ? 和 path 区分开来,其具体的含义由这个具体资源来定义。
3. 保留字符
从上⾯的描述⾥看,URI 的这 4 个组件,由特定的分隔符来分离,这些分隔符各⾃有着特殊含义,⽽如果这些分隔符出现在某个组件内,⽐如 path 是 /a/b?c.html,那么从 URI 整体⾓度来看的话, c.htm
l 会被当做是 query,这样就破坏了 path 原本的含义,因此 URI 引⼊了保留字符集,这些字符有着特殊的⽬的,如果它们被⽤于描述资源(⽽不是作为分隔符出现),那么必须对它们转义。
那么什么情况下需要对⼀个字符转义呢,引⽤ rfc2395#section-2.2 的⼀句话:
In general, a character is reserved if the semantics of the URI changes if the character is replaced with its escaped US-ASCII encoding.
即如果转义前后这个字符会影响到整个 URI 的意义,则它必须被转义。
由于 URI 由多个组件构成,⼀个字符不转义,可能会对其中⼀个组件会造成影响,但对另⼀个组件没有影响,所以“保留字符集”是由具体的 URI 组件来规定的。
· 对 path 部分⽽⾔,保留字符集是(参考⾃ rfc2396):
reserved = “/” | “?” | “;” | “=”
· 对 query 部分⽽⾔,保留字符集是(参考⾃ rfc2396):
reserved = “;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “,” | “$”
字符的转义规则如下:
escaped = “%” hex hex
hex = digit | “A” | “B” | “C” | “D” | “E” | “F” |
“a” | “b” | “c” | “d” | “e” | “f”
⽐如 , 转义后为 %2C。
4. 特殊字符
有⼀类不被允许⽤在 URI ⾥的特殊字符,它们被称为控制字符,即 ASCII 范围在0-31 之间的字符,以及 ASCII 码为 127 的这个字符。⽐如 \t,\a 这些(不包括空格),因为这些字符不可打印⽽且在某些场景下可能会消失。
另外⼀类则是扩展 ASCII 码,即范围 128-255 的那些字符,它们不属于 “US-ASCII coded character set”,因此这些字符如果出现在 URI 中,需要被转义。
URLs are written only with the graphic printable characters of the
US-ASCII coded character set. The octets 80-FF hexadecimal are not
used in US-ASCII, and the octets 00-1F and 7F hexadecimal represent
control characters; these must be encoded.
5. 不安全字符
Characters can be unsafe for a number of reasons. The space character
is unsafe because significant spaces may disappear and insignificant
spaces may be introduced when URLs are transcribed or typeset or
subjected to the treatment of word-processing programs. The characters
“<” and “>” are unsafe because they are used as the delimiters around
URLs in free text; the quote mark (“””) is used to delimit URLs in
some systems. The character “#” is unsafe and should always be encoded
because it is used in World Wide Web and in other systems to delimit a
URL from a fragment/anchor identifier that might follow it. The
character “%” is unsafe because it is used for encodings of other
characters. Other characters are unsafe because gateways and other
transport agents are known to sometimes modify such characters. These
characters are “{“, “}”, “|”, “", “^”, “~”, “[”, “]”, and “`”.
这段话引⽤⾃ rfc1738 2.2 节。因为种种的原因,存在⼀类字符,它们是 “unsafe” 的,不加处理地存在在 URI ⾥,会破坏 URI 的语义完整性,对于这类字符,如果要出现在 URI ⾥,那么也得进⾏转义。
⼆、Nginx的URI转义机制
nginx (以现在最新的 1.13.8 版本为准)提供了⼀个名为 ngx_escape_uri 的函数,函数原型如下:
uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size,
ngx_uint_t type);
第三个参数,type,可以接受这些值:
#define NGX_ESCAPE_URI            0
#define NGX_ESCAPE_ARGS          1
#define NGX_ESCAPE_URI_COMPONENT  2
#define NGX_ESCAPE_HTML          3
#define NGX_ESCAPE_REFRESH        4
#define NGX_ESCAPE_MEMCACHED      5
#define NGX_ESCAPE_MAIL_AUTH      6
我们只关⼼其中的 NGX_ESCAPE_URI ,NGX_ESCAPE_ARGS ,NGX_ESCAPE_URI_COMPONENT ,根据 nginx 官⽅所提供的nginx 模块和核⼼ API 介绍,这三个宏的含义如下:
Type Definition
NGX_ESCAPE_URI: Escape a standard URI
NGX_ESCAPE_ARGS: Escape query arguments
NGX_ESCAPE_URI_COMPONENT: Escape the URI after the domain
对应地,ngx_escape_uri这个函数,内置了⼏个相关的 bitmap,区别就是在于各⾃的转义字符集,具体可以查阅 nginx 的源码
(src/core/ngx_string.c)。其中:
针对整个 URI 的转义处理,ngx_escape_uri 会把 " ", “#”, “%”, “?” 以及 %00-%1F 和 %7F-%FF 的字符转义;
针对 query 的转义,会把 " ", “#”, “%”, “&”, “+”, “?” 以及 %00-%1F 和 %7F-%FF 的字符转义;
针对 path + query(称之为 the URI after the domain)的转义,会把除英⽂字母,数字,以及 “-”, “.”, “_”, “~” 这些以外的字符全部转义。
可以看到,NGX_ESCAPE_URI 和 NGX_ESCAPE_ARGS 没有处理不安全字符,前者站在处理整个的 URI 的⾓度上编码,后者站在处理query 的⾓度上编码;⽽ NGX_ESCAPE_URI_COMPONENT,处理⾓度不是整个 URI,⽽是 domain 之后的 URI 组件,它兼顾 path 和 query 的保留字符集,更加严格,遵守了 rfc3986#section-2.2 的规范。
nginx和网关怎么配合使用这⾥顺便提⼀下 ngx_proxy 模块对应的 URI 转义处理,在构造向上游发送的请求⾏时,ngx_proxy 模块针对 proxy_pass 指令做出了不同的处理:
如果指定的 URI 包含了变量,将解析变量,然后直接将解析后的 URI 发送到上游;
如果 URI 不含变量,且没有指定 path 部分,将使⽤客户端发来的 path 部分拼接到 URI 中,然后发送到上游;
如果URI 不含变量,且指定了 path,这⾥的处理⽐较特殊,nginx 会把解码过的,由客户端发来的 URI ⾥的 path 部分(去掉和当前location 的公共前缀),进⾏编码(按 NGX_ESCAPE_URI 来操作),和 proxy_pass 指令指定 的 path 拼接,发送到上游,⽐如这样的配置:
location /foo {
proxy_pass 127.0.0.1:8082/bar;
}
如果客户端发来的 URI ⾥ path 是 /foo/%5B-%5D,最终上游的 URI path 会是 /bar/[-]。
因此我们在做 nginx conf 配置的时候,也需要⼩⼼考虑 URI 编码的问题。
三、Nginx编解码机制
1. 解码(ngx_http_process_request_uri -> ngx_http_parse_complex_uri)
对于未解码的uri,保存在r->unparsed_uri。⽽如果uri存在r->complex_uri或r->quoted_uri情况,则需要对unparsed_uri进⾏解码,并保存在r->uri中。
r->complex_uri
即uri存在“/.”、“//”、“#”的情况。为什么这⼏种情况需要解码处理?
1)对于“/.”,可能存在“/./”或“/…/”的情况,此时需要对路径进⾏简化;
2)对于“//”,需要根据配置简化为单斜杠;
3)uri中的#⽤来指导浏览器动作,对服务器端完全⽆作⽤,所以http请求中不应包含#字符。
r->quoted_uri
即uri转义情况,需要对已转义的字符进⾏解码保存。
r->valid_unparsed_uri
当uri中存在空格时,不需要对其进⾏解码保存(可能空格可以hash?),但会置r->valid_unparsed_uri为1,即r->unparsed_uri保存的uri不有效,对于这种情况,发送时会对uri进⾏转义。
2. 编码(ngx_http_proxy_create_request -> ngx_escape_uri)
对于nginx的uri转义,可以这么理解:
如果指定了uri(包含变量),那么直接使⽤指定的uri;
如果不指定uri,直接使⽤客户端的未解码的uri(r->unparsed_uri)。除⾮这个未解码的uri不符合nginx要求(r-
>valid_unparsed_uri = 0),即uri包含空格,那么需要nginx重新编码。
如果指定了uri(不含变量),那么使⽤指定uri+客户uri,这相当于nginx⾃⼰⽣成的uri,所以需要根据nginx的要求进⾏uri编码。四、总结
在做相关的代理服务,⽹关服务时,URI 的编解码处理都是⾮常重要的,某些场景我们可能需要⽤ URI 来做 key(⽐如作为 hash 函数的因⼦),如果不处理好编解码问题,可能在 URI 复杂的情况下会达不到我们的预期效果,反⽽会浪费很多时间去排查问题的原因,特别地,在使⽤ nginx 和 ngx_lua 做服务时,我们更应该熟知它们在 URI 编解码上的区别,在理解它们的区别上做⾃⾝的业务处理,避免踩坑。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。