IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JavaScript Discussion :

Gestion d'erreur sur une saisie


Sujet :

JavaScript

  1. #1
    Membre confirmé
    Homme Profil pro
    Analyse système
    Inscrit en
    Mai 2014
    Messages
    388
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Arménie

    Informations professionnelles :
    Activité : Analyse système
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mai 2014
    Messages : 388
    Points : 578
    Points
    578
    Par défaut Gestion d'erreur sur une saisie
    Bonsoir,

    Je voudrais qu'un utilisateur puisse entrer un nombre, mais aussi une opération basique.

    Par exemple, il peut entrer : 500
    ou encore (g=9.81 et 35 en degrés) : 300*g*sin(35)

    J'ai donc écrit un code ressemblant à celui que vous trouverez ci-dessous. Vous constaterez que l'utilisateur est prévenu s'il fait une erreur de saisie. Le problème, c'est qu'une erreur fréquente de saisie concerne le point remplacé par une virgule pour un nombre décimal.

    Par exemple, au lieu de : 5.7
    l'utilisateur entre : 5,7
    Dans ce cas, le résultat devient 7 au lieu de 5.7, l'entrée est reconnue mais le résultat est erroné.

    Je ne sais pas trop comment resoudre ce problème. Si vous avez une idée...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <script>
     
    g=9.81;
    pi=PI=3.141592653589793;
    // x en degrés pour les fonctions trigonométriques
    sin=function(x) { return Math.sin(eval(x)/57.29577951308232); }
    cos=function(x) { return Math.cos(eval(x)/57.29577951308232); }
    tan=function(x) { return Math.tan(eval(x)/57.29577951308232); }
    sqrt=Math.sqrt;
    pow=Math.pow;
     
    lire=function(nb) {
    	if(nb.replace(/ /g, "")=="") return 0;
    	else {
    		try { return eval(nb); }
    		catch(erreur) { return "Erreur de saisie!"; }
    	}
    };
     
    calculer=function() {
            x=document.getElementById("entree").value;
    	document.getElementById("resultat").value=lire(x);
    };
     
    </script>
    </head>
    <body>
    <p>Entrée = <input id="entree"></p>
    <p><input type="button" value="CALCULER" onclick="calculer();"></p>
    <p>Résultat = <input id="resultat"></p>
    </body>
    </html>

  2. #2
    Rédacteur

    Avatar de danielhagnoul
    Homme Profil pro
    Étudiant perpétuel
    Inscrit en
    Février 2009
    Messages
    6 389
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 73
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant perpétuel
    Secteur : Enseignement

    Informations forums :
    Inscription : Février 2009
    Messages : 6 389
    Points : 22 933
    Points
    22 933
    Billets dans le blog
    125
    Par défaut
    str = str.replace( / /g, "" ).replace( /,/g, "." );.

    eval() c'est le diable, le mal incarné, la porte ouverte aux pirates.

    Extrait de https://developer.mozilla.org/fr/doc...s_globaux/eval

    N'utiliser eval() qu'en dernier recours !
    eval() est une fonction dangereuse qui exécute le code passé en argument avec les privilèges de l'environnement appelant. Si eval() est utilisée avec une chaîne construite de façon mal intentionnée, cela pourra entraîner l'exécution d'un code malveillant sur la machine de l'utilisateur avec les permissions données au site ou au module complémentaire. À un niveau encore plus critique, du code tiers pourrait ainsi consulter la portée dans laquelle eval() a été invoquée. Cela peut permettre des attaques qui n'auraient pas été rendues possibles en utilisant un objet Function.
    Vous devez construire une calculatrice avec un clavier. Il y a beaucoup d'exemples sur le web, par exemple : http://www.supportduweb.com/scripts_...-bouttons.html

    Blog

    Sans l'analyse et la conception, la programmation est l'art d'ajouter des bogues à un fichier texte vide.
    (Louis Srygley : Without requirements or design, programming is the art of adding bugs to an empty text file.)

  3. #3
    Membre confirmé
    Homme Profil pro
    Analyse système
    Inscrit en
    Mai 2014
    Messages
    388
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Arménie

    Informations professionnelles :
    Activité : Analyse système
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mai 2014
    Messages : 388
    Points : 578
    Points
    578
    Par défaut
    Bonjour,

    Ce n'est pas une calculatrice, que j'essaie d'obtenir. Par ailleurs, la calculatrice donnée en exemple, très basique, utilise justement... la fonction eval()!

    Si vous avez une autre solution, pour obtenir la même chose sans utiliser eval(), je suis aussi preneur.

  4. #4
    Membre confirmé
    Homme Profil pro
    Analyse système
    Inscrit en
    Mai 2014
    Messages
    388
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Arménie

    Informations professionnelles :
    Activité : Analyse système
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mai 2014
    Messages : 388
    Points : 578
    Points
    578
    Par défaut
    Rebonjour,

    La fonction pow() prend deux paramètres séparés par une virgule. Si je replace la virgule par un point, cette fonction cessera de fonctionner.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    str = str.replace( / /g, "" ).replace( /,/g, "." );

  5. #5
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 093
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 093
    Points : 6 754
    Points
    6 754
    Par défaut
    Salut,

    tu vas devoir faire un petit analyseur de texte. Ce n’est pas très compliqué si tu arrives à définir précisément le « mini-langage » que tes utilisateurs peuvent utiliser. Pour ça, une poignée d’expressions rationnelles et quelques appels récursifs suffisent.

    L’analyse de code se fait en deux étapes : l’analyse lexicale, qui décompose les objets de base (nombres, opérateurs, fonctions), qu’on appelle lexèmes ou tokens. Dans ton cas, j’imagine ces différents types de lexèmes :
    • les constantes : g, PI, etc.
    • les nombres, avec les éventuelles décimales représentées par un point ou par une virgule
    • les opérateurs : *, +, etc.
    • les fonctions : sin, cos, tan, sqrt, pow
    • les parenthèses, qui servent à appeler les fonctions
    • les éventuels espaces blancs, qui seront simplement ignorés

    Écris une regexp par type de token. Fais attention à ce qu’un même motif ne puisse pas être reconnu pas plusieurs regexps.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    /^(?:g|pi)/i                 // constantes (CONST)
    /^-?\d+(?:[.,]\d+)?/         // nombres (NUM)
    /^[+-]/                      // opérateurs additifs (ADDSUB)
    /^[*/]/                      // opérateurs multiplicatifs (MULDIV)
    /^(?:sin|cos|tan|sqrt|pow)/i // fonctions (FUNC)
    /^\(/                        // parenthèse gauche (LEFTPAR)
    /^\)/                        // parenthèse droite (RIGHTPAR)
    /^\s+/                       // espaces blancs (BLANK)
    
    Il est important que les expressions ne détectent que des séquences en début de chaîne (marqueur ^) pour éviter que le lexeur n’avance trop vite et ne saute des tokens.
    On fera « avancer » le lexeur en coupant avec substring les parties du code qui ont été traitées.

    La raison pour laquelle j’ai séparé les opérateurs + et - de / et *, c’est parce qu’on va avoir besoin de gérer les priorités de calcul. Par exemple, dans la formule 4 + 2 * 3, si on analyse bêtement de gauche à droite, on va avoir un résultat de 6 * 3 = 18, alors qu’en tenant compte de la priorité de la multiplication, le résultat est 4 + 6 = 10.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    var patterns = [
      { type: 'CONST'    , regexp: /^(?:g|pi)/i             },
      { type: 'NUM'      , regexp: /^-?\d+(?:[.,]\d+)?/     },
      { type: 'ADDSUB'   , regexp: /^[+-]/                  },
      { type: 'MULDIV'   , regexp: /^[*/]/                  },
      { type: 'FUNC'     , regexp: /^(?:sin|cos|tan|sqrt)/i },
      { type: 'LEFTPAR'  , regexp: /^\(/                    },
      { type: 'RIGHTPAR' , regexp: /^\)/                    },
      { type: 'BLANK'    , regexp: /^\s+/                   }
    ];
     
    function lex(text) {
      var
        tokenList = [],
        len = patterns.length,
        i,
        match;
     
      lexing: while (text.length > 0) {
        for (i = 0; i < len; i++) {
          match = text.match(patterns[i].regexp);
          if (match) {
            if (patterns[i].type !== 'BLANK') {
              tokenList.push({
                type: patterns[i].type,
                data: match[0]
              });
            }
            text = text.substring(match[0].length);
            continue lexing;
          }
        }
     
        // si rien ne correspond, on émet une erreur
        throw new Error('Erreur du lexeur: impossible d’interpréter ' +
          text.substring(0, 50));
      }
      return tokenList;
    }
    Un mot à propos de ce label lexing: dans mon code. C’est une fonctionnalité de JavaScript qu’on ne voit pas souvent, elle permet de marquer une boucle pour les instructions continue et break. Ici, continue lexing permet de sauter à l’itération suivante du while en quittant directement la boucle for.

    La seconde étape est l’analyse syntaxique, qui utilise l’agencement des lexèmes pour reconnaître une construction donnée, selon une grammaire formelle. La grammaire se décompose en règles, qui peuvent être récursives ou terminales.
    Code EBNF : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    expression := level1Expression ( ADDSUB level1Expression )* ;
     
    level1Expression := level2Expression ( MULDIV level2Expression )* ;
     
    level2Expression := LEFTPAR expression RIGHTPAR |
                        functionCall |
                        primitive ;
     
    functionCall := FUNC LEFTPAR expression RIGHTPAR ;
     
    primitive := NUM | CONST ;
    Plusieurs choses à expliquer ici. On a une récursivité indirecte : expression appelle level1Expression qui appelle level2Expression qui appelle expression. Ça ne fait pas de boucle infinie car tout finit par se réduire à la règle primitive qui est terminale.

    La gestion des priorités se fait de bas en haut : plus une règle est basse, plus elle est prioritaire. Les parenthèses ont la priorité la plus haute.

    En écrivant cette grammaire, je me suis assuré qu’il n’y ait pas de récursivité en partie gauche. Ainsi il suffit d’examiner le premier token de la liste des tokens restants pour savoir quelle règle appliquer.

    Bien entendu cette grammaire n’est qu’une représentation abstraite, il faut la traduire en code JavaScript. Chacune des règles va devenir une fonction. Ces fonctions se partagent la liste de tokens produite par le lexeur, et renvoient des résultats intermédiaires.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    var rules = {
      expression: function (tokens) {
        var
          result = rules.level1Expression(tokens),
          token = tokens[0];
     
        while (token && 'ADDSUB' === token.type) {
          tokens.shift(); // consomme le token
     
          switch (token.data) {
            case '+':
              result += rules.level1Expression(tokens);
              break;
            case '-':
              result -= rules.level1Expression(tokens);
              break;
          }
     
          token = tokens[0];
        }
        return result;
      },
     
      level1Expression: function (tokens) {
          var
            result = rules.level2Expression(tokens),
            token = tokens[0];
     
          while (token && 'MULDIV' === token.type) {
            tokens.shift();
     
            switch (token.data) {
              case '*':
                result *= rules.level2Expression(tokens);
                break;
              case '/':
                result /= rules.level2Expression(tokens);
                break;
            }
     
            token = tokens[0];
          }
          return result;
      },
     
      level2Expression: function (tokens) {
        var
          token = tokens[0],
          result;
     
        if (!token) {
          throwError('level2Expression', null, 'LEFTPAR, FUNC, NUM ou CONST');
        }
     
        switch (token.type) {
          case 'LEFTPAR':
            tokens.shift();
            result = rules.expression(tokens);
            if (!tokens[0] || 'RIGHTPAR' !== tokens[0].type) {
              throwError('level2Expression', tokens[0], 'RIGHTPAR');
            }
            tokens.shift();
            break;
     
          case 'FUNC':
            result = rules.functionCall(tokens);
            break;
     
          case 'NUM':
          case 'CONST':
            result = rules.primitive(tokens);
            break;
     
          default:
            throwError('level2Expression', token, 'LEFTPAR, FUNC, NUM ou CONST');
        }
     
        return result;
      },
     
      functionCall: function (tokens) {
        var
          funcToken,
          func,
          exprResult;
     
        if (!tokens[0] || 'FUNC' !== tokens[0].type) {
          throwError('functionCall', tokens[0], 'FUNC');
        }
        funcToken = tokens.shift();
     
        func = function (x) {
          return Math[funcToken.data.toLowerCase()](x);
        };
     
        if (!tokens[0] || 'LEFTPAR' !== tokens[0].type) {
          throwError('functionCall', tokens[0], 'LEFTPAR');
        }
        tokens.shift();
     
        exprResult = rules.expression(tokens);
     
        if (!tokens[0] || 'RIGHTPAR' !== tokens[0].type) {
          throwError('functionCall', tokens[0], 'RIGHTPAR');
        }
        tokens.shift();
     
        return func(exprResult);
      },
     
      primitive: function (tokens) {
        var
          result,
          token = tokens[0];
     
        if (!token) {
          throwError('primitive', token, 'NUM ou CONST');
        }
        switch (token.type) {
          case 'NUM':
            tokens.shift();
            result =  parseFloat(token.data.replace(',', '.'));
            break;
     
          case 'CONST':
            tokens.shift();
            switch (token.data.toLowerCase()) {
              case 'g':
                result = 9.81;
                break;
              case 'pi':
                result = Math.PI;
                break;
              default:
                throw new Error('Constante ' + token.data + ' non implémentée');
                break;
            }
            break;
     
          default:
            throwError('primitive', token, 'NUM ou CONST');
            break;
        }
     
        return result;
      }
    };
     
    function throwError(ruleName, token, expected) {
      if (token) {
        throw new Error(ruleName + ': token inattendu ' +
          token.type + ' "' + token.data + '"; ' +
          expected + ' était attendu');
      }
      else {
        throw new Error(ruleName + ': plus de tokens; ' +
          expected + ' était attendu');
      }
    }
     
    function parseTokens(tokenList) {
      var result = rules.expression(tokenList);
      if (tokenList.length > 0) {
        throw new Error('Erreur de syntaxe : "' +
          tokenList[0].data + '" inattendu');
      }
      return result;
    }
     
    function parse(text) {
      var tokens = lex(text);
      return parseTokens(tokens);
    }
    On est obligé d’écrire beaucoup de « code défensif » car il faut prévoir toutes les erreurs que l’utilisateur peut commettre. C’est pour ça que j’ai mis des throw partout.

    Je te laisse quelques petits détails en exercice :
    • les fonctions à plusieurs arguments (par exemple pow) ;
    • le - unaire pour pouvoir écrire -PI ;
    • faire en sorte que les calculs d’angles se fassent en degrés.


    La FAQ JavaScript – Les cours JavaScript
    Touche F12 = la console → l’outil indispensable pour développer en JavaScript !

  6. #6
    Membre confirmé
    Homme Profil pro
    Analyse système
    Inscrit en
    Mai 2014
    Messages
    388
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Arménie

    Informations professionnelles :
    Activité : Analyse système
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mai 2014
    Messages : 388
    Points : 578
    Points
    578
    Par défaut
    Bonjour,

    Merci beaucoup, Watilin, pour ton code et tes explications. Il me faudra encore un peu de temps pour les comprendre dans leur intégralité. Je continue de les étudier.

    La fonction Math.pow() ne m'étant utile qu'en de rares occasions, je préfère la mettre de coté pour l'instant. J'y reviendrai plus tard (j'ai quelques idées en tête). J'adopte donc la solution de danielhagnoul, consistant à remplacer la virgule par un point.

    Concernant la fonction eval(), je conçois qu'elle est dangereuse si elle est placée dans un fichier en PHP qui ouvre une session et gère une base de données. Prendre des précautions dans ce cas là semble indispensable. Par contre, si elle est placée dans un simple fichier au format HTML qui, une fois envoyé au client, ne communique plus avec le serveur, je ne vois vraiment pas comment mener une attaque.

  7. #7
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 093
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 093
    Points : 6 754
    Points
    6 754
    Par défaut
    Le problème n’est pas d’attaquer ton site, mais d’attaquer tes utilisateurs. Un point d’entrée pour une attaque eval sur ton site, c’est une possibilité de vol d’identifiants. Bien entendu ce n’est pas la seule condition nécessaire, mais ça augmente les risques. Et en sécurité informatique, on n’est jamais trop prudent.

    Si jamais un attaquant se trouve en mesure d’injecter une chaîne de son choix à ton appel eval, il a alors des possibilités quasi infinies. Pour faire remonter des informations sensibles à un serveur distant, il peut utiliser JSONP, une image avec des paramètres GET, une iframe…
    Et pour faire télécharger un exécutable malicieux sur la machine cliente, il peut tenter d’exploiter des failles connues de plugins non mis à jour.

    Je te parlerais bien de Content Security Policy mais je ne connais pas suffisamment bien le sujet pour savoir s’il te permettrait de toujours recourir à eval tout en mitigeant les risques d’injection. Peut-être quelqu’un peut nous en dire plus ?
    La FAQ JavaScript – Les cours JavaScript
    Touche F12 = la console → l’outil indispensable pour développer en JavaScript !

  8. #8
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 093
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 093
    Points : 6 754
    Points
    6 754
    Par défaut
    Après quelques tests il apparaît que l’en-tête :
    Code HTTP : Sélectionner tout - Visualiser dans une fenêtre à part
    Content-Security-Policy: default-src 'self' 'unsafe-eval'
    (voir https://developer.mozilla.org/en-US/...ecurity-Policy)
    permet d’appeler eval localement tout en interdisant le chargement de ressources tierces ('self' désigne le domaine de la page).
    Les ressources injectées sont bloquées par la CSP ; un message apparaît dans la console mais ce n’est pas une erreur interceptable par JavaScript.

    CSP bloque beaucoup de choses, notamment les scripts inline, ce qui inclut les balises <script> contenant directement du code (à l’opposé de ceux qui pointent vers un fichier), mais aussi les attributs d’évènements tels que onclick. Les balises et attributs de style sont aussi concernés.

    Si les trois couches de ton application (contenu / présentation / comportement) sont proprement séparées, tu n’as pas de souci à te faire. Sinon, pour autoriser les balises et attributs inline, tu peux ajouter 'unsafe-inline' à la directive default-src.

    Les scripts et les styles ne sont pas les seuls types de ressource bloqués. default-src est la directive la plus restrictive, elle s’applique à tout type de ressource (actuel ou futur). Si tu ne veux bloquer que les scripts, tu peux la remplacer par [codeinline]script-src[/codeinine], mais sois conscient que les scripts ne sont pas le seul vecteur d’attaque.

    Bref, sans entrer davantage dans les détails, CSP est puissante mais il faut apprendre un peu à l’utiliser, donc c’est à toi de voir
    La FAQ JavaScript – Les cours JavaScript
    Touche F12 = la console → l’outil indispensable pour développer en JavaScript !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [XL-2010] Gestion des erreurs sur une connexion ADODB
    Par Amiral62 dans le forum Macros et VBA Excel
    Réponses: 2
    Dernier message: 22/03/2016, 17h39
  2. Gestion des erreurs sur une sauvegarde
    Par Zak Blayde dans le forum Macros et VBA Excel
    Réponses: 2
    Dernier message: 22/05/2008, 00h22
  3. Gestion des erreurs sur une commande multiple
    Par domiq44 dans le forum Shell et commandes GNU
    Réponses: 5
    Dernier message: 05/10/2006, 15h03
  4. Erreur sur une fonction avec des paramètres
    Par Elois dans le forum PostgreSQL
    Réponses: 2
    Dernier message: 05/05/2004, 21h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo