summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/libs/installer/libarchivearchive.cpp268
-rw-r--r--tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp66
2 files changed, 297 insertions, 37 deletions
diff --git a/src/libs/installer/libarchivearchive.cpp b/src/libs/installer/libarchivearchive.cpp
index 09bcb7544..b912f19df 100644
--- a/src/libs/installer/libarchivearchive.cpp
+++ b/src/libs/installer/libarchivearchive.cpp
@@ -64,6 +64,184 @@ namespace QInstaller {
\internal
*/
+namespace ArchiveEntryPaths {
+
+// TODO: it is expected that the filename handling will change in the major
+// version jump to libarchive 4.x, and the *_w methods will disappear.
+
+/*!
+ \internal
+
+ Returns the path name from the archive \a entry as a \c QString. The path is
+ stored internally in a multistring that can contain a combination of a wide
+ character or multibyte string in current locale or unicode string encoded as UTF-8.
+
+ \note The MBS version is expected to be convertable from latin-1 which might
+ not actually be the case, as the encoding depends on the current locale.
+*/
+static QString pathname(archive_entry *const entry)
+{
+ if (!entry)
+ return QString();
+#ifdef Q_OS_WIN
+ if (const wchar_t *path = archive_entry_pathname_w(entry))
+ return QString::fromWCharArray(path);
+#endif
+ if (const char *path = archive_entry_pathname_utf8(entry))
+ return QString::fromUtf8(path);
+
+ return QString::fromLatin1(archive_entry_pathname(entry));
+}
+
+/*!
+ \internal
+
+ Sets the path name for the archive \a entry to \a path. This function
+ tries to update all variants of the string in the internal multistring
+ struct.
+*/
+static void setPathname(archive_entry *const entry, const QString &path)
+{
+ if (!entry)
+ return;
+
+ // Try updating all variants at once, stops on failure
+ if (archive_entry_update_pathname_utf8(entry, path.toUtf8()))
+ return;
+
+ // If that does not work, then set them individually
+ archive_entry_set_pathname(entry, path.toLatin1());
+ archive_entry_set_pathname_utf8(entry, path.toUtf8());
+#ifdef Q_OS_WIN
+ wchar_t *wpath = new wchar_t[path.length() + 1];
+ path.toWCharArray(wpath);
+ wpath[path.length()] = '\0';
+
+ archive_entry_copy_pathname_w(entry, wpath);
+ delete[] wpath;
+#endif
+}
+
+/*!
+ \internal
+
+ Returns the source path on disk from the current archive \a entry as a \c QString.
+ The path is stored internally in a multistring that can contain a combination
+ of a wide character or multibyte string in current locale.
+
+ \note The MBS version is expected to be convertable from UTF-8.
+*/
+static QString sourcepath(archive_entry * const entry)
+{
+ if (!entry)
+ return QString();
+#ifdef Q_OS_WIN
+ if (const wchar_t *path = archive_entry_sourcepath_w(entry))
+ return QString::fromWCharArray(path);
+#endif
+ return QString::fromUtf8(archive_entry_sourcepath(entry));
+}
+
+/*!
+ \internal
+
+ Returns the hardlink path from the current archive \a entry as a \c QString.
+ The path is stored internally in a multistring that can contain a combination of a wide
+ character or multibyte string in current locale or unicode string encoded as UTF-8.
+
+ \note The MBS version is expected to be convertable from latin-1 which might
+ not actually be the case, as the encoding depends on the current locale.
+*/
+static QString hardlink(archive_entry * const entry)
+{
+ if (!entry)
+ return QString();
+#ifdef Q_OS_WIN
+ if (const wchar_t *path = archive_entry_hardlink_w(entry))
+ return QString::fromWCharArray(path);
+#endif
+ if (const char *path = archive_entry_hardlink_utf8(entry))
+ return QString::fromUtf8(path);
+
+ return QString::fromLatin1(archive_entry_hardlink(entry));
+}
+
+/*!
+ \internal
+
+ Sets the hard link path for the archive \a entry to \a path. This function
+ tries to update all variants of the string in the internal multistring
+ struct.
+*/
+static void setHardlink(archive_entry *const entry, const QString &path)
+{
+ if (!entry)
+ return;
+
+ // Try updating all variants at once, stops on failure
+ if (archive_entry_update_hardlink_utf8(entry, path.toUtf8()))
+ return;
+
+ // If that does not work, then set them individually
+ archive_entry_set_hardlink(entry, path.toLatin1());
+ archive_entry_set_hardlink_utf8(entry, path.toUtf8());
+#ifdef Q_OS_WIN
+ wchar_t *wpath = new wchar_t[path.length() + 1];
+ path.toWCharArray(wpath);
+ wpath[path.length()] = '\0';
+
+ archive_entry_copy_hardlink_w(entry, wpath);
+ delete[] wpath;
+#endif
+}
+
+/*!
+ \internal
+
+ Calls a function object or pointer \a func with any number of extra
+ arguments \a args. Returns the return value of the function of type T.
+
+ On Windows, this changes the locale category LC_CTYPE from C to system
+ locale. The original LC_CTYPE is restored after the function call.
+ Currently the locale is unchanged on other platforms.
+*/
+template <typename T, typename F, typename... Args>
+static T callWithSystemLocale(F func, Args... args)
+{
+#ifdef Q_OS_WIN
+ const QByteArray oldLocale = setlocale(LC_CTYPE, "");
+#endif
+ T returnValue = func(std::forward<Args>(args)...);
+#ifdef Q_OS_WIN
+ setlocale(LC_CTYPE, oldLocale.constData());
+#endif
+ return returnValue;
+}
+
+/*!
+ \internal
+
+ Calls a function object or pointer \a func with any number of extra
+ arguments \a args.
+
+ On Windows, this changes the locale category LC_CTYPE from C to system
+ locale. The original LC_CTYPE is restored after the function call.
+ Currently the locale is unchanged on other platforms.
+*/
+template <typename F, typename... Args>
+static void callWithSystemLocale(F func, Args... args)
+{
+#ifdef Q_OS_WIN
+ const QByteArray oldLocale = setlocale(LC_CTYPE, "");
+#endif
+ func(std::forward<Args>(args)...);
+#ifdef Q_OS_WIN
+ setlocale(LC_CTYPE, oldLocale.constData());
+#endif
+}
+
+} // namespace ArchiveEntryPaths
+
/*!
\inmodule QtInstallerFramework
\class QInstaller::ExtractWorker
@@ -116,7 +294,7 @@ void ExtractWorker::extract(const QString &dirPath, const quint64 totalFiles)
emit finished(QLatin1String("Extract canceled."));
return;
}
- status = archive_read_next_header(reader.get(), &entry);
+ status = ArchiveEntryPaths::callWithSystemLocale<int>(archive_read_next_header, reader.get(), &entry);
if (status == ARCHIVE_EOF)
break;
if (status != ARCHIVE_OK) {
@@ -124,14 +302,14 @@ void ExtractWorker::extract(const QString &dirPath, const quint64 totalFiles)
emit finished(LibArchiveArchive::errorStringWithCode(reader.get()));
return;
}
- const char *current = archive_entry_pathname(entry);
- const QString outputPath = dirPath + QDir::separator() + QString::fromLocal8Bit(current);
- archive_entry_set_pathname(entry, outputPath.toLocal8Bit());
-
- const char *hardlink = archive_entry_hardlink(entry);
- if (hardlink) {
- const QString hardLinkPath = dirPath + QDir::separator() + QString::fromLocal8Bit(hardlink);
- archive_entry_set_hardlink(entry, hardLinkPath.toLocal8Bit());
+ const QString current = ArchiveEntryPaths::callWithSystemLocale<QString>(ArchiveEntryPaths::pathname, entry);
+ const QString outputPath = dirPath + QDir::separator() + current;
+ ArchiveEntryPaths::callWithSystemLocale(&ArchiveEntryPaths::setPathname, entry, outputPath);
+
+ const QString hardlink = ArchiveEntryPaths::callWithSystemLocale<QString>(ArchiveEntryPaths::hardlink, entry);
+ if (!hardlink.isEmpty()) {
+ const QString hardLinkPath = dirPath + QDir::separator() + hardlink;
+ ArchiveEntryPaths::callWithSystemLocale(ArchiveEntryPaths::setHardlink, entry, hardLinkPath);
}
emit currentEntryChanged(outputPath);
@@ -441,20 +619,20 @@ bool LibArchiveArchive::extract(const QString &dirPath, const quint64 totalFiles
if (m_cancelScheduled)
throw Error(QLatin1String("Extract canceled."));
- status = archive_read_next_header(reader.get(), &entry);
+ status = ArchiveEntryPaths::callWithSystemLocale<int>(archive_read_next_header, reader.get(), &entry);
if (status == ARCHIVE_EOF)
break;
if (status != ARCHIVE_OK)
throw Error(errorStringWithCode(reader.get()));
- const char *current = archive_entry_pathname(entry);
- const QString outputPath = dirPath + QDir::separator() + QString::fromLocal8Bit(current);
- archive_entry_set_pathname(entry, outputPath.toLocal8Bit());
+ const QString current = ArchiveEntryPaths::callWithSystemLocale<QString>(ArchiveEntryPaths::pathname, entry);
+ const QString outputPath = dirPath + QDir::separator() + current;
+ ArchiveEntryPaths::callWithSystemLocale(ArchiveEntryPaths::setPathname, entry, outputPath);
- const char *hardlink = archive_entry_hardlink(entry);
- if (hardlink) {
- const QString hardLinkPath = dirPath + QDir::separator() + QString::fromLocal8Bit(hardlink);
- archive_entry_set_hardlink(entry, hardLinkPath.toLocal8Bit());
+ const QString hardlink = ArchiveEntryPaths::callWithSystemLocale<QString>(ArchiveEntryPaths::hardlink, entry);
+ if (!hardlink.isEmpty()) {
+ const QString hardLinkPath = dirPath + QDir::separator() + hardlink;
+ ArchiveEntryPaths::callWithSystemLocale(ArchiveEntryPaths::setHardlink, entry, hardLinkPath);
}
emit currentEntryChanged(outputPath);
@@ -508,29 +686,50 @@ bool LibArchiveArchive::create(const QStringList &data)
try {
int status;
- if ((status = archive_write_open_filename(writer.get(), m_data->file.fileName().toLocal8Bit())))
- throw Error(errorStringWithCode(writer.get()));
+#ifdef Q_OS_WIN
+ QScopedPointer<wchar_t, QScopedPointerArrayDeleter<wchar_t>> fileName_w(
+ new wchar_t[m_data->file.fileName().length() + 1]);
+
+ m_data->file.fileName().toWCharArray(fileName_w.get());
+ fileName_w.get()[m_data->file.fileName().length()] = '\0';
+ if ((status = archive_write_open_filename_w(writer.get(), fileName_w.get())))
+ throw Error(errorStringWithCode(writer.get()));
+#else
+ if ((status = archive_write_open_filename(writer.get(), m_data->file.fileName().toUtf8())))
+ throw Error(errorStringWithCode(writer.get()));
+#endif
for (auto &dataEntry : globbedData) {
QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_disk_new());
configureDiskReader(reader.get());
- if ((status = archive_read_disk_open(reader.get(), dataEntry.toLocal8Bit())))
- throw Error(errorStringWithCode(reader.get()));
+#ifdef Q_OS_WIN
+ QScopedPointer<wchar_t, QScopedPointerArrayDeleter<wchar_t>> dataEntry_w(
+ new wchar_t[dataEntry.length() + 1]);
+
+ dataEntry.toWCharArray(dataEntry_w.get());
+ dataEntry_w.get()[dataEntry.length()] = '\0';
+ if ((status = archive_read_disk_open_w(reader.get(), dataEntry_w.get())))
+ throw Error(errorStringWithCode(reader.get()));
+#else
+ if ((status = archive_read_disk_open(reader.get(), dataEntry.toUtf8())))
+ throw Error(errorStringWithCode(reader.get()));
+#endif
QDir basePath = QFileInfo(dataEntry).dir();
forever {
QScopedPointer<archive_entry, ScopedPointerEntryDeleter> entry(archive_entry_new());
- status = archive_read_next_header2(reader.get(), entry.get());
+ status = ArchiveEntryPaths::callWithSystemLocale<int>(archive_read_next_header2, reader.get(), entry.get());
if (status == ARCHIVE_EOF)
break;
if (status != ARCHIVE_OK)
throw Error(errorStringWithCode(reader.get()));
- const QFileInfo fileOrDir(pathWithoutNamespace(QLatin1String(archive_entry_sourcepath(entry.get()))));
+ const QFileInfo fileOrDir(pathWithoutNamespace(
+ ArchiveEntryPaths::callWithSystemLocale<QString>(ArchiveEntryPaths::sourcepath, entry.get())));
// Set new path name in archive, otherwise we add all directories from absolute path
const QString newPath = basePath.relativeFilePath(fileOrDir.filePath());
- archive_entry_set_pathname(entry.get(), newPath.toLocal8Bit());
+ ArchiveEntryPaths::callWithSystemLocale(ArchiveEntryPaths::setPathname, entry.get(), newPath);
archive_read_disk_descend(reader.get());
status = archive_write_header(writer.get(), entry.get());
@@ -540,7 +739,8 @@ bool LibArchiveArchive::create(const QStringList &data)
if (fileOrDir.isDir() || archive_entry_size(entry.get()) == 0)
continue; // nothing to copy
- QFile file(pathWithoutNamespace(QLatin1String(archive_entry_sourcepath(entry.get()))));
+ QFile file(pathWithoutNamespace(ArchiveEntryPaths::callWithSystemLocale<QString>(
+ ArchiveEntryPaths::sourcepath, entry.get())));
if (!file.open(QIODevice::ReadOnly))
throw Error(file.errorString());
@@ -583,14 +783,14 @@ QVector<ArchiveEntry> LibArchiveArchive::list()
throw Error(errorStringWithCode(reader.get()));
forever {
- status = archive_read_next_header(reader.get(), &entry);
+ status = ArchiveEntryPaths::callWithSystemLocale<int>(archive_read_next_header, reader.get(), &entry);
if (status == ARCHIVE_EOF)
break;
if (status != ARCHIVE_OK)
throw Error(errorStringWithCode(reader.get()));
ArchiveEntry archiveEntry;
- archiveEntry.path = QLatin1String(archive_entry_pathname(entry));
+ archiveEntry.path = ArchiveEntryPaths::callWithSystemLocale<QString>(ArchiveEntryPaths::pathname, entry);
archiveEntry.utcTime = QDateTime::fromTime_t(archive_entry_mtime(entry));
archiveEntry.isDirectory = (archive_entry_filetype(entry) == AE_IFDIR);
archiveEntry.isSymbolicLink = (archive_entry_filetype(entry) == AE_IFLNK);
@@ -724,15 +924,19 @@ void LibArchiveArchive::configureWriter(archive *archive)
// The Qt board support package file extension is really a 7z.
archive_write_set_format_7zip(archive);
} else {
- archive_write_set_format_filter_by_ext(archive, fileName.toLatin1());
+ archive_write_set_format_filter_by_ext(archive, fileName.toUtf8());
}
+ const QByteArray charset = "hdrcharset=UTF-8";
+ // not checked as this is ignored on some archive formats like 7z
+ archive_write_set_options(archive, charset);
+
if (compressionLevel() == CompressionLevel::Normal)
return;
- const QByteArray options = "compression-level=" + QString::number(compressionLevel()).toLatin1();
- if (archive_write_set_options(archive, options.constData())) { // not fatal
- qCWarning(QInstaller::lcInstallerInstallLog) << "Could not set options" << options
+ const QByteArray compression = "compression-level=" + QString::number(compressionLevel()).toLatin1();
+ if (archive_write_set_options(archive, compression.constData())) { // not fatal
+ qCWarning(QInstaller::lcInstallerInstallLog) << "Could not set option" << compression
<< "for archive" << m_data->file.fileName() << ":" << errorStringWithCode(archive);
}
}
@@ -964,7 +1168,7 @@ quint64 LibArchiveArchive::totalFiles()
throw Error(errorStringWithCode(reader.get()));
forever {
- status = archive_read_next_header(reader.get(), &entry);
+ status = ArchiveEntryPaths::callWithSystemLocale<int>(archive_read_next_header, reader.get(), &entry);
if (status == ARCHIVE_EOF)
break;
if (status != ARCHIVE_OK)
diff --git a/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp b/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp
index 57f16b762..ab7030e2f 100644
--- a/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp
+++ b/tests/auto/installer/libarchivearchive/tst_libarchivearchive.cpp
@@ -99,8 +99,10 @@ private slots:
const QString filename = generateTemporaryFileName() + suffix;
LibArchiveArchive target(filename);
- QVERIFY(target.open(QIODevice::ReadWrite));
+ QVERIFY(target.open(QIODevice::WriteOnly));
QVERIFY(target.create(QStringList() << path1 << path2));
+ target.close();
+ QVERIFY(target.open(QIODevice::ReadOnly));
QCOMPARE(target.list().count(), 2);
target.close();
QVERIFY(QFile(filename).remove());
@@ -115,7 +117,7 @@ private slots:
{
QFETCH(QString, suffix);
- const QString baseDir(QDir::tempPath() + "/tst_libarchivearchive");
+ const QString baseDir(generateTemporaryFileName(QDir::tempPath() + "/tst_libarchivearchive.XXXXXX"));
QVERIFY(QDir().mkpath(baseDir));
const QString path1 = tempSourceFile(
@@ -129,8 +131,10 @@ private slots:
const QString filename = generateTemporaryFileName() + suffix;
LibArchiveArchive target(filename);
- QVERIFY(target.open(QIODevice::ReadWrite));
+ QVERIFY(target.open(QIODevice::WriteOnly));
QVERIFY(target.create(QStringList() << baseDir + "/*"));
+ target.close();
+ QVERIFY(target.open(QIODevice::ReadOnly));
QCOMPARE(target.list().count(), 2);
target.close();
@@ -159,8 +163,10 @@ private slots:
const QString filename = QDir::tempPath() + "/target file with spaces" + suffix;
LibArchiveArchive target(filename);
target.setFilename(filename);
- QVERIFY(target.open(QIODevice::ReadWrite));
+ QVERIFY(target.open(QIODevice::WriteOnly));
QVERIFY(target.create(QStringList() << path1 << path2));
+ target.close();
+ QVERIFY(target.open(QIODevice::ReadOnly));
QCOMPARE(target.list().count(), 2);
target.close();
QVERIFY(QFile(filename).remove());
@@ -211,10 +217,12 @@ private slots:
QVERIFY(QFile::link(source.fileName(), linkName));
LibArchiveArchive archive(archiveName);
- QVERIFY(archive.open(QIODevice::ReadWrite));
+ QVERIFY(archive.open(QIODevice::WriteOnly));
QVERIFY(archive.create(QStringList() << source.fileName() << linkName));
QVERIFY(QFileInfo::exists(archiveName));
+ archive.close();
+ QVERIFY(archive.open(QIODevice::ReadOnly));
QVERIFY(archive.extract(targetName));
const QString sourceFilename = QFileInfo(source.fileName()).fileName();
const QString linkFilename = QFileInfo(linkName).fileName();
@@ -237,6 +245,54 @@ private slots:
removeDirectory(workingDir, true);
}
+ void testCreateExtractWithUnicodePaths_data()
+ {
+ archiveSuffixesTestData();
+ }
+
+ void testCreateExtractWithUnicodePaths()
+ {
+ QFETCH(QString, suffix);
+
+ const QString targetName = generateTemporaryFileName() + QDir::separator();
+ const QString archiveName = QDir::tempPath() + "/test_archive" + suffix;
+
+ const QString path1 = tempSourceFile(
+ "Source File 1.",
+ QDir::tempPath() + QString::fromUtf8("/测试文件.XXXXXX")
+ );
+ const QString path2 = tempSourceFile(
+ "Source File 2.",
+ QDir::tempPath() + QString::fromUtf8("/тестовый файл.XXXXXX")
+ );
+ const QString path3 = tempSourceFile(
+ "Source File 3.",
+ QDir::tempPath() + QString::fromUtf8("/ملف الاختبار.XXXXXX")
+ );
+
+ LibArchiveArchive archive(archiveName);
+ archive.setFilename(archiveName);
+ QVERIFY(archive.open(QIODevice::WriteOnly));
+ QVERIFY(archive.create(QStringList() << path1 << path2 << path3));
+ archive.close();
+ QVERIFY(archive.open(QIODevice::ReadOnly));
+ QCOMPARE(archive.list().count(), 3);
+
+ QVERIFY(archive.extract(targetName));
+
+ const QString targetPath1 = targetName + QFileInfo(path1).fileName();
+ const QString targetPath2 = targetName + QFileInfo(path2).fileName();
+ const QString targetPath3 = targetName + QFileInfo(path3).fileName();
+
+ QVERIFY(QFileInfo::exists(targetPath1));
+ QVERIFY(QFileInfo::exists(targetPath2));
+ QVERIFY(QFileInfo::exists(targetPath3));
+
+ archive.close();
+ QVERIFY(QFile::remove(archiveName));
+ QVERIFY(QDir(targetName).removeRecursively());
+ }
+
private:
void archiveFilenamesTestData()
{