[PVR][Estuary] Timer settings dialog: Show client name in timer type selection dialog...
[xbmc.git] / xbmc / filesystem / ZipFile.cpp
blob9978dc7cd6ce0ee980f654a8ac91cfd519251fa7
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "ZipFile.h"
11 #include "URL.h"
12 #include "utils/URIUtils.h"
13 #include "utils/log.h"
15 #include <sys/stat.h>
17 #define ZIP_CACHE_LIMIT 4*1024*1024
19 using namespace XFILE;
21 CZipFile::CZipFile()
23 m_szStringBuffer = NULL;
24 m_szStartOfStringBuffer = NULL;
25 m_iDataInStringBuffer = 0;
26 m_bCached = false;
27 m_iRead = -1;
30 CZipFile::~CZipFile()
32 delete[] m_szStringBuffer;
33 Close();
36 bool CZipFile::Open(const CURL&url)
38 const std::string& strOpts = url.GetOptions();
39 CURL url2(url);
40 url2.SetOptions("");
41 if (!g_ZipManager.GetZipEntry(url2,mZipItem))
42 return false;
44 if ((mZipItem.flags & 64) == 64)
46 CLog::Log(LOGERROR,"FileZip: encrypted file, not supported!");
47 return false;
50 if ((mZipItem.method != 8) && (mZipItem.method != 0))
52 CLog::Log(LOGERROR,"FileZip: unsupported compression method!");
53 return false;
56 if (mZipItem.method != 0 && mZipItem.usize > ZIP_CACHE_LIMIT && strOpts != "?cache=no")
58 if (!CFile::Exists("special://temp/" + URIUtils::GetFileName(url2)))
60 url2.SetOptions("?cache=no");
61 const CURL pathToUrl("special://temp/" + URIUtils::GetFileName(url2));
62 if (!CFile::Copy(url2, pathToUrl))
63 return false;
65 m_bCached = true;
66 return mFile.Open("special://temp/" + URIUtils::GetFileName(url2));
69 if (!mFile.Open(url.GetHostName())) // this is the zip-file, always open binary
71 CLog::Log(LOGERROR, "FileZip: unable to open zip file {}!", url.GetHostName());
72 return false;
74 mFile.Seek(mZipItem.offset,SEEK_SET);
75 return InitDecompress();
78 bool CZipFile::InitDecompress()
80 m_iRead = 1;
81 m_iFilePos = 0;
82 m_iZipFilePos = 0;
83 m_iAvailBuffer = 0;
84 m_bFlush = false;
85 m_ZStream.zalloc = Z_NULL;
86 m_ZStream.zfree = Z_NULL;
87 m_ZStream.opaque = Z_NULL;
88 if( mZipItem.method != 0 )
90 if (inflateInit2(&m_ZStream,-MAX_WBITS) != Z_OK)
92 CLog::Log(LOGERROR,"FileZip: error initializing zlib!");
93 return false;
96 m_ZStream.next_in = (Bytef*)m_szBuffer;
97 m_ZStream.avail_in = 0;
98 m_ZStream.total_out = 0;
100 return true;
103 int64_t CZipFile::GetLength()
105 return mZipItem.usize;
108 int64_t CZipFile::GetPosition()
110 if (m_bCached)
111 return mFile.GetPosition();
113 return m_iFilePos;
116 int64_t CZipFile::Seek(int64_t iFilePosition, int iWhence)
118 if (m_bCached)
119 return mFile.Seek(iFilePosition,iWhence);
120 if (mZipItem.method == 0) // this is easy
122 int64_t iResult;
123 switch (iWhence)
125 case SEEK_SET:
126 if (iFilePosition > mZipItem.usize)
127 return -1;
128 m_iFilePos = iFilePosition;
129 m_iZipFilePos = m_iFilePos;
130 iResult = mFile.Seek(iFilePosition+mZipItem.offset,SEEK_SET)-mZipItem.offset;
131 return iResult;
132 break;
134 case SEEK_CUR:
135 if (m_iFilePos+iFilePosition > mZipItem.usize)
136 return -1;
137 m_iFilePos += iFilePosition;
138 m_iZipFilePos = m_iFilePos;
139 iResult = mFile.Seek(iFilePosition,SEEK_CUR)-mZipItem.offset;
140 return iResult;
141 break;
143 case SEEK_END:
144 if (iFilePosition > mZipItem.usize)
145 return -1;
146 m_iFilePos = mZipItem.usize+iFilePosition;
147 m_iZipFilePos = m_iFilePos;
148 iResult = mFile.Seek(mZipItem.offset+mZipItem.usize+iFilePosition,SEEK_SET)-mZipItem.offset;
149 return iResult;
150 break;
151 default:
152 return -1;
156 // here goes the stupid part..
157 if (mZipItem.method == 8)
159 static const int blockSize = 128 * 1024;
160 std::vector<char> buf(blockSize);
161 switch (iWhence)
163 case SEEK_SET:
164 if (iFilePosition == m_iFilePos)
165 return m_iFilePos; // mp3reader does this lots-of-times
166 if (iFilePosition > mZipItem.usize || iFilePosition < 0)
167 return -1;
168 // read until position in 128k blocks.. only way to do it due to format.
169 // can't start in the middle of data since then we'd have no clue where
170 // we are in uncompressed data..
171 if (iFilePosition < m_iFilePos)
173 m_iFilePos = 0;
174 m_iZipFilePos = 0;
175 inflateEnd(&m_ZStream);
176 inflateInit2(&m_ZStream,-MAX_WBITS); // simply restart zlib
177 mFile.Seek(mZipItem.offset,SEEK_SET);
178 m_ZStream.next_in = (Bytef*)m_szBuffer;
179 m_ZStream.avail_in = 0;
180 m_ZStream.total_out = 0;
181 while (m_iFilePos < iFilePosition)
183 ssize_t iToRead = (iFilePosition - m_iFilePos) > blockSize ? blockSize : iFilePosition - m_iFilePos;
184 if (Read(buf.data(), iToRead) != iToRead)
185 return -1;
187 return m_iFilePos;
189 else // seek forward
190 return Seek(iFilePosition-m_iFilePos,SEEK_CUR);
191 break;
193 case SEEK_CUR:
194 if (iFilePosition < 0)
195 return Seek(m_iFilePos+iFilePosition,SEEK_SET); // can't rewind stream
196 // read until requested position, drop data
197 if (m_iFilePos+iFilePosition > mZipItem.usize)
198 return -1;
199 iFilePosition += m_iFilePos;
200 while (m_iFilePos < iFilePosition)
202 ssize_t iToRead = (iFilePosition - m_iFilePos)>blockSize ? blockSize : iFilePosition - m_iFilePos;
203 if (Read(buf.data(), iToRead) != iToRead)
204 return -1;
206 return m_iFilePos;
207 break;
209 case SEEK_END:
210 // now this is a nasty bastard, possibly takes lotsoftime
211 // uncompress, minding m_ZStream.total_out
213 while(static_cast<ssize_t>(m_ZStream.total_out) < mZipItem.usize+iFilePosition)
215 ssize_t iToRead = (mZipItem.usize + iFilePosition - m_ZStream.total_out > blockSize) ? blockSize : mZipItem.usize + iFilePosition - m_ZStream.total_out;
216 if (Read(buf.data(), iToRead) != iToRead)
217 return -1;
219 return m_iFilePos;
220 break;
221 default:
222 return -1;
225 return -1;
228 bool CZipFile::Exists(const CURL& url)
230 SZipEntry item;
231 if (g_ZipManager.GetZipEntry(url,item))
232 return true;
233 return false;
236 int CZipFile::Stat(struct __stat64 *buffer)
238 int ret;
239 struct tm tm = {};
241 ret = mFile.Stat(buffer);
242 tm.tm_sec = (mZipItem.mod_time & 0x1F) << 1;
243 tm.tm_min = (mZipItem.mod_time & 0x7E0) >> 5;
244 tm.tm_hour = (mZipItem.mod_time & 0xF800) >> 11;
245 tm.tm_mday = (mZipItem.mod_date & 0x1F);
246 tm.tm_mon = (mZipItem.mod_date & 0x1E0) >> 5;
247 tm.tm_year = (mZipItem.mod_date & 0xFE00) >> 9;
248 buffer->st_atime = buffer->st_ctime = buffer->st_mtime = mktime(&tm);
250 buffer->st_size = mZipItem.usize;
251 buffer->st_dev = (buffer->st_dev << 16) ^ (buffer->st_ino << 16);
252 buffer->st_ino ^= mZipItem.crc32;
253 return ret;
256 int CZipFile::Stat(const CURL& url, struct __stat64* buffer)
258 if (!buffer)
259 return -1;
261 if (!g_ZipManager.GetZipEntry(url, mZipItem))
263 if (url.GetFileName().empty() && CFile::Exists(url.GetHostName()))
264 { // when accessing the zip "root" recognize it as a directory
265 buffer->st_mode = _S_IFDIR;
266 return 0;
268 else
269 return -1;
272 *buffer = {};
273 buffer->st_gid = 0;
274 buffer->st_atime = buffer->st_ctime = mZipItem.mod_time;
275 buffer->st_size = mZipItem.usize;
276 return 0;
279 ssize_t CZipFile::Read(void* lpBuf, size_t uiBufSize)
281 if (uiBufSize > SSIZE_MAX)
282 uiBufSize = SSIZE_MAX;
284 if (m_bCached)
285 return mFile.Read(lpBuf,uiBufSize);
287 // flush what might be left in the string buffer
288 if (m_iDataInStringBuffer > 0)
290 size_t iMax = uiBufSize>m_iDataInStringBuffer?m_iDataInStringBuffer:uiBufSize;
291 memcpy(lpBuf,m_szStartOfStringBuffer,iMax);
292 uiBufSize -= iMax;
293 m_iDataInStringBuffer -= iMax;
295 if (mZipItem.method == 8) // deflated
297 uLong iDecompressed = 0;
298 uLong prevOut = m_ZStream.total_out;
299 while ((iDecompressed < uiBufSize) && ((m_iZipFilePos < mZipItem.csize) || (m_bFlush)))
301 m_ZStream.next_out = (Bytef*)(lpBuf)+iDecompressed;
302 m_ZStream.avail_out = static_cast<uInt>(uiBufSize-iDecompressed);
303 if (m_bFlush) // need to flush buffer !
305 int iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
306 m_bFlush = ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))?true:false;
307 if (!m_ZStream.avail_out) // flush filled buffer, get out of here
309 iDecompressed = m_ZStream.total_out-prevOut;
310 break;
314 if (!m_ZStream.avail_in)
316 if (!FillBuffer()) // eof!
318 iDecompressed = m_ZStream.total_out-prevOut;
319 break;
323 int iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
324 if (iMessage < 0)
326 Close();
327 return -1; // READ ERROR
330 m_bFlush = ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))?true:false; // more info in input buffer
332 iDecompressed = m_ZStream.total_out-prevOut;
334 m_iFilePos += iDecompressed;
335 return static_cast<unsigned int>(iDecompressed);
337 else if (mZipItem.method == 0) // uncompressed. just read from file, but mind our boundaries.
339 if (uiBufSize+m_iFilePos > mZipItem.csize)
340 uiBufSize = mZipItem.csize-m_iFilePos;
342 if (uiBufSize == 0)
343 return 0; // we are past eof, this shouldn't happen but test anyway
345 ssize_t iResult = mFile.Read(lpBuf,uiBufSize);
346 if (iResult < 0)
347 return -1;
348 m_iZipFilePos += iResult;
349 m_iFilePos += iResult;
350 return iResult;
352 else
353 return -1; // shouldn't happen. compression method checked in open
356 void CZipFile::Close()
358 if (mZipItem.method == 8 && !m_bCached && m_iRead != -1)
359 inflateEnd(&m_ZStream);
361 mFile.Close();
364 bool CZipFile::FillBuffer()
366 ssize_t sToRead = 65535;
367 if (m_iZipFilePos+65535 > mZipItem.csize)
368 sToRead = mZipItem.csize-m_iZipFilePos;
370 if (sToRead <= 0)
371 return false; // eof!
373 if (mFile.Read(m_szBuffer,sToRead) != sToRead)
374 return false;
375 m_ZStream.avail_in = static_cast<unsigned int>(sToRead);
376 m_ZStream.next_in = reinterpret_cast<Byte*>(m_szBuffer);
377 m_iZipFilePos += sToRead;
378 return true;
381 void CZipFile::DestroyBuffer(void* lpBuffer, int iBufSize)
383 if (!m_bFlush)
384 return;
385 int iMessage = Z_OK;
386 while ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))
388 m_ZStream.next_out = (Bytef*)lpBuffer;
389 m_ZStream.avail_out = iBufSize;
390 iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
392 m_bFlush = false;
395 int CZipFile::UnpackFromMemory(std::string& strDest, const std::string& strInput, bool isGZ)
397 unsigned int iPos=0;
398 int iResult=0;
399 while( iPos+LHDR_SIZE < strInput.size() || isGZ)
401 if (!isGZ)
403 CZipManager::readHeader(strInput.data()+iPos,mZipItem);
404 if (mZipItem.header == ZIP_DATA_RECORD_HEADER)
406 // this header concerns a file we already processed, so we can just skip it
407 iPos += DREC_SIZE;
408 continue;
410 if (mZipItem.header != ZIP_LOCAL_HEADER)
411 return iResult;
412 if( (mZipItem.flags & 8) == 8 )
414 // if an extended local header (=data record header) is present,
415 // the following fields are 0 in the local header and we need to read
416 // them from the extended local header
418 // search for the extended local header
419 unsigned int i = iPos + LHDR_SIZE + mZipItem.flength + mZipItem.elength;
420 while (1)
422 if (i + DREC_SIZE > strInput.size())
424 CLog::Log(LOGERROR, "FileZip: extended local header expected, but not present!");
425 return iResult;
427 if ((strInput[i] == 0x50) && (strInput[i + 1] == 0x4b) &&
428 (strInput[i + 2] == 0x07) && (strInput[i + 3] == 0x08))
429 break; // header found
430 i++;
432 // ZIP is little endian:
433 mZipItem.crc32 = static_cast<uint8_t>(strInput[i + 4]) |
434 static_cast<uint8_t>(strInput[i + 5]) << 8 |
435 static_cast<uint8_t>(strInput[i + 6]) << 16 |
436 static_cast<uint8_t>(strInput[i + 7]) << 24;
437 mZipItem.csize = static_cast<uint8_t>(strInput[i + 8]) |
438 static_cast<uint8_t>(strInput[i + 9]) << 8 |
439 static_cast<uint8_t>(strInput[i + 10]) << 16 |
440 static_cast<uint8_t>(strInput[i + 11]) << 24;
441 mZipItem.usize = static_cast<uint8_t>(strInput[i + 12]) |
442 static_cast<uint8_t>(strInput[i + 13]) << 8 |
443 static_cast<uint8_t>(strInput[i + 14]) << 16 |
444 static_cast<uint8_t>(strInput[i + 15]) << 24;
447 if (!InitDecompress())
448 return iResult;
449 // we have a file - fill the buffer
450 char* temp;
451 ssize_t toRead=0;
452 if (isGZ)
454 m_ZStream.avail_in = static_cast<unsigned int>(strInput.size());
455 m_ZStream.next_in = const_cast<Bytef*>((const Bytef*)strInput.data());
456 temp = new char[8192];
457 toRead = 8191;
459 else
461 m_ZStream.avail_in = mZipItem.csize;
462 m_ZStream.next_in = const_cast<Bytef*>((const Bytef*)strInput.data())+iPos+LHDR_SIZE+mZipItem.flength+mZipItem.elength;
463 // init m_zipitem
464 strDest.reserve(mZipItem.usize);
465 temp = new char[mZipItem.usize+1];
466 toRead = mZipItem.usize;
468 int iCurrResult;
469 while((iCurrResult = static_cast<int>(Read(temp, toRead))) > 0)
471 strDest.append(temp,temp+iCurrResult);
472 iResult += iCurrResult;
474 Close();
475 delete[] temp;
476 iPos += LHDR_SIZE+mZipItem.flength+mZipItem.elength+mZipItem.csize;
477 if (isGZ)
478 break;
481 return iResult;
484 bool CZipFile::DecompressGzip(const std::string& in, std::string& out)
486 const int windowBits = MAX_WBITS + 16;
488 z_stream strm;
489 strm.zalloc = Z_NULL;
490 strm.zfree = Z_NULL;
491 strm.opaque = Z_NULL;
493 int err = inflateInit2(&strm, windowBits);
494 if (err != Z_OK)
496 CLog::Log(LOGERROR, "FileZip: zlib error {}", err);
497 return false;
500 const int bufferSize = 16384;
501 unsigned char buffer[bufferSize];
503 strm.avail_in = static_cast<unsigned int>(in.size());
504 strm.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(in.c_str()));
508 strm.avail_out = bufferSize;
509 strm.next_out = buffer;
510 int err = inflate(&strm, Z_NO_FLUSH);
511 switch (err)
513 case Z_NEED_DICT:
514 err = Z_DATA_ERROR;
515 [[fallthrough]];
516 case Z_DATA_ERROR:
517 case Z_MEM_ERROR:
518 case Z_STREAM_ERROR:
519 CLog::Log(LOGERROR, "FileZip: failed to decompress. zlib error {}", err);
520 inflateEnd(&strm);
521 return false;
523 int read = bufferSize - strm.avail_out;
524 out.append((char*)buffer, read);
526 while (strm.avail_out == 0);
528 inflateEnd(&strm);
529 return true;