Вступление:
Многие, не исключая автора, сталкивались с проблемой, когда нужно использовать одновременно php и ajax в web проекте, написанном с использованием однобайтовой кодовой страницы (cp1251, koi-8, cp866, cp1252, latin-1 и т.д.). Все находили известный тезис что «AJAX работает только с UNICODE».
Попробуем разобрать проблему и найти решение.
Для нетерпеливых читателей скажу, что решение есть в виде написанной функции “encodeFormField”, которая работает с ЛЮБОЙ однобайтовой кодировкой. Эту функцию можно (нужно) использовать вместо escape или encodeURIComponent, после чего строковые данные доставляются до php в исходной кодировке (нет необходимости в использовании iconv в php скриптах).
Исподники (BSD License) можно загрузить по адресу:
www.viasoft.com.ua/js/encode_form_field.zip
Итак, задача:
1. Есть сайт, написанный на php
2. Кодировка html страниц сайта cp1251 (koi-8, cp866, cp1252, latin-1 и т.д.).
3. Нужно добавить AJAX функциональность, без необходимости переписывать php код
Лирика:
Как пишутся php скрипты обычно:
<body>
<form>
<input type="text" name="my_field" value="Привет!" />
<input type="submit" value="show me" />
</form>
<div>
<?
if (isset($_REQUEST['my_field']))
{
if ($_REQUEST['my_field'] == 'Привет!')
{
echo 'Напишите что-то поинтереснее!';
}
else
{
echo 'Получено:'.htmlspecialchars($_REQUEST['my_field']);
}
}
?>
</div>
</body>
Все работает, как и ожидалось:
Мы просто читаем данные запроса из массива $_REQUEST['my_field'] и получаем их в том виде, который ввел пользователь (если мы не забыли про «Content-Type: text/html; charset=windows-1251»).
Как это работает на самом деле:
1. Браузер передавая данные на сервер, кодирует символы текущей кодировки с кодами > 127 как последовательность байт в виде %{2 hex символа}. Например, в примере выше (для cp1251) мы увидим «test.php?my_field=%CF%F0%E8%E2%E5%F2%21». В этом случае CF — это код буквы «П» F0 — код буквы «р» и т.д.
2. PHP обрабатывает строковые данные «как есть», т.е. манипулирует строковыми данными как последовательностями однобайтовых символов, не пытаясь преобразовывать их в unicode (это скорее хорошо, так как это быстро). Поэтому последовательность байт %CF%F0%E8%E2%E5%F2%21 превратится в строку из 7-ми байт, что соответствует 7-ми символам слова «Привет!» в cp1251
3. Мы можем эту строку записать в БД, сравнить с другой строкой из таких же 7-ми символов и т.д.
Все это очень удобно, просто, так и хочется писать код дальше.
Собственно на php так это и делается в большинстве случаев.
А теперь JavaScript:
Допустим, мы хотим отправить на сервер HTTP request, сформированный в JavaScript (например, AJAX или даже простой location.replace переход) в котором поле query запроса будет кодироваться из JavaScript функцией.
Попробуем для кодирования поля из нашей формы применить функцию escape:
<body>
<form>
<input type="text" name="my_field" id="my_field" value="Привет!" />
<input type="button" value="show me" onclick="sendform()"/>
</form>
<div>
<script>
function sendform()
{
location.replace(location.pathname+'?my_field='+encodeURIComponent(document.getElementById('my_field').value));
}
</script>
<?
if (isset($_REQUEST['my_field']))
{
if ($_REQUEST['my_field'] == 'Привет!')
{
echo 'Напишите что-то поинтереснее!';
}
else
{
echo 'Получено:'.htmlspecialchars($_REQUEST['my_field']);
}
}
?>
</div>
</body>
Как видим, «escape» не подходит:
Вместо:
test.php?my_field=%CF%F0%E8%E2%E5%F2%21
На сервер передается:
test.php?my_field=%u041F%u0440%u0438%u0432%u0435%u0442%21
В чем же дело?
«escape» кодирует символы с кодами > 127 из исходной кодовой страницы как %u{4 hex символа(unicode)} и php не распознает такую кодировку (что логично, вспомним, что строки в php это просто последовательности байт).
Попробуем применить вместо escape функцию encodeURIComponent:
Заменим:
location.replace(location.pathname+'?my_field='+escape(document.getElementById('my_field').value));
на:
location.replace(location.pathname+'?my_field='+encodeURIComponent(document.getElementById('my_field').value));
Как видим, «encodeURIComponent» тоже не подходит:
Вместо:
test.php?my_field=%CF%F0%E8%E2%E5%F2%21
На сервер передается:
test.php?my_field=%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD!
В чем же дело?
«encodeURIComponent» кодирует символы с кодами > 127 из исходной кодовой страницы путем перевода их в UTF-8 и затем передает их как последовательность %{2 hex символа}.
Примечание: Аналогично работает с символами > 127 функция encodeURI, впрочем она не подходит для url компонент.
Причина проблемы:
1. В JavaScript все строки представляются в unicode (именно так: При любой загрузке JavaScript кода, все строковые константы в нем автоматически переводятся в unicode).
2. В JavaScript нет других функций, которые кодируют строки параметров url, кроме escape и encodeURIComponent
Собственно вот мы и видим причину, по которой так много пишется что «AJAX работает только с UNICODE».
Существующие варианты решения:
1. Конечно, это не проблема, если весь сайт в UTF-8 или если его можно перенести в UTF-8, но это большой кусок работы и это не всегда возможно.
2. Можно использовать на PHP стороне функцию iconv для того чтобы перекодировать UTF-8 в нужную кодировку, но это не удобно (в частности, нужно просмотреть все обращения к аргументам) — хотя это наиболее часто рекомендуемый способ.
Как решить проблему проще:
Нужна JavaScript функция, которая кодирует данные также, как и браузер, тогда можно писать на php «как обычно».
Фактически, нужна функция которая использует таблицу перекодировки UNICODE -> текущая кодовая страница.
Существуют решения, которые реализуют такое преобразование для некоторых кодировок, но они не универсальны.
Решение:
После нескольких дней экспериментов, было найдено универсальное решение — функция encodeFormField, которая работает в любой однобайтовой кодировке (cp1251, koi-8, cp866, cp1252, latin-1 и т.д.).
Это и было опробовано в нескольких проектах.
Загрузить скрипт можно отсюда:
www.viasoft.com.ua/js/encode_form_field.zip
Основная идея:
0. Используем браузер для построения таблицы перекодировки unicode -> текущая кодовая страница:
1. javascript файл содержит строковую константу, которая содержит символы с кодами от 128 до 255 включительно.
2. Броузер загружает эту строку и преобразовывает ее в unicode в ТЕКУЩЕЙ кодировке.
3. Когда нужно преобразовать символ с кодом > 127 из ТЕКУЩЕЙ кодировки, ищется его позиция в этой строке.
4. Найденная позиция+128 и будет искомым hex кодом символа и она используется для кодировки url (или post) компонента запроса.
Как пользоваться:
1. Подключите encode_form_field.js
2. Используйте функцию encodeFormField(text) вместо escape(text) или encodeURIComponent(text) и получите правильную кодирование символов для однобайтовых кодировок.
Комменты приветствуются.
Успехов!
PS:
НЕ РЕКОМЕНДУЕТСЯ использовать copy-n-paste для того, чтобы вставить этот код к вам на страницу, так как большинство редакторов «сломают» строку преобразования. По этой же причине редактировать исходный файл нужно с осторожностью. Лучше загрузить файл из архива и использовать его на вашем сайте.
PPS:
Скрипит публикуется под BSD License, так что если есть идеи как все это улучшить — welcome!