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.File; 022 import java.io.IOException; 023 import java.io.OutputStream; 024 025 import org.apache.commons.compress.archivers.ArchiveEntry; 026 import org.apache.commons.compress.archivers.ArchiveOutputStream; 027 import org.apache.commons.compress.utils.ArchiveUtils; 028 029 /** 030 * Implements the "ar" archive format as an output stream. 031 * 032 * @NotThreadSafe 033 */ 034 public class ArArchiveOutputStream extends ArchiveOutputStream { 035 036 private final OutputStream out; 037 private long archiveOffset = 0; 038 private long entryOffset = 0; 039 private ArArchiveEntry prevEntry; 040 private boolean haveUnclosedEntry = false; 041 042 /** indicates if this archive is finished */ 043 private boolean finished = false; 044 045 public ArArchiveOutputStream( final OutputStream pOut ) { 046 this.out = pOut; 047 } 048 049 private long writeArchiveHeader() throws IOException { 050 byte [] header = ArchiveUtils.toAsciiBytes(ArArchiveEntry.HEADER); 051 out.write(header); 052 return header.length; 053 } 054 055 /** {@inheritDoc} */ 056 public void closeArchiveEntry() throws IOException { 057 if(finished) { 058 throw new IOException("Stream has already been finished"); 059 } 060 if (prevEntry == null || !haveUnclosedEntry){ 061 throw new IOException("No current entry to close"); 062 } 063 if ((entryOffset % 2) != 0) { 064 out.write('\n'); // Pad byte 065 archiveOffset++; 066 } 067 haveUnclosedEntry = false; 068 } 069 070 /** {@inheritDoc} */ 071 public void putArchiveEntry( final ArchiveEntry pEntry ) throws IOException { 072 if(finished) { 073 throw new IOException("Stream has already been finished"); 074 } 075 076 ArArchiveEntry pArEntry = (ArArchiveEntry)pEntry; 077 if (prevEntry == null) { 078 archiveOffset += writeArchiveHeader(); 079 } else { 080 if (prevEntry.getLength() != entryOffset) { 081 throw new IOException("length does not match entry (" + prevEntry.getLength() + " != " + entryOffset); 082 } 083 084 if (haveUnclosedEntry) { 085 closeArchiveEntry(); 086 } 087 } 088 089 prevEntry = pArEntry; 090 091 archiveOffset += writeEntryHeader(pArEntry); 092 093 entryOffset = 0; 094 haveUnclosedEntry = true; 095 } 096 097 private long fill( final long pOffset, final long pNewOffset, final char pFill ) throws IOException { 098 final long diff = pNewOffset - pOffset; 099 100 if (diff > 0) { 101 for (int i = 0; i < diff; i++) { 102 write(pFill); 103 } 104 } 105 106 return pNewOffset; 107 } 108 109 private long write( final String data ) throws IOException { 110 final byte[] bytes = data.getBytes("ascii"); 111 write(bytes); 112 return bytes.length; 113 } 114 115 private long writeEntryHeader( final ArArchiveEntry pEntry ) throws IOException { 116 117 long offset = 0; 118 119 final String n = pEntry.getName(); 120 if (n.length() > 16) { 121 throw new IOException("filename too long, > 16 chars: "+n); 122 } 123 offset += write(n); 124 125 offset = fill(offset, 16, ' '); 126 final String m = "" + (pEntry.getLastModified()); 127 if (m.length() > 12) { 128 throw new IOException("modified too long"); 129 } 130 offset += write(m); 131 132 offset = fill(offset, 28, ' '); 133 final String u = "" + pEntry.getUserId(); 134 if (u.length() > 6) { 135 throw new IOException("userid too long"); 136 } 137 offset += write(u); 138 139 offset = fill(offset, 34, ' '); 140 final String g = "" + pEntry.getGroupId(); 141 if (g.length() > 6) { 142 throw new IOException("groupid too long"); 143 } 144 offset += write(g); 145 146 offset = fill(offset, 40, ' '); 147 final String fm = "" + Integer.toString(pEntry.getMode(), 8); 148 if (fm.length() > 8) { 149 throw new IOException("filemode too long"); 150 } 151 offset += write(fm); 152 153 offset = fill(offset, 48, ' '); 154 final String s = "" + pEntry.getLength(); 155 if (s.length() > 10) { 156 throw new IOException("size too long"); 157 } 158 offset += write(s); 159 160 offset = fill(offset, 58, ' '); 161 162 offset += write(ArArchiveEntry.TRAILER); 163 164 return offset; 165 } 166 167 public void write(byte[] b, int off, int len) throws IOException { 168 out.write(b, off, len); 169 count(len); 170 entryOffset += len; 171 } 172 173 /** 174 * Calls finish if necessary, and then closes the OutputStream 175 */ 176 public void close() throws IOException { 177 if(!finished) { 178 finish(); 179 } 180 out.close(); 181 prevEntry = null; 182 } 183 184 /** {@inheritDoc} */ 185 public ArchiveEntry createArchiveEntry(File inputFile, String entryName) 186 throws IOException { 187 if(finished) { 188 throw new IOException("Stream has already been finished"); 189 } 190 return new ArArchiveEntry(inputFile, entryName); 191 } 192 193 /** {@inheritDoc} */ 194 public void finish() throws IOException { 195 if(haveUnclosedEntry) { 196 throw new IOException("This archive contains unclosed entries."); 197 } else if(finished) { 198 throw new IOException("This archive has already been finished"); 199 } 200 finished = true; 201 } 202 }