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 public void closeArchiveEntry() throws IOException { 056 if(finished) { 057 throw new IOException("Stream has already been finished"); 058 } 059 if (prevEntry == null || !haveUnclosedEntry){ 060 throw new IOException("No current entry to close"); 061 } 062 if ((entryOffset % 2) != 0) { 063 out.write('\n'); // Pad byte 064 archiveOffset++; 065 } 066 haveUnclosedEntry = false; 067 } 068 069 public void putArchiveEntry( final ArchiveEntry pEntry ) throws IOException { 070 if(finished) { 071 throw new IOException("Stream has already been finished"); 072 } 073 074 ArArchiveEntry pArEntry = (ArArchiveEntry)pEntry; 075 if (prevEntry == null) { 076 archiveOffset += writeArchiveHeader(); 077 } else { 078 if (prevEntry.getLength() != entryOffset) { 079 throw new IOException("length does not match entry (" + prevEntry.getLength() + " != " + entryOffset); 080 } 081 082 if (haveUnclosedEntry) { 083 closeArchiveEntry(); 084 } 085 } 086 087 prevEntry = pArEntry; 088 089 archiveOffset += writeEntryHeader(pArEntry); 090 091 entryOffset = 0; 092 haveUnclosedEntry = true; 093 } 094 095 private long fill( final long pOffset, final long pNewOffset, final char pFill ) throws IOException { 096 final long diff = pNewOffset - pOffset; 097 098 if (diff > 0) { 099 for (int i = 0; i < diff; i++) { 100 write(pFill); 101 } 102 } 103 104 return pNewOffset; 105 } 106 107 private long write( final String data ) throws IOException { 108 final byte[] bytes = data.getBytes("ascii"); 109 write(bytes); 110 return bytes.length; 111 } 112 113 private long writeEntryHeader( final ArArchiveEntry pEntry ) throws IOException { 114 115 long offset = 0; 116 117 final String n = pEntry.getName(); 118 if (n.length() > 16) { 119 throw new IOException("filename too long, > 16 chars: "+n); 120 } 121 offset += write(n); 122 123 offset = fill(offset, 16, ' '); 124 final String m = "" + (pEntry.getLastModified()); 125 if (m.length() > 12) { 126 throw new IOException("modified too long"); 127 } 128 offset += write(m); 129 130 offset = fill(offset, 28, ' '); 131 final String u = "" + pEntry.getUserId(); 132 if (u.length() > 6) { 133 throw new IOException("userid too long"); 134 } 135 offset += write(u); 136 137 offset = fill(offset, 34, ' '); 138 final String g = "" + pEntry.getGroupId(); 139 if (g.length() > 6) { 140 throw new IOException("groupid too long"); 141 } 142 offset += write(g); 143 144 offset = fill(offset, 40, ' '); 145 final String fm = "" + Integer.toString(pEntry.getMode(), 8); 146 if (fm.length() > 8) { 147 throw new IOException("filemode too long"); 148 } 149 offset += write(fm); 150 151 offset = fill(offset, 48, ' '); 152 final String s = "" + pEntry.getLength(); 153 if (s.length() > 10) { 154 throw new IOException("size too long"); 155 } 156 offset += write(s); 157 158 offset = fill(offset, 58, ' '); 159 160 offset += write(ArArchiveEntry.TRAILER); 161 162 return offset; 163 } 164 165 public void write(byte[] b, int off, int len) throws IOException { 166 out.write(b, off, len); 167 count(len); 168 entryOffset += len; 169 } 170 171 public void close() throws IOException { 172 if(!finished) { 173 finish(); 174 } 175 out.close(); 176 prevEntry = null; 177 } 178 179 public ArchiveEntry createArchiveEntry(File inputFile, String entryName) 180 throws IOException { 181 if(finished) { 182 throw new IOException("Stream has already been finished"); 183 } 184 return new ArArchiveEntry(inputFile, entryName); 185 } 186 187 /* (non-Javadoc) 188 * @see org.apache.commons.compress.archivers.ArchiveOutputStream#finish() 189 */ 190 public void finish() throws IOException { 191 if(haveUnclosedEntry) { 192 throw new IOException("This archive contains unclosed entries."); 193 } else if(finished) { 194 throw new IOException("This archive has already been finished"); 195 } 196 finished = true; 197 } 198 }