Ce matin, en effectuant une recherche sur Google, j'ai été surpris de voir que tous les liens qu'il me proposait contenait la mention "This site may harm your computer" (Ce site risque d'endommager votre ordinateur).
Selon le screenshot ci-dessous, l'alerte s'applique même à Google ! Hum... Don't be evil.
Même les liens sponsorisés semblaient agoniser :
Mise à jour
Qu'il s'agisse d'un bogue ou d'une maintenance, ça semble avoir duré au moins 30 minutes (entre 10h et 10h30 ce matin). J'ai voulu soumettre cette curiosité à Digg mais quelques internautes s'en étaient déjà occupés :-)
Voici un petit truc qui date un peu mais qui est encore fonctionnel. Depuis l'arrivée de la version 7 du Flash Player (fin de 2003), la sécurité a été améliorée pour faire en sorte que si un élément Flash tente d'accéder à des données se trouvant sur un autre domaine, il affiche une alerte si l'accès n'est pas autorisé par le serveur.
Dans certains cas, c'est pourtant ce qu'on souhaiterait faire. Pour corriger le problème, le serveur doit posséder un fichier nommé crossdomain.xml. Lorsque le document Flash tente d'accéder à une ressource externe, il vérifie si le serveur distant possède ce fichier à la racine (http://www.serveur-distant.com/crossdomain.xml). En cas d'absence, l'alerte apparaît. S'il s'y trouve, il vérifie si le domaine référant est autorisé à accéder à la ressource.
Par exemple, si le fichier crossdomain.xml suivant est déposé à la racine du site www.serveur-distant.com, ça veut dire que seul le domaine www de site-referant-1.com et tous les sous-domaines de site-referant-2.com sont autorisés.
<?xml version="1.0"?>Évidemment, le serveur qui offre le service est le seul en mesure de définir ces droits. Vous pourrez définir votre propre crossdomain.xml si vous voulez que votre serveur soit accessible par les documents Flash hébergés sur des sites externes.
<!DOCTYPE cross-domain-policy
SYSTEM "www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="www.site-referant-1.com" />
<allow-access-from domain="*.site-referant-2.com" />
</cross-domain-policy>
Quelques conseils :
- On utilise le wildcard allow-access-from domain="*" pour indiquer que tous les domaines peuvent y accéder.
- Si on veut forcer un domaine à accéder à la ressource en HTTPS, on peut ajouter secure="false" dans la balise allow-access-from.
Souvent quand on développe un site ou une application web, on a tendance à regrouper certaines variables de configuration à l'intérieur d'un fichier php qu'on inclut de façon globale au projet. On en arrive facilement à quelque chose comme ceci :
$mail_default_address = 'name@domain.com';Il n'est pas rare non plus d'avoir plus d'une configuration, par exemple en utiliser une en cours de développement et une différente en production. Lorsque ça arrive, on doit redéfinir nos variables, ne serait-ce que pour le temps d'implémenter une nouvelle fonctionnalité ou de vouloir reproduire un bogue dans le but de le corriger.
$mail_smtp = 'mail.domain.com';
$db_adapter = 'pdo_pgsql';
$db_host = 'domain.com';
$db_name = 'my_database_name';
$db_username = 'my_username';
$db_password = 'my_password';
// etc...
Le problème avec les variables ci-dessus est qu'il n'y a pas de lien logique qui les regroupe, ce qui peut causer un risque d'écrasement de la variable si une du même nom est réaffectée ailleurs dans le programme. Plusieurs programmeurs ont adapté leur configuration en regroupant les variables dans un tableau associatif contenu à l'intérieur d'une seule variable PHP. Pour reprendre l'exemple précédent, la même configuration aurait plutôt l'air de ceci :
$config = array(Encore là, c'est mieux mais il serait préférable que la configuration soit indépendante de l'application. Supposez que vous devez installer votre application chez un client et que vous voulez protéger votre code à l'aide de Zend Guard (ou d'un outil similaire). L'ensemble du code PHP sera transformé ("encodé") en code intermédiaire, y compris la configuration puisqu'elle fait partie du code PHP. D'où la nécessité de l'extraire.
'mail' => array(
'default_email' => 'name@domain.com',
'smtp' => 'mail.domain.com'
),
'database' => array(
'adapter' => 'pdo_pgsql',
'params' => array(
'host' => 'localhost',
'username' => 'my_username',
'password' => 'my_password',
'dbname' => 'my_database_name'
)
)
);
À l'intérieur du Zend Framework, il existe une classe qui se nomme Zend_Config qui permet de définir notre configuration dans un fichier .ini ou XML et de la charger par programmation. Le résultat sera un array associatif comme celui présenté plus haut, à l'exception que la configuration sera séparée du code source.
Par exemple, si on veut redéfinir notre configuration dans un fichier .ini :
; Configuration pour le site en productionUne fois le fichier .ini créé, on pourra le charger par PHP :
[production]
database.adapter = pdo_pgsql
database.params.host = domain.com
database.params.username = my_username
database.params.password = my_password
database.params.dbname = my_database_name
mail.default_email = name@domain.com
mail.smtp = mail.domain.com
; Redéfinition des valeurs pour le site en développement
; (autrement, on conserve celles de production)
[dev : production]
database.params.host = dev.localhost
database.params.username = my_dev_username
database.params.password = my_dev_password
database.params.dbname = my_database_name_for_tests
mail.default_email = programmer@dev-company.com
mail.smtp = mail.dev-company.com
require_once('Zend/Config.php');Encore une fois, le code est mieux structuré, plus facile à lire et à maintenir.
require_once('Zend/Config/Ini.php');
// on choisit la section "production" de la configuration
$config = new Zend_Config_Ini('/chemin/vers/config.ini', 'production');
// accès à une valeur
echo $config->database->params->host;
Quelques conseils :
- À la toute fin, n'oubliez pas de protéger l'accès à votre fichier .ini ou XML pour ne pas qu'il soit lisible en ligne!
- Dans un fichier .ini, si une valeur contient des caractères autres que alpha-numériques, placez la entre des guillemets ("").
- Utilisez Zend_Config_Writer pour exporter et enregistrer des configurations.
- Le format présenté dans le fichier .ini est recommandé par le Zend Framework puisqu'il permet d'instancier un objet Zend_Db automatiquement avec les clés définies.
Ce matin, ma conjointe m'a envoyé un lien vidéo assez surprenant. Un homme utilise un engin propulsé par l'eau pour léviter dans les airs, probablement inspiré du jeu vidéo Super Mario Sunshine (Nintendo GameCube).
V0ici le Amazing New Water-Powered Jet Pack, tiré du site Break.com.
Dans l'entreprise pour laquelle je travaille, une des étapes de notre processus d'embauche pour les nouveaux programmeurs est de demander un échantillon de code source, question d'avoir un aperçu de son expérience, de la qualité du travail et de sa façon de coder. Le code parle de lui-même avant d'inviter le candidat en entrevue.
Aujourd'hui, un des postulants nous a soumis du code PHP qui, malheureusement pour lui, lui a fait perdre ses chances de sélection. Un des points (car il y en avait plusieurs) était que ce qu'il avait choisit de mieux à nous présenter était une page PHP4 très simple qui faisait référence aux anciennes variables $HTTP_POST_VARS et $HTTP_GET_VARS. L'art de faire mauvaise impression. Disons que nous avons tous été surpris. À nos yeux, ce genre d'indice nous démontrait que le candidat n'avait pas su se tenir à jour au fil du temps.
Depuis PHP5, ces variables prédéfinies sont dépréciées au profit de $_GET et $_POST. Malgré tout, il existe encore des vieilles librairies PHP4 qui doivent rouler avec des scripts plus récents. Si c'est le cas, il est possible d'inscrire la ligne ci-dessous dans le fichier .htaccess pour assurer la compatibilité :
php_flag register_long_arrays on
Initialiser cette variable permettra d'écraser la valeur par défaut de register_long_arrays (off) spécifiée dans php.ini. Ainsi, les variables courtes ($_GET) et longues ($HTTP_GET_VARS) coexisteront. Comme PHP est en phase de transition, cette directive sera dépréciée dès PHP 5.3 et disparaîtra complètement à partir de la version 6.
On pourrait certainement argumenter que le code fonctionne... Mais engageriez-vous un programmeur qui ne semble avoir rien acquis au court des dernières années, surtout dans le Web où tout évolue si vite ? Si c'est le cas, dites-le moi, je serai heureux de vous faire suivre son curriculum vitae.
Depuis la version 4.2 de Google Earth (gratuit), la combinaison des touches CTRL+ALT+A (Pomme+Option+A sur Mac) vous donnera accès à un simulateur de vol complet!
Vous aurez le choix entre un jet F16 Viper ou un avion à hélices SR22 pour planer à l'endroit que vous êtes en train d'explorer ou encore, tentez votre chance et décollez à partir d'un des 27 aéroports disponibles.
Dans le simulateur, les bâtiments ne sont pas en trois dimensions mais vous pourrez quand même apprécier les élévations et espérer éviter les montagnes que vous croiserez sur votre chemin pour éviter un crash. Et bonne chance pour effectuer un attérissage, je n'ai toujours pas réussi.
Amusez-vous bien et n'oubliez pas de jeter un oeil à la liste complète des commandes de Google Earth Flight Simulator.
PHP possède un moyen assez simple pour créer des fonctions anonymes (une fonction qui ne possède pas de nom). Pour y arriver, on doit appeler la fonction create_function et fournir deux arguments de type "string", le premier correspondant à la liste des arguments de la fonction (ses paramètres), le deuxième étant le code PHP de la fonction qui sera évalué à l'interne pour être interprété en code PHP :
$args = '$amount, $pcnt_tps, $pcnt_tvq';Ça fonctionne, mais avouez que ça ne produit pas un code très élégant et que c'est plus difficile à lire, surtout quand on est habitué à écrire des fonctions anonymes selon le modèle de JavaScript. En fouillant un peu, je suis tombé sur un article qui décrivait comment effectuer une patch à la source C de l'engin PHP pour pouvoir l'écrire avec une meilleure syntaxe. Et la bonne nouvelle est qu'à partir de PHP 5.3 (encore en release candidate alpha), on pourra vraiment faire comme ceci :
$code = '
$tps = $amount * ($pcnt_tps / 100);
$tvq = ($amount + $tps) * ($pcnt_tvq / 100);
return $amount + $tps + $tvq;';
// création de la fonction anonyme
$addQuebecTaxes = create_function($args, $code);
// appel de la fonction anonyme contenue dans la variable
// 100$ + les taxes TVQ et TPS = 112.88$
echo $addQuebecTaxes(100, 5, 7.5);
$addQuebecTaxes = function ($amount, $pcnt_tps, $pcnt_tvq) {Pour les lecteurs qui viennent de l'extérieur du Québec, l'exemple du code ci-dessus calcule les taxes de la belle province sur un montant donné. Ce n'est pas une erreur si la taxe de vente du Québec est calculée par dessus la taxe sur les produits et services (TPS). Nous sommes malheureusement un des rares endroits dans le monde où la taxe est taxable!
$tps = $amount * ($pcnt_tps / 100);
$tvq = ($amount + $tps) * ($pcnt_tvq / 100);
return $amount + $tps + $tvq;
};
Dans la catégorie "fait vécu"...
Faut-il en rire ou en pleurer quand un client croit que pour convertir un fichier Microsoft Word en PDF, il suffit de renommer l'extension ?
La semaine dernière, j'ai donné un coup de main aux administrateurs réseau car ils avaient besoin d'un script Perl pour effectuer des vérifications de "Real-time Blackhole List" afin de s'assurer que nos serveurs SMTP n'étaient pas sur une liste noire.
Ça faisait un bout de temps que je n'avais pas fait de Perl et je dois dire que j'étais heureux de me replonger dans le premier langage avec lequel j'avais programmé sur le web.
Pour me connecter sur le serveur Linux, j'ai utilisé puTTY et je me suis identifié avec le compte root. Avant de lancer l'éditeur Pico pour écrire le script, je devais connaître l'emplacement de l'interpréteur Perl.
Sous Linux, il existe une commande whereis qui sert à localiser les fichiers binaires, les sources et les manuels qui correspondent au nom entré. Dans mon cas, je cherchais à savoir où se trouvait l'interpréteur Perl, donc le binaire (-b) :
whereis -b perl
En retour, j'ai pu voir qu'il se trouvait dans /usr/bin/perl. C'est ce chemin qui sera indiqué à la première ligne du script.
#!/usr/bin/perl
Ensuite, juste pour le plaisir, j'ai exécuté la commande perl -v pour connaître la version que j'utilisais : 5.10. Pas si mal. Ma dernière expérience avec Perl doit remonter quelque part entre la 5.2 et la 5.6 avec une tentative de comprendre les arcanes du Perl orienté objet. Passons...
Pour le reste, je n'ai eu qu'à utiliser l'extension Mail::RBL disponible sur CPAN (Comprehensive Perl Archive Network) pour écrire le script de vérification.
À noter que whereis permet de trouver n'importe quoi, à partir de Firefox, en passant par le fichier hosts, jusqu'à l'interpréteur Python.
Depuis quelques années, on entend parler de plus en plus de web 2.0, de AJAX et de JSON pour créer des applications web plus riches et interactives. Le principe est qu'on initie une requête AJAX (Asynchronous JavaScript and XML) et on reçoit la réponse en XML ou encore en JSON (JavaScript Object Notation) à partir de laquelle on peut rafraîchir une portion du contenu de la page.
À la base, un objet JSON peut ressembler à quelque chose comme ceci:
{ "site" : "http://code18.blogspot.com" }
Lorsque les données proviennent d'une base de données, j'ai remarqué que plusieurs programmeurs construisaient l'objet JSON à partir du recordset en effectuant une boucle sur les enregistrements, en utilisant des conditions pour savoir à quel moment utiliser le bris de séquence pour ajouter une virgule ou une accolade.
Or, il existe un petit truc qui peut nous simplifier la vie : transformer le résultat d'une requête SQL directement en JSON.
Pour ce faire, vous aurez besoin au minimum de PHP 5.2 et de l'extension l'extension JSON de PECL (à faire installer par votre administrateur réseau préféré si ce n'est pas déjà fait). Pour savoir si elle est installée, faites afficher phpinfo() et repérez l'entête JSON (json support doit être à "enabled"). Et attention, les valeurs doivent obligatoirement être encodées en UTF-8, autrement vous aurez des problèmes avec les accents.
Ce snippet de code PHP récupère de la base de données la liste des villes :
try {$json_output contiendra alors ceci (un array d'objets contenant le nom de la ville et son ID) :
$dbh = new PDO('pgsql:host=server;dbname=utf8_database','usr','pwd');
$rs = $dbh->query('SELECT * FROM cities');
$obj = $rs->fetchAll();
// conversion en json
$json_output = json_encode($obj);
}
catch(PDOException $e) {
echo $e->getMessage();
}
[Ensuite, on utilise la variable PHP $json_output pour remplir une liste déroulante :
{"city":"Montr\u00e9al","0":"Montr\u00e9al","city_id":1},
{"city":"Toronto","0":"Toronto","city_id":2},
{"city":"Vancouver","0":"Vancouver","city_id":3}
]
function onLoad() {Si vous avez des accents, vous remarquerez dans le code souce que le texte, par exemple "Montréal", sera converti en UTF-8 en "Montr\u00e9al". C'est normal et JavaScript sera capable d'afficher correctement le texte dans la liste déroulante (inspectez-le avec Firebug et vous verrez que tout s'est déroulé normalement).
var obj = <?php echo $json_output; ?>
for (i in obj) {
var new_option = new Option( obj[i].city, obj[i].city_id );
document.getElementById('mySelectBox').options.add(new_option);
}
}
En conclusion : un code beaucoup plus facile à lire, sans se casser la tête avec les détails. Pour en connaître davantage, voici une bonne référence JSON.
PostgreSQL possède un type de donnée particulier qui se nomme SERIAL. En fait, il ne s'agit pas d'un vrai type, c'est plutôt un raccourci qu'on peut utiliser lors de la création d'une table pour définir une colonne de type auto-incrémentée.
Pour être plus précis, le DDL (Data Definition Language) à écrire lors de la définition d'une table (quand on n'utilise pas d'interface graphique d'édition) :
CREATE TABLE nom_de_la_table(est l'équivalent de :
nom_de_colonne SERIAL
);
CREATE SEQUENCE nom_de_la_sequence;Une chose importante à retenir est que bien que serial soit souvent utilisé pour la colonne représentant la clé primaire, celle-ci n'est pas définie automatiquement. Il faut alors déclarer explicitement la contrainte :
CREATE TABLE nom_de_la_table (
nom_de_colonne integer DEFAULT nextval('nom_de_sequence') NOT NULL
);
CONSTRAINT nom_de_la_contrainte PRIMARY KEY (nom_de_colonne)
Aujourd'hui, j'ai reçu un fichier de code source de plusieurs centaines de lignes que je devais utiliser mais le programmeur qui me l'a fait parvenir a eu la brillante idée de "formater" le code en y incluant les numéros de lignes.
Pour mieux comprendre, imaginez un fichier source comme celui ci-dessous (ne faites pas attention, j'ai pratiquement fait du pseudo-code pour illustrer le problème). Comme chaque début de ligne incluait son numéro, le code ne pouvait pas être exécuté, à moins de les retirer préalablement.
1 <?phpPour y arriver, j'ai utilisé un moyen simple de faire de la substitution à l'aide de l'outil SED. Sed (Stream EDitor) est un utilitaire Unix qui permet d'analyser et de traiter le contenu de fichiers textes (contrairement à GREP qui ne fait que rechercher à l'intérieur). Il lit le fichier séquentiellement et applique le traitement ligne par ligne selon la commande passée en paramètre dans l'invite de commande.
2 require_once('classes/DBI.php');
3
4 $dbi = new DBI('pgsql');
5 $dbi->connect('server', 'database', 'usr', 'pwd');
6
7 $dbi->execute('SELECT * FROM table ...');
8
9 if ($a == $b) {
10 /* indentation */
11 echo 'OK';
12 }
13
14 ?>
La syntaxe de substitution est la suivante (comme j'utilise UnxUtils sur Windows, je dois mettre des double-quotes) :
sed "s/chercher/remplacer/" inputFile > outputFile
Dans cette requête, on appele l'outil sed en lui spécifiant une commande de substitution (l'option s), autrement dit, le bon vieux "find and replace". Ensuite, on spécifie ce que l'on veut trouver et par quoi on veut le remplacer en les séparant par /.
On spécifiera ce qu'on veut trouver en utilisant une expression régulière. Si on remarque bien, chaque ligne commence par son numéro, suivi de trois ou quatre espaces avant que le code source PHP débute. Notre objectif est de retirer ces caractères en début de ligne et de conserver le reste.
La commande que j'ai utilisé :
sed "s/^[0-9]*[ ]\{3,4\}//" source.txt > resultat.php
Dans la partie "chercher" de la substitution, j'ai défini l'expression régulière :
^[0-9]*[ ]\{3,4\}
Si vous n'êtes pas familier avec les regexp, voici ce que chaque partie représente :
^ au début de la ligneQui sera remplacée dans la deuxième partie de la commande par //, ce qui veut dire par rien (donc on coupe ce qu'on a trouvé et on garde le reste).
[0-9] les caractères de 0 à 9
* répétés plusieurs fois
[ ] suivi du caractère "espace" (il y a un espace à l'intérieur)
{3,4} répétés 3 ou 4 fois
On spécifie ensuite le nom du fichier source et on envoie le résultat en sortie vers un autre fichier à l'aide de l'opérateur > (il sera créé s'il n'existe pas). Notez que > écrase le contenu du fichier tandis que >> ajoute le contenu à sa suite.
Nous voilà donc rendu avec un fichier source PHP qui peut maintenant être exécuté proprement. Mais la prochaine fois, je lui dirai que mon éditeur numérote les lignes automatiquement, alors pas la peine de se donner tant de mal... :-)
Saviez-vous que le siège social de l'entreprise Apple était situé au 1, Infinite Loop, Cupertino en Californie ? En informatique, une "boucle infinie" signifie une suite d'instructions s'exécutant à l'infini, souvent en raison d'une condition mal définie qui empêche l'interruption du processus. Autrement dit, ça tourne en rond à l'infini.
À ce propos, sur les appareils iPhone et iPod Touch, l'icône représentant l'application Google Maps illustre la localisation de cette adresse.
Pour faire suite à ce que j'écrivais la semaine dernière concernant le YUI Compressor Online (outil basé web), j'ai réfléchis à une alternative pour accélérer le processus de compression, cette fois directement à partir mon poste de travail.
À mon boulot, comme on effectue tout le développement à partir d'une plateforme Windows (XP), j'ai orienté mes recherches pour pouvoir en bénéficier dans mon travail quotidien. Mon objectif : pouvoir sélectionner un fichier JavaScript et le compresser directement à partir d'une nouvelle option que j'aurai ajouté au menu contextuel.
Voici comment j'ai procédé :
- Télécharger le YUI Compressor et trouver le fichier nommé yuicompressor-version.jar (présent dans le répertoire build). Un .jar est un JavaArchive qui peut être exécuté par l'interpréteur Java.
- Placer ce fichier sur son poste ou sur le réseau, et noter le chemin d'accès.
- Écrire le fichier batch qui permettra d'appeler le compresseur YUI. Dans un fichier texte, entrer le code suivant, configurer au besoin, enregistrer le fichier et renommer l'extension .bat
REM Auteur : http://code18.blogspot.com
SET path=C:\yui-compressor\yuicompressor-2.4\build\yuicompressor-2.4.jar
SET filename=%1
SET filename=%filename:.js=%
java -jar %path% %1 -o %filename%-min.js
Pour les fichiers .bat et .jar, je recommanderais de les placer à un endroit accessible par tous, par exemple un répertoire partagé sur le réseau.
À ce moment-ci, le script est fonctionnel mais ne fait pas encore parti du menu contextuel. Pour l'ajouter, il faudrait jouer dans la base de registre (regedit) et ajouter différentes clés aux bons endroits. Heureusement, je me suis cassé la tête pour vous.
Créez un fichier texte, copiez le code ci-dessous, adaptez-le à vos besoins (chemin menant vers le .jar) et enregistrez le fichier en renommant l'extension .reg.
Contenu du fichier yuicompressor.reg
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\JSFile\Shell\YUI_Compressor]
@="YUI Compressor"
[HKEY_CLASSES_ROOT\JSFile\Shell\YUI_Compressor\command]
@="C:\\yui-compressor\\yuicompressor-2.4\\build\\yuicompressor.bat \"%1\""
Et voilà, il ne reste plus qu'à double cliquer sur le fichier yuicompressor.reg pour finaliser la configuration. Le script effectuera deux étapes. D'abord, il définira le nom qui apparaîtra dans le menu contextuel, ensuite il indiquera la commande à appeler. Comme l'indique la clé JSFile, le tout sera disponible que pour les fichiers JavaScript.
Lorsque vous voudrez compresser un fichier JavaScript, vous n'aurez qu'à le sélectionner, faire un click droit avec la souris et choisir dans le menu contextuel "YUI Compressor". Un nouveau fichier sera créé au même emplacement (l'original sera conservé et le nouveau fichier sera renommé ainsi : prototype.js deviendra prototype-min.js).
Ensuite, n'hésitez pas à faire circuler le fichier yuicompressor.reg pour en faire bénéficier vos collègues.
Est-ce que vous connaissez le désormais classique Principe de Peter ? Il s'agit d'un livre écrit en 1968 dont la devise est "Tout employé tend à s'élever à son niveau d'incompétence". Si vous n'avez jamais lu ce livre, vous devez vous faire un devoir d'aller à la librairie la plus proche et d'y investir un petit 7$.
L'idée maîtresse du livre est que lorsqu'un employé fait du bon travail, on a tendance à le récompenser en lui accordant une promotion. Il s'élève donc dans la hiérarchie de l'entreprise et obtient plus de responsabilités. Jusqu'au moment où il atteint un poste où il perd le contrôle et qu'il devient incompétent... mais il reste en poste plutôt que de retourner là où il était productif, au grand dam des employés qui l'entourent.
Ce livre, sérieux et drôle à la fois, expose différentes manifestations de l'incompétence en milieu de travail. Il explique aussi comment survivre entouré d'incompétents ou encore comment cacher sa propre incompétence.
Alors faites gaffe la prochaine fois qu'on vous offrira une promotion, vous pourriez tomber dans le piège du poste placard, c'est à dire un poste qui vous est offert pour vous mettre hors d'état de nuire quand on ne peut pas se débarasser de vous. À ce propos, on m'a déjà raconté que dans un bureau du centre-ville de Montréal, un employé avait été "promu" au poste de "Directeur des transports verticaux". Il a eu droit à son propre bureau ainsi qu'à un téléphone pour pouvoir, au besoin, appeler le réparateur de l'ascenseur. L'employé n'y a vu que du feu.
Si le livre vous a plu, vous adorerez aussi la série Dilbert de Scott Adams, en particulier le livre le Principe de Dilbert, qui constitue une version inspirée et aggravée du Principe de Peter. Et surtout, n'oubliez pas de vous abonner à la liste de diffusion pour recevoir un comic strip par jour dans votre boîte de courriel. Ça commence drôlement mieux une journée de travail.
Nombreux sont les sites web qui fonctionnent avec une base de données en arrière-plan. Souvent, un outil CMS (Content Management System) est disponible pour gérer les données à partir de formulaires, sans que l'utilisateur ait besoin de posséder des connaissances en programmation ou en HTML.
Or, pour se connecter à l'outil CMS, on doit entrer un nom d'utilisateur et un mot de passe, qui eux, sont généralement stockés dans une table de la base de données. L'interface d'identification permet donc de fournir les paramètres nécessaires pour compléter la requête SQL qui procédera à l'authentification.
Par exemple, si les paramètres fournis sont les suivants :
username = 'code18'
password = 'cornichon'
La requête exécutée devrait ressembler à quelque chose comme ce qui suit puisque très souvent, elle est construite en concaténant les valeurs reçues directement dans la requête SQL :
SELECT * FROM users
WHERE username = 'code18' AND password = 'cornichon'
Et le problème est là. S'il n'y a pas de validation des paramètres, il y a un risque qu'on puisse entrer du code qui vient altérer la nature de la requête. Par exemple, si on passe une valeur contenant une apostrophe dans un des champs, la syntaxe de la requête SQL devient invalide (WHERE username = 'code' 18' AND ...) et le serveur lance un message d'erreur. Si le serveur ne cache pas ces messages ou qu'une page d'erreur 500 n'est pas activée, vous êtes encore plus vulnérable car les messages d'erreurs que lancera le serveur de base de données (SQL Server) sont assez explicites.
Une bonne façon de faire planter une requête SQL est d'entrer n'importe quoi comme nom d'utilisateur ainsi que la chaîne de caractères ' HAVING 1=1;-- au lieu du mot de passe.
Le résultat que le serveur tentera d'exécuter :
WHERE username = 'blablabla' AND password = '' HAVING 1=1;--'
Ici, l'apostrophe permet de fermer la chaîne pour avoir une syntaxe valide (donc une comparaison avec un mot de passe vide). La clause HAVING sera celle qui permettra de générer une erreur dans la requête, car ne peut être utilisée qu'en combinaison avec la clause GROUP BY (1=1 représente une condition qui retourne toujours vrai, pour le besoin de la cause). On termine le tout en fermant l'énoncé SQL par le symbole ";" et on commente le reste de la requête s'il y a lieu avec "--". Une fois exécutée, un message d'erreur devrait apparaître :
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.realname' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
Voilà, la base de données vient de nous faire cadeau non seulement du nom de la table, mais aussi du nom d'un champ!
Comme on en sait un peu plus, on peut reprendre le processus en tentant d'injecter autre chose. Dans le champ mot de passe, si on entre '; SELECT * FROM users GROUP BY realname;--, on fait en sorte qu'on exécute une seconde requête à la suite de l'originale qui demande à la base de données de nous retourner tous les champs de la table users, ce qui provoque une autre erreur causée par le GROUP BY qui ne peut fonctionner en raison du SELECT *.
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
En répétant le processus, on arrive à obtenir le nom de tous les champs. Vous voyez l'idée ? Rien de plus simple ensuite pour insérer du contenu, effectuer une mise à jour pour changer de mot de passe sur un compte et même obtenir un nom d'usager et son mot de passe (il y a une astuce ici mais je ne vous dis pas tout, mon objectif étant de vous sensibiliser au concept pour améliorer votre programmation et non d'en tirer profit ou en vous incitant à des pratiques douteuses).
En conclusion :
- validez toujours les paramètres reçus (GET ou POST)
- cacher ou désactivez les erreurs et alertes du serveur
- créez une page d'erreur 500
- encryptez les mots de passe avec un salt
- utilisez des procédures stockées
Deux possibilités existent : soit nous sommes seuls dans l'Univers, soit nous ne le sommes pas. Les deux hypothèses sont tout aussi effrayantes.
Je viens regarder le match de hockey opposant les Canadiens de Montréal contre les Sénateurs d'Ottawa sur les ondes de RDS et je me suis rendu compte que le sous-titre de l'émission d'après-match utilisait la même police de caractères que la console de jeux Playstation 3, tandis que que celle-ci a utilisé la même que celle des films Spider-Man.
Voici la comparaison :
RDS
Playstation 3 (2006)
Spider-Man (2002)
J'imagine que les concepteurs de RDS ont voulu faire un lien avec le "filet"...
Est-ce que ça vous est déjà arrivé de regretter qu'un site web ne possède pas une fonctionnalité que vous jugeriez pratique ou encore de vouloir en modifier une déjà existante ?
Dans mon cas, après avoir visité à plusieurs reprises le site web de l'humoriste Louis-José Houde, je commençais à trouver que les vidéos placés dans le haut des sections étaient agaçants car ils démarrent automatiquement à chaque chargement de page. On en rit lors des premières visites mais comme n'importe quelle blague, ça peut être pénible de l'entendre trop souvent.
Heureusement, mon impatience peut être contrôlée grâce à Greasemonkey, une extension pour Firefox qui permet de créer des scripts (JavaScript) pour modifier le comportement des pages, ajouter, remplacer et même supprimer du contenu. Une fois le script écrit, on l'installe et il sera exécuté à chaque visite du site en question.
D'abord, installons l'extension Greasemonkey (la version actuelle est 0.8). Une fois ajouté à Firefox, on devrait voir le visage d'un singe dans le coin inférieur droit du fureteur. Si le visage du singe est souriant et coloré, c'est que l'extension est activée et que les scripts installés seront exécutés. Si ce n'est pas le cas, il suffit de cliquer sur le visage pour basculer entre le mode actif et inactif.
Ensuite, on est prêt à installer des scripts. Bien qu'on puisse en télécharger un peu partout sur Internet (userscripts.org est une bonne source de scripts Greasemonkey), je vais tenter de présenter une brève introduction sur la façon de rédiger ce genre de script.
Pour pouvoir réaliser un nouveau script, rendez-vous dans Firefox, au menu Tools / Greasemonkey / New User Scripts. Un formulaire vous permettra de définir les metadatas :
- Name : nom du script (pas très important)
- Namespace : d'où le script provient (pas très important)
- Description (pas très important)
- Includes (important) : il s'agit de dire sur quels domaines on veut exécuter le script
- indiquer * si on veut inclure tous les sites
- sinon entrez l'URL de chaque site. Dans mon cas, je vais spécifier toutes les pages du site de Louis-José Houde : http://www.louisjosehoude.com/*
En arrière-plan, un fichier dont le nom se termine par .user.js sera créé. À l'intérieur (il sera automatiquement ouvert dans l'éditeur), on remarquera que l'entête contient les metadatas entrés dans un format particulier :
// ==UserScript==Immédiatement en dessous, on entrera le code JavaScript à exécuter. Comme mon objectif est de retirer les vidéos placés en entête des sections, je dois initialement connaître l'élément du Document Object Model (DOM) que je vais devoir retirer. En inspectant le code source HTML avec Firebug, on note que l'ID que porte la balise qui contient les vidéos est "videoTop" mais qu'elle n'est pas présente sur toutes les pages.
// @name LJH
// @namespace http://code18.blogspot.com
// @description Retirer les vidéos des entêtes de sections
// @include http://www.louisjosehoude.com/*
// ==/UserScript==
Comme le DOM doit être complètement chargé pour pouvoir y appliquer des modifications (autrement il ne trouvera pas les éléments), on appelera la fonction onLoad dès que la page sera chargée.
window.addEventListener('load', onLoad, false);Pour terminer, on enregistre le script et on le teste en allant visiter le site. Les vidéos devraient être disparus.
function onLoad() {
if ( document.getElementById('videoTop') ) {
var videoDiv = document.getElementById('videoTop');
videoDiv.innerHTML = '';
}
}
Cette semaine, je réfléchissais à l'idée de créer une interface web au YUI compressor, où on aurait pu simplement fournir le fichier JavaScript à compresser et qu'il retournerait en retour le fichier réduit, sans se soucier des arguments à passer ou de la syntaxe à utiliser dans l'invite de commande.
Avant d'entreprendre quoi que ce soit, j'ai vérifié si l'idée n'avait pas été implémentée ailleurs sur le web. Or, le développeur français Rodolphe Stoclin a eu l'idée avant moi et je dois dire qu'il a effectué un travail remarquable, allant même jusqu'à indiquer le taux de compression. Son interface web de YUI compressor est conviviale et très simple à utiliser. À mettre dans vos favoris, ça vous sera certainement utile plus d'une fois.
Dans le livre 2001, L'Odyssée de l'espace (1968) de Arthur C. Clarke (dont un film fut produit la même année par Stanley Kubrick), le nom du super ordinateur intelligent, HAL 9000, ferait référence à la marque IBM.
Pourquoi ? Si on décale les trois lettres H-A-L par les suivantes dans l'alphabet, on obtient I-B-M, alors que son nom signifierait plutôt Heuristically programmed ALgorithmic computer.
Simple coïncidence ?
Obtenir la clé primaire d'une table sous SQL Server
Voici deux requêtes SQL qui permettent de connaître le nom du champ représentant la clé primaire d'une table sous SQL Server.
Requête # 1
SELECT kcu.COLUMN_NAMERequête # 2 :
FROM INFORMATION_SCHEMA.table_constraints tc
INNER JOIN INFORMATION_SCHEMA.key_column_usage kcu
ON tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
AND tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
WHERE tc.TABLE_SCHEMA = 'dbo'
AND tc.TABLE_NAME = 'nom_de_la_table'
AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
SELECT column_nameÀ noter qu'on peut obtenir la liste des clés étrangères en remplacant IsPrimaryKey par IsForeignKey.
FROM INFORMATION_SCHEMA.key_column_usage
WHERE OBJECTPROPERTY(OBJECT_ID(constraint_name), 'IsPrimaryKey') = 1
AND table_name = 'nom_de_la_table'
Dans le cadre d'un projet web, j'ai eu à conserver un log des modifications effectuées sur une table d'une base de données SQL Server. La définition de la table contenait déjà des colonnes indiquant la date de création et la dernière modification relatives à l'enregistrement, comme c'est le cas lorsqu'on met à jour tous les champs à partir d'un formulaire web.
Dans mon cas, le besoin du client nécessitait plus de "granularité" pour pouvoir connaître à quel moment chaque champ a été modifié, de façon à être en mesure de comparer les données avant et après la mise à jour.
La façon la plus simple que j'ai trouvé a été de créer un trigger (déclencheur) au niveau de la mise à jour de la table et de comparer les valeurs à l'aide des tables spéciales inserted et deleted (automatiquement gérées par MSSQL). Le concept de ces tables est assez simple à comprendre :
- l'enregistrement de la table contient les données
- lorsque le trigger démarre, une copie des données sur le point d'être modifiées sont stockées dans la table "deleted". Ces données seront "supprimées" car elle seront remplacées par les nouvelles valeurs
- quant aux nouvelles données qui remplaceront les anciennes, elles se trouveront dans la table "inserted"
- pour ceux qui se posent la question, il n'existe pas de table spéciale "updated" ;-)
-- Attention
-- Cet exemple fonctionne pour un seul enregistrement.
-- Pour plusieurs, il faudra faire une boucle
-- sur chaque enregistrement de la table inserted.
CREATE TRIGGER trigger_log_de_comparaison_sur_nom_de_la_table
ON dbo.nom_de_la_table
AFTER UPDATE
AS
BEGIN
DECLARE
@pk_record INT,
-- pour chaque champ à comparer
@old_name VARCHAR(50),
@new_name VARCHAR(50)
-- sélectionner la nouvelle valeur
SELECT
@pk_record = pk_record,
@new_name = name
FROM inserted
-- sélectionner l'ancienne valeur
SELECT @old_name = name
FROM deleted
IF @old_name <> @new_name
BEGIN
-- insert in log table
INSERT INTO dbo.log_table (pk_record, old_name, new_name, dt_changed)
VALUES ( @pk_record, @old_name, @new_name, getdate() )
END
END
Ça faisait longtemps que ça traînait et que je voulais en ouvrir un, alors voilà, c'est fait.
Voici le répertoire de liens Code 18 sur Delicious.
Dans Firefox 3, si vous entrez les mots clés suivants dans la barre d'adresse, voici les surprises que vous pourrez apercevoir :
about:mozilla
Mammon slept. And the beast reborn spread over the earth and its numbers grew legion. And they proclaimed the times and sacrificed crops unto the fire, with the cunning of foxes. And they built a new world in their own image as promised by the sacred words, and spoke of the beast with their children. Mammon awoke, and lo! it was naught but a follower.
from The Book of Mozilla, 11:9
(10th Edition)
Désormais classique. Selon la version, différents versets de l'imaginaire Book of Mozilla sont affichés. Après vérification, ces messages apparaîssent aussi dans d'autres applications utilisant le même engin de rendu, comme le lecteur multimédia Songbird et le navigateur social Flock.
about:robots
Gort! Klaatu barada nikto!
Welcome Humans!
We have come to visit you in peace and with goodwill!
- Robots may not injure a human being or, through inaction, allow a human being to come to harm.
- Robots have seen things you people wouldn't believe.
- Robots are Your Plastic Pal Who's Fun To Be With.
- Robots have shiny metal posteriors which should not be bitten.
Try Again
...
Please do not press this button again.
La citation "Gort! Klaatu barada nikto!" fait référence au film de 1951 The Day the Earth Stood Still. Pour les intéressés, des extraits du film sont disponibles sur YouTube.
chrome://browser/content/browser.xul
Permet d'ouvrir Firefox à l'intérieur de Firefox!
chrome://global/content/alerts/alert.xul
Firefox se fout de votre gueule en disparaissant curieusement. Mais attention, si c'est votre seul onglet d'ouvert, Firefox s'éteindra automatiquement à la fin de son petit tour. Sinon, vous pouvez faire la combinaison des touches CTRL+W pour arrêter le tout.
Dans la suite bureautique OpenOffice, si on ouvre un tableur Calc (feuille de calcul comme Excel), qu'on entre la formule =GAME("StarWars") dans n'importe quelle cellule et qu'on appuie la touche Enter, une version intégrée du jeu de Space Invaders démarre automatiquement.
- Le mot clé "StarWars" est sensible à la casse
- On peut jouer qu'une seule partie, autrement le message "oh no, not again!" apparaît
- Pour reproduire le easter egg, il faut redémarrer OpenOffice (attention au pré-chargement dans le system tray!)
- On y joue avec les flèches du clavier et la barre d'espacement ou avec la souris
La dernière personne à démissionner ou à se faire virer sera tenue pour responsable de tout ce qui ira mal par la suite.
Jeudi, j'ai introduit la possibilité d'ajouter par JavaScript de la coloration syntaxique aux codes sources publiés en exemple. Aujourd'hui, j'aborderai le même sujet mais cette fois en utilisant la librairie PHP GeSHi - Generic Syntax Highlighter.
Contrairement à la librairie JavaScript dont j'ai parlé, GeSHi nécessite une seule dépendance : rouler sur un serveur PHP (c'est d'ailleurs la raison pour laquelle je n'ai pas pu l'utiliser sur Blogger). Par ailleurs, GeSHi étend sa reconnaissance syntaxique à plus d'une centaine de langages de programmation.
Pour l'utiliser, c'est assez simple :
- télécharger l'archive (zip, tar.gz ou tar.bz2)
- la décompresser
- dans le répertoire principal, on trouvera 3 sous-dossiers : contrib, docs, geshi et le fichier geshi.php. Sur le serveur de production, les répertoires contrib et docs ne sont pas nécessaires
- quand viendra le temps d'utiliser la librairie dans notre code source PHP, on devra inclure le fichier geshi.php (dans mon cas, j'ai placé GeSHi dans le répertoire includes où j'y mets toutes mes librairies) :
require_once('/includes/GeSHi/geshi.php'); - quant au répertoire geshi (/includes/GeSHi/geshi/), il contient un fichier pour chaque langage à traiter. Les fichiers de langue seront inclus automatiquement par le script principal, geshi.php lors de l'instanciation de l'objet
- pour colorer un code source :
$source = 'C'est aussi simple que ça.
$max = 10;
$find = 7;
for ( $i=1; $i<$max; $i++ ){
if ($i == $find) {
echo "Value $i found";
}
}';
$geshi = new GeSHi($source, 'php');
$geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS, 2);
$geshi->start_line_numbers_at(1);
$geshi->set_line_style('background:#f2f1ed;', 'background:#ffffff;');
$geshi->set_case_keywords(GESHI_CAPS_LOWER);
// afficher la source
echo $geshi->parse_code();
Voici le résultat :
À noter que Wikipedia, basé sur l'application MediaWiki, utilisent l'extension SyntaxHighlight GeSHi pour procéder à la coloration syntaxique.
Dans leur cas, il faut utiliser la syntaxe suivante, où l'attribut lang représente le langage de programmation à traiter :
<source lang="php">
code source...
</source>
Pour tous les sportifs de salon qui se respectent, on vient de confirmer que l'équipe du Sportnographe, LA référence en sport (désolé Ron...) aura son émission sur les ondes de la Première chaîne de Radio-Canada, tous les vendredis soirs de 19h à 20h, immédiatement avant Macadam Tribus (une autre excellente émission).
Une heure en compagnie d'Yvan Piquette et Paul Meilleur-Aucoin, dès le 16 janvier 2009! En attendant, suivez leur blogue en direct.
Génial non ? Je laisse le commentaire de la fin à Joël Bouchard :
J’aimerais ça pouvoir dire quelque chose de plus intelligent, mais... chu pas vraiment capable...
Ajouter de la coloration syntaxique au code source publié
Comme je publie parfois du code source sur mon blogue, je me disais que ça serait intéressant de pouvoir ajouter de la couleur à mes exemples pour faciliter la lisibilité. Au début, j'ai vérifié si je ne pouvais pas utiliser quelque chose comme la coloration syntaxique de GeSHI (Generic Syntax Highlighter) mais comme il s'agit d'une librairie PHP, je devais trouver une autre solution.
En fouillant un peu, je suis tombé sur Quick Highlighter, un convertisseur web qui prend le code en entrée (on doit spécifier le langage) et qui retourne le code coloré en format HTML. Avec ça, j'aurais pu simplement copier / coller le résultat dans mon blogue.
Mais comme je voulais quelque chose d'un peu plus automatisé, j'ai découvert sur Google Code un projet qui s'appele SyntaxHighlighter, complètement indépendant du serveur web puisqu'il fonctionne côté client en JavaScript et CSS. Peut-être avez-vous déjà vu le rendu du code source de Yahoo Developer Network ou Aptana ? C'est ce composant qu'ils utilisent.
Par rapport à GeSHi, SyntaxHighlighter possède trois défauts notables:
- il est plus lent que GeSHi (qui roule côté serveur)
- il est efficace pour une source comptant peu de code
- il est limité au niveau des langages supportés : C++, C#, CSS, Delphi, Java, JavaScript, PHP, Python, Ruby, SQL, VB, XML et HTML
- Dans l'onglet Layout, aller dans Edit HTML
- Juste avant la balise </head>, faire un lien vers les fichiers JavaScript de chaque langage pour lequel on veut supporter la coloration :
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shCore.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushXml.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushSql.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushPhp.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushCss.js'/>
<script language='javascript' src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushJScript.js'/> - Juste avant la balise </body>, copier le code suivant qui permet d'initialiser le composant. Il doit se trouver à la fin car le DOM doit être complètement chargé pour pouvoir le détecter.
<script language="javascript">
dp.SyntaxHighlighter.BloggerMode();
dp.SyntaxHighlighter.HighlightAll('code');
</script> - Comme je n'ai pas réussi à lier la feuille de style CSS par une balise <link> pour que ça fonctionne avec Firefox, il m'a fallu ouvrir le fichier http://syntaxhighlighter.googlecode.com/svn/trunk/Styles/SyntaxHighlighter.css et copier le code CSS et le coller avant la ligne ]]></b:skin>. Le mieux est de le placer à la fin des CSS définis par Blogger sinon la taille du texte sera changée (dans mon cas, le texte avait considérablement grossit).
- Placer le code source entre la balise suivante. Prenez soin d'indiquer dans l'attribut "class" le langage à colorer :
<pre name="code" class="PHP">code à placer ici</pre> - Vous devriez voir l'exemple ci-dessous avec la coloration syntaxique :
$var = 1;
if ($var == 1) {
echo 'OK';
}
Ajouter au Windows Explorer un raccourci vers le command prompt
Quand je travaille avec Windows, une chose qui m'agace (et c'est loin d'être la seule) est de devoir accéder à un répertoire en invite de commande (command prompt), surtout quand j'ai utilisé le Windows Explorer pour trouver l'emplacement et que je dois réinscrire le chemin complet.
En général, on peut copier le chemin inscrit dans la barre d'adresse, entrer dans l'invite de commande et coller l'adresse comme paramètre à la commande "cd" en cliquant sur le bouton droit de la souris (tout simplement choisir "coller").
On peut automatiser le tout en modifiant la base de registres de Windows.
- Créer un fichier texte
- Copier à l'intérieur le code suivant :
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\shell\Command]
@="Lancer le command prompt ici"
[HKEY_CLASSES_ROOT\Directory\shell\Command\Command]
@="cmd.exe /k cd %1" - Renommer le fichier addprompt.reg
- Double-cliquer sur le fichier pour appliquer les modifications
En programmation, on peut optimiser la performance des recherches en créant un arbre binaire. Cette technique consiste à maintenir un index des valeurs et peut être représentée sous la forme de branches d'arbre, partant de la racine, où chaque noeud est associé à une valeur unique qui se subdivise en deux éléments enfants. Ainsi, on pourra effectuer très rapidement une recherche dite "dichotomique" (couper en deux) dans un ensemble de valeurs très large.
Comme c'est un cas d'école, je laisserai les livres expliquer en détails tout le concept et je me contenterai de démontrer l'avantage de ce système de classement qui illustre bien ce qui se produit à l'interne. De façon plus concrète, on peut, dans une base de données, appliquer sur des champs des index déjà programmés pour améliorer la performance d'accès aux données, par exemple sur une clé primaire ou dans une jointure.
Pour mieux illustrer ce qui se passe, prenons par exemple un échantillon d'un million de cartes numérotées de 1 à 1 million. Si on recherche une valeur dans le lot sans qu'il y ait d'index, on risque de devoir comparer la valeur recherchée avec la valeur de chaque carte, jusqu'à ce qu'on la trouve (et on arrête de comparer dès qu'on l'a trouvée). Par exemple, si on recherche le nombre 420 003, le nombre nécessaire de comparaisons pourrait osciller entre une seule (si on est très chanceux et qu'on tombe sur la bonne carte dès le premier tour) et 1 million (si on est vraiment malchanceux et que c'est la dernière).
En maintenant un index binaire, c'est un peu comme si on classait les cartes en ordre croissant de valeur. Lorsque vient le temps d'en rechercher une, on prend la carte se trouvant au milieu du paquet et on la compare avec la valeur recherchée. Si elle est plus petite, on poursuivra la recherche avec la première moitié du paquet tandis que si elle est plus grande, on utilisera l'autre moitié, et ainsi de suite.
Nombre recherché : 420 003
- Cartes 1 à 1 000 000 (1 million de combinaisons possibles)
On coupe le paquet en deux pour trouver la carte 500000. Le nombre recherché est plus petit donc on garde la moitié de paquet inférieure - Cartes 1 à 500 000 (500 000 combinaisons)
On coupe le paquet en deux. Le nombre recherché est plus grand que 250 000, on poursuit avec la moitié de paquet supérieure - Cartes 250 000 à 500 000 (250 000 combinaisons)
On coupe encore le paquet en deux pour y trouver la carte 375 000. Le nombre recherché est plus grand, on utilise la moitié du paquet supérieure - Cartes 375 000 à 500 000 (125 000 combinaisons)
On coupe en deux : c'est la carte 437 500. Notre nombre est plus petit... - Cartes 375 000 à 437 500 (62 500)
On coupe en deux : voilà la carte 406 250. Notre notre est plus grande - Cartes 406 250 à 437 500 (31 250)
On divise le paquet en deux. La carte du milieu est 421 875. Notre nombre est plus petit - Cartes 406 250 à 421 875 (15 625)
On divise encore. La carte est 414 063, donc notre nombre est plus grand - Cartes 414 063 à 421 875 (7812)
On divise toujours... 417 969. Plus grand.
Ensuite... - Cartes 417 969 à 421 875 (3906)
- Cartes 419 922 à 421 875 (1953)
- Cartes 419 922 à 420898 (946)
- Cartes 419 922 à 420 395 (473)
- Cartes 419 922 à 420 159 (237)
- Cartes 419 922 à 420 040 (118)
- Cartes 419 981 à 420 040 (59)
- Cartes 419 981 à 420 011 (30)
- Cartes 419 996 à 420 011 (15)
- Cartes 419 996 à 420 004 (8)
- Cartes 420 000 à 420 004 (4)
- Cartes 420 002 à 420 004 (2)
- Carte 420 003 trouvée en seulement 21 comparaisons !
À titre d'information, PostgreSQL possède les types d'index suivants :
- B-tree : un arbre "balancé", similaire à l'arbre binaire, conçu pour un large volume de données
- R-tree : spécialisé pour les recherches spaciales et multidimentionnelles
- Hash : indexé comme une table d'hachage et performant avec l'opérateur =
- GiST : Generalized Search Tree, une infrastructure permettant d'implanter différentes stratégies d'index, comme par exemple un cube de données
L'open source s'étend aux produits de la vie quotidienne
Selon le site web du GNU Project, la définition de "Free Software", telle que décrite par son fondateur Richard Stallman, se résume comme suit :
Free software is a matter of liberty, not price. To understand the concept, you should think of free as in free speech, not as in free beer.
Autrement dit, on prône la liberté d'utiliser le produit, de comprendre son fonctionnement, de l'adapter à ses besoins, de le redistribuer et d'en faire profiter la communauté.
S'inspirant du concept, une recette de bière est apparue au Danemark sous le nom de Free Beer (originalement appelée Vores Øl, Our Beer en danois).
Pour ceux qui préfèrent les boissons non-alcoolisées, jetez un oeil à OpenCola, une propriété de l'entreprise canadienne Open Text Corporation.
Aujourd'hui, laissez-moi vous introduire au monde de Homestar Runner, une série humoristique de cartoons web qui compte plusieurs personnages colorés et dont les capsules sont réalisées en Flash. Bien que Strong Bad ne soit pas le personnage central de cet univers déluré, il en reste pas moins que sa série, Strong Bad Email, soit ma favorite. Depuis sa création en 2001, elle compte déjà plus de 200 épisodes, que pour cette série. Imaginez la charge de travail que cela a dû nécessiter...
Le personnage au masque de lutteur se rend périodiquement devant son ordinateur pour y répondre aux courriels des lecteurs, ce qui met la table à une mise en scène qui ne manque pas de surprendre. Une de mes préférée est la capsule "Website" où un lecteur demande l'avis de Strong Bad pour construire un nouveau site web. Ses recommandations : une introduction Flash vraiment horrible, des cadres (frames), BEAUCOUP de gif animés... Le résultat est ici.
À remarquer : lorsque Strong Bad répond à ses courriels, le modèle de son ordinateur évolue (si on peut dire) au fil du temps. Aussi, à la fin de la plupart des épisodes, déplacez le curseur de votre souris sur le Flash pour découvrir un petit clin d'oeil relié à la capsule.
Ah oui, une autre petite chose que beaucoup de gens ignorent... Deux pièces musicales provenant de l'univers de Homestar Runner figurent sur les jeux Guitar Hero II et Guitar Hero Rocks the 80s... À vous de trouver lesquelles.
TROGDOR!!!
Voici le message de sagesse que contenait un fortune cookie trouvé récemment dans un restaurant de Montréal :
You love Chinese food
Yeah, right...
Photoshop a beau être un standard de l'industrie, la version CS4 se vend quand même 699$ US. Quand on n'est pas un infographiste professionnel, on est loin d'utiliser toutes les caractéristiques avancées qu'offre le produit.
Une alternative, venant encore une fois de la communauté Open Source, se nomme GIMP - GNU Image Manipulation Program. Cette application graphique gratuite est disponible pour plusieurs plateformes (Linux, Windows, Mac OS X, FreeBSD, OpenSolaris) et dans plusieurs langues, dont le français, bien que je préfère travailler avec des interfaces anglophones (c'est beaucoup plus facile de trouver de la documentation et du support en anglais).
GIMP est compatible avec les fichiers PSD de Photoshop, Paint Shop Pro, les fichiers svg, tiff, importation PDF, etc. Pour moi, c'est plus que ce que j'en aurai jamais besoin. À ajouter sans faute à son coffre à outil.
Un autre produit potentiellement intéressant est Paint.net, un remplaçant gratuit pour le très désuet Paint de Windows. Cependant, l'utilisation de celui-ci est limitée à Windows en raison de son architecture construite sur la plateforme .NET. Peut-on espérer une version indépendante construite sur Mono ? Il semble que oui : paint-mono sur Google Code.
Avez-vous remarqué que :
- dans l'univers des bases de données, certaines personnes ont tendance à prononcer le terme SQL (Structured Query Language) en disant "sequel"
- en infographie, les fichiers d'images PNG sont souvent prononcés "ping"
- dans le web, on comprend que l'acronyme WYSIWYG ("ouiziouig") se réfère à "What You See Is What You Get"
- en Flash, les extensions SWF peuvent être dits "Swiff"
- Un service web WSDL : "weezdel"
En allemand, SuSE est l'acronyme de "Software-und System-Entwicklung", ce qui veut dire "Software and System Development". Selon ce que j'ai trouvé, la prononciation anglaise la plus fréquente serait "soose", mais "zoose", "suzy", "suzi", "soo-zuh", "suezuh" et "souzeu" sont aussi utilisées de façon générale.
Décidément, je suis dans le sujet des favicons cette semaine. Suite à ma découverte d'hier, j'ai fait mes expériences et j'ai vu qu'il était possible de spécifier un favicon spécifique pour les iPod Touch / iPhone. Habituellement, quand on ajoute un site dans ses favoris sur un de ces appareils, on a le choix de créer un favori normal (bouton "Add Bookmark") ou de créer une icône sur le tableau de bord (bouton "Add to Home Screen"). Par défaut, l'ajout d'un raccourci crée une capture d'écran de la page web et génère automatiquement une icône qui sera placée sur le tableau de bord, à moins qu'une balise <link> spéciale soit trouvée dans le code HTML.
Voici les spécifications nécessaires :
- une balise link
- une relation de type "apple-touch-icon"
- une image au format PNG
- l'image doit être de dimension 57 x 57 pixels
Une fois l'icône créée, il suffit de la placer sur le site et d'ajouter aux pages un code HTML semblable au suivant (à l'intérieur de la balise <head>) :
<link rel="apple-touch-icon" href="favicon-57.png"/>
Juste pour le plaisir, j'ai regardé plusieurs sites web d'entreprises spécialisées dans la conception web au Québec et je me rends compte que la plupart n'en ont toujours pas, y compris l'entreprise pour laquelle je travaille. Dès mon retour de vacances, je proposerai cette amélioration.
Mise à jour 2009-01-05
Après vérification, on peut aussi simplement le placer à la racine du site si le nom du fichier est apple-touch-icon.png, sans même utiliser de balise <link>.