2025-01-22 16:18:30 +01:00

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);
}
}