MantisBT et PHPMailer: problème avec l’envoi de mails

Je me suis retrouvé récemment face à un problème assez facheux. MantisBT, un logiciel de tracking de bugs, n’envoyait plus de mail. Que ce soit lors d’un ajout de bug ou lors de la procédure de réinitialisation du mot de passe: rien, nada. Pourtant, MantisBT affirmait que le mail était envoyé.

Un petit coup d’oeil dans le fichier core/email_api.php permet de voir que Mantis prend en compte les erreurs de PHPMailer via les try/catch suivants:

1
2
3
4
5
6
7
8
9
10
// Insertion du mail dans la BDD
try
{
  // Envoi du mail
  // Suppression du mail dans la BDD
}
catch ( phpmailerException $e )
{
  // Rien
}

Mais vous l’avez bien compris, MantisBT ne génère pas de message d’erreur (ni de log) si l’envoi du mail n’est pas correctement effectué. Par contre, il laisse le mail dans la BDD pour qu’il puisse être envoyé dès que le système marche à nouveau (voir core/email_queue_api.php).

Pour débugger mon affaire, j’ai donc du insérer une petite ligne de code juste après le catch:

1
echo $e->getMessage();

Et là, bingo: Could not instantiate mail function.

Pourtant, j’avais bien vérifié que la fonction mail fonctionnait correctement avec un petit if (mail("toto@test.com", "sujet", "message")) echo "ok";. Chose que je ne savais pas: si PHP est en safe_mode, la fonction mail() est amputée d’un argument:

Fonctions désactivées par le Safe Mode:
mail(): Si le Safe Mode est actif, le 5ème paramètre est désactivé (note : uniquement affecté depuis PHP 4.2.3)

Super.

Il faut donc vérifier que safe_mode = On dans php.ini pour que tout ça marche. Autre solution si votre hébergeur ne vous permet pas d’accéder au fichier de configuration, ajouter php_flag safe_mode off dans votre .htaccess.

Cela montre encore une fois que PHP est totalement imprévisible. Il y a tellement d’endroits où l’on peut écraser les paramètres d’une configuration que l’on ne peut savoir si telle ou telle fonction va marcher. Mais bon, problème résolu !

Bloquer ou monitorer AJAX à partir d’un script GreaseMonkey (ou Userscript)

Facebook a récemment ajouté une fonctionnalité qui m’est très désagréable: la notification de la lecture des messages à l’expéditeur. Impossible dès lors d’ignorer un message instantané sans que la personne le sache, or c’est un droit immuable qui devrait être inscrit au burin dans la roche des droits de l’homme.

Modification de XMLHttpRequest

Pour empêcher ces notifications, il faut empêcher l’envoi de données vers http(s)://www.facebook.com/ajax/mercury/change_read_status.php à partir d’un script Greasemonkey. Le principe du blocage consiste en la modification de l’objet XMLHttpRequest pour que la méthode open n’autorise pas l’ouverture du socket vers la page en question:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// On sauvegarde la méthode open originale
window.XMLHttpRequest.prototype.trueOpen = window.XMLHttpRequest.prototype.open;

// On modifie la méthode open avec la notre
window.XMLHttpRequest.prototype.open = function() {
   
  // Du code pour bloquer ou logger ce qui se passe
  // On peut notamment regarder ce qu'il y a dans arguments
  // pour bloquer: return;

  // Si on ne souhaite pas bloquer l'ouverture du socket,
  // on fait appel à la méthode originale
  this.trueOpen.apply(this, arguments);
};

Vous pouvez ainsi intercepter chaque appel aux méthodes de XMLHttpRequest, et les bloquer ou les rerouter à votre guise. Pour la liste complète des méthodes en question, je vous invite à consulter sa documentation W3C.

Dans le cas d’un blocage (pas d’appel à trueOpen), voici ce qu’il va se passer du côté de la page monitorée:

1
2
3
4
5
6
// Instanciation normale de notre objet
var xhr = new XMLHttpRequest();
// Rien ne se passe, l'instance de xhr n'est pas modifiée et le socket n'est pas ouvert
xhr.open("get", "page_bloquee.html?param=1", true);
// Le send échoue car le socket n'est pas ouvert
xhr.send()

L’appel à xhr.send ne provoque aucune erreur, du moins sous Chrome.

Le script Greasemonkey

Le script abordé précédemment suppose que vous soyez dans le scope de la page dont vous souhaitez monitorer les appels AJAX. Ce n’est pas le cas pour les scripts GreaseMonkey. Comme expliqué très judicieusement dans ce post sur StackOverflow, Firefox et Chrome exécutent ces scripts dans une sandbox qui ne permet pas d’accéder à l’environnement javascript de la page concernée.

Néanmoins, il existe une méthode assez simple pour sortir de cette sandbox: injecter un élément script dans la page en question qui exécutera le code de notre choix.

1
2
3
4
5
6
7
8
9
function main() {
  // Notre code précédent
}

// On crée un élément <script></script> et on l'injecte dans la page à contrôler
// Dans cet élément, on copie la fonction main afin qu'elle soit exécutée
var script = document.createElement("script");
script.textContent = "(" + main.toString() + ")();";
document.body.appendChild(script);

Le code final

1
2
3
4
5
6
7
8
9
10
11
12
function main() {
  window.XMLHttpRequest.prototype.trueOpen = window.XMLHttpRequest.prototype.open;

  window.XMLHttpRequest.prototype.open = function() {
    // ...
    // this.trueOpen.apply(this, arguments);
  };
}

var script = document.createElement("script");
script.textContent = "(" + main.toString() + ")();";
document.body.appendChild(script);

En ce qui concerne le blocage de la notification facebook, voici facebook-read-status-disabler. Il est également à noter que ce monitoring ne fonctionnera pas avec Internet Explorer car celui-ci utilise un autre objet pour les requêtes AJAX. Un script plus complet est disponible sur github: AJAX Calls Intercepter, avec notamment la prise en compte d’Internet Explorer.