Named function parameters in PHP

Officially, PHP doesn’t support named function parameters. But there is one easy way to emulate this feature – so easy that it doesn’t really matter that this feature is missing. It’s actually a pretty old trick inspired from Javascript I’ve been using since forever. I thought everyone knew it, but I’m still surprised by how many people come up with overly complicated solutions. Anyway, imagine you have a method that have a lot of optional parameters. Without named parameters, it goes like this:

// $queue is required, the rest is optionnal
function bind_queue($queue, $durable = false, $auto_delete = true, $name = false, $answer = null)
{ 
...
}

Later in your code, if you want to specify another value for $answer, you have to copy the values of every intermediate parameters in your function call. It’s time consuming, error prone and confusing.

// Good luck remember which parameter is what. 
bind_queue($my_queue, false, true, false, 42);

So instead, it’s much better to use an associative array, like this:

// leave the required parameter part of the function definition, the options goes into an array
function bind_queue($queue, array $opt = array())
{
  // we define the default values, and merge with the user values at the same timle
  $opt = array_merge(array(
    'durable' => false,
    'auto_delete' => true,
    'name' => false,
    'answer' => null
  ), $opt);

  // use $opt['answer'] to access the 'answer' optionnal parameter
}

Later in your code, if you want to override answer, you can only write this:

bind_queue($my_queue, ['answer' => 42]);

Much easier to read, isn’t it?

Fixing WordPress auto-update

Are you tired of WordPress failing to auto-update, and asking for a username for a FTP server even if every possible files and directories have the right permissions (i.e. are writeable by the web server)? Well, me too. Good news is, after countless hours of browsing and trying every possible solution, I stumble upon one that works. Just add the following line to wp-config.php:

define('FS_METHOD', 'direct');

I tried this out of desperation, and what do you know, the auto-update started to work just fine with this line… Don’t get me started on WordPress’ code logic.

Bypassing PHP’s open_basedir with MySQL

PHP feature open_basedir is supposed to limit the files that can be opened by PHP to a specified directory-tree (full doc is here). Functions like fopen or file_get_contents will returns an error if the file is outside the allowed directory. So far, it sounds like a good protection.

However, it is also very famous for being flawed by design and easy to violate (like safe_mode by the way). Well, until now I didn’t realize how easy it is indeed to bypass it. During a security audit, I add the opportunity to study a backdoor left here by some script kiddie (thanks to an outdated version of a web application). Here is just one interesting example that uses MySQL:

  1. create a temporary table
  2. use MySQL’s command LOAD DATA INFILE to read any file and load is content to the table
  3. select the content of the table

In clean PHP, the code would looks like:

$filename = '/etc/passwd';

$pdo = new PDO($dsn, $username, $password);
$pdo->exec('CREATE TEMPORARY TABLE tmp_file ( content LONGBLOB NOT NULL)');
$pdo->exec(sprintf(
    'LOAD DATA INFILE %s INTO TABLE tmp_file',
    $pdo->quote($filename)
));
$content = $pdo->query('SELECT * FROM tmp_file')->fetchAll(PDO::FETCH_COLUMN);

To prevent that exploit in particular, it’s easy: just make sure that the MySQL’s user doesn’t have the FILE privilege. But open_basedir is definitely not safe. As seen in Debian’s php.ini default file:

This is considered a “broken” security measure. Applications relying on this feature will not recieve full support by the security team

Implementation of “tail -f” in PHP

This is a small algorithm to implement a functionality similar to “tail -f” in PHP. The script is able watch a file in real time and do something everytime a new line is added (for example, a log file). It doesn’t implement the “tail” functionnality however (outputing only the end of the file) and instead starts processing the file from the beginning.

$file = @ fopen($filename, 'r');
$pos = 0; 

while (true) {
    fseek($file, $pos);
    while ($line = fgets($file)) {
        // do something with $line
    }
    $pos = ftell($file);
    sleep(1);
}
fclose($file);

Use destructor carefully

