OVH Cloud OVH Cloud

Partage d'une string en deux selon critères

42 réponses
Avatar
TigrouMeow
Bonjour,

Aujourd'hui j'ai une nouvelle question. J'ai un peu cherché et je vois pas
la bonne manière de faire ceci...

Je lis un fichier et j'ai une entrée de ce genre :
12 87
4783 49
1 0
429 452

Il s'agit de récupérer les deux nombres à chaque ligne. En Perl, c'est assez
aisé de le faire en utilisant une expression régulière. En C++, j'imagine
qu'il y a peut-etre une possibilité de faire ça, donc je préfère demander
avant de recoder un parseur.

Je vous remercie de votre aide une nouvelle fois ;)

10 réponses

1 2 3 4 5
Avatar
David
C'est à toi de savoir d'où vient les données.
Et si tu ne sais pas, imagine le pire. En informatique (et surtout en
sécurité informatique), le paranoïaque est encore optimiste.

Du coup, j'aurais tendance à croire que ce genre de source passe
son temps à valider des données...


A valider les données extérieures, oui.



D'un point de vue générale cela peut être sûrement justifié
pour des données en entrées plus dommageable pour le système
( comme des scripts à interpréter ) mais pour ce qui
est d'une simple validité de format de fichier, je
ne vois qu'un simple agrément de confort permettant
d'indiquer une erreur de l'utilisateur.
Je ne connais pas en C++ d'attaque de type buffer overflow
(cela doit bien être de ça dont ton parle, enfin j'espère).


Au moment où tu es sûr que la ligne lue est conforme aux
spécifications.
Je dois dire que les expressions régulières/rationnelles seraient
parfaitement adaptées dans le cas présent : puisqu'on sait qu'il y a
exactement deux nombres entiers positifs sur chaque ligne, et que le
seul caractère possible est un espace, il suffit d'utiliser
l'expression "^ *([0-9]+) +([0-9]+) *$" et le tour est joué : soit la
ligne est conforme à cette expression, et les nombres sont 1 et 2,
soit la ligne n'est pas conforme, et le programme râle.


Les regexp ne sont pas (encore) intégrées au langage
et je trouve cette solution disproportionnée par rapport
à la simplicité du fichier d'entrée ou du risque de sécurité (aucun IMHO).
Le problème se compliquant nettement pour des réels en notation
scientifique par exemple.

Au mieux, cela donnerait une indication à l'utilisateur
que le fichier en entrée n'est pas valide. Je ne suis pas
sûr que dans un cas aussi simple cele soit même utile (deux nombres
par ligne).
Mais pourquoi faire à côté un programme validant le fichier
pour un utilisateur ne voyant pas le résultat voulu.

egrep -vn "^ *([0-9]+) +([0-9]+) *" nombres.txt

(Je n'ai pas réussi à faire fonctionner le $, il faudrait le mettre)

C'est différent : les données fournies à strcpy sont fournies par le
programme, pas lues depuis un fichier externe. Donc la fonction qui
appelle strcpy() doit faire les vérifications qu'elle juge utiles.


Je ne crois pas que je soit d'accord.
Je ne vois pas de différence, la validité des données
pourraient vérifiées aussi bien dans ce cas.

Si un programmeur utilise ma librairie, j'ai pas de raison
de faire confiance aux données qu'il passera en paramètre
de ma fonction, pas plus qu'un fichier que je lirais d'un flux
d'entrée.

S'il est plus facile de faire un programme fiable en C++, ce langage
n'est pas sans ses pièges et comportements indéfinis. Une classe de
base sans destructeur virtuel vaut bien un char* utilisé sans rigueur.


A part une fuite de mémoire, que risque t-on ?
std::string n'a pas de destructeur virtuelle et on s'en sort ;-)

Même sans attaques, si l'entrée n'est pas conforme aux spécifications,
ton programme doit l'indiquer, ne serait-ce que pour pouvoir déboguer
le programme qui crée cette entrée.


Ca en effet, mais ça reste plus de la politesse.


David.


Avatar
kanze
David wrote in message
news:<416ad8ea$0$555$...

Il ne faut jamais faire confiance aux données entrantes,


Jamais ?


Jamais.

Pour moi, ça dépend beaucoup d'où viennent les données entrantes.


Tout à fait. Et puisque tu ne sais jamais d'où elle viennent...

En l'occurence ici, ce qui semble être un simple exercice, il ne doit
pas y avoir de trop grand danger.


Danger, ça dépend de quoi. « Garbage in, garbage out », comme on dit. Si
le fichier d'entrée n'est pas du bon format, c'est qu'il est erroné.
Garbage, donc. Un bon programme le reconnaît, et signale l'erreur.

pour éviter tout abus. Une grande partie des failles de sécurité
viennent justement du manque de rigueur dans la vérification de
l'input.


Il y a vraiment une faille de sécurite dans ce code ?
int v1, v2;
while( file >> v1 >> v2 )
;

En effet, comme le souligne drkm, il semble délicat de valider le
format en entrée du fichier mais à part ça.


Faille de sécurité, ça dépend de ce que fait le programme. Il y a une
bonne probabilité qu'il donne de mauvais résultats.

Je ne suis pas spécialiste de le programmation en milieu 'hostile'


Tu ne programmes pas sous Windows, in sur un système Unix ? Tu ne lis
jamais de fichiers ?

mais je ne suis pas sûr qu'il soit facile de trouver un fichier en
entrée permettant de prendre le contrôle de la machine ou autre.


C'est déjà passé.

Autant en C, il faut faire plus attention, autant en C++ je suis
beaucoup plus souple, les outils de la librairie C++ faisant le plus
gros du travail.


À vrai dire, à part une meilleur gestion des types, je ne vois pas la
différence. On a même réinventer gets (>> vers un char*).

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Fabien LE LEZ wrote in message
news:...
On Mon, 11 Oct 2004 22:23:55 +0200, David :

Je dois dire que les expressions régulières/rationnelles seraient
parfaitement adaptées dans le cas présent : puisqu'on sait qu'il y a
exactement deux nombres entiers positifs sur chaque ligne, et que le
seul caractère possible est un espace, il suffit d'utiliser
l'expression "^ *([0-9]+) +([0-9]+) *$" et le tour est joué : soit la
ligne est conforme à cette expression, et les nombres sont 1 et 2,
soit la ligne n'est pas conforme, et le programme râle.


Dans ce cas-ci, et vue qu'on va avoir à rélire la ligne pour en extraire
les valeurs de toute façon, une expression régulière me semble un peu de
trop. Je me contenterais de :

std::istringstream s( line ) ;
line >> i >> j >> std::ws ;
if ( ! line || line.peek() != EOF ) {
// erreur...
}

[...]
Même sans attaques, si l'entrée n'est pas conforme aux spécifications,
ton programme doit l'indiquer, ne serait-ce que pour pouvoir déboguer
le programme qui crée cette entrée.


En fait, si quelque part il y a une ligne qui manque un nombre, si tu ne
vérifies pas ligne par ligne, tu ne détectes l'erreur qu'à la fin.

La question fondamentale, c'est qu'est-ce qu'on va faire avec les
erreurs qu'on détecte. Mon expérience suggère qu'on va afficher
l'erreur. Avec son numéro de ligne, pour que l'utilisateur peut le
rétrouver facilement. Dans ce cas-là, la lecture ligne par ligne est
tout à fait indiquée, pour faciliter aussi le comptage des lignes.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Fabien LE LEZ
On Tue, 12 Oct 2004 08:24:34 +0200, David :

mais pour ce qui
est d'une simple validité de format de fichier, je
ne vois qu'un simple agrément de confort permettant
d'indiquer une erreur de l'utilisateur.


Je ne suis pas d'accord. On ne sait pas ce qui sera fait des données
en question.


--
;-)

