亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

underscore 系列之字符實(shí)體與 _.escape

only_do / 2272人閱讀

摘要:前言提供了函數(shù),用于轉(zhuǎn)義字符串,替換和字符為字符實(shí)體。如果希望正確地顯示預(yù)留字符,我們必須在源代碼中使用字符實(shí)體。字符實(shí)體有兩種形式。轉(zhuǎn)義我們的應(yīng)對(duì)方式就是將取得的值中的特殊字符轉(zhuǎn)為字符實(shí)體。

前言

underscore 提供了 _.escape 函數(shù),用于轉(zhuǎn)義 HTML 字符串,替換 &, <, >, ", ", 和 ` 字符為字符實(shí)體。

_.escape("Curly, Larry & Moe");
=> "Curly, Larry & Moe"

underscore 同樣提供了 _.unescape 函數(shù),功能與 _.escape 相反:

_.unescape("Curly, Larry & Moe");
=> "Curly, Larry & Moe"
XSS 攻擊

可是我們?yōu)槭裁葱枰D(zhuǎn)義 HTML 呢?

舉個(gè)例子,一個(gè)個(gè)人中心頁的地址為:www.example.com/user.html?name=kevin,我們希望從網(wǎng)址中取出用戶的名稱,然后將其顯示在頁面中,使用 JavaScript,我們可以這樣做:

/**
 * 該函數(shù)用于取出網(wǎng)址參數(shù)
 */
function getQueryString(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var r = window.location.search.substr(1).match(reg);
    if (r != null) return unescape(r[2]);
    return null;
}

var name = getQueryString("name");
document.getElementById("username").innerHTML = name;

如果被一個(gè)同樣懂技術(shù)的人發(fā)現(xiàn)的話,那么他可能會(huì)動(dòng)點(diǎn)“壞心思”:

比如我把這個(gè)頁面的地址修改為:www.example.com/user.html?name=。

就相當(dāng)于:

document.getElementById("username").innerHTML = "";

會(huì)有什么效果呢?

結(jié)果是什么也沒有發(fā)生……

這是因?yàn)?

根據(jù) W3C 規(guī)范,script 標(biāo)簽中所指的腳本僅在瀏覽器第一次加載頁面時(shí)對(duì)其進(jìn)行解析并執(zhí)行其中的腳本代碼,所以通過 innerHTML 方法動(dòng)態(tài)插入到頁面中的 script 標(biāo)簽中的腳本代碼在所有瀏覽器中默認(rèn)情況下均不能被執(zhí)行。

千萬不要以為這樣就安全了……

你把地址改成 www.example.com/user.html?name= 呢?

就相當(dāng)于:

document.getElementById("username").innerHTML = "";

整理下其中 onerror 的代碼:

var s = document.createElement("script");
s.src = "https://mqyqingfeng.github.io/demo/js/alert.js";
document.body.appendChild(s);

代碼中引入了一個(gè)第三方的腳本,這樣做的事情就多了,從取你的 cookie,發(fā)送到黑客自己的服務(wù)器,到監(jiān)聽你的輸入,到發(fā)起 CSRF 攻擊,直接以你的身份調(diào)用網(wǎng)站的各種接口……

總之,很危險(xiǎn)。

為了防止這種情況的發(fā)生,我們可以將網(wǎng)址上的值取到后,進(jìn)行一個(gè)特殊處理,再賦值給 DOM 的 innerHTML。

字符實(shí)體

問題是怎么進(jìn)行轉(zhuǎn)義呢?而這就要談到字符實(shí)體的概念了。

在 HTML 中,某些字符是預(yù)留的。比如說在 HTML 中不能使用小于號(hào)(<)和大于號(hào)(>),因?yàn)闉g覽器會(huì)誤認(rèn)為它們是標(biāo)簽。

如果希望正確地顯示預(yù)留字符,我們必須在 HTML 源代碼中使用字符實(shí)體(character entities)。

字符實(shí)體有兩種形式:

&entity_name;

&#entity_number;

比如說我們要顯示小于號(hào),我們可以這樣寫:<<;

值得一提的是,使用實(shí)體名而不是數(shù)字的好處是,名稱易于記憶。不過壞處是,瀏覽器也許并不支持所有實(shí)體名稱(但是對(duì)實(shí)體數(shù)字的支持卻很好)。

也許你會(huì)好奇,為什么 < 的字符實(shí)體是 < 呢?這是怎么進(jìn)行計(jì)算的呢?

其實(shí)很簡(jiǎn)單,就是取字符的 unicode 值,以 &# 開頭接十進(jìn)制數(shù)字 或者以 &#x開頭接十六進(jìn)制數(shù)字。舉個(gè)例子:

var num = "<".charCodeAt(0); // 60
num.toString(10) // "60"
num.toString(16) // "3c"

我們可以以 < 或者 < 在 HTML 中表示出 <。

不信你可以寫這樣一段 HTML,顯示的效果都是 <

<
<
<

再舉個(gè)例子:以字符 "喵" 為例:

var num = "喵".charCodeAt(0); // 21941
num.toString(10) // "21941"
num.toString(16) // "55b5"

在 HTML 中,我們就可以用 或者 表示,不過“喵”并不具有實(shí)體名。

轉(zhuǎn)義

我們的應(yīng)對(duì)方式就是將取得的值中的特殊字符轉(zhuǎn)為字符實(shí)體。

舉個(gè)例子,當(dāng)頁面地址是 www.example.com/user.html?name=123時(shí),我們通過 getQueryString 取得 name 的值:

var name = getQueryString("name"); // 123

如果我們直接:

document.getElementById("username").innerHTML = name;

如我們所知,使用 innerHTML 會(huì)解析內(nèi)容字符串,并且改變?cè)氐?HMTL 內(nèi)容,最終,從樣式上,我們會(huì)看到一個(gè)加粗的 123。

如果我們轉(zhuǎn)義,將 123 中的 <> 轉(zhuǎn)為實(shí)體字符,即 123,我們?cè)僭O(shè)置 innerHTML,瀏覽器就不會(huì)將其解釋為標(biāo)簽,而是一段字符,最終會(huì)直接顯示 123,這樣就避免了潛在的危險(xiǎn)。

思考

那么問題來了,我們具體要轉(zhuǎn)義哪些字符呢?

想想我們之所以要轉(zhuǎn)義 <> ,是因?yàn)闉g覽器會(huì)將其認(rèn)為是一個(gè)標(biāo)簽的開始或結(jié)束,所以要轉(zhuǎn)義的字符一定是瀏覽器會(huì)特殊對(duì)待的字符,那還有什么字符會(huì)被特殊對(duì)待的呢?(O_o)??

& 是一個(gè),因?yàn)闉g覽器會(huì)認(rèn)為 & 是一個(gè)字符實(shí)體的開始,如果你輸入了 <,瀏覽器會(huì)將其解釋為 <,但是當(dāng) < 是作為用戶輸入的值時(shí),應(yīng)該僅僅是顯示用戶輸入的值,而不是將其解釋為一個(gè) <。

"" 也要注意,舉個(gè)例子:

服務(wù)器端渲染的代碼為:

function render (input) {
  return ""
}

input 的值如果直接來自于用戶的輸入,用戶可以輸入 "> ,最終渲染的 HTML 代碼就變成了:

 ">

結(jié)果又是一次 XSS 攻擊……

最后還有一個(gè)是反引號(hào) `,在 IE 低版本中(≤ 8),反引號(hào)可以用于關(guān)閉標(biāo)簽:

