filter.py 8.08 KB
Newer Older
1 2
#/usr/bin/env python
# -*- coding: utf8 -*-
3 4 5 6 7 8 9 10 11
#
# filter.py --- Workaround with ldap filters
#
# Redraw filters like attr1=val1&attr2=val2|attr3=val3
# in (|(&(attr1=val1)(attr2=val2))(attr3=val3))
#
# Copyright (C) 2010-2013 Cr@ns <roots@crans.org>
# Author: Pierre-Elliott Bécue <becue@crans.org>
#
12
#
13
# License: WTFPL
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

def human_to_ldap(filtre):
    """
    Transforme les filtres "human readables" en
    filtres respectant la syntaxe LDAP.
    """
    # Piles, quand on croise une parenthèse ouvrante
    # on change de pile
    stacks = {0:''}

    # Position dans la pile
    pos = 0

    # operateur \in {&, |, &|, }
    operateur = ""

    # La pile externe, qu'on merge à la pile en cours quand c'est utile.
    ext_stack = ""

    # Élement de la forme paiement=2012 ou retour de la pile
    # supérieure (quand on croise une parenthèse fermante, la
    # pile est dumpée dans argument)
    # Est systématiquement dumpé dans ext_stack quand on croise
    # un opérateur.
    argument = ""

    # Y a-t-il un ! dans la salle ?
    neg = False

    # Quand on quitte une pile parenthésée, on veut savoir quel était
    # l'opérateur actif avant, pour que le comportement de la fonction
    # soit défini.
    anciens_operateurs = []

    for char in filtre:
        if char == "(":
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
            # Une nouvelle stack ne démarre que si le dernier argument a été dumpé
            # dans l'ext stack (si ce n'est pas le cas quand on respecte la syntaxe
            # des filtres, la fonction est mal codée, donc on plante).
            if argument != "":
                raise ValueError('Argument entammé non terminé !')

            # Un dumpe ext_stack dans la stack en cours.
            stacks[pos] += "%s" % (ext_stack)

            # On augmente le conteur de la stack
            pos = pos + 1

            # On (ré)?initialise la stack
            stacks[pos] = ''

            # On stocke l'opérateur en cours dans la stack inférieure dans
            # une liste.
            anciens_operateurs.append(operateur)

            # On flush operateur
            operateur = ""

            # On flush ext_stack, l'environnement est enfin propre pour
            # bosser dans la nouvelle pile.
            ext_stack = ""

        elif char == ")":
            # Cas classique
            if operateur == "|":
                ext_stack += "(%s)" % argument

            # Moins classique, &| est un opérateur servant à dire qu'on
            # a déjà croisé un | dans la stack en cours, le cas échéant,
            # celui-ci doit être pris en compte, pour respecter la
            # priorité du &. Dans ce cas seulement, une parenthèse
            # est ouverte et non fermée (cf elif char == '&' cas
            # operateur == "|"), on la ferme ici.
            elif operateur == "&|":
                argument += ")"
                ext_stack += "(%s)" % argument

            # Classique
            elif operateur == "&":
                ext_stack += "(%s)" % argument

            # Pas d'opérateur, pas de parenthèse superflue.
            else:
                ext_stack += "%s" % argument

            # On passe la stack en argument, les parenthèses
            # l'encadrant seront placées d'un qu'un opérateur
            # sera croisé.
            argument = "%s%s" % (stacks[pos], ext_stack)

            # Ménage
            stacks[pos] = ""
            ext_stack = ""
            pos = pos - 1

            # Retour à l'opérateur de la stack précédente.
            operateur = anciens_operateurs.pop()

        elif char == "|":
            if not argument:
                raise ValueError('Aucun argument')

            # Ce cas permet d'éviter une répétition de la forme :
            # (|(a)(|(b)(c))), quand on est déjà dans un ou, on
            # rajoute juste l'argument suivant sans remettre de
            # symbole.
            if operateur == "|":
                # neg est True si on a croisé un ! dans la chaîne.
                # À améliorer pour qu'il ne marche que pour !=
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                # Ajout à la stack externe de l'argument
                ext_stack += "(%s)" % argument
                argument = ""
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 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
            elif operateur == "&":
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                ext_stack += "(%s)" % argument

                # | prend le relais sur &, on dumpe ext_stack et on commence une nouvelle
                # chaîne qui sera ajoutée à droite. Le ou va tout à gauche de la stack
                # en cours, pour s'appliquer sur tout son contenu.
                stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack)
                ext_stack = ""
                argument = ""
                operateur = "|"

            # C'est un & dans un |, donc on ferme juste la chaîne
            # des &, d'où la parenthèse fermante en trop.
            elif operateur == "&|":
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                ext_stack += "(%s)" % argument
                argument = ""
                stacks[pos] = "%s%s)" % (stacks[pos], ext_stack)
                ext_stack = ""
                operateur = "|"

            # Pas encore d'opérateur annoncé
            elif operateur == "":
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                ext_stack += "%s" % argument
                argument = ""

                # ouverture
                stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack)
                ext_stack = ""
                operateur = "|"

            else:
                raise TypeError('Erreur d\'opérateur.')

        elif char == "&":
            if not argument:
                raise ValueError('Aucun argument')

            if operateur == "&":
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                ext_stack += "(%s)" % argument
                argument = ""

            # Le cas spécial, on ouvre un & après un |, donc pour respecter
            # la priorité de &, on démarre une nouvelle chaîne, mais dans
            # l'ext_stack (contrairement à char == '|', operateur == "&")
            elif operateur == "|":
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                ext_stack += "(%s(%s)" % (char, argument)
                argument = ""
                operateur = "&|"

            # On était déjà dans un &|...
            elif operateur == "&|":
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                ext_stack += "(%s)" % argument
                argument = ""

            # Comme ci-dessus
            elif operateur == "":
                if neg:
                    argument = "!(%s)" % argument
                    neg = False

                ext_stack += "%s" % argument
                argument = ""

                stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack)
                ext_stack = ""
                operateur = "&"

            else:
                raise TypeError('Erreur d\'opérateur.')

        elif char == "!":
            neg = True

        # Remplissage d'argument
        else:
            argument += char
232

233 234 235
        # Décommenter pour débug.
        # En modifiant un peu, encore plus utile pour savoir ce qu'il
        # fait à chaque étape !
236
        #print stacks
237 238 239 240 241 242 243 244 245
        #print pos, argument, ext_stack, operateur

    if pos > 0:
        raise Exception("Tu ne sais pas parenthéser, crétin.")

    else:
        # Comme parenthèse fermante.
        if neg:
            argument = "!(%s)" % argument
246 247

        # Surtout ça
248 249 250 251 252 253 254 255 256 257
        if operateur == "&|":
            argument += ')'
        ext_stack += "(%s)" % argument

        argument = ""
        stacks[pos] = "(%s%s)" % (stacks[pos], ext_stack)
        ext_stack = ""

    # On retourne la pile de plus haut niveau
    return stacks[0]