web-dev-qa-db-fra.com

Commentaire analyseur $ QUERY_STRING à partir d'un script CGI bash

J'ai un script bash qui est utilisé dans un CGI. Le CGI définit la variable d'environnement $ QUERY_STRING en lisant tout ce qui suit le ? dans l'URL. Par exemple, http://example.com?a=123&b=456&c=ok sets QUERY_STRING=a=123&b=456&c=ok.

Quelque part j'ai trouvé la laideur suivante:

b=$(echo "$QUERY_STRING" | sed -n 's/^.*b=\([^&]*\).*$/\1/p' | sed "s/%20/ /g")

qui définira $ b sur tout ce qui a été trouvé dans $ QUERY_STRING pour b. Cependant, mon script a grandi pour avoir plus de dix paramètres d'entrée. Existe-t-il un moyen plus simple de convertir automatiquement les paramètres de $ QUERY_STRING en variables d’environnement utilisables par bash?

J'utiliserai peut-être simplement une boucle for, mais ce serait encore mieux si le script était suffisamment intelligent pour détecter automatiquement chaque paramètre et créer un tableau ressemblant à ceci:

 $ {parm [a]} = 123 
 $ {parm [b]} = 456 
 $ {parm [c]} = ok 

Comment pourrais-je écrire du code pour faire ça?

20
User1

Essaye ça:

saveIFS=$IFS
IFS='=&'
parm=($QUERY_STRING)
IFS=$saveIFS

Maintenant vous avez ceci:

parm[0]=a
parm[1]=123
parm[2]=b
parm[3]=456
parm[4]=c
parm[5]=ok

Dans Bash 4, qui a des tableaux associatifs, vous pouvez le faire (en utilisant le tableau créé ci-dessus):

