美文网首页
【转】你看到的事实,不一定是事实

【转】你看到的事实,不一定是事实

作者: AlphaHinex | 来源:发表于2021-03-07 11:06 被阅读0次

原文地址:https://wyiyi.github.io/amber/2021/03/06/zero-width-space/


description: "零宽字符"
date: 2021.03.07 10:34
categories:
- Others
tags: [Others]
keywords: 零宽字符, 零宽空格, zero width, zero width space


大家都熟悉的 Unicode(万国码)几乎包含 所有符号

  • 常用的 Emoji: 😂 😸 ✌
  • 颜文字: (๑•̀ㅂ•́) ٩(͡๏̯͡๏)۶
  • 表意文字:𠁀 𡮘 𠆳
  • 国际象棋图案:♕ ♛ ♙
  • 扑克牌: 🂡 🃁 🂳
  • 麻将牌: 🀄 🀝 🀇

还有很多种玩法,比如在朋友圈火热的花式飞机坦克等。

一些特殊符号对应的 Unicode 编码及 HTML 代码如下:

图形 Unicode 编码 HTML 代码
U+25D9 ◙
U+25AC ▬
U+25A6 ▦
U+25B2 ▲
U+25CF ●
U+25E5 ◥
... ... ...

在这些 Unicode 中,有一类特殊的字符,它们虚无缥缈,摸得着,看不见,它们就是:零宽字符

什么是零宽字符

顾名思义,就是字节宽度为 0 的特殊字符。比如 Byte-Order Mark 就是零宽字符的一种。