A couple of days ago I lost half a day of work on a stupid bug in a PHP CLI script. Basically, the script wouldn’t die. Instead it would hang forever, eating all the CPU time. Even explicitly calling die() (or exit(), since they are synonyms) wouldn’t terminate it. Yep, that’s right, die() didn’t work! Until then, I naively believed that die() was some failsafe language construct that would terminate the script not matter what. But as it turned out, it’s not the case…

According to PHP documentation: The destructor will be called even if script execution is stopped using exit(). So destructor’s code will be executed even after die() is called. What happens if a destructor’s code is faulty and gets stuck in an infinite loop ? The script will never end. In my story, I was using an third-party object-oriented library (for AMQP) that did some funky stuff in some objects destructor, and then waited forever for an event on the network… Needless to say, it’s a BAD idea to write that much application logic in a destructor.

So the moral of the story is: (1) die() can fail and (2) use destructor with caution, only write code that will NEVER fail and that is STRICLY necessary like closing connections, closing file handlers and such. Same goes for shutdown functions by the way.

A quick example for the sake of demonstration:

class Evil
{
    public function __destruct()
    {
        while (true);
    }
}

$foo = new Evil();
die();

Trees in SQL : an approach based on materialized paths and normalization for MySQL

I’m working since a few months on how storing trees in MySQL. It started like “hey, let’s make a database of every genre of heavy metal music!”. So I began with the easy way : a tree structure in a table genre with id and parent_id columns. And then I came to the point where I wanted to display the entire tree. This is just impossible with a reasonable number of queries, as there is no recursive syntax in standard SQL nor in MySQL (Oracle has the CONNECT BY extension). So I started researching the web.

There are basically three ways to store a tree in a relationnal databases : adjacency list, nested set and materialized path, plus a few more like nested intervals. Let’s put it clear : they all suck.

  • Adjacency list model is the one I used the first time (with parent_id column). It’s easy to create but impossible to query deeply without recursivity.
  • Nested set model is quite easy to query, but is indeed pretty fucked up (tree is limited in size, very hard to read without maths, almost all the lines of the table need to be updated each time a node is added, moved or deleted!). Nested intervals model tries to remove the size limitation of nested sets model, but involves way to much maths.
  • Materialized path is great and easy to understand, but very slow because it involves heavy use of the “LIKE” operator.

More on that here, here and here.

Then I found two very interesting articles. One is a reply on MySQL Forums about materialized path performance problem, recommending an approach based on normalized schema to store paths. The other is an implementation using PostgreSQL by depesz that also use a normalized schema. The point is to create a table dedicated to store all the paths from every nodes to every nodes. This looks to me like a very good approach, as it’s easy to understand, easy to maintain, easy to query and performance-ok (with appropriate indexes). Unfortunalty, the implementation I found relies to much on PostgreSQL and doesn’t work out-of-the-box with MySQL (because of the use of triggers and of some queries that needs to be rewriten). So I reworked it to work with MySQL 5, and changed a few things. I strongly recommend that you read depesz’s post for a complete understanding of the approach before you continue.

Continue reading

Quel est le meilleur type pour stocker un mot de passe dans une table MySQL ?

D’abord, il va de soi (mais ça va mieux en le disant) que les mots de passe d’une application web ne doivent pas être stockés en clair, mais sous forme d’un hash. Lors de la procédure d’authentification, il suffit de calculer le hash du mot de passe saisi par l’utilisateur, et de le comparer avec celui stocké en base.

L’algorithme le plus répandu pour calculer un hash est MD5. Néanmoins, depuis 2004 plusieurs vulnérabilités ont été découvertes, aussi il est préférable d’utiliser SHA1. Quel est meilleur type pour stocker ces hashs dans une table MySQL ?

Un hash SHA1 mesure 160 bits ou 40 caractères hexadécimaux. Un hash MD5 mesure 128 bits soit 32 caractères hexadécimaux.

Stocker sous forme de chaine

La longueur du hash est fixe : il est donc préférable d’utiliser le type CHAR (CHAR(40) pour du SHA1, CHAR(32) pour du MD5). En effet, le type VARCHAR prendrait plus d’espace car il utilise un octet supplémentaire pour stocker la longueur de la chaine.

