1 /* === S Y N F I G ========================================================= */
2 /*!	\file filecontainerzip.cpp
3 **	\brief FileContainerZip
4 **
5 **	$Id$
6 **
7 **	\legal
8 **	......... ... 2013 Ivan Mahonin
9 **
10 **	This package is free software; you can redistribute it and/or
11 **	modify it under the terms of the GNU General Public License as
12 **	published by the Free Software Foundation; either version 2 of
13 **	the License, or (at your option) any later version.
14 **
15 **	This package is distributed in the hope that it will be useful,
16 **	but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **	General Public License for more details.
19 **	\endlegal
20 */
21 /* ========================================================================= */
22 
23 /* === H E A D E R S ======================================================= */
24 
25 #ifdef USING_PCH
26 #	include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #	include <config.h>
30 #endif
31 
32 #include <cstring>
33 #include <stdint.h>
34 #include <cstddef>
35 
36 #include <libxml++/libxml++.h>
37 
38 #include <ETL/stringf>
39 
40 #include "zstreambuf.h"
41 
42 #include "filecontainerzip.h"
43 
44 #endif
45 
46 /* === U S I N G =========================================================== */
47 
48 using namespace std;
49 using namespace etl;
50 using namespace synfig;
51 
52 /* === M A C R O S ========================================================= */
53 
54 /* === G L O B A L S ======================================================= */
55 
56 /* === P R O C E D U R E S ================================================= */
57 
58 /* === M E T H O D S ======================================================= */
59 
60 namespace synfig
61 {
62 	namespace FileContainerZip_InternalStructs
63 	{
64 
65 		#pragma pack(push, 1)
66 
67 		struct DOSTimestamp
68 		{
69 			uint16_t dos_time;
70 			uint16_t dos_date;
71 
DOSTimestampsynfig::FileContainerZip_InternalStructs::DOSTimestamp72 			inline DOSTimestamp():
73 				dos_time(0), dos_date(0) { }
74 
DOSTimestampsynfig::FileContainerZip_InternalStructs::DOSTimestamp75 			inline explicit DOSTimestamp(uint16_t dos_time, uint16_t dos_date):
76 				dos_time(dos_time), dos_date(dos_date) { }
77 
DOSTimestampsynfig::FileContainerZip_InternalStructs::DOSTimestamp78 			inline explicit DOSTimestamp(time_t time)
79 			{
80 				tm *t = localtime(&time);
81 				dos_time = ((t->tm_sec  & 0x3f) >>  1)
82 						 | ((t->tm_min  & 0x3f) <<  5)
83 						 | ((t->tm_hour & 0x1f) << 11);
84 				dos_date = ((t->tm_mday & 0x1f) <<  0)
85 						 | ((t->tm_mon  & 0x0f) <<  5)
86 						 | ((t->tm_year & 0x7f) <<  9);
87 			}
88 
get_timesynfig::FileContainerZip_InternalStructs::DOSTimestamp89 			inline time_t get_time()
90 			{
91 				tm t;
92 				memset(&t, 0, sizeof(t));
93 				t.tm_sec  = (dos_time <<  1) & 0x3f;
94 				t.tm_min  = (dos_time >>  5) & 0x3f;
95 				t.tm_hour = (dos_time >> 11) & 0x1f;
96 				t.tm_mday = (dos_time >>  0) & 0x1f;
97 				t.tm_mon  = (dos_time >>  5) & 0x0f;
98 				t.tm_year = (dos_time >>  9) & 0x7f;
99 				return mktime(&t);
100 			}
101 		};
102 
103 		struct LocalFileHeader
104 		{
105 			enum { valid_signature__ = 0x04034b50 };
106 
107 			uint32_t signature;			//!< Local file header signature = 0x04034b50 (read as a little-endian number)
108 			uint16_t version;			//!< Version needed to extract (minimum)
109 			uint16_t flags;				//!< General purpose bit flag
110 			uint16_t compression;		//!< Compression method
111 			uint16_t modification_time;	//!< File last modification time
112 			uint16_t modification_date; //!< File last modification date
113 			uint32_t crc32;				//!< CRC-32
114 			uint32_t compressed_size;	//!< Compressed size
115 			uint32_t uncompressed_size;	//!< Uncompressed size
116 			uint16_t filename_length;	//!< File name length (n)
117 			uint16_t extrafield_length; //!< Extra field length (m)
118 			// next:
119 			//   filename - n bytes
120 			//   extrafield - m bytes
121 			//   file data
122 			//   optional LocalFileDataDescriptor (if bit 3 (0x08) set in flags)
123 
LocalFileHeadersynfig::FileContainerZip_InternalStructs::LocalFileHeader124 			inline LocalFileHeader()
125 			{
126 				memset(this, 0, sizeof(*this));
127 				signature = valid_signature__;
128 			}
129 		};
130 
131 		struct LocalFileHeaderOverwrite
132 		{
133 			uint32_t crc32;				//!< CRC-32
134 			uint32_t compressed_size;	//!< Compressed size
135 			uint32_t uncompressed_size;	//!< Uncompressed size
136 
LocalFileHeaderOverwritesynfig::FileContainerZip_InternalStructs::LocalFileHeaderOverwrite137 			inline LocalFileHeaderOverwrite()
138 			{
139 				memset(this, 0, sizeof(*this));
140 			}
141 
offset_from_headersynfig::FileContainerZip_InternalStructs::LocalFileHeaderOverwrite142 			inline static size_t offset_from_header()
143 			{
144 				const static LocalFileHeader dummy;
145 				return (size_t)((const char *)&dummy.crc32 - (const char *)&dummy);
146 			}
147 		};
148 
149 		struct CentralDirectoryFileHeader
150 		{
151 			enum { valid_signature__ = 0x02014b50 };
152 
153 			uint32_t signature;			//!< Central directory file header signature = 0x02014b50
154 			uint16_t version;			//!< Version made by
155 			uint16_t min_version;		//!< Version needed to extract (minimum)
156 			uint16_t flags;				//!< General purpose bit flag
157 			uint16_t compression;		//!< Compression method
158 			uint16_t modification_time;	//!< File last modification time
159 			uint16_t modification_date; //!< File last modification date
160 			uint32_t crc32;				//!< CRC-32
161 			uint32_t compressed_size;	//!< Compressed size
162 			uint32_t uncompressed_size;	//!< Uncompressed size
163 			uint16_t filename_length;	//!< File name length (n)
164 			uint16_t extrafield_length;	//!< Extra field length (m)
165 			uint16_t filecomment_length;//!< File comment length (k)
166 			uint16_t disk_number;		//!< Disk number where file starts
167 			uint16_t internal_attrs;	//!< Internal file attributes
168 			uint32_t external_attrs;	//!< External file attributes
169 			uint32_t offset;			//!< Relative offset of local file header.
170 										//!< This is the number of bytes between the
171 										//!< start of the first disk on which the
172 										//!< file occurs, and the start of the local
173 										//!< file header. This allows software reading
174 										//!< the central directory to locate the
175 										//!< position of the file inside the .ZIP file.
176 			// next:
177 			//   filename - n bytes
178 			//   extrafield - m bytes
179 			//   filecomment - k bytes
180 			//   next CentralDirectorySignature
181 
CentralDirectoryFileHeadersynfig::FileContainerZip_InternalStructs::CentralDirectoryFileHeader182 			inline CentralDirectoryFileHeader()
183 			{
184 				memset(this, 0, sizeof(*this));
185 				signature = valid_signature__;
186 			}
187 		};
188 
189 		struct EndOfCentralDirectory
190 		{
191 			enum { valid_signature__ = 0x06054b50 };
192 
193 			uint32_t signature;			//!< End of central directory signature = 0x06054b50
194 			uint16_t current_disk;		//!< Number of this disk
195 			uint16_t first_disk;		//!< Disk where central directory starts
196 			uint16_t current_records;	//!< Number of central directory records on this disk
197 			uint16_t total_records;		//!< Total number of central directory records
198 			uint32_t size;				//!< Size of central directory (bytes)
199 			uint32_t offset;			//!< Offset of start of central directory, relative to start of archive
200 			uint16_t comment_length;	//!< Comment length (n)
201 			// next:
202 			//   comment - n bytes
203 			//   end of file
204 
EndOfCentralDirectorysynfig::FileContainerZip_InternalStructs::EndOfCentralDirectory205 			inline EndOfCentralDirectory()
206 			{
207 				memset(this, 0, sizeof(*this));
208 				signature = valid_signature__;
209 			}
210 		};
211 
212 		#pragma pack(pop)
213 	}
214 }
215 
216 using namespace synfig::FileContainerZip_InternalStructs;
217 
split_name()218 void FileContainerZip::FileInfo::split_name()
219 {
220 	size_t pos = name.rfind('/');
221 	if (pos == String::npos || pos == 0)
222 	{
223 		name_part_directory.clear();
224 		name_part_localname = name;
225 	}
226 	else
227 	{
228 		name_part_directory = name.substr(0, pos);
229 		name_part_localname = name.substr(pos + 1);
230 	}
231 }
232 
FileContainerZip()233 FileContainerZip::FileContainerZip():
234 storage_file_(NULL),
235 prev_storage_size_(0),
236 file_reading_whole_container_(false),
237 file_reading_(false),
238 file_writing_(false),
239 file_processed_size_(0),
240 changed_(false)
241 { }
242 
~FileContainerZip()243 FileContainerZip::~FileContainerZip() { close(); }
244 
crc32(unsigned int previous_crc,const void * buffer,size_t size)245 unsigned int FileContainerZip::crc32(unsigned int previous_crc, const void *buffer, size_t size)
246 {
247 	static const unsigned int table[] = {
248 	    0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
249 	    0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
250 	    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
251 	    0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
252 	    0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
253 	    0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
254 	    0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
255 	    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
256 	    0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
257 	    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
258 	    0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
259 	    0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
260 	    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
261 	    0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
262 	    0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
263 	    0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
264 	    0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
265 	    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
266 	    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
267 	    0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
268 	    0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
269 	    0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
270 	    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
271 	    0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
272 	    0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
273 	    0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
274 	    0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
275 	    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
276 	    0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
277 	    0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
278 	    0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
279 	    0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
280 	};
281 
282 	unsigned int crc = previous_crc ^ 0xFFFFFFFFUL;
283 	unsigned char *char_buf = (unsigned char *)buffer;
284 	size_t remain = size;
285 	while (remain--)
286 		crc = table[(crc ^ *char_buf++) & 0xFF] ^ (crc >> 8);
287 	return crc ^ 0xFFFFFFFFUL;
288 }
289 
encode_history(const FileContainerZip::HistoryRecord & history_record)290 String FileContainerZip::encode_history(const FileContainerZip::HistoryRecord &history_record)
291 {
292 	xmlpp::Document document;
293 	document.
294 		create_root_node("history")->
295 		add_child("prev_storage_size")->
296 		set_child_text(strprintf("%lld", history_record.prev_storage_size));
297 	return document.write_to_string_formatted();
298 }
299 
decode_history(const String & comment)300 FileContainerZip::HistoryRecord FileContainerZip::decode_history(const String &comment)
301 {
302 	HistoryRecord history_record;
303 	xmlpp::DomParser parser;
304 	parser.parse_memory(comment);
305 	if(parser)
306 	{
307 		xmlpp::Element *root = parser.get_document()->get_root_node();
308 		if (root->get_name() == "history")
309 		{
310 			xmlpp::Element::NodeList list = root->get_children();
311 			for(xmlpp::Element::NodeList::iterator i = list.begin(); i != list.end(); i++)
312 			{
313 				if ((*i)->get_name() == "prev_storage_size")
314 				{
315 					String s;
316 					xmlpp::Element::NodeList list = (*i)->get_children();
317 					for(xmlpp::Element::NodeList::iterator j = list.begin(); j != list.end(); j++)
318 						if (dynamic_cast<xmlpp::TextNode*>(*j))
319 							s += dynamic_cast<xmlpp::TextNode*>(*j)->get_content();
320 					history_record.prev_storage_size = strtoll(s.c_str(), NULL, 10);
321 					if (history_record.prev_storage_size < 0)
322 						history_record.prev_storage_size = 0;
323 				}
324 			}
325 		}
326 	}
327 	return history_record;
328 }
329 
read_history(std::list<HistoryRecord> & list,FILE * f,file_size_t size)330 void FileContainerZip::read_history(std::list<HistoryRecord> &list, FILE *f, file_size_t size)
331 {
332 	if (size < (long int)sizeof(EndOfCentralDirectory))
333 		return;
334 
335 	// search "end of central directory" record
336 
337 	char buffer[(1 << 16) + sizeof(EndOfCentralDirectory)];
338 	String comment;
339 	size_t read_size = size > (long int)sizeof(buffer)
340 					 ? sizeof(buffer) : (size_t)size;
341 	fseek(f, (long int)size - (long int)read_size, SEEK_SET);
342 	read_size = fread(&buffer, 1, read_size, f);
343 	bool found = false;
344 	HistoryRecord history_record;
345 
346 	for(int i = read_size - sizeof(EndOfCentralDirectory); i >= 0; i--)
347 	{
348 		EndOfCentralDirectory *e = (EndOfCentralDirectory*)&buffer[i];
349 		if (e->signature == EndOfCentralDirectory::valid_signature__
350 		 && e->comment_length == (uint16_t)(read_size - sizeof(EndOfCentralDirectory) - i))
351 		{
352 			// found
353 			if (e->comment_length > 0)
354 			{
355 				comment = String(buffer + i + sizeof(EndOfCentralDirectory), e->comment_length);
356 				history_record = decode_history(comment);
357 				history_record.storage_size = size;
358 				found = true;
359 			}
360 			break;
361 		}
362 	}
363 
364 	if (found)
365 	{
366 		list.front() = history_record;
367 		if (history_record.prev_storage_size > 0 && history_record.prev_storage_size < size) {
368 			list.push_front(HistoryRecord(0, history_record.prev_storage_size));
369 			read_history(list, f, history_record.prev_storage_size);
370 		}
371 	}
372 }
373 
read_history(const String & container_filename)374 std::list<FileContainerZip::HistoryRecord> FileContainerZip::read_history(const String &container_filename)
375 {
376 	std::list<HistoryRecord> list;
377 
378 	FILE *f = fopen(container_filename.c_str(), "rb");
379 	if (f == NULL) return list;
380 
381 	fseek(f, 0, SEEK_END);
382 	long int size = ftell(f);
383 	if (size >= (long int)sizeof(EndOfCentralDirectory))
384 	{
385 		list.push_front(HistoryRecord(0, size));
386 		read_history(list, f, size);
387 	}
388 
389 	fclose(f);
390 	return list;
391 }
392 
create(const String & container_filename)393 bool FileContainerZip::create(const String &container_filename)
394 {
395 	if (is_opened()) return false;
396 	storage_file_ = fopen(fix_slashes(container_filename).c_str(), "w+b");
397 	if (is_opened()) changed_ = true;
398 	return is_opened();
399 }
400 
open_from_history(const String & container_filename,file_size_t truncate_storage_size)401 bool FileContainerZip::open_from_history(const String &container_filename, file_size_t truncate_storage_size) {
402 	if (is_opened()) return false;
403 	FILE *f = fopen(fix_slashes(container_filename).c_str(), "r+b");
404 	if (f == NULL) return false;
405 
406 	// check size of file
407 	fseek(f, 0, SEEK_END);
408 	long int filesize = ftell(f);
409 	long int actual_filesize = filesize;
410 	if (filesize < (long int)sizeof(EndOfCentralDirectory))
411 		{ fclose(f); return false; }
412 
413 	if (truncate_storage_size > 0 && truncate_storage_size < filesize)
414 		filesize = (long int)truncate_storage_size;
415 
416 	char buffer[(1 << 16) + sizeof(EndOfCentralDirectory)];
417 
418 	// search "end of central directory" record
419 	EndOfCentralDirectory ecd;
420 	size_t read_size = filesize > (long int)sizeof(buffer)
421 					 ? sizeof(buffer) : (size_t)filesize;
422 	fseek(f, filesize - (long int)read_size, SEEK_SET);
423 	read_size = fread(&buffer, 1, read_size, f);
424 	bool found = false;
425 	for(int i = read_size - sizeof(EndOfCentralDirectory); i >= 0; i--)
426 	{
427 		EndOfCentralDirectory *e = (EndOfCentralDirectory*)&buffer[i];
428 		if (e->signature == EndOfCentralDirectory::valid_signature__
429 		 && e->comment_length == (uint16_t)(read_size - sizeof(EndOfCentralDirectory) - i))
430 		{
431 			ecd = *e;
432 			found = true;
433 			break;
434 		}
435 	}
436 	if (!found)
437 		{ fclose(f); return false; }
438 
439 	// read "central directory"
440 	FileMap files;
441 	fseek(f, ecd.offset, SEEK_SET);
442 	for(int i = 0; i < ecd.current_records; i++)
443 	{
444 		CentralDirectoryFileHeader cdfh;
445 		if (sizeof(cdfh) != fread(&cdfh, 1, sizeof(cdfh), f))
446 			{ fclose(f); return false; }
447 
448 		// read name, comment and extrafield
449 		size_t extra_size = cdfh.filename_length
450 		                  + cdfh.filecomment_length
451 		                  + cdfh.extrafield_length;
452 		if (extra_size != fread(buffer, 1, extra_size, f))
453 			{ fclose(f); return false; }
454 
455 		if (cdfh.filename_length > 0
456 		 && (cdfh.flags & 0x0071) == 0 )
457 		{
458 			FileInfo info;
459 			if (buffer[cdfh.filename_length - 1] == '/')
460 			{
461 				info.name = String(buffer, buffer + cdfh.filename_length - 1);
462 				info.is_directory = true;
463 			}
464 			else
465 			{
466 				info.name = String(buffer, buffer + cdfh.filename_length);
467 			}
468 
469 			info.directory_saved = info.is_directory;
470 			info.size = cdfh.compressed_size;
471 			info.header_offset = cdfh.offset;
472 			info.compression = cdfh.compression;
473 			info.crc32 = cdfh.crc32;
474 			info.time = DOSTimestamp(cdfh.modification_time, cdfh.modification_date).get_time();
475 			info.split_name();
476 			files[info.name] = info;
477 		}
478 	}
479 
480 	// create directories
481 	for(FileMap::iterator i = files.begin(); i != files.end();)
482 	{
483 		if (!i->second.name_part_directory.empty()
484 		 && files.find( i->second.name_part_directory ) == files.end())
485 		{
486 			FileInfo info;
487 			info.name = i->second.name_part_directory;
488 			info.is_directory = true;
489 			info.time = time(NULL);
490 			info.split_name();
491 			files[ info.name ] = info;
492 			i = files.begin();
493 		}
494 		else i++;
495 	}
496 
497 	// loaded
498 	fseek(f, 0, SEEK_END);
499 	storage_file_ = f;
500 	files_.swap( files );
501 	prev_storage_size_ = actual_filesize;
502 	file_reading_ = false;
503 	file_writing_ = false;
504 	changed_ = false;
505 	return true;
506 }
507 
open(const String & container_filename)508 bool FileContainerZip::open(const String &container_filename)
509 {
510 	return open_from_history(container_filename);
511 }
512 
save()513 bool FileContainerZip::save()
514 {
515 	if (file_is_opened()) return false;
516 	if (!changed_) return true;
517 
518 	fseek(storage_file_, 0, SEEK_END);
519 
520 	// write headers of new directories
521 	for(FileMap::iterator i = files_.begin(); i != files_.end(); i++)
522 	{
523 		FileInfo &info = i->second;
524 		if (info.is_directory && !info.directory_saved)
525 		{
526 			LocalFileHeader lfh;
527 			lfh.version = 20;
528 			lfh.filename_length = info.name.size() + 1;
529 			DOSTimestamp dos_timestamp(info.time);
530 			lfh.modification_time = dos_timestamp.dos_time;
531 			lfh.modification_date = dos_timestamp.dos_date;
532 
533 			info.header_offset = ftell(storage_file_);
534 			if (sizeof(lfh) != fwrite(&lfh, 1, sizeof(lfh), storage_file_))
535 				return false;
536 			if (info.name.size() != fwrite(info.name.c_str(), 1, info.name.size(), storage_file_))
537 				return false;
538 			if ((int)'/' != fputc('/', storage_file_))
539 				return false;
540 
541 			info.directory_saved = true;
542 		}
543 	}
544 
545 	// write central directory
546 	uint32_t central_directory_offset = (uint32_t)ftell(storage_file_);
547 	for(FileMap::iterator i = files_.begin(); i != files_.end(); i++)
548 	{
549 		FileInfo &info = i->second;
550 		CentralDirectoryFileHeader cdfh;
551 		cdfh.min_version = 20;
552 		cdfh.offset = info.header_offset;
553 		cdfh.compressed_size = cdfh.uncompressed_size = info.size;
554 		cdfh.crc32 = info.crc32;
555 		cdfh.filename_length = (uint16_t)info.name.size();
556 		if (info.is_directory)
557 			cdfh.filename_length++;
558 		DOSTimestamp dos_timestamp(info.time);
559 		cdfh.modification_time = dos_timestamp.dos_time;
560 		cdfh.modification_date = dos_timestamp.dos_date;
561 
562 		// write header
563 		if (sizeof(cdfh) != fwrite(&cdfh, 1, sizeof(cdfh), storage_file_))
564 			return false;
565 
566 		// write name
567 		if (info.name.size() != fwrite(info.name.c_str(), 1, info.name.size(), storage_file_))
568 			return false;
569 		if (info.is_directory)
570 			if ((int)'/' != fputc('/', storage_file_))
571 				return false;
572 	}
573 
574 	// end of central directory
575 	EndOfCentralDirectory ecd;
576 	ecd.offset = central_directory_offset;
577 	ecd.current_records = ecd.total_records = files_.size();
578 	ecd.size = ftell(storage_file_) - central_directory_offset;
579 	String comment = encode_history(HistoryRecord(prev_storage_size_));
580 	ecd.comment_length = comment.size();
581 
582 	// write header
583 	if (sizeof(ecd) != fwrite(&ecd, 1, sizeof(ecd), storage_file_))
584 		return false;
585 
586 	// write comment
587 	if (ecd.comment_length > 0
588 	 && ecd.comment_length != fwrite(comment.c_str(), 1, ecd.comment_length, storage_file_))
589 	{
590 		return false;
591 	}
592 
593 	prev_storage_size_ = ftell(storage_file_);
594 	fflush(storage_file_);
595 	changed_ = false;
596 	return true;
597 }
598 
close()599 void FileContainerZip::close()
600 {
601 	if (!is_opened()) return;
602 
603 	// close opened file if need
604 	file_close();
605 
606 	save();
607 
608 	// close storage file and clead variables
609 	fclose(storage_file_);
610 	storage_file_ = NULL;
611 	files_.clear();
612 	prev_storage_size_ = 0;
613 	file_reading_ = false;
614 	file_writing_ = false;
615 	changed_ = false;
616 }
617 
is_opened()618 bool FileContainerZip::is_opened()
619 {
620 	return storage_file_ != NULL;
621 }
622 
is_file(const String & filename)623 bool FileContainerZip::is_file(const String &filename)
624 {
625 	if (!is_opened()) return false;
626 	FileMap::const_iterator i = files_.find(fix_slashes(filename));
627 	return i != files_.end() && !i->second.is_directory;
628 }
629 
is_directory(const String & filename)630 bool FileContainerZip::is_directory(const String &filename)
631 {
632 	if (!is_opened()) return false;
633 	if (filename.empty()) return true;
634 	FileMap::const_iterator i = files_.find(fix_slashes(filename));
635 	return i != files_.end() && i->second.is_directory;
636 }
637 
directory_check_name(const String & dirname)638 bool FileContainerZip::directory_check_name(const String &dirname)
639 {
640 	return dirname.size() <= (1 << 16) - 2 - sizeof(CentralDirectoryFileHeader);
641 }
642 
directory_create(const String & dirname)643 bool FileContainerZip::directory_create(const String &dirname)
644 {
645 	if (!is_opened()) return false;
646 	if (is_file(dirname)) return false;
647 	if (is_directory(dirname)) return true;
648 	if (!directory_check_name(dirname)) return false;
649 
650 	FileInfo info;
651 	info.name = fix_slashes(dirname);
652 	info.split_name();
653 	info.is_directory = true;
654 	info.time = time(NULL);
655 	if (info.name_part_localname.empty()
656 	 || !is_directory(info.name_part_directory)) return false;
657 
658 	changed_ = true;
659 	files_[info.name] = info;
660 	return true;
661 }
662 
directory_scan(const String & dirname,FileList & out_files)663 bool FileContainerZip::directory_scan(const String &dirname, FileList &out_files)
664 {
665 	out_files.clear();
666 	if (!is_directory(dirname)) return false;
667 	for(FileMap::iterator i = files_.begin(); i != files_.end(); i++)
668 		if (i->second.name_part_directory == fix_slashes(dirname))
669 			out_files.push_back(i->second.name_part_localname);
670 	return true;
671 }
672 
file_remove(const String & filename)673 bool FileContainerZip::file_remove(const String &filename)
674 {
675 	if (is_directory(filename))
676 	{
677 		FileList files;
678 		directory_scan(filename, files);
679 		if (!files.empty()) return false;
680 		changed_ = true;
681 		files_.erase(fix_slashes(filename));
682 	}
683 	else
684 	if (is_file(filename))
685 	{
686 		if (file_is_opened() && file_->first == fix_slashes(filename))
687 			return false;
688 		changed_ = true;
689 		files_.erase(fix_slashes(filename));
690 	}
691 	return true;
692 }
693 
file_check_name(const String & filename)694 bool FileContainerZip::file_check_name(const String &filename)
695 {
696 	return filename.size() <= (1 << 16) - 1 - sizeof(CentralDirectoryFileHeader);
697 }
698 
file_open_read_whole_container()699 bool FileContainerZip::file_open_read_whole_container()
700 {
701 	if (!is_opened() || file_is_opened()) return false;
702 	fseek(storage_file_, 0, SEEK_SET);
703 	file_reading_whole_container_ = true;
704 	file_processed_size_ = 0;
705 	return true;
706 }
707 
file_open_read(const String & filename)708 bool FileContainerZip::file_open_read(const String &filename)
709 {
710 	if (!is_opened() || file_is_opened()) return false;
711 	file_ = files_.find(fix_slashes(filename));
712 	if (file_ == files_.end() || file_->second.is_directory)
713 		return false;
714 
715 	// read header
716 	LocalFileHeader lfh;
717 	fseek(storage_file_, file_->second.header_offset, SEEK_SET);
718 	if (sizeof(lfh) != fread(&lfh, 1, sizeof(lfh), storage_file_))
719 		return false;
720 	if (lfh.signature != LocalFileHeader::valid_signature__)
721 		return false;
722 
723 	// seek to file begin
724 	fseek(storage_file_, lfh.filename_length + lfh.extrafield_length, SEEK_CUR);
725 	file_reading_ = true;
726 	file_processed_size_ = 0;
727 	return true;
728 }
729 
file_open_write(const String & filename)730 bool FileContainerZip::file_open_write(const String &filename)
731 {
732 	if (!is_opened() || file_is_opened()) return false;
733 	if (!file_check_name(filename)) return false;
734 
735 	file_ = files_.find(fix_slashes(filename));
736 
737 	FileInfo new_info;
738 	if (file_ == files_.end())
739 	{
740 		// create new file
741 		new_info.name = fix_slashes(filename);
742 		new_info.split_name();
743 		if (new_info.name_part_localname.empty()
744 		 || !is_directory(new_info.name_part_directory)) return false;
745 	}
746 	else
747 	if (file_->second.is_directory)
748 		return false;
749 
750 	FileInfo &info = file_ == files_.end() ? new_info : file_->second;
751 
752 	// write header
753 	LocalFileHeader lfh;
754 	time_t t = time(NULL);
755 	lfh.version = 20;
756 	lfh.filename_length = info.name.size();
757 	DOSTimestamp dos_timestamp(t);
758 	lfh.modification_time = dos_timestamp.dos_time;
759 	lfh.modification_date = dos_timestamp.dos_date;
760 
761 	fseek(storage_file_, 0, SEEK_END);
762 	long int offset = ftell(storage_file_);
763 	changed_ = true;
764 	if (sizeof(lfh) != fwrite(&lfh, 1, sizeof(lfh), storage_file_))
765 		return false;
766 	if (info.name.size() != fwrite(info.name.c_str(), 1, info.name.size(), storage_file_))
767 		return false;
768 
769 	// update file info
770 	info.header_offset = offset;
771 	info.size = 0;
772 	info.compression = 0;
773 	info.crc32 = 0;
774 	info.time = t;
775 	if (file_ == files_.end())
776 	{
777 		files_[new_info.name] = new_info;
778 		file_ = files_.find(fix_slashes(filename));
779 	}
780 	file_writing_ = true;
781 	file_processed_size_ = 0;
782 	return true;
783 }
784 
file_close()785 void FileContainerZip::file_close()
786 {
787 	if (file_is_opened_for_write())
788 	{
789 		LocalFileHeaderOverwrite lfho;
790 		lfho.crc32 = file_->second.crc32;
791 		lfho.compressed_size = lfho.uncompressed_size = file_->second.size;
792 		fseek(storage_file_, file_->second.header_offset + LocalFileHeaderOverwrite::offset_from_header(), SEEK_SET);
793 		fwrite(&lfho, 1, sizeof(lfho), storage_file_);
794 		file_writing_ = false;
795 		fflush(storage_file_);
796 	}
797 	file_reading_whole_container_ = false;
798 	file_reading_ = false;
799 	file_writing_ = false;
800 	file_processed_size_ = 0;
801 
802 	// call base-class method to invalidate streams
803 	FileContainer::file_close();
804 }
805 
file_is_opened_for_read()806 bool FileContainerZip::file_is_opened_for_read()
807 {
808 	return is_opened() && (file_reading_ || file_reading_whole_container_);
809 }
810 
file_is_opened_for_write()811 bool FileContainerZip::file_is_opened_for_write()
812 {
813 	return is_opened() && file_writing_;
814 }
815 
file_read(void * buffer,size_t size)816 size_t FileContainerZip::file_read(void *buffer, size_t size)
817 {
818 	if (!file_is_opened_for_read()) return 0;
819 	file_size_t file_size = file_reading_whole_container_
820 	                      ? prev_storage_size_ : file_->second.size;
821 	file_size_t remain_size = file_size - file_processed_size_;
822 	size_t s = remain_size > (file_size_t)size ? size : (size_t)remain_size;
823 	s = fread(buffer, 1, s, storage_file_);
824 	file_processed_size_ += s;
825 	return s;
826 }
827 
file_write(const void * buffer,size_t size)828 size_t FileContainerZip::file_write(const void *buffer, size_t size)
829 {
830 	if (!file_is_opened_for_write()) return 0;
831 	size_t s = fwrite(buffer, 1, size, storage_file_);
832 	file_processed_size_ += s;
833 	file_->second.size = file_processed_size_;
834 	file_->second.crc32 = crc32(file_->second.crc32, buffer, s);
835 	return s;
836 }
837 
get_read_stream(const String & filename)838 FileSystem::ReadStream::Handle FileContainerZip::get_read_stream(const String &filename)
839 {
840 	FileSystem::ReadStream::Handle stream = FileContainer::get_read_stream(filename);
841 	if (stream
842 	 && file_is_opened_for_read()
843 	 && !file_reading_whole_container_
844 	 && file_->second.compression > 0)
845 		return new ZReadStream(stream);
846 	return stream;
847 }
848 
849 /* === E N T R Y P O I N T ================================================= */
850