Avatar
David
std::istringstream s( line ) ;
line >> i >> j >> std::ws ;


Je connaissais pas l'utilisation de ws comme ça.
Intéressant.

if ( ! line || line.peek() != EOF ) {


Pas sûr du line.peek(), probablement s.peek().

// erreur...
}

En fait, si quelque part il y a une ligne qui manque un nombre, si tu ne
vérifies pas ligne par ligne, tu ne détectes l'erreur qu'à la fin.

La question fondamentale, c'est qu'est-ce qu'on va faire avec les
erreurs qu'on détecte. Mon expérience suggère qu'on va afficher
l'erreur. Avec son numéro de ligne, pour que l'utilisateur peut le
rétrouver facilement. Dans ce cas-là, la lecture ligne par ligne est
tout à fait indiquée, pour faciliter aussi le comptage des lignes.


Ca parait très raisonnable.
Mon expérience me ferait croire qu'il n'y aurait pas grand monde
pour lire l'erreur afficher si le traitement n'indique pas
une erreur claire (genre return != 0 dans le main).

Avatar
drkm
David writes:

[...]

pour ce qui
est d'une simple validité de format de fichier, je
ne vois qu'un simple agrément de confort permettant
d'indiquer une erreur de l'utilisateur.


Et d'abord de savoir soi-même que le format n'est pas valide, et
entreprendre alors l'action qui va bien.

[...]

C'est différent : les données fournies à strcpy sont fournies par le
programme, pas lues depuis un fichier externe. Donc la fonction qui
appelle strcpy() doit faire les vérifications qu'elle juge utiles.


Je ne crois pas que je soit d'accord.
Je ne vois pas de différence, la validité des données
pourraient vérifiées aussi bien dans ce cas.

Si un programmeur utilise ma librairie, j'ai pas de raison
de faire confiance aux données qu'il passera en paramètre
de ma fonction, pas plus qu'un fichier que je lirais d'un flux
d'entrée.


La situation est tout de même différente. Une fonction de
bibliothèque peut se décharger de responsabiltés sur son utilisateur.
Si ce dernier ne remplit pas le contrat d'utilisation, tant pis pour
lui.

Mais je vois mal un programme faire la même chose : « il y a moyen
d'obtenir un buffer overflow, mais j'indique dans mes préconditions
que vous ne pouvez pas utiliser ce type d'entrée, donc tout va bien ».

--drkm


Avatar
David
Il ne faut jamais faire confiance aux données entrantes,
Jamais ?

Jamais.



Ma confiance me perdra ;-)

Danger, ça dépend de quoi. « Garbage in, garbage out », comme on dit. Si
le fichier d'entrée n'est pas du bon format, c'est qu'il est erroné.
Garbage, donc. Un bon programme le reconnaît, et signale l'erreur.


Le "Garbage In, Garbage Out" me va bien. Si l'utilsateur
n'est pas capable de fournir un fichier en entrée, pourquoi
serait-il capable de comprendre le fichier de sortie (qu'il
soit bon ou mauvais) ?

Tu ne programmes pas sous Windows, in sur un système Unix ? Tu ne lis
jamais de fichiers ?


Si, je passe pas mal de temps à ça. Mes les fichiers reçus
ne sont pas là pour faire planter le traitement.

Mais je vérifie les données fonctionnelles, pas le format du fichier.
Si je reçois en fichier posant un pb sur le format de fichier,
je me pose d'abord la question sur le programme qui m'a généré le fichier.
Ce ne sont jamais des fichiers écris à la main (ce qui simplifie
le problème).


À vrai dire, à part une meilleur gestion des types, je ne vois pas la
différence. On a même réinventer gets (>> vers un char*).


l'équivalent d'un :

std::string line;
while( getline( file, line ) )
;

en C est vraiment hors de portée d'un simple débutant.

David.



Avatar
David

La situation est tout de même différente


Pas d'accord. Je donne une raison dans une réponse précédente.
IL n'y a pas moyen de savoir d'où vient le paramètre en entrée.

Une fonction de
bibliothèque peut se décharger de responsabiltés sur son utilisateur.
Si ce dernier ne remplit pas le contrat d'utilisation, tant pis pour
lui.

Mais je vois mal un programme faire la même chose : « il y a moyen
d'obtenir un buffer overflow, mais j'indique dans mes préconditions
que vous ne pouvez pas utiliser ce type d'entrée, donc tout va bien ».



