基于RFC 3986(统一资源描述符(URI): 通用语法)

摘要

统一资源标识符是标识抽象或物理资源的精简字符序列。规范定义了通用的URI语法和解析URI引用的过程,以及在Internet上的使用指南和安全性考虑。URI语法是所有有效URIs的超集,定义了URL引用的公共组件,不需要知道每种标识符方案的特殊要求。uri的生成语法由每个URI方案的单独规范执行。

1. 介绍

通用资源标识符提供一种简单且可扩展的标识资源的方法。这份规范中的URI语法和语义的来自万维网全球信息计划,这些标识符的使用可以追溯到1990年,在“Universal Resource Identifiers in WWW”[RFC1630]中有描述。该语法满足以下两个文档提出的要求 “Functional Recommendations for Internet Resource Locators” [RFC1736] and “Functional Requirements for Uniform Resource Names” [RFC1737].

这份文档废弃了 [RFC2396](它合并了[RFC1738]和[RFC1808]两份文档);废弃了[RFC2732](IPv6地址语法);排除了RFC 1738关于特定URI schemes的语法并放到特定的文档中。注册新URI schemes的方法单独定义于[BCP35]。对于新URI方案设计者的建议可以在[RFC2718]中找到。RFC 2396的所有重要变化都在附录D中注明。

这份规范使用[BCP19]定义的术语“字符”和“字符集”,以及使用“字符编码”来代替[BCP19]所说的“字符集”。

万维网全球信息计划:World Wide Web global information initiative

1.1 uri概述

URIs 特征如下:

统一的
统一提供了以下几方面的好处:
允许在同一上下文中使用不同类型的资源标识符,即使访问这些资源的机制可能有所不同。
允许对不同类型的资源标识符的语法做统一的语义解释。
允许引入新的资源标识符类型,而不会干扰现有标识符的使用方式。
允许标识符在许多不同的上下文中被重用,从而允许新的应用程序或协议利用一个预先存在的、大型的、广泛使用的资源标识符集。

资源
这个规范不限制资源的范围;
更确切的说,术语“资源”用于URI可能标识的任何内容。
熟悉的例子包括电子文档、图像、具有一致目的的信息源(例如,“洛杉矶今天的天气报告”)、服务(例如,HTTP-to-SMS网关)和其他资源的集合。资源不一定能通过互联网获取到;例如人,公司,图书馆的书籍也可以是资源。
同样,抽象的概念也可以是资源,例如数学方程的操作符和操作数,关系的类型(例如,“父”或“雇员”),或数值(例如,零、一和无穷大)。

操作数:
操作数是操作符作用于的实体,
是表达式中的一个组成部分,
它规定了指令中进行数字运算的量 。
表达式是操作数与操作符的组合。

例如汇编 MOV AH,02
将02放到寄存器AH中,MOV是操作符,02和AH都是操作数。

标识符
标识符包含了标识的内容和分隔符。术语“identify”和“identifying”指的是将一种资源和其他资源区分开的目的,无论这种目的是如何实现的(例如,通过姓名、地址或上下文)。不要错误的认为对引用对象的标识就是标识符,这仅是标识符的部分功能。系统使用uri并不代表资源标识符能够被访问:许多情况下uri仅表示资源,与访问无关。标识符表示的资源可能是单一的,也可能是一个name set,也可能是一个随时间推移的映射。

URI 语法见 section3。URI通过单独定义一个可扩展的模式(section 3.1)集合支持资源的统一识别。

这份规范对资源的性质不做限制,可能用于引用资源,或用于标识资源。本规范不要求URI在一段时间内持续标识相同的资源,尽管这是所有URI方案的共同目标。应用程序可以限制资源的类型或维护应用程序所需特征的URIs子集。

URIs在全球范围内任何的上下文中得到相同的解释,尽管根据最终用户的不同会得到不同的结果。比如:“http://localhost/”。

1.1.1 通用语法

每个URI都是以方案名称开头,定义于Section 3.1。URI语法是个插件化可扩展的命令系统。其中每个方案的规范可能进一步限制使用该方案的标识符的语法和语义。

这份规范定义了所有方案共有的元素,定义了解析URI引用所需要的语法和语义,而具体方案相关的处理推迟到需要的时候。标识符方案的演化和URI协议、数据格式和实现是解耦的。

通用URI语法解析器可以解析URI引用的主要组件。一旦确定了模式,可以进一步使用具体方案对组件进行解析。换句话说,URI通用语法是所有URI模式语法的超集。

1.1.2 举例