所以我們最終確定的要轉(zhuǎn)義的字符為:&, <, >, ", ", 和 `。轉(zhuǎn)義對(duì)應(yīng)的值為:

& --> &
< --> <
> --> >
" --> "
" --> '
` --> <

值得注意的是:?jiǎn)我?hào)和反引號(hào)使用是實(shí)體數(shù)字、而其他使用的是實(shí)體名稱,這主要是從兼容性的角度考慮的,有的瀏覽器并不能很好的支持單引號(hào)和反引號(hào)的實(shí)體名稱。

_.escape

那么具體我們?cè)撊绾螌?shí)現(xiàn)轉(zhuǎn)義呢?我們直接看一個(gè)簡(jiǎn)單的實(shí)現(xiàn):

var _ = {};

var escapeMap = {
    "&": "&",
    "<": "<",
    ">": ">",
    """: """,
    """: "'",
    "`": "`"
};

_.escape = function(string) {
    var escaper = function(match) {
        return escapeMap[match];
    };
    // 使用非捕獲性分組
    var source = "(?:" + Object.keys(escapeMap).join("|") + ")";
    console.log(source) // (?:&|<|>|"|"|`)
    var testRegexp = RegExp(source);
    var replaceRegexp = RegExp(source, "g");

    string = string == null ? "" : "" + string;
    return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
}

實(shí)現(xiàn)的思路很簡(jiǎn)單,構(gòu)造一個(gè)正則表達(dá)式,先判斷是否能匹配到,如果能匹配到,就執(zhí)行 replace,根據(jù) escapeMap 將特殊字符進(jìn)行替換,如果不能匹配,說明不需要轉(zhuǎn)義,直接返回原字符串。

值得一提的是,我們?cè)诖a中打印了構(gòu)造出的正則表達(dá)式為:

(?:&|<|>|"|"|`)

其中的 ?: 是個(gè)什么意思?沒有這個(gè) ?: 就不可以匹配嗎?我們接著往下看。

非捕獲分組

(?:pattern) 表示非捕獲分組,即會(huì)匹配 pattern 但不獲取匹配結(jié)果,不進(jìn)行存儲(chǔ)供以后使用。

我們來看個(gè)例子:

function replacer(match, p1, p2, p3) {
    // match,表示匹配的子串 abc12345#$*%
    // p1,第 1 個(gè)括號(hào)匹配的字符串 abc
    // p2,第 2 個(gè)括號(hào)匹配的字符串 12345
    // p3,第 3 個(gè)括號(hào)匹配的字符串 #$*%
    return [p1, p2, p3].join(" - ");
}
var newString = "abc12345#$*%".replace(/([^d]*)(d*)([^w]*)/, replacer); // abc - 12345 - #$*%

現(xiàn)在我們給第一個(gè)括號(hào)中的表達(dá)式加上 ?:,表示第一個(gè)括號(hào)中的內(nèi)容不需要儲(chǔ)存結(jié)果:

function replacer(match, p1, p2) {
    // match,表示匹配的子串 abc12345#$*%
    // p1,現(xiàn)在匹配的是字符串 12345
    // p1,現(xiàn)在匹配的是字符串 #$*%
    return [p1, p2].join(" - ");
}
var newString = "abc12345#$*%".replace(/(?:[^d]*)(d*)([^w]*)/, replacer); // 12345 - #$*%

_.escape 函數(shù)中,即使不使用 ?: 也不會(huì)影響匹配結(jié)果,只是使用 ?: 性能會(huì)更高一點(diǎn)。

反轉(zhuǎn)義

我們使用了 _.escape 將指定字符轉(zhuǎn)為字符實(shí)體,我們還需要一個(gè)方法將字符實(shí)體轉(zhuǎn)義回來。

寫法與 _.unescape 類似:

var _ = {};

var unescapeMap = {
    "&": "&",
    "<": "<",
    ">": ">",
    """: """,
    "'": """,
    "`": "`"
};

