Commit 1f53c6c0 authored by thebusytypist's avatar thebusytypist

Implement stack size limitation for iterative parsing.

parent 46e89dad
...@@ -1221,12 +1221,13 @@ public: ...@@ -1221,12 +1221,13 @@ public:
\tparam SourceEncoding Encoding of input stream \tparam SourceEncoding Encoding of input stream
\tparam InputStream Type of input stream, implementing Stream concept \tparam InputStream Type of input stream, implementing Stream concept
\param is Input stream to be parsed. \param is Input stream to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
\return The document itself for fluent API. \return The document itself for fluent API.
*/ */
template <unsigned parseFlags, typename SourceEncoding, typename InputStream> template <unsigned parseFlags, typename SourceEncoding, typename InputStream>
GenericDocument& ParseStream(InputStream& is) { GenericDocument& ParseStream(InputStream& is, size_t limit = 0) {
ValueType::SetNull(); // Remove existing root if exist ValueType::SetNull(); // Remove existing root if exist
GenericReader<SourceEncoding, Encoding, Allocator> reader(&GetAllocator()); GenericReader<SourceEncoding, Encoding, Allocator> reader(limit, &GetAllocator());
ClearStackOnExit scope(*this); ClearStackOnExit scope(*this);
parseResult_ = reader.template Parse<parseFlags>(is, *this); parseResult_ = reader.template Parse<parseFlags>(is, *this);
if (parseResult_) { if (parseResult_) {
...@@ -1240,21 +1241,23 @@ public: ...@@ -1240,21 +1241,23 @@ public:
/*! \tparam parseFlags Combination of \ref ParseFlag. /*! \tparam parseFlags Combination of \ref ParseFlag.
\tparam InputStream Type of input stream, implementing Stream concept \tparam InputStream Type of input stream, implementing Stream concept
\param is Input stream to be parsed. \param is Input stream to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
\return The document itself for fluent API. \return The document itself for fluent API.
*/ */
template <unsigned parseFlags, typename InputStream> template <unsigned parseFlags, typename InputStream>
GenericDocument& ParseStream(InputStream& is) { GenericDocument& ParseStream(InputStream& is, size_t limit = 0) {
return ParseStream<parseFlags,Encoding,InputStream>(is); return ParseStream<parseFlags,Encoding,InputStream>(is, limit);
} }
//! Parse JSON text from an input stream (with \ref kParseDefaultFlags) //! Parse JSON text from an input stream (with \ref kParseDefaultFlags)
/*! \tparam InputStream Type of input stream, implementing Stream concept /*! \tparam InputStream Type of input stream, implementing Stream concept
\param is Input stream to be parsed. \param is Input stream to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
\return The document itself for fluent API. \return The document itself for fluent API.
*/ */
template <typename InputStream> template <typename InputStream>
GenericDocument& ParseStream(InputStream& is) { GenericDocument& ParseStream(InputStream& is, size_t limit = 0) {
return ParseStream<kParseDefaultFlags, Encoding, InputStream>(is); return ParseStream<kParseDefaultFlags, Encoding, InputStream>(is, limit);
} }
//!@} //!@}
...@@ -1265,30 +1268,33 @@ public: ...@@ -1265,30 +1268,33 @@ public:
/*! \tparam parseFlags Combination of \ref ParseFlag. /*! \tparam parseFlags Combination of \ref ParseFlag.
\tparam SourceEncoding Transcoding from input Encoding \tparam SourceEncoding Transcoding from input Encoding
\param str Mutable zero-terminated string to be parsed. \param str Mutable zero-terminated string to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
\return The document itself for fluent API. \return The document itself for fluent API.
*/ */
template <unsigned parseFlags, typename SourceEncoding> template <unsigned parseFlags, typename SourceEncoding>
GenericDocument& ParseInsitu(Ch* str) { GenericDocument& ParseInsitu(Ch* str, size_t limit = 0) {
GenericInsituStringStream<Encoding> s(str); GenericInsituStringStream<Encoding> s(str);
return ParseStream<parseFlags | kParseInsituFlag, SourceEncoding>(s); return ParseStream<parseFlags | kParseInsituFlag, SourceEncoding>(s, limit);
} }
//! Parse JSON text from a mutable string //! Parse JSON text from a mutable string
/*! \tparam parseFlags Combination of \ref ParseFlag. /*! \tparam parseFlags Combination of \ref ParseFlag.
\param str Mutable zero-terminated string to be parsed. \param str Mutable zero-terminated string to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
\return The document itself for fluent API. \return The document itself for fluent API.
*/ */
template <unsigned parseFlags> template <unsigned parseFlags>
GenericDocument& ParseInsitu(Ch* str) { GenericDocument& ParseInsitu(Ch* str, size_t limit = 0) {
return ParseInsitu<parseFlags, Encoding>(str); return ParseInsitu<parseFlags, Encoding>(str, limit);
} }
//! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags)
/*! \param str Mutable zero-terminated string to be parsed. /*! \param str Mutable zero-terminated string to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
\return The document itself for fluent API. \return The document itself for fluent API.
*/ */
GenericDocument& ParseInsitu(Ch* str) { GenericDocument& ParseInsitu(Ch* str, size_t limit = 0) {
return ParseInsitu<kParseDefaultFlags, Encoding>(str); return ParseInsitu<kParseDefaultFlags, Encoding>(str, limit);
} }
//!@} //!@}
...@@ -1299,28 +1305,31 @@ public: ...@@ -1299,28 +1305,31 @@ public:
/*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag).
\tparam SourceEncoding Transcoding from input Encoding \tparam SourceEncoding Transcoding from input Encoding
\param str Read-only zero-terminated string to be parsed. \param str Read-only zero-terminated string to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
*/ */
template <unsigned parseFlags, typename SourceEncoding> template <unsigned parseFlags, typename SourceEncoding>
GenericDocument& Parse(const Ch* str) { GenericDocument& Parse(const Ch* str, size_t limit = 0) {
RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag));
GenericStringStream<SourceEncoding> s(str); GenericStringStream<SourceEncoding> s(str);
return ParseStream<parseFlags, SourceEncoding>(s); return ParseStream<parseFlags, SourceEncoding>(s, limit);
} }
//! Parse JSON text from a read-only string //! Parse JSON text from a read-only string
/*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag).
\param str Read-only zero-terminated string to be parsed. \param str Read-only zero-terminated string to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
*/ */
template <unsigned parseFlags> template <unsigned parseFlags>
GenericDocument& Parse(const Ch* str) { GenericDocument& Parse(const Ch* str, size_t limit = 0) {
return Parse<parseFlags, Encoding>(str); return Parse<parseFlags, Encoding>(str, limit);
} }
//! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags)
/*! \param str Read-only zero-terminated string to be parsed. /*! \param str Read-only zero-terminated string to be parsed.
\param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
*/ */
GenericDocument& Parse(const Ch* str) { GenericDocument& Parse(const Ch* str, size_t limit = 0) {
return Parse<kParseDefaultFlags>(str); return Parse<kParseDefaultFlags>(str, limit);
} }
//!@} //!@}
......
...@@ -40,6 +40,7 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErro ...@@ -40,6 +40,7 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErro
case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error.");
case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error.");
case kParseErrorStackSizeLimitExceeded: return RAPIDJSON_ERROR_STRING("Parsing stack size limit is exceeded.");
default: default:
return RAPIDJSON_ERROR_STRING("Unknown error."); return RAPIDJSON_ERROR_STRING("Unknown error.");
......
...@@ -59,7 +59,8 @@ enum ParseErrorCode { ...@@ -59,7 +59,8 @@ enum ParseErrorCode {
kParseErrorNumberMissExponent, //!< Miss exponent in number. kParseErrorNumberMissExponent, //!< Miss exponent in number.
kParseErrorTermination, //!< Parsing was terminated. kParseErrorTermination, //!< Parsing was terminated.
kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. kParseErrorUnspecificSyntaxError, //!< Unspecific syntax error.
kParseErrorStackSizeLimitExceeded //!< Parsing stack size limit is exceeded.
}; };
//! Result of parsing (wraps ParseErrorCode) //! Result of parsing (wraps ParseErrorCode)
......
...@@ -272,10 +272,11 @@ public: ...@@ -272,10 +272,11 @@ public:
typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type
//! Constructor. //! Constructor.
/*! \param allocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) /*! \param limit Parsing stack size limit(in bytes). Pass 0 means no limit.
\param allocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing)
\param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing)
*/ */
GenericReader(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseResult_() {} GenericReader(size_t limit = 0, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), kStackSizeLimit(limit), parseResult_() {}
//! Parse JSON text. //! Parse JSON text.
/*! \tparam parseFlags Combination of \ref ParseFlag. /*! \tparam parseFlags Combination of \ref ParseFlag.
...@@ -569,8 +570,14 @@ private: ...@@ -569,8 +570,14 @@ private:
if (c == '\\') { // Escape if (c == '\\') { // Escape
is.Take(); is.Take();
Ch e = is.Take(); Ch e = is.Take();
if ((sizeof(Ch) == 1 || unsigned(e) < 256) && escape[(unsigned char)e]) if ((sizeof(Ch) == 1 || unsigned(e) < 256) && escape[(unsigned char)e]) {
if (!(parseFlags & kParseInsituFlag)) {
if (!IsStackSpaceSufficient<Ch>(1)) {
RAPIDJSON_PARSE_ERROR(kParseErrorStackSizeLimitExceeded, is.Tell() - 1);
}
}
os.Put(escape[(unsigned char)e]); os.Put(escape[(unsigned char)e]);
}
else if (e == 'u') { // Unicode else if (e == 'u') { // Unicode
unsigned codepoint = ParseHex4(is); unsigned codepoint = ParseHex4(is);
if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { if (codepoint >= 0xD800 && codepoint <= 0xDBFF) {
...@@ -589,6 +596,11 @@ private: ...@@ -589,6 +596,11 @@ private:
} }
else if (c == '"') { // Closing double quote else if (c == '"') { // Closing double quote
is.Take(); is.Take();
if (!(parseFlags & kParseInsituFlag)) {
if (!IsStackSpaceSufficient<Ch>(1)) {
RAPIDJSON_PARSE_ERROR(kParseErrorStackSizeLimitExceeded, is.Tell() - 1);
}
}
os.Put('\0'); // null-terminate the string os.Put('\0'); // null-terminate the string
return; return;
} }
...@@ -1038,8 +1050,16 @@ private: ...@@ -1038,8 +1050,16 @@ private:
else if (src == IterativeParsingKeyValueDelimiterState) else if (src == IterativeParsingKeyValueDelimiterState)
n = IterativeParsingMemberValueState; n = IterativeParsingMemberValueState;
// Push current state. // Push current state.
if (!IsStackSpaceSufficient<IterativeParsingState>(1)) {
RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStackSizeLimitExceeded, is.Tell());
return IterativeParsingErrorState;
}
*stack_.template Push<IterativeParsingState>(1) = n; *stack_.template Push<IterativeParsingState>(1) = n;
// Initialize and push the member/element count. // Initialize and push the member/element count.
if (!IsStackSpaceSufficient<int>(1)) {
RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStackSizeLimitExceeded, is.Tell());
return IterativeParsingErrorState;
}
*stack_.template Push<int>(1) = 0; *stack_.template Push<int>(1) = 0;
// Call handler // Call handler
if (dst == IterativeParsingObjectInitialState) if (dst == IterativeParsingObjectInitialState)
...@@ -1206,7 +1226,13 @@ private: ...@@ -1206,7 +1226,13 @@ private:
return parseResult_; return parseResult_;
} }
template <typename T>
bool IsStackSpaceSufficient(size_t count) const {
return kStackSizeLimit == 0 || (stack_.GetSize() + sizeof(T) * count <= kStackSizeLimit);
}
static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string.
const size_t kStackSizeLimit; //!< Stack size limit(in bytes). A value of 0 means no limit.
internal::Stack<Allocator> stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. internal::Stack<Allocator> stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing.
ParseResult parseResult_; ParseResult parseResult_;
}; // class GenericReader }; // class GenericReader
......
...@@ -1288,6 +1288,18 @@ TEST(Reader, IterativeParsing_ShortCircuit) { ...@@ -1288,6 +1288,18 @@ TEST(Reader, IterativeParsing_ShortCircuit) {
} }
} }
TEST(Reader, IterativeParsing_LimitStackSize) {
BaseReaderHandler<> handler;
Reader reader(20);
StringStream is("[[[]]]");
ParseResult r = reader.Parse<kParseIterativeFlag>(is, handler);
EXPECT_TRUE(reader.HasParseError());
EXPECT_EQ(kParseErrorStackSizeLimitExceeded, r.Code());
EXPECT_EQ(2, r.Offset());
}
#ifdef __GNUC__ #ifdef __GNUC__
RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_POP
#endif #endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment