今天收到一个 Bug, 一个超级奇怪的人名叫做 Isxxxxa Onxxxna Anton

LinkedIn 居然搜得到这个人全名, 果断隐藏了部分 o( ̄▽ ̄)o

一个已经老掉牙的模块报错, 错误原因是URL 提交参数出错导致后续 JSON 解析出错

email.jsp?name=Isxxxxa Onxxxna Anton&[email protected]

首先很严重的原因 URL 参数里面的空格就应该先被 Encode 掉

然而……很神奇的……

其他一些更是莫名其妙的名字却没有问题……甚至后面那一段怎么看都觉得会引发错误的邮箱字段却一直没有 bug 出现

借此机会对 URL 进行一次深入而系统的学习

URL & URI

what is URL1?

A Uniform Resource Locator (URL), commonly informally termed a web address (a term which is not defined identically) is a reference to a web resource that specifies its location on a computer network and a mechanism for retrieving it. A URL is a specific type of Uniform Resource Identifier (URI), although many people use the two terms interchangeably. A URL implies the means to access an indicated resource, which is not true of every URI. URLs occur most commonly to reference web pages (http), but are also used for file transfer (ftp), email (mailto), database access (JDBC), and many other applications.

这里提到了一句"urluri的特殊形式"

so……what is URI?

In information technology, a Uniform Resource Identifier (URI) is a string of characters used to identify a resource. Such identification enables interaction with representations of the resource over a network, typically the World Wide Web, using specific protocols. Schemes specifying a concrete syntax and associated protocols define each URI. The most common form of URI is the Uniform Resource Locator (URL), frequently referred to informally as a web address. More rarely seen in usage is the Uniform Resource Name (URN), which was designed to complement URLs by providing a mechanism for the identification of resources in particular namespaces.

可以理解为URIURL的父类, URI的目的是指向一个资源, 而URL的目的是引用这个资源

借用 Chokcoco 博客2的一句话:

URI 属于 URL 更低层次的抽象,一种字符串文本标准。

就是说,URI 属于父类,而 URL 属于 URI 的子类。URL 是 URI 的一个子集。

二者的区别在于,URI 表示请求服务器的路径,定义这么一个资源。而 URL 同时说明要如何访问这个资源(http://)。

URI Syntax

URI 的原生格式为

scheme:[//[user:[email protected]]host[:port]][/]path[?query][#fragment]

关于各部分的内容:

PartsComments
scheme这里包含各种协议名, http, https, ftp 等等
协议名大小写不敏感, 但是一般小写简单可观, 同时一些非开放注册的协议可以使用, 例如迅雷的 ed2k, 电驴的协议等等
user name and password一些特殊的协议访问需要携带这些信息
host主机名
port
path
query虽然称作 Query, 但这部分实际就是 URL 参数, 不同情况下可以使用&或者;进行分割
fragment片段, 通过在参数后方放一个#进行判断, 这里会放置一个fragment identifier 来访问当前页面的次级内容, 一般这儿就是放 HTML 元素的 ID

URI 编码

为什么要对URI进行编码3?

实际上就是为了防止歧义, 无歧义的情况下直接输入完全没有问题, 然而更多时候我们需要对一些特定的字符进行转换

URI 编码标准

2005 年 1 月发布的 RFC 3986,强制所有新的 URI 必须对未保留字符不加以百分号编码;其它字符要先转换为 UTF-8 字节序列, 然后对其字节值使用百分号编码。此前的 URI 不受此标准的影响。

URI 文字类型

URI允许接受 2 类文字:

  1. Reserved Characters -- RFC 3986 Reserved Characters (January 2005)

    Encode Required

    ! * ' ( ) ; : @ & = + $ , / ? ## [ ]

  2. Unreserved Characters -- RFC 3986 Unreserved Characters (January 2005)

    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

    a b c d e f g h i j k l m n o p q r s t u v w x y z

    0 1 2 3 4 5 6 7 8 9 - _ . ~

编码之后的文字

beforeafter
!21%
#23%
$24%
&26%
'27%
(28%
)29%
*%2A
+%2B
,%2C
/%2F
:%3A
;%3B
=%3D
?%3F
@40%
[%5B
]%5D

这里解释了为何之前的 Email 没有出现和空格一样的错误, 因为@. 字符都属于保留字符, 并且在最早期编码标准中就已经投入使用, 因此多数语言的编码实现都没有问题

空格?

application/x-www-form-urlencoded类型编辑

当 HTML 表单中的数据被提交时,表单的域名与值被编码并通过HTTPGET或者POST方法甚至更古远的 email[2]把请求发送给服务器。

这里的编码方法采用了一个非常早期的通用的 URI 百分号编码方法,并且有很多小的修改如新行规范化以及把空格符的编码"%20"替换为"+" . 按这套方法编码的数据的 MIME 类型是application/x-www-form-urlencoded, 当前仍用于(虽然非常过时了)HTML 与 XForms 规范中. 此外,CGI 规范包括了 web 服务器如何解码这类数据、利用这类数据的内容。

关于编程习惯

其实看到这里, 一开始的问题就已经有解决方案了, 就是将那个奇怪的人名中的空格进行百分号编码, 然后提交到服务器或者其他地方直接使用即可

但是这只是一个 temp solution, 出错的页面迟早要进行 redesign。 不过为了保证程序后期的健壮性, 依然建议 URL 中不要放置URI 编码标准中无法接受的字符(即除去上述两类字符之外的字符)

同时将空格转为下划线也是个不错的选择

参考文献

Wikipedia - Uniform Resource Locator

Chokcoco - URL 详解与 URL 编码

Wikipedia - Percent-encoding