lucidiot's cybrecluster

Une console VBScript pour Internet Explorer 6, partie 1

Lucidiot Informatique 2022-09-25
On prend les mêmes et on recommence.


De nos jours, si vous voulez ajouter un script JavaScript dans une page Web, vous n'avez rien d'autre à faire que de le mettre dans une balise <script> sans arguments.

<script>
alert('hello');
</script>

Dans des navigateurs plus anciens, il peut être nécessaire d'ajouter un type MIME :

<script type="text/javascript">
alert('hello');
</script>

Alternativement, la détection se faisait parfois avec un attribut language, ou même les deux en même temps :

<script type="text/javascript" language="javascript">
alert('hello');
</script>

Les scripts sur la PSP peuvent notamment avoir besoin de l'attribut language pour fonctionner correctement. Il était aussi nécessaire d'entourer son code JavaScript s'il était intégré dans la page, sans utiliser d'attribut src, par des commentaires HTML pour s'assurer que des navigateurs qui ne connaissent pas l'existence de JavaScript ignorent les scripts en toute sécurité. C'est aujourd'hui une pratique déconseillée.

<script type="text/javascript" language="javascript">
<!--
alert('hello');
-->
</script>

Les attributs type et language ont globalement disparu des pages Web. Ils sont encore utilisables, mais soit les navigateurs ignoreront complètement leurs valeurs, choisissant de tout interpéter comme du JS, ou n'accepteront que text/javascript et javascript respectivement. Certains frameworks JavaScript récents peuvent parfois utiliser cet attribut pour indiquer un langage étrange, par exemple Pug pour écrire du HTML dans une syntaxe simplifiée, ou TypeScript, Sass, Less, etc. pour du JS ou du CSS, mais tout ça finit par être compilé en du HTML, du JS et du CSS classiques.

Avec Internet Explorer 6, on est encore dans cette époque où type, language et les commentaires HTML peuvent être nécessaires. Avec Internet Explorer 8, on arrive dans une époque où la quasi-totalité des navigateurs peuvent au minimum ignorer JavaScript correctement, donc les commentaires HTML ne sont plus trop une bonne idée, mais type, et surtou language, résistent encore.

Internet Explorer utilise vraiment ces attributs, car il prend véritablement en charge d'autres langages. Cette page en fournit une documentation non officielle.

Pour indiquer JavaScript, on peut utiliser javascript tout court, ou javascript1.1, javascript1.2, javascript1.3 qui désignent des versions de JavaScript à l'époque où c'était encore un langage spécifique à Netscape Navigator et pas standardisé par Ecma, ou encore jscript, le moteur d'exécution JavaScript concurrent écrit par Microsoft, ou enfin livescript, l'ancien nom de JavaScript très tôt durant son développement.

Il y a aussi des valeurs un peu plus ésotériques : JScript.Compact active le mode ECMA 327, aussi connu sous le nom de ES-CP, qui désactive toutes les fonctionnalités que ce standard rend optionnelles dans JavaScript ES3. JScript.Encode est un script en JavaScript classique, mais chiffré de telle sorte à rendre la copie plus difficile depuis une page Web ; Internet Explorer va déchiffrer le script avant de l'exécuter. Des décodeurs ont très vite été créés, rendant cette sécurité par l'obscurité inutile. XML permet d'imbriquer du XML à l'intérieur d'un fichier HTML, une syntaxe marquée comme dépréciée depuis Internet Explorer 5.

Le langage particulier qui nous intéresse aujourd'hui est vbscript. VBScript est le langage Active Scripting, ou ActiveX, équivalent à Visual Basic. Alors que Java et JavaScript n'ont absolument rien à voir entre eux, VB et VBScript sont bel et bien liés. On ne parle cependant pas de Visual Basic .NET, qui est un tout autre langage avec un fonctionnement très différent et des concepts de programmation orientée objet ; on reste sur un langage procédural. VBScript est aussi similaire à VBA, le langage qui existe toujours dans les applications Microsoft Office, et qui représente tout ce que les développeurs modernes détestent : la possibilité pour les utilisateurs de coder par eux-mêmes.

Puisque Active Scripting avait été imaginé comme permettant d'ajouter la capacité de script à toutes les applications , on peut retrouver VBScript dans divers contextes, dont deux principaux. Les fichiers avec l'extension .vbs sont exécutés par le Windows Script Host, et ont longtemps été l'étape au-dessus des scripts Batch pour les développeurs du dimanche. Certains connaissent peut-être ces fichiers par l'intermédiaire du célèbre virus ILOVEYOU.VBS. Et ensuite, on retrouvera des scripts VBScript dans Internet Explorer :

