Je veux lire en toute sécurité une ligne d’un std::istream
. Le stream peut être n’importe quoi, par exemple une connexion sur un serveur Web ou quelque chose traitant des fichiers soumis par des sources inconnues. Il y a beaucoup de réponses qui commencent à faire l’équivalent moral de ce code:
void read(std::istream& in) { std::ssortingng line; if (std::getline(in, line)) { // process the line } }
Étant donné la source de données potentiellement douteuse, l’utilisation du code ci-dessus entraînerait une vulnérabilité: un agent malveillant pourrait lancer une attaque par déni de service contre ce code en utilisant une ligne énorme. Ainsi, je voudrais limiter la longueur de la ligne à une valeur plutôt élevée, disons 4 millions de caractères. Bien que quelques lignes importantes puissent être rencontrées, il n’est pas viable d’allouer un tampon pour chaque fichier et d’utiliser std::istream::getline()
.
Comment limiter la taille maximale de la ligne, idéalement sans trop dénaturer le code et sans allouer de gros morceaux de mémoire au départ?
Vous pouvez écrire votre propre version de std::getline
avec un nombre maximum de caractères en lecture, quelque chose appelé getline_n
ou quelque chose.
#include #include template auto getline_n(std::basic_istream& in, std::basic_ssortingng& str, std::streamsize n) -> decltype(in) { std::ios_base::iostate state = std::ios_base::goodbit; bool extracted = false; const typename std::basic_istream::sentry s(in, true); if(s) { try { str.erase(); typename Traits::int_type ch = in.rdbuf()->sgetc(); for(; ; ch = in.rdbuf()->snextc()) { if(Traits::eq_int_type(ch, Traits::eof())) { // eof spotted, quit state |= std::ios_base::eofbit; break; } else if(str.size() == n) { // maximum number of characters met, quit extracted = true; in.rdbuf()->sbumpc(); break; } else if(str.max_size() <= str.size()) { // string too big state |= std::ios_base::failbit; break; } else { // character valid str += Traits::to_char_type(ch); extracted = true; } } } catch(...) { in.setstate(std::ios_base::badbit); } } if(!extracted) { state |= std::ios_base::failbit; } in.setstate(state); return in; } int main() { std::string s; getline_n(std::cin, s, 10); // maximum of 10 characters std::cout << s << '\n'; }
Peut-être trop exagéré.
Il existe déjà une fonction getline
tant que fonction membre de istream
, il vous suffit de l’envelopper pour la gestion des tampons.
#include #include #include // ptrdiff_t #include // std::ssortingng, std::char_traits typedef ptrdiff_t Size; namespace my { using std::istream; using std::ssortingng; using std::char_traits; istream& getline( istream& stream, ssortingng& s, Size const buf_size, char const delimiter = '\n' ) { s.resize( buf_size ); assert( s.size() > 1 ); stream.getline( &s[0], buf_size, delimiter ); if( !stream.fail() ) { Size const n = char_traits::length( &s[0] ); s.resize( n ); // Downsizing. } return stream; } } // namespace my
Remplacez std :: getline en créant un wrapper autour de std :: istream :: getline :
std::istream& my::getline( std::istream& is, std::streamsize n, std::ssortingng& str, char delim ) { try { str.resize(n); is.getline(&str[0],n,delim); str.resize(is.gcount()); return is; } catch(...) { str.resize(0); throw; } }
Si vous souhaitez éviter des allocations de mémoire temporaires excessives, vous pouvez utiliser une boucle qui augmente l’allocation selon vos besoins (dont la taille doublera probablement à chaque passage). N’oubliez pas que les exceptions peuvent ou non être activées sur l’object istream.
Voici une version avec la stratégie d’allocation la plus efficace:
std::istream& my::getline( std::istream& is, std::streamsize n, std::ssortingng& str, char delim ) { std::streamsize base=0; do { try { is.clear(); std::streamsize chunk=std::min(n-base,std::max(static_cast(2),base)); if ( chunk == 0 ) break; str.resize(base+chunk); is.getline(&str[base],chunk,delim); } catch( std::ios_base::failure ) { if ( !is.gcount () ) str.resize(0), throw; } base += is.gcount(); } while ( is.fail() && is.gcount() ); str.resize(base); return is; }
Sur la base des commentaires et des réponses, il semble y avoir trois approches:
getline()
en utilisant éventuellement le membre std::istream::getline()
interne pour obtenir les caractères réels. std::ssortingng
, utilisez une instanciation de chaîne avec un allocateur personnalisé limitant la quantité de mémoire stockée dans la chaîne. Toutes les suggestions ne sont pas accompagnées de code. Cette réponse fournit un code pour toutes les approches et un peu de discussion sur les trois approches. Avant d’entrer dans les détails de la mise en œuvre, il est important de souligner qu’il existe de multiples choix de ce qui devrait arriver si un apport excessivement long est reçu:
std::ios_base::failbit
et / ou std::ios_base::bad_bit
) et, comme la lecture a échoué, générer une chaîne vide. Si vous cédez une chaîne vide, évitez de regarder la chaîne lue jusqu’à présent pour voir ce qui se passe. Bien qu’il existe déjà plusieurs exemples de code implémentant une version limitée de getline()
, en voici une autre! Je pense que c’est plus simple (mais peut-être plus lent; les performances peuvent être traitées si nécessaire), ce qui conserve également l’interface std::getline()
: il utilise la width()
du stream width()
pour communiquer une limite (prendre en compte width()
une extension raisonnable de std::getline()
):
template std::basic_istream& safe_getline(std::basic_istream& in, std::basic_ssortingng& value, cT delim) { typedef std::basic_ssortingng ssortingng_type; typedef typename ssortingng_type::size_type size_type; typename std::basic_istream::sentry cerberos(in); if (cerberos) { value.clear(); size_type width(in.width(0)); if (width == 0) { width = std::numeric_limits::max(); } std::istreambuf_iterator it(in), end; for (; value.size() != width && it != end; ++it) { if (!Traits::eq(delim, *it)) { value.push_back(*it); } else { ++it; break; } } if (value.size() == width) { in.setstate(std::ios_base::failbit); } } return in; }
Cette version de getline()
est utilisée comme std::getline()
mais quand il semble raisonnable de limiter la quantité de données lue, la width()
est définie, par exemple:
std::ssortingng line; if (safe_getline(in >> std::setw(max_characters), line)) { // do something with the input }
Une autre approche consiste à utiliser un tampon de stream de filtrage pour limiter la quantité d’entrée: le filtre comptera simplement le nombre de caractères traités et limitera la quantité à un nombre approprié de caractères. Cette approche est en fait plus facile à appliquer à un stream entier qu’à une ligne individuelle: lors du traitement d’une seule ligne, le filtre ne peut pas simplement obtenir des tampons pleins de caractères du stream sous-jacent, car il n’existe aucun moyen fiable de remettre les caractères. L’implémentation d’une version non tamponnée rest simple mais probablement pas particulièrement efficace:
template > class basic_limitbuf : std::basic_streambuf { public: typedef Traits traits_type; typedef typename Traits::int_type int_type; private: std::streamsize size; std::streamsize max; std::basic_istream* stream; std::basic_streambuf* sbuf; int_type underflow() { if (this->size < this->max) { return this->sbuf->sgetc(); } else { this->stream->setstate(std::ios_base::failbit); return traits_type::eof(); } } int_type uflow() { if (this->size < this->max) { ++this->size; return this->sbuf->sbumpc(); } else { this->stream->setstate(std::ios_base::failbit); return traits_type::eof(); } } public: basic_limitbuf(std::streamsize max, std::basic_istream& stream) : size() , max(max) , stream(&stream) , sbuf(this->stream->rdbuf(this)) { } ~basic_limitbuf() { std::ios_base::iostate state = this->stream->rdstate(); this->stream->rdbuf(this->sbuf); this->stream->setstate(state); } };
Ce tampon de stream est déjà configuré pour s’insérer lors de la construction et se retirer lors de la destruction. Autrement dit, il peut être utilisé simplement comme ceci:
std::ssortingng line; basic_limitbuf sbuf(max_characters, in); if (std::getline(in, line)) { // do something with the input }
Il serait également facile d’append un manipulateur définissant la limite. Un avantage de cette approche est qu’aucun code de lecture ne doit être touché si la taille totale du stream peut être limitée: le filtre peut être configuré juste après la création du stream. Lorsque le filtre n’est pas nécessaire, le filtre peut également utiliser un tampon qui améliore considérablement les performances.
La troisième approche proposée consiste à utiliser un std::basic_ssortingng
avec un allocateur personnalisé. L’approche allocateur comporte deux aspects:
std::ssortingng
(bien qu’il ne soit pas difficile de faire la conversion). Voici le code nécessaire pour un allocateur limitant la taille allouée:
template struct limit_alloc { private: std::size_t max_; public: typedef T value_type; limit_alloc(std::size_t max): max_(max) {} template limit_alloc(limit_alloc const& other): max_(other.max()) {} std::size_t max() const { return this->max_; } T* allocate(std::size_t size) { return size <= max_ ? static_cast(operator new[](size)) : throw std::bad_alloc(); } void deallocate(void* ptr, std::size_t) { return operator delete[](ptr); } }; template bool operator== (limit_alloc const& a0, limit_alloc const& a1) { return a0.max() == a1.max(); } template bool operator!= (limit_alloc const& a0, limit_alloc const& a1) { return !(a0 == a1); }
L’allocateur serait utilisé quelque chose comme ceci (le code comstack OK avec une version récente de clang mais pas avec gcc ):
std::basic_ssortingng, limit_alloc > tmp(limit_alloc (max_chars)); if (std::getline(in, tmp)) { std::ssortingng(tmp.begin(), tmp.end()); // do something with the input }
En résumé, il existe des approches multiples, chacune avec son petit inconvénient, mais chacune étant raisonnablement viable dans le but déclaré de limiter les attaques par déni de service basées sur des lignes trop longues:
getline()
signifie que le code de lecture doit être modifié.