Skip to content

Commit

Permalink
Merge 66ce529 into 74e37d4
Browse files Browse the repository at this point in the history
  • Loading branch information
Querz committed Jan 29, 2019
2 parents 74e37d4 + 66ce529 commit e37a1fb
Show file tree
Hide file tree
Showing 42 changed files with 489 additions and 402 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ ListTag<FloatTag> fl = new ListTag<>(FloatTag.class);
fl.add(new FloatTag(1.234f);
fl.addFloat(5.678f);
```

#### Nesting
All methods serializing instances or deserializing data track the nesting levels to prevent circular references or malicious data which could, when deserialized, result in thousands of instances causing a denial of service.

These methods have a parameter for the maximum nesting depth they are allowed to traverse. A value of `0` means that only the object itself, but no nested object may be processed.

If an instance is nested further than allowed, a [MaxDepthReachedException](src/main/java/net/querz/nbt/MaxDepthReachedException.java) will be thrown. A negative maximum depth will cause an `IllegalArgumentException`.

Some methods do not provide a parameter to specify the maximum depth, but instead use `Tag.DEFAULT_MAX_DEPTH` (`512`) which is also the maximum used in Minecraft.

---
### Utility
There are several utility methods to make your life easier if you use this library.
Expand Down Expand Up @@ -124,3 +134,8 @@ To be able to use a custom tag with deserialization, a `Supplier` and the custom
```java
TagFactory.registerCustomTag(90, ObjectTag::new, ObjectTag.class);
```
#### Nesting
As mentioned before, serialization and deserialization methods are provided with a parameter indicating the maximum processing depth of the structure. This is not guaranteed when using custom tags, it is the responsibility of the creator of that custom tag to call `Tag#decrementMaxDepth(int)` to correctly update the nesting depth.
It is also highly encouraged to document the custom tag behaviour when it does so to make users aware of the possible exceptions thrown by `Tag#decrementMaxDepth(int)`.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ apply plugin: 'jacoco'

group = 'net.querz.nbt'
archivesBaseName = 'nbt'
version = '3.0'
version = '4.0'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava.options.encoding = 'UTF-8'
Expand Down
14 changes: 3 additions & 11 deletions src/main/java/net/querz/nbt/ArrayTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
public abstract class ArrayTag<T> extends Tag<T> {

public ArrayTag(T value) {
super(value);
if (!value.getClass().isArray()) {
throw new UnsupportedOperationException("type of array tag must be an array");
}
setValue(value);
}

public int length() {
Expand All @@ -27,22 +27,14 @@ public T getValue() {

@Override
public void setValue(T value) {
super.setValue(checkNull(value));
super.setValue(value);
}

@Override
public String valueToString(int depth) {
public String valueToString(int maxDepth) {
return arrayToString("", "");
}

@Override
public int compareTo(Tag<T> other) {
if (!(other instanceof ArrayTag) || this.getClass() != other.getClass()) {
throw new IllegalArgumentException("array types are incompatible");
}
return Integer.compare(Array.getLength(getValue()), Array.getLength(other.getValue()));
}

protected String arrayToString(String prefix, String suffix) {
StringBuilder sb = new StringBuilder("[").append(prefix).append("".equals(prefix) ? "" : ";");
for (int i = 0; i < length(); i++) {
Expand Down
22 changes: 12 additions & 10 deletions src/main/java/net/querz/nbt/ByteArrayTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,33 @@
import java.io.IOException;
import java.util.Arrays;

public class ByteArrayTag extends ArrayTag<byte[]> {
public class ByteArrayTag extends ArrayTag<byte[]> implements Comparable<ByteArrayTag> {

public static final byte[] ZERO_VALUE = new byte[0];

public ByteArrayTag() {
super(new byte[0]);
super(ZERO_VALUE);
}

public ByteArrayTag(byte[] value) {
super(value);
}

@Override
protected byte[] getEmptyValue() {
return new byte[0];
}

@Override
public void serializeValue(DataOutputStream dos, int depth) throws IOException {
public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException {
dos.writeInt(length());
dos.write(getValue());
}

@Override
public void deserializeValue(DataInputStream dis, int depth) throws IOException {
public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException {
int length = dis.readInt();
setValue(new byte[length]);
dis.readFully(getValue());
}

@Override
public String valueToTagString(int depth) {
public String valueToTagString(int maxDepth) {
return arrayToString("B", "b");
}

Expand All @@ -48,6 +45,11 @@ public int hashCode() {
return Arrays.hashCode(getValue());
}

@Override
public int compareTo(ByteArrayTag other) {
return Integer.compare(length(), other.length());
}

@Override
public ByteArrayTag clone() {
return new ByteArrayTag(Arrays.copyOf(getValue(), length()));
Expand Down
24 changes: 14 additions & 10 deletions src/main/java/net/querz/nbt/ByteTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import java.io.DataOutputStream;
import java.io.IOException;

public class ByteTag extends NumberTag<Byte> {
public class ByteTag extends NumberTag<Byte> implements Comparable<ByteTag> {

public ByteTag() {}
public static final byte ZERO_VALUE = 0;

public ByteTag() {
super(ZERO_VALUE);
}

public ByteTag(byte value) {
super(value);
Expand All @@ -16,11 +20,6 @@ public ByteTag(boolean value) {
super((byte) (value ? 1 : 0));
}

@Override
protected Byte getEmptyValue() {
return 0;
}

public boolean asBoolean() {
return getValue() > 0;
}
Expand All @@ -30,17 +29,17 @@ public void setValue(byte value) {
}

@Override
public void serializeValue(DataOutputStream dos, int depth) throws IOException {
public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException {
dos.writeByte(getValue());
}

@Override
public void deserializeValue(DataInputStream dis, int depth) throws IOException {
public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException {
setValue(dis.readByte());
}

@Override
public String valueToTagString(int depth) {
public String valueToTagString(int maxDepth) {
return getValue() + "b";
}

Expand All @@ -49,6 +48,11 @@ public boolean equals(Object other) {
return super.equals(other) && asByte() == ((ByteTag) other).asByte();
}

@Override
public int compareTo(ByteTag other) {
return getValue().compareTo(other.getValue());
}

@Override
public ByteTag clone() {
return new ByteTag(getValue());
Expand Down
72 changes: 37 additions & 35 deletions src/main/java/net/querz/nbt/CompoundTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;

public class CompoundTag extends Tag<Map<String, Tag<?>>> implements Iterable<Map.Entry<String, Tag<?>>> {
public class CompoundTag extends Tag<Map<String, Tag<?>>> implements Iterable<Map.Entry<String, Tag<?>>>, Comparable<CompoundTag> {

public CompoundTag() {}
public CompoundTag() {
super(createEmptyValue());
}

@Override
protected Map<String, Tag<?>> getEmptyValue() {
private static Map<String, Tag<?>> createEmptyValue() {
return new HashMap<>(8);
}

Expand Down Expand Up @@ -123,56 +129,56 @@ public boolean getBoolean(String key) {

public byte getByte(String key) {
ByteTag t = getByteTag(key);
return t == null ? new ByteTag().getEmptyValue() : t.asByte();
return t == null ? ByteTag.ZERO_VALUE : t.asByte();
}

public short getShort(String key) {
ShortTag t = getShortTag(key);
return t == null ? new ShortTag().getEmptyValue() : t.asShort();
return t == null ? ShortTag.ZERO_VALUE : t.asShort();
}

public int getInt(String key) {
IntTag t = getIntTag(key);
return t == null ? new IntTag().getEmptyValue() : t.asInt();
return t == null ? IntTag.ZERO_VALUE : t.asInt();
}

public long getLong(String key) {
LongTag t = getLongTag(key);
return t == null ? new LongTag().getEmptyValue() : t.asLong();
return t == null ? LongTag.ZERO_VALUE : t.asLong();
}

public float getFloat(String key) {
FloatTag t = getFloatTag(key);
return t == null ? new FloatTag().getEmptyValue() : t.asFloat();
return t == null ? FloatTag.ZERO_VALUE : t.asFloat();
}

public double getDouble(String key) {
DoubleTag t = getDoubleTag(key);
return t == null ? new DoubleTag().getEmptyValue() : t.asDouble();
return t == null ? DoubleTag.ZERO_VALUE : t.asDouble();
}

public String getString(String key) {
StringTag t = getStringTag(key);
return t == null ? new StringTag().getEmptyValue() : t.getValue();
return t == null ? StringTag.ZERO_VALUE : t.getValue();
}

public byte[] getByteArray(String key) {
ByteArrayTag t = getByteArrayTag(key);
return t == null ? new ByteArrayTag().getEmptyValue() : t.getValue();
return t == null ? ByteArrayTag.ZERO_VALUE : t.getValue();
}

public int[] getIntArray(String key) {
IntArrayTag t = getIntArrayTag(key);
return t == null ? new IntArrayTag().getEmptyValue() : t.getValue();
return t == null ? IntArrayTag.ZERO_VALUE : t.getValue();
}

public long[] getLongArray(String key) {
LongArrayTag t = getLongArrayTag(key);
return t == null ? new LongArrayTag().getEmptyValue() : t.getValue();
return t == null ? LongArrayTag.ZERO_VALUE : t.getValue();
}

public Tag<?> put(String key, Tag<?> tag) {
return getValue().put(checkNull(key), checkNull(tag));
return getValue().put(Objects.requireNonNull(key), Objects.requireNonNull(tag));
}

public Tag<?> putBoolean(String key, boolean value) {
Expand Down Expand Up @@ -204,63 +210,62 @@ public Tag<?> putDouble(String key, double value) {
}

public Tag<?> putString(String key, String value) {
return put(key, new StringTag(checkNull(value)));
return put(key, new StringTag(value));
}

public Tag<?> putByteArray(String key, byte[] value) {
return put(key, new ByteArrayTag(checkNull(value)));
return put(key, new ByteArrayTag(value));
}

public Tag<?> putIntArray(String key, int[] value) {
return put(key, new IntArrayTag(checkNull(value)));
return put(key, new IntArrayTag(value));
}

public Tag<?> putLongArray(String key, long[] value) {
return put(key, new LongArrayTag(checkNull(value)));
return put(key, new LongArrayTag(value));
}

@Override
public void serializeValue(DataOutputStream dos, int depth) throws IOException {
public void serializeValue(DataOutputStream dos, int maxDepth) throws IOException {
for (Map.Entry<String, Tag<?>> e : getValue().entrySet()) {
dos.writeByte(e.getValue().getID());
dos.writeUTF(e.getKey());
e.getValue().serializeValue(dos, incrementDepth(depth));
e.getValue().serialize(dos, e.getKey(), decrementMaxDepth(maxDepth));
}
EndTag.INSTANCE.serialize(dos, depth);
EndTag.INSTANCE.serialize(dos, maxDepth);
}

@Override
public void deserializeValue(DataInputStream dis, int depth) throws IOException {
public void deserializeValue(DataInputStream dis, int maxDepth) throws IOException {
clear();
for (int id = dis.readByte() & 0xFF; id != 0; id = dis.readByte() & 0xFF) {
Tag<?> tag = TagFactory.fromID(id);
String name = dis.readUTF();
tag.deserializeValue(dis, incrementDepth(depth));
tag.deserializeValue(dis, decrementMaxDepth(maxDepth));
put(name, tag);
}
}

@Override
public String valueToString(int depth) {
public String valueToString(int maxDepth) {
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Tag<?>> e : getValue().entrySet()) {
sb.append(first ? "" : ",")
.append(escapeString(e.getKey(), false)).append(":")
.append(e.getValue().toString(incrementDepth(depth)));
.append(e.getValue().toString(decrementMaxDepth(maxDepth)));
first = false;
}
sb.append("}");
return sb.toString();
}

@Override
public String valueToTagString(int depth) {
public String valueToTagString(int maxDepth) {
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Tag<?>> e : getValue().entrySet()) {
sb.append(first ? "" : ",")
.append(escapeString(e.getKey(), true)).append(":")
.append(e.getValue().valueToTagString(incrementDepth(depth)));
.append(e.getValue().valueToTagString(decrementMaxDepth(maxDepth)));
first = false;
}
sb.append("}");
Expand All @@ -285,10 +290,7 @@ public boolean equals(Object other) {
}

@Override
public int compareTo(Tag<Map<String, Tag<?>>> o) {
if (!(o instanceof CompoundTag)) {
return 0;
}
public int compareTo(CompoundTag o) {
return Integer.compare(size(), o.getValue().size());
}

Expand Down

0 comments on commit e37a1fb

Please sign in to comment.