<script type="text/vbscript" languge="vbscript">
MsgBox "hello"
</script>

Dans les épisodes précédents de mon masochisme avec Internet Explorer, j'ai développé un interpréteur JavaScript très spartiate pour fournir un équivalent aux outils de développement des navigateurs plus modernes et explorer les API ActiveX. Mais puisque VBScript est un langage créé directement par Microsoft, il peut avoir quelques avantages quant à l'exploration de ces API : il aura probablement une meilleure intégration, quelque chose de mieux que typeof window.external renvoyant juste unknown, ou quelque chose permettant d'énumérer les propriétés d'un objet. On va donc essayer de créer exactement la même console, mais pour VBScript.

Structure de la console

La structure HTML de la console est pratiquement la même que pour la console JavaScript, avec l'exception bien sûr que notre balise <script> a des attributs différents :

<script type="text/vbscript" language="vbscript" src="console.vbs"></script>

Le style CSS ne change pas du tout. On peut immédiatement commencer à écrire la toute première ligne de notre script :

Option Explicit

Cette ligne est une directive, un peu comme "use strict"; en JavaScript : elle indique que les variables doivent être explicitement déclarées avant d'être initialisées. C'est une bien maigre protection qu'on peut utiliser contre du code mal écrit.

Outils de logging

Qu'on soit en VBScript ou en JavaScript, on n'aura pas accès à l'API console vu qu'elle n'existe pas dans IE6 ou IE7. Mais c'est encore pire avec VBScript : JavaScript définit un objet window.console qui existera dans IE8, et l'objet peut être modifié ou réécrit par du code JavaScript. Mais avec VBScript, il ne nous est pas possible de réécrire comme ça des méthodes globales ; on ne peut pas par exemple écrit Sub console.log(message). On va donc inventer nos propres méthodes qui ne suivront pas les habituels console.xxx qu'on a eu jusqu'alors. Ce n'est qu'une question de nommage des méthodes ; le fonctionnement restera le même.

Private Sub ConsoleLog(message, color)
    Dim entry
    Set entry = document.createElement("div")
    With entry.style
        .margin = "0"
        .padding = "1px"
        .borderBottom = "1px solid lightgray"
        If Not IsNull(color) Then
            .color = color
        End If
    End With
    If IsEmpty(message) Then message = "[Empty]"
    If IsNull(message) Then message = "[Null]"
    entry.appendChild(document.createTextNode(CStr(message)))
    output.appendChild(entry)
End Sub

Voici notre équivalent de makeLogger. On ne peut pas créer une fonction qui renvoie une fonction comme on l'avait fait en JS, alors on crée à la place une procédure, c'est-à-dire une fonction qui ne renvoie pas de valeur, qui fera juste un seul log. On déclarera ensuite des procédures publiques qui appelleront cette procédure privée.

On définit notre variable entry, un équivalent à ce qu'on faisait en JavaScript, avec le mot-clé Set, après l'avoir déclarée avec Dim. Contrairement à du VB classique, on n'a pas à indiquer de types, car tout le typage est dynamique (Variant en VB). Toutes les assignations ne nécessitent pas forcément un mot-clé particulier : a = 4 est autorisé. Mais pour assigner un objet à une variable, il faut utiliser Set.

On utilise ensuite un bloc souvent utilisé par les développeurs VB, With. Ce bloc permet de définir un contexte où . représente ce qu'on a indiqué dans l'en-tête du With : .margin correspond à entry.style.margin. C'est un raccourci qui peut améliorer la lisibilité.

With existe aussi en JavaScript, mais son utilisation est fortement déconseillée car comme pour tout en JavaScript, c'est une source de bugs supplémentaire : alors que VBScript demande un . pour distinguer entre les variables globales ou locales et l'objet désigné par le With, JavaScript ne demande rien de particulier, donc il devient possible pour l'objet désigné par un with de réécrire des fonctions intégrées comme JSON.parse ou parseInt et ainsi introduire des vulnérabilités, des incompatibilités ou des bugs incompréhensibles.

Il reste encore un dernier détail à préciser : CStr est la fonction qui permet de forcer la conversion à une chaîne de caractères, et fonctionne de façon généralement plus fiable que .toString() en JavaScript, notamment parce que c'est une fonction intégrée et pas quelque chose de modifiable sur chaque objet. CStr ne peut par contre pas gérer l'équivalent VBS de undefined ou de null, donc on ajoute des conditions qui modifient le message pour gérer ces cas.