declare -A array
for ((i=0; i<${#parm[@]}; i+=2))
do
    array[${parm[i]}]=${parm[i+1]}
done

qui vous donnera ceci:

array[a]=123
array[b]=456
array[c]=ok

Modifier:

Pour utiliser l'indirection dans Bash 2 et versions ultérieures (à l'aide du tableau parm créé ci-dessus): 

for ((i=0; i<${#parm[@]}; i+=2))
do
    declare var_${parm[i]}=${parm[i+1]}
done

Ensuite, vous aurez:

var_a=123
var_b=456
var_c=ok

Vous pouvez y accéder directement:

echo $var_a

ou indirectement:

for p in a b c
do
    name="var$p"
    echo ${!name}
done

Si possible, il vaut mieux éviter d'éviter l'indirection car cela peut rendre le code compliqué et être une source de bogues.

37
Dennis Williamson

vous pouvez décomposer $QUERY avec IFS. Par exemple, définissez-le sur &

$ QUERY="a=123&b=456&c=ok"
$ echo $QUERY
a=123&b=456&c=ok
$ IFS="&"
$ set -- $QUERY
$ echo $1
a=123
$ echo $2
b=456
$ echo $3
c=ok

$ array=($@)

$ for i in "${array[@]}"; do IFS="=" ; set -- $i; echo $1 $2; done
a 123
b 456
c ok

Et vous pouvez enregistrer dans un hash/dictionnaire dans Bash 4+

$ declare -A hash
$ for i in "${array[@]}"; do IFS="=" ; set -- $i; hash[$1]=$2; done
$ echo ${hash["b"]}
456
14
ghostdog74

Pour convertir le contenu de QUERY_STRING en variables bash, utilisez la commande suivante:

eval $(echo ${QUERY_STRING//&/;})

L'étape interne, echo ${QUERY_STRING//&/;}, remplace toutes les esperluettes par des points-virgules générant a = 123; b = 456; c = ok, que la variable eval évalue dans le shell actuel.

Le résultat peut ensuite être utilisé comme variable bash.

echo $a
echo $b
echo $c

Les hypothèses sont les suivantes:

  • les valeurs ne contiendront jamais '&'
  • les valeurs ne contiendront jamais ';'
  • QUERY_STRING ne contiendra jamais de code malveillant
3
Tai Paul

S'il vous plaît ne pas utiliser le mal eval maléfique.

Voici comment analyser de manière fiable la chaîne et obtenir un tableau associatif:

declare -A param   
while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do
    param["$key"]=$value
done <<<"${QUERY_STRING}&"

Si vous n'aimez pas la vérification des clés, vous pouvez le faire à la place:

declare -A param   
while IFS='=' read -r -d '&' key value; do
    param["$key"]=$value
done <<<"${QUERY_STRING:+"${QUERY_STRING}&"}"

Liste de toutes les clés et valeurs du tableau:

for key in "${!param[@]}"; do
    echo "$key: ${param[$key]}"
done
3
bolt

J'ai intégré la commande sed dans un autre script:

$ cat getvar.sh

s='s/^.*'${1}'=\([^&]*\).*$/\1/p'
echo $QUERY_STRING | sed -n $s | sed "s/%20/ /g"

et je l'appelle depuis mon cgi principal en tant que:

id=`./getvar.sh id`
ds=`./getvar.sh ds`
dt=`./getvar.sh dt`

... etc, etc - vous avez une idée.

fonctionne pour moi même avec un appareil busybox très basique (mon PVR dans ce cas).

2
Simon

Je voudrais simplement remplacer le & to; Cela deviendra quelque chose comme:

a=123;b=456;c=ok

Alors maintenant, il vous suffit d’évaluer et de lire vos vars:

eval `echo "${QUERY_STRING}"|tr '&' ';'`
echo $a
echo $b
echo $c
1
Petras L

Une bonne façon de gérer les chaînes de requête CGI consiste à utiliser Haserl , qui encapsule votre script Bash cgi et offre une analyse pratique et sécurisée des chaînes de requête.

1
user3292713

@giacecco

Pour inclure un trait d'union dans l'expression régulière, vous pouvez modifier les deux lignes comme telles dans la réponse de @starfry.

Changer ces deux lignes:

  local re1='^(\w+=\w+)&?'
  local re2='^(\w+)=(\w+)$'

À ces deux lignes:

  local re1='^(\w+=(\w+|-|)+)&?'
  local re2='^(\w+)=((\w+|-|)+)$'
0
L. Nozot

pourquoi pas ça

    $ echo "${QUERY_STRING}"
    name=carlo&last=lanza&city=pfungen-CH
    $ saveIFS=$IFS
    $ IFS='&'
    $ eval $QUERY_STRING
    $ IFS=$saveIFS

maintenant vous avez ceci

    name = carlo
    last = lanza
    city = pfungen-CH

    $ echo "name is ${name}"
    name is carlo
    $ echo "last is ${last}"
    last is lanza
    $ echo "city is ${city}"
    city is pfungen-CH
0
Carlisto

Après la bonne réponse, je me suis moi-même apporté quelques modifications pour prendre en charge les variables de tableau, comme dans cette autre question . J'ai ajouté aussi une fonction de décodage dont je ne trouve pas l'auteur pour donner du crédit. 

Le code semble un peu brouillon, mais cela fonctionne. Des changements et autres recommandations seraient grandement appréciés. 

function cgi_decodevar() {
    [ $# -ne 1 ] && return
    local v t h
    # replace all + with whitespace and append %%
    t="${1//+/ }%%"
    while [ ${#t} -gt 0 -a "${t}" != "%" ]; do
        v="${v}${t%%\%*}" # digest up to the first %
        t="${t#*%}"       # remove digested part
        # decode if there is anything to decode and if not at end of string
        if [ ${#t} -gt 0 -a "${t}" != "%" ]; then
            h=${t:0:2} # save first two chars
            t="${t:2}" # remove these
            v="${v}"`echo -e \\\\x${h}` # convert hex to special char
        fi
    done
    # return decoded string
    echo "${v}"
    return
}

saveIFS=$IFS
IFS='=&'
VARS=($QUERY_STRING)
IFS=$saveIFS

for ((i=0; i<${#VARS[@]}; i+=2))
do
  curr="$(cgi_decodevar ${VARS[i]})"
  next="$(cgi_decodevar ${VARS[i+2]})"
  prev="$(cgi_decodevar ${VARS[i-2]})"
  value="$(cgi_decodevar ${VARS[i+1]})"

  array=${curr%"[]"}

  if  [ "$curr" == "$next" ] && [ "$curr" != "$prev" ] ;then
      j=0
      declare var_${array}[$j]="$value"
  Elif [ $i -gt 1 ] && [ "$curr" == "$prev" ]; then
    j=$((j + 1))
    declare var_${array}[$j]="$value"
  else
    declare var_$curr="$value"
  fi
done
0
badc0de

Pour tous ceux qui ne pouvaient pas le faire fonctionner avec les réponses affichées (comme moi), ce gars a compris.

Je ne peux malheureusement pas voter son message ...

Laissez-moi republier le code ici très rapidement:

#!/bin/sh

if [ "$REQUEST_METHOD" = "POST" ]; then
  if [ "$CONTENT_LENGTH" -gt 0 ]; then
      read -n $CONTENT_LENGTH POST_DATA <&0
  fi
fi

#echo "$POST_DATA" > data.bin
IFS='=&'
set -- $POST_DATA

#2- Value1
#4- Value2
#6- Value3
#8- Value4

echo $2 $4 $6 $8

echo "Content-type: text/html"
echo ""
echo "<html><head><title>Saved</title></head><body>"
echo "Data received: $POST_DATA"
echo "</body></html>"

J'espère que cela peut aider n'importe qui.

À votre santé

0
kindaleek

Pour mettre à jour cela, si vous avez une version récente de Bash, vous pouvez y parvenir avec des expressions régulières:

q="$QUERY_STRING"
re1='^(\w+=\w+)&?'
re2='^(\w+)=(\w+)$'
declare -A params
while [[ $q =~ $re1 ]]; do
  q=${q##*${BASH_REMATCH[0]}}       
  [[ ${BASH_REMATCH[1]} =~ $re2 ]] && params+=([${BASH_REMATCH[1]}]=${BASH_REMATCH[2]})
done

Si vous ne souhaitez pas utiliser de tableaux associatifs, modifiez simplement l'avant-dernière ligne pour faire ce que vous voulez. Pour chaque itération de la boucle, le paramètre est dans ${BASH_REMATCH[1]} et sa valeur est dans ${BASH_REMATCH[2]}.

Voici la même chose qu'une fonction dans un script de test court qui effectue une itération sur le tableau et génère les paramètres de la chaîne de requête et leurs valeurs.

#!/bin/bash
QUERY_STRING='foo=hello&bar=there&baz=freddy'

get_query_string() {
  local q="$QUERY_STRING"
  local re1='^(\w+=\w+)&?'
  local re2='^(\w+)=(\w+)$'
  while [[ $q =~ $re1 ]]; do
    q=${q##*${BASH_REMATCH[0]}}
    [[ ${BASH_REMATCH[1]} =~ $re2 ]] && eval "$1+=([${BASH_REMATCH[1]}]=${BASH_REMATCH[2]})"
  done
}

declare -A params
get_query_string params

for k in "${!params[@]}"
do
  v="${params[$k]}"
  echo "$k : $v"
done          

Notez que les paramètres se retrouvent dans le tableau dans l'ordre inverse (c'est associatif, cela ne devrait donc pas avoir d'importance).

0
starfry

Bien que la réponse acceptée soit probablement la plus belle, il peut arriver que la sécurité soit extrêmement importante et qu'elle soit également bien visible depuis votre script.

Dans un tel cas, d’abord, je n’utiliserais pas bash pour la tâche, mais si cela devait être fait pour une raison quelconque, il serait peut-être préférable d’éviter ces nouvelles fonctions tableau - dictionnaire, car vous ne pouvez pas être sûr de leur exactitude. Ils se sont échappés.

Dans ce cas, les bonnes vieilles solutions primitives pourraient fonctionner:

QS="${QUERY_STRING}"
while [ "${QS}" != "" ]
do
  nameval="${QS%%&*}"
  QS="${QS#$nameval}"
  QS="${QS#&}"
  name="${nameval%%=*}"
  val="${nameval#$name}"
  val="${nameval#=}"

  # and here we have $name and $val as names and values

  # ...

done

Ceci itère sur les paires nom-valeur du QUERY_STRING, et il n'y a aucun moyen de le contourner avec une séquence d'échappement délicate - le " est très puissant dans bash, à l'exception d'une seule substitution de nom de variable, que nous contrôlons entièrement, rien ne peut être trompé.

De plus, vous pouvez injecter votre propre code de traitement dans "# ...". Cela vous permet de n'autoriser que votre propre liste bien définie (et, idéalement, courte) des noms de variables autorisés. Inutile de dire que LD_PRELOAD ne devrait pas en faire partie. ;-)

De plus, aucune variable ne sera exportée, et exclusivement QS, nameval, name et val est utilisé.

0
peterh