Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文精心挑選了許多優秀的Unicode小技巧、軟件包和資源。
譯者 | 彎月,責編 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下為譯文:
Unicode非常了不起!在Unicode出現之前,國際交流是一團糟——每個人都在ASCII碼表的后半部分區域(稱為“代碼頁”)定義了自己的擴展和字符集,從而導致各種沖突。想想就知道,德國人要與韓國人只使用127個字符組成的代碼頁進行交流會有多么困難。
——幸虧有了Unicode標準和統一的交流規范。
Unicode 8.0根據129多種書寫體系,標準化了超過120,000個字符,其中包括現代字符、古代字符,甚至還包括人類尚未解密的文字。Unicode能處理從左到右和從右到左兩種書寫方式,支持組合標記,還支持多種文化、政治、宗教方面的字符,甚至還有表情符號。
Unicode太了不起了,我們對它的崇拜猶如滔滔江水綿綿不絕。
Unicode的背景
Unicode標準支持什么字符?
Unicode標準定義了今日所有主流的書寫語言中用到的字符。Unicode支持的書寫體系包括歐洲的語系、中東的從右至左書寫的語系,以及亞洲的多種語系。
Unicode標準還包含了標點符號、聲調符號、數學符號、科技符號、箭頭、各種圖形符號、表情符號,等等。Unicode為聲調符號(用來改變其他字符的符號,如波浪線~)單獨提供了代碼,這些代碼可以與基礎字符組合使用,來表示有聲調的字符(如?)。Unicode標準9.0版總共提供了128,172個字符的代碼,其中包括了全世界的字符、圖形和符號。
絕大部分的常用字符都能映射到最前面的64K個代碼點上,這一區域叫做基本多文種平面(basic multilingual plane,簡稱為BMP)。還有十六個補充平面用來編碼其他字符,目前尚有850,000個未使用的代碼點。人們還在考慮在以后的版本中添加更多的字符。
Unicode標準還保留了一些代碼點供私人使用。供應商或最終用戶可以在內部利用這些代碼點表示他們自己的字符和符號,或者通過特殊的字體來使用。BMP上有6,400個私有代碼點,如果不夠的話,補充平面上還有131,068個私有代碼點可供使用。
Unicode字符編碼
字符編碼標準不僅定義了每個字符的唯一標識(即字符的數字值,或者叫做代碼點),也定義了怎樣用比特來表示這個值。
Unicode標準定義了三種編碼形式,允許同一個數據以一字節、兩字節或四字節的格式來傳輸(即每個代碼單元可以是8比特、16比特或32比特)。同一個字符集可以使用所有三種編碼形式,它們之間可以互相轉換,而不會丟失數據。Unicode聯盟建議根據實際需要,選擇任何一種方便的編碼方式來實現Unicode標準。
UTF-8在HTML和類似協議上非常常用。UTF-8使用變長編碼。它的優點是,對應于ASCII字符集的那些Unicode字符的字節值與它們在ASCII中的值完全相同,因此使用UTF-8編碼的Unicode字符可以在絕大多數已有軟件上使用,無需對軟件做出任何修改。
UTF-16在許多需要平衡性能和存儲效率的環境中非常常用。它足夠緊湊,所有常用的字符都可以用一個16比特的代碼單元來表示,其他字符可以使用一對16比特代碼單元來表示。
UTF-32在無需顧慮內存空間的情況下使用,它是定長編碼,每個字符只有一個代碼單元。每個Unicode字符編碼成一個32比特代碼單元。
在所有三種編碼中,每個字符最多需要4個字節(32比特)表示。
數字問題
Unicode字符集被分成17個核心段,稱為“平面”,每個平面又被分成若干區塊。每個平面的空間足夠容納65,536(216)個代碼點,因此總共有1,114,112個代碼點。還有兩個“私有區域”平面(#16和#17),可以按照使用者的意愿定義。這兩個私有平面共包含131,072個代碼點。
第一個平面叫做“基本多文種平面”,或者稱為BMP。它包含代碼點U+0000到U+FFFF,這個范圍內包含了絕大部分常用字符。另外16個平面(U+010000到U+10FFFF)稱為補充平面。
UTF-16代理對
“BMP之外的字符,例如U+1D306 tetragram for centre (),在UTF-16編碼中只能編碼成兩個16比特代碼單元:0xD834 0xDF06。這種情況稱為代理對(surrogate pair)。注意代理對只表示一個字符。
“代理對的第一個字符永遠在0xD800到0xDBFF的范圍內,稱為高位代理,或者叫起始字節代理。代理對的第二個代碼單元永遠在0xDC00到0xDFFF的范圍內,稱為低位代理,或者叫末端代理。”
——Mathias Bynens
“代理對:一種表示方式,用于表示由兩個16比特代碼單元組成的單個抽象字符,其中第一個值稱為高位代理代碼單元,第二個值稱為低位代理單位。代理對僅在UTF-16中使用?!?/p>
——Unicode 8.0 第3.9章,代理對(參見Unicode編碼)
計算代理對
代理字符 Pile of Poo (U+1F4A9) 在UTF-16中必須編碼成代理對,即兩個代理。要將代碼點轉換成代理對,可以使用以下算法(用JavaScript編寫)。注意我們使用的是十六進制表示。
var High_Surrogate=function(Code_Point){ return Math.floor((Code_Point - 0x10000) / 0x400) + 0xD800 };
var Low_Surrogate=function(Code_Point){ return (Code_Point - 0x10000) % 0x400 + 0xDC00 };
// Reverses The Conversion
var Code_Point=function(High_Surrogate, Low_Surrogate){
return (High_Surrogate - 0xD800) * 0x400 + Low_Surrogate - 0xDC00 + 0x10000;
};
> var codepoint=0x1F4A9; // 0x1F4A9==128169
> High_Surrogate(codepoint).toString(16)
"d83d" // 0xD83D==55357
> Low_Surrogate(codepoint).toString(16)
"dca9" // 0xDCA9==56489
> String.fromCharCode( High_Surrogate(codepoint) , Low_Surrogate(codepoint) );
""
> String.fromCodePoint(0x1F4A9)
""
> '\ud83d\udca9'
""
組合和解組合
Unicode包括了一種修改字符形狀的機制,大幅擴展了Unicode支持的字符量。使用聲調符號進行組合就是其中一種方式。聲調符號寫在主字符的后面。多個聲調符號可以疊在同一個字符上。對于絕大部分常用的字母聲調組合,Unicode還包括了預先組合好的版本。
特定的字符序列也可以用單個字符表示,稱為“預組合字符”(或者叫組合字符,可以解組合的字符)。例如,字符“ü”可以編碼成單個代碼單元U+00FC “ü”,也可以編碼成基本字符U+0075 “u”后接無空白字符U+0308 “¨”。Unicode標準中設置的預組合字符是為了兼容Latin 1等標準,后者包含了許多預組合字符,如“ü”和“?”。
預組合字符可以進行接組合,以保持一致性,或用于分析。例如,需要將一組名稱轉換為英文字母時,可以將字符“ü”解組合為“u”后接非空白字符“¨”。解組合后的結果很容易處理,因為該組合字符可以處理成“u”后接一個修飾字符。這樣很容易進行按字母順序排序等,因為修飾字符不會影響字母順序。Unicode標準為所有預組合字符定義了解組合方式(https://unicode.org/versions/Unicode8.0.0/ch03.pdf#page=44)。它還定義了正規化的方式,以便為字符提供唯一的表示方法。
Unicode之謎
來自Mark Davis的《Unicode之謎》幻燈片(https://macchiato.com/slides/UnicodeMyths.pdf)。
Unicode只不過是16比特編碼。一些人誤認為Unicode只不過是16比特編碼,每個字符占用16比特,因此一共有65,536個可能的字符。實際上這是不正確的。這樣是關于Unicode的最大誤解,所以也難怪一些人會這么想。
任何未分配的代碼點都可以用于內部用途?錯。最終,那些未分配的地方都會被某個字符使用。你應該使用私有用途代碼點,或非字符代碼點。
每個Unicode代碼點都表示一個字符?錯。有許多非字符代碼點(FFFE,FFFF,1FFFE,……)還有許多代理代碼點、私有代碼點和未分配的代碼點,還有控制和格式“字符(RLM,ZWNJ,……)
字符映射是一對一的?錯。映射關系也可能是:
一對多:(? → SS )
上下文相關:(…Σ ? …? 和 …ΣΤ… ? …στ… )
語言相關:( I ? ? 和 ? ? i )
實用Unicode編碼手冊
編碼類型編碼
神奇的字符列表
特殊字符
詳情可以參照Unicode聯盟發布的《通用標點符號表》(https://www.unicode.org/charts/PDF/U2000.pdf)。
等等,你說什么?
變量標識符可以包含空白!
U+3164 HANGUL FILLER 字符顯示為占據空間的空白字符。如果渲染器不支持,則會渲染成完全不可見(也不會占據任何空間,即“零寬度”)。這就是說,永遠不會看到丑陋的字符替代符號。
我不知道為什么U+3164被設計成這種行為。有意思的是,U+3164是在Unicode 1.1版本(1993年)加入的,所以聯盟一定是花了很多時間思考它。下面是幾個例子:
> var ?='foo';
undefined
> ?
'foo'
> var ?=alert;
undefined
> var foo='bar'
undefined
> if ( foo===?`baz` ){} // alert
undefined
> var var?foo?\u{A60C}?π='bar';
undefined
> var?foo???π
'bar'
注意:我在Ubuntu和OSX下測試了下述程序的渲染結果:Node,PHP,Ruby,Python3.5,Scala,Vim,Cat,Chrome+GitHub gist。Atom是唯一無法正確渲染,將其顯示成空方塊的程序。我還沒有測試Emacs和Sublime。據我的理解,Unicode聯盟不會改變或重命名字符或代碼點,但有可能會改變字符屬性,如ID_Start或ID_Continue等。
修飾符
零寬度連接符(ZWJ)是個不可打印字符,用于某些復雜語系的計算機排版系統中,如阿拉伯語系、印度語系等。將ZWJ放在兩個本來不會連接的字符之間,將會導致它們以連接的形式打印。
零寬度不連接符(ZWNJ)是個不可打印字符,那些使用連接的書寫系統的計算機化。將ZWNJ放在兩個本來會連接在一起的字符之間,會導致它們以本來的形式打印。空格字符也有同樣的效果,但ZWNJ的作用是它能保證輸出的兩個字符盡可能靠近,或者連接一個詞及其語素。
> 'a'
"a"
> 'a\u{0308}'
"a?"
> 'a\u{20DE}\u{0308}'
"a??"
> 'a\u{20DE}\u{0308}\u{20DD}'
"a???"
// Modifying Invisible Characters
> '\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}'
" "
> '\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}'.length
10
大寫變換沖突
小寫變換沖突
奇怪現象和排查方法
字符串長度通常由統計代碼點的個數來計算。這就是說,代理對會被統計成兩個字符。多個聲調符號會疊放在同一個字符上,例如a + ?==?a,從而增加長度,但它們只會產生一個字符。
類似地,字符串翻轉通常非常困難。同樣,代理對和帶有聲調符號的字符必須作為整體進行翻轉。ES Reverser提供了一個非常好的解決方法。
字符映射是一對一的?錯。映射關系也可能是:
一對多:(? → SS )
上下文相關:(…Σ ? …? 和 …ΣΤ… ? …στ… )
語言相關:( I ? ? 和 ? ? i )
一對多映射
絕大多數字符,在大寫的時候表示一對多關系;另一些字符小寫的時候表示一對多關系。
優秀的軟件包和庫
PhantomScript::ghost: :flashlight: 不可見的JavaScript代碼執行和社會工程工具。
ESReverser:用JavaScript編寫的支持Unicode的字符串翻轉。
mimic:Unicode的惡作劇。
python-ftfy:輸入Unicode文本,輸出更一致、更不容易出現顯示錯誤的表現形式。
vim-troll-stopper:防止Unicode的搗亂字符搞亂你的代碼。
表情符號
Unicode聯盟的表情符號表(https://www.unicode.org/emoji/charts/full-emoji-list.html)
Emojipedia(https://emojipedia.org/):關于特定表情符號的信息,新聞博客。
World Translation Foundation(https://www.emojifoundation.com/):宣傳、探索,還可以將文本翻譯成用表情符號表示的形式。
Can I Emoji? (https://caniemoji.com/android-2/):顯示當前iOS、Android和Windows對于表情符號的原生支持情況。
怎樣注冊一個表情符號URL(https://www.name.com/blog/how-tos/2015/12/want-an-emoji-url-this-is-how-you-register-one/)
多樣性
Unicode聯盟在支持人類的多樣性和多元文化方面做出了很多努力。這里是聯盟提供的多樣性報告(https://unicode.org/reports/tr51/#Diversity)。
現在表情符號已經支持混合型別,比如同性家庭、握手、接吻等。真正轟動的是表情符號組合序列?;旧蟻碚f:
此外,表情符號現在還支持膚色修飾字符了。
“有五個符號修飾字符可以為Unicode 8.0版(2015年中期)中發布的人類的表情符號提供一系列的膚色。這些字符基于Fitzpatrick度量(皮膚學上的著名標準,網上也有許多例子,比如FitzpatrickSkinType.pdf)定義的六種膚色。不同的實現的精確顏色可能不同?!?/p>
——Unicode聯盟的多樣性報告
只需要在所需的表情符號后面接上膚色修飾字符 \u{1F466}\u{1F3FE} 即可。
有創意的變量名和方法名
示例采用JavaScript(ES6)編寫。
一般而言,帶有ID_START屬性的字符可以用在變量名開頭,而帶有ID_CONTINUE屬性的字符可以用在變量名中除了首字符之外的其他位置。
function rand(μ,σ){ ... };
String.prototype.reverse?=function{..};
Number.prototype.isTrue?=function{..};
var WhatDoesThisDo????=42
下面是Mathias Bynes(https://mathiasbynens.be/notes/javascript-identifiers#examples)提供的一些極富創意的變量名:
// How convenient!
var π=Math.PI;
// Sometimes, you just have to use the Bad Parts of JavaScript:
var ?_?=eval;
// Code, Y U NO WORK?!
var ?_?益?_?=42;
// How about a JavaScript library for functional programming?
var λ=function {};
// Obfuscate boring variable names for great justice
var \u006C\u006F\u006C\u0077\u0061\u0074='heh';
// …or just make up random ones
var ????='huh';
// While perfectly valid, this doesn’t work in most browsers:
var foo\u200Cbar=42;
// This is *not* a bitwise left shift (`<<`):
var ??=2;
// This is, though:
?? << ??; // 8
// Give yourself a discount:
var price_9?9?_89='cheap';
// Fun with Roman numerals
var Ⅳ=4;
var Ⅴ=5;
Ⅳ + Ⅴ; // 9
// Cthulhu was here
var H??????????????????E????????????????_?????????????????????????????O?????????????M????????????????E????????????T?????????????????????????????????='Zalgo';
下面是David Walsh提供的一些Unicode CSS類名(https://davidwalsh.name/unicode-css-classes):
<!-- place this within the document head -->
<meta charset="UTF-8" />
<!-- error message -->
<div class="?_?">You do not have access to this page.</div>
<!-- success message -->
<div class="?">Your changes have been saved successfully!</div>
.?_? {
border: 1px solid #f00;
}
.? {
background: lightgreen;
}
遞歸的HTML標簽重命名腳本
如果你想把所有HTML標簽重命名,使之看上去像什么都沒有,那么可以使用以下的腳本。
但要注意,HTML并不會支持所有的Unicode字符。
// U+1160 HANGUL JUNGSEONG FILLER
transformAllTags('?');
// An actual HTML element node designed to look like a comment node, using the U+01C3 LATIN LETTER RETROFLEX CLICK
// <?-- name="viewport" content="width=device-width"></?-->
transformAllTags('?--');
// or even <??
transformAllTags('\u{1160}\u{20dd}');
// and for a bonus, all existing tag names will have each character ensquared. h?t?m?l?
transformAllTags;
function transformAllTags(newName){
// querySelectorAll doesn't actually return an array.
Array.from(document.querySelectorAll('*'))
.forEach(function(x){
transformTag(x, newName);
});
}
functionwonky(str){
return str.split('').join('\u{20de}') + '\u{20de}';
}
functiontransformTag(tagIdOrElem, tagType){
var elem=(tagIdOrElem instanceof HTMLElement) ? tagIdOrElem : document.getElementById(tagIdOrElem);
if(!elem || !(elem instanceof HTMLElement))return;
var children=elem.childNodes;
var parent=elem.parentNode;
var newNode=document.createElement(tagType||wonky(elem.tagName));
for(var a=0;a<elem.attributes.length;a++){
newNode.setAttribute(elem.attributes[a].nodeName, elem.attributes[a].value);
}
for(var i=0,clen=children.length;i<clen;i++){
newNode.appendChild(children[0]); //0...always point to the first non-moved element
}
newNode.style.cssText=elem.style.cssText;
parent.replaceChild(newNode,elem);
}
下面是確定能夠支持的字符:
function testBegin(str){
try{
eval(`document.createElement( '${str}' );`)
return true;
}
catch(e){ return false; }
}
function testContinue(str){
try{
eval(`document.createElement( 'a${str}' );`)
return true;
}
catch(e){ return false; }
}
下面是一些基本的結果:
// Test if dashes can start an HTML Tag
> testBegin('-')
< false
> testContinue('-')
< true
> testBegin('?-') // Prepend dash with U+1160 HANGUL JUNGSEONG FILLER
< true
Unicode字體
單一的TrueType / OpenType 字體格式無法支持所有UTF-8字符,因為字體文件有最大65535個字形的限制。UTF-8的字形超過了110萬,因此你需要一個font-family才能覆蓋所有字體。
https://en.wikipedia.org/wiki/Unicode_font#List_of_Unicode_fonts
https://www.unifont.org/fontguide/
Unicode標準的原則
Unicode標準設定了下述基本原則:
通用原則——曾經出現過的一切書寫系統都應該在標準中體現。
邏輯順序——在雙向文本中,字符以邏輯順序存儲,而不是表現順序存儲。
效率——文檔必須是高效的、完整的。
統一——不同文化或語言使用同一個字符時,應該僅存儲一次。
記錄字符而不是字形——應當對字符進行編碼,而不是字形。字形就是字符的實際圖形表示。
動態組合——新的字符可以與已有的標準化后的字符進行組合。例如,字符“?”可以用字符“A”和分音符“¨”組合而成。
語義——包含的字符必須有明確定義,必須與其他字符有明確的區別。
穩定——字符一旦被定義,就永遠不能被移除,其代碼點也不能被挪作他用。如果出錯,則應該將代碼點標記為棄用。
純文本——標準中的字符應當是純文本,永遠不應該包含標記或元字符。
可轉換——每一種編碼都應該可以用Unicode編碼表示。
原文:https://wisdom.engineering/awesome-unicode/
本文為 CSDN 翻譯,轉載請注明來源出處。
【End】
Python系列學習成長課來了!15年經驗專家、CSDN特級講師親自授課,還等什么?立即掃碼報名學習:
nicode 聯盟(Unicode Consortium)
Unicode 聯盟(Unicode Consortium)開發了 Unicode 標準(Unicode Standard)。他們的目標是使用標準的 Unicode 轉換格式(即 UTF,全稱 Unicode Transformation Format)取代現有的字符集。
Unicode 標準是一個成功的創舉,在 HTML、XML、Java、JavaScript、E-mail、ASP、PHP 中都得到實現。Unicode 標準也得到許多操作系統和所有現代瀏覽器的支持。
Unicode 聯盟與領先的標準開發組織合作,這些組織有 ISO、W3C 和 ECMA。
Unicode 字符集
Unicode 可以由不同的字符集實現。最常用的編碼是 UTF-8 和 UTF-16:
字符集 | 描述 |
---|---|
UTF-8 | UTF8 中的字符可以是 1 到 4 字節長。UTF-8 可以代表 Unicode 標準中的任何字符。UTF-8 向后兼容 ASCII。UTF-8 是電子郵件和網頁的首選編碼。 |
UTF-16 | 16 位 Unicode 轉換格式是一種可變長度的 Unicode 字符編碼,能夠編碼整個 Unicode 指令表。UTF-16 主要用于操作系統和環境,如 Microsoft Windows、Java 和 .NET。 |
提示:Unicode 的前 128 個字符(與 ASCII 一一對應)使用一個與 ASCII二進制值相同的八位組進行編碼,使有效的 ASCII 文本在進行 UTF-8 編碼時也是有效的。
提示:所有的 HTML 4 處理器支持 UTF-8,所有的 HTML 5 和 XML 處理器支持 UTF-8 和 UTF-16!
HTML5 標準:Unicode UTF-8
因為 ISO-8859 中字符集大小是有限的,且在多語言環境中不兼容,所以 Unicode 聯盟開發了 Unicode 標準。
Unicode 標準覆蓋了(幾乎)所有的字符、標點符號和符號。
Unicode 使文本的處理、存儲和運輸,獨立于平臺和語言。
HTML-5 中默認的字符編碼是 UTF-8。
下面列出了一些 HTML5 支持的 UTF-8 字符集:
字符集 | 十進制 | 十六進制 |
---|---|---|
C0 控制與基本的 Latin(C0 Controls and Basic Latin) | 0-127 | 0000-007F |
C1 控制與 Latin-1 的補充(C1 Controls and Latin-1 Supplement) | 128-255 | 0080-00FF |
Latin 擴展 A(Latin Extended-A) | 256-383 | 0100-017F |
Latin 擴展 B(Latin Extended-B) | 384-591 | 0180-024F |
如果 HTML5 網頁使用不同于 UTF-8 的字符,則需要在 <meta> 標簽中指定,如下:
實例
<meta charset="ISO-8859-1">
如您還有不明白的可以在下面與我留言或是與我探討QQ群308855039,我們一起飛!
周的時候,朋友圈的直升飛機不知道為什么就火了,很多朋友開著各種花式飛機帶著起飛。
圖片來自網絡
還沒來得及了解咋回事來著,這個直升飛機就到的微博熱搜。
圖片來自網絡
后面越來越多人開來他們的直升飛機,盤旋在朋友圈上方。于是很多朋友開來他們的坦克,專打直升飛機,一轟一個準。
圖片來自網絡
好了,說回正題!
程序員朋友應該都很熟悉 Unicode (萬國碼),它幾乎包含世界上所有符號,比如組成直升飛機這幾個特殊符號對應的 Unicode 碼分別為:
ps:推薦一個網站,可以根據符號搜對應的 Unicode 碼:https://unicode.yunser.com/unicode
除了這些正常字符以外,Unicode 還包含著各種各樣的奇葩字符。
除了正常的我們熟知的文字以外,Unicode 中還有一些奇怪的文字,比如下面這些文字
這咋讀?某少?
世代?
恩?超出認知范圍
除了這些奇怪文字以外,Unicode 還有一些奇葩的的符號。
例如下面一整套麻將牌:
一整套的撲克牌:
一整套國際象棋:
image-20200725215319183
除了這些,通過組合符合,我們還可以造出各種各樣的顏文字(??????)??、
另外 Unicode 還收錄著我們常用的 Emoji 。
除了這些之外,Unicode 中還有一些特殊字符的,利用這些字符,我們還可以玩出很多有趣的騷操作。
Unicode 有一類字符稱為組合字符,它可以附加在前一個非組合字符上,從而使整體看起來像是一個字符。
組合字符原來目的是為了解決一些地區語言、文字特殊的需要,比如說泰文聲調符號與母音符號。
正常使用的情況下,這些組合字符數量都會有一些限制。但是在 Unicode 組合字符設計上,并沒有加這種限制,這樣使我們可以無限加這類組合字符。
利用這個特性,可以達到一些惡搞效果,比如「擊穿天花板」與「鑿穿地板」的效果。
上面實現原理其是利用以下兩個組合字符:
上翻字符
下翻字符
只要復制這兩個字符相應的 HTML 代碼,跟在正常的字符后面,就可以使這兩個字符附加在普通字符上,比如下面實現效果為
黑??
Unicode 碼值通常使用 U+N(16 進制N 代表碼值),比如 A 的碼值為 U+0041。
在 HTML 中 Unicode 可以使用 N;(十進制,N 代表碼值)表示
在 JS 中 Unicode 中需要使用] \uN(16 進制N 代表碼值)表示
只要我們在普通字符多復制幾個這類附加字符,就可以形成上述「擊穿」效果。
還記得上面說的泰文嗎,曾經有一段時間貼吧,很流行一種噴射文,比如下面的效果。
向左噴
向右噴
左右互噴
這種噴射文實際原理就是利用泰文中聲調符號附加在其他正常符號上。
不過現在這個效果貌似已經沒辦法再復現了,現在我們只能看到這樣的效果:
在一些老版本的系統/瀏覽器可能還能看到這種效果,知道的小伙伴留言區可以告知一下。
Unicode 中還有一類格式字符,不可見,不可打印,主要作用于調整字符的顯示格式,所以我們將其稱為零寬字符。
零寬字符主要有以下幾類:
零寬度空格符 (zero-width space) U+200B : 用于較長單詞的換行分隔
零寬度非斷空格符 (zero width no-break space) U+FEFF : 用于阻止特定位置的換行分隔
零寬度連字符 (zero-width joiner) U+200D : 用于阿拉伯文與印度語系等文字中,使不會發生連字的字符間產生連字效果
零寬度斷字符 (zero-width non-joiner) U+200C : 用于阿拉伯文,德文,印度語系等文字中,阻止會發生連字的字符間的連字效果
左至右符 (left-to-right mark) U+200E : 用于在混合文字方向的多種語言文本中(例:混合左至右書寫的英語與右至左書寫的希伯來語),規定排版文字書寫方向為左至右
右至左符 (right-to-left mark) U+200F : 用于在混合文字方向的多種語言文本中,規定排版文字書寫方向為右至左
利用零寬字符不不可見的特性,我們也可以玩出一些騷效果。
發布微博的時候,如果內容都是空格,將沒辦法發布。
但是如果我們將零寬字符,比如說「零寬度空格符 U+200B」復制到微博,這樣我們就可以發布空白微博。
我們可以利用 Chrome 瀏覽器的控制臺復制零寬字符,操作方式如下:
發布效果如下:
真的沒有改 HTML 導致的.jpg
對于一些內部論壇或者說小說網站來說,可以通過零寬字符在帖子或小說內容嵌入隱形水印。
當這些內容被一些爬蟲復制到其他網站時,我們就可以通過隱形水印,輕松查找時那位用戶泄漏內容。
隱形水印主要原理就是將用戶信息比如用戶名,通過一定算法轉成零寬字符,這樣普通用戶瀏覽時完全看不到這個水印。
如果內容被復制到其他網站,隱形誰贏也被復制,只要找到這個水印,將這些零寬字符反轉成用戶名即可。
下面展示一種轉換方法,JS 代碼主要參考以下 Github 項目:
https://github.com/umpox/zero-width-detection
隱形水印生成方法
第一步我們需要將明文字符串每個字符都轉成二進制串。
// 每個字符轉為二進制,用空格分隔
const textToBinary = username => (
username
.split('')
// charCodeAt 將字符轉成相應的 Unicode 碼值
.map(char => char.charCodeAt(0).toString(2))
.join(' ')
);
示例如下:
第二步,將二進制串轉為零度字符串,轉換規則如下:
const binaryToZeroWidth = binary => (
binary.split('').map((binaryNum) => {
const num = parseInt(binaryNum, 10);
if (num === 1) {
return '\u200b'; // \u200b 零寬度字符(zero-width space)
} else if(num===0) {
return '\u200c'; // \u200c 零寬度斷字符(zero-width non-joiner)
}
return '\u200d'; // \u200d 零寬度連字符 (zero-width joiner)
}).join('\ufeff') // \ufeff 零寬度非斷空格符 (zero width no-break space)
);
最終加密方法如下:
const encode = username => {
const binaryUsername = textToBinary(username);
const zeroWidthUsername = binaryToZeroWidth(binaryUsername);
return zeroWidthUsername;
};
使用加密方法將明文字符串加密之后,加密字符串肉眼是看不到了,但是實際還是存在的。
實際上,如果我們將加密之后字符串復制到 BEJSON 網站,就可以看到字符。
image-20200722083507869
另外你還可以把加密字符串復制到 IDEA 中,可以看到相應的 Unicode 編碼值。
解密隱形水印
知道了加密的方式,解密其實就很簡單,我們只要按照相反步驟的來就可以了。
第一步,將隱形水印按照以下規則轉換為二進制串。轉換規則如下:
const zeroWidthToBinary = string => (
string.split('\ufeff').map((char) => { // \ufeff 零寬度非斷空格符 (zero width no-break space)
if (char === '\u200b') { // \u200b 零寬度字符(zero-width space)
return '1';
} else if(char === '\u200c') { // \u200c 零寬度斷字符(zero-width non-joiner)
return '0';
}
return ' ';
}).join('')
);
調用該方法,隱形水印轉成二進制串。
第二步,將二進制再轉為相應的字符。
const binaryToText = string => (
// fromCharCode 二進制轉化
string.split(' ').map(num => String.fromCharCode(parseInt(num, 2))).join('')
);
最終解密方法如下:
const decode = zeroWidthUsername => {
const binaryUsername = zeroWidthToBinary(zeroWidthUsername);
const textUsername = binaryToText(binaryUsername);
return textUsername;
};
解密示例如下:
?
我們常用的短網址,域名后面會跟上一串隨機串,從而實現短網址到長網址的映射。比如以下網址:
https://sourl.cn/iLyn9S
然而我們可以利用零寬字符也可以實現短網址的效果,,比如下面這個網站,就可以生成這類短網址。
https://zws.im/
可以看到這個短網址后面看不到任何字符,實際上這后面跟著一串零寬字符。當瀏覽器訪問該短網址時,后端程序只要反解密的后面零寬字符,拿到相應的網址,然后在做跳轉就可以到指定的網站。
反解密的原理可以參考上面隱形水印的代碼
日常開發過程中,我們有時需要從一些文件中讀取文本內容,然后做相應的處理。
有時候我們可能會碰到一些詭異的現象,比如我們之前碰到的例子。
后臺程序從 Excel 讀取文本內容,然后程序中判斷是讀取的文本內容是否與指定的字符串相等。
然后當我們讀取一份 Excel 內容后,返現這段比較邏輯怎么也通過不了。本來以為是 Excel 內容存在空格什么的,但是打開 Excel 仔細一看,跟指定字符串一模一樣,并沒有什么其他字符。
第一次碰到這種例子,沒有什么經驗,真的排查了很久,到最后都有點懷疑人生了。最后無意間將文本內容復制到了 IDEA 中,才發現整理混雜著零寬字符!
如果各位小伙伴也碰到這類問題,不妨將復制文本內容,然后到 IDEA 中查看是否存在某些看不見字符~
這兩個星期一直很忙,一直都在 9106 的節奏,真的是累,所以斷更了一周!
所幸最近項目提測,稍微輕松了一點,能有點劃水時間來寫寫文章。不過再提起筆來寫文章,就有點斷節奏了!
這篇文章墨跡了很久才水出來,下周開始再次恢復周更的節奏,再忙再累,每周都來一篇。
歡迎各位小伙伴,每周來這里蹲我,Gank 我?。?!
好了,我是樓下小黑哥,下周見!??!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。