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, Long.parseLong(new String(length).trim())); 146 return currentEntry; 147 } 148 149 /* 150 * (non-Javadoc) 151 * 152 * @see 153 * org.apache.commons.compress.archivers.ArchiveInputStream#getNextEntry() 154 */ 155 public ArchiveEntry getNextEntry() throws IOException { 156 return getNextArEntry(); 157 } 158 159 /* 160 * (non-Javadoc) 161 * 162 * @see java.io.InputStream#close() 163 */ 164 public void close() throws IOException { 165 if (!closed) { 166 closed = true; 167 input.close(); 168 } 169 currentEntry = null; 170 } 171 172 /* 173 * (non-Javadoc) 174 * 175 * @see java.io.InputStream#read(byte[], int, int) 176 */ 177 public int read(byte[] b, final int off, final int len) throws IOException { 178 int toRead = len; 179 if (currentEntry != null) { 180 final long entryEnd = entryOffset + currentEntry.getLength(); 181 if (len > 0 && entryEnd > offset) { 182 toRead = (int) Math.min(len, entryEnd - offset); 183 } else { 184 return -1; 185 } 186 } 187 final int ret = this.input.read(b, off, toRead); 188 count(ret); 189 offset += (ret > 0 ? ret : 0); 190 return ret; 191 } 192 193 /** 194 * Checks if the signature matches ASCII "!<arch>" followed by a single LF 195 * control character 196 * 197 * @param signature 198 * the bytes to check 199 * @param length 200 * the number of bytes to check 201 * @return true, if this stream is an Ar archive stream, false otherwise 202 */ 203 public static boolean matches(byte[] signature, int length) { 204 // 3c21 7261 6863 0a3e 205 206 if (length < 8) { 207 return false; 208 } 209 if (signature[0] != 0x21) { 210 return false; 211 } 212 if (signature[1] != 0x3c) { 213 return false; 214 } 215 if (signature[2] != 0x61) { 216 return false; 217 } 218 if (signature[3] != 0x72) { 219 return false; 220 } 221 if (signature[4] != 0x63) { 222 return false; 223 } 224 if (signature[5] != 0x68) { 225 return false; 226 } 227 if (signature[6] != 0x3e) { 228 return false; 229 } 230 if (signature[7] != 0x0a) { 231 return false; 232 } 233 234 return true; 235 } 236 237 }