diff --git a/changelog/current.md b/changelog/current.md index 03976c06..84aabb08 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -3,9 +3,18 @@ Most of the changes are from the giant Parser refactor described below. Before g ### Fixes -- Fix `_RYML_CB_ALLOC()` using `T` in parenthesis, making the macro unusable. +- [#PR431](https://github.com/biojppm/rapidyaml/pull/431) - Emitter: prevent stack overflows when emitting malicious trees by providing a max tree depth for the emit visitor. This was done by adding an `EmitOptions` structure as an argument both to the emitter and to the emit functions, which is then forwarded to the emitter. This `EmitOptions` structure has a max tree depth setting with a default value of 64. +- [#PR431](https://github.com/biojppm/rapidyaml/pull/431) - Fix `_RYML_CB_ALLOC()` using `(T)` in parenthesis, making the macro unusable. +### New features + +- [#PR431](https://github.com/biojppm/rapidyaml/pull/431) - append-emitting to the `emitrs_` functions, suggested in [#345](https://github.com/biojppm/rapidyaml/issues/345). This was achieved by adding a `bool append=false` as the last parameter of these functions. + + +------ +All other changes come from [#PR414](https://github.com/biojppm/rapidyaml/pull/414). + ### Parser refactor The parser was completely refactored ([#PR414](https://github.com/biojppm/rapidyaml/pull/414)). This was a large and hard job carried out over several months, but it brings important improvements. diff --git a/src/c4/yml/emit.def.hpp b/src/c4/yml/emit.def.hpp index 7f9b0594..13a46805 100644 --- a/src/c4/yml/emit.def.hpp +++ b/src/c4/yml/emit.def.hpp @@ -36,21 +36,6 @@ substr Emitter::emit_as(EmitType_e type, Tree const& t, id_type id, bool return this->Writer::_get(error_on_excess); } -template -substr Emitter::emit_as(EmitType_e type, Tree const& t, bool error_on_excess) -{ - if(t.empty()) - return {}; - return this->emit_as(type, t, t.root_id(), error_on_excess); -} - -template -substr Emitter::emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess) -{ - _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); - return this->emit_as(type, *n.tree(), n.id(), error_on_excess); -} - //----------------------------------------------------------------------------- @@ -263,6 +248,8 @@ void Emitter::_do_visit_flow_sl(id_type node, id_type ilevel) RYML_ASSERT(!m_tree->is_stream(node)); RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node)); RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); + if(C4_UNLIKELY(ilevel > m_opts.max_depth())) + _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_doc(node)) { @@ -368,6 +355,8 @@ void Emitter::_do_visit_flow_ml(id_type id, id_type ilevel, id_type do_i C4_UNUSED(id); C4_UNUSED(ilevel); C4_UNUSED(do_indent); + if(C4_UNLIKELY(ilevel > m_opts.max_depth())) + _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); const bool prev_flow = m_flow; m_flow = true; c4::yml::error("not implemented"); @@ -461,6 +450,8 @@ void Emitter::_do_visit_block(id_type node, id_type ilevel, id_type do_i RYML_ASSERT(!m_tree->is_stream(node)); RYML_ASSERT(m_tree->is_container(node) || m_tree->is_doc(node)); RYML_ASSERT(m_tree->is_root(node) || (m_tree->parent_is_map(node) || m_tree->parent_is_seq(node))); + if(C4_UNLIKELY(ilevel > m_opts.max_depth())) + _RYML_CB_ERR(m_tree->callbacks(), "max depth exceeded"); if(m_tree->is_doc(node)) { _write_doc(node); diff --git a/src/c4/yml/emit.hpp b/src/c4/yml/emit.hpp index 80fbc6ec..df271f1e 100644 --- a/src/c4/yml/emit.hpp +++ b/src/c4/yml/emit.hpp @@ -58,6 +58,29 @@ typedef enum { } EmitType_e; +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +/** A lightweight object containing options to be used when emitting. */ +struct EmitOptions +{ +public: + /** @name max depth for the emitted tree + * + * This makes the emitter fail when emitting trees exceeding the + * max_depth. + * + * @{ */ + C4_ALWAYS_INLINE id_type max_depth() const noexcept { return m_max_depth; } + EmitOptions& max_depth(id_type d) noexcept { m_max_depth = d; return *this; } + static constexpr const id_type max_depth_default = 64; + /** @} */ +private: + id_type m_max_depth{max_depth_default}; +}; + + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -69,12 +92,21 @@ class Emitter : public Writer { public: - /** Construct the emitter and its internal Writer state. Every - * parameter is forwarded to the constructor of the writer. */ + /** Construct the emitter and its internal Writer state, using default emit options. + * @param args arguments to be forwarded to the constructor of the writer. + * */ template - Emitter(Args &&...args) : Writer(std::forward(args)...), m_tree() {} - /** emit! + Emitter(Args &&...args) : Writer(std::forward(args)...), m_tree(), m_opts(), m_flow(false) {} + /** Construct the emitter and its internal Writer state. + * + * @param opts EmitOptions + * @param args arguments to be forwarded to the constructor of the writer. + * */ + template + Emitter(EmitOptions const& opts, Args &&...args) : Writer(std::forward(args)...), m_tree(), m_opts(opts), m_flow(false) {} + + /** emit! * * When writing to a buffer, returns a substr of the emitted YAML. * If the given buffer has insufficient space, the returned substr @@ -93,15 +125,37 @@ class Emitter : public Writer * */ substr emit_as(EmitType_e type, Tree const& t, id_type id, bool error_on_excess); /** emit starting at the root node */ - substr emit_as(EmitType_e type, Tree const& t, bool error_on_excess=true); - /** emit the given node */ - substr emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess=true); + substr emit_as(EmitType_e type, Tree const& t, bool error_on_excess=true) + { + if(t.empty()) + return {}; + return this->emit_as(type, t, t.root_id(), error_on_excess); + } + /** emit starting at the given node */ + substr emit_as(EmitType_e type, ConstNodeRef const& n, bool error_on_excess=true) + { + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); + return this->emit_as(type, *n.tree(), n.id(), error_on_excess); + } + +public: + + /** get the emit options for this object */ + EmitOptions const& options() const noexcept { return m_opts; } + + /** set the max depth for emitted trees (to prevent a stack overflow) */ + void max_depth(id_type max_depth) noexcept { m_opts.max_depth(max_depth); } + /** get the max depth for emitted trees (to prevent a stack overflow) */ + id_type max_depth() const noexcept { return m_opts.max_depth(); } private: Tree const* C4_RESTRICT m_tree; + EmitOptions m_opts; bool m_flow; +private: + void _emit_yaml(id_type id); void _do_visit_flow_sl(id_type id, id_type ilevel=0); void _do_visit_flow_ml(id_type id, id_type ilevel=0, id_type do_indent=1); @@ -168,15 +222,29 @@ class Emitter : public Writer */ -/** emit YAML to the given file. A null file defaults to stdout. - * Return the number of bytes written. */ +// emit from tree and node id ----------------------- + +/** (1) emit YAML to the given file, starting at the given node. A null + * file defaults to stdout. Return the number of bytes written. */ +inline size_t emit_yaml(Tree const& t, id_type id, EmitOptions const& opts, FILE *f) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ inline size_t emit_yaml(Tree const& t, id_type id, FILE *f) { EmitterFile em(f); return em.emit_as(EMIT_YAML, t, id, /*error_on_excess*/true).len; } -/** emit JSON to the given file. A null file defaults to stdout. - * Return the number of bytes written. */ +/** (1) emit JSON to the given file, starting at the given node. A null + * file defaults to stdout. Return the number of bytes written. */ +inline size_t emit_json(Tree const& t, id_type id, EmitOptions const& opts, FILE *f) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_JSON, t, id, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ inline size_t emit_json(Tree const& t, id_type id, FILE *f) { EmitterFile em(f); @@ -184,17 +252,29 @@ inline size_t emit_json(Tree const& t, id_type id, FILE *f) } -/** emit YAML to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ +// emit from root ------------------------- + +/** (1) emit YAML to the given file, starting at the root node. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_yaml(Tree const& t, EmitOptions const& opts, FILE *f=nullptr) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ inline size_t emit_yaml(Tree const& t, FILE *f=nullptr) { EmitterFile em(f); return em.emit_as(EMIT_YAML, t, /*error_on_excess*/true).len; } -/** emit JSON to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ +/** (1) emit JSON to the given file. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_json(Tree const& t, EmitOptions const& opts, FILE *f=nullptr) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_JSON, t, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ inline size_t emit_json(Tree const& t, FILE *f=nullptr) { EmitterFile em(f); @@ -202,17 +282,29 @@ inline size_t emit_json(Tree const& t, FILE *f=nullptr) } -/** emit YAML to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ +// emit from ConstNodeRef ------------------------ + +/** (1) emit YAML to the given file. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_yaml(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ inline size_t emit_yaml(ConstNodeRef const& r, FILE *f=nullptr) { EmitterFile em(f); return em.emit_as(EMIT_YAML, r, /*error_on_excess*/true).len; } -/** emit JSON to the given file. A null file defaults to stdout. - * Return the number of bytes written. - * @overload */ +/** (1) emit JSON to the given file. A null file defaults to stdout. + * Return the number of bytes written. */ +inline size_t emit_json(ConstNodeRef const& r, EmitOptions const& opts, FILE *f=nullptr) +{ + EmitterFile em(opts, f); + return em.emit_as(EMIT_JSON, r, /*error_on_excess*/true).len; +} +/** (2) like (1), but use default emit options */ inline size_t emit_json(ConstNodeRef const& r, FILE *f=nullptr) { EmitterFile em(f); @@ -229,24 +321,6 @@ inline size_t emit_json(ConstNodeRef const& r, FILE *f=nullptr) * @{ */ -/** mark a tree or node to be emitted as json when using @ref operator<< . For example: - * - * ```cpp - * Tree t = parse_in_arena("{foo: bar}"); - * std::cout << t; // emits YAML - * std::cout << as_json(t); // emits JSON - * ``` - * - * @see @ref operator<< */ -struct as_json -{ - Tree const* tree; - size_t node; - as_json(Tree const& t) : tree(&t), node(t.empty() ? NONE : t.root_id()) {} - as_json(Tree const& t, size_t id) : tree(&t), node(id) {} - as_json(ConstNodeRef const& n) : tree(n.tree()), node(n.id()) {} -}; - /** emit YAML to an STL-like ostream */ template inline OStream& operator<< (OStream& s, Tree const& t) @@ -266,15 +340,65 @@ inline OStream& operator<< (OStream& s, ConstNodeRef const& n) return s; } +/** mark a tree or node to be emitted as yaml when using @ref + * operator<<, with options. For example: + * + * ```cpp + * Tree t = parse_in_arena("{foo: bar}"); + * std::cout << t; // emits YAML + * std::cout << as_yaml(t); // emits YAML, same as above + * std::cout << as_yaml(t, EmitOptions().max_depth(10)); // emits JSON with a max tree depth + * ``` + * + * @see @ref operator<< */ +struct as_json +{ + Tree const* tree; + size_t node; + EmitOptions options; + as_json(Tree const& t, EmitOptions const& opts={}) : tree(&t), node(t.empty() ? NONE : t.root_id()), options(opts) {} + as_json(Tree const& t, size_t id, EmitOptions const& opts={}) : tree(&t), node(id), options(opts) {} + as_json(ConstNodeRef const& n, EmitOptions const& opts={}) : tree(n.tree()), node(n.id()), options(opts) {} +}; + +/** mark a tree or node to be emitted as yaml when using @ref operator<< . For example: + * + * ```cpp + * Tree t = parse_in_arena("{foo: bar}"); + * std::cout << t; // emits YAML + * std::cout << as_json(t); // emits JSON + * std::cout << as_json(t, EmitOptions().max_depth(10)); // emits JSON with a max tree depth + * ``` + * + * @see @ref operator<< */ +struct as_yaml +{ + Tree const* tree; + size_t node; + EmitOptions options; + as_yaml(Tree const& t, EmitOptions const& opts={}) : tree(&t), node(t.empty() ? NONE : t.root_id()), options(opts) {} + as_yaml(Tree const& t, size_t id, EmitOptions const& opts={}) : tree(&t), node(id), options(opts) {} + as_yaml(ConstNodeRef const& n, EmitOptions const& opts={}) : tree(n.tree()), node(n.id()), options(opts) {} +}; + /** emit json to an STL-like stream */ template inline OStream& operator<< (OStream& s, as_json const& j) { - EmitterOStream em(s); + EmitterOStream em(j.options, s); em.emit_as(EMIT_JSON, *j.tree, j.node, true); return s; } +/** emit yaml to an STL-like stream */ +template +inline OStream& operator<< (OStream& s, as_yaml const& y) +{ + EmitterOStream em(y.options, s); + em.emit_as(EMIT_YAML, *y.tree, y.node, true); + return s; +} + /** @} */ @@ -285,29 +409,43 @@ inline OStream& operator<< (OStream& s, as_json const& j) * @{ */ -/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. +// emit from tree and node id ----------------------- + +/** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param t the tree to emit. * @param id the node where to start emitting. * @param buf the output buffer. + * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @return a substr trimmed to the result. If the buffer is - * insufficient (and error_on_excess is false), the pointer of the - * result will be set to null. - * @overload */ + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_yaml(Tree const& t, id_type id, substr buf, EmitOptions const& opts, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_YAML, t, id, error_on_excess); +} +/** (2) like (1), but use default emit options */ inline substr emit_yaml(Tree const& t, id_type id, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); return em.emit_as(EMIT_YAML, t, id, error_on_excess); } -/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. +/** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param t the tree to emit. * @param id the node where to start emitting. * @param buf the output buffer. + * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @return a substr trimmed to the result. If the buffer is - * insufficient (and error_on_excess is false), the pointer of the - * result will be set to null. - * @overload */ + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_json(Tree const& t, id_type id, substr buf, EmitOptions const& opts, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_JSON, t, id, error_on_excess); +} +/** (2) like (1), but use default emit options */ inline substr emit_json(Tree const& t, id_type id, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); @@ -315,27 +453,39 @@ inline substr emit_json(Tree const& t, id_type id, substr buf, bool error_on_exc } -/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. +// emit from root ------------------------- + +/** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param t the tree; will be emitted from the root node. - * @param error_on_excess Raise an error if the space in the buffer is insufficient. * @param buf the output buffer. - * @return a substr trimmed to the result. If the buffer is - * insufficient (and error_on_excess is false), the pointer of the - * result will be set to null. - * @overload */ + * @param error_on_excess Raise an error if the space in the buffer is insufficient. + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_yaml(Tree const& t, substr buf, EmitOptions const& opts, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_YAML, t, error_on_excess); +} +/** (2) like (1), but use default emit options */ inline substr emit_yaml(Tree const& t, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); return em.emit_as(EMIT_YAML, t, error_on_excess); } -/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. +/** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param t the tree; will be emitted from the root node. * @param buf the output buffer. * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @return a substr trimmed to the result. If the buffer is - * insufficient (and error_on_excess is false), the pointer of the - * result will be set to null. - * @overload */ + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_json(Tree const& t, substr buf, EmitOptions const& opts, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_JSON, t, error_on_excess); +} +/** (2) like (1), but use default emit options */ inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); @@ -343,29 +493,41 @@ inline substr emit_json(Tree const& t, substr buf, bool error_on_excess=true) } -/** emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. +// emit from ConstNodeRef ------------------------ + +/** (1) emit YAML to the given buffer. Return a substr trimmed to the emitted YAML. * @param r the starting node. * @param buf the output buffer. + * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @return a substr trimmed to the result. If the buffer is - * insufficient (and error_on_excess is false), the pointer of the - * result will be set to null. - * @overload - */ + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_yaml(ConstNodeRef const& r, substr buf, EmitOptions const& opts, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_YAML, r, error_on_excess); +} +/** (2) like (1), but use default emit options */ inline substr emit_yaml(ConstNodeRef const& r, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); return em.emit_as(EMIT_YAML, r, error_on_excess); } -/** emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. +/** (1) emit JSON to the given buffer. Return a substr trimmed to the emitted JSON. * @param r the starting node. * @param buf the output buffer. + * @param opts emit options. * @param error_on_excess Raise an error if the space in the buffer is insufficient. - * @return a substr trimmed to the result. If the buffer is - * insufficient (and error_on_excess is false), the pointer of the - * result will be set to null. - * @overload - */ + * @return a substr trimmed to the result in the output buffer. If the buffer is + * insufficient (when error_on_excess is false), the string pointer of the + * result will be set to null, and the length will report the required buffer size. */ +inline substr emit_json(ConstNodeRef const& r, substr buf, EmitOptions const& opts, bool error_on_excess=true) +{ + EmitterBuf em(opts, buf); + return em.emit_as(EMIT_JSON, r, error_on_excess); +} +/** (2) like (1), but use default emit options */ inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess=true) { EmitterBuf em(buf); @@ -375,141 +537,212 @@ inline substr emit_json(ConstNodeRef const& r, substr buf, bool error_on_excess= //----------------------------------------------------------------------------- -/** emit+resize: emit YAML to the given `std::string`/`std::vector`-like - * container, resizing it as needed to fit the emitted YAML. */ +/** @defgroup doc_emit_to_container Emit to resizeable container + * + * @{ + */ + +// emit from tree and node id --------------------------- + +/** (1) emit+resize: emit YAML to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted YAML. If @p append is + * set to true, the emitted YAML is appended at the end of the container. + * + * @return a substr trimmed to the emitted YAML (excluding the initial contents, when appending) */ template -substr emitrs_yaml(Tree const& t, id_type id, CharOwningContainer * cont) +substr emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { - substr buf = to_substr(*cont); - substr ret = emit_yaml(t, id, buf, /*error_on_excess*/false); + const size_t startpos = append ? cont->size() : 0u; + cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail + substr buf = to_substr(*cont).sub(startpos); + substr ret = emit_yaml(t, id, buf, opts, /*error_on_excess*/false); if(ret.str == nullptr && ret.len > 0) { - cont->resize(ret.len); - buf = to_substr(*cont); - ret = emit_yaml(t, id, buf, /*error_on_excess*/true); + cont->resize(startpos + ret.len); + buf = to_substr(*cont).sub(startpos); + ret = emit_yaml(t, id, buf, opts, /*error_on_excess*/true); + } + else + { + cont->resize(startpos + ret.len); } return ret; } -/** emit+resize: emit JSON to the given `std::string`/`std::vector`-like - * container, resizing it as needed to fit the emitted JSON. */ +/** (2) like (1), but use default emit options */ +template +substr emitrs_yaml(Tree const& t, id_type id, CharOwningContainer * cont, bool append=false) +{ + return emitrs_yaml(t, id, EmitOptions{}, cont, append); +} +/** (1) emit+resize: emit JSON to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted JSON. If @p append is + * set to true, the emitted YAML is appended at the end of the container. + * + * @return a substr trimmed to the emitted JSON (excluding the initial contents, when appending) */ template -substr emitrs_json(Tree const& t, id_type id, CharOwningContainer * cont) +substr emitrs_json(Tree const& t, id_type id, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { - substr buf = to_substr(*cont); - substr ret = emit_json(t, id, buf, /*error_on_excess*/false); + const size_t startpos = append ? cont->size() : 0u; + cont->resize(cont->capacity()); // otherwise the first emit would be certain to fail + substr buf = to_substr(*cont).sub(startpos); + substr ret = emit_json(t, id, buf, opts, /*error_on_excess*/false); if(ret.str == nullptr && ret.len > 0) { - cont->resize(ret.len); - buf = to_substr(*cont); - ret = emit_json(t, id, buf, /*error_on_excess*/true); + cont->resize(startpos + ret.len); + buf = to_substr(*cont).sub(startpos); + ret = emit_json(t, id, buf, opts, /*error_on_excess*/true); + } + else + { + cont->resize(startpos + ret.len); } return ret; } +/** (2) like (1), but use default emit options */ +template +substr emitrs_json(Tree const& t, id_type id, CharOwningContainer * cont, bool append=false) +{ + return emitrs_json(t, id, EmitOptions{}, cont, append); +} -/** emit+resize: emit YAML to the given `std::string`/`std::vector`-like - * container, resizing it as needed to fit the emitted YAML. */ +/** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_yaml(Tree const& t, id_type id) +CharOwningContainer emitrs_yaml(Tree const& t, id_type id, EmitOptions const& opts={}, bool append=false) { CharOwningContainer c; - emitrs_yaml(t, id, &c); + emitrs_yaml(t, id, opts, &c, append); return c; } -/** emit+resize: emit JSON to the given `std::string`/`std::vector`-like - * container, resizing it as needed to fit the emitted JSON. */ +/** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_json(Tree const& t, id_type id) +CharOwningContainer emitrs_json(Tree const& t, id_type id, EmitOptions const& opts={}, bool append=false) { CharOwningContainer c; - emitrs_json(t, id, &c); + emitrs_json(t, id, opts, &c, append); return c; } -/** emit+resize: YAML to the given `std::string`/`std::vector`-like - * container, resizing it as needed to fit the emitted YAML. */ +// emit from root ------------------------- + +/** (1) emit+resize: YAML to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted YAML. + * @return a substr trimmed to the new emitted contents. */ template -substr emitrs_yaml(Tree const& t, CharOwningContainer * cont) +substr emitrs_yaml(Tree const& t, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { if(t.empty()) return {}; - return emitrs_yaml(t, t.root_id(), cont); + return emitrs_yaml(t, t.root_id(), opts, cont, append); } -/** emit+resize: JSON to the given `std::string`/`std::vector`-like - * container, resizing it as needed to fit the emitted JSON. */ +/** (2) like (1), but use default emit options */ template -substr emitrs_json(Tree const& t, CharOwningContainer * cont) +substr emitrs_yaml(Tree const& t, CharOwningContainer * cont, bool append=false) { if(t.empty()) return {}; - return emitrs_json(t, t.root_id(), cont); + return emitrs_yaml(t, t.root_id(), EmitOptions{}, cont, append); +} +/** (1) emit+resize: JSON to the given `std::string`/`std::vector`-like + * container, resizing it as needed to fit the emitted JSON. + * @return a substr trimmed to the new emitted contents. */ +template +substr emitrs_json(Tree const& t, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + if(t.empty()) + return {}; + return emitrs_json(t, t.root_id(), opts, cont, append); +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_json(Tree const& t, CharOwningContainer * cont, bool append=false) +{ + if(t.empty()) + return {}; + return emitrs_json(t, t.root_id(), EmitOptions{}, cont, append); } -/** emit+resize: YAML to the given `std::string`/`std::vector`-like container, - * resizing it as needed to fit the emitted YAML. */ +/** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_yaml(Tree const& t) +CharOwningContainer emitrs_yaml(Tree const& t, EmitOptions const& opts={}, bool append=false) { CharOwningContainer c; if(t.empty()) return c; - emitrs_yaml(t, t.root_id(), &c); + emitrs_yaml(t, t.root_id(), opts, &c, append); return c; } -/** emit+resize: JSON to the given `std::string`/`std::vector`-like container, - * resizing it as needed to fit the emitted JSON. */ +/** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_json(Tree const& t) +CharOwningContainer emitrs_json(Tree const& t, EmitOptions const& opts={}, bool append=false) { CharOwningContainer c; if(t.empty()) return c; - emitrs_json(t, t.root_id(), &c); + emitrs_json(t, t.root_id(), opts, &c, append); return c; } -/** emit+resize: YAML to the given `std::string`/`std::vector`-like container, - * resizing it as needed to fit the emitted YAML. */ +// emit from ConstNodeRef ------------------------ + + +/** (1) emit+resize: YAML to the given `std::string`/`std::vector`-like container, + * resizing it as needed to fit the emitted YAML. + * @return a substr trimmed to the new emitted contents */ +template +substr emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) +{ + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); + return emitrs_yaml(*n.tree(), n.id(), opts, cont, append); +} +/** (2) like (1), but use default emit options */ template -substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont) +substr emitrs_yaml(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) { _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); - return emitrs_yaml(*n.tree(), n.id(), cont); + return emitrs_yaml(*n.tree(), n.id(), EmitOptions{}, cont, append); } -/** emit+resize: JSON to the given `std::string`/`std::vector`-like container, - * resizing it as needed to fit the emitted JSON. */ +/** (1) emit+resize: JSON to the given `std::string`/`std::vector`-like container, + * resizing it as needed to fit the emitted JSON. + * @return a substr trimmed to the new emitted contents */ template -substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont) +substr emitrs_json(ConstNodeRef const& n, EmitOptions const& opts, CharOwningContainer * cont, bool append=false) { _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); - return emitrs_json(*n.tree(), n.id(), cont); + return emitrs_json(*n.tree(), n.id(), opts, cont, append); +} +/** (2) like (1), but use default emit options */ +template +substr emitrs_json(ConstNodeRef const& n, CharOwningContainer * cont, bool append=false) +{ + _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); + return emitrs_json(*n.tree(), n.id(), EmitOptions{}, cont, append); } -/** emit+resize: YAML to the given `std::string`/`std::vector`-like container, - * resizing it as needed to fit the emitted YAML. */ +/** (3) emit+resize: YAML to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_yaml(ConstNodeRef const& n) +CharOwningContainer emitrs_yaml(ConstNodeRef const& n, EmitOptions const& opts={}, bool append=false) { _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; - emitrs_yaml(*n.tree(), n.id(), &c); + emitrs_yaml(*n.tree(), n.id(), opts, &c, append); return c; } -/** emit+resize: JSON to the given `std::string`/`std::vector`-like container, - * resizing it as needed to fit the emitted JSON. */ +/** (3) emit+resize: JSON to a newly-created `std::string`/`std::vector`-like container. */ template -CharOwningContainer emitrs_json(ConstNodeRef const& n) +CharOwningContainer emitrs_json(ConstNodeRef const& n, EmitOptions const& opts={}, bool append=false) { _RYML_CB_CHECK(n.tree()->callbacks(), n.readable()); CharOwningContainer c; - emitrs_json(*n.tree(), n.id(), &c); + emitrs_json(*n.tree(), n.id(), opts, &c, append); return c; } + /** @} */