Обмен сообщениями перекрестного документа

Как правило сценарии, загруженные веб-контентом, подаваемым от одного источника (узел и домен), не могут получить доступ к веб-контенту, подаваемому различным источником. Это - важное средство защиты, предотвращающее множество различных векторов атаки безопасности. Однако это также мешает сценариям взаимодействовать друг с другом через эти границы.

Для создания коммуникации между документами от различных источников проще спецификация HTML 5 добавляет обмен сообщениями перекрестного документа. Эта функция поддерживается в Safari 4.0 и позже.

Добавление сообщения к окну

Для добавления сообщения необходимо сначала получить Window объект документа Вы хотите обмениваться сообщениями. В действительности это означает, что можно добавить сообщения только к:

Как только Вы получили Window объект для целевого документа, можно отправить ему сообщение со следующим кодом:

windowObj.postMessage('test message', 'http://example.com');

Первый параметр является произвольным сообщением.

Второй параметр является целевым значением источника. Значением источника является просто URL с удаленной частью пути. Например, источник локального файла file://. Путем указания целевого источника Вы говорите, что то Ваше сообщение должно только быть передано, если бы текущее содержание целевого окна прибыло из того источника.

Несмотря на то, что можно указать звездочку (*) подстановочный знак для целевого источника (чтобы позволить сообщению, которое будет отправлено независимо от того, куда содержание целевого окна прибыло из), необходимо сделать так, только если Вы уверены, что не было бы вредно, если бы Ваше сообщение было получено довольным возникновение из различного веб-сайта.

Получение сообщения, добавленного к окну

Для получения сообщений необходимо добавить слушателя события для message тип события к Вашему документу window объект. Чтобы сделать это, используйте следующий код:

function messageReceive(evt) {
    if (evt.origin == 'http://example.com') {
        // The message came from an origin that
        // your code trusts.  Work with the message.
        alert('Received data '+evt.data);
 
        // Send a message back to the source:
        evt.source.postMessage('response', evt.origin);
    } else {
        alert('unexpected message from origin '+evt.origin);
    }
}
window.addEventListener('message', messageReceive, false);

message событие, которое Вы получаете, имеет три свойства интереса:

Пример открытия службы: окна сообщения

Этот раздел содержит два листинга кода: index.html и msg_contents.html это, когда объединено, реализует открытие базовой услуги и передачу данных поверх перекрестного документа, передающего архитектуру.

Для использования этого кода необходимо сначала сделать следующие вещи:

Как только Вы завершили эти шаги, необходимо видеть несколько полей, один на запись в boxes объект. Каждое из этих полей должно содержать маленькую форму с полем ввода текста, серией флажков (один для каждого из внешних полей), и кнопка отправки.

При вводе чего-то в текстовое поле проверьте один из флажков, и щелчок подчиняется, текст должен появиться у основания окна, имя которого соответствует флажку.

  Перекрестный документ перечисления 1, передающий пример: index.html

<html><head>
<script language='javascript' type='text/javascript'><!--
/*global alert, navigator, document, window */
 
 
var window_list = [];
var origin_list = [];
 
var boxes = {
    local: "http://host1.domain1.top/messages2/msg_contents.html",
    remote: "http://host2.domain2.top/messages2/msg_contents.html",
    third: "http://host3.domain3.top/messages2/msg_contents.html"
};
 
var allowed_origins = {
    'http://host1.domain1.top': 1,
    'http://host2.domain2.top': 1,
    'http://host3.domain3.top': 1
};
 
function smartsplit(string, pattern, count)
{
    // alert('string '+string);
    // alert('pattern "'+pattern+'"');
    // alert('count '+count);
 
    var lastpos = count - 1;
 
    var arr = string.split(/ /);
    // alert('AC: '+arr.length+" "+string);
    if (arr.length > lastpos) {
        var temparr = [];
        for (var i=lastpos; i<arr.length; i++) {
            temparr[i-lastpos] = arr[i];
            arr[i] = undefined;
        }
        arr[lastpos] = temparr.join(pattern);
    }
    return arr;
}
 
