1 /*
2  *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
3  *
4  *  SPDX-License-Identifier: GPL-2.0-or-later
5  *  See LICENSE.md for more information.
6  */
7 
8 #include "FileUtils.h"
9 
10 #include "../Settings.h"
11 
12 #include <lzma.h>
13 #include <zlib.h>
14 
15 using namespace iptvsimple;
16 using namespace iptvsimple::utilities;
17 
PathCombine(const std::string & path,const std::string & fileName)18 std::string FileUtils::PathCombine(const std::string& path, const std::string& fileName)
19 {
20   std::string result = path;
21 
22   if (!result.empty())
23   {
24     if (result.at(result.size() - 1) == '\\' ||
25         result.at(result.size() - 1) == '/')
26     {
27       result.append(fileName);
28     }
29     else
30     {
31       result.append("/");
32       result.append(fileName);
33     }
34   }
35   else
36   {
37     result.append(fileName);
38   }
39 
40   return result;
41 }
42 
GetUserDataAddonFilePath(const std::string & fileName)43 std::string FileUtils::GetUserDataAddonFilePath(const std::string& fileName)
44 {
45   return PathCombine(Settings::GetInstance().GetUserPath(), fileName);
46 }
47 
GetFileContents(const std::string & url,std::string & content)48 int FileUtils::GetFileContents(const std::string& url, std::string& content)
49 {
50   content.clear();
51   kodi::vfs::CFile file;
52   if (file.OpenFile(url))
53   {
54     char buffer[1024];
55     while (int bytesRead = file.Read(buffer, 1024))
56       content.append(buffer, bytesRead);
57   }
58 
59   return content.length();
60 }
61 
62 /*
63  * This method uses zlib to decompress a gzipped file in memory.
64  * Author: Andrew Lim Chong Liang
65  * http://windrealm.org
66  */
67 
GzipInflate(const std::string & compressedBytes,std::string & uncompressedBytes)68 bool FileUtils::GzipInflate(const std::string& compressedBytes, std::string& uncompressedBytes)
69 {
70   if (compressedBytes.size() == 0)
71   {
72     uncompressedBytes = compressedBytes;
73     return true;
74   }
75 
76   uncompressedBytes.clear();
77 
78   unsigned uncompLength = compressedBytes.size();
79   const unsigned half_length = compressedBytes.size() / 2;
80 
81   char* uncomp = static_cast<char*>(calloc(sizeof(char), uncompLength));
82 
83   z_stream strm;
84   strm.next_in = (Bytef*)compressedBytes.c_str();
85   strm.avail_in = compressedBytes.size();
86   strm.total_out = 0;
87   strm.zalloc = Z_NULL;
88   strm.zfree = Z_NULL;
89 
90   int status = inflateInit2(&strm, 16 + MAX_WBITS);
91   if (status != Z_OK)
92   {
93     free(uncomp);
94     return false;
95   }
96 
97   bool done = false;
98   while (!done)
99   {
100     // If our output buffer is too small
101     if (strm.total_out >= uncompLength)
102     {
103       // Increase size of output buffer
104       uncomp = static_cast<char*>(realloc(uncomp, uncompLength + half_length));
105       if (!uncomp)
106         return false;
107       uncompLength += half_length;
108     }
109 
110     strm.next_out = reinterpret_cast<Bytef*>(uncomp + strm.total_out);
111     strm.avail_out = uncompLength - strm.total_out;
112 
113     // Inflate another chunk.
114     int err = inflate(&strm, Z_SYNC_FLUSH);
115     if (err == Z_STREAM_END)
116       done = true;
117     else if (err != Z_OK)
118       break;
119   }
120 
121   status = inflateEnd(&strm);
122   if (status != Z_OK)
123   {
124     free(uncomp);
125     return false;
126   }
127 
128   for (size_t i = 0; i < strm.total_out; ++i)
129     uncompressedBytes += uncomp[i];
130 
131   free(uncomp);
132   return true;
133 }
134 
XzDecompress(const std::string & compressedBytes,std::string & uncompressedBytes)135 bool FileUtils::XzDecompress(const std::string& compressedBytes, std::string& uncompressedBytes)
136 {
137   if (compressedBytes.size() == 0)
138   {
139     uncompressedBytes = compressedBytes;
140     return true;
141   }
142 
143   uncompressedBytes.clear();
144 
145   lzma_stream strm = LZMA_STREAM_INIT;
146   lzma_ret ret = lzma_stream_decoder(&strm, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED);
147 
148   if (ret != LZMA_OK)
149     return false;
150 
151   uint8_t* in_buf = (uint8_t*) compressedBytes.c_str();
152   uint8_t out_buf[LZMA_OUT_BUF_MAX];
153   size_t out_len;
154 
155   strm.next_in = in_buf;
156   strm.avail_in = compressedBytes.size();
157   do
158   {
159     strm.next_out = out_buf;
160     strm.avail_out = LZMA_OUT_BUF_MAX;
161     ret = lzma_code(&strm, LZMA_FINISH);
162 
163     out_len = LZMA_OUT_BUF_MAX - strm.avail_out;
164     uncompressedBytes.append((char*) out_buf, out_len);
165     out_buf[0] = 0;
166   } while (strm.avail_out == 0);
167   lzma_end (&strm);
168 
169   return true;
170 }
171 
GetCachedFileContents(const std::string & cachedName,const std::string & filePath,std::string & contents,const bool useCache)172 int FileUtils::GetCachedFileContents(const std::string& cachedName, const std::string& filePath,
173                                        std::string& contents, const bool useCache /* false */)
174 {
175   bool needReload = false;
176   const std::string cachedPath = FileUtils::GetUserDataAddonFilePath(cachedName);
177 
178   // check cached file is exists
179   if (useCache && kodi::vfs::FileExists(cachedPath, false))
180   {
181     kodi::vfs::FileStatus statCached;
182     kodi::vfs::FileStatus statOrig;
183 
184     kodi::vfs::StatFile(cachedPath, statCached);
185     kodi::vfs::StatFile(filePath, statOrig);
186 
187     needReload = statCached.GetModificationTime() < statOrig.GetModificationTime() || statOrig.GetModificationTime() == 0;
188   }
189   else
190   {
191     needReload = true;
192   }
193 
194   if (needReload)
195   {
196     FileUtils::GetFileContents(filePath, contents);
197 
198     // write to cache
199     if (useCache && contents.length() > 0)
200     {
201       kodi::vfs::CFile file;
202       if (file.OpenFileForWrite(cachedPath, true))
203       {
204         file.Write(contents.c_str(), contents.length());
205       }
206     }
207     return contents.length();
208   }
209 
210   return FileUtils::GetFileContents(cachedPath, contents);
211 }
212 
FileExists(const std::string & file)213 bool FileUtils::FileExists(const std::string& file)
214 {
215   return kodi::vfs::FileExists(file, false);
216 }
217 
DeleteFile(const std::string & file)218 bool FileUtils::DeleteFile(const std::string& file)
219 {
220   return kodi::vfs::DeleteFile(file);
221 }
222 
CopyFile(const std::string & sourceFile,const std::string & targetFile)223 bool FileUtils::CopyFile(const std::string& sourceFile, const std::string& targetFile)
224 {
225   kodi::vfs::CFile file;
226   bool copySuccessful = true;
227 
228   Logger::Log(LEVEL_DEBUG, "%s - Copying file: %s, to %s", __FUNCTION__, sourceFile.c_str(), targetFile.c_str());
229 
230   if (file.OpenFile(sourceFile, ADDON_READ_NO_CACHE))
231   {
232     const std::string fileContents = ReadFileContents(file);
233 
234     file.Close();
235 
236     if (file.OpenFileForWrite(targetFile, true))
237     {
238       file.Write(fileContents.c_str(), fileContents.length());
239     }
240     else
241     {
242       Logger::Log(LEVEL_ERROR, "%s - Could not open target file to copy to: %s", __FUNCTION__, targetFile.c_str());
243       copySuccessful = false;
244     }
245   }
246   else
247   {
248     Logger::Log(LEVEL_ERROR, "%s - Could not open source file to copy: %s", __FUNCTION__, sourceFile.c_str());
249     copySuccessful = false;
250   }
251 
252   return copySuccessful;
253 }
254 
CopyDirectory(const std::string & sourceDir,const std::string & targetDir,bool recursiveCopy)255 bool FileUtils::CopyDirectory(const std::string& sourceDir, const std::string& targetDir, bool recursiveCopy)
256 {
257   bool copySuccessful = true;
258 
259   kodi::vfs::CreateDirectory(targetDir);
260 
261   std::vector<kodi::vfs::CDirEntry> entries;
262   if (kodi::vfs::GetDirectory(sourceDir, "", entries))
263   {
264     for (const auto& entry : entries)
265     {
266       if (entry.IsFolder() && recursiveCopy)
267       {
268         copySuccessful = CopyDirectory(sourceDir + "/" + entry.Label(), targetDir + "/" + entry.Label(), true);
269       }
270       else if (!entry.IsFolder())
271       {
272         copySuccessful = CopyFile(sourceDir + "/" + entry.Label(), targetDir + "/" + entry.Label());
273       }
274     }
275   }
276   else
277   {
278     Logger::Log(LEVEL_ERROR, "%s - Could not copy directory: %s, to directory: %s", __FUNCTION__, sourceDir.c_str(), targetDir.c_str());
279     copySuccessful = false;
280   }
281   return copySuccessful;
282 }
283 
GetSystemAddonPath()284 std::string FileUtils::GetSystemAddonPath()
285 {
286   return kodi::GetAddonPath();
287 }
288 
GetResourceDataPath()289 std::string FileUtils::GetResourceDataPath()
290 {
291   return kodi::GetAddonPath("/resources/data");
292 }
293 
ReadFileContents(kodi::vfs::CFile & file)294 std::string FileUtils::ReadFileContents(kodi::vfs::CFile& file)
295 {
296   std::string fileContents;
297 
298   char buffer[1024];
299   int bytesRead = 0;
300 
301   // Read until EOF or explicit error
302   while ((bytesRead = file.Read(buffer, sizeof(buffer) - 1)) > 0)
303     fileContents.append(buffer, bytesRead);
304 
305   return fileContents;
306 }
307