diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index 1035b0aba59..c3e4025ebb4 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -67,13 +67,21 @@ doLedgerEntry(RPC::JsonContext& context) } else if (context.params.isMember(jss::account_root)) { - expectedType = ltACCOUNT_ROOT; - auto const account = parseBase58( - context.params[jss::account_root].asString()); - if (!account || account->isZero()) - jvResult[jss::error] = "malformedAddress"; + if (context.apiVersion > 1u && + !context.params[jss::account_root].isString()) + { + jvResult[jss::error] = "invalidParams"; + } else - uNodeIndex = keylet::account(*account).key; + { + expectedType = ltACCOUNT_ROOT; + auto const account = parseBase58( + context.params[jss::account_root].asString()); + if (!account || account->isZero()) + jvResult[jss::error] = "malformedAddress"; + else + uNodeIndex = keylet::account(*account).key; + } } else if (context.params.isMember(jss::check)) { @@ -178,6 +186,12 @@ doLedgerEntry(RPC::JsonContext& context) // May not specify both dir_root and owner. jvResult[jss::error] = "malformedRequest"; } + else if ( + context.apiVersion > 1u && + !context.params[jss::directory][jss::dir_root].isString()) + { + jvResult[jss::error] = "invalidParams"; + } else if (!uDirRoot.parseHex( context.params[jss::directory][jss::dir_root] .asString())) @@ -192,17 +206,26 @@ doLedgerEntry(RPC::JsonContext& context) } else if (context.params[jss::directory].isMember(jss::owner)) { - auto const ownerID = parseBase58( - context.params[jss::directory][jss::owner].asString()); - - if (!ownerID) + if (context.apiVersion > 1u && + !context.params[jss::directory][jss::owner].isString()) { - jvResult[jss::error] = "malformedAddress"; + jvResult[jss::error] = "invalidParams"; } else { - uNodeIndex = - keylet::page(keylet::ownerDir(*ownerID), uSubIndex).key; + auto const ownerID = parseBase58( + context.params[jss::directory][jss::owner].asString()); + + if (!ownerID) + { + jvResult[jss::error] = "malformedAddress"; + } + else + { + uNodeIndex = + keylet::page(keylet::ownerDir(*ownerID), uSubIndex) + .key; + } } } else @@ -216,7 +239,13 @@ doLedgerEntry(RPC::JsonContext& context) expectedType = ltESCROW; if (!context.params[jss::escrow].isObject()) { - if (!uNodeIndex.parseHex(context.params[jss::escrow].asString())) + if (context.apiVersion > 1u && + !context.params[jss::escrow].isString()) + { + jvResult[jss::error] = "invalidParams"; + } + else if (!uNodeIndex.parseHex( + context.params[jss::escrow].asString())) { uNodeIndex = beast::zero; jvResult[jss::error] = "malformedRequest"; @@ -229,6 +258,12 @@ doLedgerEntry(RPC::JsonContext& context) { jvResult[jss::error] = "malformedRequest"; } + else if ( + context.apiVersion > 1u && + !context.params[jss::escrow][jss::owner].isString()) + { + jvResult[jss::error] = "invalidParams"; + } else { auto const id = parseBase58( @@ -247,7 +282,13 @@ doLedgerEntry(RPC::JsonContext& context) expectedType = ltOFFER; if (!context.params[jss::offer].isObject()) { - if (!uNodeIndex.parseHex(context.params[jss::offer].asString())) + if (context.apiVersion > 1u && + !context.params[jss::offer].isString()) + { + jvResult[jss::error] = "invalidParams"; + } + else if (!uNodeIndex.parseHex( + context.params[jss::offer].asString())) { uNodeIndex = beast::zero; jvResult[jss::error] = "malformedRequest"; @@ -260,6 +301,12 @@ doLedgerEntry(RPC::JsonContext& context) { jvResult[jss::error] = "malformedRequest"; } + else if ( + context.apiVersion > 1u && + !context.params[jss::offer][jss::account].isString()) + { + jvResult[jss::error] = "invalidParams"; + } else { auto const id = parseBase58( @@ -277,8 +324,13 @@ doLedgerEntry(RPC::JsonContext& context) { expectedType = ltPAYCHAN; - if (!uNodeIndex.parseHex( - context.params[jss::payment_channel].asString())) + if (context.apiVersion > 1u && + !context.params[jss::payment_channel].isString()) + { + jvResult[jss::error] = "invalidParams"; + } + else if (!uNodeIndex.parseHex( + context.params[jss::payment_channel].asString())) { uNodeIndex = beast::zero; jvResult[jss::error] = "malformedRequest"; @@ -312,6 +364,12 @@ doLedgerEntry(RPC::JsonContext& context) { jvResult[jss::error] = "malformedAddress"; } + else if ( + context.apiVersion > 1u && + !jvRippleState[jss::currency].isString()) + { + jvResult[jss::error] = "invalidParams"; + } else if (!to_currency( uCurrency, jvRippleState[jss::currency].asString())) { @@ -328,7 +386,13 @@ doLedgerEntry(RPC::JsonContext& context) expectedType = ltTICKET; if (!context.params[jss::ticket].isObject()) { - if (!uNodeIndex.parseHex(context.params[jss::ticket].asString())) + if (context.apiVersion > 1u && + !context.params[jss::ticket].isString()) + { + jvResult[jss::error] = "invalidParams"; + } + else if (!uNodeIndex.parseHex( + context.params[jss::ticket].asString())) { uNodeIndex = beast::zero; jvResult[jss::error] = "malformedRequest"; @@ -341,6 +405,12 @@ doLedgerEntry(RPC::JsonContext& context) { jvResult[jss::error] = "malformedRequest"; } + else if ( + context.apiVersion > 1u && + !context.params[jss::ticket][jss::account].isString()) + { + jvResult[jss::error] = "invalidParams"; + } else { auto const id = parseBase58( @@ -358,15 +428,6 @@ doLedgerEntry(RPC::JsonContext& context) if (context.params[jss::nft_page].isString()) { - // XRPL Doc states "nft_page" field is a string. check for this - // apiVersion 2 onwards. invalidParam error is thrown if the above - // condition is false - if (context.apiVersion > 1u && - !context.params[jss::nft_page].isString()) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "invalidParams"; - } if (!uNodeIndex.parseHex(context.params[jss::nft_page].asString())) { uNodeIndex = beast::zero; @@ -383,7 +444,11 @@ doLedgerEntry(RPC::JsonContext& context) expectedType = ltAMM; if (!context.params[jss::amm].isObject()) { - if (!uNodeIndex.parseHex(context.params[jss::amm].asString())) + if (context.apiVersion > 1u && !context.params[jss::amm].isString()) + { + jvResult[jss::error] = "invalidParams"; + } + else if (!uNodeIndex.parseHex(context.params[jss::amm].asString())) { uNodeIndex = beast::zero; jvResult[jss::error] = "malformedRequest"; diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index d72ffdf1777..a8e0f7f6c9c 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1223,12 +1223,19 @@ class LedgerRPC_test : public beast::unit_test::suite std::string const ledgerHash{to_string(env.closed()->info().hash)}; + auto makeParams = [&apiVersion](std::function f) { + Json::Value params; + params[jss::api_version] = apiVersion; + f(params); + return params; + }; // "features" is not an option supported by ledger_entry. - Json::Value jvParams; - jvParams[jss::api_version] = apiVersion; { - jvParams[jss::features] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; + auto const jvParams = + makeParams([&ledgerHash](Json::Value& jvParams) { + jvParams[jss::features] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + }); Json::Value const jrr = env.rpc( "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -1237,12 +1244,28 @@ class LedgerRPC_test : public beast::unit_test::suite else checkErrorValue(jrr, "invalidParams", ""); } - // invalid check input + Json::Value const injectObject = []() { + Json::Value obj(Json::objectValue); + obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + obj[jss::ledger_index] = "validated"; + return obj; + }(); + Json::Value const injectArray = []() { + Json::Value arr(Json::arrayValue); + arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + arr[1u] = "validated"; + return arr; + }(); + + // invalid input for fields that can handle an object, but can't handle + // an array + for (auto const& field : + {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm}) { - Json::Value checkParams; - checkParams[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; - checkParams[jss::ledger_index] = "validated"; - jvParams[jss::check] = checkParams; + auto const jvParams = + makeParams([&field, &injectArray](Json::Value& jvParams) { + jvParams[field] = injectArray; + }); Json::Value const jrr = env.rpc( "json", "ledger_entry", to_string(jvParams))[jss::result]; @@ -1252,6 +1275,164 @@ class LedgerRPC_test : public beast::unit_test::suite else checkErrorValue(jrr, "invalidParams", ""); } + // Fields that can handle objects just fine + for (auto const& field : + {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm}) + { + auto const jvParams = + makeParams([&field, &injectObject](Json::Value& jvParams) { + jvParams[field] = injectObject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "malformedRequest", ""); + } + + for (auto const& inject : {injectObject, injectArray}) + { + // invalid input for fields that can't handle an object or an array + for (auto const& field : + {jss::index, + jss::account_root, + jss::check, + jss::payment_channel}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + jvParams[field] = inject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // directory sub-fields + for (auto const& field : {jss::dir_root, jss::owner}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + jvParams[jss::directory][field] = inject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // escrow sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + jvParams[jss::escrow][jss::owner] = inject; + jvParams[jss::escrow][jss::seq] = 99; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // offer sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + jvParams[jss::offer][jss::account] = inject; + jvParams[jss::offer][jss::seq] = 99; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // ripple_state sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + Json::Value rs(Json::objectValue); + rs[jss::currency] = "FOO"; + rs[jss::accounts] = Json::Value(Json::arrayValue); + rs[jss::accounts][0u] = + "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + rs[jss::accounts][1u] = + "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv"; + rs[jss::currency] = inject; + jvParams[jss::ripple_state] = std::move(rs); + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // ticket sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + jvParams[jss::ticket][jss::account] = inject; + jvParams[jss::ticket][jss::ticket_seq] = 99; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + + // Fields that can handle malformed inputs just fine + for (auto const& field : {jss::nft_page, jss::deposit_preauth}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + jvParams[field] = inject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "malformedRequest", ""); + } + // Subfields of deposit_preauth that can handle malformed inputs + // fine + for (auto const& field : {jss::owner, jss::authorized}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + auto pa = Json::Value(Json::objectValue); + pa[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + pa[jss::authorized] = + "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv"; + pa[field] = inject; + jvParams[jss::deposit_preauth] = std::move(pa); + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "malformedRequest", ""); + } + } } /// @brief ledger RPC requests as a way to drive