#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { namespace STParsedJSONDetail { template constexpr std::enable_if_t::value && std::is_signed::value, U> to_unsigned(S value) { if (value < 0 || std::numeric_limits::max() < value) Throw("Value out of range"); return static_cast(value); } template constexpr std::enable_if_t::value && std::is_unsigned::value, U1> to_unsigned(U2 value) { if (std::numeric_limits::max() < value) Throw("Value out of range"); return static_cast(value); } // LCOV_EXCL_START static inline std::string make_name(std::string const& object, std::string const& field) { if (field.empty()) return object; return object + "." + field; } static inline Json::Value not_an_object(std::string const& object, std::string const& field) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + make_name(object, field) + "' is not a JSON object."); } static inline Json::Value not_an_object(std::string const& object) { return not_an_object(object, ""); } static inline Json::Value not_an_array(std::string const& object) { return RPC::make_error(rpcINVALID_PARAMS, "Field '" + object + "' is not a JSON array."); } static inline Json::Value unknown_field(std::string const& object, std::string const& field) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + make_name(object, field) + "' is unknown."); } static inline Json::Value out_of_range(std::string const& object, std::string const& field) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + make_name(object, field) + "' is out of range."); } static inline Json::Value bad_type(std::string const& object, std::string const& field) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + make_name(object, field) + "' has bad type."); } static inline Json::Value invalid_data(std::string const& object, std::string const& field) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + make_name(object, field) + "' has invalid data."); } static inline Json::Value invalid_data(std::string const& object) { return invalid_data(object, ""); } static inline Json::Value array_expected(std::string const& object, std::string const& field) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + make_name(object, field) + "' must be a JSON array."); } static inline Json::Value string_expected(std::string const& object, std::string const& field) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + make_name(object, field) + "' must be a string."); } static inline Json::Value too_deep(std::string const& object) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + object + "' exceeds nesting depth limit."); } static inline Json::Value singleton_expected(std::string const& object, unsigned int index) { return RPC::make_error( rpcINVALID_PARAMS, "Field '" + object + "[" + std::to_string(index) + "]' must be an object with a single key/object value."); } static inline Json::Value template_mismatch(SField const& sField) { return RPC::make_error( rpcINVALID_PARAMS, "Object '" + sField.getName() + "' contents did not meet requirements for that type."); } static inline Json::Value non_object_in_array(std::string const& item, Json::UInt index) { return RPC::make_error( rpcINVALID_PARAMS, "Item '" + item + "' at index " + std::to_string(index) + " is not an object. Arrays may only contain objects."); } // LCOV_EXCL_STOP template static std::optional parseUnsigned( SField const& field, std::string const& json_name, std::string const& fieldName, SField const* name, Json::Value const& value, Json::Value& error) { std::optional ret; try { if (value.isString()) { ret = detail::make_stvar( field, safe_cast( beast::lexicalCastThrow(value.asString()))); } else if (value.isInt()) { ret = detail::make_stvar( field, to_unsigned(value.asInt())); } else if (value.isUInt()) { ret = detail::make_stvar( field, to_unsigned(value.asUInt())); } else { error = bad_type(json_name, fieldName); return ret; } } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } return ret; } template static std::optional parseUint16( SField const& field, std::string const& json_name, std::string const& fieldName, SField const* name, Json::Value const& value, Json::Value& error) { std::optional ret; try { if (value.isString()) { std::string const strValue = value.asString(); if (!strValue.empty() && ((strValue[0] < '0') || (strValue[0] > '9'))) { if (field == sfTransactionType) { ret = detail::make_stvar( field, safe_cast(static_cast( TxFormats::getInstance().findTypeByName(strValue)))); if (*name == sfGeneric) name = &sfTransaction; } else if (field == sfLedgerEntryType) { ret = detail::make_stvar( field, safe_cast(static_cast( LedgerFormats::getInstance().findTypeByName(strValue)))); if (*name == sfGeneric) name = &sfLedgerEntry; } else { error = invalid_data(json_name, fieldName); return ret; } } } if (!ret) { return parseUnsigned( field, json_name, fieldName, name, value, error); } } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } return ret; } template static std::optional parseUint32( SField const& field, std::string const& json_name, std::string const& fieldName, SField const* name, Json::Value const& value, Json::Value& error) { std::optional ret; try { if (value.isString()) { if (field == sfPermissionValue) { std::string const strValue = value.asString(); auto const granularPermission = Permission::getInstance().getGranularValue(strValue); if (granularPermission) { ret = detail::make_stvar(field, *granularPermission); } else { auto const& txType = TxFormats::getInstance().findTypeByName(strValue); ret = detail::make_stvar( field, Permission::getInstance().txToPermissionType(txType)); } } else { ret = detail::make_stvar( field, safe_cast( beast::lexicalCastThrow(value.asString()))); } } if (!ret) { return parseUnsigned( field, json_name, fieldName, name, value, error); } } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } return ret; } // This function is used by parseObject to parse any JSON type that doesn't // recurse. Everything represented here is a leaf-type. static std::optional parseLeaf( std::string const& json_name, std::string const& fieldName, SField const* name, Json::Value const& value, Json::Value& error) { std::optional ret; auto const& field = SField::getField(fieldName); // checked in parseObject if (field == sfInvalid) { // LCOV_EXCL_START error = unknown_field(json_name, fieldName); return ret; // LCOV_EXCL_STOP } switch (field.fieldType) { case STI_UINT8: try { constexpr auto minValue = std::numeric_limits::min(); constexpr auto maxValue = std::numeric_limits::max(); if (value.isString()) { std::string const strValue = value.asString(); if (!strValue.empty() && ((strValue[0] < '0') || (strValue[0] > '9'))) { if (field == sfTransactionResult) { auto ter = transCode(strValue); if (!ter || TERtoInt(*ter) < minValue || TERtoInt(*ter) > maxValue) { error = out_of_range(json_name, fieldName); return ret; } ret = detail::make_stvar( field, static_cast(TERtoInt(*ter))); } else { error = bad_type(json_name, fieldName); return ret; } } else { ret = detail::make_stvar( field, beast::lexicalCastThrow(strValue)); } } else if (value.isInt()) { if (value.asInt() < minValue || value.asInt() > maxValue) { error = out_of_range(json_name, fieldName); return ret; } ret = detail::make_stvar( field, static_cast(value.asInt())); } else if (value.isUInt()) { if (value.asUInt() > maxValue) { error = out_of_range(json_name, fieldName); return ret; } ret = detail::make_stvar( field, static_cast(value.asUInt())); } else { error = bad_type(json_name, fieldName); return ret; } } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_UINT16: ret = parseUint16(field, json_name, fieldName, name, value, error); if (!ret) return ret; break; case STI_UINT32: ret = parseUint32(field, json_name, fieldName, name, value, error); if (!ret) return ret; break; case STI_UINT64: try { if (value.isString()) { auto const str = value.asString(); std::uint64_t val = 0; bool const useBase10 = field.shouldMeta(SField::sMD_BaseTen); // if the field is amount, serialize as base 10 auto [p, ec] = std::from_chars( str.data(), str.data() + str.size(), val, useBase10 ? 10 : 16); if (ec != std::errc() || (p != str.data() + str.size())) Throw("invalid data"); ret = detail::make_stvar(field, val); } else if (value.isInt()) { ret = detail::make_stvar( field, to_unsigned(value.asInt())); } else if (value.isUInt()) { ret = detail::make_stvar( field, safe_cast(value.asUInt())); } else { error = bad_type(json_name, fieldName); return ret; } } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_UINT128: { if (!value.isString()) { error = bad_type(json_name, fieldName); return ret; } uint128 num; if (auto const s = value.asString(); !num.parseHex(s)) { if (!s.empty()) { error = invalid_data(json_name, fieldName); return ret; } num.zero(); } ret = detail::make_stvar(field, num); break; } case STI_UINT160: { if (!value.isString()) { error = bad_type(json_name, fieldName); return ret; } uint160 num; if (auto const s = value.asString(); !num.parseHex(s)) { if (!s.empty()) { error = invalid_data(json_name, fieldName); return ret; } num.zero(); } ret = detail::make_stvar(field, num); break; } case STI_UINT192: { if (!value.isString()) { error = bad_type(json_name, fieldName); return ret; } uint192 num; if (auto const s = value.asString(); !num.parseHex(s)) { if (!s.empty()) { error = invalid_data(json_name, fieldName); return ret; } num.zero(); } ret = detail::make_stvar(field, num); break; } case STI_UINT256: { if (!value.isString()) { error = bad_type(json_name, fieldName); return ret; } uint256 num; if (auto const s = value.asString(); !num.parseHex(s)) { if (!s.empty()) { error = invalid_data(json_name, fieldName); return ret; } num.zero(); } ret = detail::make_stvar(field, num); break; } case STI_INT32: try { if (value.isString()) { ret = detail::make_stvar( field, beast::lexicalCastThrow(value.asString())); } else if (value.isInt()) { // future-proofing - a static assert failure if the JSON // library ever supports larger ints // In such case, we will need additional bounds checks here static_assert(std::is_same_v); ret = detail::make_stvar(field, value.asInt()); } else if (value.isUInt()) { auto const uintValue = value.asUInt(); if (uintValue > static_cast(std::numeric_limits::max())) { error = out_of_range(json_name, fieldName); return ret; } ret = detail::make_stvar(field, static_cast(uintValue)); } else { error = bad_type(json_name, fieldName); return ret; } } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_VL: if (!value.isString()) { error = bad_type(json_name, fieldName); return ret; } try { if (auto vBlob = strUnHex(value.asString())) { ret = detail::make_stvar(field, vBlob->data(), vBlob->size()); } else { Throw("invalid data"); } } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_AMOUNT: try { ret = detail::make_stvar(amountFromJson(field, value)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_NUMBER: try { ret = detail::make_stvar(numberFromJson(field, value)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_VECTOR256: if (!value.isArrayOrNull()) { error = array_expected(json_name, fieldName); return ret; } try { STVector256 tail(field); for (Json::UInt i = 0; value.isValidIndex(i); ++i) { uint256 s; if (!s.parseHex(value[i].asString())) Throw("invalid data"); tail.push_back(s); } ret = detail::make_stvar(std::move(tail)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_PATHSET: if (!value.isArrayOrNull()) { error = array_expected(json_name, fieldName); return ret; } try { STPathSet tail(field); for (Json::UInt i = 0; value.isValidIndex(i); ++i) { STPath p; if (!value[i].isArrayOrNull()) { std::stringstream ss; ss << fieldName << "[" << i << "]"; error = array_expected(json_name, ss.str()); return ret; } for (Json::UInt j = 0; value[i].isValidIndex(j); ++j) { std::stringstream ss; ss << fieldName << "[" << i << "][" << j << "]"; std::string const element_name(json_name + "." + ss.str()); // each element in this path has some combination of // account, asset, or issuer Json::Value pathEl = value[i][j]; if (!pathEl.isObject()) { error = not_an_object(element_name); return ret; } if (pathEl.isMember(jss::currency) && pathEl.isMember(jss::mpt_issuance_id)) { error = RPC::make_error(rpcINVALID_PARAMS, "Invalid Asset."); return ret; } bool const isMPT = pathEl.isMember(jss::mpt_issuance_id); auto const assetName = isMPT ? jss::mpt_issuance_id : jss::currency; Json::Value const& account = pathEl[jss::account]; Json::Value const& asset = pathEl[assetName]; Json::Value const& issuer = pathEl[jss::issuer]; bool hasAsset = false; AccountID uAccount, uIssuer; PathAsset uAsset; if (!account && !asset && !issuer) { error = invalid_data(element_name); return ret; } if (account) { // human account id if (!account.isString()) { error = string_expected(element_name, jss::account.c_str()); return ret; } // If we have what looks like a 160-bit hex value, // we set it, otherwise, we assume it's an AccountID if (!uAccount.parseHex(account.asString())) { auto const a = parseBase58(account.asString()); if (!a) { error = invalid_data(element_name, jss::account.c_str()); return ret; } uAccount = *a; } } if (asset) { // human asset if (!asset.isString()) { error = string_expected(element_name, assetName.c_str()); return ret; } hasAsset = true; if (isMPT) { MPTID u; if (!u.parseHex(asset.asString())) { error = invalid_data(element_name, assetName.c_str()); return ret; } if (getMPTIssuer(u) == beast::zero) { error = invalid_data(element_name, jss::account.c_str()); return ret; } uAsset = u; } else { Currency currency; if (!currency.parseHex(asset.asString())) { if (!to_currency(currency, asset.asString())) { error = invalid_data(element_name, assetName.c_str()); return ret; } } uAsset = currency; } } if (issuer) { // human account id if (!issuer.isString()) { error = string_expected(element_name, jss::issuer.c_str()); return ret; } if (!uIssuer.parseHex(issuer.asString())) { auto const a = parseBase58(issuer.asString()); if (!a) { error = invalid_data(element_name, jss::issuer.c_str()); return ret; } uIssuer = *a; } if (isMPT && uIssuer != getMPTIssuer(uAsset.get())) { error = invalid_data(element_name, jss::issuer.c_str()); return ret; } } p.emplace_back(uAccount, uAsset, uIssuer, hasAsset); } tail.push_back(p); } ret = detail::make_stvar(std::move(tail)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_ACCOUNT: { if (!value.isString()) { error = bad_type(json_name, fieldName); return ret; } std::string const strValue = value.asString(); try { if (AccountID account; account.parseHex(strValue)) return detail::make_stvar(field, account); if (auto result = parseBase58(strValue)) return detail::make_stvar(field, *result); error = invalid_data(json_name, fieldName); return ret; } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } } break; case STI_ISSUE: try { ret = detail::make_stvar(issueFromJson(field, value)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_XCHAIN_BRIDGE: try { ret = detail::make_stvar(STXChainBridge(field, value)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; case STI_CURRENCY: try { ret = detail::make_stvar(currencyFromJson(field, value)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return ret; } break; default: error = bad_type(json_name, fieldName); return ret; } return ret; } static int const maxDepth = 64; // Forward declaration since parseObject() and parseArray() call each other. static std::optional parseArray( std::string const& json_name, Json::Value const& json, SField const& inName, int depth, Json::Value& error); static std::optional parseObject( std::string const& json_name, Json::Value const& json, SField const& inName, int depth, Json::Value& error) { if (!json.isObjectOrNull()) { error = not_an_object(json_name); return std::nullopt; } if (depth > maxDepth) { error = too_deep(json_name); return std::nullopt; } try { STObject data(inName); for (auto const& fieldName : json.getMemberNames()) { Json::Value const& value = json[fieldName]; auto const& field = SField::getField(fieldName); if (field == sfInvalid) { error = unknown_field(json_name, fieldName); return std::nullopt; } switch (field.fieldType) { // Object-style containers (which recurse). case STI_OBJECT: case STI_TRANSACTION: case STI_LEDGERENTRY: case STI_VALIDATION: if (!value.isObject()) { error = not_an_object(json_name, fieldName); return std::nullopt; } try { auto ret = parseObject( json_name + "." + fieldName, value, field, depth + 1, error); if (!ret) return std::nullopt; data.emplace_back(std::move(*ret)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return std::nullopt; } break; // Array-style containers (which recurse). case STI_ARRAY: try { auto array = parseArray(json_name + "." + fieldName, value, field, depth + 1, error); if (!array.has_value()) return std::nullopt; data.emplace_back(std::move(*array)); } catch (std::exception const&) { error = invalid_data(json_name, fieldName); return std::nullopt; } break; // Everything else (types that don't recurse). default: { auto leaf = parseLeaf(json_name, fieldName, &inName, value, error); if (!leaf) return std::nullopt; data.emplace_back(std::move(*leaf)); } break; } } // Some inner object types have templates. Attempt to apply that. data.applyTemplateFromSField(inName); // May throw return data; } catch (STObject::FieldErr const& e) { std::cerr << "template_mismatch: " << e.what() << "\n"; error = template_mismatch(inName); } catch (std::exception const&) { error = invalid_data(json_name); } return std::nullopt; } static std::optional parseArray( std::string const& json_name, Json::Value const& json, SField const& inName, int depth, Json::Value& error) { if (!json.isArrayOrNull()) { error = not_an_array(json_name); return std::nullopt; } if (depth > maxDepth) { error = too_deep(json_name); return std::nullopt; } try { STArray tail(inName); for (Json::UInt i = 0; json.isValidIndex(i); ++i) { bool const isObjectOrNull(json[i].isObjectOrNull()); bool const singleKey(isObjectOrNull ? json[i].size() == 1 : true); if (!isObjectOrNull || !singleKey) { // null values are !singleKey error = singleton_expected(json_name, i); return std::nullopt; } // TODO: There doesn't seem to be a nice way to get just the // first/only key in an object without copying all keys into // a vector std::string const memberName(json[i].getMemberNames()[0]); ; auto const& nameField(SField::getField(memberName)); if (nameField == sfInvalid) { error = unknown_field(json_name, memberName); return std::nullopt; } Json::Value const objectFields(json[i][memberName]); std::stringstream ss; ss << json_name << "." << "[" << i << "]." << memberName; auto ret = parseObject(ss.str(), objectFields, nameField, depth + 1, error); if (!ret) { std::string const errMsg = error["error_message"].asString(); error["error_message"] = "Error at '" + ss.str() + "'. " + errMsg; return std::nullopt; } if (ret->getFName().fieldType != STI_OBJECT) { ss << "Field type: " << ret->getFName().fieldType << " "; error = non_object_in_array(ss.str(), i); return std::nullopt; } tail.push_back(std::move(*ret)); } return detail::make_stvar(std::move(tail)); } catch (std::exception const&) { error = invalid_data(json_name); return std::nullopt; } } } // namespace STParsedJSONDetail //------------------------------------------------------------------------------ STParsedJSONObject::STParsedJSONObject(std::string const& name, Json::Value const& json) { using namespace STParsedJSONDetail; object = parseObject(name, json, sfGeneric, 0, error); } } // namespace xrpl