Ensuite, il faut faire attention à l’encodage. En effet, si la table est en UTF-8, les caractères seront probablement stockés sur 3 octets (ça peut varier selon le moteur de stockage). Il vaut mieux spécifier l’encodage de cette colonne à ASCII, grâce à CHARACTER SET ascii.

Enfin, la colonne est évidemment NOT NULL, pour économiser un bit (la valeur NULL est stockée dans un bit supplémentaire), à moins que l’application en ait besoin (ça existe des utilisateurs sans mot de passe ?).

Exemple :

mysql> create table t (
    -> password char(40) character set ascii not null
    -> ) engine=MyISAM character set utf8;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t values(SHA1('monSuperMotDePasse'));
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+------------------------------------------+
| password                                 |
+------------------------------------------+
| 35ea5a462298bd78c1648fbea2195988c46d103f |
+------------------------------------------+
1 row in set (0.00 sec)

L’espace utilisé est donc 40 octets pour un hash SHA1, car le type CHAR avec le jeu de caractère ASCII stocke chaque caractère sur un octet (8 bits).

Stocker sous forme binaire

Vous l’aurez surement remarqué, 40 octets ça fait 320 bits, c’est à dire le double de la taille du hash SHA1 brut (idem pour MD5 : 32×8 = 256). Pour faire simple, c’est le prix à payer pour avoir un mot de passe lisible et “imprimable” (c’est à dire que vous pouvez copier/coller sans problème) en base. L’autre solution consiste à stocker le hash brut directement sous forme binaire.

Le type a utilisé n’est donc plus CHAR, mais BINARY qui stocke des chaines binaires. Il faut lui préciser la longueur en octets, donc BINARY(20) pour du SHA1 (160/8 = 20), et BINARY(16) pour du MD5 (128/8 = 16). Il n’y a plus à se soucier du jeu de caractères.

