Reviewed-by: prr, kcr
This commit is contained in:
Brent Christian 2026-07-21 18:20:34 +00:00
commit 9efe2ddd38
29 changed files with 1426 additions and 477 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -68,7 +68,7 @@ class JarVerifier {
private ArrayList<SignatureFileVerifier> pendingBlocks;
/* cache of CodeSigner objects */
private ArrayList<CodeSigner[]> signerCache;
private List<CodeSigner[]> signerCache;
/* Are we parsing a block? */
private boolean parsingBlockOrSF = false;
@ -288,7 +288,7 @@ class JarVerifier {
String key = uname.substring(0, uname.lastIndexOf('.'));
if (signerCache == null)
signerCache = new ArrayList<>();
signerCache = new LinkedList<>();
if (manDig == null) {
synchronized(manifestRawBytes) {

View File

@ -571,8 +571,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
throws ProtocolException {
lock();
try {
if (connecting) {
throw new IllegalStateException("connect in progress");
if (connected || connecting) {
throw new IllegalStateException("Already connected");
}
super.setRequestMethod(method);
} finally {

View File

@ -178,7 +178,7 @@ public abstract class AbstractDelegateHttpsURLConnection extends
public void connect() throws IOException {
if (connected)
return;
plainConnect();
super.connect();
if (cachedResponse != null) {
// using cached response
return;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -341,6 +341,21 @@ public class SignerInfo implements DerEncoder {
// if there are authenticated attributes, get the message
// digest and compare it with the digest of data
if (authenticatedAttributes == null) {
// RFC 5652 Section 5.3. "[signedAttrs] MUST be present if the
// content type of the EncapsulatedContentInfo value being
// signed is not id-data."
if (!content.getContentType().equals(ContentInfo.DATA_OID)) {
throw new SignatureException("Missing authenticatedAttributes");
} else {
try {
var c = new DerValue(data);
if (c.tag == DerValue.tag_Set) {
throw new SignatureException("Not a .SF file content");
}
} catch (IOException e) {
// Expected or ignored
}
}
dataSigned = data;
} else {
@ -688,6 +703,12 @@ public class SignerInfo implements DerEncoder {
return null;
}
// RFC 3161 Section 2.4.2. id-ct-TSTInfo.
if (!tsToken.getContentInfo().getContentType()
.equals(ContentInfo.TIMESTAMP_TOKEN_INFO_OID)) {
throw new SignatureException("Not using id-ct-TSTInfo");
}
// Extract the content (an encoded timestamp token info)
byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
// Extract the signer (the Timestamping Authority)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,6 +25,7 @@
package sun.security.provider.certpath;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
@ -188,6 +189,16 @@ class URICertStore extends CertStoreSpi {
return timeoutVal;
}
/**
* Maximum size for a CRL downloaded through a URICertStore
* in bytes. This can be controlled by the com.sun.security.crl.maxSize
* Security or System property. The System property, if set, overrides
* the Security property. The default size is 20MiB.
*/
private static final long MAX_CRL_DOWNLOAD_SIZE =
SecurityProperties.getOverridableLongProp(
"com.sun.security.crl.maxSize", 20971520, debug);
/**
* Enumeration for the allowed schemes we support when following a
* URI from an authorityInfoAccess extension on a certificate.
@ -228,6 +239,13 @@ class URICertStore extends CertStoreSpi {
private static final boolean CA_ISS_ALLOW_ANY;
static {
// Add a debug message for the configured CRL download limit
if (debug != null) {
debug.println("Maximum downloadable CRL size: " +
MAX_CRL_DOWNLOAD_SIZE +
((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : ""));
}
boolean allowAny = false;
try {
if (Builder.USE_AIA) {
@ -623,7 +641,19 @@ class URICertStore extends CertStoreSpi {
if (debug != null) {
debug.println("Downloading new CRL...");
}
crl = (X509CRL) factory.generateCRL(in);
InputStream crlIn = (MAX_CRL_DOWNLOAD_SIZE > -1) ?
new SizeLimitedInputStream(in, MAX_CRL_DOWNLOAD_SIZE) :
in;
try {
crl = (X509CRL) factory.generateCRL(crlIn);
} catch (IllegalArgumentException iae) {
// IAE should only be thrown when the CRL exceeds a
// configured maximum length.
if (debug != null) {
debug.println("Discarding CRL: " + iae.getMessage());
crl = null;
}
}
}
return getMatchingCRLs(crl, selector);
} catch (IOException | CRLException e) {
@ -816,4 +846,59 @@ class URICertStore extends CertStoreSpi {
return true;
}
}
/**
* Stream wrapper used when an InputStream passed into a CertificateFactory
* needs to be size limited. It will throw IllegalArgumentException when
* the downloaded resource via the underlying stream exceeds the maximum
* limit.
*/
private static class SizeLimitedInputStream extends FilterInputStream {
private final long maxBytes;
private long bytesRead = 0;
private SizeLimitedInputStream(InputStream in, long maxBytes) {
super(in);
this.maxBytes = maxBytes;
}
@Override
public int read() throws IOException {
if (bytesRead >= maxBytes) {
// We will use IAE here to differentiate this special case
// from other IOEs that the underlying input stream might
// legitimately throw.
throw new IllegalArgumentException("InputStream exceeded max " +
"size of " + maxBytes);
}
int b = super.read();
if (b != -1) {
bytesRead++;
}
return b;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (bytesRead >= maxBytes) {
// We will use IAE here to differentiate this special case
// from other IOEs that the underlying input stream might
// legitimately throw.
throw new IllegalArgumentException("InputStream exceeded max " +
"size of " + maxBytes);
}
long remaining = maxBytes - bytesRead;
int toRead = (int) Math.min(len, remaining);
int n = super.read(b, off, toRead);
if (n != -1) {
bytesRead += n;
}
return n;
}
}
}

View File

@ -181,11 +181,13 @@ public enum Alert {
AlertMessage(TransportContext context,
ByteBuffer m) throws IOException {
// From RFC 8446 "Implementations
// MUST NOT send Handshake and Alert records that have a zero-length
// TLSInnerPlaintext.content; if such a message is received, the
// receiving implementation MUST terminate the connection with an
// "unexpected_message" alert."
// From RFC 8446: TLSv1.3
//
// Implementations MUST NOT send Handshake and Alert records that
// have a zero-length TLSInnerPlaintext.content; if such a message
// is received, the receiving implementation MUST terminate the
// connection with an "unexpected_message" alert.
if (m.remaining() == 0) {
throw context.fatal(Alert.UNEXPECTED_MESSAGE,
"Alert fragments must not be zero length.");
@ -264,27 +266,39 @@ public enum Alert {
} else if ((level == Level.WARNING) && (alert != null)) {
// Terminate the connection if an alert with a level of warning
// is received during handshaking, except the no_certificate
// warning.
if (alert.handshakeOnly && (tc.handshakeContext != null)) {
// It's OK to get a no_certificate alert from a client of
// which we requested client authentication. However,
// if we required it, then this is not acceptable.
if (tc.sslConfig.isClientMode ||
alert != Alert.NO_CERTIFICATE ||
(tc.sslConfig.clientAuthType !=
// warning for SSLv3.
HandshakeContext hc = tc.handshakeContext;
if (alert.handshakeOnly && (hc != null)) {
// In SSLv3, it's OK to get a no_certificate alert from a
// client where we requested (want) client authentication.
// If we required it (need), this is not acceptable
// and must fail.
//
// no_certificate alerts are not acceptable in TLSv1.*.
//
if (!tc.sslConfig.isClientMode &&
(hc.negotiatedProtocol == ProtocolVersion.SSL30) &&
(alert == Alert.NO_CERTIFICATE) &&
(tc.sslConfig.clientAuthType ==
ClientAuthType.CLIENT_AUTH_REQUESTED)) {
throw tc.fatal(Alert.HANDSHAKE_FAILURE,
"received handshake warning: " + alert.description);
} else {
// Otherwise, ignore the warning but remove the
// Certificate and CertificateVerify handshake
// consumer so the state machine doesn't expect it.
tc.handshakeContext.handshakeConsumers.remove(
SSLHandshake.CERTIFICATE.id);
tc.handshakeContext.handshakeConsumers.remove(
// We'll ignore the warning and remove the Certificate,
// CompressedCertificate and CertificateVerify handshake
// consumers so the state machine isn't expecting them.
if (hc.handshakeConsumers.remove(
SSLHandshake.CERTIFICATE.id) != null) {
hc.handshakeConsumers.remove(
SSLHandshake.COMPRESSED_CERTIFICATE.id);
tc.handshakeContext.handshakeConsumers.remove(
hc.handshakeConsumers.remove(
SSLHandshake.CERTIFICATE_VERIFY.id);
} else {
throw tc.fatal(Alert.HANDSHAKE_FAILURE,
"NO_CERTIFICATE alert received when certs" +
" were not expected or already received");
}
} else {
throw tc.fatal(Alert.HANDSHAKE_FAILURE,
"Received handshake warning: " + alert.description);
}
} // Otherwise, ignore the warning
} else { // fatal or unknown

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,7 @@
package sun.security.ssl;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@ -121,6 +122,7 @@ abstract class HelloCookieManager {
private static final
class D10HelloCookieManager extends HelloCookieManager {
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
final SecureRandom secureRandom;
private int cookieVersion; // allow to wrap, version + sequence
private final byte[] cookieSecret;
@ -170,6 +172,7 @@ abstract class HelloCookieManager {
}
byte[] helloBytes = clientHello.getHelloCookieBytes();
md.update(helloBytes);
md.update(getHostPortBytes(context));
byte[] cookie = md.digest(secret); // 32 bytes
cookie[0] = (byte)((version >> 24) & 0xFF);
@ -205,11 +208,30 @@ abstract class HelloCookieManager {
}
byte[] helloBytes = clientHello.getHelloCookieBytes();
md.update(helloBytes);
md.update(getHostPortBytes(context));
byte[] target = md.digest(secret); // 32 bytes
target[0] = cookie[0];
return MessageDigest.isEqual(target, cookie);
}
/**
* Returns host and port bytes if those are set.
* Using ASCII unit separator character to separate host and port so we
* can differentiate between otherwise identical host and port string
* concatenations, for example host 172.0.0.1 with port 25 and host
* 172.0.0.12 with port 5.
*/
private static byte[] getHostPortBytes(ServerHandshakeContext context) {
final String host = context.conContext.transport.getPeerHost();
final int port = context.conContext.transport.getPeerPort();
final String hostStr = host != null ? host : "";
final String portStr = port > -1 ? Integer.toString(port) : "";
return hostStr.isEmpty() && portStr.isEmpty() ?
EMPTY_BYTE_ARRAY :
(hostStr + '\u001F' + portStr).getBytes(
StandardCharsets.UTF_8);
}
}
private static final

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,8 @@
package sun.security.timestamp;
import java.io.IOException;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.util.Debug;
import sun.security.util.DerValue;
@ -357,6 +359,11 @@ public final class TSResponse {
DerValue timestampToken = derValue.data.getDerValue();
encodedTsToken = timestampToken.toByteArray();
tsToken = new PKCS7(encodedTsToken);
// RFC 3161 Section 2.4.2. id-ct-TSTInfo.
if (!tsToken.getContentInfo().getContentType()
.equals(ContentInfo.TIMESTAMP_TOKEN_INFO_OID)) {
throw new TimestampException("Not using id-ct-TSTInfo");
}
tstInfo = new TimestampToken(tsToken.getContentInfo().getData());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -157,6 +157,9 @@ public class DerValue {
*/
public static final byte tag_SetOf = 0x31;
// Max nested depth for constructed data
private static final int MAX_CONSTRUCTED_NEST = 30;
// This class is mostly immutable except that:
//
// 1. resetTag() modifies the tag
@ -564,6 +567,14 @@ public class DerValue {
* @return the octet string held in this DER value
*/
public byte[] getOctetString() throws IOException {
return getOctetString(0);
}
private byte[] getOctetString(int limit) throws IOException {
if (++limit > MAX_CONSTRUCTED_NEST) {
throw new IOException("Nested OctetString limit reached ("
+ MAX_CONSTRUCTED_NEST + ").");
}
if (tag != tag_OctetString && !isConstructed(tag_OctetString)) {
throw new IOException(
@ -582,7 +593,7 @@ public class DerValue {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DerInputStream dis = data();
while (dis.available() > 0) {
bout.write(dis.getDerValue().getOctetString());
bout.write(dis.getDerValue().getOctetString(limit));
}
return bout.toByteArray();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -263,7 +263,7 @@ public class HostnameChecker {
* The <code>name</code> parameter should represent a DNS name. The
* <code>template</code> parameter may contain the wildcard character '*'.
*/
private boolean isMatched(String name, String template,
public boolean isMatched(String name, String template,
boolean chainsToPublicCA) {
// Normalize to Unicode, because PSL is in Unicode.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -139,6 +139,36 @@ public class SecurityProperties {
}
}
/**
* A convenience routine for fetching a numeric value from a Security
* or System property and returning it as a long. The value from the
* property is obtained according to the logic in
* {@link SecurityProperties#getOverridableProperty(String)}
*
* @param prop the property to query
* @param defaultValue the default value
* @param dbg a Debug object, if null no debug messages will be sent
* @return the value of the property as a {@code long}. If a non-numeric
* value is supplied, the default value will be returned.
*/
public static long getOverridableLongProp(String prop, long defaultValue,
Debug dbg) {
long longVal = defaultValue;
try {
String propVal = SecurityProperties.getOverridableProperty(prop);
if (propVal != null) {
longVal = Long.parseLong(propVal);
}
} catch (NumberFormatException nfe) {
// We will use the default, but add a warning debug message
if (dbg != null) {
dbg.println("Warning: Non-numeric value found in property " +
prop + ", using default value of " + defaultValue);
}
}
return longVal;
}
/**
* Convenience method for fetching System property values that are booleans.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -46,7 +46,12 @@ public class SignatureFileVerifier {
/* Are we debugging ? */
private static final Debug debug = Debug.getInstance("jar");
private final ArrayList<CodeSigner[]> signerCache;
private final List<CodeSigner[]> signerCache;
// The maximum size of the signerCache. This is for debug only
// and not intended to be adjusted by users.
private static int SIGNER_CACHE_SIZE
= Integer.getInteger("sun.security.util.jar.signer.cache.size", 5);
private static final String ATTR_DIGEST =
"-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS.toUpperCase(Locale.ENGLISH);
@ -97,7 +102,7 @@ public class SignatureFileVerifier {
*
* @param rawBytes the raw bytes of the signature block file
*/
public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache,
public SignatureFileVerifier(List<CodeSigner[]> signerCache,
ManifestDigester md,
String name,
byte[] rawBytes)
@ -282,7 +287,6 @@ public class SignatureFileVerifier {
} finally {
Providers.stopJarVerification(obj);
}
}
private void processImpl(Hashtable<String, CodeSigner[]> signers,
@ -850,6 +854,9 @@ public class SignatureFileVerifier {
newSigners.length);
}
signerCache.add(cachedSigners);
if (signerCache.size() > SIGNER_CACHE_SIZE) {
signerCache.remove(0);
}
signers.put(name, cachedSigners);
}

View File

@ -52,6 +52,8 @@ import sun.security.util.*;
public class DNSName implements GeneralNameInterface {
private final String name;
private static final HostnameChecker HOSTNAME_CHECKER =
HostnameChecker.getInstance(HostnameChecker.TYPE_TLS);
private static final String DNS_ALLOWED =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
@ -218,17 +220,22 @@ public class DNSName implements GeneralNameInterface {
* For example, www.host.example.com would satisfy the constraint but
* host1.example.com would not.
* <p>
* RFC6125: Match wildcard pattern in the input name being constrained,
* any wildcard in this name will be matched as a literal character.
* <p>
* RFC1034: By convention, domain names can be stored with arbitrary case, but
* domain name comparisons for all present domain functions are done in a
* case-insensitive manner, assuming an ASCII character set, and a high
* order zero bit.
*
* @param inputName to be checked for being constrained
* @param matchWildcard whether to match a wildcard in inputName
* @return constraint type above
* @throws UnsupportedOperationException if name is not exact match, but narrowing and widening are
* not supported for this name type.
*/
public int constrains(GeneralNameInterface inputName) throws UnsupportedOperationException {
public int constrains(GeneralNameInterface inputName, boolean matchWildcard)
throws UnsupportedOperationException {
int constraintType;
if (inputName == null)
constraintType = NAME_DIFF_TYPE;
@ -238,7 +245,10 @@ public class DNSName implements GeneralNameInterface {
String inName =
(((DNSName)inputName).getName()).toLowerCase(Locale.ENGLISH);
String thisName = name.toLowerCase(Locale.ENGLISH);
if (inName.equals(thisName))
if (inName.equals(thisName) || (matchWildcard
&& inName.contains("*")
&& HOSTNAME_CHECKER.isMatched(thisName, inName, false)))
constraintType = NAME_MATCH;
else if (thisName.endsWith(inName)) {
int inNdx = thisName.lastIndexOf(inName);
@ -259,6 +269,10 @@ public class DNSName implements GeneralNameInterface {
return constraintType;
}
public int constrains(GeneralNameInterface inputName) {
return constrains(inputName, false);
}
/**
* Return subtree depth of this name for purposes of determining
* NameConstraints minimum and maximum bounds and for calculating

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -506,9 +506,15 @@ public class NameConstraintsExtension extends Extension
if (exName == null)
continue;
// Match a wildcard in DNSName only against the excluded subtree
int matchResult =
exName.getType() == GeneralNameInterface.NAME_DNS
? ((DNSName) exName).constrains(name, true)
: exName.constrains(name);
// if name matches or narrows any excluded subtree,
// return false
switch (exName.constrains(name)) {
switch (matchResult) {
case GeneralNameInterface.NAME_DIFF_TYPE:
case GeneralNameInterface.NAME_WIDENS: // name widens excluded
case GeneralNameInterface.NAME_SAME_TYPE:

View File

@ -1730,6 +1730,22 @@ jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128
# ldap://ldap.company.com/dc=company,dc=com?caCertificate;binary
com.sun.security.allowedAIALocations=
#
# Certificate Revocation List (CRL) Download Size Limitation
#
# This property sets a size limit for CRLs downloaded via URIs provided
# in the CRL Distribution Points certificate extension. This property
# must be a numeric value that is the size in bytes of the DER-encoded CRL.
# For protocols that can return multi-value responses, such as LDAP, the
# size threshold is the sum of all CRLs downloaded from a single search
# query. CRLs that exceed this length will not be processed during certificate
# path validation. This size limit does not apply to CRLs that are imported
# through non-network-based means. A negative value will disable this size
# limitation. A non-numeric value will be ignored and the default size will
# be used instead. The default size limit is 20MiB.
# This property may be overridden by a System property of the same name.
com.sun.security.crl.maxSize = 20971520
#
# PKCS #8 encoding format for newly created ML-KEM and ML-DSA private keys
#

View File

@ -31,12 +31,8 @@ package sun.awt.image;
import java.awt.image.ImageConsumer;
import java.awt.image.IndexColorModel;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.IOException;
import static java.lang.Math.multiplyExact;
@ -62,9 +58,8 @@ public class XbmImageDecoder extends ImageDecoder {
ImageConsumer.SINGLEPASS |
ImageConsumer.SINGLEFRAME);
private static final int MAX_CHAR_LIMIT = 128000;
private static final int MAX_XBM_SIZE = 16384;
private static final int HEADER_SCAN_LIMIT = 100;
public XbmImageDecoder(InputStreamImageSource src, InputStream is) {
super(src, is);
if (!(input instanceof BufferedInputStream)) {
@ -86,150 +81,229 @@ public class XbmImageDecoder extends ImageDecoder {
* produce an image from the stream.
*/
public void produceImage() throws IOException, ImageFormatException {
char[] nm = new char[80];
int c;
int i = 0;
int state = 0;
int H = 0;
int W = 0;
int x = 0;
int y = 0;
int n = 0;
int state = 0;
boolean consumeWidthValue = false;
boolean consumeHeightValue = false;
boolean validWidthConsumed = false;
boolean validHeightConsumed = false;
// number of tokens seen as part of this define statement
int defineTokenCount = 0;
byte[] raster = null;
IndexColorModel model = null;
int charCount = 0;
String matchRegex = "\\s*(0[xX])?((?:(?!,|\\};).)+)(,|\\};)";
String replaceRegex = "0[xX]|,|\\s+|\\};";
String line;
int lineNum = 0;
try (BufferedReader br = new BufferedReader(new InputStreamReader(input))) {
// loop to process XBM header - width, height and create raster
while (!aborted && (line = br.readLine()) != null
&& lineNum <= HEADER_SCAN_LIMIT) {
lineNum++;
// process #define stmts
if (line.trim().startsWith("#define")) {
String[] token = line.split("\\s+");
if (token.length != 3) {
error("Error while parsing define statement");
}
try {
if (state < 2) {
if (token[1].endsWith("h")) {
W = Integer.parseInt(token[2]);
} else if (token[1].endsWith("ht")) {
H = Integer.parseInt(token[2]);
}
// After the 1st dimension is set, state becomes 1;
// after the 2nd dimension is set, state becomes 2
++state;
}
} catch (NumberFormatException nfe) {
// parseInt() can throw NFE
error("Error while parsing width or height.");
}
}
if (state == 2) {
if (W <= 0 || H <= 0) {
error("Invalid values for width or height.");
}
if (multiplyExact(W, H) > MAX_XBM_SIZE) {
error("Large XBM file size."
+ " Maximum allowed size: " + MAX_XBM_SIZE);
}
model = new IndexColorModel(8, 2, XbmColormap,
0, false, 0);
setDimensions(W, H);
setColorModel(model);
setHints(XbmHints);
headerComplete();
raster = new byte[W];
state = 3;
break;
}
//read header info
while (!aborted && (c = input.read()) != -1) {
charCount++;
if (charCount > MAX_CHAR_LIMIT) {
error("Incomplete image after reading "
+ "the maximum allowed number of characters: "
+ MAX_CHAR_LIMIT);
}
if (state != 3) {
error("Width or Height of XBM file not defined");
}
boolean contFlag = false;
StringBuilder sb = new StringBuilder();
// loop to process image data
while (!aborted && (line = br.readLine()) != null) {
lineNum++;
if (!contFlag) {
if (line.contains("[]")) {
contFlag = true;
} else {
if ('a' <= c && c <= 'z' ||
'A' <= c && c <= 'Z' ||
'0' <= c && c <= '9' || c == '#' || c == '_') {
if (i < nm.length) {
nm[i++] = (char) c;
} else {
error("XBM header contains literal greater than size 80");
}
} else if (i > 0) {
int nc = i;
i = 0;
if (defineTokenCount >= 1) {
// we are inside a #define line
defineTokenCount++;
}
if (defineTokenCount == 0) {
if (nc == 7 &&
nm[0] == '#' &&
nm[1] == 'd' &&
nm[2] == 'e' &&
nm[3] == 'f' &&
nm[4] == 'i' &&
nm[5] == 'n' &&
nm[6] == 'e')
{
defineTokenCount++;
continue;
}
}
} else if (defineTokenCount == 2) {
// consume second token in #define line
if (state < 2) {
if (nm[nc - 1] == 'h' &&
!validWidthConsumed) {
consumeWidthValue = true;
} else if ((nm[nc - 1] == 't' && nc > 1 &&
nm[nc - 2] == 'h') &&
!validHeightConsumed) {
consumeHeightValue = true;
}
}
} else if (defineTokenCount == 3) {
defineTokenCount = 0;
// consume third token in #define line
int n = 0;
for (int p = 0; p < nc; p++) {
if ('0' <= (c = nm[p]) && c <= '9') {
n = n * 10 + c - '0';
if (n > MAX_XBM_SIZE) {
error("Width/Height cannot be more than: "
+ MAX_XBM_SIZE);
}
} else {
error("Invalid width/height value");
}
}
int end = line.indexOf(';');
if (end >= 0) {
sb.append(line, 0, end + 1);
break;
} else {
sb.append(line).append(System.lineSeparator());
if (n > 0 && (consumeWidthValue || consumeHeightValue)) {
if (consumeWidthValue) {
if (!validWidthConsumed) {
W = n;
validWidthConsumed = true;
state++;
}
consumeWidthValue = false;
} else if (consumeHeightValue) {
if (!validHeightConsumed) {
H = n;
validHeightConsumed = true;
state++;
}
consumeHeightValue = false;
}
}
// verify the consumed width & height value and initialize
// required constructs
if (state == 2) {
if (multiplyExact(W, H) > MAX_XBM_SIZE) {
error("Large XBM file size."
+ " Maximum allowed size: " + MAX_XBM_SIZE);
}
model = new IndexColorModel(8, 2, XbmColormap,
0, false, 0);
setDimensions(W, H);
setColorModel(model);
setHints(XbmHints);
headerComplete();
raster = new byte[W];
state = 3;
break;
}
}
}
String resultLine = sb.toString();
int cutOffIndex = resultLine.indexOf('{');
resultLine = resultLine.substring(cutOffIndex + 1);
Matcher matcher = Pattern.compile(matchRegex).matcher(resultLine);
while (matcher.find()) {
if (y >= H) {
error("Scan size of XBM file exceeds"
+ " the defined width x height");
}
int startIndex = matcher.start();
int endIndex = matcher.end();
String hexByte = resultLine.substring(startIndex, endIndex);
hexByte = hexByte.replaceAll("^\\s+", "");
if (!(hexByte.startsWith("0x")
|| hexByte.startsWith("0X"))) {
error("Invalid hexadecimal number at Ln#:" + lineNum
+ " Col#:" + (startIndex + 1));
}
hexByte = hexByte.replaceAll(replaceRegex, "");
if (hexByte.length() != 2) {
error("Invalid hexadecimal number at Ln#:" + lineNum
+ " Col#:" + (startIndex + 1));
}
try {
n = Integer.parseInt(hexByte, 16);
} catch (NumberFormatException nfe) {
error("Error parsing hexadecimal at Ln#:" + lineNum
+ " Col#:" + (startIndex + 1));
}
for (int mask = 1; mask <= 0x80; mask <<= 1) {
if (x < W) {
if ((n & mask) != 0)
raster[x] = 1;
else
raster[x] = 0;
}
x++;
}
if (x >= W) {
int result = setPixels(0, y, W, 1, model, raster, 0, W);
if (result <= 0) {
error("Unexpected error occurred during setPixel()");
}
x = 0;
y++;
}
}
imageComplete(ImageConsumer.STATICIMAGEDONE, true);
}
if (state != 3) {
error("Width or Height of XBM file not defined");
}
// skip until we find '{'
boolean imageDataStarted = false;
while (!aborted && (c = input.read()) != -1) {
charCount++;
if (charCount > MAX_CHAR_LIMIT) {
error("Incomplete image after reading "
+ "the maximum allowed number of characters: "
+ MAX_CHAR_LIMIT);
}
if (c == '{') {
imageDataStarted = true;
break;
}
}
if (!imageDataStarted) {
error("Missing '{' at the start of image data");
}
// used to make sure that we have the final delimiter '};',
// while parsing the image data
int previousChar = '{';
// parse image data
boolean imageDataTerminated = false;
while (!aborted && (c = input.read()) != -1) {
charCount++;
if (charCount > MAX_CHAR_LIMIT) {
error("Incomplete image after reading "
+ "the maximum allowed number of characters: "
+ MAX_CHAR_LIMIT);
}
if (c == ';') {
if (previousChar != '}') {
error("Abrupt end of image data without '};' delimiter");
}
imageDataTerminated = true;
break;
}
if (!Character.isWhitespace(c)) {
previousChar = c;
}
if (',' != c && '}' != c &&
!Character.isWhitespace(c)) {
nm[i++] = (char) c;
if (i > 4) {
error("Image hex data should be 3 or 4 characters long");
}
} else if (i == 3 || i == 4) {
// consume valid hex image data
int n = 0;
int nc = i;
i = 0;
if (nm[0] == '0' &&
(nm[1] == 'x' || nm[1] == 'X')) {
for (int p = 2; p < nc; p++) {
c = nm[p];
if ('0' <= c && c <= '9')
c = c - '0';
else if ('A' <= c && c <= 'F')
c = c - 'A' + 10;
else if ('a' <= c && c <= 'f')
c = c - 'a' + 10;
else
error("Corrupt hex image data");
n = n * 16 + c;
}
for (int mask = 1; mask <= 0x80; mask <<= 1) {
if (x < W) {
if ((n & mask) != 0)
raster[x] = 1;
else
raster[x] = 0;
}
x++;
}
if (x >= W) {
if ((y + 1) > H) {
error("Scan size of XBM file exceeds"
+ " the defined width x height");
}
if (setPixels(0, y, W, 1, model, raster, 0, W) == 0) {
error("Unexpected error occurred during setPixel()");
}
x = 0;
y++;
}
} else {
error("Corrupt hex image data");
}
} else if (i == 1 || i == 2) {
error("Image hex data should be 3 or 4 characters long");
}
}
if (!imageDataTerminated) {
error("Missing terminator ';'");
}
input.close();
imageComplete(ImageConsumer.STATICIMAGEDONE, true);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -2218,7 +2218,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP,
/* Means we need to fill in alpha */
if (!cvtToDefault && addAlpha) {
*mlibImagePP = (*sMlibSysFns.createFP)(MLIB_BYTE, 4, width, height);
if (*mlibImagePP != NULL) {
if (*mlibImagePP == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return -1;
} else {
unsigned int *dstP = (unsigned int *)
mlib_ImageGetData(*mlibImagePP);
int dstride = (*mlibImagePP)->stride>>2;
@ -2234,10 +2238,10 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP,
dP[x] = sP[x] | 0xff000000;
}
}
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return 0;
}
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return 0;
}
else if ((hintP->packing & BYTE_INTERLEAVED) == BYTE_INTERLEAVED) {
int nChans = (cmP->isDefaultCompatCM ? 4 : hintP->numChans);
@ -2252,6 +2256,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP,
hintP->sStride,
(unsigned char *)dataP
+ hintP->dataOffset);
if (*mlibImagePP == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return -1;
}
}
else if ((hintP->packing & SHORT_INTERLEAVED) == SHORT_INTERLEAVED) {
*mlibImagePP = (*sMlibSysFns.createStructFP)(MLIB_SHORT,
@ -2261,6 +2270,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP,
imageP->raster.scanlineStride*2,
(unsigned short *)dataP
+ hintP->channelOffset);
if (*mlibImagePP == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return -1;
}
}
else {
/* Release the data array */
@ -2360,6 +2374,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP,
width, height,
rasterP->scanlineStride*4,
(unsigned char *)dataP + offset);
if (*mlibImagePP == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return -1;
}
*dataPP = dataP;
return 0;
case sun_awt_image_IntegerComponentRaster_TYPE_BYTE_SAMPLES:
@ -2388,6 +2407,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP,
width, height,
rasterP->scanlineStride,
(unsigned char *)dataP + offset);
if (*mlibImagePP == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return -1;
}
*dataPP = dataP;
return 0;
case sun_awt_image_IntegerComponentRaster_TYPE_USHORT_SAMPLES:
@ -2418,6 +2442,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP,
width, height,
rasterP->scanlineStride*2,
(unsigned char *)dataP + offset);
if (*mlibImagePP == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP,
JNI_ABORT);
return -1;
}
*dataPP = dataP;
return 0;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -120,6 +120,7 @@ typedef struct streamBufferStruct {
size_t bufferLength; // Allocated, nut just used
int suspendable; // Set to true to suspend input
long remaining_skip; // Used only on input
jboolean isCopy; // GetByteArrayElements copied/pinned the Java array
} streamBuffer, *streamBufferPtr;
/*
@ -200,7 +201,8 @@ static void destroyStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
// Forward reference
static void unpinStreamBuffer(JNIEnv *env,
streamBufferPtr sb,
const JOCTET *next_byte);
const JOCTET *next_byte,
int streamReleaseMode);
/*
* Resets the state of a streamBuffer object that has been in use.
* The global reference to the stream is released, but the reference
@ -212,15 +214,16 @@ static void resetStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
(*env)->DeleteWeakGlobalRef(env, sb->ioRef);
sb->ioRef = NULL;
}
unpinStreamBuffer(env, sb, NULL);
unpinStreamBuffer(env, sb, NULL, JNI_ABORT);
sb->bufferOffset = NO_DATA;
sb->suspendable = FALSE;
sb->remaining_skip = 0;
}
/*
* Pins the data buffer associated with this stream. Returns OK on
* success, NOT_OK on failure, as GetPrimitiveArrayCritical may fail.
* Pins/copies the data buffer associated with this stream. Returns OK on
* success, NOT_OK on failure, as GetByteArrayElements
* may fail.
*/
static int pinStreamBuffer(JNIEnv *env,
streamBufferPtr sb,
@ -228,9 +231,9 @@ static int pinStreamBuffer(JNIEnv *env,
if (sb->hstreamBuffer != NULL) {
assert(sb->buf == NULL);
sb->buf =
(JOCTET *)(*env)->GetPrimitiveArrayCritical(env,
sb->hstreamBuffer,
NULL);
(JOCTET *)(*env)->GetByteArrayElements(env,
sb->hstreamBuffer,
&sb->isCopy);
if (sb->buf == NULL) {
return NOT_OK;
}
@ -242,11 +245,12 @@ static int pinStreamBuffer(JNIEnv *env,
}
/*
* Unpins the data buffer associated with this stream.
* Unpins/releases the data buffer associated with this stream.
*/
static void unpinStreamBuffer(JNIEnv *env,
streamBufferPtr sb,
const JOCTET *next_byte) {
const JOCTET *next_byte,
int streamReleaseMode) {
if (sb->buf != NULL) {
assert(sb->hstreamBuffer != NULL);
if (next_byte == NULL) {
@ -254,11 +258,13 @@ static void unpinStreamBuffer(JNIEnv *env,
} else {
sb->bufferOffset = next_byte - sb->buf;
}
(*env)->ReleasePrimitiveArrayCritical(env,
sb->hstreamBuffer,
sb->buf,
0);
sb->buf = NULL;
(*env)->ReleaseByteArrayElements(env,
sb->hstreamBuffer,
(jbyte *)sb->buf,
streamReleaseMode);
if (streamReleaseMode != JNI_COMMIT) {
sb->buf = NULL;
}
}
}
@ -276,6 +282,7 @@ static void clearStreamBuffer(streamBufferPtr sb) {
typedef struct pixelBufferStruct {
jobject hpixelObject; // Usually a DataBuffer bank as a byte array
unsigned int byteBufferLength;
jboolean isCopy; // GetByteArrayElements copied/pinned the Java array
union pixptr {
INT32 *ip; // Pinned buffer pointer, as 32-bit ints
unsigned char *bp; // Pinned buffer pointer, as bytes
@ -309,7 +316,7 @@ static int setPixelBuffer(JNIEnv *env, pixelBufferPtr pb, jobject obj) {
}
// Forward reference
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb);
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode);
/*
* Resets a pixel buffer to its initial state. Unpins any pixel buffer,
@ -318,7 +325,7 @@ static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb);
*/
static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
if (pb->hpixelObject != NULL) {
unpinPixelBuffer(env, pb);
unpinPixelBuffer(env, pb, JNI_ABORT);
(*env)->DeleteGlobalRef(env, pb->hpixelObject);
pb->hpixelObject = NULL;
pb->byteBufferLength = 0;
@ -326,13 +333,13 @@ static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
}
/*
* Pins the data buffer. Returns OK on success, NOT_OK on failure.
* Pins/copies the data buffer. Returns OK on success, NOT_OK on failure.
*/
static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
if (pb->hpixelObject != NULL) {
assert(pb->buf.ip == NULL);
pb->buf.bp = (unsigned char *)(*env)->GetPrimitiveArrayCritical
(env, pb->hpixelObject, NULL);
pb->buf.bp = (unsigned char *)(*env)->GetByteArrayElements
(env, pb->hpixelObject, &pb->isCopy);
if (pb->buf.bp == NULL) {
return NOT_OK;
}
@ -341,17 +348,19 @@ static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
}
/*
* Unpins the data buffer.
* Unpins/releases the pixel buffer.
*/
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode) {
if (pb->buf.ip != NULL) {
assert(pb->hpixelObject != NULL);
(*env)->ReleasePrimitiveArrayCritical(env,
pb->hpixelObject,
pb->buf.ip,
0);
pb->buf.ip = NULL;
(*env)->ReleaseByteArrayElements(env,
pb->hpixelObject,
(jbyte *)pb->buf.ip,
pixelReleaseMode);
if (pixelReleaseMode != JNI_COMMIT) {
pb->buf.ip = NULL;
}
}
}
@ -468,34 +477,28 @@ static j_common_ptr destroyImageioData(JNIEnv *env, imageIODataPtr data) {
/******************** Java array pinning and unpinning *****************/
/* We use Get/ReleasePrimitiveArrayCritical functions to avoid
* the need to copy array elements for the above two objects.
*
* MAKE SURE TO:
*
* - carefully insert pairs of RELEASE_ARRAYS and GET_ARRAYS around
* callbacks to Java.
* - call RELEASE_ARRAYS before returning to Java.
*
* Otherwise things will go horribly wrong. There may be memory leaks,
* excessive pinning, or even VM crashes!
*
* Note that GetPrimitiveArrayCritical may fail!
/*
* We use Get/ReleaseByteArrayElements functions for access stream
* and pixel information from Java level arrays.
* If we receive reference to copy of Java array make sure you update
* Java array also when the latest information is needed at Java level.
* Also we use specific release modes for performance optimizations.
*/
/*
* Release (unpin) all the arrays in use during a read.
* Release (unpin) both stream and pixel arrays.
*/
static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte)
static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte,
int streamReleaseMode, int pixelReleaseMode)
{
unpinStreamBuffer(env, &data->streamBuf, next_byte);
unpinStreamBuffer(env, &data->streamBuf, next_byte, streamReleaseMode);
unpinPixelBuffer(env, &data->pixelBuf);
unpinPixelBuffer(env, &data->pixelBuf, pixelReleaseMode);
}
/*
* Get (pin) all the arrays in use during a read.
* Get (pin) both stream and pixel arrays.
*/
static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte) {
if (pinStreamBuffer(env, &data->streamBuf, next_byte) == NOT_OK) {
@ -503,7 +506,7 @@ static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte
}
if (pinPixelBuffer(env, &data->pixelBuf) == NOT_OK) {
RELEASE_ARRAYS(env, data, *next_byte);
RELEASE_ARRAYS(env, data, *next_byte, JNI_ABORT, JNI_ABORT);
return NOT_OK;
}
return OK;
@ -570,26 +573,16 @@ sun_jpeg_output_message (j_common_ptr cinfo)
theObject = data->imageIOobj;
if (cinfo->is_decompressor) {
struct jpeg_source_mgr *src = ((j_decompress_ptr)cinfo)->src;
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, theObject,
JPEGImageReader_warningWithMessageID,
string);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit(cinfo);
}
} else {
struct jpeg_destination_mgr *dest = ((j_compress_ptr)cinfo)->dest;
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
(*env)->CallVoidMethod(env, theObject,
JPEGImageWriter_warningWithMessageID,
string);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit(cinfo);
}
}
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit(cinfo);
}
}
@ -941,7 +934,7 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
#ifdef DEBUG_IIO_JPEG
printf("Filling input buffer, remaining skip is %ld, ",
sb->remaining_skip);
printf("Buffer length is %d\n", sb->bufferLength);
printf("Buffer length is %zu\n", sb->bufferLength);
#endif
/*
@ -956,8 +949,15 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
/*
* Now fill a complete buffer, or as much of one as the stream
* will give us if we are near the end.
*
* The native copy of java array is not valid anymore so we just
* release it and get new copy, if we don't have native copy we rely
* on JVM to maintain the pinned handle of java array.
*/
RELEASE_ARRAYS(env, data, src->next_input_byte);
jboolean isCopy = sb->isCopy;
if (isCopy) {
unpinStreamBuffer(env, &data->streamBuf, src->next_input_byte, JNI_ABORT);
}
GET_IO_REF(input);
@ -969,9 +969,12 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
if ((ret > 0) && ((unsigned int)ret > sb->bufferLength)) {
ret = (int)sb->bufferLength;
}
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
if ((*env)->ExceptionCheck(env) ||
(isCopy && (pinStreamBuffer(env,
&data->streamBuf,
&(src->next_input_byte)) == NOT_OK))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
#ifdef DEBUG_IIO_JPEG
@ -988,12 +991,10 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
#ifdef DEBUG_IIO_JPEG
printf("YO! Early EOI! ret = %d\n", ret);
#endif
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, reader,
JPEGImageReader_warningOccurredID,
READ_NO_EOI);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
@ -1008,97 +1009,6 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo)
return TRUE;
}
/*
* With I/O suspension turned on, the JPEG library requires that all
* buffer filling be done at the top application level, using this
* function. Due to the way that backtracking works, this procedure
* saves all of the data that was left in the buffer when suspension
* occurred and read new data only at the end.
*/
GLOBAL(void)
imageio_fill_suspended_buffer(j_decompress_ptr cinfo)
{
struct jpeg_source_mgr *src = cinfo->src;
imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
streamBufferPtr sb = &data->streamBuf;
JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2);
jint ret;
size_t offset, buflen;
jobject input = NULL;
/*
* The original (jpegdecoder.c) had code here that called
* InputStream.available and just returned if the number of bytes
* available was less than any remaining skip. Presumably this was
* to avoid blocking, although the benefit was unclear, as no more
* decompression can take place until more data is available, so
* the code would block on input a little further along anyway.
* ImageInputStreams don't have an available method, so we'll just
* block in the skip if we have to.
*/
if (sb->remaining_skip) {
src->skip_input_data(cinfo, 0);
}
/* Save the data currently in the buffer */
offset = src->bytes_in_buffer;
if (src->next_input_byte > sb->buf) {
memcpy(sb->buf, src->next_input_byte, offset);
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
GET_IO_REF(input);
buflen = sb->bufferLength - offset;
if (buflen <= 0) {
if (!GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
return;
}
ret = (*env)->CallIntMethod(env, input,
JPEGImageReader_readInputDataID,
sb->hstreamBuffer,
offset, buflen);
if ((ret > 0) && ((unsigned int)ret > buflen)) ret = (int)buflen;
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
/*
* If we have reached the end of the stream, then the EOI marker
* is missing. We accept such streams but generate a warning.
* The image is likely to be corrupted, though everything through
* the end of the last complete MCU should be usable.
*/
if (ret <= 0) {
jobject reader = data->imageIOobj;
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, reader,
JPEGImageReader_warningOccurredID,
READ_NO_EOI);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
sb->buf[offset] = (JOCTET) 0xFF;
sb->buf[offset + 1] = (JOCTET) JPEG_EOI;
ret = 2;
}
src->next_input_byte = sb->buf;
src->bytes_in_buffer = ret + offset;
return;
}
/*
* Skip num_bytes worth of data. The buffer pointer and count are
* advanced over num_bytes input bytes, using the input stream
@ -1160,16 +1070,13 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
return;
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
GET_IO_REF(input);
ret = (*env)->CallLongMethod(env,
input,
JPEGImageReader_skipInputBytesID,
(jlong) num_bytes);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
@ -1181,15 +1088,12 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
*/
if (ret <= 0) {
reader = data->imageIOobj;
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env,
reader,
JPEGImageReader_warningOccurredID,
READ_NO_EOI);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
sb->buf[0] = (JOCTET) 0xFF;
sb->buf[1] = (JOCTET) JPEG_EOI;
@ -1215,7 +1119,7 @@ imageio_term_source(j_decompress_ptr cinfo)
JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2);
jobject reader = data->imageIOobj;
if (src->bytes_in_buffer > 0) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
(*env)->CallVoidMethod(env,
reader,
JPEGImageReader_pushBackID,
@ -1659,7 +1563,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while reading the header. */
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
@ -1678,7 +1582,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
(*env)->ExceptionClear(env);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return retval;
}
@ -1701,7 +1605,11 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
printf("just read tables-only image; q table 0 at %p\n",
cinfo->quant_tbl_ptrs[0]);
#endif
RELEASE_ARRAYS(env, data, src->next_input_byte);
/*
* readImageHeader can be called independently, so
* we release the arrays when we return back.
*/
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
} else {
/*
* Now adjust the jpeg_color_space variable, which was set in
@ -1802,7 +1710,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
/* Leave the output space as CMYK */
}
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
/* read icc profile data */
profileData = read_icc_profile(env, cinfo);
@ -1819,14 +1726,17 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
cinfo->out_color_space,
cinfo->num_components,
profileData);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
if (reset) {
jpeg_abort_decompress(cinfo);
}
RELEASE_ARRAYS(env, data, src->next_input_byte);
/*
* readImageHeader can be called independently, so
* we release the arrays when we return back.
*/
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
}
return retval;
@ -1987,7 +1897,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while reading. */
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
@ -2005,7 +1915,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
(*env)->ExceptionClear(env);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return data->abortFlag;
}
@ -2037,7 +1947,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
jpeg_start_decompress(cinfo);
if (numBands != cinfo->output_components) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
JNU_ThrowByName(env, "javax/imageio/IIOException",
"Invalid argument to native readImage");
return data->abortFlag;
@ -2046,7 +1956,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
if (cinfo->output_components <= 0 ||
cinfo->image_width > (0xffffffffu / (unsigned int)cinfo->output_components))
{
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
JNU_ThrowByName(env, "javax/imageio/IIOException",
"Invalid number of output components");
return data->abortFlag;
@ -2055,7 +1965,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
// Allocate a 1-scanline buffer
scanLinePtr = (JSAMPROW)malloc(cinfo->image_width*cinfo->output_components);
if (scanLinePtr == NULL) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT);
JNU_ThrowByName( env,
"java/lang/OutOfMemoryError",
"Reading JPEG Stream");
@ -2070,22 +1980,18 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
// the first interesting pass.
jpeg_start_output(cinfo, cinfo->input_scan_number);
if (wantUpdates) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, this,
JPEGImageReader_passStartedID,
cinfo->input_scan_number-1);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
} else if (wantUpdates) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, this,
JPEGImageReader_passStartedID,
0);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
@ -2136,16 +2042,20 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
}
}
// And call it back to Java
RELEASE_ARRAYS(env, data, src->next_input_byte);
/*
* Optimisation to just commit the native pixel buffer
* content back to java array without releasing the
* native buffer.
*/
if (pb->isCopy) {
unpinPixelBuffer(env, pb, JNI_COMMIT);
}
(*env)->CallVoidMethod(env,
this,
JPEGImageReader_acceptPixelsID,
targetLine++,
progressive);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
@ -2175,11 +2085,9 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
done = TRUE;
}
if (wantUpdates) {
RELEASE_ARRAYS(env, data, src->next_input_byte);
(*env)->CallVoidMethod(env, this,
JPEGImageReader_passCompleteID);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data, &(src->next_input_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
@ -2204,13 +2112,16 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
this,
JPEGImageReader_skipPastImageID,
imageIndex);
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
} else {
jpeg_finish_decompress(cinfo);
}
free(scanLinePtr);
RELEASE_ARRAYS(env, data, src->next_input_byte);
RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0);
return data->abortFlag;
}
@ -2405,8 +2316,16 @@ imageio_empty_output_buffer (j_compress_ptr cinfo)
JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2);
jobject output = NULL;
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
/*
* Optimization to not delete the native copy of stream buffer,
* but just commit the content back to the Java array.
* In case where we don't have a copy, we rely on JVM to maintain
* the native reference of Java array.
*/
jboolean isCopy = sb->isCopy;
if (isCopy) {
unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT);
}
GET_IO_REF(output);
(*env)->CallVoidMethod(env,
@ -2415,10 +2334,8 @@ imageio_empty_output_buffer (j_compress_ptr cinfo)
sb->hstreamBuffer,
0,
sb->bufferLength);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
dest->next_output_byte = sb->buf;
@ -2447,7 +2364,16 @@ imageio_term_destination (j_compress_ptr cinfo)
if (datacount != 0) {
jobject output = NULL;
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
/*
* Optimization to not delete the native copy of stream buffer,
* but just commit the content back to the Java array.
* In case where we don't have a copy, we rely on JVM to maintain
* the native reference of Java array.
*/
jboolean isCopy = sb->isCopy;
if (isCopy) {
unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT);
}
GET_IO_REF(output);
@ -2457,17 +2383,13 @@ imageio_term_destination (j_compress_ptr cinfo)
sb->hstreamBuffer,
0,
datacount);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
dest->next_output_byte = NULL;
dest->free_in_buffer = 0;
}
/*
@ -2668,7 +2590,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while writing. */
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((j_common_ptr) cinfo,
@ -2683,7 +2605,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
(*env)->ExceptionClear(env);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return;
}
@ -2703,7 +2625,15 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
}
jpeg_write_tables(cinfo); // Flushes the buffer for you
RELEASE_ARRAYS(env, data, NULL);
/*
* writeTables can be called independently, so
* we release the arrays when we return back.
* Also the table content in output_buffer is
* already flushed, so no need to commit the
* native copy of stream content back to the
* Java array.
*/
RELEASE_ARRAYS(env, data, NULL, JNI_ABORT, 0);
}
static void freeArray(UINT8** arr, jint size) {
@ -2766,7 +2696,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
UINT8** scale = NULL;
boolean success = TRUE;
/* verify the inputs */
if (data == NULL) {
@ -2891,7 +2820,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
if (setjmp(jerr->setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error
while writing. */
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT);
if (!(*env)->ExceptionCheck(env)) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) ((j_common_ptr) cinfo,
@ -2973,7 +2902,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
free(scanLinePtr);
JNU_ThrowByName(env,
"javax/imageio/IIOException",
"Array pin failed");
"Get array elements failed");
return data->abortFlag;
}
@ -3006,7 +2935,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
scanptr = (int *) cinfo->script_space;
scanData = (*env)->GetIntArrayElements(env, scanInfo, NULL);
if (scanData == NULL) {
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT);
freeArray(scale, numBands);
free(scanLinePtr);
return data->abortFlag;
@ -3034,16 +2963,13 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
if (haveMetadata) {
// Flush the buffer
imageio_flush_destination(cinfo);
// Call Java to write the metadata
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
// Call Java to write the metadata.
(*env)->CallVoidMethod(env,
this,
JPEGImageWriter_writeMetadataID);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
if ((*env)->ExceptionCheck(env)) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
}
targetLine = 0;
@ -3053,20 +2979,29 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
// for each line in destHeight
while ((data->abortFlag == JNI_FALSE)
&& (cinfo->next_scanline < cinfo->image_height)) {
// get the line from Java
RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
/*
* Get a line of pixel data from Java.
* In case where we have native copy of Java pixel array,
* we need to just use JNI_ABORT to exclude any copy operation
* and then get new copy for next scanline.
*
* If we have direct reference to Java array, we rely on
* JVM to maintain the reference appropriately.
*/
jboolean isCopy = pb->isCopy;
if (isCopy) {
unpinPixelBuffer(env, pb, JNI_ABORT);
}
(*env)->CallVoidMethod(env,
this,
JPEGImageWriter_grabPixelsID,
targetLine);
if ((*env)->ExceptionCheck(env)
|| !GET_ARRAYS(env, data,
(const JOCTET **)(&dest->next_output_byte))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
if ((*env)->ExceptionCheck(env) ||
(isCopy && (pinPixelBuffer(env, pb) == NOT_OK))) {
cinfo->err->error_exit((j_common_ptr) cinfo);
}
// subsample it into our buffer
in = data->pixelBuf.buf.bp;
out = scanLinePtr;
pixelLimit = in + ((pixelBufferSize > data->pixelBuf.byteBufferLength) ?
@ -3108,7 +3043,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
freeArray(scale, numBands);
free(scanLinePtr);
RELEASE_ARRAYS(env, data, NULL);
RELEASE_ARRAYS(env, data, NULL, 0, JNI_ABORT);
return data->abortFlag;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -52,6 +52,7 @@ import sun.security.util.HexDumpEncoder;
import sun.security.provider.certpath.X509CertificatePair;
import sun.security.util.Cache;
import sun.security.util.Debug;
import sun.security.util.SecurityProperties;
/**
* Core implementation of a LDAP Cert Store.
@ -96,6 +97,16 @@ final class LDAPCertStoreImpl {
private static final String PROP_DISABLE_APP_RESOURCE_FILES =
"sun.security.certpath.ldap.disable.app.resource.files";
/**
* Maximum size for a CRL downloaded through an LDAPCertStoreImpl
* in bytes. This can be controlled by the com.sun.security.crl.maxSize
* Security or System property. The System property, if set, overrides
* the Security property. The default size is 20MiB.
*/
private static final long MAX_CRL_DOWNLOAD_SIZE =
SecurityProperties.getOverridableLongProp(
"com.sun.security.crl.maxSize", 20971520, debug);
static {
String s = System.getProperty(PROP_LIFETIME);
if (s != null) {
@ -103,6 +114,13 @@ final class LDAPCertStoreImpl {
} else {
LIFETIME = DEFAULT_CACHE_LIFETIME;
}
// Add a debug message for the configured CRL download limit
if (debug != null) {
debug.println("Maximum downloadable CRL size: " +
MAX_CRL_DOWNLOAD_SIZE +
((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : ""));
}
}
/**
@ -672,12 +690,12 @@ final class LDAPCertStoreImpl {
return certs;
}
/*
/**
* Gets CRLs from an attribute id and location in the LDAP directory.
* Returns a Collection containing only the CRLs that match the
* specified X509CRLSelector.
*
* @param name the location holding the attribute
* @param request the LDAP request used for this CRL fetch operation
* @param id the attribute identifier
* @param sel a X509CRLSelector that the CRLs must match
* @return a Collection of CRLs found
@ -689,7 +707,26 @@ final class LDAPCertStoreImpl {
/* fetch the encoded crls from storage */
byte[][] encodedCRL;
try {
encodedCRL = request.getValues(id);
byte[][] tmpCrls = request.getValues(id);
if (MAX_CRL_DOWNLOAD_SIZE > -1) {
int totalSize = 0;
for (byte[] tCrl : tmpCrls) {
totalSize += tCrl.length;
}
if (totalSize <= MAX_CRL_DOWNLOAD_SIZE) {
encodedCRL = tmpCrls;
} else {
if (debug != null) {
debug.println("Received " + tmpCrls.length +
" CRL(s). Combined length of " + totalSize +
" exceeds configured maximum. Discarding.");
}
encodedCRL = new byte[0][];
}
} else {
// Download limits disabled
encodedCRL = tmpCrls;
}
} catch (NamingException namingEx) {
throw new CertStoreException(namingEx);
}

View File

@ -770,15 +770,24 @@ class ServerImpl {
requestLine, "Bad request line");
return;
}
// Read the request URI
String uriStr = requestLine.substring(start, space);
// Reject ambiguous URIs
if (uriStr.startsWith("//")) {
reject(Code.HTTP_BAD_REQUEST,
requestLine, "Bad request URI");
return;
}
URI uri;
try {
uri = new URI(uriStr);
} catch (URISyntaxException e3) {
reject(Code.HTTP_BAD_REQUEST,
requestLine, "URISyntaxException thrown");
requestLine, "Bad request URI");
return;
}
start = space+1;
String version = requestLine.substring(start);
Headers headers = req.headers();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -106,16 +106,22 @@ public final class FileServerHandler implements HttpHandler {
private void handleSupportedMethod(HttpExchange exchange, Path path, boolean writeBody)
throws IOException {
boolean requestURIEndsWithSlash = pathEndsWithSlash(exchange);
if (Files.isDirectory(path)) {
if (missingSlash(exchange)) {
if (!requestURIEndsWithSlash) {
handleMovedPermanently(exchange);
return;
}
if (indexFile(path) != null) {
serveFile(exchange, indexFile(path), writeBody);
Path indexFile = indexFile(path);
if (indexFile != null) {
serveFile(exchange, indexFile, writeBody);
} else {
listFiles(exchange, path, writeBody);
}
}
// Disallow non-directory paths ending with slash
else if (requestURIEndsWithSlash) {
handleNotFound(exchange);
} else {
serveFile(exchange, path, writeBody);
}
@ -126,10 +132,6 @@ public final class FileServerHandler implements HttpHandler {
exchange.sendResponseHeaders(301, RSPBODY_EMPTY);
}
private void handleForbidden(HttpExchange exchange) throws IOException {
exchange.sendResponseHeaders(403, RSPBODY_EMPTY);
}
private void handleNotFound(HttpExchange exchange) throws IOException {
String fileNotFound = ResourceBundleHelper.getMessage("html.not.found");
var bytes = (openHTML
@ -161,8 +163,8 @@ public final class FileServerHandler implements HttpHandler {
return query == null ? redirectPath : redirectPath + "?" + query;
}
private static boolean missingSlash(HttpExchange exchange) {
return !exchange.getRequestURI().getPath().endsWith("/");
private static boolean pathEndsWithSlash(HttpExchange exchange) {
return exchange.getRequestURI().getPath().endsWith("/");
}
private static String contextPath(HttpExchange exchange) {

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.util.HexFormat;
import jdk.test.lib.Asserts;
import jdk.test.lib.Utils;
import jdk.test.lib.security.DerUtils;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.KnownOIDs;
import sun.security.util.ObjectIdentifier;
/*
* @test
* @bug 8381049
* @library /test/lib
* @modules java.base/sun.security.util
* @summary Tests DerUtils navigation, assertions, and editing helpers
*/
public class DerUtilsTest {
public static void main(String[] args) throws Exception {
//0000:0015 [] SEQUENCE
//0002:0004 [0] OID 1.2.3
//0006:0003 [1] INTEGER 1
//0009:000C [2] OCTET STRING
// >>> into 10 octets
//000B:000A [2c] SEQUENCE
//000D:0005 [2c0] OID 2.5.4.3 (CommonName)
//0012:0003 [2c1] INTEGER 2
byte[] der = bytes("30 13 06022a03 020101 04 0a 30 08 0603550403 020102");
// Test innerDerValue
Asserts.assertEQ(DerUtils.innerDerValue(der, "0").getOID(),
ObjectIdentifier.of("1.2.3"));
Asserts.assertEQ(DerUtils.innerDerValue(der, "1").getInteger(), 1);
Asserts.assertEQ(DerUtils.innerDerValue(der, "2c0").getOID(),
ObjectIdentifier.of(KnownOIDs.CommonName));
Asserts.assertEQ(DerUtils.innerDerValue(der, "2c1").getInteger(), 2);
Asserts.assertTrue(DerUtils.innerDerValue(der, "3") == null);
// Test checks
DerUtils.checkAlg(der, "0", ObjectIdentifier.of("1.2.3"));
DerUtils.checkInt(der, "1", 1);
DerUtils.checkAlg(der, "2c0", ObjectIdentifier.of(KnownOIDs.CommonName));
DerUtils.checkInt(der, "2c1", 2);
DerUtils.shouldNotExist(der, "3");
// Test edit
der = DerUtils.edit(der, "0", oidValue("1.2.3.4"));
Asserts.assertEqualsByteArray(
bytes("30 14 06032a0304 020101 04 0a 30 08 0603550403 020102"), der);
der = DerUtils.edit(der, "2c1", intValue(8));
Asserts.assertEqualsByteArray(
bytes("30 14 06032a0304 020101 04 0a 30 08 0603550403 020108"), der);
der = DerUtils.edit(der, "1", null);
Asserts.assertEqualsByteArray(
bytes("30 11 06032a0304 04 0a 30 08 0603550403 020108"), der);
// Test insert
der = DerUtils.insert(der, "0", oidValue("1.2.5"));
Asserts.assertEqualsByteArray(
bytes("30 15 06022a05 06032a0304 04 0a 30 08 0603550403 020108"), der);
der = DerUtils.insert(der, "2c1", intValue(9));
Asserts.assertEqualsByteArray(
bytes("30 18 06022a05 06032a0304 04 0d 30 0b 0603550403 020109 020108"), der);
der = DerUtils.insert(der, "2c1", oidValue("1.2.6"));
Asserts.assertEqualsByteArray(
bytes("30 1c 06022a05 06032a0304 04 11 30 0f 0603550403 06022a06 020109 020108"), der);
// Cannot insert into a position ends with "c"
var derClone = der.clone(); // non-final reference cannot be used in lambda
Utils.runAndCheckException(() -> DerUtils.insert(derClone, "2c",
oidValue("1.2.7")), IOException.class);
}
static DerValue oidValue(String oid) throws IOException {
return DerValue.wrap(new DerOutputStream()
.putOID(ObjectIdentifier.of(oid)).toByteArray());
}
static DerValue intValue(int value) throws IOException {
return DerValue.wrap(new DerOutputStream()
.putInteger(value).toByteArray());
}
public static byte[] bytes(String hex) {
return HexFormat.of().parseHex(hex.replace(" ", ""));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,6 +24,7 @@
package jdk.test.lib.security;
import java.io.*;
import java.net.IDN;
import java.security.cert.*;
import java.security.cert.Extension;
import java.util.*;
@ -41,11 +42,15 @@ import sun.security.x509.AccessDescription;
import sun.security.x509.AlgorithmId;
import sun.security.x509.AuthorityInfoAccessExtension;
import sun.security.x509.AuthorityKeyIdentifierExtension;
import sun.security.x509.CRLDistributionPointsExtension;
import sun.security.x509.GeneralSubtrees;
import sun.security.x509.IPAddressName;
import sun.security.x509.NameConstraintsExtension;
import sun.security.x509.SubjectKeyIdentifierExtension;
import sun.security.x509.BasicConstraintsExtension;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.ExtendedKeyUsageExtension;
import sun.security.x509.DistributionPoint;
import sun.security.x509.DNSName;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
@ -58,13 +63,13 @@ import sun.security.x509.X500Name;
/**
* Helper class that builds and signs X.509 certificates.
*
* <p>
* A CertificateBuilder is created with a default constructor, and then
* uses additional public methods to set the public key, desired validity
* dates, serial number and extensions. It is expected that the caller will
* have generated the necessary key pairs prior to using a CertificateBuilder
* to generate certificates.
*
* <p>
* The following methods are mandatory before calling build():
* <UL>
* <LI>{@link #setSubjectName(java.lang.String)}
@ -78,12 +83,12 @@ import sun.security.x509.X500Name;
* Additionally, the caller can either provide a {@link List} of
* {@link Extension} objects, or use the helper classes to add specific
* extension types.
*
* <p>
* When all required and desired parameters are set, the
* {@link #build(java.security.cert.X509Certificate, java.security.PrivateKey,
* java.lang.String)} method can be used to create the {@link X509Certificate}
* object.
*
* <p>
* Multiple certificates may be cut from the same settings using subsequent
* calls to the build method. Settings may be cleared using the
* {@link #reset()} method.
@ -109,20 +114,23 @@ public class CertificateBuilder {
KEY_CERT_SIGN,
CRL_SIGN,
ENCIPHER_ONLY,
DECIPHER_ONLY;
DECIPHER_ONLY
}
/**
* Create a new CertificateBuilder instance. This method sets the subject name,
* public key, authority key id, and serial number.
* Create a new {@code CertificateBuilder} instance. This method sets the
* subject name, public key, authority key id, and serial number.
*
* @param subjectName entity associated with the public key
* @param publicKey the entity's public key
* @param caKey public key of certificate signer
* @param keyUsages list of key uses
* @return
* @throws CertificateException
* @throws IOException
* @return a {@code CertificateBuilder} configured with the provided
* parameters
*
* @throws CertificateException if an error occurs when obtaining the
* underlying {@link CertificateFactory}
* @throws IOException if any extension encoding errors occur
*/
public static CertificateBuilder newCertificateBuilder(String subjectName,
PublicKey publicKey, PublicKey caKey, KeyUsage... keyUsages)
@ -148,9 +156,13 @@ public class CertificateBuilder {
/**
* Create a Subject Alternative Name extension for the given DNS name
*
* @param critical Sets the extension to critical or non-critical
* @param dnsName DNS name to use in the extension
* @throws IOException
* @param dnsNames one or more DNS names to use in the extension
* @return a {@code SubjectAlternativeNameExtension} configured with
* the {@code dnsNames} as individual DNSName entries.
*
* @throws IOException if any encoding errors occur
*/
public static SubjectAlternativeNameExtension createDNSSubjectAltNameExt(
boolean critical, String... dnsNames) throws IOException {
@ -163,9 +175,13 @@ public class CertificateBuilder {
/**
* Create a Subject Alternative Name extension for the given IP address
*
* @param critical Sets the extension to critical or non-critical
* @param ipAddresses IP addresses to use in the extension
* @throws IOException
* @param ipAddresses one or more IP addresses to use in the extension
* @return a {@code SubjectAlternativeNameExtension} configured with
* the {@code ipAddresses} as individual IPAddressName entries.
*
* @throws IOException if any encoding errors occur
*/
public static SubjectAlternativeNameExtension createIPSubjectAltNameExt(
boolean critical, String... ipAddresses) throws IOException {
@ -212,6 +228,9 @@ public class CertificateBuilder {
* Set the subject name for the certificate.
*
* @param name The subject name in RFC 2253 format
*
* @throws IllegalArgumentException if any parsing errors on the
* {@code name} parameter occur.
*/
public CertificateBuilder setSubjectName(String name) {
try {
@ -238,6 +257,9 @@ public class CertificateBuilder {
* Set the public key for this certificate.
*
* @param pubKey The {@link PublicKey} to be used on this certificate.
*
* @throws NullPointerException if the {@code pubKey} parameter
* is {@code null}
*/
public CertificateBuilder setPublicKey(PublicKey pubKey) {
publicKey = Objects.requireNonNull(pubKey, "Caught null public key");
@ -249,6 +271,9 @@ public class CertificateBuilder {
*
* @param nbDate A {@link Date} object specifying the start of the
* certificate validity period.
*
* @throws NullPointerException if the {@code nbDate} parameter
* is {@code null}
*/
public CertificateBuilder setNotBefore(Date nbDate) {
Objects.requireNonNull(nbDate, "Caught null notBefore date");
@ -261,6 +286,9 @@ public class CertificateBuilder {
*
* @param naDate A {@link Date} object specifying the end of the
* certificate validity period.
*
* @throws NullPointerException if the {@code naDate} parameter
* is {@code null}
*/
public CertificateBuilder setNotAfter(Date naDate) {
Objects.requireNonNull(naDate, "Caught null notAfter date");
@ -275,6 +303,9 @@ public class CertificateBuilder {
* certificate validity period.
* @param naDate A {@link Date} object specifying the end of the
* certificate validity period.
*
* @throws NullPointerException if either the {@code nbDate} or
* {@code naDate} parameters are {@code null}
*/
public CertificateBuilder setValidity(Date nbDate, Date naDate) {
return setNotBefore(nbDate).setNotAfter(naDate);
@ -289,6 +320,8 @@ public class CertificateBuilder {
* Set the serial number on the certificate.
*
* @param serial A serial number in {@link BigInteger} form.
*
* @throws NullPointerException if {@code serial} is {@code null}
*/
public CertificateBuilder setSerialNumber(BigInteger serial) {
Objects.requireNonNull(serial, "Caught null serial number");
@ -313,6 +346,8 @@ public class CertificateBuilder {
*
* @param extList The {@link List} of extensions to be added to
* the certificate.
*
* @throws NullPointerException if {@code extList} is {@code null}
*/
public CertificateBuilder addExtensions(List<Extension> extList) {
Objects.requireNonNull(extList, "Caught null extension list");
@ -334,7 +369,8 @@ public class CertificateBuilder {
if (!dnsNames.isEmpty()) {
GeneralNames gNames = new GeneralNames();
for (String name : dnsNames) {
gNames.add(new GeneralName(new DNSName(name)));
gNames.add(new GeneralName(new DNSName(new DerValue(
DerValue.tag_IA5String, IDN.toASCII(name)))));
}
addExtension(new SubjectAlternativeNameExtension(false,
gNames));
@ -347,6 +383,7 @@ public class CertificateBuilder {
*
* @param ipAddresses A {@code List} of names to add as IPAddress
* types
*
* @throws IOException if an encoding error occurs.
*/
public CertificateBuilder addSubjectAltNameIPExt(List<String> ipAddresses)
@ -362,13 +399,41 @@ public class CertificateBuilder {
return this;
}
/**
* Helper method to add one or more distribution points to the CRL
* Distribution Points extension. This form of the method only supports
* URI name types, but can be extended in the future to support other types.
*
* @param uriNames a list of URIs in String form
* @return the {@code CertificateBuilder} configured to add this
* CRL Distribution Points extension.
*
* @throws IOException if any of the URIs in {@code uriNames} are
* malformed
*/
public CertificateBuilder addCrlDistributionPointsExt(List<String> uriNames)
throws IOException {
if (uriNames != null && !uriNames.isEmpty()) {
GeneralNames gNames = new GeneralNames();
for (String name : uriNames) {
gNames.add(new GeneralName(new URIName(name)));
}
addExtension(new CRLDistributionPointsExtension(List.of(
new DistributionPoint(gNames, null, null))));
}
return this;
}
/**
* Helper method to add one or more OCSP URIs to the Authority Info Access
* certificate extension. Location strings can be in two forms:
* 1) Just a URI by itself: This will be treated as using the OCSP
* <ol>
* <li>Just a URI by itself: This will be treated as using the OCSP
* access description (legacy behavior).
* 2) An access description name (case-insensitive) followed by a
* pipe (|) and the URI (e.g. OCSP|http://ocsp.company.com/revcheck).
* <li>An access description name (case-insensitive) followed by a
* pipe (|) and the URI (e.g.
* {@code OCSP|http://ocsp.company.com/revcheck}).
* </ol>
* Current description names are OCSP and CAISSUER. Others may be
* added later.
*
@ -389,16 +454,12 @@ public class CertificateBuilder {
adObj = AccessDescription.Ad_OCSP_Id;
uriLoc = tokens[0];
} else {
switch (tokens[0].toUpperCase()) {
case "OCSP":
adObj = AccessDescription.Ad_OCSP_Id;
break;
case "CAISSUER":
adObj = AccessDescription.Ad_CAISSUERS_Id;
break;
default:
throw new IOException("Unknown AD: " + tokens[0]);
}
adObj = switch (tokens[0].toUpperCase()) {
case "OCSP" -> AccessDescription.Ad_OCSP_Id;
case "CAISSUER" -> AccessDescription.Ad_CAISSUERS_Id;
default -> throw new IOException("Unknown AD: " +
tokens[0]);
};
uriLoc = tokens[1];
}
acDescList.add(new AccessDescription(adObj,
@ -437,6 +498,17 @@ public class CertificateBuilder {
maxPathLen));
}
/**
* Set the Name Constraints Extension for a certificate.
*
* @param permitted permitted names
* @param excluded excluded names
*/
public CertificateBuilder addNameConstraintsExt(
GeneralSubtrees permitted, GeneralSubtrees excluded) {
return addExtension(new NameConstraintsExtension(permitted, excluded));
}
/**
* Add the Authority Key Identifier extension.
*
@ -554,7 +626,7 @@ public class CertificateBuilder {
}
/**
* Encode the contents of the outer-most ASN.1 SEQUENCE:
* Encode the contents of the outermost ASN.1 SEQUENCE:
*
* <PRE>
* Certificate ::= SEQUENCE {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,6 +24,7 @@ package jdk.test.lib.security;
import jdk.test.lib.Asserts;
import sun.security.util.DerInputStream;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.KnownOIDs;
import sun.security.util.ObjectIdentifier;
@ -124,4 +125,84 @@ public class DerUtils {
throws Exception {
Asserts.assertTrue(innerDerValue(der, location) == null);
}
/// Replaces a `DerValue` (deep) inside with another one.
///
/// @param data the `DerValue`
/// @param target the location to edit. Cannot be empty.
/// @param replacement replace the value at `target` with this. Can be a `DerValue`
/// or a `DerOutputStream`. Remove if `null`.
/// @return the new value
public static byte[] edit(byte[] data, String target, Object replacement)
throws IOException {
if (target.isEmpty()) throw new IOException("Must be a sub-location");
return modify0(data, "", target, replacement, false).toByteArray();
}
/// Inserts a `DerValue` (deep) into another one.
///
/// @param data the `DerValue`
/// @param target the location to insert at. Cannot be empty. The new value
/// is inserted before the existing value at `target`, and following
/// values are shifted. After insertion, the value at `target`
/// is the inserted value. A target ending exactly with `c` is not
/// a valid insertion position because the content of an OCTET
/// STRING must be only one `DerValue`.
/// @param addition the value to insert. Can be a `DerValue` or a
/// `DerOutputStream`
/// @return the new value
public static byte[] insert(byte[] data, String target, Object addition)
throws IOException {
if (target.isEmpty()) throw new IOException("Must be a sub-location");
return modify0(data, "", target, addition, true).toByteArray();
}
/// Implementation of [#edit] and [#insert], recursively.
///
/// @param data the `DerValue`
/// @param now the current location
/// @param target the location to edit or insert at
/// @param replacement the replacement or inserted value
/// @param insert true to insert before the target, false to replace it
/// @return the new value at this location
private static DerOutputStream modify0(byte[] data, String now, String target,
Object replacement, boolean insert) throws IOException {
var out = new DerOutputStream();
var parent = DerUtils.innerDerValue(data, now);
if (target.equals(now + "c")) {
if (insert) {
throw new IOException("Action cannot be performed at position " + target);
}
if (replacement instanceof DerValue v) {
out.putDerValue(v);
} else if (replacement instanceof DerOutputStream s) {
out.write(s);
}
} else if (target.startsWith(now + "c")) { // not there yet, go inside
return out.write(parent.tag, modify0(data, now + "c", target, replacement, insert));
} else {
for (int i = 0; ; i++) {
// We only support locations of one digit now
if (i > 9) throw new IllegalStateException("Too big " + i);
String pos = now + i;
var sub = DerUtils.innerDerValue(data, pos); // current value
if (sub == null) break; // at the end
if (target.equals(pos)) { // the one we want to change
if (replacement instanceof DerValue v) {
out.putDerValue(v);
} else if (replacement instanceof DerOutputStream s) {
out.write(s);
}
if (insert) {
out.putDerValue(sub);
}
} else if (target.startsWith(pos)) { // not there yet, go inside
out.write(modify0(data, pos, target, replacement, insert));
} else { // the untouched values
out.putDerValue(sub);
}
}
}
return new DerOutputStream().write(parent.tag, out);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -148,7 +148,6 @@ public class TsaHandler implements HttpHandler {
*/
protected TsaParam getParam(URI uri) {
String query = uri.getQuery();
TsaParam param = TsaParam.newInstance();
if (query != null) {
for (String bufParam : query.split("&")) {
@ -186,6 +185,12 @@ public class TsaHandler implements HttpHandler {
} else if ("certReq".equalsIgnoreCase(pair[0])) {
param.certReq(Boolean.valueOf(pair[1]));
System.out.println("certReq: " + param.certReq());
} else if ("noSignedAttrs".equalsIgnoreCase(pair[0])) {
param.noSignedAttrs(Boolean.valueOf(pair[1]));
System.out.println("noSignedAttrs: " + param.noSignedAttrs());
} else if ("notTimestampOID".equalsIgnoreCase(pair[0])) {
param.notTimestampOID(Boolean.valueOf(pair[1]));
System.out.println("notTimestampOID: " + param.notTimestampOID());
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -71,6 +71,12 @@ public class TsaParam {
// Indicate if request TSA server certificate
private Boolean certReq;
// Do not create signedAttrs
private Boolean noSignedAttrs;
// Do not use TIMESTAMP_TOKEN_INFO_OID
private Boolean notTimestampOID;
public static TsaParam newInstance() {
return new TsaParam();
}
@ -177,4 +183,22 @@ public class TsaParam {
this.certReq = certReq;
return this;
}
public Boolean noSignedAttrs() {
return noSignedAttrs;
}
public TsaParam noSignedAttrs(Boolean noSignedAttrs) {
this.noSignedAttrs = noSignedAttrs;
return this;
}
public Boolean notTimestampOID() {
return notTimestampOID;
}
public TsaParam notTimestampOID(Boolean notTimestampOID) {
this.notTimestampOID = notTimestampOID;
return this;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,8 +23,8 @@
package jdk.test.lib.security.timestamp;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Date;
@ -33,6 +33,8 @@ import java.util.Objects;
import jdk.test.lib.hexdump.HexPrinter;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.security.pkcs.SignerInfo;
import sun.security.util.*;
import sun.security.x509.AlgorithmId;
@ -202,8 +204,11 @@ public class TsaSigner {
DerOutputStream eContentOut = new DerOutputStream();
eContentOut.putOctetString(tstInfoSeqData);
ObjectIdentifier infoOid = respParam.notTimestampOID() == Boolean.TRUE
? ContentInfo.DATA_OID
: ContentInfo.TIMESTAMP_TOKEN_INFO_OID;
ContentInfo eContentInfo = new ContentInfo(
ObjectIdentifier.of(KnownOIDs.TimeStampTokenInfo),
infoOid,
new DerValue(eContentOut.toByteArray()));
String defaultSigAlgo = SignatureUtil.getDefaultSigAlgForKey(
@ -213,16 +218,36 @@ public class TsaSigner {
System.out.println(
"Signature algorithm: " + signature.getAlgorithm());
signature.initSign(signerEntry.privateKey);
signature.update(tstInfoSeqData);
AlgorithmId digestAlg = SignatureUtil.getDigestAlgInPkcs7SignerInfo(
signature, sigAlgo, signerEntry.privateKey,
signerEntry.cert.getPublicKey(), false);
PKCS9Attributes authAttrs = null;
if (respParam.noSignedAttrs() == Boolean.TRUE) {
signature.update(tstInfoSeqData);
} else {
authAttrs = new PKCS9Attributes(new PKCS9Attribute[]{
new PKCS9Attribute(PKCS9Attribute.CONTENT_TYPE_OID,
infoOid),
new PKCS9Attribute(PKCS9Attribute.SIGNING_TIME_OID,
new Date()),
new PKCS9Attribute(PKCS9Attribute.MESSAGE_DIGEST_OID,
MessageDigest.getInstance(digestAlg.getName())
.digest(tstInfoSeqData))
});
signature.update(authAttrs.getDerEncoding());
}
SignerInfo signerInfo = new SignerInfo(
new X500Name(issuerName),
signerEntry.cert.getSerialNumber(),
SignatureUtil.getDigestAlgInPkcs7SignerInfo(
signature, sigAlgo, signerEntry.privateKey,
signerEntry.cert.getPublicKey(), false),
digestAlg,
authAttrs,
AlgorithmId.get(sigAlgo),
signature.sign());
signature.sign(),
null);
X509Certificate[] signerCertChain = interceptor.getSignerCertChain(
signerEntry.certChain, requestParam.certReq());

View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.javax.imageio.plugins.jpeg;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.stream.ImageInputStream;
/**
* Measure time taken to read large jpeg image
* make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWithProgressBench"
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(3)
@State(Scope.Benchmark)
public class LargeJpegReadWithProgressBench {
private static final File pwd = new File(".");
private static ImageReader reader;
@Setup
public void setup() throws IOException {
BufferedImage src = createSource();
ImageInputStream iis = prepareInput(src);
reader = null;
Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName("jpeg");
if (it.hasNext()) {
reader = (ImageReader)it.next();
} else {
throw new RuntimeException("Could not find JPEG reader");
}
reader.setInput(iis);
ImageReadProgressListener listener = new ImageReadProgressListener();
reader.addIIOReadProgressListener(listener);
}
@Benchmark
public void readLargeJpegImage(Blackhole bh) throws IOException {
reader.read(0);
}
private static BufferedImage createSource() {
int width = 2000;
int height = 2000;
int squareSize = 20;
Color red = Color.RED;
Color green = Color.GREEN;
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (((x / squareSize) + (y / squareSize)) % 2 == 0) {
image.setRGB(x, y, red.getRGB());
} else {
image.setRGB(x, y, green.getRGB());
}
}
}
return image;
}
private static ImageInputStream prepareInput(BufferedImage src)
throws IOException {
File f = File.createTempFile("src_", ".jpeg", pwd);
if (ImageIO.write(src, "jpeg", f)) {
ImageInputStream iis = ImageIO.createImageInputStream(f);
f.deleteOnExit();
return iis;
} else {
throw new RuntimeException("Unable to write jpeg image");
}
}
}
class ImageReadProgressListener implements IIOReadProgressListener {
// This class is a no-op, it is added just to have a progress listener
@Override
public void sequenceStarted(ImageReader source, int minIndex) {
}
@Override
public void sequenceComplete(ImageReader source) {
}
@Override
public void imageStarted(ImageReader source, int imageIndex) {
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
}
@Override
public void imageComplete(ImageReader source) {
}
@Override
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
}
@Override
public void thumbnailProgress(ImageReader source, float percentageDone) {
}
@Override
public void thumbnailComplete(ImageReader source) {
}
@Override
public void readAborted(ImageReader source) {
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.javax.imageio.plugins.jpeg;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
/**
* Measure time taken to read large jpeg image
* make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWriteBench"
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(3)
@State(Scope.Benchmark)
public class LargeJpegReadWriteBench {
private static final File pwd = new File(".");
private static ImageReader reader;
private static ImageWriter writer;
private static BufferedImage src;
@Setup
public void setup() throws IOException {
src = createSource();
ImageInputStream iis = prepareInput(src);
reader = null;
Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByFormatName("jpeg");
if (readerIterator.hasNext()) {
reader = readerIterator.next();
} else {
throw new RuntimeException("Could not find JPEG reader");
}
reader.setInput(iis);
ImageOutputStream ios = prepareOutput(src);
writer = null;
Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByFormatName("jpeg");
if (writerIterator.hasNext()) {
writer = writerIterator.next();
} else {
throw new RuntimeException("Could not find JPEG writer");
}
writer.setOutput(ios);
}
@Benchmark
public void readLargeJpegImage(Blackhole bh) throws IOException {
reader.read(0);
}
@Benchmark
public void writeLargeJpegImage(Blackhole bh) throws IOException {
writer.write(src);
}
private static BufferedImage createSource() {
int width = 2000;
int height = 2000;
int squareSize = 20;
Color red = Color.RED;
Color green = Color.GREEN;
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (((x / squareSize) + (y / squareSize)) % 2 == 0) {
image.setRGB(x, y, red.getRGB());
} else {
image.setRGB(x, y, green.getRGB());
}
}
}
return image;
}
private static ImageInputStream prepareInput(BufferedImage src)
throws IOException {
File f = File.createTempFile("src_", ".jpeg", pwd);
if (ImageIO.write(src, "jpeg", f)) {
ImageInputStream iis = ImageIO.createImageInputStream(f);
f.deleteOnExit();
return iis;
} else {
throw new RuntimeException("Unable to write jpeg image");
}
}
private static ImageOutputStream prepareOutput(BufferedImage src) throws IOException {
File f = File.createTempFile("dest_", ".jpeg", pwd);
ImageOutputStream ios = ImageIO.createImageOutputStream(f);
f.deleteOnExit();
return ios;
}
}