Skip to content

Commit 45275e6

Browse files
committed
Merge branch '2.2.x' into 2.3.x
Closes gh-23264
2 parents 5294c34 + a20fdf8 commit 45275e6

File tree

6 files changed

+147
-61
lines changed

6 files changed

+147
-61
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
.recommenders
1919
.settings
2020
.springBeans
21+
.vscode
2122
/code
2223
MANIFEST.MF
2324
_site/

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntry.java

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,20 +32,21 @@
3232
*/
3333
class JarEntry extends java.util.jar.JarEntry implements FileHeader {
3434

35+
private final int index;
36+
3537
private final AsciiBytes name;
3638

3739
private final AsciiBytes headerName;
3840

39-
private Certificate[] certificates;
40-
41-
private CodeSigner[] codeSigners;
42-
4341
private final JarFile jarFile;
4442

4543
private long localHeaderOffset;
4644

47-
JarEntry(JarFile jarFile, CentralDirectoryFileHeader header, AsciiBytes nameAlias) {
45+
private volatile JarEntryCertification certification;
46+
47+
JarEntry(JarFile jarFile, int index, CentralDirectoryFileHeader header, AsciiBytes nameAlias) {
4848
super((nameAlias != null) ? nameAlias.toString() : header.getName().toString());
49+
this.index = index;
4950
this.name = (nameAlias != null) ? nameAlias : header.getName();
5051
this.headerName = header.getName();
5152
this.jarFile = jarFile;
@@ -61,6 +62,10 @@ class JarEntry extends java.util.jar.JarEntry implements FileHeader {
6162
}
6263
}
6364

65+
int getIndex() {
66+
return this.index;
67+
}
68+
6469
AsciiBytes getAsciiBytesName() {
6570
return this.name;
6671
}
@@ -87,23 +92,24 @@ public Attributes getAttributes() throws IOException {
8792

8893
@Override
8994
public Certificate[] getCertificates() {
90-
if (this.jarFile.isSigned() && this.certificates == null) {
91-
this.jarFile.setupEntryCertificates(this);
92-
}
93-
return this.certificates;
95+
return getCertification().getCertificates();
9496
}
9597

9698
@Override
9799
public CodeSigner[] getCodeSigners() {
98-
if (this.jarFile.isSigned() && this.codeSigners == null) {
99-
this.jarFile.setupEntryCertificates(this);
100-
}
101-
return this.codeSigners;
100+
return getCertification().getCodeSigners();
102101
}
103102

104-
void setCertificates(java.util.jar.JarEntry entry) {
105-
this.certificates = entry.getCertificates();
106-
this.codeSigners = entry.getCodeSigners();
103+
private JarEntryCertification getCertification() {
104+
if (!this.jarFile.isSigned()) {
105+
return JarEntryCertification.NONE;
106+
}
107+
JarEntryCertification certification = this.certification;
108+
if (certification == null) {
109+
certification = this.jarFile.getCertification(this);
110+
this.certification = certification;
111+
}
112+
return certification;
107113
}
108114

109115
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader.jar;
18+
19+
import java.security.CodeSigner;
20+
import java.security.cert.Certificate;
21+
22+
/**
23+
* {@link Certificate} and {@link CodeSigner} details for a {@link JarEntry} from a signed
24+
* {@link JarFile}.
25+
*
26+
* @author Phillip Webb
27+
*/
28+
class JarEntryCertification {
29+
30+
static final JarEntryCertification NONE = new JarEntryCertification(null, null);
31+
32+
private final Certificate[] certificates;
33+
34+
private final CodeSigner[] codeSigners;
35+
36+
JarEntryCertification(Certificate[] certificates, CodeSigner[] codeSigners) {
37+
this.certificates = certificates;
38+
this.codeSigners = codeSigners;
39+
}
40+
41+
Certificate[] getCertificates() {
42+
return (this.certificates != null) ? this.certificates.clone() : null;
43+
}
44+
45+
CodeSigner[] getCodeSigners() {
46+
return (this.codeSigners != null) ? this.codeSigners.clone() : null;
47+
}
48+
49+
static JarEntryCertification from(java.util.jar.JarEntry certifiedEntry) {
50+
Certificate[] certificates = (certifiedEntry != null) ? certifiedEntry.getCertificates() : null;
51+
CodeSigner[] codeSigners = (certifiedEntry != null) ? certifiedEntry.getCodeSigners() : null;
52+
if (certificates == null && codeSigners == null) {
53+
return NONE;
54+
}
55+
return new JarEntryCertification(certificates, codeSigners);
56+
}
57+
58+
}

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.util.Spliterator;
3232
import java.util.Spliterators;
3333
import java.util.function.Supplier;
34-
import java.util.jar.JarInputStream;
3534
import java.util.jar.Manifest;
3635
import java.util.stream.Stream;
3736
import java.util.stream.StreamSupport;
@@ -385,33 +384,15 @@ boolean isSigned() {
385384
return this.signed;
386385
}
387386

388-
void setupEntryCertificates(JarEntry entry) {
389-
// Fallback to JarInputStream to obtain certificates, not fast but hopefully not
390-
// happening that often.
387+
JarEntryCertification getCertification(JarEntry entry) {
391388
try {
392-
try (JarInputStream inputStream = new JarInputStream(getData().getInputStream())) {
393-
java.util.jar.JarEntry certEntry = inputStream.getNextJarEntry();
394-
while (certEntry != null) {
395-
inputStream.closeEntry();
396-
if (entry.getName().equals(certEntry.getName())) {
397-
setCertificates(entry, certEntry);
398-
}
399-
setCertificates(getJarEntry(certEntry.getName()), certEntry);
400-
certEntry = inputStream.getNextJarEntry();
401-
}
402-
}
389+
return this.entries.getCertification(entry);
403390
}
404391
catch (IOException ex) {
405392
throw new IllegalStateException(ex);
406393
}
407394
}
408395

409-
private void setCertificates(JarEntry entry, java.util.jar.JarEntry certEntry) {
410-
if (entry != null) {
411-
entry.setCertificates(certEntry);
412-
}
413-
}
414-
415396
public void clearCache() {
416397
this.entries.clearCache();
417398
}

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.NoSuchElementException;
2727
import java.util.jar.Attributes;
2828
import java.util.jar.Attributes.Name;
29+
import java.util.jar.JarInputStream;
2930
import java.util.jar.Manifest;
3031
import java.util.zip.ZipEntry;
3132

@@ -94,14 +95,13 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
9495

9596
private Boolean multiReleaseJar;
9697

98+
private JarEntryCertification[] certifications;
99+
97100
private final Map<Integer, FileHeader> entriesCache = Collections
98101
.synchronizedMap(new LinkedHashMap<Integer, FileHeader>(16, 0.75f, true) {
99102

100103
@Override
101104
protected boolean removeEldestEntry(Map.Entry<Integer, FileHeader> eldest) {
102-
if (JarFileEntries.this.jarFile.isSigned()) {
103-
return false;
104-
}
105105
return size() >= ENTRY_CACHE_SIZE;
106106
}
107107

@@ -320,7 +320,7 @@ private <T extends FileHeader> T getEntry(int index, Class<T> type, boolean cach
320320
FileHeader entry = (cached != null) ? cached : CentralDirectoryFileHeader
321321
.fromRandomAccessData(this.centralDirectoryData, this.centralDirectoryOffsets[index], this.filter);
322322
if (CentralDirectoryFileHeader.class.equals(entry.getClass()) && type.equals(JarEntry.class)) {
323-
entry = new JarEntry(this.jarFile, (CentralDirectoryFileHeader) entry, nameAlias);
323+
entry = new JarEntry(this.jarFile, index, (CentralDirectoryFileHeader) entry, nameAlias);
324324
}
325325
if (cacheEntry && cached != entry) {
326326
this.entriesCache.put(index, entry);
@@ -351,6 +351,41 @@ private AsciiBytes applyFilter(AsciiBytes name) {
351351
return (this.filter != null) ? this.filter.apply(name) : name;
352352
}
353353

354+
JarEntryCertification getCertification(JarEntry entry) throws IOException {
355+
JarEntryCertification[] certifications = this.certifications;
356+
if (certifications == null) {
357+
certifications = new JarEntryCertification[this.size];
358+
// We fallback to use JarInputStream to obtain the certs. This isn't that
359+
// fast, but hopefully doesn't happen too often.
360+
try (JarInputStream certifiedJarStream = new JarInputStream(this.jarFile.getData().getInputStream())) {
361+
java.util.jar.JarEntry certifiedEntry = null;
362+
while ((certifiedEntry = certifiedJarStream.getNextJarEntry()) != null) {
363+
int index = getEntryIndex(certifiedEntry.getName());
364+
if (index != -1) {
365+
certifications[index] = JarEntryCertification.from(certifiedEntry);
366+
}
367+
certifiedJarStream.closeEntry();
368+
}
369+
}
370+
this.certifications = certifications;
371+
}
372+
JarEntryCertification certification = certifications[entry.getIndex()];
373+
return (certification != null) ? certification : JarEntryCertification.NONE;
374+
}
375+
376+
private int getEntryIndex(CharSequence name) {
377+
int hashCode = AsciiBytes.hashCode(name);
378+
int index = getFirstIndex(hashCode);
379+
while (index >= 0 && index < this.size && this.hashCodes[index] == hashCode) {
380+
CentralDirectoryFileHeader candidate = getEntry(index, CentralDirectoryFileHeader.class, false, null);
381+
if (candidate.hasName(name, NO_SUFFIX)) {
382+
return index;
383+
}
384+
index++;
385+
}
386+
return -1;
387+
}
388+
354389
/**
355390
* Iterator for contained entries.
356391
*/

spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.springframework.boot.loader.data.RandomAccessDataFile;
5656
import org.springframework.test.util.ReflectionTestUtils;
5757
import org.springframework.util.FileCopyUtils;
58+
import org.springframework.util.StopWatch;
5859
import org.springframework.util.StreamUtils;
5960

6061
import static org.assertj.core.api.Assertions.assertThat;
@@ -427,29 +428,33 @@ void sensibleToString() throws Exception {
427428

428429
@Test
429430
void verifySignedJar() throws Exception {
430-
String classpath = System.getProperty("java.class.path");
431-
String[] entries = classpath.split(System.getProperty("path.separator"));
432-
String signedJarFile = null;
433-
for (String entry : entries) {
434-
if (entry.contains("bcprov")) {
435-
signedJarFile = entry;
431+
File signedJarFile = getSignedJarFile();
432+
assertThat(signedJarFile).exists();
433+
try (java.util.jar.JarFile expected = new java.util.jar.JarFile(signedJarFile)) {
434+
try (JarFile actual = new JarFile(signedJarFile)) {
435+
StopWatch stopWatch = new StopWatch();
436+
Enumeration<JarEntry> actualEntries = actual.entries();
437+
while (actualEntries.hasMoreElements()) {
438+
JarEntry actualEntry = actualEntries.nextElement();
439+
java.util.jar.JarEntry expectedEntry = expected.getJarEntry(actualEntry.getName());
440+
assertThat(actualEntry.getCertificates()).as(actualEntry.getName())
441+
.isEqualTo(expectedEntry.getCertificates());
442+
assertThat(actualEntry.getCodeSigners()).as(actualEntry.getName())
443+
.isEqualTo(expectedEntry.getCodeSigners());
444+
}
445+
assertThat(stopWatch.getTotalTimeSeconds()).isLessThan(3.0);
436446
}
437447
}
438-
assertThat(signedJarFile).isNotNull();
439-
java.util.jar.JarFile jarFile = new JarFile(new File(signedJarFile));
440-
jarFile.getManifest();
441-
Enumeration<JarEntry> jarEntries = jarFile.entries();
442-
while (jarEntries.hasMoreElements()) {
443-
JarEntry jarEntry = jarEntries.nextElement();
444-
InputStream inputStream = jarFile.getInputStream(jarEntry);
445-
inputStream.skip(Long.MAX_VALUE);
446-
inputStream.close();
447-
if (!jarEntry.getName().startsWith("META-INF") && !jarEntry.isDirectory()
448-
&& !jarEntry.getName().endsWith("TigerDigest.class")) {
449-
assertThat(jarEntry.getCertificates()).isNotNull();
448+
}
449+
450+
private File getSignedJarFile() {
451+
String[] entries = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
452+
for (String entry : entries) {
453+
if (entry.contains("bcprov")) {
454+
return new File(entry);
450455
}
451456
}
452-
jarFile.close();
457+
return null;
453458
}
454459

455460
@Test

0 commit comments

Comments
 (0)