1 /******************************************************************************* 2 * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 *******************************************************************************/ 12 package org.jacoco.core.internal.instr; 13 14 import java.io.IOException; 15 import java.io.InputStream; 16 import java.io.OutputStream; 17 import java.util.Collection; 18 import java.util.Iterator; 19 import java.util.jar.Attributes; 20 import java.util.jar.Manifest; 21 import java.util.regex.Pattern; 22 23 /** 24 * Support class to filter entries from JARs related to signatures. 25 */ 26 public class SignatureRemover { 27 28 private static final Pattern SIGNATURE_FILES = Pattern 29 .compile("META-INF/[^/]*\\.SF|" // 30 + "META-INF/[^/]*\\.DSA|" // 31 + "META-INF/[^/]*\\.RSA|" // 32 + "META-INF/SIG-[^/]*"); 33 34 private static final String MANIFEST_MF = "META-INF/MANIFEST.MF"; 35 36 private static final String DIGEST_SUFFIX = "-Digest"; 37 38 private boolean active; 39 40 /** 41 * Creates a new remover which is active. 42 */ 43 public SignatureRemover() { 44 active = true; 45 } 46 47 /** 48 * Defines whether this remover should be active. If it is not active it 49 * will not remove any entries. 50 * 51 * @param active 52 * <code>true</code> if it should remove signature related 53 * entries. 54 */ 55 public void setActive(final boolean active) { 56 this.active = active; 57 } 58 59 /** 60 * Checks whether a entry with the provided name should be ignored at all. 61 * 62 * @param name 63 * path name of the entry in question 64 * @return true is the entry should be ignored 65 */ 66 public boolean removeEntry(final String name) { 67 return active && SIGNATURE_FILES.matcher(name).matches(); 68 } 69 70 /** 71 * Filters the content of the entry with the provided name if necessary. 72 * 73 * @param name 74 * path name of the entry in question 75 * @param in 76 * source for the element to filter 77 * @param out 78 * output for the filtered contents 79 * @return <code>true</code> if the content was filtered 80 * @throws IOException 81 * if the content can't be read or written 82 */ 83 public boolean filterEntry(final String name, final InputStream in, 84 final OutputStream out) throws IOException { 85 if (!active || !MANIFEST_MF.equals(name)) { 86 return false; 87 } 88 final Manifest mf = new Manifest(in); 89 filterManifestEntry(mf.getEntries().values()); 90 mf.write(out); 91 return true; 92 } 93 94 private void filterManifestEntry(final Collection<Attributes> entry) { 95 for (final Iterator<Attributes> i = entry.iterator(); i.hasNext();) { 96 final Attributes attributes = i.next(); 97 filterManifestEntryAttributes(attributes); 98 if (attributes.isEmpty()) { 99 i.remove(); 100 } 101 } 102 } 103 104 private void filterManifestEntryAttributes(final Attributes attrs) { 105 for (final Iterator<Object> i = attrs.keySet().iterator(); i.hasNext();) { 106 if (String.valueOf(i.next()).endsWith(DIGEST_SUFFIX)) { 107 i.remove(); 108 } 109 } 110 } 111 112 } 113