001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 package org.apache.commons.compress.archivers.ar; 020 021 import java.io.IOException; 022 import java.io.InputStream; 023 024 import org.apache.commons.compress.archivers.ArchiveEntry; 025 import org.apache.commons.compress.archivers.ArchiveInputStream; 026 import org.apache.commons.compress.utils.ArchiveUtils; 027 028 /** 029 * Implements the "ar" archive format as an input stream. 030 * 031 * @NotThreadSafe 032 * 033 */ 034 public class ArArchiveInputStream extends ArchiveInputStream { 035 036 private final InputStream input; 037 private long offset = 0; 038 private boolean closed; 039 040 /* 041 * If getNextEnxtry has been called, the entry metadata is stored in 042 * currentEntry. 043 */ 044 private ArArchiveEntry currentEntry = null; 045 046 /* 047 * The offset where the current entry started. -1 if no entry has been 048 * called 049 */ 050 private long entryOffset = -1; 051 052 /** 053 * Constructs an Ar input stream with the referenced stream 054 * 055 * @param pInput 056 * the ar input stream 057 */ 058 public ArArchiveInputStream(final InputStream pInput) { 059 input = pInput; 060 closed = false; 061 } 062 063 /** 064 * Returns the next AR entry in this stream. 065 * 066 * @return the next AR entry. 067 * @throws IOException 068 * if the entry could not be read 069 */ 070 public ArArchiveEntry getNextArEntry() throws IOException { 071 if (currentEntry != null) { 072 final long entryEnd = entryOffset + currentEntry.getLength(); 073 while (offset < entryEnd) { 074 int x = read(); 075 if (x == -1) { 076 // hit EOF before previous entry was complete 077 // TODO: throw an exception instead? 078 return null; 079 } 080 } 081 currentEntry = null; 082 } 083 084 if (offset == 0) { 085 final byte[] expected = ArchiveUtils.toAsciiBytes(ArArchiveEntry.HEADER); 086 final byte[] realized = new byte[expected.length]; 087 final int read = read(realized); 088 if (read != expected.length) { 089 throw new IOException("failed to read header. Occured at byte: " + getCount()); 090 } 091 for (int i = 0; i < expected.length; i++) { 092 if (expected[i] != realized[i]) { 093 throw new IOException("invalid header " + ArchiveUtils.toAsciiString(realized)); 094 } 095 } 096 } 097 098 if (offset % 2 != 0) { 099 if (read() < 0) { 100 // hit eof 101 return null; 102 } 103 } 104 105 if (input.available() == 0) { 106 return null; 107 } 108 109 final byte[] name = new byte[16]; 110 final byte[] lastmodified = new byte[12]; 111 final byte[] userid = new byte[6]; 112 final byte[] groupid = new byte[6]; 113 final byte[] filemode = new byte[8]; 114 final byte[] length = new byte[10]; 115 116 read(name); 117 read(lastmodified); 118 read(userid); 119 read(groupid); 120 read(filemode); 121 read(length); 122 123 { 124 final byte[] expected = ArchiveUtils.toAsciiBytes(ArArchiveEntry.TRAILER); 125 final byte[] realized = new byte[expected.length]; 126 final int read = read(realized); 127 if (read != expected.length) { 128 throw new IOException("failed to read entry header. Occured at byte: " + getCount()); 129 } 130 for (int i = 0; i < expected.length; i++) { 131 if (expected[i] != realized[i]) { 132 throw new IOException("invalid entry header. not read the content? Occured at byte: " + getCount()); 133 } 134 } 135 } 136 137 entryOffset = offset; 138 139 // SVR4/GNU adds a trailing "/" to names 140 // entry name is stored as ASCII string 141 String temp = ArchiveUtils.toAsciiString(name).trim(); 142 if (temp.endsWith("/")) { 143 temp = temp.substring(0, temp.length() - 1); 144 } 145 currentEntry = new ArArchiveEntry(temp, asLong(length), asInt(userid), 146 asInt(groupid), asInt(filemode, 8), 147 asLong(lastmodified)); 148 return currentEntry; 149 } 150 151 private long asLong(byte[] input) { 152 return Long.parseLong(new String(input).trim()); 153 } 154 155 private int asInt(byte[] input) { 156 return asInt(input, 10); 157 } 158 159 private int asInt(byte[] input, int base) { 160 return Integer.parseInt(new String(input).trim(), base); 161 } 162 163 /* 164 * (non-Javadoc) 165 * 166 * @see 167 * org.apache.commons.compress.archivers.ArchiveInputStream#getNextEntry() 168 */ 169 public ArchiveEntry getNextEntry() throws IOException { 170 return getNextArEntry(); 171 } 172 173 /* 174 * (non-Javadoc) 175 * 176 * @see java.io.InputStream#close() 177 */ 178 public void close() throws IOException { 179 if (!closed) { 180 closed = true; 181 input.close(); 182 } 183 currentEntry = null; 184 } 185 186 /* 187 * (non-Javadoc) 188 * 189 * @see java.io.InputStream#read(byte[], int, int) 190 */ 191 public int read(byte[] b, final int off, final int len) throws IOException { 192 int toRead = len; 193 if (currentEntry != null) { 194 final long entryEnd = entryOffset + currentEntry.getLength(); 195 if (len > 0 && entryEnd > offset) { 196 toRead = (int) Math.min(len, entryEnd - offset); 197 } else { 198 return -1; 199 } 200 } 201 final int ret = this.input.read(b, off, toRead); 202 count(ret); 203 offset += (ret > 0 ? ret : 0); 204 return ret; 205 } 206 207 /** 208 * Checks if the signature matches ASCII "!<arch>" followed by a single LF 209 * control character 210 * 211 * @param signature 212 * the bytes to check 213 * @param length 214 * the number of bytes to check 215 * @return true, if this stream is an Ar archive stream, false otherwise 216 */ 217 public static boolean matches(byte[] signature, int length) { 218 // 3c21 7261 6863 0a3e 219 220 if (length < 8) { 221 return false; 222 } 223 if (signature[0] != 0x21) { 224 return false; 225 } 226 if (signature[1] != 0x3c) { 227 return false; 228 } 229 if (signature[2] != 0x61) { 230 return false; 231 } 232 if (signature[3] != 0x72) { 233 return false; 234 } 235 if (signature[4] != 0x63) { 236 return false; 237 } 238 if (signature[5] != 0x68) { 239 return false; 240 } 241 if (signature[6] != 0x3e) { 242 return false; 243 } 244 if (signature[7] != 0x0a) { 245 return false; 246 } 247 248 return true; 249 } 250 251 }