MythTV  master
unzip2.cpp
Go to the documentation of this file.
1 /*
2  * Class UnZip
3  *
4  * Copyright (c) David Hampton 2021
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include "unzip2.h"
22 
23 // libmythbase headers
24 #include "mythdate.h"
25 #include "mythlogging.h"
26 
27 static constexpr uint32_t ZIP_ATTR_FILE_TYPE_MASK { 0xFE000000 };
28 static constexpr uint32_t ZIP_ATTR_FILE_TYPE_SYMLINK { 0xA0000000 };
29 //static constexpr uint32_t ZIP_ATTR_FILE_TYPE_NORMAL { 0x80000000 };
30 
31 static constexpr uint32_t ZIP_ATTR_USER_PERM_MASK { 0x01C00000 };
32 static constexpr uint32_t ZIP_ATTR_GROUP_PERM_MASK { 0x03800000 };
33 static constexpr uint32_t ZIP_ATTR_OTHER_PERM_MASK { 0x00700000 };
34 static constexpr uint8_t ZIP_ATTR_USER_PERM_SHIFT { 22 };
35 static constexpr uint8_t ZIP_ATTR_GROUP_PERM_SHIFT { 19 };
36 static constexpr uint8_t ZIP_ATTR_OTHER_PERM_SHIFT { 16 };
37 
38 UnZip::UnZip(QString zipFileName)
39  : m_zipFileName(std::move(zipFileName))
40 {
41  int err { ZIP_ER_OK };
42  m_zip = zip_open(qPrintable(m_zipFileName), 0, &err);
43  if (m_zip != nullptr)
44  return;
45 
46  LOG(VB_GENERAL, LOG_ERR,
47  QString("UnZip: Unable to open zip file %1, error %2")
48  .arg(m_zipFileName).arg(err));
49 }
50 
52 {
53  int err = zip_close(m_zip);
54  if (err == 0)
55  return;
56 
57  LOG(VB_GENERAL, LOG_DEBUG,
58  QString("UnZip: Error closing zip file %1, error %2")
59  .arg(m_zipFileName).arg(err));
60 }
61 
63 {
64  zip_stat_init(&entry.m_stats);
65  if (-1 == zip_stat_index(m_zip, entry.m_index, 0, &entry.m_stats))
66  {
67  LOG(VB_GENERAL, LOG_ERR,
68  QString("UnZip: Can't get info for index %1 in %2")
69  .arg(entry.m_index).arg(m_zipFileName));
70  return false;
71  }
72  if ((entry.m_stats.valid & kSTATS_REQUIRED) != kSTATS_REQUIRED)
73  {
74  LOG(VB_GENERAL, LOG_ERR,
75  QString("UnZip: Invalid status for index %1 in %2")
76  .arg(entry.m_index).arg(m_zipFileName));
77  return false;
78  }
79  return true;
80 }
81 
83 {
84  zip_uint8_t opsys {ZIP_OPSYS_UNIX};// NOLINT(readability-uppercase-literal-suffix)
85 
86  entry.m_attributes = 0;
87  zip_file_get_external_attributes(m_zip, entry.m_index, 0, &opsys,
88  &entry.m_attributes);
89 }
90 
92 {
93  QDir dir = entry.m_fi.absoluteDir();
94  if (dir.exists())
95  return true;
96  if (dir.mkpath(dir.absolutePath()))
97  return true;
98  LOG(VB_GENERAL, LOG_ERR,
99  QString("UnZip: Failed to create directory %1")
100  .arg(dir.absolutePath()));
101  return false;
102 }
103 
104 // Validate that the filename is beneath the extraction directrory.
105 // This prevents a zip from overwriting arbitrary files by using names
106 // with a sequence of ".." directories.
107 bool UnZip::zipValidateFilename(const QFileInfo& fi)
108 {
109  if (fi.absoluteFilePath().startsWith(m_outDir.absolutePath()))
110  return true;
111  LOG(VB_GENERAL, LOG_ERR,
112  QString("UnZip: Attempt to write outside destination directory. File: %1")
113  .arg(QString(fi.fileName())));
114  return false;
115 }
116 
117 // Would be nice if Qt provided a unix perm to Qt perm conversion.
118 QFileDevice::Permissions UnZip::zipToQtPerms(const zipEntry& entry)
119 {
120  QFileDevice::Permissions qt_perms;
121  zip_uint32_t attrs = entry.m_attributes;
122 
124  int32_t group = (attrs & ZIP_ATTR_GROUP_PERM_MASK) >> ZIP_ATTR_GROUP_PERM_SHIFT;
125  int32_t other = (attrs & ZIP_ATTR_OTHER_PERM_MASK) >> ZIP_ATTR_OTHER_PERM_SHIFT;
126 
127  if (user & 4)
128  qt_perms |= (QFileDevice::ReadOwner | QFileDevice::ReadUser);
129  if (user & 2)
130  qt_perms |= (QFileDevice::WriteOwner | QFileDevice::WriteUser);
131  if (user & 1)
132  qt_perms |= (QFileDevice::ExeOwner | QFileDevice::ExeUser);
133  if (group & 4)
134  qt_perms |= QFileDevice::ReadGroup;
135  if (group & 2)
136  qt_perms |= QFileDevice::WriteGroup;
137  if (group & 1)
138  qt_perms |= QFileDevice::ExeGroup;
139  if (other & 4)
140  qt_perms |= QFileDevice::ReadOther;
141  if (other & 2)
142  qt_perms |= QFileDevice::WriteOther;
143  if (other & 1)
144  qt_perms |= QFileDevice::ExeOther;
145  return qt_perms;
146 }
147 
148 void UnZip::zipSetFileAttributes(const zipEntry& entry, QFile& outfile)
149 {
150  // Set times
151  auto dateTime = MythDate::fromSecsSinceEpoch(entry.m_stats.mtime);
152 
153  outfile.setFileTime(dateTime, QFileDevice::FileAccessTime);
154  outfile.setFileTime(dateTime, QFileDevice::FileBirthTime);
155  outfile.setFileTime(dateTime, QFileDevice::FileMetadataChangeTime);
156  outfile.setFileTime(dateTime, QFileDevice::FileModificationTime);
157 
158  if (entry.m_attributes == 0)
159  return;
160  outfile.setPermissions(zipToQtPerms(entry));
161 }
162 
164 {
165  zip_file_t *infile = zip_fopen_index(m_zip, entry.m_index, 0);
166  if (infile == nullptr)
167  {
168  LOG(VB_GENERAL, LOG_ERR,
169  QString("UnZip: Can't open index %1 in %2")
170  .arg(entry.m_index).arg(m_zipFileName));
171  return false;
172  }
173 
174  int64_t readLen {0};
175  static constexpr int BLOCK_SIZE { 4096 };
176  QByteArray data; data.resize(BLOCK_SIZE);
177  readLen = zip_fread(infile, data.data(), BLOCK_SIZE);
178  if (readLen < 1)
179  {
180  LOG(VB_GENERAL, LOG_ERR,
181  QString("UnZip: Invalid symlink name for index %1 in %2")
182  .arg(entry.m_index).arg(m_zipFileName));
183  return false;
184  }
185  data.resize(readLen);
186 
187  auto target = QFileInfo(entry.m_fi.absolutePath() + "/" + data);
188  if (!zipValidateFilename(target))
189  return false;
190  if (!QFile::link(target.absoluteFilePath(), entry.m_fi.absoluteFilePath()))
191  {
192  LOG(VB_GENERAL, LOG_ERR,
193  QString("UnZip: Failed to create symlink from %1 to %2")
194  .arg(entry.m_fi.absoluteFilePath(),
195  target.absoluteFilePath()));
196  return false;
197  }
198  return true;
199 }
200 
202 {
203  zip_file_t *infile = zip_fopen_index(m_zip, entry.m_index, 0);
204  if (infile == nullptr)
205  {
206  LOG(VB_GENERAL, LOG_ERR,
207  QString("UnZip: Can't open file at index %1 in %2")
208  .arg(entry.m_index).arg(m_zipFileName));
209  return false;
210  }
211 
212  auto outfile = QFile(entry.m_fi.absoluteFilePath());
213  if (!outfile.open(QIODevice::Truncate|QIODevice::WriteOnly))
214  {
215  LOG(VB_GENERAL, LOG_ERR,
216  QString("UnZip: Failed to open output file %1")
217  .arg(entry.m_fi.absoluteFilePath()));
218  return false;
219  }
220 
221  int64_t readLen {0};
222  uint64_t bytesRead {0};
223  uint64_t bytesWritten {0};
224  static constexpr int BLOCK_SIZE { 4096 };
225  QByteArray data; data.resize(BLOCK_SIZE);
226  while ((readLen = zip_fread(infile, data.data(), BLOCK_SIZE)) > 0)
227  {
228  bytesRead += readLen;
229  int64_t writeLen = outfile.write(data.data(), readLen);
230  if (writeLen < 0)
231  {
232  LOG(VB_GENERAL, LOG_ERR,
233  QString("UnZip: Failed to write %1/%2 bytes to output file %3")
234  .arg(writeLen).arg(readLen).arg(entry.m_fi.absoluteFilePath()));
235  return false;
236  }
237  bytesWritten += writeLen;
238  }
239 
240  if ((entry.m_stats.size != bytesRead) ||
241  (entry.m_stats.size != bytesWritten))
242  {
243  LOG(VB_GENERAL, LOG_ERR,
244  QString("UnZip: Failed to copy file %1. Read %2 and wrote %3 of %4.")
245  .arg(entry.m_fi.fileName()).arg(bytesRead).arg(bytesWritten)
246  .arg(entry.m_stats.size));
247  return false;
248  }
249 
250  outfile.flush();
251  zipSetFileAttributes(entry, outfile);
252  outfile.close();
253  if (zip_fclose(infile) == -1)
254  {
255  LOG(VB_GENERAL, LOG_ERR,
256  QString("UnZip: Failed to close file at index %1 in %2")
257  .arg(entry.m_index).arg(entry.m_fi.fileName()));
258  return false;
259  }
260 
261  return true;
262 }
263 
264 bool UnZip::extractFile(const QString& outDirName)
265 {
266  if (!isValid())
267  return false;
268 
269  m_outDir = QDir(outDirName);
270  if (!m_outDir.exists())
271  {
272  LOG(VB_GENERAL, LOG_ERR,
273  QString("UnZip: Target directory %1 doesn't exist")
274  .arg(outDirName));
275  return false;
276  }
277 
278  m_zipFileCount = zip_get_num_entries(m_zip, 0);
279  if (m_zipFileCount < 1)
280  {
281  LOG(VB_GENERAL, LOG_ERR,
282  QString("UnZip: Zip archive %1 is empty")
283  .arg(m_zipFileName));
284  return false;
285  }
286 
287  bool ok { true };
288  for (auto index = 0; ok && (index < m_zipFileCount); index++)
289  {
290  zipEntry entry;
291  entry.m_index = index;
292  if (!getEntryStats(entry))
293  return false;
294  if (entry.m_stats.encryption_method > 0)
295  {
296  LOG(VB_GENERAL, LOG_WARNING,
297  QString("UnZip: Skipping encryped file %1 in %2")
298  .arg(entry.m_index).arg(m_zipFileName));
299  continue;
300  }
301  getEntryAttrs(entry);
302 
303  entry.m_fi = QFileInfo(outDirName + '/' + entry.m_stats.name);
304  ok = zipValidateFilename(entry.m_fi);
305  if (ok)
306  ok = zipCreateDirectory(entry);
307  if (ok && (entry.m_stats.size > 0))
308  {
309  switch (entry.m_attributes & ZIP_ATTR_FILE_TYPE_MASK)
310  {
312  ok = zipCreateSymlink(entry);
313  break;
314  default:
315  ok = zipWriteOneFile(entry);
316  break;
317  }
318  }
319  }
320 
321  return ok;
322 }
UnZip::zipCreateDirectory
static bool zipCreateDirectory(const zipEntry &entry)
Definition: unzip2.cpp:91
UnZip::isValid
bool isValid()
Definition: unzip2.h:48
UnZip::zipToQtPerms
static QFileDevice::Permissions zipToQtPerms(const zipEntry &entry)
Definition: unzip2.cpp:118
ZIP_ATTR_GROUP_PERM_MASK
static constexpr uint32_t ZIP_ATTR_GROUP_PERM_MASK
Definition: unzip2.cpp:32
UnZip::UnZip
UnZip(QString zipFile)
Definition: unzip2.cpp:38
musicbrainzngs.musicbrainz.user
string user
Definition: musicbrainz.py:287
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
UnZip::zipValidateFilename
bool zipValidateFilename(const QFileInfo &fi)
Definition: unzip2.cpp:107
unzip2.h
UnZip::kSTATS_REQUIRED
static constexpr uint64_t kSTATS_REQUIRED
Definition: unzip2.h:32
UnZip::~UnZip
~UnZip()
Definition: unzip2.cpp:51
mythdate.h
MythDate::fromSecsSinceEpoch
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:72
UnZip::zipWriteOneFile
bool zipWriteOneFile(const zipEntry &entry)
Definition: unzip2.cpp:201
UnZip::getEntryStats
bool getEntryStats(zipEntry &entry)
Definition: unzip2.cpp:62
mythlogging.h
ZIP_ATTR_FILE_TYPE_MASK
static constexpr uint32_t ZIP_ATTR_FILE_TYPE_MASK
Definition: unzip2.cpp:27
UnZip::m_zip
zip_t * m_zip
Definition: unzip2.h:62
UnZip::zipEntry::m_attributes
zip_uint32_t m_attributes
Definition: unzip2.h:38
UnZip::zipSetFileAttributes
static void zipSetFileAttributes(const zipEntry &entry, QFile &outfile)
Definition: unzip2.cpp:148
ZIP_ATTR_USER_PERM_SHIFT
static constexpr uint8_t ZIP_ATTR_USER_PERM_SHIFT
Definition: unzip2.cpp:34
UnZip::m_zipFileName
QString m_zipFileName
Definition: unzip2.h:61
ZIP_ATTR_FILE_TYPE_SYMLINK
static constexpr uint32_t ZIP_ATTR_FILE_TYPE_SYMLINK
Definition: unzip2.cpp:28
UnZip::zipEntry::m_fi
QFileInfo m_fi
Definition: unzip2.h:39
UnZip::extractFile
bool extractFile(const QString &outDir)
Definition: unzip2.cpp:264
ZIP_ATTR_USER_PERM_MASK
static constexpr uint32_t ZIP_ATTR_USER_PERM_MASK
Definition: unzip2.cpp:31
UnZip::zipEntry::m_index
int m_index
Definition: unzip2.h:36
UnZip::getEntryAttrs
void getEntryAttrs(zipEntry &entry)
Definition: unzip2.cpp:82
ZIP_ATTR_OTHER_PERM_SHIFT
static constexpr uint8_t ZIP_ATTR_OTHER_PERM_SHIFT
Definition: unzip2.cpp:36
UnZip::m_outDir
QDir m_outDir
Definition: unzip2.h:58
UnZip::zipEntry
Definition: unzip2.h:34
UnZip::zipEntry::m_stats
zip_stat_t m_stats
Definition: unzip2.h:37
UnZip::m_zipFileCount
zip_int64_t m_zipFileCount
Definition: unzip2.h:63
UnZip::zipCreateSymlink
bool zipCreateSymlink(const zipEntry &entry)
Definition: unzip2.cpp:163
ZIP_ATTR_OTHER_PERM_MASK
static constexpr uint32_t ZIP_ATTR_OTHER_PERM_MASK
Definition: unzip2.cpp:33
ZIP_ATTR_GROUP_PERM_SHIFT
static constexpr uint8_t ZIP_ATTR_GROUP_PERM_SHIFT
Definition: unzip2.cpp:35