Fix QTemporaryDir to handle Unicode characters on Windows

For platforms not providing mkdtemp(), QTemporaryDir relied on an implementation
of q_mkdtemp() operating on char *, converting back and forth using
QFile::encodeName()/decodeName() when passing the name to QFileSystemEngine.
This caused failures on Windows (which uses "System"/Latin1 encoding)
for names containing characters outside the Latin1 space.

Reimplement q_mkdtemp() to operate on QString, which avoids the conversions
altogether and also enables the use of larger character spaces for the
pattern.

Add tests.

Task-number: QTBUG-54810
Change-Id: Ie4323ad73b5beb8a1b8ab81425f73d03c626d58a
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Friedemann Kleint 2016-07-19 10:18:39 +02:00
parent b2029e9ca6
commit c14c149b51
3 changed files with 111 additions and 21 deletions

View File

@ -44,8 +44,12 @@
#include "qcoreapplication.h"
#endif
#if !defined(Q_OS_QNX) && !defined(Q_OS_WIN) &&!defined(Q_OS_ANDROID)
# define USE_SYSTEM_MKDTEMP
#endif
#include <stdlib.h> // mkdtemp
#if defined(Q_OS_QNX) || defined(Q_OS_WIN) || defined(Q_OS_ANDROID)
#ifndef USE_SYSTEM_MKDTEMP
#include <private/qfilesystemengine_p.h>
#endif
@ -91,8 +95,7 @@ static QString defaultTemplateName()
return QDir::tempPath() + QLatin1Char('/') + baseName + QLatin1String("-XXXXXX");
}
#if defined(Q_OS_QNX ) || defined(Q_OS_WIN) || defined(Q_OS_ANDROID)
#ifndef USE_SYSTEM_MKDTEMP
static int nextRand(int &v)
{
int r = v % 62;
@ -102,30 +105,28 @@ static int nextRand(int &v)
return r;
}
QPair<QString, bool> q_mkdtemp(char *templateName)
QPair<QString, bool> q_mkdtemp(QString templateName)
{
Q_ASSERT(templateName.endsWith(QLatin1String("XXXXXX")));
static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const size_t length = strlen(templateName);
const int length = templateName.size();
char *XXXXXX = templateName + length - 6;
Q_ASSERT((length >= 6u) && strncmp(XXXXXX, "XXXXXX", 6) == 0);
QChar *XXXXXX = templateName.data() + length - 6;
for (int i = 0; i < 256; ++i) {
int v = qrand();
/* Fill in the random bits. */
XXXXXX[0] = letters[nextRand(v)];
XXXXXX[1] = letters[nextRand(v)];
XXXXXX[2] = letters[nextRand(v)];
XXXXXX[3] = letters[nextRand(v)];
XXXXXX[4] = letters[nextRand(v)];
XXXXXX[5] = letters[v % 62];
XXXXXX[0] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[1] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[2] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[3] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[4] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[5] = QLatin1Char(letters[v % 62]);
QString templateNameStr = QFile::decodeName(templateName);
QFileSystemEntry fileSystemEntry(templateNameStr);
QFileSystemEntry fileSystemEntry(templateName);
if (QFileSystemEngine::createDirectory(fileSystemEntry, false)) {
QSystemError error;
QFileSystemEngine::setPermissions(fileSystemEntry,
@ -134,10 +135,10 @@ QPair<QString, bool> q_mkdtemp(char *templateName)
QFile::ExeOwner, error);
if (error.error() != 0) {
if (!QFileSystemEngine::removeDirectory(fileSystemEntry, false))
qWarning() << "Unable to remove unused directory" << templateNameStr;
qWarning() << "Unable to remove unused directory" << templateName;
continue;
}
return qMakePair(QFile::decodeName(templateName), true);
return qMakePair(templateName, true);
}
# ifdef Q_OS_WIN
const int exists = ERROR_ALREADY_EXISTS;
@ -152,7 +153,7 @@ QPair<QString, bool> q_mkdtemp(char *templateName)
return qMakePair(qt_error_string(), false);
}
#else // defined(Q_OS_QNX ) || defined(Q_OS_WIN) || defined(Q_OS_ANDROID)
#else // !USE_SYSTEM_MKDTEMP
QPair<QString, bool> q_mkdtemp(char *templateName)
{
@ -160,14 +161,21 @@ QPair<QString, bool> q_mkdtemp(char *templateName)
return qMakePair(ok ? QFile::decodeName(templateName) : qt_error_string(), ok);
}
#endif
#endif // USE_SYSTEM_MKDTEMP
void QTemporaryDirPrivate::create(const QString &templateName)
{
#ifndef USE_SYSTEM_MKDTEMP
QString buffer = templateName;
if (!buffer.endsWith(QLatin1String("XXXXXX")))
buffer += QLatin1String("XXXXXX");
const QPair<QString, bool> result = q_mkdtemp(buffer);
#else // !USE_SYSTEM_MKDTEMP
QByteArray buffer = QFile::encodeName(templateName);
if (!buffer.endsWith("XXXXXX"))
buffer += "XXXXXX";
QPair<QString, bool> result = q_mkdtemp(buffer.data()); // modifies buffer
#endif // USE_SYSTEM_MKDTEMP
pathOrError = result.first;
success = result.second;
}

View File

@ -39,6 +39,7 @@
#include <qfile.h>
#include <qdir.h>
#include <qset.h>
#include <qtextcodec.h>
#ifdef Q_OS_WIN
# include <windows.h>
#endif
@ -113,6 +114,38 @@ void tst_QTemporaryDir::getSetCheck()
QCOMPARE(true, obj1.autoRemove());
}
static inline bool canHandleUnicodeFileNames()
{
#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
return true;
#else
// Check for UTF-8 by converting the Euro symbol (see tst_utf8)
return QFile::encodeName(QString(QChar(0x20AC))) == QByteArrayLiteral("\342\202\254");
#endif
}
static QString hanTestText()
{
QString text;
text += QChar(0x65B0);
text += QChar(0x5E10);
text += QChar(0x6237);
return text;
}
static QString umlautTestText()
{
QString text;
text += QChar(0xc4);
text += QChar(0xe4);
text += QChar(0xd6);
text += QChar(0xf6);
text += QChar(0xdc);
text += QChar(0xfc);
text += QChar(0xdf);
return text;
}
void tst_QTemporaryDir::fileTemplate_data()
{
QTest::addColumn<QString>("constructorTemplate");
@ -129,6 +162,14 @@ void tst_QTemporaryDir::fileTemplate_data()
QTest::newRow("constructor with XXXX suffix") << "qt_XXXXXX_XXXX" << "qt_";
QTest::newRow("constructor with XXXX prefix") << "qt_XXXX" << "qt_";
QTest::newRow("constructor with XXXXX prefix") << "qt_XXXXX" << "qt_";
if (canHandleUnicodeFileNames()) {
// Test Umlauts (contained in Latin1)
QString prefix = "qt_" + umlautTestText();
QTest::newRow("Umlauts") << (prefix + "XXXXXX") << prefix;
// Test Chinese
prefix = "qt_" + hanTestText();
QTest::newRow("Chinese characters") << (prefix + "XXXXXX") << prefix;
}
}
void tst_QTemporaryDir::fileTemplate()

View File

@ -39,6 +39,7 @@
#include <qfile.h>
#include <qdir.h>
#include <qset.h>
#include <qtextcodec.h>
#if defined(Q_OS_WIN)
# include <windows.h>
@ -145,6 +146,38 @@ void tst_QTemporaryFile::getSetCheck()
QCOMPARE(true, obj1.autoRemove());
}
static inline bool canHandleUnicodeFileNames()
{
#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
return true;
#else
// Check for UTF-8 by converting the Euro symbol (see tst_utf8)
return QFile::encodeName(QString(QChar(0x20AC))) == QByteArrayLiteral("\342\202\254");
#endif
}
static QString hanTestText()
{
QString text;
text += QChar(0x65B0);
text += QChar(0x5E10);
text += QChar(0x6237);
return text;
}
static QString umlautTestText()
{
QString text;
text += QChar(0xc4);
text += QChar(0xe4);
text += QChar(0xd6);
text += QChar(0xf6);
text += QChar(0xdc);
text += QChar(0xfc);
text += QChar(0xdf);
return text;
}
void tst_QTemporaryFile::fileTemplate_data()
{
QTest::addColumn<QString>("constructorTemplate");
@ -171,6 +204,14 @@ void tst_QTemporaryFile::fileTemplate_data()
QTest::newRow("set template, with xxx") << "" << "qt_" << ".xxx" << "qt_XXXXXX.xxx";
QTest::newRow("set template, with >6 X's") << "" << "qt_" << ".xxx" << "qt_XXXXXXXXXXXXXX.xxx";
QTest::newRow("set template, with >6 X's, no suffix") << "" << "qt_" << "" << "qt_XXXXXXXXXXXXXX";
if (canHandleUnicodeFileNames()) {
// Test Umlauts (contained in Latin1)
QString prefix = "qt_" + umlautTestText();
QTest::newRow("Umlauts") << (prefix + "XXXXXX") << prefix << QString() << QString();
// Test Chinese
prefix = "qt_" + hanTestText();
QTest::newRow("Chinese characters") << (prefix + "XXXXXX") << prefix << QString() << QString();
}
}
void tst_QTemporaryFile::fileTemplate()