[= Суть проблемы =]
Ответ сервера при использовании Ajax по умолчанию приходит в кодировке "utf-8".
Если запрашиваемая страница изначально имеет кодировку "windows-1251", то кириллические символы будут безвозвратно искажены свойством responseText. В таком случае вывод ответа сервера будет представлен знаками вопроса "?", или другими неопознанными символами.
[= Способ решения =]
Их много. Все способы можно разделить на две группы: изменение кодировки на стороне сервера и на стороне клиента.
Когда мы имеем доступ к серверу, особых проблем обычно не возникает: хедеры, встроенные функции, перекодировка по таблицам символов. Описаний этого полно в нете.
Обратная ситуация. Например, нашли мы уязвимость XSS, желаем использовать Ajax для незаметного получения каких-либо данных с других страниц на сервере. При этом: во-первых, кодировка у страниц "windows-1251", во-вторых, сервер в ответе на Ajax-запрос не указывает кодировку пересылаемых данных.
В таких случаях возникает необходимость изменить кодировку уже на стороне клиента.
Универсального способа для этого нет. Для каждого браузера (группы браузеров) они свои. Эти способы, являясь от части продуктом моего обобщения разрозненной информации, а отчасти моего собственного творчества, я представляю.
[= Вариант, работающий во всех основных браузерах =]
test.html (необходимо убирать пробелы-разрывы, которые вставляет сайт)
PHP код:
<html>
<div id=qwerty></div>
<script>
if (window.execScript)
window.execScript('Function BinaryToString(Binary) \r cl1=1 \r cl2=1 \r cl3=1 \r L=LenB(Binary) \r Do While cl1<=L pl3=pl3&Chr(AscB(MidB(Binary,cl1,1))) \r cl1=cl1+1 \r cl3=cl3+1 \r If cl3>300 Then \r pl2=pl2&pl3 \r pl3="" \r cl3=1 \r cl2=cl2+1 \r If cl2>200 Then \r pl1=pl1&pl2 \r pl2="" \r cl2=1 \r End If \r End If \r Loop \r BinaryToString=pl1&pl2&pl3 \r End Function', 'VBScript');
var request=false;
try {request=new XMLHttpRequest();}
catch (forIE) {request=new ActiveXObject('Msxml2.XMLHTTP');}
function ReadFile()
{
request.open("GET", 'text.html');
if (request.overrideMimeType)
{
request.overrideMimeType('text/html; charset=windows-1251');
request.onreadystatechange=override;
}
else
request.onreadystatechange=readResponse;
request.send(null);
}
function override()
{
if (request.readyState == 4)
{
request.open("GET", 'text.html');
request.onreadystatechange=readResponse;
request.send(null);
}
}
function readResponse()
{
if (request.readyState == 4)
{
if (request.overrideMimeType)
document.getElementById('qwerty').innerHTML=request.responseText;
else
document.getElementById('qwerty').innerHTML=BinaryToString(request.responseBody);
}
}
</script>
<input type=button value='Жмах' onclick='ReadFile()'>
</html>
text.html
Код:
<html>Здесь что-нибудь на русском</html>
А теперь по каждому браузеру в отдельности, чтобы стало понятно тем, кто не разобрался.
1) Internet Explorer
Объекты Msxml2.XMLHTTP и XMLHttpRequest (IE 7.0) в этом браузере имеют фирменное свойство - "responseBody". В нем находится ответ сервера не в обычном строчном, а в бинарном виде. То есть в том виде, когда еще он не распарсился в соответствии с какой-либо кодировкой. В данном случае именно эта сторона для наc имеет значение. Будем парсить бинарный ответ самостоятельно.
В стандартном JavaScript функций для работы с таким бинарным ответом сервера нет. IE имеет несколько элементов управления ActieX, которые могут это делать, но в некоторых случаях они из соображений безопасности бывают выключены.
Имеется отличный способ обработки такого ответа с помощью VBScript, в котором присутствуют необходимые для этого встроенные функции.
VBScript-функция
Код:
<script language=vbscript>
Function BinaryToString(Binary)
'Antonin Foller, http://www.pstruh.cz
'Optimized version of a simple BinaryToString algorithm.
Dim cl1, cl2, cl3, pl1, pl2, pl3, L
cl1 = 1
cl2 = 1
cl3 = 1
L = LenB(Binary)
Do While cl1<=L
pl3 = pl3 & Chr(AscB(MidB(Binary,cl1,1)))
'Аналогов функций AscB() и MidB() в js нет.
cl1 = cl1 + 1
cl3 = cl3 + 1
If cl3 > 300 Then
pl2 = pl2 & pl3
pl3 = ""
cl3 = 1
cl2 = cl2 + 1
If cl2 > 200 Then
pl1 = pl1 & pl2
pl2 = ""
cl2 = 1
End If
End If
Loop
BinaryToString = pl1 & pl2 & pl3
End Function
</script>
Внедряем vbs-функцию в js с помощью фирменной функции IE - execScript()
(убрать пробел в предпоследнем упоминании функции BinaryToString)
PHP код:
<html>
<div id=qwerty></div>
<script>
window.execScript('Function BinaryToString(Binary) \r cl1=1 \r cl2=1 \r cl3=1 \r L=LenB(Binary) \r Do While cl1<=L pl3=pl3&Chr(AscB(MidB(Binary,cl1,1))) \r cl1=cl1+1 \r cl3=cl3+1 \r If cl3>300 Then \r pl2=pl2&pl3 \r pl3="" \r cl3=1 \r cl2=cl2+1 \r If cl2>200 Then \r pl1=pl1&pl2 \r pl2="" \r cl2=1 \r End If \r End If \r Loop \r BinaryToString=pl1&pl2&pl3 \r End Function', 'VBScript');
var request = new ActiveXObject('Msxml2.XMLHTTP');
function ReadFile()
{
request.open('GET', 'text.html');
request.onreadystatechange=readResponse;
request.send(null);
}
function readResponse()
{
if (request.readyState == 4) document.getElementById('qwerty').innerHTML=BinaryToString(request.responseBody); // передаем в функцию BinaryToString бинарный ответ сервера в качестве параметра и помещаем в див результат ее работы
}
</script>
<input type=button value='Жмах' onclick='ReadFile()'>
</html>
2) FireFox, Netscape
Используется метод overrideMimeType() объекта реквест
PHP код:
<html>
<div id=qwerty></div>
<script>
var request = new XMLHttpRequest();
function ReadFile()
{
request.open("GET", 'text.html');
request.overrideMimeType('text/html; charset=windows-1251'); // метод переопределяет заголовок "Content-Type" от сервера
request.onreadystatechange=readResponse;
request.send(null);
}
function readResponse()
{
if (request.readyState == 4)
document.getElementById('qwerty').innerHTML=request.responseText; }
</script>
<input type=button value='Жмах' onclick='ReadFile()'>
</html>
3) Opera
Метод overrideMimeType() предусмотрен в этом браузере в объекте реквест, однако в текущей версии 9.24 кодировку он не переопределяет.
У данного браузера имеется другая особенность. Состоит она в автоматическом определении кодировки контента. То есть, когда запрашиваешь аяксом страницу в 'windows-1251' первый раз (с хоста), она выводится искаженной. Во второй раз та же страница отображается уже корректно. Данную особенность мы и будем использовать.
PHP код:
<html>
<div id=qwerty></div>
<script>
var request = new XMLHttpRequest();
function ReadFile()
{
request.open("GET", 'text.html'); // первый запрос
request.onreadystatechange=override;
request.send(null);
}
function override()
{
if (request.readyState == 4)
{
request.open("GET", 'text.html'); // второй запрос
request.onreadystatechange=readResponse;
request.send(null);
}
}
function readResponse()
{
if (request.readyState == 4) // во второй раз, поскольку мы не разорвали соединение, состояние готовности сразу равно 4, в свойство попадает ответ сервера, находящийся в памяти.
document.getElementById('qwerty').innerHTML=request.responseText;
}
</script>
<input type=button value='Жмах' onclick='ReadFile()'>
</html>
В действительности не происходит ожидания ответа сервера при втором запросе, результат выводится сразу. Потеря времени ничтожна мала.
Автоматическое определение кодировки Оперой производится независимо от установленной пользователем (пользовательской кодировкой может не быть "win-1251"), а также от настроек кэширования.
Ключи поиска: кодировка Ajax кодировка перекодировка Ajax перекодировка UTF-8 Win-1251