当前位置: 首页 > 科技观察

识破%20这个字符的奥秘,百分号代码和后面的%20

时间:2023-03-22 00:26:15 科技观察

前言里提到了这个%20,想必大家都见过,熟悉coding的就知道这个东西是从空间!那我们一起破解吧,怎么编码呢?今天我们继续学习前端编码知识,其他编码文章:前端Base64编码知识,一网打尽,探究本源,追寻localStorage灵魂的五个问题。5M空间??10M!!!字母a的6种表示方式,以及它们背后的编码知识,后面的UTF-16编码和UTF-8编码前端所需的编码基础知识体系基本形成。Unicode基础知识Unicode只是一个字符集,它为每个字符提供了一个编号,我们称之为代码点。Unicode可以使用三种编码,分别是:UFT-8:一种变长编码方案,使用1到6个字节进行存储。UTF-16:对于码位小于0xFFFF(65535)的字符,用2个字节存储,否则用4个字节存储。UFT-32:一种定长编码方案,无论字符数大小,始终使用4个字节进行存储。所以UTF-8和UTF-16都是变长编码方案,而UTF-32是定长编码方案。定长编码方案的优点当然是简单,缺点是比较耗空间,这就是为什么会有UTF-16和UTF-8的原因。网络传输常用UTF-8,javascript运行时的字符编码为UTF-16。我们看看%20是怎么来的,又是怎么得到这个%20的:escape("")"%20"encodeURI("")"%20"encodeURIComponent("")"%20"是十六进制格式的值字符,就是百分号,后面会详细介绍。这段代码如何获取,写一个简单的方法你就明白了3两种方法可以获得相同的值。很少有人告诉你esacpe是基于UTF-16的,而另外两个是基于UTF-8的。举个例子:0-0xFF的代码点范围导致相同的代码点范围。0xFF上面,结果是不一样的,原理我们后面讲。escape("")//%20encodeURI("")//%20escape("person")//"%u4EBA"encodeURI("person")//"%E4%BA%BA"escape("𣑕")//%uD84D%uDC55encodeURI("𣑕")//"%F0%A3%91%95"总结:转义,encodeURI和encodeURIComponent可以编码空格""可以获得20%转义是UTF-16位编码,后两者是UTF-8编码,但码位0xFF以下的编码结果是一样的。当然,并不是所有的字符都会被编码,让我们看看哪些字符不会被编码。哪些字符不会被%20编码,不得不提一下我们常用编码的三对姐妹:escape(unescape)outdatedencodeURI(decodeURI)encodeURIComponent(decodeURIComponent)我们先分别列出A-Za-z0-9,因为Will不被编码,看哪些字符不会被编码。系列保留字符编码转义@*_+-。/UTF-16encodeURI-_。!~*'();,/?:@&=+$#UTF-8encodeURIComponent-_。!~*'()UTF-8编码的转义简单来说,转义就是生成新的字符串,用十六进制转义序列代替,使其在所有计算机上都可读。编码后的结果格式为%XX或%uXXXX。当您需要对URL进行编码时,请使用encodeURI或encodeURIComponent。要点:EncodingUTF-16字符编码基于UTF-16。对于码位大于0xFFFF的字符,编码结果分为高位和低位。charCodeAt(0)可以取高位,charCodeAt(1)可以取低位。转义码点大于0xFFFF的字符转义成两个%uXXXX先直接看编码结果:varch=String.fromCodePoint(0x23455);//"𣑕"escape(ch)//'%uD84D%uDC55'码位大于0xFFFFunescape(escape(ch))//"𣑕"ch.charCodeAt(0).toString(16).toUpperCase();//高位//'D84D'ch.charCodeAt(1).toString(16).toUpperCase();//Lowbit//'DC55'看结论就知道了,和charCodeAt的逻辑处理是一致的。两者都返回UTF-16编码的高位和低位编码。EncodedencodeURI由于URL只能由标准ASCII字符组成,因此必须对其他特殊字符进行编码。它们将被一系列代表UTF-8编码的不同字符所取代。encodeURI和encodeURIComponent用于此目的。关键是encodeURI和encodeURIComponent使用的是UTF-8编码。首先看代码点和UTF-8编码格式,以及需要的字节数。Unicode码位范围(十六进制)十进制范围UTF-8编码方式(二进制)字节数00000000~0000007F0~1270xxxxxxx100000080~000007FF128~2047110xxxxx10xxxxxx20000FF0801100~35xxxx~60xxxFF10xxxxxx300010000~0010FFFF65536~111411111110xxx10xxxxxx10xxxxxx10xxxxxx4先来看字符字符:获取其码位4ebavarcodePoint="人".codePointAt(0).toString(16)`它位于00000800~0000FFFF,格式为1110xxxx10xxxxxx10xxxxxx,需要三个字节的encodeURI,可以看到是三个%XXencodeURI("person")//%E4%BA%BA这里省略具体的编码过程具体编码结果验证,可以到ConvertUTF8toBinaryBits-OnlineUTF8Tools[5]验证最终编码结果:111001001011101010111010(0b11100100).toString(16).toUpperCase()//E4(0b10111010).toString(16).toUpperCase()//BA(0b10111010).toString(16).toUpperCase()//BAencodeURI("person")//%E4%BA%BA=>E4BABA再次演绎??码位在0x2345500010000~0010FFFF之间,格式为11110xxx10xxxxxx10xxxxxx10xxxxxx,需要四个字节encodeURI,由四个%XXencodeURI("??")//"%F0%A3%91%95组成"encodeURIComponent既然有了encodeURI,为什么还要encodeURIComponent呢?用于对地址后的参数值进行编码。我们通常称它为queryString。看一个例子:varparam="http://www.yyy.com";//param是参数param=encodeURIComponent(param);varurl="http://www.xxxx.com?target="+param;同样的原因之后的部分?为空Key=ahha&type=x,所有键值对都需要encodeURIComponent进行编码。http://wwww.xxxyyy.com/哈哈?Emptykey=ahha&type=x其实现代浏览器都会默认编码,不妨把上面的地址粘贴到浏览器:image.pngapplication/x-www-form-urlencodeddatamethod`application/x也需要编码-www-form-urlencoded`[6](POST)。它的编码规则:数据被编码成以'&'分隔的键值对,键和值以'='分隔。不是字母或数字的字符将被percent-encoding[7]我们来看看percent-encoding(百分比编码)。percent-encoding百分比编码(也称为百分比编码)是一种具有8位字符编码的编码机制,在URL[8]的上下文中具有特定含义。它有时被称为URL编码。编码由英文字母替换组成:“%”后跟替换字符的ASCII十六进制表示。它广泛用于统一资源标识符/统一资源定位器(URI)的主要集合,其中包括URL和统一资源名称(URN)。它还用于准备应用application/x-www-form-urlencoded媒体类型的数据,该媒体类型通常用于在HTTP请求中提交HTML表单数据。URI中允许的字符分为保留字符和非保留字符。保留字符是那些具有特殊含义的字符,例如:斜杠[9]字符用于分隔URL(或URI)的不同部分;非保留字符没有这些特殊含义。百分比编码将保留字符表示为特殊字符序列。保留字符保留字符需要编码,包括:':','/','?','#','[',']','@','!','$','&',"'",'(',')','*','+',',',';','='和'%'本身,以及一个空格""。percent-encoding编码对照表可参考:percent-encoding|MDN[10]非保留字符不需要编码,直接使用即可。A-Za-z0-9-_。~特殊字符“”,当作为URL使用时,编码转换为%20postsubmission(application/x-www-form-urlencoded)并替换为+然后,我们这里直接使用encodeURLComponent编码values和keys,可以吗?答案是否定的:百分比编码需要编码20个特殊字符(加上''):/?#[]@!$&'()*+,;=%encodeURLComponent不会编码的字符是9:-_.!~*'(),所以需要额外编码:['!',"'",'(',')','*'],如何计算,看下面代码:varpercentChars=[':','/','?','#','[',']','@','!','$','&',"'",'(',')','*','+',',',';','=','%',''];vareURICChars=['-','_','.','!','~','*',"'",'(',')'];varnotInPChars=percentChars.filter(c=>eURICChars.includes(c));console.log("notInPChars:",notInPChars);//notInPChars:(5)['!',"'",'(',')','*']所以,完整的编码应该是这样的:functionencodeValue(val){vareVal=encodeURIComponent(val);//单独处理没有经过encodeURIComponent编码的字符eVal=eVal.replace(/\*/g,'%2A');eVal=eVal.replace(/!/g,'%21');eVal=eVal.replace(/\(/g,'%28');eVal=eVal.replace(/\)/g,'%29');eVal=eVal.replace(/'/g,'%27');//对空格字符的特殊处理returnVal.replace(/\%20/g,'+');}Content-Disposition:附件;filename我们在后台返回文件时,如果指定Content-Disposition:attachment并设置文件名,客户端收到请求后可以直接下载文件。问题出在文件名上,同样需要编码,我们看一下:参考MDN:varfileName='myfile(2).txt';varheader="Content-Disposition:attachment;filename*=UTF-8''"+encodeRFC5987ValueChars(fileName);console.log(header);//输出"Content-Disposition:attachment;filename*=UTF-8''my%20file%282%29.txt"functionencodeRFC5987ValueChars(str){returnencodeURIComponent(str).//注意,虽然RFC3986保留了“!”,但RFC5987没有//所以我们不需要过滤它replace(/['()]/g,escape).//i.e.,%27%28%29replace(/\*/g,'%2A').//RFC5987中URI编码不需要下面的//所以对于3个字符|`^,我们可以稍微提高可读性replace(/%(?:7C|60|5E)/g,unescape);}也和percent-encoding有些不同,percent-encoding在注释里写的很清楚。我真的很想说,搞那么多协议不累吗?看到注册,我们可以看到RFC3986、RFC5987等协议,一起来看看吧。RFC3986[11]、RFC1738[12]、RFC5987[13]RFC3986、RFC1738是关于URI编码规范,RFC5987是关于http协议文件头域规范。RFC3986[14]于2005年发布,是当前的标准。文档对URL的编码和解码做了详细的建议,指出了哪些字符需要编码以免引起Url语义的改变,并解释了为什么这些字符需要编码RFC1738[15]94release.同上。RFC5987[16]超文本传输??协议(HTTP)标头字段参数的字符集和语言编码。译文:超文本传输??协议文件头域参数的字符集和语言编码,HTTP传输头字符串编码规范。您会发现许多代码也处理~符号。虽然RFC3986文档规定波浪号~不需要Url编码,但是还是有很多老的网关或者中转代理。兼容性好的代码会兼容RFC1738。比如著名的qs库的formats.js[17]image.pngwindow.btoa和window.atobwindow.btoa可以输入字符进行base64编码,window.atob可以解码。window.btoa("abcd")//"YWJjZA=="window.atob("YWJjZA==")//"abcd"但是它的函数代码是ASCII字符串,试试中文:window.btoa("人")//UncaughtDOMException:Failedtoexecute'btoa'on'Window'://ThestringtobeencodedcontainscharactersoutsideoftheLatin1range.怎么解决呢?//ucs-2stringtobase64encodedasciifunctionutoa(str){returnwindow.btoa(unescape(encodeURIComponent(str)));}//base64encodedastr){returndecodeURIComponent(escape(window.atob(str)));}验证一下,完美的。utoa("person")//5Lq6atou("5Lq6")//person那么思路是什么???encodeURIComponent将字符转换为百分比UTF-8字节存储为%XX,unescape将其转换为btoa单码点请求。所以,btoa(unescape(encodeURIComponent(str)))既把文本编码成utf-8字节,又编码成Base64。虽然,去掉中间的unescape和escape也可以正常使用,但是必须要配合使用。不过,它不再是标准的utf-8转换成Base64了。自己玩:window.btoa(encodeURIComponent("Iamhumana"))//JUU2JTg4JTkxJUU2JTk4JUFGJUU0JUJBJUJBYQ==decodeURIComponent(window.atob("JUU2JTg4JTkxJUU2JTk4JUFGJUU0JUJBJUJBYQ==")结果:%20转义对应的URL的摘要或结果到空字符“”。也可以说是百分号编码。escape将字符串转换为十六进制转义序列,使其在所有计算机上都可读。现在已经过时了,没有用了。encodeURI是URL编码,不处理参数部分.encodeURIComponent也是URL编码,主要用于url的参数部分,post数据类型为application/x-www-form-urlencoded,附件文件名为filenameRFC3986[18],RFC1738[19]为URL编码协议RFC5987[20]是HTTP传输头字符串编码规范,window.btoa和window.atob默认只能处理ASCII字符,配合encodeURIComponent和escape可以处理任意字符。最后问一个问题:percentencoding和escape、encodeURI、encodeURIComponent有什么关系?