浅谈 URI 及其转义
淺談 URI 及其轉(zhuǎn)義
URI
URI,全稱是 Uniform Resource Identifiers,即統(tǒng)一資源標(biāo)識符,用于在互聯(lián)網(wǎng)上標(biāo)識一個(gè)資源,比如?https://www.upyun.com/products/cdn?這個(gè) URI,指向的是一張漂亮的,描述又拍云 CDN 產(chǎn)品特性的網(wǎng)頁。
URI 的組成
完整的 URI,由四個(gè)主要的部分構(gòu)成:
<scheme>://<authority><path>?<query>
scheme?表示協(xié)議,比如?http,ftp?等等,詳細(xì)介紹可以參考?rfc2396#section-3.1。
authority,用?://?來和?scheme?區(qū)分。從字面意思看就是“認(rèn)證”,“鑒權(quán)”的意思,引用?rfc2396#secion-3.2?的一句話:
This authority component is typically defined by an Internet-based server or a scheme-specific registry of naming authorities.這個(gè)“認(rèn)證”部分,由一個(gè)基于 Internet 的服務(wù)器定義或者由命名機(jī)關(guān)注冊登記(和具體的協(xié)議有關(guān))。
而常見的?authority?則是:“由基于 Internet 的服務(wù)器定義”,其格式如下:
<userinfo>@<host>:<port>
userinfo?這個(gè)域用于填寫一些用戶相關(guān)的信息,比如可能會(huì)填寫 “user:password”,當(dāng)然這是不被建議的。拋開這個(gè)不講,后面的?<host>:<port>?則是被熟知的服務(wù)器地址了,host?可以是域名,也可以是對應(yīng)的 IP 地址,port?表示端口,這是一個(gè)可選項(xiàng),如果不填寫,會(huì)使用默認(rèn)端口(也是和協(xié)議相關(guān),比如?http?協(xié)議默認(rèn)端口是 80)。
path,在?scheme?和?authority?確定下來的情況下標(biāo)識資源,path?由幾個(gè)段組成,每個(gè)段用?/?來分隔。注意,path?不等同于文件系統(tǒng)定義的路徑。
query,查詢串(或者說參數(shù)串),用???和?path?區(qū)分開來,其具體的含義由這個(gè)具體資源來定義。
保留字符
從上面的描述里看,URI 的這 4 個(gè)組件,由特定的分隔符來分離,這些分隔符各自有著特殊含義,而如果這些分隔符出現(xiàn)在某個(gè)組件內(nèi),比如?path?是?/a/b?c.html,那么從 URI 整體角度來看的話,?c.html?會(huì)被當(dāng)做是?query,這樣就破壞了?path?原本的含義,因此 URI 引入了保留字符集,這些字符有著特殊的目的,如果它們被用于描述資源(而不是作為分隔符出現(xiàn)),那么必須對它們轉(zhuǎn)義。
那么什么情況下需要對一個(gè)字符轉(zhuǎn)義呢,引用?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.即如果轉(zhuǎn)義前后這個(gè)字符會(huì)影響到整個(gè) URI 的意義,則它必須被轉(zhuǎn)義。
由于 URI 由多個(gè)組件構(gòu)成,一個(gè)字符不轉(zhuǎn)義,可能會(huì)對其中一個(gè)組件會(huì)造成影響,但對另一個(gè)組件沒有影響,所以“保留字符集”是由具體的 URI 組件來規(guī)定的。
- 對?path?部分而言,保留字符集是(參考自 rfc2396):
reserved = "/" | "?" | ";" | "="
- 對?query?部分而言,保留字符集是(參考自 rfc2396):
reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "," | "$"
字符的轉(zhuǎn)義規(guī)則如下:
escaped = "%" hex hex hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |"a" | "b" | "c" | "d" | "e" | "f"比如?,?轉(zhuǎn)義后為?%2C。
特殊字符
有一類不被允許用在 URI 里的特殊字符,它們被稱為控制字符,即 ASCII 范圍在0-31 之間的字符,以及 ASCII 碼為 127 的這個(gè)字符。比如?\t,\a?這些(不包括空格),因?yàn)檫@些字符不可打印而且在某些場景下可能會(huì)消失。
另外一類則是擴(kuò)展 ASCII 碼,即范圍 128-255 的那些字符,它們不屬于 “US-ASCII coded character set”,因此這些字符如果出現(xiàn)在 URI 中,需要被轉(zhuǎn)義。
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.不安全字符
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 節(jié)。因?yàn)榉N種的原因,存在一類字符,它們是 “unsafe” 的,不加處理地存在在 URI 里,會(huì)破壞 URI 的語義完整性,對于這類字符,如果要出現(xiàn)在 URI 里,那么也得進(jìn)行轉(zhuǎn)義。
nginx 的 URI 轉(zhuǎn)義機(jī)制
nginx (以現(xiàn)在最新的 1.13.8 版本為準(zhǔn))提供了一個(gè)名為?ngx_escape_uri?的函數(shù),函數(shù)原型如下:
uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size,ngx_uint_t type);第三個(gè)參數(shù),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我們只關(guān)心其中的?NGX_ESCAPE_URI?,NGX_ESCAPE_ARGS?,NGX_ESCAPE_URI_COMPONENT?,根據(jù) nginx 官方所提供的?nginx 模塊和核心 API 介紹,這三個(gè)宏的含義如下:
TypeDefinitionNGX_ESCAPE_URIEscape a standard URINGX_ESCAPE_ARGSEscape query argumentsNGX_ESCAPE_URI_COMPONENTEscape the URI after the domain
對應(yīng)地,ngx_escape_uri這個(gè)函數(shù),內(nèi)置了幾個(gè)相關(guān)的?bitmap,區(qū)別就是在于各自的轉(zhuǎn)義字符集,具體可以查閱 nginx 的源碼(src/core/ngx_string.c)。
其中針對整個(gè) URI 的轉(zhuǎn)義處理,ngx_escape_uri?會(huì)把?" ", "#", "%", "?"?以及?%00-%1F?和?%7F-%FF?的字符轉(zhuǎn)義;針對?query?的轉(zhuǎn)義,會(huì)把?" ", "#", "%", "&", "+", "?"?以及?%00-%1F?和?%7F-%FF?的字符轉(zhuǎn)義;針對?path?+?query(稱之為 the URI after the domain)的轉(zhuǎn)義,會(huì)把除英文字母,數(shù)字,以及?"-", ".", "_", "~"?這些以外的字符全部轉(zhuǎn)義。
可以看到,NGX_ESCAPE_URI?和?NGX_ESCAPE_ARGS?沒有處理不安全字符,前者站在處理整個(gè)的 URI 的角度上編碼,后者站在處理?query?的角度上編碼;而?NGX_ESCAPE_URI_COMPONENT?,處理角度不是整個(gè) URI,而是 domain 之后的 URI 組件,它兼顧?path?和?query?的保留字符集,更加嚴(yán)格,遵守了?rfc3986#section-2.2?的規(guī)范。
這里順便提一下?ngx_proxy?模塊對應(yīng)的 URI 轉(zhuǎn)義處理,在構(gòu)造向上游發(fā)送的請求行時(shí),ngx_proxy 模塊針對?proxy_pass?指令做出了不同的處理:
- 如果指定的 URI 包含了變量,將解析變量,然后直接將解析后的 URI 發(fā)送到上游;
- 如果 URI 不含變量,且沒有指定?path?部分,將使用客戶端發(fā)來的?path?部分拼接到 URI 中,然后發(fā)送到上游;
- 如果URI 不含變量,且指定了?path,這里的處理比較特殊,nginx 會(huì)把解碼過的,由客戶端發(fā)來的 URI 里的?path?部分(去掉和當(dāng)前?location?的公共前綴),進(jìn)行編碼(按?NGX_ESCAPE_URI?來操作),和?proxy_pass?指令指定 的?path?拼接,發(fā)送到上游,比如這樣的配置:
?
如果客戶端發(fā)來的 URI 里?path?是?/foo/%5B-%5D,最終上游的 URI?path?會(huì)是?/bar/[-]。
因此我們在做 nginx conf 配置的時(shí)候,也需要小心考慮 URI 編碼的問題。
ngx_lua 的 URI 轉(zhuǎn)義機(jī)制
ngx_lua?提供的?ngx.escape_uri?函數(shù),和 nginx 核心的轉(zhuǎn)義機(jī)制也有一些差異(基于 ngx_lua v0.10.11),體現(xiàn)在對保留字符的處理上,ngx.escape_uri底層使用的?ngx_http_lua_escape_uri,結(jié)構(gòu)和?ngx_escape_uri?一致,而對應(yīng)的?bitmap?不同。
對于整個(gè) URI 的轉(zhuǎn)義處理,在?ngx_escape_uri?的基礎(chǔ)上,對?'"', '&', '+', '/', ':', ';', '<', '=', '>', '[', '\', ']', '^', '_', '{' , '}'進(jìn)行轉(zhuǎn)義;對于?query?的處理,這里去掉了?&?的轉(zhuǎn)義;對于?path?+?query?的處理,去掉了對?"'", "*", ")", "(", "!"?的轉(zhuǎn)義。目前?ngx.escape_uri?使用的是?NGX_ESCAPE_URI_COMPONENT,從 PR 提交的信息來看,目前?ngx.escape_uri?的行為和 Chrome JS 實(shí)現(xiàn)的?encodeURIComponent?一致。
另外,ngx_lua 對 URI 的解碼操作,除了它把?+?解碼為空格以外,其他和 nginx 相同。
總結(jié)
在做相關(guān)的代理服務(wù),網(wǎng)關(guān)服務(wù)時(shí),URI 的編解碼處理都是非常重要的,某些場景我們可能需要用 URI 來做 key(比如作為 hash 函數(shù)的因子),如果不處理好編解碼問題,可能在 URI 復(fù)雜的情況下會(huì)達(dá)不到我們的預(yù)期效果,反而會(huì)浪費(fèi)很多時(shí)間去排查問題的原因,特別地,在使用 nginx 和 ngx_lua 做服務(wù)時(shí),我們更應(yīng)該熟知它們在 URI 編解碼上的區(qū)別,在理解它們的區(qū)別上做自身的業(yè)務(wù)處理,避免踩坑。
參考資料
- https://www.ietf.org/rfc/rfc1738.txt
- https://tools.ietf.org/html/rfc2396
- https://tools.ietf.org/html/rfc3986
- https://www.nginx.com/resources/wiki/extending/api/utility/#ngx-escape-uri
- https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
原文閱讀
淺談 URI 及其轉(zhuǎn)義
總結(jié)
以上是生活随笔為你收集整理的浅谈 URI 及其转义的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【推荐】Nginx基础知识之————多模
- 下一篇: Shell脚本中字符串的一些常用操作