Skip to content

Commit cdede9e

Browse files
committed
Moving most of the sanitization logic and tests to JsonTelemetryDataSerializer, updating tests
1 parent 0e1e4cd commit cdede9e

File tree

2 files changed

+143
-45
lines changed

2 files changed

+143
-45
lines changed

core/src/main/java/com/microsoft/applicationinsights/telemetry/JsonTelemetryDataSerializer.java

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
import com.google.common.base.Strings;
2525
import com.microsoft.applicationinsights.internal.schemav2.DataPointType;
2626
import com.microsoft.applicationinsights.internal.util.LocalStringsUtils;
27+
import org.apache.http.annotation.Obsolete;
2728

2829
import java.io.BufferedWriter;
2930
import java.io.IOException;
3031
import java.io.StringWriter;
3132
import java.io.Writer;
33+
import java.text.StringCharacterIterator;
3234
import java.util.*;
3335

3436
/**
@@ -178,14 +180,26 @@ public void write(String name, Date value) throws IOException {
178180
separator = JSON_SEPARATOR;
179181
}
180182

183+
@Obsolete
181184
public void write(String name, String value) throws IOException {
182-
if (value == null) {
185+
//This method is practically not used anywhere .Will be removed with all other
186+
//obsolete classes in next major release
187+
}
188+
189+
public void write(String name, String value, int len, boolean isReq) throws IOException {
190+
if (value == null && !isReq) {
183191
return;
184192
}
185193

194+
//If field is required and not present set default value
195+
if (value == null && isReq) {
196+
value = "DEFAULT " + name;
197+
}
198+
186199
writeName(name);
187200
out.write(JSON_COMMA);
188-
writeEscapedString(value);
201+
String sanitizedValue = sanitizeStringForJSON(value, len);
202+
out.write(sanitizedValue);
189203
out.write(JSON_COMMA);
190204
separator = JSON_SEPARATOR;
191205
}
@@ -207,6 +221,7 @@ public <T extends JsonSerializable> void write(String name, T value) throws IOEx
207221
}
208222

209223
public <T> void write(String name, Map<String, T> map) throws IOException {
224+
210225
if (map == null) {
211226
return;
212227
}
@@ -220,7 +235,7 @@ public <T> void write(String name, Map<String, T> map) throws IOException {
220235

221236
separator = "";
222237
for (Map.Entry<String, T> entry : map.entrySet()) {
223-
writeName(entry.getKey());
238+
writeName(sanitizeKey(entry.getKey()));
224239
write(entry.getValue());
225240
separator = JSON_SEPARATOR;
226241
}
@@ -232,6 +247,7 @@ public <T> void write(String name, Map<String, T> map) throws IOException {
232247
}
233248
}
234249

250+
235251
public <T> void write(String name, List<T> list) throws IOException {
236252
if (list == null) {
237253
return;
@@ -273,7 +289,7 @@ private <T> void write(T item) throws IOException {
273289
out.write(String.valueOf(item));
274290
} else {
275291
String truncatedName = truncate(String.valueOf(item), 8192);
276-
String sanitizedItem = SanitizationUtils.sanitizeStringForJSON(truncatedName, false);
292+
String sanitizedItem = sanitizeStringForJSON(truncatedName, 8192);
277293
out.write(JSON_COMMA);
278294
out.write(sanitizedItem);
279295
out.write(JSON_COMMA);
@@ -296,11 +312,10 @@ private <T extends JsonSerializable> String createJsonFor(T value) throws IOExce
296312
}
297313

298314
private void writeName(String name) throws IOException {
299-
String truncatedString = truncate(name, 150);
300-
String sanitizedName = SanitizationUtils.sanitizeStringForJSON(truncatedString, true);
315+
301316
out.write(separator);
302317
out.write(JSON_COMMA);
303-
out.write(sanitizedName);
318+
out.write(name);
304319
out.write(JSON_COMMA);
305320
out.write(JSON_NAME_VALUE_SEPARATOR);
306321
}
@@ -320,35 +335,76 @@ private static Set<Class<?>> getWrapperTypes()
320335
return ret;
321336
}
322337

323-
protected void writeEscapedString(String value) throws IOException {
324-
for (char c : value.toCharArray()) {
325-
switch (c) {
326-
case '\\':
327-
out.write("\\\\");
328-
break;
329-
case '"':
330-
out.write("\\\"");
331-
break;
332-
case '\n':
333-
out.write("\\n");
334-
break;
335-
case '\b':
336-
out.write("\\b");
337-
break;
338-
case '\f':
339-
out.write("\\f");
340-
break;
341-
case '\r':
342-
out.write("\\r");
343-
break;
344-
case '\t':
345-
out.write("\\t");
346-
break;
347-
default:
348-
out.write(c);
338+
private String sanitizeStringForJSON(String text, int maxLength) {
339+
340+
final StringBuilder result = new StringBuilder();
341+
StringCharacterIterator iterator = new StringCharacterIterator(text);
342+
for (char curr = iterator.current(); curr != iterator.DONE && result.length() < maxLength - 2; curr = iterator.next()) {
343+
if( curr == '\"' ){
344+
result.append("\\\"");
345+
}
346+
else if (curr == '\'') {
347+
result.append("\\\'");
348+
}
349+
else if(curr == '\\'){
350+
result.append("\\\\");
351+
}
352+
else if(curr == '/'){
353+
result.append("\\/");
354+
}
355+
else if(curr == '\b'){
356+
result.append("\\b");
357+
}
358+
else if(curr == '\f'){
359+
result.append("\\f");
360+
}
361+
else if(curr == '\n'){
362+
result.append("\\n");
363+
}
364+
else if(curr == '\r'){
365+
result.append("\\r");
366+
}
367+
else if(curr == '\t'){
368+
result.append("\\t");
369+
}
370+
else if (!Character.isISOControl(curr)){
371+
result.append(curr);
372+
}
373+
else {
374+
if (result.length() + 7 < maxLength) { // needs 7 more character space to be appended
375+
result.append("\\u");
376+
result.append((String.format( "%04x", Integer.valueOf(curr))));
377+
}
378+
else {
349379
break;
380+
}
350381
}
351382
}
383+
return result.toString();
384+
}
385+
386+
private String sanitizeKey(String key) {
387+
String sanitizedKey = trimAndTruncate(key, 150);
388+
sanitizedKey = sanitizeStringForJSON(sanitizedKey, 150);
389+
sanitizedKey = MakeKeyNonEmpty(sanitizedKey);
390+
return sanitizedKey;
391+
}
392+
393+
private static String trimAndTruncate(String value, int maxLength) {
394+
if (value == null) {
395+
return value;
396+
}
397+
398+
String sanitized = value.trim();
399+
if (sanitized.length() > maxLength) {
400+
sanitized = sanitized.substring(0, maxLength);
401+
}
402+
403+
return sanitized;
404+
}
405+
406+
private String MakeKeyNonEmpty(String key) {
407+
return Strings.isNullOrEmpty(key) ? "(required property name is empty)" : key;
352408
}
353409

354410
private String truncate(String value, int len) {

core/src/test/java/com/microsoft/applicationinsights/telemetry/JsonTelemetryDataSerializerTest.java

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121

2222
package com.microsoft.applicationinsights.telemetry;
2323

24-
import java.io.BufferedWriter;
24+
import com.google.gson.Gson;
25+
import com.google.gson.reflect.TypeToken;
26+
import org.junit.Test;
27+
2528
import java.io.IOException;
2629
import java.io.Serializable;
2730
import java.io.StringWriter;
@@ -30,20 +33,18 @@
3033
import java.util.List;
3134
import java.util.Map;
3235

33-
import com.google.gson.Gson;
34-
import org.junit.Test;
35-
3636
import static org.junit.Assert.assertEquals;
37+
import static org.junit.Assert.assertNotEquals;
3738

3839
public class JsonTelemetryDataSerializerTest {
39-
private final static class TestClassWithStrings implements JsonSerializable, Serializable {
40+
private final static class TestClassWithStrings implements JsonSerializable, Serializable {
4041
private String s1;
4142
private String s2;
4243

4344
@Override
4445
public void serialize(JsonTelemetryDataSerializer serializer) throws IOException {
45-
serializer.write("s1", s1);
46-
serializer.write("s2", s2);
46+
serializer.write("s1", s1, 100, true);
47+
serializer.write("s2", s2, 15, false);
4748
}
4849

4950
public String getS1() {
@@ -155,10 +156,10 @@ public void setList1(List<String> list1) {
155156
public void serialize(JsonTelemetryDataSerializer serializer) throws IOException {
156157
serializer.write("i1", i1);
157158
serializer.write("i2", i2);
158-
serializer.write("s1", s1);
159+
serializer.write("s1", s1, 10, true);
159160
serializer.write("l1", l1);
160161
serializer.write("l2", l2);
161-
serializer.write("s2", s2);
162+
serializer.write("s2", s2, 15, false);
162163
serializer.write("m1", m1);
163164
serializer.write("list1", list1);
164165
}
@@ -189,7 +190,6 @@ public void testStrings() throws IOException {
189190
testClassWithStrings.setS2("s2");
190191

191192
StringWriter stringWriter = new StringWriter();
192-
BufferedWriter bufferedWriter = new BufferedWriter(stringWriter);
193193
JsonTelemetryDataSerializer tested = new JsonTelemetryDataSerializer(stringWriter);
194194
testClassWithStrings.serialize(tested);
195195
tested.close();
@@ -198,6 +198,42 @@ public void testStrings() throws IOException {
198198
assertEquals(bac, testClassWithStrings);
199199
}
200200

201+
//This is to test if the write method with name parameters work
202+
@Test
203+
public void testLengthOfStrings() throws IOException {
204+
TestClassWithStrings testClassWithStrings = new TestClassWithStrings();
205+
String s1 = TelemetryTestsUtils.createString(110);
206+
testClassWithStrings.setS1(s1);
207+
testClassWithStrings.setS2("abc");
208+
StringWriter stringWriter = new StringWriter();
209+
JsonTelemetryDataSerializer tested = new JsonTelemetryDataSerializer(stringWriter);
210+
testClassWithStrings.serialize(tested);
211+
tested.close();
212+
String str = stringWriter.toString();
213+
TestClassWithStrings bac = new Gson().fromJson(str, TestClassWithStrings.class);
214+
Map<String, String> recoveryMap = new Gson().fromJson(str, new TypeToken<HashMap<String, String>>() {}.getType());
215+
assertNotEquals((recoveryMap.get("s1")).length(), s1.length());
216+
assertNotEquals(bac, testClassWithStrings);
217+
}
218+
219+
220+
@Test
221+
public void testSanitization() throws IOException {
222+
TestClassWithStrings testClassWithStrings = new TestClassWithStrings();
223+
String s1 = "\\'\\f\\b\\f\\n\\r\\t/\\";
224+
String s2 = "0x0021\t";
225+
testClassWithStrings.setS1(s1);
226+
testClassWithStrings.setS2(s2);
227+
StringWriter stringWriter = new StringWriter();
228+
JsonTelemetryDataSerializer tested = new JsonTelemetryDataSerializer(stringWriter);
229+
testClassWithStrings.serialize(tested);
230+
tested.close();
231+
String str = stringWriter.toString();
232+
Map<String, String> recoveryMap = new Gson().fromJson(str, new TypeToken<HashMap<String, String>>() {}.getType());
233+
assertEquals(recoveryMap.get("s1"), "\\'\\f\\b\\f\\n\\r\\t/\\");
234+
assertEquals(recoveryMap.get("s2"), "0x0021\t");
235+
236+
}
201237
@Test
202238
public void test() throws IOException {
203239
StubClass stubClass = new StubClass();
@@ -210,15 +246,21 @@ public void test() throws IOException {
210246
stubClass.getM1().put("key1", 5);
211247
stubClass.getM1().put("key2", 6);
212248
stubClass.getM1().put("key3", 7);
213-
214249
StringWriter stringWriter = new StringWriter();
215-
BufferedWriter bufferedWriter = new BufferedWriter(stringWriter);
216250
JsonTelemetryDataSerializer tested = new JsonTelemetryDataSerializer(stringWriter);
217251
stubClass.serialize(tested);
218252
tested.close();
219253
String str = stringWriter.toString();
220254
StubClass bac = new Gson().fromJson(str, StubClass.class);
255+
assertEquals(bac.i1, stubClass.i1);
256+
assertEquals(bac.i2, stubClass.i2);
257+
assertEquals(bac.l1, stubClass.l1);
258+
assertEquals(bac.l2, stubClass.l2);
259+
assertEquals(bac.list1, stubClass.list1);
260+
assertEquals(bac.m1, stubClass.m1);
221261
// System.out.println(str);
222262
}
223263

264+
265+
224266
}

0 commit comments

Comments
 (0)