根据评论和答案,似乎有三种方法:
- 编写自定义版本的
getline()
,可能在内部使用std::istream::getline()
成员获取实际字符。 - 使用过滤流缓冲区来限制可能接收的数据量。
- 不是读取
std::string
,而是使用具有自定义分配器的字符串实例化,限制存储在字符串中的内存量。
并非所有建议都带有代码。此答案为所有方法提供代码,并略微讨论了这三种方法。在进入实现细节之前,首先值得指出的是,如果收到的输入过长,则有多种选择:1.读取过长的行可能导致成功读取部分行,即结果字符串包含读取内容并且流没有设置任何错误标志。然而,这意味着无法区分一条正好达到限制或太长的行。由于限制本身有点任意,因此可能并不重要。2.读取过长的行可能被视为失败(即设置
std::ios_base::failbit
和/或
std::ios_base::bad_bit
),并且由于读取失败,产生一个空字符串。显然,产生一个空字符串可以防止潜在地查看迄今为止已读取的字符串,以可能看到正在发生的事情。3.读取过长的行可以提供部分行读取,并在流上设置错误标志。这似乎是合理的行为,既检测到有问题,也提供了输入以供检查。虽然已经有多个代码示例实现了一个有限版本的
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)) {
}
另一种方法是使用过滤流缓冲器来限制输入的数量:该过滤器仅计算处理的字符数并将其限制为适当数量的字符。实际上,这种方法更容易应用于整个流而不是单个行:在处理一个行时,过滤器无法从底层流获取完整的缓冲区,因为没有可靠的方式将字符放回去。实现非缓冲版本仍然很简单,但可能不太高效。
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());
}
总之,有多种方法可以限制基于过长行的拒绝服务攻击,每种方法都有自己的小缺点,但对于所述目标而言都是合理可行的:
- 使用自定义版本的
getline()
需要更改读取代码。
- 使用自定义流缓冲区,除非整个流的大小可以受限,否则会变慢。
- 使用自定义分配器给予更少的控制,并需要对读取代码进行一些更改。
basic_string
对象,并将内容读入其中。 - Praetorianmax_size()
函数? - Collinstd::string
的类型。 - Dietmar Kühlin
的streambuf,该实现包装了原始的streambuf,并在读取一定数量的字符时发送一个'\n'
。 - jrokif (in.rdbuf()->in_avail() > max_size) { /* end */ }
... - mb84