2727import java .lang .reflect .InvocationHandler ;
2828import java .lang .reflect .Method ;
2929import java .lang .reflect .Proxy ;
30+ import java .util .Collections ;
3031import java .util .HashMap ;
32+ import java .util .HashSet ;
33+ import java .util .List ;
3134import java .util .Map ;
35+ import java .util .Set ;
3236import java .util .concurrent .TimeoutException ;
3337
3438/**
5458 * <p>
5559 * {@link JsonRpcClient} delegates JSON parsing and generating to
5660 * a {@link JsonRpcMapper}.
61+ * <p>
62+ * By default only a safe set of return types is accepted from the remote
63+ * service description (primitives, {@link String}, {@link Map}, {@link List},
64+ * and common boxed types). Services that return custom types must pass an
65+ * explicit allowlist via the constructors that accept {@code allowedReturnTypes}.
5766 *
5867 * @see #call(String, Object[])
5968 * @see JsonRpcMapper
6271public class JsonRpcClient extends RpcClient implements InvocationHandler {
6372
6473 private static final Logger LOGGER = LoggerFactory .getLogger (JsonRpcClient .class );
74+
75+ // Types that are safe to deserialize into without an explicit allowlist.
76+ private static final Set <String > SAFE_RETURN_TYPES ;
77+
78+ static {
79+ Set <String > types = new HashSet <>();
80+ types .add ("void" );
81+ types .add ("boolean" );
82+ types .add ("int" );
83+ types .add ("long" );
84+ types .add ("double" );
85+ types .add ("float" );
86+ types .add ("short" );
87+ types .add ("byte" );
88+ types .add ("char" );
89+ types .add (String .class .getName ());
90+ types .add (Boolean .class .getName ());
91+ types .add (Integer .class .getName ());
92+ types .add (Long .class .getName ());
93+ types .add (Double .class .getName ());
94+ types .add (Float .class .getName ());
95+ types .add (Short .class .getName ());
96+ types .add (Byte .class .getName ());
97+ types .add (Number .class .getName ());
98+ types .add (Map .class .getName ());
99+ types .add (List .class .getName ());
100+ types .add (Object .class .getName ());
101+ SAFE_RETURN_TYPES = Collections .unmodifiableSet (types );
102+ }
103+
65104 private final JsonRpcMapper mapper ;
105+ private final Set <String > allowedReturnTypes ;
66106 /**
67107 * Holds the JSON-RPC service description for this client.
68108 */
69109 private ServiceDescription serviceDescription ;
70110
71111 /**
72- * Construct a new {@link JsonRpcClient}, passing the {@link RpcClientParams} through {@link RpcClient}'s constructor.
112+ * Construct a new {@link JsonRpcClient} that only accepts return types from the built-in
113+ * safe set (primitives, {@link String}, {@link Map}, {@link List}, common boxed types).
73114 * <p>
74- * The service description record is
75- * retrieved from the server during construction.
115+ * Services that declare custom return types will cause construction to fail with an
116+ * {@link IllegalStateException}. Use
117+ * {@link #JsonRpcClient(RpcClientParams, JsonRpcMapper, Set)} to allow additional types.
76118 *
77- * @param rpcClientParams
78- * @param mapper
79- * @throws IOException
80- * @throws JsonRpcException
81- * @throws TimeoutException
119+ * @throws IllegalStateException if the service description contains a return type not in
120+ * the allowlist
82121 */
83122 public JsonRpcClient (RpcClientParams rpcClientParams , JsonRpcMapper mapper )
84123 throws IOException , JsonRpcException , TimeoutException {
124+ this (rpcClientParams , mapper , Collections .emptySet ());
125+ }
126+
127+ /**
128+ * Construct a new {@link JsonRpcClient} with an explicit allowlist of permitted return types.
129+ * <p>
130+ * The provided types are merged with the built-in safe set, so common types ({@link String},
131+ * {@link Map}, primitives, etc.) do not need to be listed explicitly.
132+ *
133+ * @param allowedReturnTypes additional return types to accept from the remote service
134+ * description, on top of the built-in safe set
135+ * @throws IllegalStateException if the service description contains a return type not in
136+ * the allowlist
137+ */
138+ public JsonRpcClient (RpcClientParams rpcClientParams , JsonRpcMapper mapper , Set <Class <?>> allowedReturnTypes )
139+ throws IOException , JsonRpcException , TimeoutException {
85140 super (rpcClientParams );
86141 this .mapper = mapper ;
142+ this .allowedReturnTypes = buildAllowedReturnTypes (allowedReturnTypes );
87143 retrieveServiceDescription ();
144+ validateReturnTypes ();
88145 }
89146
90147 /**
91- * Construct a new JsonRpcClient, passing the parameters through
92- * to RpcClient's constructor. The service description record is
93- * retrieved from the server during construction.
148+ * Construct a new JsonRpcClient using the safe default return-type allowlist.
94149 *
95150 * @throws TimeoutException if a response is not received within the timeout specified, if any
151+ * @throws IllegalStateException if the service description contains a return type not in
152+ * the allowlist
96153 */
97154 public JsonRpcClient (Channel channel , String exchange , String routingKey , int timeout , JsonRpcMapper mapper )
98155 throws IOException , JsonRpcException , TimeoutException {
99- super (new RpcClientParams ()
100- .channel (channel )
101- .exchange (exchange )
102- .routingKey (routingKey )
103- .timeout (timeout )
104- );
105- this .mapper = mapper ;
106- retrieveServiceDescription ();
156+ this (
157+ new RpcClientParams ().channel (channel ).exchange (exchange ).routingKey (routingKey ).timeout (timeout ),
158+ mapper );
107159 }
108160
109161 /**
110- * Construct a new JsonRpcClient, passing the parameters through
111- * to RpcClient's constructor. The service description record is
112- * retrieved from the server during construction.
162+ * Construct a new JsonRpcClient with an explicit allowlist of permitted return types.
163+ *
164+ * @param allowedReturnTypes additional return types to accept from the remote service
165+ * description, on top of the built-in safe set
166+ * @throws TimeoutException if a response is not received within the timeout specified, if any
167+ * @throws IllegalStateException if the service description contains a return type not in
168+ * the allowlist
169+ */
170+ public JsonRpcClient (Channel channel , String exchange , String routingKey , int timeout , JsonRpcMapper mapper ,
171+ Set <Class <?>> allowedReturnTypes )
172+ throws IOException , JsonRpcException , TimeoutException {
173+ this (
174+ new RpcClientParams ().channel (channel ).exchange (exchange ).routingKey (routingKey ).timeout (timeout ),
175+ mapper ,
176+ allowedReturnTypes );
177+ }
178+
179+ /**
180+ * Construct a new JsonRpcClient using the safe default return-type allowlist.
113181 *
114182 * @throws TimeoutException if a response is not received within the timeout specified, if any
115183 */
@@ -123,6 +191,29 @@ public JsonRpcClient(Channel channel, String exchange, String routingKey)
123191 this (channel , exchange , routingKey , RpcClient .NO_TIMEOUT );
124192 }
125193
194+ private static Set <String > buildAllowedReturnTypes (Set <Class <?>> extraTypes ) {
195+ if (extraTypes == null || extraTypes .isEmpty ()) {
196+ return SAFE_RETURN_TYPES ;
197+ }
198+ Set <String > types = new HashSet <>(SAFE_RETURN_TYPES );
199+ for (Class <?> c : extraTypes ) {
200+ types .add (c .getName ());
201+ }
202+ return Collections .unmodifiableSet (types );
203+ }
204+
205+ private void validateReturnTypes () {
206+ for (ProcedureDescription proc : serviceDescription .getProcs ()) {
207+ String javaReturnType = proc .getJavaReturnType ();
208+ if (javaReturnType != null && !allowedReturnTypes .contains (javaReturnType )) {
209+ throw new IllegalStateException (
210+ "Return type not in allowlist: '"
211+ + javaReturnType
212+ + "'. Pass it via the allowedReturnTypes constructor parameter." );
213+ }
214+ }
215+ }
216+
126217 /**
127218 * Private API - used by {@link #call(String[])} to ad-hoc convert
128219 * strings into the required data types for a call.
0 commit comments