2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "ZipManager.h"
25 #include "utils/CharsetConverter.h"
26 #include "utils/log.h"
27 #include "utils/EndianSwap.h"
28 #include "utils/URIUtils.h"
29 #include "SpecialProtocol.h"
33 #define min(a,b) (((a) < (b)) ? (a) : (b))
36 using namespace XFILE
;
39 CZipManager::CZipManager()
43 CZipManager::~CZipManager()
48 bool CZipManager::GetZipList(const CStdString
& strPath
, vector
<SZipEntry
>& items
)
50 CLog::Log(LOGDEBUG
, "%s - Processing %s", __FUNCTION__
, strPath
.c_str());
53 struct __stat64 m_StatData
= {};
55 CStdString strFile
= url
.GetHostName();
57 if (CFile::Stat(strFile
,&m_StatData
))
59 CLog::Log(LOGDEBUG
,"CZipManager::GetZipList: failed to stat file %s", strPath
.c_str());
63 map
<CStdString
,vector
<SZipEntry
> >::iterator it
= mZipMap
.find(strFile
);
64 if (it
!= mZipMap
.end()) // already listed, just return it if not changed, else release and reread
66 map
<CStdString
,int64_t>::iterator it2
=mZipDate
.find(strFile
);
67 CLog::Log(LOGDEBUG
,"statdata: %"PRId64
" new: %"PRIu64
, it2
->second
, (uint64_t)m_StatData
.st_mtime
);
69 if (m_StatData
.st_mtime
== it2
->second
)
79 if (!mFile
.Open(strFile
))
81 CLog::Log(LOGDEBUG
,"ZipManager: unable to open file %s!",strFile
.c_str());
87 if( Endian_SwapLE32(hdr
) != ZIP_LOCAL_HEADER
)
89 CLog::Log(LOGDEBUG
,"ZipManager: not a zip file!");
93 // push date for update detection
94 mZipDate
.insert(make_pair(strFile
,m_StatData
.st_mtime
));
97 // Look for end of central directory record
98 // Zipfile comment may be up to 65535 bytes
99 // End of central directory record is 22 bytes (ECDREC_SIZE)
100 // -> need to check the last 65557 bytes
101 int64_t fileSize
= mFile
.GetLength();
102 // Don't need to look in the last 18 bytes (ECDREC_SIZE-4)
103 // But as we need to do overlapping between blocks (3 bytes),
104 // we start the search at ECDREC_SIZE-1 from the end of file
105 int searchSize
= (int) min(65557, fileSize
-ECDREC_SIZE
+1);
106 int blockSize
= (int) min(1024, searchSize
);
107 int nbBlock
= searchSize
/ blockSize
;
108 int extraBlockSize
= searchSize
% blockSize
;
109 // Signature is on 4 bytes
110 // It could be between 2 blocks, so we need to read 3 extra bytes
111 char *buffer
= new char[blockSize
+3];
114 // Loop through blocks starting at the end of the file (minus ECDREC_SIZE-1)
115 for (int nb
=1; !found
&& (nb
<= nbBlock
); nb
++)
117 mFile
.Seek(fileSize
-ECDREC_SIZE
+1-(blockSize
*nb
),SEEK_SET
);
118 mFile
.Read(buffer
,blockSize
+3);
119 for (int i
=blockSize
-1; !found
&& (i
>= 0); i
--)
121 if ( Endian_SwapLE32(*((unsigned int*)(buffer
+i
))) == ZIP_END_CENTRAL_HEADER
)
123 // Set current position to start of end of central directory
124 mFile
.Seek(fileSize
-ECDREC_SIZE
+1-(blockSize
*nb
)+i
,SEEK_SET
);
130 // If not found, look in the last block left...
131 if ( !found
&& (extraBlockSize
> 0) )
133 mFile
.Seek(fileSize
-ECDREC_SIZE
+1-searchSize
,SEEK_SET
);
134 mFile
.Read(buffer
,extraBlockSize
+3);
135 for (int i
=extraBlockSize
-1; !found
&& (i
>= 0); i
--)
137 if ( Endian_SwapLE32(*((unsigned int*)(buffer
+i
))) == ZIP_END_CENTRAL_HEADER
)
139 // Set current position to start of end of central directory
140 mFile
.Seek(fileSize
-ECDREC_SIZE
+1-searchSize
+i
,SEEK_SET
);
150 CLog::Log(LOGDEBUG
,"ZipManager: broken file %s!",strFile
.c_str());
155 unsigned int cdirOffset
, cdirSize
;
156 // Get size of the central directory
157 mFile
.Seek(12,SEEK_CUR
);
158 mFile
.Read(&cdirSize
,4);
159 cdirSize
= Endian_SwapLE32(cdirSize
);
160 // Get Offset of start of central directory with respect to the starting disk number
161 mFile
.Read(&cdirOffset
,4);
162 cdirOffset
= Endian_SwapLE32(cdirOffset
);
164 // Go to the start of central directory
165 mFile
.Seek(cdirOffset
,SEEK_SET
);
167 char temp
[CHDR_SIZE
];
168 while (mFile
.GetPosition() < cdirOffset
+ cdirSize
)
171 mFile
.Read(temp
,CHDR_SIZE
);
172 readCHeader(temp
, ze
);
173 if (ze
.header
!= ZIP_CENTRAL_HEADER
)
175 CLog::Log(LOGDEBUG
,"ZipManager: broken file %s!",strFile
.c_str());
180 // Get the filename just after the central file header
182 mFile
.Read(strName
.GetBuffer(ze
.flength
), ze
.flength
);
183 strName
.ReleaseBuffer();
184 g_charsetConverter
.unknownToUTF8(strName
);
185 ZeroMemory(ze
.name
, 255);
186 strncpy(ze
.name
, strName
.c_str(), strName
.size()>254 ? 254 : strName
.size());
188 // Jump after central file header extra field and file comment
189 mFile
.Seek(ze
.eclength
+ ze
.clength
,SEEK_CUR
);
194 /* go through list and figure out file header lengths */
195 for(vector
<SZipEntry
>::iterator it
= items
.begin(); it
!= items
.end(); ++it
)
198 // Go to the local file header to get the extra field length
199 // !! local header extra field length != central file header extra field length !!
200 mFile
.Seek(ze
.lhdrOffset
+28,SEEK_SET
);
201 mFile
.Read(&(ze
.elength
),2);
202 ze
.elength
= Endian_SwapLE16(ze
.elength
);
204 // Compressed data offset = local header offset + size of local header + filename length + local file header extra field length
205 ze
.offset
= ze
.lhdrOffset
+ LHDR_SIZE
+ ze
.flength
+ ze
.elength
;
209 mZipMap
.insert(make_pair(strFile
,items
));
214 bool CZipManager::GetZipEntry(const CStdString
& strPath
, SZipEntry
& item
)
218 CStdString strFile
= url
.GetHostName();
220 map
<CStdString
,vector
<SZipEntry
> >::iterator it
= mZipMap
.find(strFile
);
221 vector
<SZipEntry
> items
;
222 if (it
== mZipMap
.end()) // we need to list the zip
224 GetZipList(strPath
,items
);
231 CStdString strFileName
= url
.GetFileName();
232 for (vector
<SZipEntry
>::iterator it2
=items
.begin();it2
!= items
.end();++it2
)
234 if (CStdString(it2
->name
) == strFileName
)
236 memcpy(&item
,&(*it2
),sizeof(SZipEntry
));
243 bool CZipManager::ExtractArchive(const CStdString
& strArchive
, const CStdString
& strPath
)
245 vector
<SZipEntry
> entry
;
246 CStdString strZipPath
;
247 URIUtils::CreateArchivePath(strZipPath
, "zip", strArchive
, "");
248 GetZipList(strZipPath
,entry
);
249 for (vector
<SZipEntry
>::iterator it
=entry
.begin();it
!= entry
.end();++it
)
251 if (it
->name
[strlen(it
->name
)-1] == '/') // skip dirs
253 CStdString
strFilePath(it
->name
);
256 URIUtils::CreateArchivePath(strZipPath
, "zip", strArchive
, strFilePath
);
257 if (!CFile::Cache(strZipPath
.c_str(),(strPath
+strFilePath
).c_str()))
263 void CZipManager::CleanUp(const CStdString
& strArchive
, const CStdString
& strPath
)
265 vector
<SZipEntry
> entry
;
266 CStdString strZipPath
;
267 URIUtils::CreateArchivePath(strZipPath
, "zip", strArchive
, "");
269 GetZipList(strZipPath
,entry
);
270 for (vector
<SZipEntry
>::iterator it
=entry
.begin();it
!= entry
.end();++it
)
272 if (it
->name
[strlen(it
->name
)-1] == '/') // skip dirs
274 CStdString
strFilePath(it
->name
);
275 CLog::Log(LOGDEBUG
,"delete file: %s",(strPath
+strFilePath
).c_str());
276 CFile::Delete((strPath
+strFilePath
).c_str());
280 // Read local file header
281 void CZipManager::readHeader(const char* buffer
, SZipEntry
& info
)
283 info
.header
= Endian_SwapLE32(*(unsigned int*)buffer
);
284 info
.version
= Endian_SwapLE16(*(unsigned short*)(buffer
+4));
285 info
.flags
= Endian_SwapLE16(*(unsigned short*)(buffer
+6));
286 info
.method
= Endian_SwapLE16(*(unsigned short*)(buffer
+8));
287 info
.mod_time
= Endian_SwapLE16(*(unsigned short*)(buffer
+10));
288 info
.mod_date
= Endian_SwapLE16(*(unsigned short*)(buffer
+12));
289 info
.crc32
= Endian_SwapLE32(*(unsigned int*)(buffer
+14));
290 info
.csize
= Endian_SwapLE32(*(unsigned int*)(buffer
+18));
291 info
.usize
= Endian_SwapLE32(*(unsigned int*)(buffer
+22));
292 info
.flength
= Endian_SwapLE16(*(unsigned short*)(buffer
+26));
293 info
.elength
= Endian_SwapLE16(*(unsigned short*)(buffer
+28));
296 // Read central file header (from central directory)
297 void CZipManager::readCHeader(const char* buffer
, SZipEntry
& info
)
299 info
.header
= Endian_SwapLE32(*(unsigned int*)buffer
);
300 // Skip version made by
301 info
.version
= Endian_SwapLE16(*(unsigned short*)(buffer
+6));
302 info
.flags
= Endian_SwapLE16(*(unsigned short*)(buffer
+8));
303 info
.method
= Endian_SwapLE16(*(unsigned short*)(buffer
+10));
304 info
.mod_time
= Endian_SwapLE16(*(unsigned short*)(buffer
+12));
305 info
.mod_date
= Endian_SwapLE16(*(unsigned short*)(buffer
+14));
306 info
.crc32
= Endian_SwapLE32(*(unsigned int*)(buffer
+16));
307 info
.csize
= Endian_SwapLE32(*(unsigned int*)(buffer
+20));
308 info
.usize
= Endian_SwapLE32(*(unsigned int*)(buffer
+24));
309 info
.flength
= Endian_SwapLE16(*(unsigned short*)(buffer
+28));
310 info
.eclength
= Endian_SwapLE16(*(unsigned short*)(buffer
+30));
311 info
.clength
= Endian_SwapLE16(*(unsigned short*)(buffer
+32));
312 // Skip disk number start, internal/external file attributes
313 info
.lhdrOffset
= Endian_SwapLE32(*(unsigned int*)(buffer
+42));
317 void CZipManager::release(const CStdString
& strPath
)
320 map
<CStdString
,vector
<SZipEntry
> >::iterator it
= mZipMap
.find(url
.GetHostName());
321 if (it
!= mZipMap
.end())
323 map
<CStdString
,int64_t>::iterator it2
=mZipDate
.find(url
.GetHostName());