Merge pull request #4594 from FernetMenta/paplayer
[xbmc.git] / xbmc / filesystem / ZipManager.cpp
blobabd866e79677e7fc3ea3afc6b148611579eafb20
1 /*
2 * Copyright (C) 2005-2013 Team XBMC
3 * http://xbmc.org
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)
8 * any later version.
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/>.
21 #include "system.h"
22 #include "ZipManager.h"
23 #include "URL.h"
24 #include "File.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"
32 #ifndef min
33 #define min(a,b) (((a) < (b)) ? (a) : (b))
34 #endif
36 using namespace XFILE;
37 using namespace std;
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());
52 CURL url(strPath);
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());
60 return false;
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)
71 items = it->second;
72 return true;
74 mZipMap.erase(it);
75 mZipDate.erase(it2);
78 CFile mFile;
79 if (!mFile.Open(strFile))
81 CLog::Log(LOGDEBUG,"ZipManager: unable to open file %s!",strFile.c_str());
82 return false;
85 unsigned int hdr;
86 mFile.Read(&hdr, 4);
87 if( Endian_SwapLE32(hdr) != ZIP_LOCAL_HEADER )
89 CLog::Log(LOGDEBUG,"ZipManager: not a zip file!");
90 mFile.Close();
91 return false;
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];
112 bool found = false;
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);
125 found = true;
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);
141 found = true;
146 delete [] buffer;
148 if ( !found )
150 CLog::Log(LOGDEBUG,"ZipManager: broken file %s!",strFile.c_str());
151 mFile.Close();
152 return false;
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)
170 SZipEntry ze;
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());
176 mFile.Close();
177 return false;
180 // Get the filename just after the central file header
181 CStdString strName;
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);
191 items.push_back(ze);
194 /* go through list and figure out file header lengths */
195 for(vector<SZipEntry>::iterator it = items.begin(); it != items.end(); ++it)
197 SZipEntry& ze = *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));
210 mFile.Close();
211 return true;
214 bool CZipManager::GetZipEntry(const CStdString& strPath, SZipEntry& item)
216 CURL url(strPath);
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);
226 else
228 items = it->second;
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));
237 return true;
240 return false;
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
252 continue;
253 CStdString strFilePath(it->name);
256 URIUtils::CreateArchivePath(strZipPath, "zip", strArchive, strFilePath);
257 if (!CFile::Cache(strZipPath.c_str(),(strPath+strFilePath).c_str()))
258 return false;
260 return true;
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
273 continue;
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)
319 CURL url(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());
324 mZipMap.erase(it);
325 mZipDate.erase(it2);