下面的示例URI说明了几种URI模式及其公共语法组件的变体:

ftp://ftp.is.co.za/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc2396.txt
ldap://[2001:db8::7]/c=GB?objectClass?one
mailto:John.Doe@example.com
news:comp.infosystems.www.servers.unix
tel:+1-816-555-1212
telnet://192.0.2.16:80/
urn:oasis:names:specification:docbook:dtd:xml:4.1.2
1.1.3. URI, URL 和 URN

URI可进一步分类为定位器名称或两者都是。
定位器:术语“URL”是URIs的子集,通过描述资源的访问机制,定位资源(例如:network “location”)。
名称:术语“URN”包含两个URIs集合方案详见[RFC2141],这两个URIs需要保持全局唯一性和持久,即使资源不存在或不可用时。所有具有名称属性的URI都要保持全局唯一性和持久。

任何方案的uri实例可能即是“name”也是“locator”。我们应该使用术语“URI”,而不是限制更强的“URL”和“URN”。

1.2 设计注意事项

1.2.1 转录

因为每个地区语言都有差异,所以URI语法设计时的主要考虑因素之一是全球转录。URI的字符序列来自一个有限的集合:26字母,数字,几个特殊字符。

转录的目的可以用一个简单的场景来解释:想象两个同事,Sam和Kim,坐在一个国际会议的酒吧里,交换研究想法。Sam向Kim询问一个location以获得更多信息,因此Kim将研究站点的URI写在一张餐巾纸上。回到家后,Sam拿出餐巾并在计算机中输入URI,然后计算机检索Kim引用的信息。

这个场景反映出几个设计注意事项:

  • URI 是一个字符序列,它并不总是表示为一个字节序列。
  • URI可能是从非网络源转录而来的,因此输入到计算机中的字符应该能够跨语言和地区。
  • URI通常需要人们记住,当URI由有意义的或熟悉的组件组成时,人们更容易记住它。

输入到计算机中的字符能够跨语言地区是最重要的,对于US-ASCII以外的字符在section2.1提供了通过**%-编码**将字符映射成八位元(octets)的方式。

1.2.2 从交互中分离标识
交互:举例说明,请求/响应。
dereference:解引用,通过URI确定访问及之后,使用该访问机制对URI资源执行操作
retrieval:检索,利用URI来查找相关资源的“表示”,检索由进程实现。
representation:“表示”是一个字节序列。

URIs只用于引用可访问资源的理解是错误的。URI只提供标识;URI即不保证也不暗示着能否访问资源。相反,与URI引用相关的操作由协议元素,数据格式属性和URI中出现的自然语言文本定义的。

给与URI,系统可能试图对资源执行各种操作,比如访问,更新,替换。这些操作由协议决定而不是URI规范。然而,的确使用了一些通用术语来描述uri上的通用操作。比如:URI “resolution”,意味着确定访问机制和URI解引用后得到合适的参数;解析可能需要多次的迭代。

解引用最常见的形式是 “检索”,URI解引用在信息检索系统中被设计为延迟绑定:访问的结果通常在访问时确定,随着时间或交互的其他方面而变化。

尽管许多URI方案以协议命名,但是这些URI并不一定通过命名的协议访问资源,URI通常只做标识。即使是检索的场景,访问可能通过网关、代理、缓存和DNS进行的,这些服务独立于方案名称相关的协议。某些URI的解析可能需要使用多种协议。

1.2.3 分层标识符

URI 语法是有组织的层级结构,组件按重要程度从左向右排列。某些模式,方案组件分隔符 “:” 之后的内容对URI处理是不透明的。大部分URI模式对通用解析算法是透明的。

通用语法使用一些常见的符号 “/” “?” “#” 作为组件的分隔符,这对理解标识符的层次结构非常重要。

通常会为了一个共同得目的而构造一组文档或者一颗文档树,绝大多数资源都在树内。站点内文档会互相引用,此时需要一个相对引用。此外,这样的文档树可以作为一个整体移动,而不需要改变任何相关的引用。

相对引用(Section 4.2)通过引用上下文和目标URI的区别来描述被引用的资源。相对引用解析算法,存在于section 5,定义了如何将相对引用转换为目标URI。相对引用只能在分层级URI的上下文中使用,新URI方案的设计者应该使用通用语法的分层结构组件,除非强制禁止在该方案中使用相对引用。

相对引用不是URIs的子集,而是引用uri的一种方法。

所有的URI引用使用通用语法解析器解析。绝对引用不受分层处理影响,除非包含dot-segments(见 section3.3)。

