Skip to content

Commit dea7fc1

Browse files
Escape JSON request bodies to handle special characters safely
1 parent e8c9b3d commit dea7fc1

2 files changed

Lines changed: 42 additions & 20 deletions

File tree

src/main/java/co/ipregistry/api/client/request/DefaultRequestHandler.java

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import co.ipregistry.api.client.model.UserAgentList;
2626
import co.ipregistry.api.client.model.error.LookupError;
2727
import co.ipregistry.api.client.options.IpregistryOption;
28+
import com.fasterxml.jackson.core.JsonProcessingException;
2829
import com.fasterxml.jackson.databind.DeserializationFeature;
2930
import com.fasterxml.jackson.databind.ObjectMapper;
3031
import org.apache.hc.client5.http.config.RequestConfig;
@@ -40,11 +41,10 @@
4041
import java.io.IOException;
4142
import java.net.URLEncoder;
4243
import java.nio.charset.StandardCharsets;
44+
import java.util.ArrayList;
4345
import java.util.Arrays;
44-
import java.util.Iterator;
46+
import java.util.List;
4547
import java.util.concurrent.TimeUnit;
46-
import java.util.stream.Collectors;
47-
import java.util.stream.StreamSupport;
4848

4949

5050
/**
@@ -207,20 +207,9 @@ public IpInfoList lookup(final Iterable<String> ips, final IpregistryOption... o
207207

208208
@Override
209209
public UserAgentList parse(final String... userAgents) throws ApiException, ClientException {
210-
final StringBuilder buffer = new StringBuilder();
211-
final Iterator<String> iterator = Arrays.stream(userAgents).iterator();
212-
while (iterator.hasNext()) {
213-
buffer.append('"');
214-
buffer.append(iterator.next());
215-
buffer.append('"');
216-
if (iterator.hasNext()) {
217-
buffer.append(',');
218-
}
219-
}
220-
221210
try {
222211
final Object result = Request.post(config.getBaseUrl() + "/user_agent")
223-
.bodyString("[" + buffer + "]", ContentType.APPLICATION_JSON)
212+
.bodyString(toJsonList(Arrays.asList(userAgents)), ContentType.APPLICATION_JSON)
224213
.addHeader("authorization", "ApiKey " + config.getApiKey())
225214
.connectTimeout(Timeout.ofMilliseconds(config.getConnectionTimeout()))
226215
.responseTimeout(Timeout.ofMilliseconds(config.getSocketTimeout()))
@@ -246,11 +235,19 @@ public UserAgentList parse(final String... userAgents) throws ApiException, Clie
246235
}
247236
}
248237

249-
private String toJsonList(final Iterable<String> ips) {
250-
return '[' + StreamSupport
251-
.stream(ips.spliterator(), false)
252-
.map(ip -> "\"" + ip + "\"")
253-
.collect(Collectors.joining(",")) + ']';
238+
/**
239+
* Serializes the specified {@code values} into a JSON array, escaping each value so that
240+
* characters such as double quotes, backslashes, and control characters produce a valid
241+
* JSON body rather than a malformed or injectable one.
242+
*
243+
* @param values the values to serialize.
244+
* @return a JSON array representation of the specified {@code values}.
245+
* @throws JsonProcessingException if the values cannot be serialized.
246+
*/
247+
String toJsonList(final Iterable<String> values) throws JsonProcessingException {
248+
final List<String> list = new ArrayList<>();
249+
values.forEach(list::add);
250+
return objectMapper.writeValueAsString(list);
254251
}
255252

256253
@Override

src/test/java/co/ipregistry/api/client/request/DefaultRequestHandlerTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818

1919
import co.ipregistry.api.client.IpregistryConfig;
2020
import co.ipregistry.api.client.options.IpregistryOption;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
2122
import org.junit.jupiter.api.Assertions;
2223
import org.junit.jupiter.api.Test;
2324

25+
import java.util.Arrays;
26+
2427
class DefaultRequestHandlerTest {
2528

2629
@Test
@@ -35,4 +38,26 @@ void testBuildApiUrl_optionsEncoded() {
3538
Assertions.assertEquals(config.getBaseUrl() + "/8.8.8.8?test=%5Btest%5D", url);
3639
}
3740

41+
@Test
42+
void testToJsonListEscapesSpecialCharacters() throws Exception {
43+
final IpregistryConfig config =
44+
IpregistryConfig.builder().apiKey("test").build();
45+
final DefaultRequestHandler requestHandler =
46+
new DefaultRequestHandler(config);
47+
48+
final String[] values = {
49+
"Mozilla/5.0 \"quoted\"",
50+
"back\\slash",
51+
"line\nbreak",
52+
"comma,and]bracket"
53+
};
54+
55+
final String json = requestHandler.toJsonList(Arrays.asList(values));
56+
57+
// The produced body must be valid JSON that round-trips to the exact input values.
58+
// The previous string-concatenation implementation produced malformed JSON for these inputs.
59+
final String[] parsed = new ObjectMapper().readValue(json, String[].class);
60+
Assertions.assertArrayEquals(values, parsed);
61+
}
62+
3863
}

0 commit comments

Comments
 (0)