Improved metadata trimming, to better handle multibyte UTF-8 characters.

This commit is contained in:
CalDescent 2023-04-28 12:48:38 +01:00
parent 46e2e1043d
commit 45bc2e46d6
2 changed files with 63 additions and 2 deletions

View File

@ -7,6 +7,7 @@ import org.qortal.arbitrary.misc.Category;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -217,6 +218,25 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
// Static helper methods // Static helper methods
public static String trimUTF8String(String string, int maxLength) {
byte[] inputBytes = string.getBytes(StandardCharsets.UTF_8);
int length = Math.min(inputBytes.length, maxLength);
byte[] outputBytes = new byte[length];
System.arraycopy(inputBytes, 0, outputBytes, 0, length);
String result = new String(outputBytes, StandardCharsets.UTF_8);
// check if last character is truncated
int lastIndex = result.length() - 1;
if (lastIndex > 0 && result.charAt(lastIndex) != string.charAt(lastIndex)) {
// last character is truncated so remove the last character
return result.substring(0, lastIndex);
}
return result;
}
public static String limitTitle(String title) { public static String limitTitle(String title) {
if (title == null) { if (title == null) {
return null; return null;
@ -225,7 +245,7 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
return null; return null;
} }
return title.substring(0, Math.min(title.length(), MAX_TITLE_LENGTH)); return trimUTF8String(title, MAX_TITLE_LENGTH);
} }
public static String limitDescription(String description) { public static String limitDescription(String description) {
@ -236,7 +256,7 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata {
return null; return null;
} }
return description.substring(0, Math.min(description.length(), MAX_DESCRIPTION_LENGTH)); return trimUTF8String(description, MAX_DESCRIPTION_LENGTH);
} }
public static List<String> limitTags(List<String> tags) { public static List<String> limitTags(List<String> tags) {

View File

@ -248,6 +248,47 @@ public class ArbitraryTransactionMetadataTests extends Common {
} }
} }
@Test
public void testUTF8Metadata() throws DataException, IOException, MissingDataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String publicKey58 = Base58.encode(alice.getPublicKey());
String name = "TEST"; // Can be anything for this test
String identifier = null; // Not used for this test
Service service = Service.ARBITRARY_DATA;
int chunkSize = 100;
int dataLength = 900; // Actual data length will be longer due to encryption
// Example (modified) strings from real world content
String title = "Доля юаня в трансграничных Доля юаня в трансграничных";
String description = "Когда рыночек порешал";
List<String> tags = Arrays.asList("Доля", "юаня", "трансграничных");
Category category = Category.OTHER;
// Register the name to Alice
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
TransactionUtils.signAndMint(repository, transactionData, alice);
// Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
title, description, tags, category);
// Check the chunk count is correct
assertEquals(10, arbitraryDataFile.chunkCount());
// Check the metadata is correct
String expectedTrimmedTitle = "Доля юаня в трансграничных Доля юаня в тран";
assertEquals(expectedTrimmedTitle, arbitraryDataFile.getMetadata().getTitle());
assertEquals(description, arbitraryDataFile.getMetadata().getDescription());
assertEquals(tags, arbitraryDataFile.getMetadata().getTags());
assertEquals(category, arbitraryDataFile.getMetadata().getCategory());
assertEquals("text/plain", arbitraryDataFile.getMetadata().getMimeType());
}
}
@Test @Test
public void testMetadataLengths() throws DataException, IOException, MissingDataException { public void testMetadataLengths() throws DataException, IOException, MissingDataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {