@@ -30,6 +30,8 @@ public class JarRemoteClassSource implements ClassSource, AutoCloseable {
3030 private static final Logger logger = LoggerFactory .getLogger (JarRemoteClassSource .class );
3131 private static final int DEFAULT_CONNECT_TIMEOUT_MS = 10000 ;
3232 private static final int DEFAULT_READ_TIMEOUT_MS = 30000 ;
33+ private static final long MAX_JAR_SIZE = 100 * 1024 * 1024 ; // 100MB default
34+ private static final long MAX_CLASS_SIZE = 10 * 1024 * 1024 ; // 10MB default
3335
3436 private final String jarUrl ;
3537 private final AuthConfig authConfig ;
@@ -113,12 +115,29 @@ private synchronized void ensureJarReady() throws IOException {
113115 if (responseCode != HttpURLConnection .HTTP_OK ) {
114116 throw new IOException ("HTTP error code: " + responseCode + " for URL: " + url );
115117 }
118+
119+ // Check JAR size before downloading
120+ long contentLength = httpConnection .getContentLengthLong ();
121+ if (contentLength > MAX_JAR_SIZE ) {
122+ throw new IOException (
123+ "JAR file too large: " + contentLength + " bytes (max " + MAX_JAR_SIZE + ")"
124+ );
125+ }
116126 }
117127
118128 try (InputStream in = connection .getInputStream ()) {
119129 Files .copy (in , tempJarPath , StandardCopyOption .REPLACE_EXISTING );
120130 }
121131
132+ // Validate downloaded file size
133+ long actualSize = Files .size (tempJarPath );
134+ if (actualSize > MAX_JAR_SIZE ) {
135+ Files .deleteIfExists (tempJarPath );
136+ throw new IOException (
137+ "Downloaded JAR exceeds size limit: " + actualSize + " bytes"
138+ );
139+ }
140+
122141 return null ;
123142 });
124143
@@ -137,19 +156,64 @@ public byte[] loadClassData(String className) throws IOException {
137156 throw new IOException ("Class not found in JAR: " + className );
138157 }
139158
140- try (InputStream in = jarFile .getInputStream (entry );
141- ByteArrayOutputStream out = new ByteArrayOutputStream ()) {
159+ long size = entry .getSize ();
160+ if (size < 0 ) {
161+ // Unknown size - read with limit
162+ return readWithSizeLimit (jarFile .getInputStream (entry ), MAX_CLASS_SIZE );
163+ }
142164
165+ if (size > MAX_CLASS_SIZE ) {
166+ throw new IOException (
167+ "Class file too large: " + size + " bytes (max " + MAX_CLASS_SIZE + ")"
168+ );
169+ }
170+
171+ if (size > Integer .MAX_VALUE ) {
172+ throw new IOException ("Class file exceeds Java array limit: " + size );
173+ }
174+
175+ try (InputStream in = jarFile .getInputStream (entry )) {
176+ byte [] data = new byte [(int )size ];
177+ int totalRead = 0 ;
178+
179+ while (totalRead < size ) {
180+ int n = in .read (data , totalRead , (int )size - totalRead );
181+ if (n == -1 ) break ;
182+ totalRead += n ;
183+ }
184+
185+ return data ;
186+ }
187+ }
188+
189+ private byte [] readWithSizeLimit (InputStream in , long maxSize ) throws IOException {
190+ try (ByteArrayOutputStream out = new ByteArrayOutputStream ()) {
143191 byte [] buffer = new byte [DEFAULT_BUFFER_SIZE ];
192+ long totalRead = 0 ;
144193 int bytesRead ;
194+
145195 while ((bytesRead = in .read (buffer )) != -1 ) {
196+ totalRead += bytesRead ;
197+ if (totalRead > maxSize ) {
198+ throw new IOException ("Entry exceeds maximum size: " + totalRead );
199+ }
146200 out .write (buffer , 0 , bytesRead );
147201 }
148202
149203 return out .toByteArray ();
150204 }
151205 }
152206
207+ /**
208+ * Checks if this source can load the specified class.
209+ *
210+ * <p><b>Performance Note:</b> The first call to this method (or loadClassData())
211+ * will download the entire JAR file. Subsequent calls use the cached JAR and are fast.
212+ * The download is synchronized and happens only once per instance.</p>
213+ *
214+ * @param className The fully qualified class name to check
215+ * @return true if the class exists in the JAR, false otherwise
216+ */
153217 @ Override
154218 public boolean canLoad (String className ) {
155219 try {
0 commit comments