_.unescape = function(string) {
    var escaper = function(match) {
        return unescapeMap[match];
    };
    // 使用非捕獲性分組
    var source = "(?:" + Object.keys(unescapeMap).join("|") + ")";
    console.log(source) // (?:&|<|>|"|"|`)
    var testRegexp = RegExp(source);
    var replaceRegexp = RegExp(source, "g");

    string = string == null ? "" : "" + string;
    return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
}

console.log(_.unescape("Curly, Larry & Moe")) // Curly, Larry & Moe
抽象

你會(huì)不會(huì)覺得 _.escape_.unescape 的代碼實(shí)在是太像了,以至于讓人感覺很冗余呢?

那么我們又該如何優(yōu)化呢?

我們可以先寫一個(gè) _.invert 函數(shù),將 escapeMap 傳入的時(shí)候,可以得到 unescapeMap,然后我們?cè)俑鶕?jù)傳入的 map (escapeMap 或者 unescapeMap) 不同,返回不同的函數(shù)。

實(shí)現(xiàn)的方式很簡(jiǎn)單,直接看代碼:

/**
 * 返回一個(gè)object副本,使其鍵(keys)和值(values)對(duì)換。
 * _.invert({a: "b"});
 * => {b: "a"};
 */
_.invert = function(obj) {
    var result = {};
    var keys = Object.keys(obj);
    for (var i = 0, length = keys.length; i < length; i++) {
        result[obj[keys[i]]] = keys[i];
    }
    return result;
};

