我想安全地从std::istream
读取一行。流可以是任何东西,例如,Web服务器上的连接或处理由未知来源提交的文件的东西。有很多答案开始做这个代码的道德等价物:
void read(std::istream& in) {
std::string line;
if (std::getline(in, line)) {
// process the line
}
}
鉴于可能可疑的in
来源,使用上述代码会导致漏洞:恶意代理可能会使用巨大的线路对此代码发起拒绝服务攻击。因此,我想将线路长度限制在一个相当高的值,比如4百万char
s。虽然可能会遇到一些大行,但为每个文件分配一个缓冲区并使用std::istream::getline()
是不可行的。
如何限制线路的最大尺寸,理想情况下不会过度扭曲代码并且不预先分配大块内存?
答案 0 :(得分:36)
您可以编写自己的std::getline
版本,其中包含最大字符数读取参数,称为getline_n
或其他内容。
#include <string>
#include <iostream>
template<typename CharT, typename Traits, typename Alloc>
auto getline_n(std::basic_istream<CharT, Traits>& in, std::basic_string<CharT, Traits, Alloc>& str, std::streamsize n) -> decltype(in) {
std::ios_base::iostate state = std::ios_base::goodbit;
bool extracted = false;
const typename std::basic_istream<CharT, Traits>::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';
}
但可能会有点矫枉过正。
答案 1 :(得分:17)
已经存在getline
函数作为istream
的成员函数,您只需要将其包装起来进行缓冲区管理。
#include <assert.h>
#include <istream>
#include <stddef.h> // ptrdiff_t
#include <string> // std::string, std::char_traits
typedef ptrdiff_t Size;
namespace my {
using std::istream;
using std::string;
using std::char_traits;
istream& getline(
istream& stream, string& 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<char>::length( &s[0] );
s.resize( n ); // Downsizing.
}
return stream;
}
} // namespace my
答案 2 :(得分:8)
通过在std::getline周围创建包装来替换std::istream::getline:
std::istream& my::getline( std::istream& is, std::streamsize n, std::string& str, char delim )
{
try
{
str.resize(n);
is.getline(&str[0],n,delim);
str.resize(is.gcount());
return is;
}
catch(...) { str.resize(0); throw; }
}
如果你想避免过多的临时内存分配,你可以使用一个循环来根据需要增加分配(每次传递可能会增加一倍)。不要忘记在istream对象上可能启用或不启用异常。
这是一个具有更高效分配策略的版本:
std::istream& my::getline( std::istream& is, std::streamsize n, std::string& str, char delim )
{
std::streamsize base=0;
do {
try
{
is.clear();
std::streamsize chunk=std::min(n-base,std::max(static_cast<std::streamsize>(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;
}
答案 3 :(得分:5)
根据评论和答案,似乎有三种方法:
getline()
成员编写std::istream::getline()
的自定义版本以获取实际字符。std::string
。并非所有建议都附带代码。这个答案为所有方法提供了代码,并对所有三种方法进行了一些讨论。在进入实施细节之前,首先要指出的是,如果收到过长的输入,会有多种选择:
std::ios_base::failbit
和/或std::ios_base::bad_bit
),并且由于读取失败,因此产生一个空字符串。显然,产生一个空字符串会阻止潜在地查看到目前为止读取的字符串,以便可能看到正在发生的事情。虽然已经有多个代码示例实现了getline()
的限制版本,但这是另一个!我认为它更简单(虽然可能更慢;性能可以在必要时处理),它也保留了std::getline()
的界面:它使用流的width()
来传达限制(可能需要width()
考虑是对std::getline()
)的合理延伸:
template <typename cT, typename Traits, typename Alloc>
std::basic_istream<cT, Traits>&
safe_getline(std::basic_istream<cT, Traits>& in,
std::basic_string<cT, Traits, Alloc>& value,
cT delim)
{
typedef std::basic_string<cT, Traits, Alloc> string_type;
typedef typename string_type::size_type size_type;
typename std::basic_istream<cT, Traits>::sentry cerberos(in);
if (cerberos) {
value.clear();
size_type width(in.width(0));
if (width == 0) {
width = std::numeric_limits<size_type>::max();
}
std::istreambuf_iterator<char> 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;
}
此getline()
版本的使用方式与std::getline()
类似,但如果限制读取的数据量似乎合理,则设置width()
,例如:
std::string line;
if (safe_getline(in >> std::setw(max_characters), line)) {
// do something with the input
}
另一种方法是使用过滤流缓冲区来限制输入量:过滤器只计算处理的字符数,并将数量限制为适当的字符数。这种方法实际上比单个行更容易应用于整个流:当只处理一行时,过滤器不能只从底层流中获取充满字符的缓冲区,因为没有可靠的方法来放回字符。实现无缓冲版本仍然很简单,但可能效率不高:
template <typename cT, typename Traits = std::char_traits<char> >
class basic_limitbuf
: std::basic_streambuf <cT, Traits> {
public:
typedef Traits traits_type;
typedef typename Traits::int_type int_type;
private:
std::streamsize size;
std::streamsize max;
std::basic_istream<cT, Traits>* stream;
std::basic_streambuf<cT, Traits>* 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<cT, Traits>& 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);
}
};
此流缓冲区已设置为在构造时插入自身并在销毁时自行移除。也就是说,它可以像这样使用:
std::string line;
basic_limitbuf<char> sbuf(max_characters, in);
if (std::getline(in, line)) {
// do something with the input
}
添加设置限制的操纵器也很容易。这种方法的一个优点是,如果流的总大小可能受到限制,则不需要触摸任何读取代码:可以在创建流之后立即设置过滤器。当不需要退出过滤器时,过滤器也可以使用缓冲器,这将大大提高性能。
建议的第三种方法是使用std::basic_string
和自定义分配器。分配器方法有两个方面有点尴尬:
std::string
的类型(尽管转换也不难)。以下是限制分配大小的分配器的必要代码:
template <typename T>
struct limit_alloc
{
private:
std::size_t max_;
public:
typedef T value_type;
limit_alloc(std::size_t max): max_(max) {}
template <typename S>
limit_alloc(limit_alloc<S> const& other): max_(other.max()) {}
std::size_t max() const { return this->max_; }
T* allocate(std::size_t size) {
return size <= max_
? static_cast<T*>(operator new[](size))
: throw std::bad_alloc();
}
void deallocate(void* ptr, std::size_t) {
return operator delete[](ptr);
}
};
template <typename T0, typename T1>
bool operator== (limit_alloc<T0> const& a0, limit_alloc<T1> const& a1) {
return a0.max() == a1.max();
}
template <typename T0, typename T1>
bool operator!= (limit_alloc<T0> const& a0, limit_alloc<T1> const& a1) {
return !(a0 == a1);
}
分配器将使用类似的东西(代码可以使用最新版本的clang编译,但不能编译为gcc):
std::basic_string<char, std::char_traits<char>, limit_alloc<char> >
tmp(limit_alloc<char>(max_chars));
if (std::getline(in, tmp)) {
std::string(tmp.begin(), tmp.end());
// do something with the input
}
总之,有多种方法,每种方法都有自己的小缺点,但每种方法都适用于基于超长线限制拒绝服务攻击的既定目标:
getline()
的自定义版本意味着需要更改阅读代码。