function listWindows()
{
    var retstring = "";
    for (var i in origin_list) {
        if (origin_list.hasOwnProperty(i)) {
        // alert('UUID: '+i+' Origin: '+origin_list[i]);
        retstring += i+' '+origin_list[i]+'\n';
        }
    }
    return retstring;
}
 
 
function messageReceive(evt) {
    var windowlist;
 
    if (evt.origin === null || evt.origin in allowed_origins) {
    var arr = smartsplit(evt.data, " ", 2);
 
    if (arr[0] == 'sendto') {
        // usage: sendto UUID remote_origin message
        arr = smartsplit(evt.data, " ", 4);
        var remote_window = window_list[arr[1]];
        remote_window.postMessage('sendto_output '+arr[3], arr[2]);
    } else if (arr[0] == 'register') {
        // usage register UUID
        var name = arr[1];
 
        if (window_list[name]) {
            // name conflict.
            var add=1;
            while (window_list[name+'_'+add]) {
                add++;
            }
            name = name+'_'+add;
            evt.source.postMessage("register_newid "+name, evt.origin);
        }
 
        window_list[name] = evt.source;
        origin_list[name] = evt.origin;
        windowlist = listWindows();
        for (var windowid in window_list) {
            if (window_list.hasOwnProperty(windowid)) {
            // alert('windowid: '+windowid);
            window_list[windowid].postMessage("list_output "+windowlist, origin_list[windowid]);
            }
        }
    } else if (arr[0] == 'list') {
        // usage list
        windowlist = listWindows();
        evt.source.postMessage("list_output "+windowlist, evt.origin);
    } else {
        alert('unknown command '+arr[0]);
    }
    } else {
        alert('unexpected message from origin '+evt.origin);
    }
}
 
function dosetup()
{
    if (navigator.userAgent.match(/Safari/)) {
        var version = parseFloat(navigator.userAgent.replace(/.*AppleWebKit\//, "").replace(/[^0-9.].*$/, ""));
 
        if (version < 528) {
            alert('WebKit version '+version+' does not support this application.');
        }
 
    }
    // for (var i=0; i<nchannels; i++) {
        // alert('setup channel '+i);
        // channel[i] = new MessageChannel();
    // }
    window.addEventListener('message', messageReceive, false);
 
    var boxstr = "";
    for (var i in boxes) {
        if (boxes.hasOwnProperty(i)) {
        boxstr += "<iframe height='600' id='"+i+"' src='"+boxes[i]+"'></iframe>\n";
        }
    }
    var boxlistdiv = document.getElementById('boxlist');
    boxlistdiv.innerHTML = boxstr;
}
 
dosetup();
 
--></script>
</head>
<body onload='dosetup();'>
<div id='boxlist'>
</div>
</body>
</html>

  Перекрестный документ перечисления 2, передающий пример: message_contents.html

<html><head>
<script language='javascript' type='text/javascript'><!--
/*global alert, document, window, navigator */
 
var allowed_origins = {
    'http://stavromula-beta.apple.com': 1,
    'http://holst.apple.com': 1
};
 
var received_root_origin = '';
 
var root_origin = '*';
 
function smartsplit(string, pattern, count)
{
        // alert('string '+string);
        // alert('pattern "'+pattern+'"');
        // alert('count '+count);
 
        var lastpos = count - 1;
 
        var arr = string.split(/ /);
        // alert('AC: '+arr.length+" "+string);
        if (arr.length > lastpos) {
                var temparr = [];
                for (var i=lastpos; i<arr.length; i++) {
                        temparr[i-lastpos] = arr[i];
            arr[i] = undefined;
                }
                arr[lastpos] = temparr.join(pattern);        }
        return arr;
}
 
function mkcheckbox(inpstr)
{
    var str = "";
 
    var arr = inpstr.split("\n");
    for (var entid in arr) {
        if (arr.hasOwnProperty(entid)) {
        var ent = arr[entid];
        if (ent !== "") {
            // alert('ent: '+ent);
            var bits = smartsplit(ent, " ", 2);
            str += "<input type=checkbox name='"+ent+"'>"+bits[0]+"</input>\n";
        }
        }
    }
    return str;
}
 
function messageReceive(evt) {
    if (evt.origin === null || evt.origin in allowed_origins) {
 
        // The message came from an origin that
        // your code trusts.  Work with the message.
 
        // alert('Received data: '+evt.data);
    // var tmp = 'Test this, please';
    // var x = smartsplit(tmp, " ", 2);
    // alert('x[0] = '+x[0]);
    // alert('x[1] = '+x[1]);
    // alert('x[2] = '+x[2]);
 
    var arr = smartsplit(evt.data, " ", 2);
    if (arr[0] == 'list_output') {
        if (evt.origin == root_origin || root_origin == '*') {
        var div2 = document.getElementById('temp2');
        div2.innerHTML = mkcheckbox(arr[1]);
        received_root_origin = evt.origin;
        // alert('arr[1] = '+arr[1]);
        } else {
        alert('received list_output message from unexpected origin: '+evt.origin);
        }
    } else if (arr[0] == 'sendto_output') {
            // alert('Received data: '+evt.data);
        var div3 = document.getElementById('temp3');
        div3.innerHTML += arr[1]+'<br />\n';
    } else if (arr[0] == 'register_newid') {
        var mydiv = document.getElementById('temp');
        mydiv.innerHTML = arr[1]+" box";
    }
 
        // Send a message back to the source:
        // evt.source.postMessage('response', evt.origin);
    } else {
        alert('unexpected message from origin '+evt.origin);
    }
}
 
function setup_listener()
{
    window.addEventListener('message', messageReceive, false);
}
 
function setup()
{
    var mydiv = document.getElementById('temp');
 
    // mydiv.innerHTML = 'Test';
    // mydiv.innerHTML = ' '+bigdoc;
 
    // If we can access the document object, we are locally loaded and should
    // talk to the remotely-loaded window.  Otherwise, the reverse
    // is true.
 
    var myid = '';
    if (window.parent.document) {
    myid = 'local';
    } else {
    myid = 'remote';
    }
 
    mydiv.innerHTML = myid+" box";
    // window.addEventListener('message', messageReceive, false);
    setup_listener();
 
    var topwindow = window;
    while (topwindow.parent && (topwindow.parent != topwindow)) { topwindow = topwindow.parent; }
 
    topwindow.postMessage('register '+myid, root_origin);
    // window.parent.postMessage('list', '*');
 
}
 
function sendmsg()
{
    var message = document.getElementById('sendtext').value;
    var mydiv2 = document.getElementById('temp2');
    var checkboxes = mydiv2.children;
 
    // alert('checkboxes: '+checkboxes);
    var topwindow = window;
    while (topwindow.parent && (topwindow.parent != topwindow)) { topwindow = topwindow.parent; }
 
    for (var boxid in checkboxes) {
        if (checkboxes.hasOwnProperty(boxid)) {
        var checkbox = checkboxes[boxid];
 
        // alert('checkbox: '+checkbox);
        if (checkbox.tagName == "INPUT") {
            // alert('input');
            if (checkbox.checked) {
                // alert('checked');
                var arr = smartsplit(checkbox.name, " ", 2);
                var uuid = arr[0];
                var origin = arr[1];
                // alert('send to: '+uuid+' origin '+origin);
 
                // alert('topwindow is '+topwindow);
                topwindow.postMessage('sendto '+uuid+' '+origin+' '+message, received_root_origin);
            }
        }
        }
    }
 
 
    return false;
}
 
 
--></script>
</head><body onload='setup();'>
<div id='temp'></div>
<form onsubmit='return false;'>
<input type='text' id='sendtext'></input>
<input type='submit' value='submit' onclick='sendmsg();'></input>
<div id='temp2'></div>
</form>
<div id='temp3'></div>
 
</body></html>

Можно создать многочисленные интересные расширения поверх этого вида проекта. Например, Вы могли бы добавить сообщение команды, спрашивающее окно в другом конце, какие команды это поддерживает, затем свяжитесь с ним при совместном использовании единого набора команд. Возможности безграничны.

Соображения безопасности

Существует несколько ключевых вещей, о которых необходимо знать при использовании обмена сообщениями перекрестного документа:

Как с любым программным обеспечением, для максимальной надежности и безопасности, необходимо записать выходной код тщательно для минимизации риска порождения проблем для другого кода, и необходимо записать код под предположением, что другой код злонамеренно пытается атаковать код, и таким образом необходимо выполнить тип, границы и другие проверки работоспособности соответственно.