Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensuring valid UTF-8 in PHP

Tags:

I'm using PHP to handle text from a variety of sources. I don't anticipate it will be anything other than UTF-8, ISO 8859-1, or perhaps Windows-1252. If it's anything other than one of those, I just need to make sure the text gets turned into a valid UTF-8 string, even if characters are lost. Does the //TRANSLIT option of iconv solve this?

For example, would this code ensure that a string is safe to insert into a UTF-8 encoded document (or database)?

function make_safe_for_utf8_use($string) {      $encoding = mb_detect_encoding($string, "UTF-8,ISO-8859-1,WINDOWS-1252");      if ($encoding != 'UTF-8') {         return iconv($encoding, 'UTF-8//TRANSLIT', $string);     }     else {         return $string;     } } 
like image 617
Brian Avatar asked Oct 06 '09 03:10

Brian


People also ask

How do I check if a UTF-8 file is valid?

You can use GNU iconv: $ iconv -f UTF-8 your_file -o /dev/null; echo $? Or with older versions of iconv, such as on macOS: $ iconv -f UTF-8 your_file > /dev/null; echo $?

Does PHP support UTF-8?

PHP does not natively support UTF-8. This is fairly important to keep in mind when dealing with UTF-8 encoded data in PHP.


2 Answers

UTF-8 can store any Unicode character. If your encoding is anything else at all, including ISO-8859-1 or Windows-1252, UTF-8 can store every character in it. So you don't have to worry about losing any characters when you convert a string from any other encoding to UTF-8.

Further, both ISO-8859-1 and Windows-1252 are single-byte encodings where any byte is valid. It is not technically possible to distinguish between them. I would chose Windows-1252 as your default match for non-UTF-8 sequences, as the only bytes that decode differently are the range 0x80-0x9F. These decode to various characters like smart quotes and the Euro in Windows-1252, whereas in ISO-8859-1 they are invisible control characters which are almost never used. Web browsers may sometimes say they are using ISO-8859-1, but often they will really be using Windows-1252.

would this code ensure that a string is safe to insert into a UTF-8 encoded document

You would certainly want to set the optional ‘strict’ parameter to TRUE for this purpose. But I'm not sure this actually covers all invalid UTF-8 sequences. The function does not claim to check a byte sequence for UTF-8 validity explicitly. There have been known cases where mb_detect_encoding would guess UTF-8 incorrectly before, though I don't know if that can still happen in strict mode.

If you want to be sure, do it yourself using the W3-recommended regex:

if (preg_match('%^(?:       [\x09\x0A\x0D\x20-\x7E]            # ASCII     | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte     | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs     | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte     | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates     | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16 )*$%xs', $string))     return $string; else     return iconv('CP1252', 'UTF-8', $string); 
like image 165
bobince Avatar answered Oct 07 '22 14:10

bobince


With the mbstring library, you have mb_check_encoding().

Example of use:

mb_check_encoding($string, 'UTF-8'); 

With PHP 7.1.9 on a recent Windows 10 system, the regex solution outperforms mb_check_encoding() for any string length (still 20,000 iterations):

  • 10 characters: regex => 4 ms, mb_check_encoding() => 64 ms
  • 10000 chars: regex => 125 ms, mb_check_encoding() => 2.4 s
like image 36
Frosty Z Avatar answered Oct 07 '22 16:10

Frosty Z