package com.faforever.client.remote; import com.faforever.client.remote.domain.SerializableMessage; import com.faforever.client.remote.io.QDataWriter; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.stream.JsonWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.serializer.Serializer; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.Arrays; public class JsonMessageSerializer<T extends SerializableMessage> implements Serializer<T> { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final String CONFIDENTIAL_INFORMATION_MASK = "********"; private Gson gson; // TODO Clean this up, such that the message is logged within ServerWriter and everything makes much more sense @Override public void serialize(SerializableMessage message, OutputStream outputStream) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Writer jsonStringWriter = new StringWriter(); // Serialize the object into a StringWriter which is later send as one string block with its size prepended. getGson().toJson(message, message.getClass(), fixedJsonWriter(jsonStringWriter)); QDataWriter qDataWriter = new QDataWriter(byteArrayOutputStream); qDataWriter.append(jsonStringWriter.toString()); appendMore(qDataWriter); byte[] byteArray = byteArrayOutputStream.toByteArray(); if (logger.isDebugEnabled()) { // Remove the first 4 bytes which contain the length of the following data String data = new String(Arrays.copyOfRange(byteArray, 4, byteArray.length), StandardCharsets.UTF_16BE); for (String stringToMask : message.getStringsToMask()) { data = data.replace("\"" + stringToMask + "\"", "\"" + CONFIDENTIAL_INFORMATION_MASK + "\""); } logger.debug("Writing to server: {}", data); } outputStream.write(byteArray); } private Gson getGson() { if (gson == null) { GsonBuilder gsonBuilder = new GsonBuilder() .disableHtmlEscaping() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); addTypeAdapters(gsonBuilder); gson = gsonBuilder.create(); } return gson; } private JsonWriter fixedJsonWriter(Writer writer) { // Does GSON suck because its separator can't be set, or python because it can't handle JSON without a space after colon? try { JsonWriter jsonWriter = new JsonWriter(writer); jsonWriter.setSerializeNulls(false); Field separatorField = JsonWriter.class.getDeclaredField("separator"); separatorField.setAccessible(true); separatorField.set(jsonWriter, ": "); return jsonWriter; } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } /** * Allows subclasses to append more stuff after the serialized JSON. Default implementation does nothing, so super * doesn't need to be called. */ protected void appendMore(QDataWriter qDataWriter) throws IOException { // To be overridden by subclasses, if desired } /** * Allows subclasses to register additional type adapters. Super doesn't need to be called. */ protected void addTypeAdapters(GsonBuilder gsonBuilder) { // To be overridden by subclasses, if desired } }