- URL: https://www.laruence.com/en/2010/04/12/1396.html
- Please include attribution when republishing.
Recently my company organized a PHP secure programming training, part of which covered MySQL's "SET NAMES" and mysql_set_charset (mysqli_set_charset):
it said to prefer mysqli_set_charset(mysqli:set_charset) over "SET NAMES". The PHP manual mentions this as well, but it never explains why.
Several friends have asked me this question lately. Why, exactly?
Enough people asked that I figured it was worth writing a post devoted to it.
First of all, most people have no idea what "SET NAMES" actually does.
In my earlier post MySQL character set settings in depth, I covered the three MySQL "environment variables" character_set_client/character_set_connection/character_set_results. Let me recap briefly here:
these three variables tell the MySQL server the client's encoding set, the encoding set to use when data is transmitted to the server, and the encoding set you expect MySQL to return results in.
For instance, using "SET NAMES utf8" tells the server: I'm using utf-8, and I want you to return utf-8 encoded query results to me too.
In general, "SET NAMES" is enough, and it does guarantee correctness. So why does the manual still recommend mysqli_set_charset (PHP>=5.0.5)?
First, let's look at what mysqli_set_charset actually does (note the starred comment; mysql_set_charset is similar):
//php-5.2.11-SRC/ext/mysqli/mysqli_nonapi.c line 342
PHP_FUNCTION(mysqli_set_charset)
{
MY_MYSQL *mysql;
zval *mysql_link;
char *cs_name = NULL;
unsigned int len;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis()
, "Os", &mysql_link, mysqli_link_class_entry, &cs_name, &len) == FAILURE) {
return;
}
MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL*, &mysql_link, "mysqli_link"
, MYSQLI_STATUS_VALID);
if (mysql_set_character_set(mysql->mysql, cs_name)) {
//** call the corresponding libmysql function
RETURN_FALSE;
}
RETURN_TRUE;
}
So what does mysql_set_character_set do?
//mysql-5.1.30-SRC/libmysql/client.c, line 3166:
int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name)
{
struct charset_info_st *cs;
const char *save_csdir= charsets_dir;
if (mysql->options.charset_dir)
charsets_dir= mysql->options.charset_dir;
if (strlen(cs_name) < MY_CS_NAME_SIZE &&
(cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(0))))
{
char buff[MY_CS_NAME_SIZE + 10];
charsets_dir= save_csdir;
/* Skip execution of "SET NAMES" for pre-4.1 servers */
if (mysql_get_server_version(mysql) < 40100)
return 0;
sprintf(buff, "SET NAMES %s", cs_name);
if (!mysql_real_query(mysql, buff, strlen(buff)))
{
mysql->charset= cs;
}
}
// the rest is omitted
We can see that besides doing "SET NAMES", mysqli_set_charset takes one extra step:
sprintf(buff, "SET NAMES %s", cs_name);
if (!mysql_real_query(mysql, buff, strlen(buff)))
{
mysql->charset= cs;
}
And what does the charset member of that core mysql structure do?
That brings us to mysql_real_escape_string(). The difference between it and mysql_escape_string is that it takes the "current" character set into account. So where does that current character set come from?
Right, you guessed it: mysql->charset.
When mysql_real_string decides about characters in a wide character set, it picks a different strategy based on that member variable. For utf-8, for example, it uses libmysql/ctype-utf8.c.
Consider an example. The default mysql connection charset is latin-1 (the classic 5c problem):
<?php
$db = mysql_connect('localhost:3737', 'root' ,'123456');
mysql_select_db("test");
$a = "x91x5c";// gbk encoding of "慭", low byte is 5c, which is "" in ascii
var_dump(addslashes($a));
var_dump(mysql_real_escape_string($a, $db));
mysql_query("set names gbk");
var_dump(mysql_real_escape_string($a, $db));
mysql_set_charset("gbk");
var_dump(mysql_real_escape_string($a, $db));
?>
Because the gbk encoding of "慭" has a low byte of 5c, which is "" in ascii, and because mysql->charset stays at its default value at every moment other than when mysql(i)_set_charset touches it, the result is:
$ php -f 5c.php string(3) "慭\" string(3) "慭\" string(3) "慭\" string(2) "慭"
All clear now, right?
Be First to Comment