零宽度字符是一些不可见的,不可打印的字符。它们存在于页面中主要用于调整字符的显示格式,下面就是一些常见的零宽度字符及它们的 Unicode 码和原本用途:

  • zero-width space(ZWSP)用于较长单词的换行分隔。Unicode: U+200B,HTML: ​
  • zero-width non-joiner(ZWNJ)放在两个字符之间,用于阻止这两个字符发生连字效果。Unicode: U+200C,HTML: ‌
  • zero-width joiner(ZWJ)是一个控制字符,放在某些需要复杂排版语言(如阿拉伯语、印地语)的两个字符之间,使得这两个本不会发生连字的字符产生了连字效果。Unicode: U+200D,HTML: ‍
  • Left-to-right mark(LRM)是一种控制字符,用于在混合文字方向的多种语言文本中(例:混合左至右书写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右。Unicode: U+200E,HTML: ‎‎‎
  • Right-to-left mark(RLM)是右至左控制字符,用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左。Unicode: U+200F,HTML: ‏‏‏
  • Word joiner(WJ),自 Unicode 3.2 版本(2002 年发布)之后,替代了之前的 zero width no-break space(ZWNBSP),用来表示不应该在此处进行单词的换行分割。Unicode: U+2060,HTML: ⁠⁠
  • Byte Order Mark(BOM),表示字节顺序标识。Unicode 3.2 之后,使用 U+FEFF 来代表 BOM。而在 3.2 版本之前,U+FEFF 是用来表示 zero width no-break space(ZWNBSP)的,即不进行换行。

怎么输入零宽字符

可以使用 js,在浏览器 console 中解码 Unicode 编码,实现零宽字符的输入,如:

> unescape('%u200e')
< ""
> unescape('%u2060')
< "⁠"

零宽字符应用

传递隐秘信息

利用零宽度字符不可见的特性,我们可以用零宽度字符在任何未对零宽度字符做过滤的网页内插入不可见的隐形文本。

安利一个小工具

隐形水印

通过零宽度字符我们可以对内部文件添加隐形水印。

在浏览者登录页面对内部文件进行浏览时,我们可以在文件的各处插入使用零宽度字符加密的浏览者信息,如果浏览者又恰好使用复制粘贴的方式在公共媒体上匿名分享了这个文件,我们就能通过嵌入在文件中的隐形水印轻松找到分享者了。

完整源码可见仓库

@SpringBootTest
class ZeroWidthUnicodeTest {
    private WaterMark waterMark = new WaterMark();
    private DeEncode deEncode = new DeEncode();

    @Test
    void testWaterMark() {
        String input = "测试添加水印";
        String string = "原文本:\"" + input + "\",文本长度:" + input.length();
        String output = "原文本:\"测试添加水印\",文本长度:6";
        assert string.equals(output);

        String watermarkInput = "抓鸭子,抓几只?";
        String string1 = "水印文本:\"" + watermarkInput + "\",文本长度:" + watermarkInput.length();
        String watermarkOutput = "水印文本:\"抓鸭子,抓几只?\",文本长度:8";
        assert string1.equals(watermarkOutput);

        String encode = deEncode.encode(watermarkInput);
        String waterCode = "水印编码:\"" + encode + "\",编码长度:" + encode.length();
        String waterTextOutput = "水印编码:\"\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\",编码长度:256";
        assert waterCode.equals(waterTextOutput);


        String result = waterMark.addWatermark(input, encode, CodeUtil.WATERMARK_POS_HEAD);
        String resultOutput = "输出:\"" + result + "\",文本长度:" + result.length();
        String resultTrue = "输出:\"\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200C\uFEFF\u200C\uFEFF\u200C\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF\u200B\uFEFF测试添加水印\",文本长度:262";
        assert resultOutput.equals(resultTrue);

        result = waterMark.extractWatermark(result, CodeUtil.WATERMARK_POS_HEAD);
        String watermark = deEncode.decode(result);
        assert watermarkInput.equals(watermark);
    }
}

逃脱关键字过滤

通过零宽度字符我们可以轻松逃脱关键字过滤。关键字自动过滤是维持互联网社区秩序的一项重要工具,只需导入关键字库和匹配相应关键字,即可将大量的预设关键字拒之门外。使用谐音与拼音来逃脱关键字过滤会让语言传递信息的效率降低,而使用零宽度字符可以在逃脱关键字过滤的同时将词义原封不动地传达给接受者,大大提高信息传播者与接受者之间交流的效率。

const sensitive = '关键字'
// 利用零宽度字符 zero-width joiner U+200D 来分隔关键字 
sensitive.replace(/关键字/g, '')
// 使用零宽度空格 zero-width space U+200B对字符串进行分隔
Array.from(sensitive).join('').replace(/关键字/g, '')

小心零宽字符带来的困扰,同时也可以很好的利用零宽字符!

参考资料

相关文章

  • 【转】你看到的事实,不一定是事实

    原文地址:https://wyiyi.github.io/amber/2021/03/06/zero-width-...

  • 看到的不一定是事实,事实不一定是真相

    作者:吖希 很多时候,我们看到的,不一定是事实,而我们以为的事实,不一定是真相,相信很多人都被误解过,那种心情比真...

  • 你看到的不一定是事实

    这两天看到的新闻,马蜂窝数据涉嫌造假,大量评论都是从其他点评平台搬过来的,具体事实如何我不得而知,就是由此引发了点...

  • 晨语问安2021年10月10日

    『晨语问安10.10』看到的不一定是事实,听到的不一定是事实,事实往往隐藏在表象之下,需要用心用力去挖掘。过于相信...

  • 复杂

    有时候,你看到的不一定是事实。 你以为的事实不一定是真相。 今天看到的今年一道高考作文题和之前的一期《奔跑吧》的内...

  • 眼睛看到的不一定是事实

    早上在小区的花园里散步,在一片树荫底下坐下乘凉。 旁边坐着一个老奶奶,望着前方的草坪,手里拿着扇子扇着。 她旁边坐...

  • 你总是看不到真相的原因:

    1.你看到的,是你想看到的,不一定是事实; 2.你看到的,是你过去的经历,不一定是真相; 3.你看到的,是你自己的...

  • 李晨从最开始就露馅了,导演组巧妙掩盖过去,却没人注意到

    在《奔跑吧》最新一期内容中,导演组安排了一个非常有意义的主题:你看到的不一定是事实,你以为的事实不一定是真相。在这...

  • 眼见不一定为实

    你看到的不一定是真相 你所猜忌的未必是事实 所以无论发生什么事 不要着急下定论 你看到的真实的东西,未必是事实的全...

  • 别光说,要做到

    有时候亲眼看到的都不一定是事实,更何况是你听到的呢!能证明是事实的只有事实本身,事实胜于雄辩,让事实证明一切吧。 ...

网友评论

      本文标题:【转】你看到的事实,不一定是事实

      本文链接:https://www.haomeiwen.com/subject/akosqltx.html