diff --git a/app/src/main/java/io/xpipe/app/pwman/KeePassXcMigrationDeserializer.java b/app/src/main/java/io/xpipe/app/pwman/KeePassXcMigrationDeserializer.java new file mode 100644 index 000000000..9bf8ea043 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/pwman/KeePassXcMigrationDeserializer.java @@ -0,0 +1,64 @@ +package io.xpipe.app.pwman; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TreeTraversingParser; + +import java.io.IOException; + +public class KeePassXcMigrationDeserializer extends DelegatingDeserializer { + + public KeePassXcMigrationDeserializer(JsonDeserializer d) { + super(d); + } + + @Override + protected JsonDeserializer newDelegatingInstance(JsonDeserializer newDelegatee) { + return new KeePassXcMigrationDeserializer(newDelegatee); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return super.deserialize(restructure(p), ctxt); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException { + return super.deserialize(restructure(p), ctxt, intoValue); + } + + public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) + throws IOException { + return super.deserializeWithType(restructure(jp), ctxt, typeDeserializer); + } + + public JsonParser restructure(JsonParser p) throws IOException { + var node = p.readValueAsTree(); + if (node == null) { + return p; + } + + if (node.isObject()) { + var newJsonParser = new TreeTraversingParser(migrate((ObjectNode) node), p.getCodec()); + newJsonParser.nextToken(); + return newJsonParser; + } + + var newJsonParser = new TreeTraversingParser((JsonNode) node, p.getCodec()); + newJsonParser.nextToken(); + return newJsonParser; + } + + private ArrayNode migrate(ObjectNode containerNode) { + var array = JsonNodeFactory.instance.arrayNode(); + array.add(containerNode); + return array; + } +} diff --git a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java index 26b2a6411..d1d85d030 100644 --- a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java +++ b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java @@ -1,9 +1,17 @@ package io.xpipe.app.util; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; +import com.fasterxml.jackson.databind.type.ArrayType; +import com.fasterxml.jackson.databind.type.CollectionLikeType; +import com.fasterxml.jackson.databind.type.CollectionType; +import io.xpipe.app.browser.file.BrowserHistorySavedState; import io.xpipe.app.ext.HostAddress; import io.xpipe.app.process.ShellDialect; import io.xpipe.app.process.ShellDialects; import io.xpipe.app.process.ShellScript; +import io.xpipe.app.pwman.KeePassXcAssociationKey; +import io.xpipe.app.pwman.KeePassXcMigrationDeserializer; import io.xpipe.app.pwman.PasswordManager; import io.xpipe.app.rdp.ExternalRdpClient; import io.xpipe.app.secret.EncryptedValue; @@ -35,6 +43,8 @@ import com.fasterxml.jackson.databind.type.SimpleType; import java.io.CharArrayReader; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.UUID; import java.util.stream.Stream; @@ -81,8 +91,25 @@ public class AppJacksonModule extends SimpleModule { context.registerSubtypes(ExternalVncClient.getClasses()); context.registerSubtypes(ExternalRdpClient.getClasses()); - context.addSerializers(_serializers); - context.addDeserializers(_deserializers); + setDeserializerModifier(new BeanDeserializerModifier() { + + private final JavaType keePassType = + JacksonMapper.getDefault().getTypeFactory().constructCollectionLikeType(ArrayList.class, KeePassXcAssociationKey.class); + + @Override + public JsonDeserializer modifyCollectionDeserializer( + DeserializationConfig config, CollectionType type, BeanDescription beanDesc, + JsonDeserializer deserializer + ) { + if (beanDesc.getType().equals(keePassType)) { + return new KeePassXcMigrationDeserializer(deserializer); + } + + return deserializer; + } + }); + + super.setupModule(context); } public static class OsTypeSerializer extends JsonSerializer {