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