Общая ошибка: 1366 Неверное строковое значение: ‘\ xD6wn Sm …’ для столбца ‘user_agent’

Мне нужно сохранить пользовательский агент посетителя.

Это мой фрагмент:

// User Agent
$ua = $_SERVER['HTTP_USER_AGENT']??'';
$ua_md5 = md5($ua);
// Search if the UA already exists in the user_agents table
$ua_id = $db->query("SELECT id FROM user_agents WHERE md5='".$ua_md5."';")->fetchColumn();

if(!$ua_id) {
// If it doesn't exist, insert it and get its id
$db->query("INSERT INTO user_agents (md5, user_agent) VALUES ('$ua_md5', ".$db->quote($ua).")");
$ua_id = $db->lastInsertId();
}

Я использую PDO: quote вместо подготовленных операторов только по соображениям производительности (это быстрее, и этот скрипт выполняется тысячи раз в секунду).

Бывает, что у некоторых пользователей есть этот пользовательский агент:

Mozilla / 5.0 (Linux; Android 5.0; \ xD6wn Smart Build / LRX21M) AppleWebKit / 537.36 (KHTML, как Gecko) Версия / 4.0 Chrome / 37.0.0.0 Mobile Safari / 537.36

И вставка терпит неудачу для этой ошибки:

«Сообщение PHP: SQLSTATE [HY000]: общая ошибка: 1366 Неверное строковое значение:« \ xD6wn Sm … »для столбца« user_agent »в строке 1» при чтении заголовка ответа из восходящего потока

В чем причина и как это можно исправить?


Изменить: больше отладки выяснил $ua значение:

Mozilla / 5.0 (Linux; Android 5.0; ÖWN S1 Build / LRX21M) AppleWebKit / 537.36 (KHTML, как Gecko) Версия / 4.0 Chrome / 37.0.0.0 Mobile Safari / 537.36

2

Решение

Прежде всего, когда речь идет о проблемах кодирования, всегда необходимо проверять необработанные байты и никогда не полагаться на выходные данные какого-либо процесса, который обрабатывает или интерпретирует входные данные. В случае с PHP, var_dump () всегда хорошая отправная точка, но вам также нужно сбросить в гекс или даже прибегнуть к шестнадцатеричный редактор:

<?php
var_dump(bin2hex($_SERVER['HTTP_USER_AGENT']));

Мое обоснованное предположение (и я не верю, что это слишком далеко от истины) таково:

  1. Некоторый браузер Android отправляет заголовок HTTP, который включает Öwn Smart Build закодировано в ISO-8859-1, где Ö ака «ПИСЬМО ЛАТИНСКОГО КАПИТАЛА O С ДИАРЕЗОМ» (U + 00D6) кодируется как D6,

  2. Стек вашего приложения настроен для UTF-8 (разумный выбор), где Ö будет закодирован как C396,

  3. PHP не знает / заботится, потому что строки PHP не поддерживают кодирование (они просто потоки байтов).

  4. MySQL обрабатывается D6Говорят, что это UTF-8 (но это не так).

  5. Имел D6 Если бы действительный символ UTF-8 (другой) или часть (другой) многобайтовой последовательности, вставка была бы завершена с незначительной потерей данных (оригинал Öwn текст был бы потерян и заменен чем-то другим). Хорошо это или плохо, это недопустимый UTF-8, поэтому MySQL прерывает вставку с сообщением об ошибке, которое вы описываете.

Почему MySQL не может справиться с этим? Давайте проверим UTF-8 определение:

Nr of
Bytes    Byte 1   Byte 2       Byte 3     Byte 4
1      0xxxxxxx
2      110xxxxx   10xxxxxx
3      1110xxxx   10xxxxxx   10xxxxxx
4      11110xxx   10xxxxxx   10xxxxxx   10xxxxxx

Оригинальная латиница-1 Öw текст закодирован как D6 77 который в переводе на двоичный:

11010110 01001101
^^^      ^^

В UTF-8, 110… означает «Начало двухбайтового символа». Второй байт должен начинаться с 10… но у нас есть 01… вместо. К сожалению!

Как вы можете решить это? Это сложнее, чем кажется. Если бы вы могли точно знать, что ввод ISO-8859-1, это просто прямое преобразование:

<?php
$input = "\xD6wn";
$output = mb_convert_encoding($input, 'UTF-8', 'ISO-8859-1');
var_dump(bin2hex($input), bin2hex($output));
string(6) "d6776e"string(8) "c396776e"

(Онлайн демо)

Но как ты можешь знать? Я не уверен, позволяет ли заголовок User-Agent MIME закодированные слова и даже там, браузер может просто отправлять неверные данные в любом случае. Возможно, вы можете поймать ошибку (MySQL код ошибки 1366 ака ER_TRUNCATED_WRONG_VALUE_FOR_FIELD выглядит достаточно точно) и попробуйте снова, предполагая ISO-8859-1. И, вероятно, это также хорошая идея, чтобы убедиться, что входные данные являются действительными UTF-8, хотя это может усложнить обработку:

<?php
$latin1 = "\xD6wn";
$utf8 = "\xc3\x96wn";
var_dump(mb_check_encoding($latin1, 'UTF-8'));
var_dump(mb_check_encoding($utf8, 'UTF-8'));
bool(false)
bool(true)

(Онлайн демо)

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]