package org.asamk.signal.manager.util; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressWarnings({"java:S6218"}) public record DataURI(String mediaType, Map parameter, byte[] data) { public static final Pattern DATA_URI_PATTERN = Pattern.compile( "\\Adata:(?.+?/.+?)?(?;.+?=.+?)?(?;base64)?,(?.+)\\z", Pattern.CASE_INSENSITIVE); public static final Pattern PARAMETER_PATTERN = Pattern.compile("\\G;(?.+)=(?.+)", Pattern.CASE_INSENSITIVE); /** * Generates a new {@link DataURI} object that follows * RFC 2397 from the given string. *

* The {@code dataURI} must be of the form: *

* {@code * data:[][;base64], * } *

* The {@code } is an Internet media type specification (with * optional parameters.) The appearance of ";base64" means that the data * is encoded as base64. Without ";base64", the data is represented using (ASCII) URL Escaped encoding. * If {@code } is omitted, it defaults to {@link MimeUtils#PLAIN_TEXT}. * Parameter values should use the URL Escaped encoding. * * @param dataURI the data URI * @return a data URI object * @throws IllegalArgumentException if the given string is not a valid data URI */ public static DataURI of(final String dataURI) { final var matcher = DATA_URI_PATTERN.matcher(dataURI); if (!matcher.find()) { throw new IllegalArgumentException("The given string is not a valid data URI."); } final Map parameters = new HashMap<>(); final var params = matcher.group("parameters"); if (params != null) { final Matcher paramsMatcher = PARAMETER_PATTERN.matcher(params); while (paramsMatcher.find()) { final var key = paramsMatcher.group("key"); final var value = URLDecoder.decode(paramsMatcher.group("value"), StandardCharsets.UTF_8); parameters.put(key, value); } } final boolean isBase64 = matcher.group("base64") != null; final byte[] data; if (isBase64) { data = Base64.getDecoder().decode(matcher.group("data").getBytes(StandardCharsets.UTF_8)); } else { data = URLDecoder.decode(matcher.group("data"), StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8); } return new DataURI(Optional.ofNullable(matcher.group("type")).orElse(MimeUtils.PLAIN_TEXT), parameters, data); } }