1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.archivers.ar;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.nio.file.LinkOption;
25 import java.nio.file.Path;
26
27 import org.apache.commons.compress.archivers.ArchiveOutputStream;
28
29
30
31
32
33
34 public class ArArchiveOutputStream extends ArchiveOutputStream<ArArchiveEntry> {
35
36 private static final char PAD = '\n';
37
38 private static final char SPACE = ' ';
39
40
41 public static final int LONGFILE_ERROR = 0;
42
43
44 public static final int LONGFILE_BSD = 1;
45
46 private long entryOffset;
47 private int headerPlus;
48 private ArArchiveEntry prevEntry;
49 private boolean prevEntryOpen;
50 private int longFileMode = LONGFILE_ERROR;
51
52
53
54
55
56
57
58 public ArArchiveOutputStream(final OutputStream out) {
59 super(out);
60 }
61
62 private String checkLength(final String value, final int max, final String name) throws IOException {
63 if (value.length() > max) {
64 throw new IOException(name + " too long");
65 }
66 return value;
67 }
68
69
70
71
72 @Override
73 public void close() throws IOException {
74 try {
75 if (!isFinished()) {
76 finish();
77 }
78 } finally {
79 prevEntry = null;
80 super.close();
81 }
82 }
83
84 @Override
85 public void closeArchiveEntry() throws IOException {
86 checkFinished();
87 if (prevEntry == null || !prevEntryOpen) {
88 throw new IOException("No current entry to close");
89 }
90 if ((headerPlus + entryOffset) % 2 != 0) {
91 out.write(PAD);
92 }
93 prevEntryOpen = false;
94 }
95
96 @Override
97 public ArArchiveEntry createArchiveEntry(final File inputFile, final String entryName) throws IOException {
98 checkFinished();
99 return new ArArchiveEntry(inputFile, entryName);
100 }
101
102
103
104
105
106
107 @Override
108 public ArArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
109 checkFinished();
110 return new ArArchiveEntry(inputPath, entryName, options);
111 }
112
113 @Override
114 public void finish() throws IOException {
115 if (prevEntryOpen) {
116 throw new IOException("This archive contains unclosed entries.");
117 }
118 checkFinished();
119 super.finish();
120 }
121
122 private int pad(final int offset, final int newOffset, final char fill) throws IOException {
123 final int diff = newOffset - offset;
124 if (diff > 0) {
125 for (int i = 0; i < diff; i++) {
126 write(fill);
127 }
128 }
129 return newOffset;
130 }
131
132 @Override
133 public void putArchiveEntry(final ArArchiveEntry entry) throws IOException {
134 checkFinished();
135 if (prevEntry == null) {
136 writeArchiveHeader();
137 } else {
138 if (prevEntry.getLength() != entryOffset) {
139 throw new IOException("Length does not match entry (" + prevEntry.getLength() + " != " + entryOffset);
140 }
141 if (prevEntryOpen) {
142 closeArchiveEntry();
143 }
144 }
145 prevEntry = entry;
146 headerPlus = writeEntryHeader(entry);
147 entryOffset = 0;
148 prevEntryOpen = true;
149 }
150
151
152
153
154
155
156
157
158 public void setLongFileMode(final int longFileMode) {
159 this.longFileMode = longFileMode;
160 }
161
162 @Override
163 public void write(final byte[] b, final int off, final int len) throws IOException {
164 out.write(b, off, len);
165 count(len);
166 entryOffset += len;
167 }
168
169 private int write(final String data) throws IOException {
170 return writeUsAscii(data).length;
171 }
172
173 private byte[] writeArchiveHeader() throws IOException {
174 return writeUsAscii(ArArchiveEntry.HEADER);
175 }
176
177 private int writeEntryHeader(final ArArchiveEntry entry) throws IOException {
178 int offset = 0;
179 boolean appendName = false;
180 final String eName = entry.getName();
181 final int nLength = eName.length();
182 if (LONGFILE_ERROR == longFileMode && nLength > 16) {
183 throw new IOException("File name too long, > 16 chars: " + eName);
184 }
185 if (LONGFILE_BSD == longFileMode && (nLength > 16 || eName.indexOf(SPACE) > -1)) {
186 appendName = true;
187 final String fileNameLen = ArArchiveInputStream.BSD_LONGNAME_PREFIX + nLength;
188 if (fileNameLen.length() > 16) {
189 throw new IOException("File length too long, > 16 chars: " + eName);
190 }
191 offset += write(fileNameLen);
192 } else {
193 offset += write(eName);
194 }
195 offset = pad(offset, 16, SPACE);
196
197 offset += write(checkLength(String.valueOf(entry.getLastModified()), 12, "Last modified"));
198 offset = pad(offset, 28, SPACE);
199
200 offset += write(checkLength(String.valueOf(entry.getUserId()), 6, "User ID"));
201 offset = pad(offset, 34, SPACE);
202
203 offset += write(checkLength(String.valueOf(entry.getGroupId()), 6, "Group ID"));
204 offset = pad(offset, 40, SPACE);
205
206 offset += write(checkLength(String.valueOf(Integer.toString(entry.getMode(), 8)), 8, "File mode"));
207 offset = pad(offset, 48, SPACE);
208
209
210 offset += write(checkLength(String.valueOf(entry.getLength() + (appendName ? nLength : 0)), 10, "Size"));
211 offset = pad(offset, 58, SPACE);
212 offset += write(ArArchiveEntry.TRAILER);
213
214 if (appendName) {
215 offset += write(eName);
216 }
217 return offset;
218 }
219
220 }