From e39153e56eaac4336fa5d9b2007e5fd248f9318b Mon Sep 17 00:00:00 2001 From: Mauro Molin Date: Mon, 13 Apr 2026 16:24:48 +0100 Subject: [PATCH] Add getXxxAsByteArray() getters and overload with escape boolean As discussed in the issue below, the library currently exposes character strings only as escaped strings for textual representation. This is a limitation as users are forced to deal with unnecessary escapes for application logic, and are also limited to interpret bytes as UTF-16 (as `byteArrayToString` casts bytes to `char`), whilst RFCs do not state which encoding for bytes should be used. This commit adds: - `getXxxAsByteArray()` getters that expose the "raw" byte array, allowing full control to users. This follows the already established pattern in 'TXTBase` with the `getStringsAsByteArrays()` method - an overload of the existing getters, which allows to pass an `escape` boolean: `true` (the default) is the current behavior, while `false` simply converts the bytes to a String using the UTF-8 encoding Closes #404 --- src/main/java/org/xbill/DNS/CAARecord.java | 21 +++++++++++-- src/main/java/org/xbill/DNS/HINFORecord.java | 21 +++++++++++-- src/main/java/org/xbill/DNS/ISDNRecord.java | 25 +++++++++++++-- src/main/java/org/xbill/DNS/NAPTRRecord.java | 31 +++++++++++++++++-- src/main/java/org/xbill/DNS/NSAPRecord.java | 11 ++++++- src/main/java/org/xbill/DNS/TXTBase.java | 10 ++++-- src/main/java/org/xbill/DNS/URIRecord.java | 11 ++++++- src/main/java/org/xbill/DNS/X25Record.java | 11 ++++++- .../java/org/xbill/DNS/CAARecordTest.java | 8 +++++ .../java/org/xbill/DNS/HINFORecordTest.java | 3 ++ .../java/org/xbill/DNS/ISDNRecordTest.java | 10 ++++-- .../java/org/xbill/DNS/NAPTRRecordTest.java | 14 +++++++-- .../java/org/xbill/DNS/NSAPRecordTest.java | 25 +++++++++++++++ .../java/org/xbill/DNS/URIRecordTest.java | 9 ++++-- .../java/org/xbill/DNS/X25RecordTest.java | 5 +++ 15 files changed, 194 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/xbill/DNS/CAARecord.java b/src/main/java/org/xbill/DNS/CAARecord.java index 2fc61cee..39e90b68 100644 --- a/src/main/java/org/xbill/DNS/CAARecord.java +++ b/src/main/java/org/xbill/DNS/CAARecord.java @@ -4,6 +4,7 @@ package org.xbill.DNS; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * Certification Authority Authorization @@ -78,13 +79,29 @@ public int getFlags() { } /** Returns the tag. */ + public String getTag(boolean escape) { + return escape ? byteArrayToString(tag, false) : new String(tag, StandardCharsets.UTF_8); + } + public String getTag() { - return byteArrayToString(tag, false); + return getTag(true); + } + + public byte[] getTagAsByteArray() { + return tag; } /** Returns the value */ + public String getValue(boolean escape) { + return escape ? byteArrayToString(value, false) : new String(value, StandardCharsets.UTF_8); + } + public String getValue() { - return byteArrayToString(value, false); + return getValue(true); + } + + public byte[] getValueAsByteArray() { + return value; } @Override diff --git a/src/main/java/org/xbill/DNS/HINFORecord.java b/src/main/java/org/xbill/DNS/HINFORecord.java index 90b0192b..af6edac2 100644 --- a/src/main/java/org/xbill/DNS/HINFORecord.java +++ b/src/main/java/org/xbill/DNS/HINFORecord.java @@ -4,6 +4,7 @@ package org.xbill.DNS; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * Host Information - describes the CPU and OS of a host @@ -52,13 +53,29 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { } /** Returns the host's CPU */ + public String getCPU(boolean escape) { + return escape ? byteArrayToString(cpu, false) : new String(cpu, StandardCharsets.UTF_8); + } + public String getCPU() { - return byteArrayToString(cpu, false); + return getCPU(true); + } + + public byte[] getCPUAsByteArray() { + return cpu; } /** Returns the host's OS */ + public String getOS(boolean escape) { + return escape ? byteArrayToString(os, false) : new String(os, StandardCharsets.UTF_8); + } + public String getOS() { - return byteArrayToString(os, false); + return getOS(true); + } + + public byte[] getOSAsByteArray() { + return os; } @Override diff --git a/src/main/java/org/xbill/DNS/ISDNRecord.java b/src/main/java/org/xbill/DNS/ISDNRecord.java index 4e275742..f706a8ff 100644 --- a/src/main/java/org/xbill/DNS/ISDNRecord.java +++ b/src/main/java/org/xbill/DNS/ISDNRecord.java @@ -4,6 +4,7 @@ package org.xbill.DNS; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * ISDN - identifies the ISDN number and subaddress associated with a name. @@ -60,16 +61,34 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { } /** Returns the ISDN number associated with the domain. */ + public String getAddress(boolean escape) { + return escape ? byteArrayToString(address, false) : new String(address, StandardCharsets.UTF_8); + } + public String getAddress() { - return byteArrayToString(address, false); + return getAddress(true); + } + + public byte[] getAddressAsByteArray() { + return address; } /** Returns the ISDN subaddress, or null if there is none. */ - public String getSubAddress() { + public String getSubAddress(boolean escape) { if (subAddress == null) { return null; } - return byteArrayToString(subAddress, false); + return escape + ? byteArrayToString(subAddress, false) + : new String(subAddress, StandardCharsets.UTF_8); + } + + public String getSubAddress() { + return getSubAddress(true); + } + + public byte[] getSubAddressAsByteArray() { + return subAddress; } @Override diff --git a/src/main/java/org/xbill/DNS/NAPTRRecord.java b/src/main/java/org/xbill/DNS/NAPTRRecord.java index 87d5f972..5bb8a5f1 100644 --- a/src/main/java/org/xbill/DNS/NAPTRRecord.java +++ b/src/main/java/org/xbill/DNS/NAPTRRecord.java @@ -4,6 +4,7 @@ package org.xbill.DNS; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * Name Authority Pointer Record - specifies rewrite rule, that when applied to an existing string @@ -111,18 +112,42 @@ public int getPreference() { } /** Returns flags */ + public String getFlags(boolean escape) { + return escape ? byteArrayToString(flags, false) : new String(flags, StandardCharsets.UTF_8); + } + public String getFlags() { - return byteArrayToString(flags, false); + return getFlags(true); + } + + public byte[] getFlagsAsByteArray() { + return flags; } /** Returns service */ + public String getService(boolean escape) { + return escape ? byteArrayToString(service, false) : new String(service, StandardCharsets.UTF_8); + } + public String getService() { - return byteArrayToString(service, false); + return getService(true); + } + + public byte[] getServiceAsByteArray() { + return service; } /** Returns regexp */ + public String getRegexp(boolean escape) { + return escape ? byteArrayToString(regexp, false) : new String(regexp, StandardCharsets.UTF_8); + } + public String getRegexp() { - return byteArrayToString(regexp, false); + return getRegexp(true); + } + + public byte[] getRegexpAsByteArray() { + return regexp; } /** Returns the replacement domain-name */ diff --git a/src/main/java/org/xbill/DNS/NSAPRecord.java b/src/main/java/org/xbill/DNS/NSAPRecord.java index 8b881757..d59e9818 100644 --- a/src/main/java/org/xbill/DNS/NSAPRecord.java +++ b/src/main/java/org/xbill/DNS/NSAPRecord.java @@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.xbill.DNS.utils.base16; /** @@ -79,8 +80,16 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { } /** Returns the NSAP address. */ + public String getAddress(boolean escape) { + return escape ? byteArrayToString(address, false) : new String(address, StandardCharsets.UTF_8); + } + public String getAddress() { - return byteArrayToString(address, false); + return getAddress(true); + } + + public byte[] getAddressAsByteArray() { + return address; } @Override diff --git a/src/main/java/org/xbill/DNS/TXTBase.java b/src/main/java/org/xbill/DNS/TXTBase.java index 96694b12..fbf6645e 100644 --- a/src/main/java/org/xbill/DNS/TXTBase.java +++ b/src/main/java/org/xbill/DNS/TXTBase.java @@ -4,6 +4,7 @@ package org.xbill.DNS; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -94,14 +95,19 @@ protected String rrToString() { * * @return A list of Strings corresponding to the text strings. */ - public List getStrings() { + public List getStrings(boolean escape) { List list = new ArrayList<>(strings.size()); for (byte[] string : strings) { - list.add(byteArrayToString(string, false)); + list.add( + escape ? byteArrayToString(string, false) : new String(string, StandardCharsets.UTF_8)); } return list; } + public List getStrings() { + return getStrings(true); + } + /** * Returns the text strings * diff --git a/src/main/java/org/xbill/DNS/URIRecord.java b/src/main/java/org/xbill/DNS/URIRecord.java index 3141f8e3..fc165fe6 100644 --- a/src/main/java/org/xbill/DNS/URIRecord.java +++ b/src/main/java/org/xbill/DNS/URIRecord.java @@ -5,6 +5,7 @@ package org.xbill.DNS; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * Uniform Resource Identifier (URI) DNS Resource Record @@ -79,8 +80,16 @@ public int getWeight() { } /** Returns the target URI */ + public String getTarget(boolean escape) { + return escape ? byteArrayToString(target, false) : new String(target, StandardCharsets.UTF_8); + } + public String getTarget() { - return byteArrayToString(target, false); + return getTarget(true); + } + + public byte[] getTargetAsByteArray() { + return target; } @Override diff --git a/src/main/java/org/xbill/DNS/X25Record.java b/src/main/java/org/xbill/DNS/X25Record.java index 9b2ca5d8..8a651ed6 100644 --- a/src/main/java/org/xbill/DNS/X25Record.java +++ b/src/main/java/org/xbill/DNS/X25Record.java @@ -4,6 +4,7 @@ package org.xbill.DNS; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * X25 - identifies the PSDN (Public Switched Data Network) address in the X.121 numbering plan @@ -59,8 +60,16 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { } /** Returns the X.25 PSDN address. */ + public String getAddress(boolean escape) { + return escape ? byteArrayToString(address, false) : new String(address, StandardCharsets.UTF_8); + } + public String getAddress() { - return byteArrayToString(address, false); + return getAddress(true); + } + + public byte[] getAddressAsByteArray() { + return address; } @Override diff --git a/src/test/java/org/xbill/DNS/CAARecordTest.java b/src/test/java/org/xbill/DNS/CAARecordTest.java index 0af2718d..992dad66 100644 --- a/src/test/java/org/xbill/DNS/CAARecordTest.java +++ b/src/test/java/org/xbill/DNS/CAARecordTest.java @@ -1,10 +1,12 @@ // SPDX-License-Identifier: BSD-3-Clause package org.xbill.DNS; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; class CAARecordTest { @@ -16,7 +18,9 @@ void ctor_6arg() { CAARecord caa = new CAARecord(n, DClass.IN, 0, CAARecord.Flags.IssuerCritical, "", ""); assertEquals(CAARecord.Flags.IssuerCritical, caa.getFlags()); assertEquals("", caa.getTag()); + assertEquals(0, caa.getTagAsByteArray().length); assertEquals("", caa.getValue()); + assertEquals(0, caa.getValueAsByteArray().length); String data = new String(new char[256]); IllegalArgumentException thrown = @@ -32,6 +36,10 @@ void rdataFromString() throws IOException { CAARecord caa = new CAARecord(); caa.rdataFromString(t, null); assertEquals("issue", caa.getTag()); + assertEquals("issue", caa.getTag(false)); + assertArrayEquals("issue".getBytes(StandardCharsets.UTF_8), caa.getTagAsByteArray()); assertEquals("entrust.net", caa.getValue()); + assertEquals("entrust.net", caa.getValue(false)); + assertArrayEquals("entrust.net".getBytes(StandardCharsets.UTF_8), caa.getValueAsByteArray()); } } diff --git a/src/test/java/org/xbill/DNS/HINFORecordTest.java b/src/test/java/org/xbill/DNS/HINFORecordTest.java index a25e6282..194051c9 100644 --- a/src/test/java/org/xbill/DNS/HINFORecordTest.java +++ b/src/test/java/org/xbill/DNS/HINFORecordTest.java @@ -41,6 +41,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; class HINFORecordTest { @@ -120,7 +121,9 @@ void rdataFromString() throws IOException { HINFORecord dr = new HINFORecord(); dr.rdataFromString(t, null); assertEquals(cpu, dr.getCPU()); + assertArrayEquals(cpu.getBytes(StandardCharsets.UTF_8), dr.getCPUAsByteArray()); assertEquals(os, dr.getOS()); + assertArrayEquals(os.getBytes(StandardCharsets.UTF_8), dr.getOSAsByteArray()); } @Test diff --git a/src/test/java/org/xbill/DNS/ISDNRecordTest.java b/src/test/java/org/xbill/DNS/ISDNRecordTest.java index ec28ccac..d91ac268 100644 --- a/src/test/java/org/xbill/DNS/ISDNRecordTest.java +++ b/src/test/java/org/xbill/DNS/ISDNRecordTest.java @@ -1,9 +1,11 @@ // SPDX-License-Identifier: BSD-3-Clause package org.xbill.DNS; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; class ISDNRecordTest { @@ -12,9 +14,13 @@ class ISDNRecordTest { @Test void ctor_5arg() { - ISDNRecord isdn = new ISDNRecord(n, DClass.IN, 0, "foo", "bar"); - assertEquals("foo", isdn.getAddress()); + ISDNRecord isdn = new ISDNRecord(n, DClass.IN, 0, "foo\\\"", "bar"); + assertEquals("foo\\\"", isdn.getAddress()); + assertEquals("foo\"", isdn.getAddress(false)); + assertArrayEquals("foo\"".getBytes(StandardCharsets.UTF_8), isdn.getAddressAsByteArray()); assertEquals("bar", isdn.getSubAddress()); + assertEquals("bar", isdn.getSubAddress(false)); + assertArrayEquals("bar".getBytes(StandardCharsets.UTF_8), isdn.getSubAddressAsByteArray()); } @Test diff --git a/src/test/java/org/xbill/DNS/NAPTRRecordTest.java b/src/test/java/org/xbill/DNS/NAPTRRecordTest.java index 2a0417c8..9ee4af51 100644 --- a/src/test/java/org/xbill/DNS/NAPTRRecordTest.java +++ b/src/test/java/org/xbill/DNS/NAPTRRecordTest.java @@ -1,23 +1,33 @@ // SPDX-License-Identifier: BSD-3-Clause package org.xbill.DNS; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; class NAPTRRecordTest { @Test void rdataFromString() throws IOException { - Tokenizer t = new Tokenizer("100 50 \"s\" \"http+N2L+N2C+N2R\" \"\" www.example.com."); + Tokenizer t = + new Tokenizer("100 50 \"s\" \"http+N2L+N2C+N2R\" \"!a\\\\!!b!i\" www.example.com."); NAPTRRecord naptr = new NAPTRRecord(); naptr.rdataFromString(t, null); assertEquals(100, naptr.getOrder()); assertEquals(50, naptr.getPreference()); assertEquals("s", naptr.getFlags()); + assertEquals("s", naptr.getFlags(false)); + assertArrayEquals(new byte[] {115}, naptr.getFlagsAsByteArray()); assertEquals("http+N2L+N2C+N2R", naptr.getService()); - assertEquals("", naptr.getRegexp()); + assertEquals("http+N2L+N2C+N2R", naptr.getService(false)); + assertArrayEquals( + "http+N2L+N2C+N2R".getBytes(StandardCharsets.UTF_8), naptr.getServiceAsByteArray()); + assertEquals("!a\\\\!!b!i", naptr.getRegexp()); + assertEquals("!a\\!!b!i", naptr.getRegexp(false)); + assertArrayEquals("!a\\!!b!i".getBytes(StandardCharsets.UTF_8), naptr.getRegexpAsByteArray()); assertEquals(Name.fromConstantString("www.example.com."), naptr.getReplacement()); } } diff --git a/src/test/java/org/xbill/DNS/NSAPRecordTest.java b/src/test/java/org/xbill/DNS/NSAPRecordTest.java index 7903154a..88f606e3 100644 --- a/src/test/java/org/xbill/DNS/NSAPRecordTest.java +++ b/src/test/java/org/xbill/DNS/NSAPRecordTest.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause package org.xbill.DNS; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; @@ -16,5 +17,29 @@ void rdataFromString() throws IOException { assertEquals( "G\\000\\005\\128\\000Z\\000\\000\\000\\000\\001\\2253\\255\\255\\255\\000\\001a\\000", nsap.getAddress()); + assertArrayEquals( + new byte[] { + 71, + 0, + 5, + (byte) 128, + 0, + 90, + 0, + 0, + 0, + 0, + 1, + (byte) 225, + 51, + (byte) 255, + (byte) 255, + (byte) 255, + 0, + 1, + 97, + 0 + }, + nsap.getAddressAsByteArray()); } } diff --git a/src/test/java/org/xbill/DNS/URIRecordTest.java b/src/test/java/org/xbill/DNS/URIRecordTest.java index 9571db78..d4cab3d5 100644 --- a/src/test/java/org/xbill/DNS/URIRecordTest.java +++ b/src/test/java/org/xbill/DNS/URIRecordTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; class URIRecordTest { @@ -20,12 +21,14 @@ void ctor_0arg() { assertEquals(0, r.getPriority()); assertEquals(0, r.getWeight()); assertEquals("", r.getTarget()); + assertEquals("", r.getTarget(false)); + assertEquals(0, r.getTargetAsByteArray().length); } @Test void ctor_6arg() throws TextParseException { Name n = Name.fromString("my.name."); - String target = "http://foo"; + String target = "http://\"foo\""; URIRecord r = new URIRecord(n, DClass.IN, 0xABCDEL, 42, 69, target); assertEquals(n, r.getName()); @@ -34,7 +37,9 @@ void ctor_6arg() throws TextParseException { assertEquals(0xABCDEL, r.getTTL()); assertEquals(42, r.getPriority()); assertEquals(69, r.getWeight()); - assertEquals(target, r.getTarget()); + assertEquals("http://\\\"foo\\\"", r.getTarget()); + assertEquals(target, r.getTarget(false)); + assertArrayEquals(target.getBytes(StandardCharsets.UTF_8), r.getTargetAsByteArray()); } @Test diff --git a/src/test/java/org/xbill/DNS/X25RecordTest.java b/src/test/java/org/xbill/DNS/X25RecordTest.java index 1dc8a094..29772a3c 100644 --- a/src/test/java/org/xbill/DNS/X25RecordTest.java +++ b/src/test/java/org/xbill/DNS/X25RecordTest.java @@ -1,9 +1,11 @@ // SPDX-License-Identifier: BSD-3-Clause package org.xbill.DNS; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; class X25RecordTest { @@ -14,5 +16,8 @@ void rdataFromString() throws IOException { X25Record x25Record = new X25Record(); x25Record.rdataFromString(t, null); assertEquals("311061700956", x25Record.getAddress()); + assertEquals("311061700956", x25Record.getAddress(false)); + assertArrayEquals( + "311061700956".getBytes(StandardCharsets.UTF_8), x25Record.getAddressAsByteArray()); } }