Sur cet exemple, je n'ai pas eu un exemple de buffer overflow.

David.

Avatar
drkm
David writes:


Une fonction de
bibliothèque peut se décharger de responsabiltés sur son utilisateur.
Si ce dernier ne remplit pas le contrat d'utilisation, tant pis pour
lui.

Mais je vois mal un programme faire la même chose : « il y a moyen
d'obtenir un buffer overflow, mais j'indique dans mes préconditions
que vous ne pouvez pas utiliser ce type d'entrée, donc tout va bien ».


Sur cet exemple, je n'ai pas eu un exemple de buffer overflow.


Je me plaçais à un niveau plus général. Je reformule : « il y a
moyen d'obtenir un résultat erroné (et l'on ne sait jamais toutes les
conséquences que cela pourra avoir), mais j'indique dans mes
préconditions que vous ne pouvez pas utiliser ce type d'entrée, donc
tout va bien ».

--drkm


Avatar
kanze
David wrote in message
news:<416c1af6$0$28953$...
Il ne faut jamais faire confiance aux données entrantes,
Jamais ?

Jamais.



Ma confiance me perdra ;-)

Danger, ça dépend de quoi. « Garbage in, garbage out », comme on
dit. Si le fichier d'entrée n'est pas du bon format, c'est qu'il est
erroné. Garbage, donc. Un bon programme le reconnaît, et signale
l'erreur.


Le "Garbage In, Garbage Out" me va bien. Si l'utilsateur n'est pas
capable de fournir un fichier en entrée, pourquoi serait-il capable de
comprendre le fichier de sortie (qu'il soit bon ou mauvais) ?


Je suppose que tu serais content aussi avec un compilateur C++ qui ne
signalait aucune erreur, mais générait simplement du mauvais code si ton
programme avait une erreur.

Errare humanae est, comme on dit. Tu ne vas jamais pouvoir attraper
toutes les erreurs -- si l'utilisateur a tapé 110, quand il voulait
taper 10, et les deux valeurs sont raisonables, il n'y a rien que tu
puisses faire. Mais en général, plus on detecte d'erreurs possibles, et
plus on les localise, mieux ça vaut. (Tu ne seras probablement pas
content avec un compilateur qui affichait simplement "?", et s'arrêtait,
dès qu'il trouvait une erreur, non plus.)

Il est même souhaitable de définir des formats d'entrées avec un peu de
rédondance, afin de faciliter la détection d'erreurs.

Tu ne programmes pas sous Windows, in sur un système Unix ? Tu ne
lis jamais de fichiers ?


Si, je passe pas mal de temps à ça. Mes les fichiers reçus
ne sont pas là pour faire planter le traitement.


Ce n'est peut-être pas le but, mais beaucoup de fichiers que je lis
sont, ou peuvent être, éditer par des humains. Et puisque errare humanae
est...

Mais je vérifie les données fonctionnelles, pas le format du fichier.
Si je reçois en fichier posant un pb sur le format de fichier, je me
pose d'abord la question sur le programme qui m'a généré le fichier.
Ce ne sont jamais des fichiers écris à la main (ce qui simplifie le
problème).


C'est vrai que on peut être plus souple avec les fichiers générés par un
programme. Et plus rigueureux, aussi -- un programme ne va pas insérer
un nombre aléatoire de blancs comme séparateur, par exemple. Une
vérification est quand même intéressant, puisqu'on a pû te donner de
mauvais fichier. En revanche, dans ce cas-là, un message simple sans
localisation, etc., peut suffire.

À vrai dire, à part une meilleur gestion des types, je ne vois pas
la différence. On a même réinventer gets (>> vers un char*).


l'équivalent d'un :

std::string line;
while( getline( file, line ) )
;

en C est vraiment hors de portée d'un simple débutant.


Pas vraiment. fgets marche assez bien. (Évidemment, il faut limiter la
longueur des lignes. Mais dans la pratique, ce n'est pas forcement un
problème.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34




1 2 3 4 5