var escapeMap = {
    "&": "&",
    "<": "<",
    ">": ">",
    """: """,
    """: "'",
    "`": "`"
};
var unescapeMap = _.invert(escapeMap);

var createEscaper = function(map) {
    var escaper = function(match) {
        return map[match];
    };
    // 使用非捕獲性分組
    var source = "(?:" + _.keys(map).join("|") + ")";
    var testRegexp = RegExp(source);
    var replaceRegexp = RegExp(source, "g");
    return function(string) {
        string = string == null ? "" : "" + string;
        return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
    };
};

_.escape = createEscaper(escapeMap);
_.unescape = createEscaper(unescapeMap);
underscore 系列

underscore 系列目錄地址:https://github.com/mqyqingfeng/Blog。

underscore 系列預(yù)計(jì)寫八篇左右,重點(diǎn)介紹 underscore 中的代碼架構(gòu)、鏈?zhǔn)秸{(diào)用、內(nèi)部函數(shù)、模板引擎等內(nèi)容,旨在幫助大家閱讀源碼,以及寫出自己的 undercore。

如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/93813.html

相關(guān)文章

  • Underscore源碼中文注釋(轉(zhuǎn))

    摘要:創(chuàng)建一個(gè)全局對(duì)象在瀏覽器中表示為對(duì)象在中表示對(duì)象保存下劃線變量被覆蓋之前的值如果出現(xiàn)命名沖突或考慮到規(guī)范可通過方法恢復(fù)被占用之前的值并返回對(duì)象以便重新命名創(chuàng)建一個(gè)空的對(duì)象常量便于內(nèi)部共享使用將內(nèi)置對(duì)象的原型鏈緩存在局部變量方便快速調(diào)用將 // Underscore.js 1.3.3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc....

    Guakin_Huang 評(píng)論0 收藏0
  • underscore 的源碼該如何閱讀?

    摘要:所以它與其他系列的文章并不沖突,完全可以在閱讀完這個(gè)系列后,再跟著其他系列的文章接著學(xué)習(xí)。如何閱讀我在寫系列的時(shí)候,被問的最多的問題就是該怎么閱讀源碼我想簡(jiǎn)單聊一下自己的思路。感謝大家的閱讀和支持,我是冴羽,下個(gè)系列再見啦 前言 別名:《underscore 系列 8 篇正式完結(jié)!》 介紹 underscore 系列是我寫的第三個(gè)系列,前兩個(gè)系列分別是 JavaScript 深入系列、...

    weknow619 評(píng)論0 收藏0
  • underscore 系列實(shí)現(xiàn)一個(gè)模板引擎(下)

    摘要:前言本篇接著上篇系列之實(shí)現(xiàn)一個(gè)模板引擎上。字符串中的每個(gè)字符均可由一個(gè)轉(zhuǎn)義序列表示。在中,有四個(gè)字符被認(rèn)為是行終結(jié)符,其他的折行字符都會(huì)被視為空白。 前言 本篇接著上篇 underscore 系列之實(shí)現(xiàn)一個(gè)模板引擎(上)。 鑒于本篇涉及的知識(shí)點(diǎn)太多,我們先來介紹下會(huì)用到的知識(shí)點(diǎn)。 反斜杠的作用 var txt = We are the so-called Vikings from th...

    gyl_coder 評(píng)論0 收藏0
  • Underscore.js 1.8.3 學(xué)習(xí)筆記

    摘要:值得注意的是,如果值在前面也就是值小于值,那么值域會(huì)被認(rèn)為是零長(zhǎng)度,而不是負(fù)增長(zhǎng)。 underscore.js源碼加注釋一共1500多行,它提供了一整套函數(shù)式編程實(shí)用的功能,一共一百多個(gè)函數(shù),幾乎每一個(gè)函數(shù)都可以作為參考典范。初讀的時(shí)候,真是一臉懵圈,各種函數(shù)閉包、迭代和嵌套的使用,讓我一時(shí)很難消化。在這里,我來記錄一下我學(xué)習(xí)underscore.js的一些發(fā)現(xiàn),以及幾個(gè)我認(rèn)為比較經(jīng)典...

    springDevBird 評(píng)論0 收藏0
  • underscore 系列實(shí)現(xiàn)一個(gè)模板引擎(上)

    摘要:第一版我們來嘗試實(shí)現(xiàn)第一版第一版為了驗(yàn)證是否有用文件文件完整的可以查看示例一在這里我們使用了,實(shí)際上在文章中使用的是構(gòu)造函數(shù)。構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對(duì)象。 前言 underscore 提供了模板引擎的功能,舉個(gè)例子: var tpl = hello: ; var compiled = _.template(tpl); compiled({name: Kevin}); // hello:...

    LeexMuller 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<