blob: e2fe3bbfe184e2817c9819c423bdfc43b914642f [file] [log] [blame]
Torsten Curdtca165392008-07-10 10:17:44 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.commons.compress.archivers.ar;
20
Sebastian Bazleyfec51a12009-03-31 00:35:56 +000021import java.io.File;
Torsten Curdtca165392008-07-10 10:17:44 +000022import java.io.IOException;
23import java.io.OutputStream;
24
25import org.apache.commons.compress.archivers.ArchiveEntry;
26import org.apache.commons.compress.archivers.ArchiveOutputStream;
Sebastian Bazley865686a2009-04-14 23:21:52 +000027import org.apache.commons.compress.utils.ArchiveUtils;
Torsten Curdtca165392008-07-10 10:17:44 +000028
Sebastian Bazley1d3d6ea2009-03-14 02:24:54 +000029/**
Sebastian Bazleya91e7c72009-03-27 21:13:51 +000030 * Implements the "ar" archive format as an output stream.
31 *
32 * @NotThreadSafe
Sebastian Bazley1d3d6ea2009-03-14 02:24:54 +000033 */
Torsten Curdtca165392008-07-10 10:17:44 +000034public class ArArchiveOutputStream extends ArchiveOutputStream {
Stefan Bodewig1b915c72011-08-08 11:39:41 +000035 /** Fail if a long file name is required in the archive. */
36 public static final int LONGFILE_ERROR = 0;
37
38 /** BSD ar extensions are used to store long file names in the archive. */
39 public static final int LONGFILE_BSD = 1;
Torsten Curdtca165392008-07-10 10:17:44 +000040
Stefan Bodewigc30b5882009-02-10 15:35:35 +000041 private final OutputStream out;
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000042 private long entryOffset = 0;
43 private ArArchiveEntry prevEntry;
Sebastian Bazley13b90ae2009-04-27 20:57:10 +000044 private boolean haveUnclosedEntry = false;
Stefan Bodewig1b915c72011-08-08 11:39:41 +000045 private int longFileMode = LONGFILE_ERROR;
46
Christian Grobmeier285ee872009-04-27 17:48:29 +000047 /** indicates if this archive is finished */
Christian Grobmeier545bfa82009-04-27 17:43:58 +000048 private boolean finished = false;
Torsten Curdtca165392008-07-10 10:17:44 +000049
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000050 public ArArchiveOutputStream( final OutputStream pOut ) {
Stefan Bodewigc30b5882009-02-10 15:35:35 +000051 this.out = pOut;
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000052 }
Torsten Curdtca165392008-07-10 10:17:44 +000053
Stefan Bodewig1b915c72011-08-08 11:39:41 +000054 /**
55 * Set the long file mode.
56 * This can be LONGFILE_ERROR(0) or LONGFILE_BSD(1).
57 * This specifies the treatment of long file names (names >= 16).
58 * Default is LONGFILE_ERROR.
59 * @param longFileMode the mode to use
Gary D. Gregory2bd0dd42012-04-01 13:02:39 +000060 * @since 1.3
Stefan Bodewig1b915c72011-08-08 11:39:41 +000061 */
62 public void setLongFileMode(int longFileMode) {
63 this.longFileMode = longFileMode;
64 }
65
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000066 private long writeArchiveHeader() throws IOException {
Sebastian Bazley865686a2009-04-14 23:21:52 +000067 byte [] header = ArchiveUtils.toAsciiBytes(ArArchiveEntry.HEADER);
Sebastian Bazley6140bf02009-03-28 15:48:15 +000068 out.write(header);
69 return header.length;
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000070 }
Torsten Curdtca165392008-07-10 10:17:44 +000071
Stefan Bodewig46628ef2011-08-06 16:30:40 +000072 @Override
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000073 public void closeArchiveEntry() throws IOException {
Christian Grobmeiera45e9cb2009-04-27 17:58:04 +000074 if(finished) {
75 throw new IOException("Stream has already been finished");
76 }
Sebastian Bazley13b90ae2009-04-27 20:57:10 +000077 if (prevEntry == null || !haveUnclosedEntry){
78 throw new IOException("No current entry to close");
79 }
80 if ((entryOffset % 2) != 0) {
Sebastian Bazley6140bf02009-03-28 15:48:15 +000081 out.write('\n'); // Pad byte
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000082 }
Stefan Bodewig29f1d082009-03-30 15:07:54 +000083 haveUnclosedEntry = false;
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000084 }
Torsten Curdtca165392008-07-10 10:17:44 +000085
Stefan Bodewig46628ef2011-08-06 16:30:40 +000086 @Override
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000087 public void putArchiveEntry( final ArchiveEntry pEntry ) throws IOException {
Christian Grobmeiera45e9cb2009-04-27 17:58:04 +000088 if(finished) {
89 throw new IOException("Stream has already been finished");
90 }
Stefan Bodewig1b915c72011-08-08 11:39:41 +000091
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000092 ArArchiveEntry pArEntry = (ArArchiveEntry)pEntry;
93 if (prevEntry == null) {
Stefan Bodewig8038cf72011-10-26 15:35:05 +000094 writeArchiveHeader();
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +000095 } else {
96 if (prevEntry.getLength() != entryOffset) {
97 throw new IOException("length does not match entry (" + prevEntry.getLength() + " != " + entryOffset);
98 }
Torsten Curdtca165392008-07-10 10:17:44 +000099
Sebastian Bazley13b90ae2009-04-27 20:57:10 +0000100 if (haveUnclosedEntry) {
101 closeArchiveEntry();
102 }
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000103 }
Torsten Curdtca165392008-07-10 10:17:44 +0000104
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000105 prevEntry = pArEntry;
Torsten Curdtca165392008-07-10 10:17:44 +0000106
Stefan Bodewig8038cf72011-10-26 15:35:05 +0000107 writeEntryHeader(pArEntry);
Torsten Curdtca165392008-07-10 10:17:44 +0000108
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000109 entryOffset = 0;
Stefan Bodewig29f1d082009-03-30 15:07:54 +0000110 haveUnclosedEntry = true;
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000111 }
Torsten Curdtca165392008-07-10 10:17:44 +0000112
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000113 private long fill( final long pOffset, final long pNewOffset, final char pFill ) throws IOException {
114 final long diff = pNewOffset - pOffset;
Torsten Curdtca165392008-07-10 10:17:44 +0000115
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000116 if (diff > 0) {
117 for (int i = 0; i < diff; i++) {
118 write(pFill);
119 }
120 }
Torsten Curdtca165392008-07-10 10:17:44 +0000121
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000122 return pNewOffset;
123 }
Torsten Curdtca165392008-07-10 10:17:44 +0000124
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000125 private long write( final String data ) throws IOException {
126 final byte[] bytes = data.getBytes("ascii");
127 write(bytes);
128 return bytes.length;
129 }
Torsten Curdtca165392008-07-10 10:17:44 +0000130
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000131 private long writeEntryHeader( final ArArchiveEntry pEntry ) throws IOException {
Torsten Curdtca165392008-07-10 10:17:44 +0000132
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000133 long offset = 0;
Stefan Bodewig1b915c72011-08-08 11:39:41 +0000134 boolean mustAppendName = false;
Torsten Curdtca165392008-07-10 10:17:44 +0000135
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000136 final String n = pEntry.getName();
Stefan Bodewig1b915c72011-08-08 11:39:41 +0000137 if (LONGFILE_ERROR == longFileMode && n.length() > 16) {
Sebastian Bazleyfec51a12009-03-31 00:35:56 +0000138 throw new IOException("filename too long, > 16 chars: "+n);
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000139 }
Stefan Bodewig1b915c72011-08-08 11:39:41 +0000140 if (LONGFILE_BSD == longFileMode &&
141 (n.length() > 16 || n.indexOf(" ") > -1)) {
142 mustAppendName = true;
143 offset += write(ArArchiveInputStream.BSD_LONGNAME_PREFIX
144 + String.valueOf(n.length()));
145 } else {
146 offset += write(n);
147 }
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000148
149 offset = fill(offset, 16, ' ');
Stefan Bodewigcf76a532009-08-01 19:52:32 +0000150 final String m = "" + (pEntry.getLastModified());
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000151 if (m.length() > 12) {
152 throw new IOException("modified too long");
153 }
154 offset += write(m);
155
156 offset = fill(offset, 28, ' ');
157 final String u = "" + pEntry.getUserId();
158 if (u.length() > 6) {
159 throw new IOException("userid too long");
160 }
161 offset += write(u);
162
163 offset = fill(offset, 34, ' ');
164 final String g = "" + pEntry.getGroupId();
165 if (g.length() > 6) {
166 throw new IOException("groupid too long");
167 }
168 offset += write(g);
169
170 offset = fill(offset, 40, ' ');
171 final String fm = "" + Integer.toString(pEntry.getMode(), 8);
172 if (fm.length() > 8) {
173 throw new IOException("filemode too long");
174 }
175 offset += write(fm);
176
177 offset = fill(offset, 48, ' ');
Stefan Bodewig1b915c72011-08-08 11:39:41 +0000178 final String s =
179 String.valueOf(pEntry.getLength()
180 + (mustAppendName ? n.length() : 0));
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000181 if (s.length() > 10) {
182 throw new IOException("size too long");
183 }
184 offset += write(s);
185
186 offset = fill(offset, 58, ' ');
187
Sebastian Bazley6140bf02009-03-28 15:48:15 +0000188 offset += write(ArArchiveEntry.TRAILER);
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000189
Stefan Bodewig1b915c72011-08-08 11:39:41 +0000190 if (mustAppendName) {
191 offset += write(n);
192 }
193
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000194 return offset;
195 }
196
Stefan Bodewig46628ef2011-08-06 16:30:40 +0000197 @Override
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000198 public void write(byte[] b, int off, int len) throws IOException {
199 out.write(b, off, len);
Christian Grobmeierab269432009-04-24 06:30:17 +0000200 count(len);
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000201 entryOffset += len;
202 }
203
Sebastian Bazley8006d312010-05-12 18:18:19 +0000204 /**
205 * Calls finish if necessary, and then closes the OutputStream
206 */
Stefan Bodewig46628ef2011-08-06 16:30:40 +0000207 @Override
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000208 public void close() throws IOException {
Christian Grobmeier545bfa82009-04-27 17:43:58 +0000209 if(!finished) {
210 finish();
211 }
Stefan Bodewig3f9bcc62009-02-10 14:20:05 +0000212 out.close();
213 prevEntry = null;
214 }
Torsten Curdtca165392008-07-10 10:17:44 +0000215
Stefan Bodewig46628ef2011-08-06 16:30:40 +0000216 @Override
Sebastian Bazleyfec51a12009-03-31 00:35:56 +0000217 public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
218 throws IOException {
Christian Grobmeiera45e9cb2009-04-27 17:58:04 +0000219 if(finished) {
220 throw new IOException("Stream has already been finished");
221 }
Sebastian Bazleyfec51a12009-03-31 00:35:56 +0000222 return new ArArchiveEntry(inputFile, entryName);
223 }
224
Stefan Bodewig46628ef2011-08-06 16:30:40 +0000225 @Override
Christian Grobmeier6dfdfb52009-04-22 05:19:58 +0000226 public void finish() throws IOException {
Christian Grobmeierd170f342009-04-22 06:05:23 +0000227 if(haveUnclosedEntry) {
Sebastian Bazley13b90ae2009-04-27 20:57:10 +0000228 throw new IOException("This archive contains unclosed entries.");
Christian Grobmeier545bfa82009-04-27 17:43:58 +0000229 } else if(finished) {
230 throw new IOException("This archive has already been finished");
Christian Grobmeierd170f342009-04-22 06:05:23 +0000231 }
Christian Grobmeier545bfa82009-04-27 17:43:58 +0000232 finished = true;
Christian Grobmeier6dfdfb52009-04-22 05:19:58 +0000233 }
Torsten Curdtca165392008-07-10 10:17:44 +0000234}