From 2da013c955423e8ae0ff7766f1005ed26e41cdbb 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 | 27 ++++++++- src/main/java/org/xbill/DNS/HINFORecord.java | 49 +++++++++++++-- src/main/java/org/xbill/DNS/ISDNRecord.java | 59 +++++++++++++++++-- src/main/java/org/xbill/DNS/NAPTRRecord.java | 51 ++++++++++++++-- src/main/java/org/xbill/DNS/NSAPRecord.java | 18 +++++- src/main/java/org/xbill/DNS/TXTBase.java | 22 +++++-- src/main/java/org/xbill/DNS/URIRecord.java | 21 ++++++- src/main/java/org/xbill/DNS/X25Record.java | 3 +- .../java/org/xbill/DNS/CAARecordTest.java | 5 ++ .../java/org/xbill/DNS/HINFORecordTest.java | 3 + .../java/org/xbill/DNS/ISDNRecordTest.java | 10 +++- .../java/org/xbill/DNS/NAPTRRecordTest.java | 12 +++- .../java/org/xbill/DNS/NSAPRecordTest.java | 25 ++++++++ .../java/org/xbill/DNS/URIRecordTest.java | 20 ++++--- 14 files changed, 287 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/xbill/DNS/CAARecord.java b/src/main/java/org/xbill/DNS/CAARecord.java index 2fc61cee..bcd8955c 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 @@ -79,12 +80,32 @@ public int getFlags() { /** Returns the tag. */ public String getTag() { - return byteArrayToString(tag, false); + return new String(tag, StandardCharsets.US_ASCII); } - /** Returns the value */ + /** + * Returns the value as a string. + * + * @param escape if true, returns the RR textual representation of the underlying bytes. If false, + * returns just the simple string using the UTF-8 charset with no additional escaping. + * @since 3.6.5 + */ + public String getValue(boolean escape) { + return escape ? byteArrayToString(value, false) : new String(value, StandardCharsets.UTF_8); + } + + /** Returns the value as a string, escaped for RR textual representation */ public String getValue() { - return byteArrayToString(value, false); + return getValue(true); + } + + /** + * Returns the value as a raw byte-array + * + * @since 3.6.5 + */ + 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..1fbd0625 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 @@ -51,14 +52,54 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { } } - /** Returns the host's CPU */ + /** + * Returns the host's CPU as a string. + * + * @param escape if true, returns the RR textual representation of the underlying bytes. If false, + * returns just the simple string using the UTF-8 charset with no additional escaping. + * @since 3.6.5 + */ + public String getCPU(boolean escape) { + return escape ? byteArrayToString(cpu, false) : new String(cpu, StandardCharsets.UTF_8); + } + + /** Returns the host's CPU as a string, escaped for RR textual representation */ public String getCPU() { - return byteArrayToString(cpu, false); + return getCPU(true); + } + + /** + * Returns the host's CPU as a raw byte-array + * + * @since 3.6.5 + */ + public byte[] getCPUAsByteArray() { + return cpu; } - /** Returns the host's OS */ + /** + * Returns the host's OS as a string. + * + * @param escape if true, returns the RR textual representation of the underlying bytes. If false, + * returns just the simple string using the UTF-8 charset with no additional escaping. + * @since 3.6.5 + */ + public String getOS(boolean escape) { + return escape ? byteArrayToString(os, false) : new String(os, StandardCharsets.UTF_8); + } + + /** Returns the host's OS as a string, escaped for RR textual representation */ public String getOS() { - return byteArrayToString(os, false); + return getOS(true); + } + + /** + * Returns the host's OS as a raw byte-array + * + * @since 3.6.5 + */ + 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..9753e732 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. @@ -59,17 +60,65 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { } } - /** Returns the ISDN number associated with the domain. */ + /** + * Returns the ISDN number associated with the domain as a string. + * + * @param escape if true, returns the RR textual representation of the underlying bytes. If false, + * returns just the simple string using the UTF-8 charset with no additional escaping. + * @since 3.6.5 + */ + public String getAddress(boolean escape) { + return escape ? byteArrayToString(address, false) : new String(address, StandardCharsets.UTF_8); + } + + /** + * Returns the ISDN number associated with the domain as a string, escaped for RR textual + * representation + */ public String getAddress() { - return byteArrayToString(address, false); + return getAddress(true); } - /** Returns the ISDN subaddress, or null if there is none. */ - public String getSubAddress() { + /** + * Returns the ISDN number associated with the domain as a raw byte-array + * + * @since 3.6.5 + */ + public byte[] getAddressAsByteArray() { + return address; + } + + /** + * Returns the ISDN subaddress as a string, or null if there is none. + * + * @param escape if true, returns the RR textual representation of the underlying bytes. If false, + * returns just the simple string using the UTF-8 charset with no additional escaping. + * @since 3.6.5 + */ + 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); + } + + /** + * Returns the ISDN subaddress as a string, escaped for RR textual representation, or null if + * there is none. + */ + public String getSubAddress() { + return getSubAddress(true); + } + + /** + * Returns the ISDN subaddress as a raw byte-array, or null if there is none. + * + * @since 3.6.5 + */ + 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..42c79ecb 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 @@ -112,17 +113,57 @@ public int getPreference() { /** Returns flags */ public String getFlags() { - return byteArrayToString(flags, false); + return new String(flags, StandardCharsets.US_ASCII); } - /** Returns service */ + /** + * Returns the service as a string. + * + * @param escape if true, returns the RR textual representation of the underlying bytes. If false, + * returns just the simple string using the UTF-8 charset with no additional escaping. + * @since 3.6.5 + */ + public String getService(boolean escape) { + return escape ? byteArrayToString(service, false) : new String(service, StandardCharsets.UTF_8); + } + + /** Returns the service as a string, escaped for RR textual representation */ public String getService() { - return byteArrayToString(service, false); + return getService(true); + } + + /** + * Returns the service as a raw byte-array + * + * @since 3.6.5 + */ + public byte[] getServiceAsByteArray() { + return service; } - /** Returns regexp */ + /** + * Returns regexp as a string. + * + * @param escape if true, returns the RR textual representation of the underlying bytes. If false, + * returns just the simple string using the UTF-8 charset with no additional escaping. + * @since 3.6.5 + */ + public String getRegexp(boolean escape) { + return escape ? byteArrayToString(regexp, false) : new String(regexp, StandardCharsets.UTF_8); + } + + /** Returns regexp as a string, escaped for RR textual representation */ public String getRegexp() { - return byteArrayToString(regexp, false); + return getRegexp(true); + } + + /** + * Returns regexp as a raw byte-array + * + * @since 3.6.5 + */ + 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..e33263bb 100644 --- a/src/main/java/org/xbill/DNS/NSAPRecord.java +++ b/src/main/java/org/xbill/DNS/NSAPRecord.java @@ -78,11 +78,27 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { } } - /** Returns the NSAP address. */ + /** + * Returns the NSAP address as a string, escaped for RR textual representation. + * + *

Obsolete, use {@link NSAPRecord#getAddressAsByteArray} instead. + * + * @deprecated + */ + @Deprecated public String getAddress() { return byteArrayToString(address, false); } + /** + * Returns the NSAP address. + * + * @since 3.6.5 + */ + public byte[] getAddressAsByteArray() { + return address; + } + @Override protected void rrToWire(DNSOutput out, Compression c, boolean canonical) { out.writeByteArray(address); diff --git a/src/main/java/org/xbill/DNS/TXTBase.java b/src/main/java/org/xbill/DNS/TXTBase.java index 96694b12..78ff4126 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; @@ -90,22 +91,31 @@ protected String rrToString() { } /** - * Returns the text strings + * Returns the text strings as a list of strings. * - * @return A list of Strings corresponding to the text strings. + * @param escape if true, returns the RR textual representation of the underlying bytes for each + * string. If false, returns just the simple strings using the UTF-8 charset with no + * additional escaping. + * @since 3.6.5 */ - 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; } + /** Returns the text strings as a list of strings, escaped for RR textual representation */ + public List getStrings() { + return getStrings(true); + } + /** - * Returns the text strings + * Returns the text strings as a list of raw byte-arrays * - * @return A list of byte arrays corresponding to the text strings. + * @since 3.6.5 */ public List getStringsAsByteArrays() { return strings; diff --git a/src/main/java/org/xbill/DNS/URIRecord.java b/src/main/java/org/xbill/DNS/URIRecord.java index 3141f8e3..cb10f6de 100644 --- a/src/main/java/org/xbill/DNS/URIRecord.java +++ b/src/main/java/org/xbill/DNS/URIRecord.java @@ -5,6 +5,9 @@ package org.xbill.DNS; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; /** * Uniform Resource Identifier (URI) DNS Resource Record @@ -78,11 +81,27 @@ public int getWeight() { return weight; } - /** Returns the target URI */ + /** + * Returns the target URI as a string, escaped for RR textual representation. + * + *

Obsolete, use {@link URIRecord#getTargetAsURI()} instead. + * + * @deprecated + */ + @Deprecated public String getTarget() { return byteArrayToString(target, false); } + /** + * Returns the target URI as a {@link URI} + * + * @since 3.6.5 + */ + public URI getTargetAsURI() throws URISyntaxException { + return new URI(new String(target, StandardCharsets.UTF_8)); + } + @Override protected void rrToWire(DNSOutput out, Compression c, boolean canonical) { out.writeU16(priority); diff --git a/src/main/java/org/xbill/DNS/X25Record.java b/src/main/java/org/xbill/DNS/X25Record.java index 9b2ca5d8..779b9fb3 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 @@ -60,7 +61,7 @@ protected void rdataFromString(Tokenizer st, Name origin) throws IOException { /** Returns the X.25 PSDN address. */ public String getAddress() { - return byteArrayToString(address, false); + return new String(address, StandardCharsets.US_ASCII); } @Override diff --git a/src/test/java/org/xbill/DNS/CAARecordTest.java b/src/test/java/org/xbill/DNS/CAARecordTest.java index 0af2718d..0bdcbe55 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 { @@ -17,6 +19,7 @@ void ctor_6arg() { assertEquals(CAARecord.Flags.IssuerCritical, caa.getFlags()); assertEquals("", caa.getTag()); assertEquals("", caa.getValue()); + assertEquals(0, caa.getValueAsByteArray().length); String data = new String(new char[256]); IllegalArgumentException thrown = @@ -33,5 +36,7 @@ void rdataFromString() throws IOException { caa.rdataFromString(t, null); assertEquals("issue", caa.getTag()); 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..0c769cdd 100644 --- a/src/test/java/org/xbill/DNS/NAPTRRecordTest.java +++ b/src/test/java/org/xbill/DNS/NAPTRRecordTest.java @@ -1,23 +1,31 @@ // 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("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..f9066a3f 100644 --- a/src/test/java/org/xbill/DNS/URIRecordTest.java +++ b/src/test/java/org/xbill/DNS/URIRecordTest.java @@ -7,11 +7,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import org.junit.jupiter.api.Test; class URIRecordTest { @Test - void ctor_0arg() { + void ctor_0arg() throws URISyntaxException { URIRecord r = new URIRecord(); assertNull(r.getName()); assertEquals(0, r.getType()); @@ -20,12 +22,13 @@ void ctor_0arg() { assertEquals(0, r.getPriority()); assertEquals(0, r.getWeight()); assertEquals("", r.getTarget()); + assertEquals(new URI(""), r.getTargetAsURI()); } @Test - void ctor_6arg() throws TextParseException { + void ctor_6arg() throws TextParseException, URISyntaxException { Name n = Name.fromString("my.name."); - String target = "http://foo"; + String target = "http://foo/bar"; URIRecord r = new URIRecord(n, DClass.IN, 0xABCDEL, 42, 69, target); assertEquals(n, r.getName()); @@ -34,18 +37,19 @@ void ctor_6arg() throws TextParseException { assertEquals(0xABCDEL, r.getTTL()); assertEquals(42, r.getPriority()); assertEquals(69, r.getWeight()); - assertEquals(target, r.getTarget()); + assertEquals("http://foo/bar", r.getTarget()); + assertEquals(new URI("http://foo/bar"), r.getTargetAsURI()); } @Test - void rdataFromString() throws IOException { + void rdataFromString() throws IOException, URISyntaxException { Tokenizer t = new Tokenizer(0xABCD + " " + 0xEF01 + " \"http://foo:1234/bar?baz=bum\""); URIRecord r = new URIRecord(); r.rdataFromString(t, null); assertEquals(0xABCD, r.getPriority()); assertEquals(0xEF01, r.getWeight()); - assertEquals("http://foo:1234/bar?baz=bum", r.getTarget()); + assertEquals(new URI("http://foo:1234/bar?baz=bum"), r.getTargetAsURI()); } @Test @@ -77,7 +81,7 @@ void rdataToWire() throws TextParseException { } @Test - void rrFromWire() throws IOException { + void rrFromWire() throws IOException, URISyntaxException { byte[] raw = new byte[] { (byte) 0xbe, @@ -101,7 +105,7 @@ void rrFromWire() throws IOException { r.rrFromWire(in); assertEquals(0xBEEF, r.getPriority()); assertEquals(0xDEAD, r.getWeight()); - assertEquals("http://foo", r.getTarget()); + assertEquals(new URI("http://foo"), r.getTargetAsURI()); } @Test