[java-support] branch dev/JSPT-111 updated: Deserialization and tests.
Scott Cantor
cantor.2 at osu.edu
Thu May 20 19:02:35 UTC 2021
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch dev/JSPT-111
in repository java-support.
View the commit online:
http://git.shibboleth.net/view/?p=java-support.git;a=commit;h=bca370686e22568b33859a4b172bc2b426c19ac6
The following commit(s) were added to refs/heads/dev/JSPT-111 by this push:
new bca3706 Deserialization and tests.
bca3706 is described below
commit bca370686e22568b33859a4b172bc2b426c19ac6
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu May 20 15:02:33 2021 -0400
Deserialization and tests.
---
.../shibboleth/utilities/java/support/ddf/DDF.java | 215 ++++++++++++++++++++-
.../utilities/java/support/ddf/DDFTest.java | 67 +++++++
2 files changed, 280 insertions(+), 2 deletions(-)
diff --git a/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java b/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
index 25724fc..5b66a92 100644
--- a/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
+++ b/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
@@ -18,7 +18,9 @@
package net.shibboleth.utilities.java.support.ddf;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -102,7 +104,7 @@ public class DDF implements Iterable<DDF> {
*
* @param val value of the type enum
*/
- private DDFType(@NonNegative final int val) {
+ private DDFType(final int val) {
value = val;
}
@@ -115,6 +117,60 @@ public class DDF implements Iterable<DDF> {
return value;
}
+ /**
+ * Convert an integer into the corresponding enum value.
+ *
+ * @param val input type
+ *
+ * @return enum constant
+ *
+ * @throws IllegalArgumentException if the type is out of range
+ */
+ public static DDFType valueOf(final int val) throws IllegalArgumentException {
+ final DDFType type;
+ switch (val) {
+ case -1:
+ type = DDF_NULL;
+ break;
+
+ case 0:
+ type = DDF_EMPTY;
+ break;
+
+ case 1:
+ type = DDF_STRING;
+ break;
+
+ case 2:
+ type = DDF_INT;
+ break;
+
+ case 3:
+ type = DDF_FLOAT;
+ break;
+
+ case 4:
+ type = DDF_STRUCT;
+ break;
+
+ case 5:
+ type = DDF_LIST;
+ break;
+
+ case 6:
+ type = DDF_POINTER;
+ break;
+
+ case 7:
+ type = DDF_STRING_UNSAFE;
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unrecognized DDF type");
+ }
+ return type;
+ }
+
};
/** Node type. */
@@ -211,6 +267,7 @@ public class DDF implements Iterable<DDF> {
*
* @return the copy
*/
+// Checkstyle: CyclomaticComplexity OFF
@SuppressWarnings("unchecked")
@Nonnull DDF copy() {
final DDF dup = new DDF(name);
@@ -258,6 +315,7 @@ public class DDF implements Iterable<DDF> {
return dup;
}
+// Checkstyle: CyclomaticComplexity ON
/**
* Get the node name.
@@ -835,7 +893,7 @@ public class DDF implements Iterable<DDF> {
} catch(final NumberFormatException e) {
index = 0;
}
- if (islist() && index < ((List<DDF>) current.value).size()) {
+ if (current.islist() && index < ((List<DDF>) current.value).size()) {
current = ((List<DDF>) current.value).get(index);
} else {
return new DDF();
@@ -1153,6 +1211,159 @@ public class DDF implements Iterable<DDF> {
return os;
}
+
+ /**
+ * Parses a seralized DDF from an input stream.
+ *
+ * @param is input stream
+ *
+ * @return the parsed object
+ *
+ * @throws IOException if an error occurs
+ */
+ @Nonnull public static DDF deserialize(@Nonnull final InputStream is) throws IOException {
+
+ int ch;
+ final StringBuilder nameBuilder = new StringBuilder();
+
+ // First field is the name.
+ while ((ch = is.read()) != -1 && !Character.isWhitespace(ch)) {
+ if (ch >= 0 && ch <= 127) {
+ // The int is a code point from 0..255, but our grammar constrains this to 0..127 so
+ // this is a safe append, to promote the ASCII into Unicode.
+ nameBuilder.appendCodePoint(ch);
+ } else {
+ throw new IOException("Invalid code point outside US-ASCII range");
+ }
+ }
+
+ if (ch != 0x20) {
+ // Name has to be followed by a space.
+ // This will also cover an early line or stream termination.
+ throw new IOException("Name not followed by space character");
+ }
+
+ final String name = nameBuilder.toString();
+ if (name.isEmpty()) {
+ // No name field.
+ throw new IOException("Name field missing");
+ }
+
+ final DDF obj = new DDF(null);
+ if (!".".equals(name)) {
+ // The name is stipulated to be UTF-8 safe so any high order ASCII characters are
+ // assumed to be part of a multi-byte sequence.
+ try {
+ obj.name(URLDecoder.decode(name, "UTF-8"));
+ } catch (final IllegalArgumentException e) {
+ throw new IOException(e);
+ }
+ }
+
+ // Next field is the numeric type designation.
+ final StringBuilder typeBuilder = new StringBuilder();
+ while ((ch = is.read()) != -1 && Character.isDigit(ch)) {
+ // This is safe because the byte contract of the stream disallows
+ // any non-ASCII digit from satisfying the isDigit check.
+ typeBuilder.appendCodePoint(ch);
+ }
+
+ // Before continuing, we convert the string into a DDF type.
+ final DDFType type;
+ try {
+ type = DDFType.valueOf(Integer.valueOf(typeBuilder.toString()));
+ } catch (final IllegalArgumentException e) {
+ throw new IOException("Invalid DDF type");
+ }
+
+ // Required last byte read will vary by type.
+ if (type == DDFType.DDF_EMPTY || type == DDFType.DDF_POINTER) {
+ if (ch != 0x0A) {
+ throw new IOException("Empty/pointer record not terminated by linefeed");
+ }
+ // Nothing else to do, it's already empty.
+ return obj;
+ }
+
+ // All others should be followed by a space.
+ if (ch != 0x20) {
+ throw new IOException("Type field not followed by space character");
+ }
+
+ // Process typical value types.
+ final StringBuilder valueBuilder = new StringBuilder();
+ switch (type) {
+ case DDF_STRING:
+ case DDF_STRING_UNSAFE:
+ case DDF_INT:
+ case DDF_FLOAT:
+ while ((ch = is.read()) != -1 && !Character.isWhitespace(ch)) {
+ if (ch >= 0 && ch <= 127) {
+ // The int is a code point from 0..255, but our grammar constrains this to 0..127 so
+ // this is a safe append, to promote the ASCII into Unicode.
+ valueBuilder.appendCodePoint(ch);
+ } else {
+ throw new IOException("Invalid code point outside US-ASCII range");
+ }
+ }
+
+ if (ch != 0x0A) {
+ throw new IOException("String value not followed by linefeed");
+ }
+
+ try {
+ if (type == DDFType.DDF_STRING) {
+ // String values are handled as UTF-8.
+ obj.string(URLDecoder.decode(valueBuilder.toString(), "UTF-8"));
+ } else if (type == DDFType.DDF_STRING_UNSAFE) {
+ // Unsafe string values are processed as ISO-8859-1.
+ // They may be anything, but it will guarantee a single byte encoding.
+ obj.unsafe_string(URLDecoder.decode(valueBuilder.toString(), "ISO-8859-1"));
+ } else if (type == DDFType.DDF_INT) {
+ obj.integer(valueBuilder.toString());
+ } else if (type == DDFType.DDF_FLOAT) {
+ obj.floating(valueBuilder.toString());
+ }
+ } catch (final IllegalArgumentException e) {
+ throw new IOException(e);
+ }
+ return obj;
+
+ case DDF_STRUCT:
+ case DDF_LIST:
+ while ((ch = is.read()) != -1 && Character.isDigit(ch)) {
+ // This is safe because the byte contract of the stream disallows
+ // any non-ASCII digit from satisfying the isDigit check.
+ valueBuilder.appendCodePoint(ch);
+ }
+
+ if (ch != 0x0A) {
+ throw new IOException("Record count not followed by linefeed");
+ }
+
+ int count;
+ try {
+ count = Integer.valueOf(valueBuilder.toString());
+ } catch (final NumberFormatException e) {
+ throw new IOException("Invalid record count");
+ }
+
+ if (type == DDFType.DDF_STRUCT) {
+ obj.structure();
+ } else {
+ obj.list();
+ }
+
+ for (; count > 0; --count) {
+ obj.add(deserialize(is));
+ }
+ return obj;
+
+
+ default:
+ throw new IOException("Unexpected record type");
+ }
+ }
// Checkstyle: MethodLength|CyclomaticComplexity ON
/**
diff --git a/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java b/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java
index 7ed2a1e..1889c58 100644
--- a/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java
+++ b/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java
@@ -19,8 +19,10 @@ package net.shibboleth.utilities.java.support.ddf;
import static org.testng.Assert.*;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import javax.annotation.Nonnull;
@@ -247,6 +249,71 @@ public class DDFTest {
}
}
+ @Test
+ public void testDeserialize() throws IOException {
+ try (final InputStream is = getClass().getResourceAsStream("empty-noname.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isempty());
+ assertNull(obj.name());
+ }
+
+ try (final InputStream is = getClass().getResourceAsStream("empty-name.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isempty());
+ assertEquals(obj.name(), "foo bar");
+ }
+
+ try (final InputStream is = getClass().getResourceAsStream("string-name.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isstring());
+ assertEquals(obj.name(), "foo bar");
+ assertEquals(obj.string(), "zorkmid☯️");
+ }
+
+ try (final InputStream is = getClass().getResourceAsStream("unsafestring-name.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isstring());
+ assertEquals(obj.name(), "foo bar");
+ final byte[] unsafe = {102, 111, 111, -128, 98, 97, 114};
+ assertEquals(obj.string(), new String(unsafe, "ISO-8859-1"));
+ }
+
+ try (final InputStream is = getClass().getResourceAsStream("int-name.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isint());
+ assertEquals(obj.name(), "foo bar");
+ assertEquals(obj.integer(), Integer.valueOf(42));
+ }
+
+ try (final InputStream is = getClass().getResourceAsStream("float-name.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isfloat());
+ assertEquals(obj.name(), "foo bar");
+ assertEquals(obj.floating(), Double.valueOf(42.1315927));
+ }
+
+ try (final InputStream is = getClass().getResourceAsStream("struct-empty.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isstruct());
+ assertEquals(obj.name(), "foo bar");
+ assertEquals(obj.integer(), Integer.valueOf(0));
+ }
+
+ try (final InputStream is = getClass().getResourceAsStream("struct-complex.ddf")) {
+ final DDF obj = DDF.deserialize(is);
+ assertTrue(obj.isstruct());
+ assertEquals(obj.name(), "foo bar");
+ assertEquals(obj.integer(), Integer.valueOf(1));
+ assertTrue(obj.getmember("infocom").isstruct());
+ assertTrue(obj.getmember("infocom.zork").islist());
+ assertEquals(obj.getmember("infocom.zork").integer(), Integer.valueOf(3));
+ assertEquals(obj.getmember("infocom.zork.[0]").integer(), Integer.valueOf(1));
+ assertEquals(obj.getmember("infocom.zork.[1]").integer(), Integer.valueOf(2));
+ assertEquals(obj.getmember("infocom.zork.[2]").integer(), Integer.valueOf(3));
+ }
+ }
+
+
/**
* Convert test file contents to a byte array.
*
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list