1.3 语法表示法

本规范使用ABNF[RFC2234],可以使用的字符如下:ALPHA (letters), CR (carriage return), DIGIT (decimal digits), DQUOTE (double quote), HEXDIG (hexadecimal digits), LF (line feed), and SP (space).完整的URI语法见[Appendix A]。

2. 字符

URI 由一组有限的字符集组成:数字 字母 一些图形符号.
这些字符分成保留字符和非保留字符。
保留字符分成分隔符和非分隔符。
分隔符用于分隔URI的语法组件,其他字符表示数据。

2.1 %-编码

如果该octet不在指定字符集范围内或者作为分隔符在组件使用,可以使用%-编码机制用于表示组件中的octet数据。

举例:
00100000(SP  %x20)--> `%20`
octet(US-ASCII) --> 字符三元组

公式:
      pct-encoded = "%" HEXDIG HEXDIG

HEXDIG:十六进制

十六进制字母不分大小写。建议URI生成器规格化器%-编码使用大写十六进制和数字。

2.2 保留字符

保留字符是URIs组件和子组件的分隔符,如果保留字符想要作为数据使用必须进行%-编码

   	  reserved    = gen-delims / sub-delims
      gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
      sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
                  / "*" / "+" / "," / ";" / "="

保留字符提供了一组分隔字符,它们与URI中的其他数据不同。在数据中使用保留字符需进行%-编码

gen-delims是section3描述的通用URI组件的分隔符。保留集中的任何字符都可以在组件中作为子组件分隔符。本规范定了最常见的公共子组件;如果在URI模式规范或者URI解引用算法中自定义子组件需要使用保留字符分隔。

2.3 非保留字符

URI中不作为分隔符使用的字符称为无保留字符,包括大写字母和小写字母、十进制数字、连字符、句点、下划线和波浪号。

      unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"

非保留字符和替换成%-编码的US-ASCII字节是等价的。
%-编码与非保留字符转换关系:

ALPHA (%41-%5A and %61-%7A)  DIGIT (%30-%39)  hyphen (%2D)
period (%2E)  underscore (%5F)  tilde (%7E)

从组件生成URI的过程中才会进行%-编码
解引用过程中需要先解析组件和子组件,%-编码的字节随时可以解析。

2.5 标识符数据

URI字符为每个URI组件提供标识数据,作为系统之间标识的外部接口。URI的产生和传输过程中涉及到多种字符编码:本地名称、数据编码、公共接口编码、URI字符编码、数据格式编码和协议编码。

本地名称 Local names:文件系统名,使用本地字符编码。产生URI的应用程序通常使用本地名称,URI生成器将本地编码转换成适合公共接口的编码,然后将公共接口编码转换为受限制的URI字符集(保留、不保留和%-编码)。这些字符依次编码为octets在互联网协议中传输。

应用程序 --> HTTP contect-type 类型 --> %-编码
本地编码 --> 公共接口编码 --> URI字符编码

对于大多数系统,URI组件中的无保留字符使用US-ASCII编码,字母“X”对应八位组“01011000”。

非保留字符的UTF-8编码使用octet编码,非UTF-8编码使用%-编码。拉丁大写字母A使用"%C3%80",片假名字母A使用"%E3%82%A2"。

3. 语法组件

通用URI语法由以下5个组件组成:方案、权限、路径、查询和的片段

URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

hier-part   = "//" authority path-abempty
            / path-absolute
            / path-rootless
            / path-empty


URI: URL + URN
foo://example.com:8042/over/there?name=ferret#nose
   \_/   \______________/\_________/ \_________/ \__/
    |           |            |            |        |
 scheme     authority       path        query   fragment
    |   _____________________|__
   / \ /                        \
   urn:example:animal:ferret:nose

组件scheme和path是必须的,虽然路径可以是空的。有权限时,路径必须为空或“/”开头。当权限不存在,路径不能以“//”开头。这些限制导致了5种不同的ABNF规则(Section 3.3)。

3.1. 模式

每个URI都以一个模式名称开头,每个模式名称对应了不同的标识符规范。因此,URI语法是一个可组合可扩展的命名系统,其中每个模式的规范进一步限制该模式的标识符的语法和语义。

方案名称由以字母开头,字母、数字、“+”、“.”或“-”的任意组合组成,字母规范使用小写,为了健壮性,不分大小写。

scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

URI模式规范定义的语法必须匹配语法标准,见Section4.3。

3.2 授权

