1 /******************************************************************************* 2 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors 3 * This program and the accompanying materials are made available under 4 * the terms of the Eclipse Public License 2.0 which is available at 5 * http://www.eclipse.org/legal/epl-2.0 6 * 7 * SPDX-License-Identifier: EPL-2.0 8 * 9 * Contributors: 10 * Marc R. Hoffmann - initial API and implementation 11 * 12 *******************************************************************************/ 13 package org.jacoco.core.internal; 14 15 import java.io.BufferedInputStream; 16 import java.io.IOException; 17 import java.io.InputStream; 18 19 /** 20 * Detector for content types of binary streams based on a magic headers. 21 */ 22 public class ContentTypeDetector { 23 24 /** Unknown file type */ 25 public static final int UNKNOWN = -1; 26 27 /** File type Java class */ 28 public static final int CLASSFILE = 0xcafebabe; 29 30 /** File type ZIP archive */ 31 public static final int ZIPFILE = 0x504b0304; 32 33 /** File type GZIP compressed Data */ 34 public static final int GZFILE = 0x1f8b0000; 35 36 /** File type Pack200 archive */ 37 public static final int PACK200FILE = 0xcafed00d; 38 39 private static final int BUFFER_SIZE = 8; 40 41 private final InputStream in; 42 43 private final int type; 44 45 /** 46 * Creates a new detector based on the given input. To process the complete 47 * original input afterwards use the stream returned by 48 * {@link #getInputStream()}. 49 * 50 * @param in 51 * input to read the header from 52 * @throws IOException 53 * if the stream can't be read 54 */ ContentTypeDetector(final InputStream in)55 public ContentTypeDetector(final InputStream in) throws IOException { 56 if (in.markSupported()) { 57 this.in = in; 58 } else { 59 this.in = new BufferedInputStream(in, BUFFER_SIZE); 60 } 61 this.in.mark(BUFFER_SIZE); 62 this.type = determineType(this.in); 63 this.in.reset(); 64 } 65 determineType(final InputStream in)66 private static int determineType(final InputStream in) throws IOException { 67 final int header = readInt(in); 68 switch (header) { 69 case ZIPFILE: 70 return ZIPFILE; 71 case PACK200FILE: 72 return PACK200FILE; 73 case CLASSFILE: 74 // Mach-O fat/universal binaries have the same magic header as Java 75 // class files, number of architectures is stored in unsigned 4 76 // bytes in the same place and in the same big-endian order as major 77 // and minor version of class file. Hopefully on practice number of 78 // architectures in single executable is less than 45, which is 79 // major version of Java 1.1 class files: 80 final int majorVersion = readInt(in) & 0xFFFF; 81 if (majorVersion >= 45) { 82 return CLASSFILE; 83 } 84 } 85 if ((header & 0xffff0000) == GZFILE) { 86 return GZFILE; 87 } 88 return UNKNOWN; 89 } 90 readInt(final InputStream in)91 private static int readInt(final InputStream in) throws IOException { 92 return in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); 93 } 94 95 /** 96 * Returns an input stream instance to read the complete content (including 97 * the header) of the underlying stream. 98 * 99 * @return input stream containing the complete content 100 */ getInputStream()101 public InputStream getInputStream() { 102 return in; 103 } 104 105 /** 106 * Returns the detected file type. 107 * 108 * @return file type 109 */ getType()110 public int getType() { 111 return type; 112 } 113 114 } 115