気ままなタンス*プログラミングなどのノートブック

プログラミングやRPGツクール、DTM、VOCALOIDについてのんびり書きます。

【Django】CSRF_TOKENをセットするJavaScriptのコードリーディング(一昨日の続き)

一昨日の続き。
rinnegrid.hatenablog.com


DjangoAjaxやるときのjQueryフックコードのサンプルが記載されていた。
ただリファレンスに沿ってコピペするだけだと意味がないので、コードで何をしているかを調べることにした。

該当のコード

jQuery.ajaxSend(function(event, xhr, settings) {
    function getCookie(name) {
        var cookieValue = null;

        if(document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for(var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);

                if(cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                }
            }
        }
        return cookieValue;
    }

    function sameOrigin(url) {
        var host = document.location.host;
        var protocol = document.location.protocol;
        var sr_origin = '//' + host;
        var origin = protocol + sr_origin;

        return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
            (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr.origin + '/') ||
            !(/^(\/\/|http:|https:).*/.test(url));

    }

    function safeMethod(method) {
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }

    if(!safeMethod(settings.type) && sameOrigin(settings.url)) {
        xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
    }
});

対象のメソッド

・getCookie(name)
・sameOrigin(url)

getCookieメソッド

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

  • コードリード
【処理の目的】
・引数で受け取ったnameをキーとして持つクッキー値を取得する

【処理の概要】
・document.cookieを調査し、document.cookieがブランクでない場合のみ値を検証する
・cookieの文字列を、ゼミコロンで分割し、クッキーのキー&バリューを配列としてcookiesに返却
・キー&バリューは以下のような状態となる
 cookies[0] "anykey=hoge"
  cookies[1] " csrftoken=xxxxxxxxxx"
・ループで各要素に対して、Trimを実施し、余分な空白を除いたものをcookieに格納
・cookieの「先頭からnameの文字数+1までの部分文字列」と、name+"="が一致する場合
 値が見つかったと判断する
  name                                 -> "csrftoken"
  name.length + 1)                     -> 10
  cookie[n]                            -> "csrftoken=xxxxx"
  cookie.substring(0, name.length + 1) -> "csrftoken="
  name + "="                           -> "csrftoken="
・name.length + 1以降の文字列を取得し、decodeURIComponentで処理し、cookieValueにセット
  • ライブラリ
jQuery.trim(cookies[i])
// 概要:文字列の先頭と末尾から空白を取り除く

// 実装:669行目(jQuery1.11.2)
////////////////////////////////////////////////
trim: function( text ) {
    return text == null ?
        "" :
        ( text + "" ).replace( rtrim, "" );
},
////////////////////////////////////////////////
// メモ1
// (text + "")は、文字列に変換するイディオム
// typeofで見てみるとわかりやすい

console.log(typeof(1 + "")); // string
console.log(typeof(1)); // number

// メモ2
// rtrimはどこで定義されているのか?
/ 78行目(jQuery1.11.2)
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

sameOriginメソッド

function sameOrigin(url) {
    var host = document.location.host;
    var protocol = document.location.protocol;
    var sr_origin = '//' + host;
    var origin = protocol + sr_origin;

    return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
        (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr.origin + '/') ||
        !(/^(\/\/|http:|https:).*/.test(url));

}
  • コードリード
【処理の目的】
・引数で受け取ったurlが、リクエストを受けているサーバーと一致するかを検証する

【処理の概要】
・各サーバー情報を取得
  http://localhost:8080/hogehogeに対するリクエストとする
  document.location.host            -> localhost:8080
  document.location.protocol        -> http:
  sr_origin                         -> //localhost:8080
  origin                            -> http://localhost:8080

感想

Ajaxにおいて、「CSRF_TOKENの検証に失敗する」問題を解決することが、もともとの目的であった。
リファレンスのコードを見たときは、「クッキーから取得して、よろしくやってくれてんのか」程度の
理解であった。
先人のコードで、「よろしくやってくれている」というところで思考停止になりがちだが、
車輪の再発明(というより、再分析?)も重要だと感じた。

・似たような問題にぶつかったときに、コードリードしたかどうかによって、解決速度が変わる
・目的をもってリーディングすることで、調査すること自体に慣れる
・わかったつもりになっていて、実は説明できるまで理解していないものが抽出できる