Pour convertir une chaine de caractère en chaine binaire, il faut utiliser UNHEX. Pour l’opération inverse, il faut utiliser HEX. Note pour les utilisateurs de PHP : la fonction hash accepte un troisième paramètre optionnel $raw_output qui, s’il vaut true, permet de récupérer le résultat binaire directement (voir http://fr.php.net/manual/fr/function.hash.php).

Exemple :

mysql> create table t2 (
    -> password binary(20) not null
    -> ) engine=MyIsam character set utf8;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t2 values(UNHEX(SHA1('monSuperMotDePasse')));
Query OK, 1 row affected (0.00 sec)

mysql> select HEX(password) from t2;
+------------------------------------------+
| HEX(password)                            |
+------------------------------------------+
| 35EA5A462298BD78C1648FBEA2195988C46D103F |
+------------------------------------------+
1 row in set (0.00 sec)

L’espace utilisé est donc 20 octets pour hash SHA1, soit 2 fois moins que la version en chaine de caractères. L’inconvénient est qu’il faut penser à utiliser UNHEX ou HEX dès qu’on veut le hash sous une forme imprimable. C’est rarement le cas, et comme les opérations de comparaisons (utilisées pour l’authentification de l’utilisateur) peuvent s’effectuer avec les version binaires, ça ne me dérange pas.

En savoir plus

MySQL INT(11) a la même taille que INT(3)

Vous ne le saviez peut-être pas, mais contrairement aux types de chaine de caractères (varchar, char, …), le chiffre entre parenthèses pour un type numérique n’a aucune influence sur la taille maximale du type. Autrement dit, on peut stocker le même nombre dans un int(11) que dans un int(3), un int ou même un int(42). Ce qui influe la taille maximale, c’est le type en lui même : tinyint, smallint, mediumint, int et bigint.

Tableau de correspondance des tailles (source : http://dev.mysql.com/doc/refman/5.1/en/numeric-types.html)

Type Bytes Minimum Value (Signed/Unsigned) Maximum Value (Signed/Unsigned)
TINYINT 1 -128 127
0 255
SMALLINT 2 -32768 32767
0 65535
MEDIUMINT 3 -8388608 8388607
0 16777215
INT 4 -2147483648 2147483647
0 4294967295
BIGINT 8 -9223372036854775808 9223372036854775807
0 18446744073709551615

Alors quel est l’intérêt de la préciser une taille ? Il n’y en a qu’un seul, c’est lorsque le type est utilisé avec l’option zerofill, option assez peu connue qui permet de compléter avec le champ avec des zéros pour atteindre la taille spécifiée.

Exemple :

mysql> create table test_int (
    -> normal_int int unsigned not null,
    -> zerofilled_int int(6) unsigned zerofill not null
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test_int values (123456789, 123456789), (42,42);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> select * from test_int;
+------------+----------------+
| normal_int | zerofilled_int |
+------------+----------------+
|  123456789 |      123456789 |
|         42 |         000042 |
+------------+----------------+
2 rows in set (0.00 sec)

Comme vous pouvez le constater dans l’exemple précédent, il n’y aucune différence sur la première ligne (un nombre composé de 9 chiffres tient sans problème dans un int(6)), par contre dans la seconde, le champ avec l’option zerofill est complété par des zéros pour atteindre 6 chiffres. Un exemple concret d’utilisation : stocker des numéros de série normalisé (genre des numéros de bon de commande du type “BDC-000042”), car ça évite de faire un padding.

Bref, tout ça pour dire : ça ne sert à rien d’essayer d’optimiser ses tables MySQL avec ça, ce n’est pas en mettant int(4) qu’on va gagner de la place, mais plutôt en utilisant le type adapté (en l’occurence smallint).

N’utilisez pas isset pour vérifier l’existence d’une clef !

La bonne blague PHP du jour concerne la fonction isset qui permet, comme son nom l’indique, de tester si une variable est affectée. Si vous êtes comme moi, vous avez pris l’habitude de l’utiliser pour vérifier qu’une certaine clef existe dans un tableau avant d’y accéder, afin d’éviter le traditionnel message “Notice: Undefined index”.

Seulement voila, isset retourne false si la variable contient null… Exemple :

$array = array(
    'toto' => null
);
var_dump(isset($array['toto'])); // boolean false

Résultat : on n’obtient pas du tout le résultat attendu, puisque selon PHP la clef n’est pas définie alors que selon le programmeur (moi, en l’occurence) l’index toto existe bien ! D’après la documentation, il faut donc utiliser array_key_exists pour tester qu’une clef existe…

Bon, que PHP considère qu’une variable définie mais de valeur null et qu’une variable non-définie soit la même chose, c’est déjà un peu curieux, mais là où ça devient complètement ridicule c’est que isset renverra bien true pour une chaine vide (ainsi que pour false), alors que c’est équivalent à null avec l’opérateur de comparaison non-typé == ! Alors pourquoi isset n’a pas le même comportement pour toutes ces valeurs ?

$array = array(
    'toto' => null,
    'tata' => ''
);

var_dump(isset($array['toto'])); // boolean false
var_dump(isset($array['tata'])); // boolean true
var_dump($array['toto'] == $array['tata']); // boolean true

Une syntaxe SQL originale

La question à 2 centimes d’euros du jour : qu’est-ce qu’on obtient comme résultat avec une requête SQL de ce genre de celle ci-dessous (avec un MySQL) ?

select * from ma_table where id = 42 = 0;

La réponse est : toute la table sauf la ligne dont l’id vaut 42 ! En fait, en raison de la précédence des opérateurs, cette requête est équivalente à :

select * from ma_table where (id = 42) = 0

Comme l’opérateur de comparaison = retourne 0 (FALSE) ou 1 (TRUE) (ou NULL si un des 2 arguments vaut NULL), l’expression id = 42 = 0 est parfaitement valide et est équivalente à id != 42.

Mais à quoi ça sert me direz-vous ? A faire des injections SQL dans un site PHP qui ne filtre pas correctement ses variables… A bon entendeur !