On peut ensuite déclarer quelques procédures publiques pour utiliser cette procédure privée, et donner quelque chose qui ressemble à une API de console classique :

Sub Log(message)
    ConsoleLog message, Null
End Sub

Sub Debug(message)
    ConsoleLog message, "darkgray"
End Sub

Sub Info(message)
    ConsoleLog message, Null
End Sub

Sub Warn(message)
    ConsoleLog message, "orange"
End Sub

Sub Error(message)
    ConsoleLog message, "red"
End Sub

On peut noter ici deux choses : d'abord, VBScript ne nous permet pas comme le faisait JavaScript d'accéder à un équivalent de arguments, donc on ne peut gérer qu'un seul argument dans les consoles. Ce sera la responsabilité de l'utilisateur de concaténer des chaînes de caractères pour construire des valeurs plus complexes. Ensuite, on appelle notre procédure ConsoleLog sans utiliser de parenthèses : VB, VBA et VBScript font la distinction entre les fonctions et les procédures de cette façon. C'est l'un des aspects qui peut causer en général le plus de levage de sourcils quand on commence à lire du code dans ces langages.

On peut enfin finir avec deux autres équivalents pour console.assert et console.clear :

Sub Assert(condition, message)
    If Not condition Then Error message
End Sub

Sub Clear
    output.innerHTML = ""
End Sub

Il n'y a pas grand chose de très spectaculaire ici. On ne peut pas cette fois implémenter un équivalent à console.trace comme on l'a fait en JS, car les fonctionnalités de gestion d'erreur de VBScript sont bien plus mauvaises que celles de JavaScript. On verra ça un peu plus tard quand on essaiera d'exécuter du code.

Gestion des boutons

On peut maintenant commencer à faire fonctionner les boutons de notre console. On a fait référence déjà quelques fois à output, une variable qu'on n'a pas encore défini ; on va le faire maintenant, en déclarant ces variables uniquement dans le contexte du script actuel avec Private, puis en utilisant le Sub window_onload, qui va se brancher tout seul sur l'événement de chargement de la page, pour définir les variables aux éléments HTML une fois qu'ils sont disponibles.

Private input, output, runButton, clearButton

Sub window_onload
    With document
        Set input = .getElementById("input")
        Set output = .getElementById("output")
        Set runButton = .getElementById("runButton")
        Set clearButton = .getElementById("clearButton")
    End With
End Sub

On peut maintenant assigner à nos deux boutons leurs gestionnaires d'événements respectifs : on va utiliser directement notre procédure Clear pour le bouton d'effacement, et on va créer plus tard la procédure Run pour exécuter du code.

Sub window_onload
    '...
    runButton.onclick = GetRef("Run")
    clearButton.onclick = GetRef("Clear")
End Sub

Note qu'on utilise ici GetRef : c'est une méthode particulière qui renvoie une référence à une procédure, pour qu'on puisse la définir comme gestionnaire d'événement, car autrement, une procédure n'est pas une variable. Pour comprendre pourquoi runButton.onclick = Run ne pourrait pas fonctionner, il faut regarder comment les fonctions peuvent renvoyer leurs valeurs : alors que dans la plupart des langages on retrouvera un équivalent d'un mot-clé return, en VBScript, on assigne la valeur de retour à la fonction elle-même :

Function Add(first, second)
    Add = first + second
End Function

Par conséquent, faire référence à Add ici pour gérer l'événement porterait à confusion ; est-ce la fonction dont on parle, ou le résultat de cette fonction ?

Il y a aussi une autre syntaxe qui pourrait rentrer en conflit ici : runButton.onclick = Run pourrait aussi signifier d'exécuter une procédure et d'utiliser sa valeur de retour comme valeur pour onclick. Ce n'est bien sûr pas possible, puisqu'une procédure ne retourne pas de valeur, mais la syntaxe de VBScript permettant une exécution des procédures en les tapant sans parenthèses, cette syntaxe porte là aussi à confusion. Par conséquent, on se retrouve avec un appel étrange à GetRef, une fonction qu'on ne retrouve nulle part ailleurs que dans la gestion d'événements.

Conclusion

On a ici fait une belle introduction aux principes de base de VBScript, quelque chose qui pourra être utile si je me remets plus tard à jouer avec d'autres projets. Dans le prochain article, on finira cette console VBScript, ce qui représentera une nouvelle étape pour plus facilement jouer avec Internet Explorer et continuer d'horrifier les développeurs Web actuels.


Commentaires

Il n'y a pour l'instant aucun commentaire. Soyez le premier !