Skip to content

Commit a20fdf8

Browse files
committed
Merge branch '2.1.x' into 2.2.x
Closes gh-23263
2 parents 326a56d + 895ff9c commit a20fdf8

File tree

6 files changed

+149
-63
lines changed

6 files changed

+149
-63
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
.settings
2020
.springBeans
2121
/build
22+
.vscode
2223
/code
2324
MANIFEST.MF
2425
_site/
@@ -38,4 +39,4 @@ transaction-logs
3839
secrets.yml
3940
.gradletasknamecache
4041
.sts4-cache
41-
.mvn/.gradle-enterprise/gradle-enterprise-workspace-id
42+
.mvn/.gradle-enterprise/gradle-enterprise-workspace-id

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
@@ -29,7 +29,6 @@
2929
import java.util.Enumeration;
3030
import java.util.Iterator;
3131
import java.util.function.Supplier;
32-
import java.util.jar.JarInputStream;
3332
import java.util.jar.Manifest;
3433
import java.util.zip.ZipEntry;
3534

@@ -353,33 +352,15 @@ boolean isSigned() {
353352
return this.signed;
354353
}
355354

356-
void setupEntryCertificates(JarEntry entry) {
357-
// Fallback to JarInputStream to obtain certificates, not fast but hopefully not
358-
// happening that often.
355+
JarEntryCertification getCertification(JarEntry entry) {
359356
try {
360-
try (JarInputStream inputStream = new JarInputStream(getData().getInputStream())) {
361-
java.util.jar.JarEntry certEntry = inputStream.getNextJarEntry();
362-
while (certEntry != null) {
363-
inputStream.closeEntry();
364-
if (entry.getName().equals(certEntry.getName())) {
365-
setCertificates(entry, certEntry);
366-
}
367-
setCertificates(getJarEntry(certEntry.getName()), certEntry);
368-
certEntry = inputStream.getNextJarEntry();
369-
}
370-
}
357+
return this.entries.getCertification(entry);
371358
}
372359
catch (IOException ex) {
373360
throw new IllegalStateException(ex);
374361
}
375362
}
376363

377-
private void setCertificates(JarEntry entry, java.util.jar.JarEntry certEntry) {
378-
if (entry != null) {
379-
entry.setCertificates(certEntry);
380-
}
381-
}
382-
383364
public void clearCache() {
384365
this.entries.clearCache();
385366
}

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

Lines changed: 40 additions & 5 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.
@@ -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

@@ -91,14 +92,13 @@ class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
9192

9293
private Boolean multiReleaseJar;
9394

95+
private JarEntryCertification[] certifications;
96+
9497
private final Map<Integer, FileHeader> entriesCache = Collections
9598
.synchronizedMap(new LinkedHashMap<Integer, FileHeader>(16, 0.75f, true) {
9699

97100
@Override
98101
protected boolean removeEldestEntry(Map.Entry<Integer, FileHeader> eldest) {
99-
if (JarFileEntries.this.jarFile.isSigned()) {
100-
return false;
101-
}
102102
return size() >= ENTRY_CACHE_SIZE;
103103
}
104104

@@ -313,7 +313,7 @@ private <T extends FileHeader> T getEntry(int index, Class<T> type, boolean cach
313313
FileHeader entry = (cached != null) ? cached : CentralDirectoryFileHeader
314314
.fromRandomAccessData(this.centralDirectoryData, this.centralDirectoryOffsets[index], this.filter);
315315
if (CentralDirectoryFileHeader.class.equals(entry.getClass()) && type.equals(JarEntry.class)) {
316-
entry = new JarEntry(this.jarFile, (CentralDirectoryFileHeader) entry, nameAlias);
316+
entry = new JarEntry(this.jarFile, index, (CentralDirectoryFileHeader) entry, nameAlias);
317317
}
318318
if (cacheEntry && cached != entry) {
319319
this.entriesCache.put(index, entry);
@@ -344,6 +344,41 @@ private AsciiBytes applyFilter(AsciiBytes name) {
344344
return (this.filter != null) ? this.filter.apply(name) : name;
345345
}
346346

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

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
@@ -51,6 +51,7 @@
5151
import org.springframework.boot.loader.data.RandomAccessDataFile;
5252
import org.springframework.test.util.ReflectionTestUtils;
5353
import org.springframework.util.FileCopyUtils;
54+
import org.springframework.util.StopWatch;
5455
import org.springframework.util.StreamUtils;
5556

5657
import static org.assertj.core.api.Assertions.assertThat;
@@ -397,29 +398,33 @@ void sensibleToString() throws Exception {
397398

398399
@Test
399400
void verifySignedJar() throws Exception {
400-
String classpath = System.getProperty("java.class.path");
401-
String[] entries = classpath.split(System.getProperty("path.separator"));
402-
String signedJarFile = null;
403-
for (String entry : entries) {
404-
if (entry.contains("bcprov")) {
405-
signedJarFile = entry;
401+
File signedJarFile = getSignedJarFile();
402+
assertThat(signedJarFile).exists();
403+
try (java.util.jar.JarFile expected = new java.util.jar.JarFile(signedJarFile)) {
404+
try (JarFile actual = new JarFile(signedJarFile)) {
405+
StopWatch stopWatch = new StopWatch();
406+
Enumeration<JarEntry> actualEntries = actual.entries();
407+
while (actualEntries.hasMoreElements()) {
408+
JarEntry actualEntry = actualEntries.nextElement();
409+
java.util.jar.JarEntry expectedEntry = expected.getJarEntry(actualEntry.getName());
410+
assertThat(actualEntry.getCertificates()).as(actualEntry.getName())
411+
.isEqualTo(expectedEntry.getCertificates());
412+
assertThat(actualEntry.getCodeSigners()).as(actualEntry.getName())
413+
.isEqualTo(expectedEntry.getCodeSigners());
414+
}
415+
assertThat(stopWatch.getTotalTimeSeconds()).isLessThan(3.0);
406416
}
407417
}
408-
assertThat(signedJarFile).isNotNull();
409-
java.util.jar.JarFile jarFile = new JarFile(new File(signedJarFile));
410-
jarFile.getManifest();
411-
Enumeration<JarEntry> jarEntries = jarFile.entries();
412-
while (jarEntries.hasMoreElements()) {
413-
JarEntry jarEntry = jarEntries.nextElement();
414-
InputStream inputStream = jarFile.getInputStream(jarEntry);
415-
inputStream.skip(Long.MAX_VALUE);
416-
inputStream.close();
417-
if (!jarEntry.getName().startsWith("META-INF") && !jarEntry.isDirectory()
418-
&& !jarEntry.getName().endsWith("TigerDigest.class")) {
419-
assertThat(jarEntry.getCertificates()).isNotNull();
418+
}
419+
420+
private File getSignedJarFile() {
421+
String[] entries = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
422+
for (String entry : entries) {
423+
if (entry.contains("bcprov")) {
424+
return new File(entry);
420425
}
421426
}
422-
jarFile.close();
427+
return null;
423428
}
424429

425430
@Test

0 commit comments

Comments
 (0)