Java tutorial
/******************************************************************************* * Copyright (c) 2015 IBH SYSTEMS GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBH SYSTEMS GmbH - initial API and implementation *******************************************************************************/ package de.dentrassi.rpm; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tukaani.xz.LZMAInputStream; import org.tukaani.xz.XZInputStream; import com.google.common.io.CountingInputStream; public class RpmInputStream extends InputStream { private final static Logger logger = LoggerFactory.getLogger(RpmInputStream.class); private static final byte[] LEAD_MAGIC = new byte[] { (byte) 0xED, (byte) 0xAB, (byte) 0xEE, (byte) 0xDB }; private static final byte[] HEADER_MAGIC = new byte[] { (byte) 0x8E, (byte) 0xAD, (byte) 0xE8 }; private static final byte[] DUMMY = new byte[128]; private final DataInputStream in; private boolean closed; private RpmLead lead; private RpmHeader<RpmSignatureTag> signatureHeader; private RpmHeader<RpmTag> payloadHeader; private InputStream payloadStream; private CpioArchiveInputStream cpioStream; private final CountingInputStream count; public RpmInputStream(final InputStream in) { this.count = new CountingInputStream(in); this.in = new DataInputStream(this.count); } @Override public void close() throws IOException { if (!this.closed) { this.in.close(); this.closed = true; } } protected void ensureInit() throws IOException { if (this.lead == null) { this.lead = readLead(); } if (this.signatureHeader == null) { this.signatureHeader = readHeader(true); } if (this.payloadHeader == null) { this.payloadHeader = readHeader(false); } // set up content stream if (this.payloadStream == null) { this.payloadStream = setupPayloadStream(); this.cpioStream = new CpioArchiveInputStream(this.payloadStream); // we did ensure that we only support CPIO before } } private InputStream setupPayloadStream() throws IOException { final Object payloadFormatValue = this.payloadHeader.getRawTags().get(RpmTag.PAYLOAD_FORMAT.getValue()); final Object payloadCodingValue = this.payloadHeader.getRawTags().get(RpmTag.PAYLOAD_CODING.getValue()); if (payloadFormatValue != null && !(payloadFormatValue instanceof String)) { throw new IOException("Payload format must be a single string"); } if (payloadFormatValue != null && !(payloadCodingValue instanceof String)) { throw new IOException("Payload coding must be a single string"); } String payloadFormat = (String) payloadFormatValue; String payloadCoding = (String) payloadCodingValue; if (payloadFormat == null || payloadFormat.isEmpty()) { payloadFormat = "cpio"; } if (payloadCoding == null || payloadCoding.isEmpty()) { payloadCoding = "gzip"; } if (!"cpio".equals(payloadFormat)) { throw new IOException(String.format("Unknown payload format: %s", payloadFormat)); } switch (payloadCoding) { case "none": return this.in; case "gzip": return new GzipCompressorInputStream(this.in); case "bzip2": return new BZip2CompressorInputStream(this.in); case "lzma": return new LZMAInputStream(this.in); case "xz": return new XZInputStream(this.in); default: throw new IOException(String.format("Unknown coding: %s", payloadCoding)); } } public CpioArchiveInputStream getCpioStream() { return this.cpioStream; } public RpmLead getLead() throws IOException { ensureInit(); return this.lead; } public RpmHeader<RpmSignatureTag> getSignatureHeader() throws IOException { ensureInit(); return this.signatureHeader; } public RpmHeader<RpmTag> getPayloadHeader() throws IOException { ensureInit(); return this.payloadHeader; } protected RpmLead readLead() throws IOException { final byte[] magic = readComplete(4); if (!Arrays.equals(magic, LEAD_MAGIC)) { throw new IOException(String.format("File corrupt: Expected magic %s, read: %s", Arrays.toString(LEAD_MAGIC), Arrays.toString(magic))); } final byte[] version = readComplete(2); skipFully(4); // TYPE + ARCH final byte[] nameData = readComplete(66); // NAME final String name = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(nameData)).toString(); skipFully(2); // OS final int sigType = this.in.readUnsignedShort(); skipFully(16); // RESERVED return new RpmLead(version[0], version[1], name, sigType); } protected <T extends RpmBaseTag> RpmHeader<T> readHeader(final boolean withPadding) throws IOException { final long start = this.count.getCount(); final byte[] magic = readComplete(3); if (!Arrays.equals(magic, HEADER_MAGIC)) { throw new IOException(String.format("File corrupt: Expected entry magic %s, read: %s", Arrays.toString(HEADER_MAGIC), Arrays.toString(magic))); } final byte version = this.in.readByte(); if (version != 1) { throw new IOException( String.format("File corrupt: Invalid header entry version: %s (valid: 1)", version)); } skipFully(4); // RESERVED final int indexCount = this.in.readInt(); final long storeSize = this.in.readInt() & 0xFFFFFFFF; final RpmEntry[] entries = new RpmEntry[indexCount]; for (int i = 0; i < indexCount; i++) { entries[i] = readEntry(); } final ByteBuffer store = ByteBuffer.wrap(readComplete((int) storeSize)); // FIXME: bad casting ... for (int i = 0; i < indexCount; i++) { entries[i].fillFromStore(store); } if (withPadding) { // pad remaining bytes - to 8 final long rem = storeSize % 8; if (rem > 0) { final int skip = (int) (8 - rem); logger.debug("Skipping {} pad bytes", skip); skipFully(skip); } } final long end = this.count.getCount(); return new RpmHeader<T>(entries, start, end - start); } private RpmEntry readEntry() throws IOException { final int tag = this.in.readInt(); final int type = this.in.readInt(); final int offset = this.in.readInt(); final int count = this.in.readInt(); return new RpmEntry(tag, type, offset, count); } private byte[] readComplete(final int size) throws IOException { final byte[] result = new byte[size]; this.in.readFully(result); return result; } private void skipFully(final int count) throws IOException { this.in.readFully(DUMMY, 0, count); } // forward methods @Override public void reset() throws IOException { ensureInit(); this.payloadStream.reset(); } @Override public int read() throws IOException { ensureInit(); return this.payloadStream.read(); } @Override public long skip(final long n) throws IOException { ensureInit(); return this.payloadStream.skip(n); } @Override public int available() throws IOException { ensureInit(); return this.payloadStream.available(); } @Override public int read(final byte[] b) throws IOException { ensureInit(); return this.payloadStream.read(b); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { return this.payloadStream.read(b, off, len); } }