194 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			194 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								 * Copyright (C)2005-2019 Haxe Foundation
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Permission is hereby granted, free of charge, to any person obtaining a
							 | 
						||
| 
								 | 
							
								 * copy of this software and associated documentation files (the "Software"),
							 | 
						||
| 
								 | 
							
								 * to deal in the Software without restriction, including without limitation
							 | 
						||
| 
								 | 
							
								 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
							 | 
						||
| 
								 | 
							
								 * and/or sell copies of the Software, and to permit persons to whom the
							 | 
						||
| 
								 | 
							
								 * Software is furnished to do so, subject to the following conditions:
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * The above copyright notice and this permission notice shall be included in
							 | 
						||
| 
								 | 
							
								 * all copies or substantial portions of the Software.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
							 | 
						||
| 
								 | 
							
								 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
							 | 
						||
| 
								 | 
							
								 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
							 | 
						||
| 
								 | 
							
								 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
							 | 
						||
| 
								 | 
							
								 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
							 | 
						||
| 
								 | 
							
								 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
							 | 
						||
| 
								 | 
							
								 * DEALINGS IN THE SOFTWARE.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package haxe.zip;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import haxe.ds.List;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Writer {
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										The next constant is required for computing the Central
							 | 
						||
| 
								 | 
							
										Directory Record(CDR) size. CDR consists of some fields
							 | 
						||
| 
								 | 
							
										of constant size and a filename. Constant represents
							 | 
						||
| 
								 | 
							
										total length of all fields with constant size for each
							 | 
						||
| 
								 | 
							
										file in archive
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									inline static var CENTRAL_DIRECTORY_RECORD_FIELDS_SIZE = 46;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
										The following constant is the total size of all fields
							 | 
						||
| 
								 | 
							
										of Local File Header. It's required for calculating
							 | 
						||
| 
								 | 
							
										offset of start of central directory record
							 | 
						||
| 
								 | 
							
									**/
							 | 
						||
| 
								 | 
							
									inline static var LOCAL_FILE_HEADER_FIELDS_SIZE = 30;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var o:haxe.io.Output;
							 | 
						||
| 
								 | 
							
									var files:List<{
							 | 
						||
| 
								 | 
							
										name:String,
							 | 
						||
| 
								 | 
							
										compressed:Bool,
							 | 
						||
| 
								 | 
							
										clen:Int,
							 | 
						||
| 
								 | 
							
										size:Int,
							 | 
						||
| 
								 | 
							
										crc:Int,
							 | 
						||
| 
								 | 
							
										date:Date,
							 | 
						||
| 
								 | 
							
										fields:haxe.io.Bytes
							 | 
						||
| 
								 | 
							
									}>;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function new(o:haxe.io.Output) {
							 | 
						||
| 
								 | 
							
										this.o = o;
							 | 
						||
| 
								 | 
							
										files = new List();
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function writeZipDate(date:Date) {
							 | 
						||
| 
								 | 
							
										var hour = date.getHours();
							 | 
						||
| 
								 | 
							
										var min = date.getMinutes();
							 | 
						||
| 
								 | 
							
										var sec = date.getSeconds() >> 1;
							 | 
						||
| 
								 | 
							
										o.writeUInt16((hour << 11) | (min << 5) | sec);
							 | 
						||
| 
								 | 
							
										var year = date.getFullYear() - 1980;
							 | 
						||
| 
								 | 
							
										var month = date.getMonth() + 1;
							 | 
						||
| 
								 | 
							
										var day = date.getDate();
							 | 
						||
| 
								 | 
							
										o.writeUInt16((year << 9) | (month << 5) | day);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function writeEntryHeader(f:Entry) {
							 | 
						||
| 
								 | 
							
										var o = this.o;
							 | 
						||
| 
								 | 
							
										var flags = 0;
							 | 
						||
| 
								 | 
							
										if (f.extraFields != null) {
							 | 
						||
| 
								 | 
							
											for (e in f.extraFields)
							 | 
						||
| 
								 | 
							
												switch (e) {
							 | 
						||
| 
								 | 
							
													case FUtf8:
							 | 
						||
| 
								 | 
							
														flags |= 0x800;
							 | 
						||
| 
								 | 
							
													default:
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										o.writeInt32(0x04034B50);
							 | 
						||
| 
								 | 
							
										o.writeUInt16(0x0014); // version
							 | 
						||
| 
								 | 
							
										o.writeUInt16(flags); // flags
							 | 
						||
| 
								 | 
							
										if (f.data == null) {
							 | 
						||
| 
								 | 
							
											f.fileSize = 0;
							 | 
						||
| 
								 | 
							
											f.dataSize = 0;
							 | 
						||
| 
								 | 
							
											f.crc32 = 0;
							 | 
						||
| 
								 | 
							
											f.compressed = false;
							 | 
						||
| 
								 | 
							
											f.data = haxe.io.Bytes.alloc(0);
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											if (f.crc32 == null) {
							 | 
						||
| 
								 | 
							
												if (f.compressed)
							 | 
						||
| 
								 | 
							
													throw "CRC32 must be processed before compression";
							 | 
						||
| 
								 | 
							
												f.crc32 = haxe.crypto.Crc32.make(f.data);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											if (!f.compressed)
							 | 
						||
| 
								 | 
							
												f.fileSize = f.data.length;
							 | 
						||
| 
								 | 
							
											f.dataSize = f.data.length;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										o.writeUInt16(f.compressed ? 8 : 0);
							 | 
						||
| 
								 | 
							
										writeZipDate(f.fileTime);
							 | 
						||
| 
								 | 
							
										o.writeInt32(f.crc32);
							 | 
						||
| 
								 | 
							
										o.writeInt32(f.dataSize);
							 | 
						||
| 
								 | 
							
										o.writeInt32(f.fileSize);
							 | 
						||
| 
								 | 
							
										o.writeUInt16(f.fileName.length);
							 | 
						||
| 
								 | 
							
										var e = new haxe.io.BytesOutput();
							 | 
						||
| 
								 | 
							
										if (f.extraFields != null) {
							 | 
						||
| 
								 | 
							
											for (f in f.extraFields)
							 | 
						||
| 
								 | 
							
												switch (f) {
							 | 
						||
| 
								 | 
							
													case FInfoZipUnicodePath(name, crc):
							 | 
						||
| 
								 | 
							
														var namebytes = haxe.io.Bytes.ofString(name);
							 | 
						||
| 
								 | 
							
														e.writeUInt16(0x7075);
							 | 
						||
| 
								 | 
							
														e.writeUInt16(namebytes.length + 5);
							 | 
						||
| 
								 | 
							
														e.writeByte(1); // version
							 | 
						||
| 
								 | 
							
														e.writeInt32(crc);
							 | 
						||
| 
								 | 
							
														e.write(namebytes);
							 | 
						||
| 
								 | 
							
													case FUnknown(tag, bytes):
							 | 
						||
| 
								 | 
							
														e.writeUInt16(tag);
							 | 
						||
| 
								 | 
							
														e.writeUInt16(bytes.length);
							 | 
						||
| 
								 | 
							
														e.write(bytes);
							 | 
						||
| 
								 | 
							
													case FUtf8:
							 | 
						||
| 
								 | 
							
														// nothing
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										var ebytes = e.getBytes();
							 | 
						||
| 
								 | 
							
										o.writeUInt16(ebytes.length);
							 | 
						||
| 
								 | 
							
										o.writeString(f.fileName);
							 | 
						||
| 
								 | 
							
										o.write(ebytes);
							 | 
						||
| 
								 | 
							
										files.add({
							 | 
						||
| 
								 | 
							
											name: f.fileName,
							 | 
						||
| 
								 | 
							
											compressed: f.compressed,
							 | 
						||
| 
								 | 
							
											clen: f.data.length,
							 | 
						||
| 
								 | 
							
											size: f.fileSize,
							 | 
						||
| 
								 | 
							
											crc: f.crc32,
							 | 
						||
| 
								 | 
							
											date: f.fileTime,
							 | 
						||
| 
								 | 
							
											fields: ebytes
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function write(files:List<Entry>) {
							 | 
						||
| 
								 | 
							
										for (f in files) {
							 | 
						||
| 
								 | 
							
											writeEntryHeader(f);
							 | 
						||
| 
								 | 
							
											o.writeFullBytes(f.data, 0, f.data.length);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										writeCDR();
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function writeCDR() {
							 | 
						||
| 
								 | 
							
										var cdr_size = 0;
							 | 
						||
| 
								 | 
							
										var cdr_offset = 0;
							 | 
						||
| 
								 | 
							
										for (f in files) {
							 | 
						||
| 
								 | 
							
											var namelen = f.name.length;
							 | 
						||
| 
								 | 
							
											var extraFieldsLength = f.fields.length;
							 | 
						||
| 
								 | 
							
											o.writeInt32(0x02014B50); // header
							 | 
						||
| 
								 | 
							
											o.writeUInt16(0x0014); // version made-by
							 | 
						||
| 
								 | 
							
											o.writeUInt16(0x0014); // version
							 | 
						||
| 
								 | 
							
											o.writeUInt16(0); // flags
							 | 
						||
| 
								 | 
							
											o.writeUInt16(f.compressed ? 8 : 0);
							 | 
						||
| 
								 | 
							
											writeZipDate(f.date);
							 | 
						||
| 
								 | 
							
											o.writeInt32(f.crc);
							 | 
						||
| 
								 | 
							
											o.writeInt32(f.clen);
							 | 
						||
| 
								 | 
							
											o.writeInt32(f.size);
							 | 
						||
| 
								 | 
							
											o.writeUInt16(namelen);
							 | 
						||
| 
								 | 
							
											o.writeUInt16(extraFieldsLength);
							 | 
						||
| 
								 | 
							
											o.writeUInt16(0); // comment length always 0
							 | 
						||
| 
								 | 
							
											o.writeUInt16(0); // disk number start
							 | 
						||
| 
								 | 
							
											o.writeUInt16(0); // internal file attributes
							 | 
						||
| 
								 | 
							
											o.writeInt32(0); // external file attributes
							 | 
						||
| 
								 | 
							
											o.writeInt32(cdr_offset); // relative offset of local header
							 | 
						||
| 
								 | 
							
											o.writeString(f.name);
							 | 
						||
| 
								 | 
							
											o.write(f.fields);
							 | 
						||
| 
								 | 
							
											cdr_size += CENTRAL_DIRECTORY_RECORD_FIELDS_SIZE + namelen + extraFieldsLength;
							 | 
						||
| 
								 | 
							
											cdr_offset += LOCAL_FILE_HEADER_FIELDS_SIZE + namelen + extraFieldsLength + f.clen;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										// end of central dir signature
							 | 
						||
| 
								 | 
							
										o.writeInt32(0x06054B50);
							 | 
						||
| 
								 | 
							
										// number of this disk
							 | 
						||
| 
								 | 
							
										o.writeUInt16(0);
							 | 
						||
| 
								 | 
							
										// number of the disk with the start of the central directory
							 | 
						||
| 
								 | 
							
										o.writeUInt16(0);
							 | 
						||
| 
								 | 
							
										// total number of entries in the central directory on this disk
							 | 
						||
| 
								 | 
							
										o.writeUInt16(files.length);
							 | 
						||
| 
								 | 
							
										// total number of entries in the central directory
							 | 
						||
| 
								 | 
							
										o.writeUInt16(files.length);
							 | 
						||
| 
								 | 
							
										// size of the central directory record
							 | 
						||
| 
								 | 
							
										o.writeInt32(cdr_size);
							 | 
						||
| 
								 | 
							
										// offset of start of central directory with respect to the starting disk number
							 | 
						||
| 
								 | 
							
										o.writeInt32(cdr_offset);
							 | 
						||
| 
								 | 
							
										// .ZIP file comment length
							 | 
						||
| 
								 | 
							
										o.writeUInt16(0);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |