/*
2 nochump.util.zip.ZipOutput
3 Copyright (C) 2007 David Chang ([email protected])
4
5 This file is part of nochump.util.zip.
6
7 nochump.util.zip is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 nochump.util.zip is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with Foobar. If not, see <http://www.gnu.org/licenses/>.
19 */
20 package nochump.util.zip {
21
22 import flash.utils.Dictionary;
23 import flash.utils.Endian;
24 import flash.utils.ByteArray;
25
26 public class ZipOutput {
27
28 private var _entry:ZipEntry;
29 private var _entries:Array = [];
30 private var _names:Dictionary = new Dictionary();
31 private var _def:Deflater = new Deflater();
32 private var _crc:CRC32 = new CRC32();
33 private var _buf:ByteArray = new ByteArray();
34 private var _comment:String = "";
35
36 public function ZipOutput() {
37 _buf.endian = Endian.LITTLE_ENDIAN;
38 }
39
40 /**
41 * Returns the number of entries in this zip file.
42 */
43 public function get size():uint {
44 return _entries.length;
45 }
46
47 /**
48 * Returns the byte array of the finished zip.
49 */
50 public function get byteArray():ByteArray {
51 _buf.position = 0;
52 return _buf;
53 }
54
55 /**
56 *
57 */
58 public function set comment(value:String):void {
59 _comment = value;
60 }
61
62 public function putNextEntry(e:ZipEntry):void {
63 if(_entry != null) closeEntry();
64 // TODO:
65 if(e.dostime == 0) e.time = new Date().time;
66 if (e.method == -1) e.method = ZipConstants.DEFLATED; // use default method
67 switch(e.method) {
68 case ZipConstants.DEFLATED:
69 if (e.size == -1 || e.compressedSize == -1 || e.crc == 0) {
70 // store size, compressed size, and crc-32 in data descriptor
71 // immediately following the compressed entry data
72 e.flag = 8;
73 } else if (e.size != -1 && e.compressedSize != -1 && e.crc != 0) {
74 // store size, compressed size, and crc-32 in LOC header
75 e.flag = 0;
76 } else {
77 throw new ZipError("DEFLATED entry missing size, compressed size, or crc-32");
78 }
79 e.version = 20;
80 break;
81 case ZipConstants.STORED:
82 // compressed size, uncompressed size, and crc-32 must all be
83 // set for entries using STORED compression method
84 if (e.size == -1) {
85 e.size = e.compressedSize;
86 } else if (e.compressedSize == -1) {
87 e.compressedSize = e.size;
88 } else if (e.size != e.compressedSize) {
89 throw new ZipError("STORED entry where compressed != uncompressed size");
90 }
91 if (e.size == -1 || e.crc == 0) {
92 throw new ZipError("STORED entry missing size, compressed size, or crc-32");
93 }
94 e.version = 10;
95 e.flag = 0;
96 break;
97 default:
98 throw new ZipError("unsupported compression method");
99 }
100 e.offset = _buf.position;
101 if (_names[e.name] != null) {
102 throw new ZipError("duplicate entry: " + e.name);
103 } else {
104 _names[e.name] = e;
105 }
106 writeLOC(e);
107 _entries.push(e);
108 _entry = e;
109 }
110
111 public function write(b:ByteArray):void {
112 if (_entry == null) {
113 throw new ZipError("no current ZIP entry");
114 }
115 //*
116 switch (_entry.method) {
117 case ZipConstants.DEFLATED:
118 //super.write(b, off, len);
119 var cb:ByteArray = new ByteArray();
120 _def.setInput(b);
121 _def.deflate(cb);
122 _buf.writeBytes(cb);
123 // TODO: test if Deflater can deflate to the end of _buf (saves from using variable cb and an extra copy)
124 break;
125 case ZipConstants.STORED:
126 // TODO:
127 //if (written - locoff > _entry.size) {
128 // throw new ZipError("attempt to write past end of STORED entry");
129 //}
130 //out.write(b, off, len);
131 _buf.writeBytes(b);
132 break;
133 default:
134 throw new Error("invalid compression method");
135 }
136 /**/
137 _crc.update(b);
138 }
139
140 // check if this method is still necessary since we're not dealing with streams
141 // seems crc and whether a data descriptor i necessary is determined here
142 public function closeEntry():void {
143 var e:ZipEntry = _entry;
144 if(e != null) {
145 switch (e.method) {
146 case ZipConstants.DEFLATED:
147 if ((e.flag & 8) == 0) {
148 // verify size, compressed size, and crc-32 settings
149 if (e.size != _def.getBytesRead()) {
150 throw new ZipError("invalid entry size (expected " + e.size + " but got " + _def.getBytesRead() + " bytes)");
151 }
152 if (e.compressedSize != _def.getBytesWritten()) {
153 throw new ZipError("invalid entry compressed size (expected " + e.compressedSize + " but got " + _def.getBytesWritten() + " bytes)");
154 }
155 if (e.crc != _crc.getValue()) {
156 throw new ZipError( "invalid entry CRC-32 (expected 0x" + e.crc + " but got 0x" + _crc.getValue() + ")");
157 }
158 } else {
159 e.size = _def.getBytesRead();
160 e.compressedSize = _def.getBytesWritten();
161 e.crc = _crc.getValue();
162 writeEXT(e);
163 }
164 _def.reset();
165 break;
166 case ZipConstants.STORED:
167 // TODO:
168 break;
169 default:
170 throw new Error("invalid compression method");
171 }
172 _crc.reset();
173 _entry = null;
174 }
175 }
176
177 public function finish():void {
178 if(_entry != null) closeEntry();
179 if (_entries.length < 1) throw new ZipError("ZIP file must have at least one entry");
180 var off:uint = _buf.position;
181 // write central directory
182 for(var i:uint = 0; i < _entries.length; i++) {
183 writeCEN(_entries[i]);
184 }
185 writeEND(off, _buf.position - off);
186 }
187
188 private function writeLOC(e:ZipEntry):void {
189 _buf.writeUnsignedInt(ZipConstants.LOCSIG);
190 _buf.writeShort(e.version);
191 _buf.writeShort(e.flag);
192 _buf.writeShort(e.method);
193 _buf.writeUnsignedInt(e.dostime); // dostime
194 if ((e.flag & 8) == 8) {
195 // store size, uncompressed size, and crc-32 in data descriptor
196 // immediately following compressed entry data
197 _buf.writeUnsignedInt(0);
198 _buf.writeUnsignedInt(0);
199 _buf.writeUnsignedInt(0);
200 } else {
201 _buf.writeUnsignedInt(e.crc); // crc-32
202 _buf.writeUnsignedInt(e.compressedSize); // compressed size
203 _buf.writeUnsignedInt(e.size); // uncompressed size
204 }
205 _buf.writeShort(e.name.length);
206 _buf.writeShort(e.extra != null ? e.extra.length : 0);
207 _buf.writeUTFBytes(e.name);
208 if (e.extra != null) {
209 _buf.writeBytes(e.extra);
210 }
211 }
212
213 /*
214 * Writes extra data descriptor (EXT) for specified entry.
215 */
216 private function writeEXT(e:ZipEntry):void {
217 _buf.writeUnsignedInt(ZipConstants.EXTSIG); // EXT header signature
218 _buf.writeUnsignedInt(e.crc); // crc-32
219 _buf.writeUnsignedInt(e.compressedSize); // compressed size
220 _buf.writeUnsignedInt(e.size); // uncompressed size
221 }
222
223 /*
224 * Write central directory (CEN) header for specified entry.
225 * REMIND: add support for file attributes
226 */
227 private function writeCEN(e:ZipEntry):void {
228 _buf.writeUnsignedInt(ZipConstants.CENSIG); // CEN header signature
229 _buf.writeShort(e.version); // version made by
230 _buf.writeShort(e.version); // version needed to extract
231 _buf.writeShort(e.flag); // general purpose bit flag
232 _buf.writeShort(e.method); // compression method
233 _buf.writeUnsignedInt(e.dostime); // last modification time
234 _buf.writeUnsignedInt(e.crc); // crc-32
235 _buf.writeUnsignedInt(e.compressedSize); // compressed size
236 _buf.writeUnsignedInt(e.size); // uncompressed size
237 _buf.writeShort(e.name.length);
238 _buf.writeShort(e.extra != null ? e.extra.length : 0);
239 _buf.writeShort(e.comment != null ? e.comment.length : 0);
240 _buf.writeShort(0); // starting disk number
241 _buf.writeShort(0); // internal file attributes (unused)
242 _buf.writeUnsignedInt(0); // external file attributes (unused)
243 _buf.writeUnsignedInt(e.offset); // relative offset of local header
244 _buf.writeUTFBytes(e.name);
245 if (e.extra != null) {
246 _buf.writeBytes(e.extra);
247 }
248 if (e.comment != null) {
249 _buf.writeUTFBytes(e.comment);
250 }
251 }
252
253 /*
254 * Writes end of central directory (END) header.
255 */
256 private function writeEND(off:uint, len:uint):void {
257 _buf.writeUnsignedInt(ZipConstants.ENDSIG); // END record signature
258 _buf.writeShort(0); // number of this disk
259 _buf.writeShort(0); // central directory start disk
260 _buf.writeShort(_entries.length); // number of directory entries on disk
261 _buf.writeShort(_entries.length); // total number of directory entries
262 _buf.writeUnsignedInt(len); // length of central directory
263 _buf.writeUnsignedInt(off); // offset of central directory
264 _buf.writeUTF(_comment); // zip file comment
265 }
266
267 }
268
269 }