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