通用语法提供了一种基于主机(注册名称或服务器地址)、端口(可选)、用户信息来区分权限的方法。

授权组件的前面有一个//,并以下一个/?#字符结束,或者以URI结尾结束。

authority   = [ userinfo "@" ] host [ ":" port ]

如果端口组件为空,URI生成器和标准化器应该省略:分隔符,该分隔符用于分隔主机和端口。有些方案不允许用户信息和/或端口子组件。

如果URI包含授权组件,那么路径组件必须为空或以/开头。非验证解析器(将URI引用分隔为主要组件的解析器)通常会忽略授权的子组件结构,将其视为从\\到第一个终止分隔符的不透明字符串,直到URI解引用时再解析。

3.2.1 用户信息

用户信息子组件可能由用户名称可选项以及具体方案的授权信息组成。如果存在用户信息使用@与主机分隔

userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )

不赞成使用"user:password"格式,明文传递身份信息很危险。

3.2.2. 主机

主机子组件不分大小写。许多情况下主机使用DNS作为注册中心;在其他情况下,主机组件中的数据标识一个与Internet主机无关的注册名称。主机组成如下:

host        = IP-literal / IPv4address / reg-name

主机应用了“先匹配再匹配”算法:如果host匹配IPv4地址的规则,那么它应该被认为是IPv4地址字面量,而不是reg-name。host不区分大小写,为了统一起见,使用小写的注册名称和十六进制的地址,使用大写字母的百分比编码。

主机ip字面量地址,使用于ipv6即以上的版本,将IP文字用方括号括起来[],规则如下:

      IP-literal = "[" ( IPv6address / IPvFuture  ) "]"

      IPvFuture  = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
      IPvFuture:未来版本

IPvFuture不表示IP版本;表示未来的字面量格式的版本。

使用IPv6标识的主机在方括号中表示。一个128位的IPv6地址被分成8个16位的地址段。每部分用不分大小写的十六进制数字表示,使用1到4个十六进制数字(允许前导零),使用“:”分隔。值全为0的地址段使用"::"代替。8个地址段中先给出重要的部分,不重要的两个地址段,可以使用ipv4代替。

      IPv6address =                            6( h16 ":" ) ls32
                  /                       "::" 5( h16 ":" ) ls32
                  / [               h16 ] "::" 4( h16 ":" ) ls32
                  / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
                  / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
                  / [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
                  / [ *4( h16 ":" ) h16 ] "::"              ls32
                  / [ *5( h16 ":" ) h16 ] "::"              h16
                  / [ *6( h16 ":" ) h16 ] "::"

      ls32        = ( h16 ":" h16 ) / IPv4address
                  ; 最不重要的的32位地址

      h16         = 1*4HEXDIG
                  ; 用十六进制表示的16位地址
                  

使用IPv4标识主机,使用四个octet的点式十进制标识:4个十进制数字,用"."分隔,取值范围0~255。
例如:255.255.255.255

 IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
 dec-octet   = DIGIT                 ; 0-9
             / %x31-39 DIGIT         ; 10-99
             / "1" 2DIGIT            ; 100-199
             / "2" %x30-34 DIGIT     ; 200-249
             / "25" %x30-35          ; 250-255

使用注册名称标识主机,注册名称通常用于在本地或注册中心查找主机。最常见的名称注册机制是DNS。

该名称由一系列域标签组成由“.”分隔,每个域标签以数字、字母、“-”开头结尾。

例如:www.rfc-editor.org
reg-name    = *( unreserved / pct-encoded / sub-delims )

有些协议,例如“file”,authority或host组件允许为空;而“http”,authority,host不允许为空。

URI解析可以使用DNS、主机表、黄页、NetInfo、WINS或任何其他系统来查找已注册的名称。使用DNS可以使uri全球有效。URI生成器应该使用符合DNS语法的名称,即使在DNS的使用并不明显的情况下,并该将这些名称的长度限制在不超过255个字符。

注册名称允许%-编码来表示non-ascii字符,non-ascii字符必须首先按照UTF-8进行编码,然后对应的UTF-8序列的每个字节进行百分比编码。当使用non-ASCII注册的名称表示DNS解析国际化域名时,在查找名称之前,名称须转换为IDNA编码。若URI生产者想要最大程度兼容URI解析器遗留的DNS问题,优先使用IDNA编码

3.2.3 端口

端口可选,使用:与host分隔

port        = *DIGIT
DIGIT    十进制数组

模式可能有默认端口,
例如:
HTTP:80

3.3 路径

路径组件,于第一个?#终止,或在URI的末尾。

若URI包含授权组件,路径组件必须以/开头,若不包含授权组件以//开头。当URI引用是相对路径时,第一个路径段不能包含:

 path          = path-abempty    ; begins with "/" or is empty
               / path-absolute   ; begins with "/" but not "//"
               / path-noscheme   ; begins with a non-colon segment
               / path-rootless   ; begins with a segment
               / path-empty      ; zero characters

 path-abempty  = *( "/" segment )
 path-absolute = "/" [ segment-nz *( "/" segment ) ]
 path-noscheme = segment-nz-nc *( "/" segment )
 path-rootless = segment-nz *( "/" segment )
 path-empty    = 0<pchar>
 segment       = *pchar
 segment-nz    = 1*pchar
 segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
               ; non-zero-length segment without any colon ":"

 pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"

路径由连续的路径分片组成,由/分隔。路径是为URI定义的,路径分片...又称点分片(dot-segments),用于表示名称层次树的相对位置。点分片的解析过程见 section5.2。

除了点分片,路径分片对于通用语法是不透明的。在路径分片不同模式可以使用保留字符自定义子组件。

3.4 查询

查询组件包含非分层的数据,服务于模式,授权,路径组件。以第一个?开始,以#结束,或者在URI的末尾。

query       = *( pchar / "/" / "?" )

查询组件通常用于携带key=value形式的标识信息。有时避免对这些字符进行%-编码可用性更好。

?from=en&to=zh-CHS&api-version=3.0

3.5. 片段(原文没看懂)

片段标识符组件以#开始并且URI到此结束。

fragment    = *( pchar / "/" / "?" )
可用于辅助检索资源,例如
https://www.rfc-editor.org/rfc/rfc3986.html#section-5.2.2

4.1 URI 引用

URI引用最常表现为资源标识符

URI-reference = URI / relative-ref

URI引用有两种形式URI相对引用。如果URI引用前缀不匹配模式并随后用:分隔,则表示相对引用。

URI引用通常先解析为五个URI组件,根据组件是否存在判断是不是相对引用。然后每个组件解析成子组件并校验。URI引用解析规则见 Appendix B。

4.2 相对引用

语法如下:

relative-ref  = relative-part [ "?" query ] [ "#" fragment ]

relative-part = "//" authority path-abempty
              / path-absolute
              / path-noscheme
              / path-empty

通过相对引用,也能获取目标URI。解析算法见Section5。

相对引用开始于//被称为network-path引用;这样的引用很少使用。开始于/称为绝对路径引用。没有/开头称为相对路径引用。

相对引用的第一个路径段不能包括:(比如 this:that),会被误认为模式名称,这样的路径段前面要加 ./(./this:that)

4.3 绝对URI

标识符不包括分段的部分是绝对URI。

absolute-URI  = scheme ":" hier-part [ "?" query ]

4.4 统一文档中的引用

举例如下:

https://www.rfc-editor.org/rfc/rfc3986.html#section-5.1.1

4.5 后缀引用

省略前缀的引用

www.w3.org/Addressing/

许多客户机实现允许用户输入后缀引用并启发式地解析它们(大多以 www 开头的引用都是以URI前缀http://)。
但是我们要尽量避免使用后缀引用,万一规范发生变化或者新的模式出现,会带来一些兼容性问题。

5 引用解析

允许相对引用通过上下文解析得到目标URI。

5.1 建立基础URI

相对引用意味着基础URI。除了 fragment-only 引用(section4.4),相对引用只能在基础URL已知的情况下使用。
引用解析器在解析相对引用之前必须建立基础URI基础URI必须符合绝对URI语法规则(section4.3)。如果基础URI从URI引用中获取,那么这个引用必须先转换成绝对形式,并去掉片段组件。

引用的基础URI可以通过4种方式创建,下面按获取优先级讨论。如下图,越内层优先级最高:

.----------------------------------------------------------.
|  .----------------------------------------------------.  |
|  |  .----------------------------------------------.  |  |
|  |  |  .----------------------------------------.  |  |  |
|  |  |  |  .----------------------------------.  |  |  |  |
|  |  |  |  |       <relative-reference>       |  |  |  |  |
|  |  |  |  |             相对引用              |  |  |  |  |
|  |  |  |  `----------------------------------'  |  |  |  |
|  |  |  | (5.1.1) Base URI embedded in content   |  |  |  |
|  |  |  `----------------------------------------'  |  |  |
|  |  | (5.1.2) Base URI of the encapsulating entity |  |  |
|  |  |         (message, representation, or none)   |  |  |
|  |  `----------------------------------------------'  |  |
|  | (5.1.3) URI used to retrieve the entity            |  |
|  `----------------------------------------------------'  |
| (5.1.4) Default Base URI (application-dependent)         |
`----------------------------------------------------------'
5.1.1 嵌在上下文中的基础URI

在某些媒体类型中,相对引用的基础URI嵌在上下文中。常用于描述性文档,比如目录。
详见附录C.在上下文中界定URI

5.1.2 封装于实体中的基础URI

如果上下文中没有嵌入基础URI,则基础URI表示检索上下文定义。比如消息,实体就是检索上下文。当表示被实体封装,表示默认基础URI就是实体基础URI

检索:利用URI来检索相关资源的表示
表示:是一个字节序列
实体:例如HTTP的消息,消息的请求行包含URI。

在MIME容器类型中嵌入基础URI的机制定义于MHTML [RFC2557]。不使用MIME消息头语法的协议,可以在消息头中自定义。

5.1.3 来自检索URI的基础URI

5.1.1 5.1.2两个条件均不符合,如果使用URI检索表示,该URI应被视为基础URI。如果检索URI是请求重定向的结果,使用最后一个URI作为基础URI。

5.1.4 默认基础URL

如果上述条件均不适用,则基础URI由应用程序的上下文定义。因为这个定义依赖于应用程序,可能导致相同URI在不同应用程序得到不同的解释。

包含相对引用表示的发送方负责建立这些引用的基础URI

5.2 引用分解

本文提供了转换URI引用的算法,目标引用可能基于基本URI来解析组件。组件可以重组(定义于section5.3)成目标URI。应用如果实现了相对引用解析算法可以通过本算法提供的结果进行检验。

5.2.1 预解析基础URL

基础URL依据section5.1建立,然后按照section3解析成组件。基础URI必须有方案组件,其他组件可能为空或未定义。如果组件分隔符未出现表示未定义;path组件可能为空但不允许未定义。

标准化基础URL见章节Sections6.2.2和6.2.3(可选)。URI引用必须转换为目标URI才能标准化

5.2.2 转换引用(包含伪代码)

将URI引用转换成目标URI。

R:URI引用
T:目标URI

      -- The URI reference is parsed into the five URI components
      -- URI引用解析为5大组件。
      (R.scheme, R.authority, R.path, R.query, R.fragment) = parse(R);

      -- A non-strict parser may ignore a scheme in the reference
      -- if it is identical to the base URI's scheme.
      -- 非严格解析器可能会忽略引用中的方案,如果它与基础URI中方案相同
      if ((not strict) and (R.scheme == Base.scheme)) then
         undefine(R.scheme);
      endif;
      
      if defined(R.scheme) then
         T.scheme    = R.scheme;
         T.authority = R.authority;
         T.path      = remove_dot_segments(R.path);
         T.query     = R.query;
      else
         if defined(R.authority) then
            T.authority = R.authority;
            T.path      = remove_dot_segments(R.path);
            T.query     = R.query;
         else
            if (R.path == "") then
               T.path = Base.path;
               if defined(R.query) then
                  T.query = R.query;
               else
                  T.query = Base.query;
               endif;
            else
               if (R.path starts-with "/") then
                  T.path = remove_dot_segments(R.path);
               else
                  T.path = merge(Base.path, R.path);
                  T.path = remove_dot_segments(T.path);
               endif;
               T.query = R.query;
            endif;
            T.authority = Base.authority;
         endif;
         T.scheme = Base.scheme;
      endif;

      T.fragment = R.fragment;
5.2.3 合并路径
  • 如果基础URI有一个已定义的授权组件和一个空路径,则返回一个由/和路径连接而成的字符串。
  • 将路径追加到基础路径的最后一段
merge("http://127.0.0.1/a/b/c", "/hello/world")
return 'http://127.0.0.1/a/b/hello/world'
5.2.4 移除点分片
      步骤   输出BUFFER            输入 BUFFER

       1 :                         /a/b/c/./../../g
       2E:   /a                    /b/c/./../../g
       2E:   /a/b                  /c/./../../g
       2E:   /a/b/c                /./../../g
       2B:   /a/b/c                /../../g
       2C:   /a/b                  /../g
       2C:   /a                    /g
       2E:   /a/g

      步骤   输出BUFFER            输入BUFFER
       1 :                         mid/content=5/../6
       2E:   mid                   /content=5/../6
       2E:   mid/content=5         /../6
       2C:   mid                   /6
       2E:   mid/6

./ 代表当前路径
../代表上一级路径

5.3. 组件重组

伪代码

      result = ""

      if defined(scheme) then
         append scheme to result;
         append ":" to result;
      endif;

      if defined(authority) then
         append "//" to result;
         append authority to result;
      endif;

      append path to result;

      if defined(query) then
         append "?" to result;
         append query to result;
      endif;

      if defined(fragment) then
         append "#" to result;
         append fragment to result;
      endif;

      return result;

5.4. 引用分解案例

表示基础URL: http://a/b/c/d;p?q
一个相对引用被转换为目标URI,如下所示。

5.4.1 正常案例
      "g:h"           =  "g:h"
      "g"             =  "http://a/b/c/g"
      "./g"           =  "http://a/b/c/g"
      "g/"            =  "http://a/b/c/g/"
      "/g"            =  "http://a/g"
      "//g"           =  "http://g"
      "?y"            =  "http://a/b/c/d;p?y"
      "g?y"           =  "http://a/b/c/g?y"
      "#s"            =  "http://a/b/c/d;p?q#s"
      "g#s"           =  "http://a/b/c/g#s"
      "g?y#s"         =  "http://a/b/c/g?y#s"
      ";x"            =  "http://a/b/c/;x"
      "g;x"           =  "http://a/b/c/g;x"
      "g;x?y#s"       =  "http://a/b/c/g;x?y#s"
      ""              =  "http://a/b/c/d;p?q"
      "."             =  "http://a/b/c/"
      "./"            =  "http://a/b/c/"
      ".."            =  "http://a/b/"
      "../"           =  "http://a/b/"
      "../g"          =  "http://a/b/g"
      "../.."         =  "http://a/"
      "../../"        =  "http://a/"
      "../../g"       =  "http://a/g"
5.4.2 异常案例
      "../../../g"    =  "http://a/g"
      "../../../../g" =  "http://a/g"

      "/./g"          =  "http://a/g"
      "/../g"         =  "http://a/g"
      "g."            =  "http://a/b/c/g."
      ".g"            =  "http://a/b/c/.g"
      "g.."           =  "http://a/b/c/g.."
      "..g"           =  "http://a/b/c/..g"

      "./../g"        =  "http://a/b/g"
      "./g/."         =  "http://a/b/c/g/"
      "g/./h"         =  "http://a/b/c/g/h"
      "g/../h"        =  "http://a/b/c/h"
      "g;x=1/./y"     =  "http://a/b/c/g;x=1/y"
      "g;x=1/../y"    =  "http://a/b/c/y"

      "g?y/./x"       =  "http://a/b/c/g?y/./x"
      "g?y/../x"      =  "http://a/b/c/g?y/../x"
      "g#s/./x"       =  "http://a/b/c/g#s/./x"
      "g#s/../x"      =  "http://a/b/c/g#s/../x"

      "http:g"        =  "http:g"         ; 严格模式
                      /  "http://a/b/c/g" ;   兼容过去版本

6.标准化和比较

URIs最常见的操作是简单比较确定两个uri是否相等。比较之前会标准化uri以减少重复请求或响应存储。

本节描述uri比较的各种方法、它们之间的优缺点以及可能使用场景

6.1 等价

因为URIs是用来标识资源的,等价意味着标识相同的资源。确定URI的等价性或差异是基于字符串比较的。

尽管两个URLs等价,也有可能通过URLs比较无法识别两个资源是否相等。如果两个不同域名提供相同的资源,可能会产生不同的URLs。比较方法在设计时要最小化或避免这种情况。

在等效测试中,应用不应该直接比较相对引用;引用转换成各自的目标URLs后进行比较。当URLs与网络操作(检索表示,例如HTTP请求)进行比较时,应该去除片段组件后再进行比较。

6.2 比较的发展阶段

假阴性:false negatives,应该返回 true,结果返回了false

比较的发展阶段,依据所需的处理量和假阴性概率来区分。假阴性概率越低,需要的处理量越大,应用程序应该在两者之间取舍。

我们以处理量小假阴性概率高的实现开始向处理量大假阴性概率低的方向进行讨论。

6.2.1 简单字符串比较

如果两个uri字符串相同的,则认为它们是等价的。
两个URI字符串转换成公共字符编码后再进行字符的逐一比较。URI使用别名会导致假阴性,生成URI引用要标准化。
大部分应用程序会使用这种比较方式。

6.2.2 基于语法的标准化
example://a/b/c/%7Bfoo%7D
eXAMPLE://a/./b/../b/%63/%7bfoo%7d

不区分大小写,移除./../ 详见章节5.2.4,%-编码标准化。
使用场景:Web用户代理(如浏览器)请求缓存时使用。

6.2.3 基于模式的标准化

以下四种URL时等价的。

http://example.com
http://example.com/
http://example.com:/
http://example.com:80/

例如:如果不存在端口子组件,会使用模式对应的默认端口。对于模式http,默认端口为80。此时URL等价规则详见具体模式的语法规则。

6.2.4 基于协议的标准化

由模式的解引用的算法决定。

附录A.URI的加强型巴科斯范式

URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

hier-part     = "//" authority path-abempty
              / path-absolute
              / path-rootless
              / path-empty

URI-reference = URI / relative-ref

absolute-URI  = scheme ":" hier-part [ "?" query ]

relative-ref  = relative-part [ "?" query ] [ "#" fragment ]

relative-part = "//" authority path-abempty
              / path-absolute
              / path-noscheme
              / path-empty

scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

authority     = [ userinfo "@" ] host [ ":" port ]
userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
host          = IP-literal / IPv4address / reg-name
port          = *DIGIT

IP-literal    = "[" ( IPv6address / IPvFuture  ) "]"

IPvFuture     = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )

IPv6address   =                            6( h16 ":" ) ls32
              /                       "::" 5( h16 ":" ) ls32
              / [               h16 ] "::" 4( h16 ":" ) ls32
              / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
              / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
              / [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
              / [ *4( h16 ":" ) h16 ] "::"              ls32
              / [ *5( h16 ":" ) h16 ] "::"              h16
              / [ *6( h16 ":" ) h16 ] "::"

h16           = 1*4HEXDIG
ls32          = ( h16 ":" h16 ) / IPv4address
IPv4address   = dec-octet "." dec-octet "." dec-octet "." dec-octet

dec-octet     = DIGIT                 ; 0-9
              / %x31-39 DIGIT         ; 10-99
              / "1" 2DIGIT            ; 100-199
              / "2" %x30-34 DIGIT     ; 200-249
              / "25" %x30-35          ; 250-255

reg-name      = *( unreserved / pct-encoded / sub-delims )

path          = path-abempty    ; begins with "/" or is empty
              / path-absolute   ; begins with "/" but not "//"
              / path-noscheme   ; begins with a non-colon segment
              / path-rootless   ; begins with a segment
              / path-empty      ; zero characters

path-abempty  = *( "/" segment )
path-absolute = "/" [ segment-nz *( "/" segment ) ]
path-noscheme = segment-nz-nc *( "/" segment )
path-rootless = segment-nz *( "/" segment )
path-empty    = 0<pchar>

segment       = *pchar
segment-nz    = 1*pchar
segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
              ; 不包含 ":"

pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"

query         = *( pchar / "/" / "?" )

fragment      = *( pchar / "/" / "?" )

pct-encoded   = "%" HEXDIG HEXDIG

unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
reserved      = gen-delims / sub-delims
gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
              / "*" / "+" / "," / ";" / "="

附录B.用正则表达式解析URI引用

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 12            3  4          5       6  7        8 9

举例

http://www.ics.uci.edu/pub/ietf/uri/#Related

$1 = http:
$2 = http
$3 = //www.ics.uci.edu
$4 = www.ics.uci.edu
$5 = /pub/ietf/uri/
$6 = <undefined>
$7 = <undefined>
$8 = #Related
$9 = Related

与URL通用组件的对应关系

scheme    = $2
authority = $4
path      = $5
query     = $7
fragment  = $9

我们可以使用第5.3节的算法从它的组件重组为URI引用。

附录C.在上下文中界定URI

传输URL的上下文多种多样。许多时候URL包含在纯文本中。例如:电子邮件,网络新闻,印刷的纸张中。我们需要将URL和文本的其他内容区分开。

通常使用 "",<>区分URL。

例如,以下文本

      Yes, Jim, I found it under "http://www.w3.org/Addressing/", but you 
can probably pick it up from <ftp://foo.example.com/rfc/>.  Note the
 warning in <http://www.ics.uci.edu/pub/ietf/uri/historical.html#WARNING>.

包含了如下URI引用

http://www.w3.org/Addressing/
ftp://foo.example.com/rfc/
http://www.ics.uci.edu/pub/ietf/uri/historical.html#WARNING

未完待续

7. 安全风险点

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值