File indexing completed on 2024-11-10 04:15:45
0001 /* This file is part of the KDE project 0002 Copyright (C) 2003-2019 Jarosław Staniek <staniek@kde.org> 0003 0004 Portions of kstandarddirs.cpp: 0005 Copyright (C) 1999 Sirtaj Singh Kang <taj@kde.org> 0006 Copyright (C) 1999,2007 Stephan Kulow <coolo@kde.org> 0007 Copyright (C) 1999 Waldo Bastian <bastian@kde.org> 0008 Copyright (C) 2009 David Faure <faure@kde.org> 0009 0010 Portions of kshell.cpp: 0011 Copyright (c) 2003,2007 Oswald Buddenhagen <ossi@kde.org> 0012 0013 This program is free software; you can redistribute it and/or 0014 modify it under the terms of the GNU Library General Public 0015 License as published by the Free Software Foundation; either 0016 version 2 of the License, or (at your option) any later version. 0017 0018 This program is distributed in the hope that it will be useful, 0019 but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0021 Library General Public License for more details. 0022 0023 You should have received a copy of the GNU Library General Public License 0024 along with this program; see the file COPYING. If not, write to 0025 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0026 * Boston, MA 02110-1301, USA. 0027 */ 0028 0029 #include "KDbUtils.h" 0030 #include "KDb.h" 0031 #include "KDbConnection.h" 0032 #include "KDbDriverManager.h" 0033 #include "KDbUtils_p.h" 0034 #include "config-kdb.h" 0035 #include "kdb_debug.h" 0036 0037 #include <QRegularExpression> 0038 #include <QDataStream> 0039 #include <QDir> 0040 #include <QFile> 0041 #include <QFileInfo> 0042 0043 static const int SQUEEZED_TEXT_LIMIT = 1024; 0044 static const int SQUEEZED_TEXT_SUFFIX = 24; 0045 0046 #ifdef Q_OS_WIN 0047 #include <windows.h> 0048 #ifdef _WIN32_WCE 0049 #include <basetyps.h> 0050 #endif 0051 #ifdef Q_OS_WIN64 0052 //! @todo did not find a reliable way to fix with kdewin mingw header 0053 #define interface struct 0054 #endif 0055 #endif 0056 0057 using namespace KDbUtils; 0058 0059 class Q_DECL_HIDDEN Property::Private 0060 { 0061 public: 0062 Private() : isNull(true) {} 0063 Private(const QVariant &aValue, const QString &aCaption) 0064 : value(aValue), caption(aCaption), isNull(false) 0065 { 0066 } 0067 bool operator==(const Private &other) const { 0068 return std::tie(value, caption, isNull) == std::tie(other.value, other.caption, other.isNull); 0069 } 0070 QVariant value; //!< Property value 0071 QString caption; //!< User visible property caption 0072 bool isNull; 0073 }; 0074 0075 Property::Property() 0076 : d(new Private) 0077 { 0078 } 0079 0080 Property::Property(const QVariant &value, const QString &caption) 0081 : d(new Private(value, caption)) 0082 { 0083 } 0084 0085 Property::Property(const Property &other) 0086 : d(new Private(*other.d)) 0087 { 0088 } 0089 0090 Property::~Property() 0091 { 0092 delete d; 0093 } 0094 0095 bool Property::operator==(const Property &other) const 0096 { 0097 return *d == *other.d; 0098 } 0099 0100 bool Property::isNull() const 0101 { 0102 return d->isNull; 0103 } 0104 0105 QVariant Property::value() const 0106 { 0107 return d->value; 0108 } 0109 0110 void Property::setValue(const QVariant &value) 0111 { 0112 d->value = value; 0113 d->isNull = false; 0114 } 0115 0116 QString Property::caption() const 0117 { 0118 return d->caption; 0119 } 0120 0121 void Property::setCaption(const QString &caption) 0122 { 0123 d->caption = caption; 0124 d->isNull = false; 0125 } 0126 0127 //--------- 0128 0129 bool KDbUtils::hasParent(QObject *par, QObject *o) 0130 { 0131 if (!o || !par) { 0132 return false; 0133 } 0134 while (o && o != par) { 0135 o = o->parent(); 0136 } 0137 return o == par; 0138 } 0139 0140 QString KDbUtils::toISODateStringWithMs(const QTime& time) 0141 { 0142 #ifdef HAVE_QT_ISODATEWITHMS 0143 return time.toString(Qt::ISODateWithMs); 0144 #else 0145 QString result; 0146 if (time.isValid()) { 0147 result = QString::asprintf("%02d:%02d:%02d.%03d", time.hour(), time.minute(), time.second(), 0148 time.msec()); 0149 } 0150 return result; 0151 #endif 0152 } 0153 0154 QString KDbUtils::toISODateStringWithMs(const QDateTime& dateTime) 0155 { 0156 #ifdef HAVE_QT_ISODATEWITHMS 0157 return dateTime.toString(Qt::ISODateWithMs); 0158 #else 0159 QString result; 0160 if (!dateTime.isValid()) { 0161 return result; 0162 } 0163 result = dateTime.toString(Qt::ISODate); 0164 if (result.isEmpty()) { // failure 0165 return result; 0166 } 0167 QString timeString = KDbUtils::toISODateStringWithMs(dateTime.time()); 0168 if (timeString.isEmpty()) { // failure 0169 return QString(); 0170 } 0171 const int offset = strlen("0000-00-00T"); 0172 const int timeLen = strlen("00:00:00"); 0173 result.replace(offset, timeLen, timeString); // replace time with time+ms 0174 return result; 0175 #endif 0176 } 0177 0178 QTime KDbUtils::timeFromISODateStringWithMs(const QString &string) 0179 { 0180 #ifdef HAVE_QT_ISODATEWITHMS 0181 return QTime::fromString(string, Qt::ISODateWithMs); 0182 #else 0183 return QTime::fromString(string, Qt::ISODate); // supports HH:mm:ss.zzzzz already 0184 #endif 0185 } 0186 0187 QDateTime KDbUtils::dateTimeFromISODateStringWithMs(const QString &string) 0188 { 0189 #ifdef HAVE_QT_ISODATEWITHMS 0190 return QDateTime::fromString(string, Qt::ISODateWithMs); 0191 #else 0192 return QDateTime::fromString(string, Qt::ISODate); // supports HH:mm:ss.zzzzz already 0193 #endif 0194 } 0195 0196 QDateTime KDbUtils::stringToHackedQTime(const QString &s) 0197 { 0198 if (s.isEmpty()) { 0199 return QDateTime(); 0200 } 0201 return QDateTime(QDate(0, 1, 2), KDbUtils::timeFromISODateStringWithMs(s)); 0202 } 0203 0204 void KDbUtils::serializeMap(const QMap<QString, QString>& map, QByteArray *array) 0205 { 0206 if (!array) { 0207 return; 0208 } 0209 QDataStream ds(array, QIODevice::WriteOnly); 0210 ds.setVersion(QDataStream::Qt_3_1); 0211 ds << map; 0212 } 0213 0214 void KDbUtils::serializeMap(const QMap<QString, QString>& map, QString *string) 0215 { 0216 if (!string) { 0217 return; 0218 } 0219 QByteArray array; 0220 QDataStream ds(&array, QIODevice::WriteOnly); 0221 ds.setVersion(QDataStream::Qt_3_1); 0222 ds << map; 0223 kdbDebug() << array[3] << array[4] << array[5]; 0224 const int size = array.size(); 0225 string->clear(); 0226 string->reserve(size); 0227 for (int i = 0; i < size; i++) { 0228 (*string)[i] = QChar(ushort(array[i]) + 1); 0229 } 0230 } 0231 0232 QMap<QString, QString> KDbUtils::deserializeMap(const QByteArray& array) 0233 { 0234 QMap<QString, QString> map; 0235 QByteArray ba(array); 0236 QDataStream ds(&ba, QIODevice::ReadOnly); 0237 ds.setVersion(QDataStream::Qt_3_1); 0238 ds >> map; 0239 return map; 0240 } 0241 0242 QMap<QString, QString> KDbUtils::deserializeMap(const QString& string) 0243 { 0244 QByteArray array; 0245 const int size = string.length(); 0246 array.resize(size); 0247 for (int i = 0; i < size; i++) { 0248 array[i] = char(string[i].unicode() - 1); 0249 } 0250 QMap<QString, QString> map; 0251 QDataStream ds(&array, QIODevice::ReadOnly); 0252 ds.setVersion(QDataStream::Qt_3_1); 0253 ds >> map; 0254 return map; 0255 } 0256 0257 QString KDbUtils::stringToFileName(const QString& string) 0258 { 0259 QString _string(string); 0260 static const QRegularExpression re(QLatin1String("[\\\\/:\\*?\"<>|]")); 0261 _string.replace(re, QLatin1String(" ")); 0262 if (_string.startsWith(QLatin1Char('.'))) { 0263 _string.prepend(QLatin1Char('_')); 0264 } 0265 return _string.simplified(); 0266 } 0267 0268 void KDbUtils::simpleCrypt(QString *string) 0269 { 0270 if (!string) { 0271 return; 0272 } 0273 for (int i = 0; i < string->length(); i++) { 0274 ushort& unicode = (*string)[i].unicode(); 0275 unicode += (47 + i); 0276 } 0277 } 0278 0279 bool KDbUtils::simpleDecrypt(QString *string) 0280 { 0281 if (!string) { 0282 return false; 0283 } 0284 QString result(*string); 0285 for (int i = 0; i < result.length(); i++) { 0286 ushort& unicode = result[i].unicode(); 0287 if (unicode <= (47 + i)) { 0288 return false; 0289 } 0290 unicode -= (47 + i); 0291 } 0292 *string = result; 0293 return true; 0294 } 0295 0296 QString KDbUtils::pointerToStringInternal(void* pointer, int size) 0297 { 0298 QString string; 0299 unsigned char* cstr_pointer = (unsigned char*) & pointer; 0300 for (int i = 0; i < size; i++) { 0301 QString s; 0302 s.asprintf("%2.2x", cstr_pointer[i]); 0303 string.append(s); 0304 } 0305 return string; 0306 } 0307 0308 void* KDbUtils::stringToPointerInternal(const QString& string, int size) 0309 { 0310 if ((string.length() / 2) < size) 0311 return nullptr; 0312 QByteArray array; 0313 array.resize(size); 0314 bool ok; 0315 for (int i = 0; i < size; i++) { 0316 array[i] = (unsigned char)(string.midRef(i * 2, 2).toUInt(&ok, 16)); 0317 if (!ok) 0318 return nullptr; 0319 } 0320 return static_cast<void*>(array.data()); 0321 } 0322 0323 //--------- 0324 0325 //! @internal 0326 class Q_DECL_HIDDEN StaticSetOfStrings::Private 0327 { 0328 public: 0329 Private() : array(nullptr), set(nullptr) {} 0330 ~Private() { 0331 delete set; 0332 } 0333 const char* const * array; 0334 QSet<QByteArray> *set; 0335 }; 0336 0337 StaticSetOfStrings::StaticSetOfStrings() 0338 : d(new Private) 0339 { 0340 } 0341 0342 StaticSetOfStrings::StaticSetOfStrings(const char* const array[]) 0343 : d(new Private) 0344 { 0345 setStrings(array); 0346 } 0347 0348 StaticSetOfStrings::~StaticSetOfStrings() 0349 { 0350 delete d; 0351 } 0352 0353 void StaticSetOfStrings::setStrings(const char* const array[]) 0354 { 0355 delete d->set; 0356 d->set = nullptr; 0357 d->array = array; 0358 } 0359 0360 bool StaticSetOfStrings::isEmpty() const 0361 { 0362 return d->array == nullptr; 0363 } 0364 0365 bool StaticSetOfStrings::contains(const QByteArray& string) const 0366 { 0367 if (!d->set) { 0368 d->set = new QSet<QByteArray>(); 0369 for (const char * const * p = d->array;*p;p++) 0370 d->set->insert(QByteArray::fromRawData(*p, qstrlen(*p))); 0371 } 0372 return d->set->contains(string); 0373 } 0374 0375 //--------- 0376 0377 #ifdef Q_OS_MACOS 0378 //! Internal, from kdelibs' kstandarddirs.cpp 0379 static QString getBundle(const QString& path, bool ignore) 0380 { 0381 QFileInfo info; 0382 QString bundle = path; 0383 bundle += QLatin1String(".app/Contents/MacOS/") + bundle.section(QLatin1Char('/'), -1); 0384 info.setFile( bundle ); 0385 FILE *file; 0386 if (file = fopen(info.absoluteFilePath().toUtf8().constData(), "r")) { 0387 fclose(file); 0388 struct stat _stat; 0389 if ((stat(info.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { 0390 return QString(); 0391 } 0392 if ( ignore || (_stat.st_mode & S_IXUSR) ) { 0393 if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { 0394 return bundle; 0395 } 0396 } 0397 } 0398 return QString(); 0399 } 0400 #endif 0401 0402 //! Internal, from kdelibs' kstandarddirs.cpp 0403 static QString checkExecutable(const QString& path, bool ignoreExecBit) 0404 { 0405 #ifdef Q_OS_MACOS 0406 QString bundle = getBundle(path, ignoreExecBit); 0407 if (!bundle.isEmpty()) { 0408 return bundle; 0409 } 0410 #endif 0411 QFileInfo info(path); 0412 QFileInfo orig = info; 0413 #ifdef Q_OS_MACOS 0414 FILE *file; 0415 if (file = fopen(orig.absoluteFilePath().toUtf8().constData(), "r")) { 0416 fclose(file); 0417 struct stat _stat; 0418 if ((stat(orig.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { 0419 return QString(); 0420 } 0421 if ( ignoreExecBit || (_stat.st_mode & S_IXUSR) ) { 0422 if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { 0423 orig.makeAbsolute(); 0424 return orig.filePath(); 0425 } 0426 } 0427 } 0428 return QString(); 0429 #else 0430 if (info.exists() && info.isSymLink()) 0431 info = QFileInfo(info.canonicalFilePath()); 0432 if (info.exists() && ( ignoreExecBit || info.isExecutable() ) && info.isFile()) { 0433 // return absolute path, but without symlinks resolved in order to prevent 0434 // problems with executables that work differently depending on name they are 0435 // run as (for example gunzip) 0436 orig.makeAbsolute(); 0437 return orig.filePath(); 0438 } 0439 return QString(); 0440 #endif 0441 } 0442 0443 //! Internal, from kdelibs' kstandarddirs.cpp 0444 #if defined _WIN32 || defined _WIN64 0445 # define KPATH_SEPARATOR ';' 0446 # define ESCAPE '^' 0447 #else 0448 # define KPATH_SEPARATOR ':' 0449 # define ESCAPE '\\' 0450 #endif 0451 0452 //! Internal, from kdelibs' kstandarddirs.cpp 0453 static inline QString equalizePath(QString &str) 0454 { 0455 #ifdef Q_OS_WIN 0456 // filter pathes through QFileInfo to have always 0457 // the same case for drive letters 0458 QFileInfo f(str); 0459 if (f.isAbsolute()) 0460 return f.absoluteFilePath(); 0461 else 0462 #endif 0463 return str; 0464 } 0465 0466 //! Internal, from kdelibs' kstandarddirs.cpp 0467 static void tokenize(QStringList& tokens, const QString& str, 0468 const QString& delim) 0469 { 0470 const int len = str.length(); 0471 QString token; 0472 0473 for(int index = 0; index < len; index++) { 0474 if (delim.contains(str[index])) { 0475 tokens.append(equalizePath(token)); 0476 token.clear(); 0477 } else { 0478 token += str[index]; 0479 } 0480 } 0481 if (!token.isEmpty()) { 0482 tokens.append(equalizePath(token)); 0483 } 0484 } 0485 0486 //! Internal, based on kdelibs' kshell.cpp 0487 static QString tildeExpand(const QString &fname) 0488 { 0489 if (!fname.isEmpty() && fname[0] == QLatin1Char('~')) { 0490 int pos = fname.indexOf( QLatin1Char('/') ); 0491 QString ret = QDir::homePath(); // simplified 0492 if (pos > 0) { 0493 ret += fname.midRef(pos); 0494 } 0495 return ret; 0496 } else if (fname.length() > 1 && fname[0] == QLatin1Char(ESCAPE) && fname[1] == QLatin1Char('~')) { 0497 return fname.mid(1); 0498 } 0499 return fname; 0500 } 0501 0502 //! Internal, from kdelibs' kstandarddirs.cpp 0503 static QStringList systemPaths(const QString& pstr) 0504 { 0505 QStringList tokens; 0506 QString p = pstr; 0507 0508 if (p.isEmpty()) { 0509 p = QString::fromLocal8Bit( qgetenv( "PATH" ) ); 0510 } 0511 0512 QString delimiters(QLatin1Char(KPATH_SEPARATOR)); 0513 delimiters += QLatin1Char('\b'); 0514 tokenize(tokens, p, delimiters); 0515 0516 QStringList exePaths; 0517 0518 // split path using : or \b as delimiters 0519 for(int i = 0; i < tokens.count(); i++) { 0520 exePaths << tildeExpand(tokens[ i ]); 0521 } 0522 return exePaths; 0523 } 0524 0525 //! Internal, from kdelibs' kstandarddirs.cpp 0526 #ifdef Q_OS_WIN 0527 static QStringList executableExtensions() 0528 { 0529 QStringList ret = QString::fromLocal8Bit(qgetenv("PATHEXT")).split(QLatin1Char(';')); 0530 if (!ret.contains(QLatin1String(".exe"), Qt::CaseInsensitive)) { 0531 // If %PATHEXT% does not contain .exe, it is either empty, malformed, or distorted in ways that we cannot support, anyway. 0532 ret.clear(); 0533 ret << QLatin1String(".exe") 0534 << QLatin1String(".com") 0535 << QLatin1String(".bat") 0536 << QLatin1String(".cmd"); 0537 } 0538 return ret; 0539 } 0540 #endif 0541 0542 //! Based on kdelibs' kstandarddirs.cpp 0543 QString KDbUtils::findExe(const QString& appname, 0544 const QString& path, 0545 FindExeOptions options) 0546 { 0547 #ifdef Q_OS_WIN 0548 QStringList executable_extensions = executableExtensions(); 0549 if (!executable_extensions.contains( 0550 appname.section(QLatin1Char('.'), -1, -1, QString::SectionIncludeLeadingSep), 0551 Qt::CaseInsensitive)) 0552 { 0553 QString found_exe; 0554 foreach (const QString& extension, executable_extensions) { 0555 found_exe = findExe(appname + extension, path, options); 0556 if (!found_exe.isEmpty()) { 0557 return found_exe; 0558 } 0559 } 0560 return QString(); 0561 } 0562 #endif 0563 0564 // absolute or relative path? 0565 if (appname.contains(QDir::separator())) { 0566 return checkExecutable(appname, options & FindExeOption::IgnoreExecBit); 0567 } 0568 0569 QString p; 0570 QString result; 0571 0572 const QStringList exePaths = systemPaths(path); 0573 for (QStringList::ConstIterator it = exePaths.begin(); it != exePaths.end(); ++it) 0574 { 0575 p = (*it) + QLatin1Char('/'); 0576 p += appname; 0577 0578 // Check for executable in this tokenized path 0579 result = checkExecutable(p, options & FindExeOption::IgnoreExecBit); 0580 if (!result.isEmpty()) { 0581 return result; 0582 } 0583 } 0584 0585 // Not found in PATH, look into a bin dir 0586 p = QFile::decodeName(BIN_INSTALL_DIR "/"); 0587 p += appname; 0588 result = checkExecutable(p, options & FindExeOption::IgnoreExecBit); 0589 if (!result.isEmpty()) { 0590 return result; 0591 } 0592 0593 // If we reach here, the executable wasn't found. 0594 // So return empty string. 0595 return QString(); 0596 } 0597 0598 // --- 0599 0600 class Q_DECL_HIDDEN PropertySet::Private 0601 { 0602 public: 0603 Private() {} 0604 Private(const Private &other) { 0605 copy(other); 0606 } 0607 void copy(const Private &other) { 0608 for (AutodeletedHash<QByteArray, Property*>::ConstIterator it(other.data.constBegin()); 0609 it != other.data.constEnd(); ++it) 0610 { 0611 data.insert(it.key(), new Property(*it.value())); 0612 } 0613 } 0614 bool operator==(const Private &other) const { 0615 if (data.count() != other.data.count()) { 0616 return false; 0617 } 0618 for (AutodeletedHash<QByteArray, Property*>::ConstIterator it(other.data.constBegin()); 0619 it != other.data.constEnd(); ++it) 0620 { 0621 AutodeletedHash<QByteArray, Property*>::ConstIterator findHere(data.constFind(it.key())); 0622 if (*findHere.value() != *it.value()) { 0623 return false; 0624 } 0625 } 0626 return true; 0627 } 0628 AutodeletedHash<QByteArray, Property*> data; 0629 }; 0630 0631 PropertySet::PropertySet() 0632 : d(new Private) 0633 { 0634 } 0635 0636 PropertySet::PropertySet(const PropertySet &other) 0637 : d(new Private(*other.d)) 0638 { 0639 } 0640 0641 PropertySet::~PropertySet() 0642 { 0643 delete d; 0644 } 0645 0646 PropertySet& PropertySet::operator=(const PropertySet &other) 0647 { 0648 if (this != &other) { 0649 d->data.clear(); 0650 d->copy(*other.d); 0651 } 0652 return *this; 0653 } 0654 0655 bool PropertySet::operator==(const PropertySet &other) const 0656 { 0657 return *d == *other.d; 0658 } 0659 0660 void PropertySet::insert(const QByteArray &name, const QVariant &value, const QString &caption) 0661 { 0662 QString realCaption = caption; 0663 Property *existing = d->data.value(name); 0664 if (existing) { 0665 existing->setValue(value); 0666 if (!caption.isEmpty()) { // if not, reuse 0667 existing->setCaption(caption); 0668 } 0669 } else { 0670 if (KDb::isIdentifier(name)) { 0671 d->data.insert(name, new Property(value, realCaption)); 0672 } else { 0673 kdbWarning() << name << "cannot be used as property name"; 0674 } 0675 } 0676 } 0677 0678 void PropertySet::setCaption(const QByteArray &name, const QString &caption) 0679 { 0680 Property *existing = d->data.value(name); 0681 if (existing) { 0682 existing->setCaption(caption); 0683 } 0684 } 0685 0686 void PropertySet::setValue(const QByteArray &name, const QVariant &value) 0687 { 0688 Property *existing = d->data.value(name); 0689 if (existing) { 0690 existing->setValue(value); 0691 } 0692 } 0693 0694 void PropertySet::remove(const QByteArray &name) 0695 { 0696 d->data.remove(name); 0697 } 0698 0699 Property PropertySet::property(const QByteArray &name) const 0700 { 0701 Property *result = d->data.value(name); 0702 return result ? *result : Property(); 0703 } 0704 0705 QList<QByteArray> PropertySet::names() const 0706 { 0707 return d->data.keys(); 0708 } 0709 0710 QVariant KDbUtils::squeezedValue(const QVariant &value) 0711 { 0712 switch(value.type()) { 0713 case QVariant::String: 0714 if (value.toString().length() > SQUEEZED_TEXT_LIMIT) { 0715 return QVariant(value.toString().left(SQUEEZED_TEXT_LIMIT - SQUEEZED_TEXT_SUFFIX) 0716 + QString::fromLatin1("...") 0717 + value.toString().right(SQUEEZED_TEXT_SUFFIX) 0718 + QString::fromLatin1("[%1 characters]").arg(value.toString().length())); 0719 } 0720 break; 0721 case QVariant::ByteArray: 0722 if (value.toByteArray().length() > SQUEEZED_TEXT_LIMIT) { 0723 return QVariant(value.toByteArray().left(SQUEEZED_TEXT_LIMIT - SQUEEZED_TEXT_SUFFIX) 0724 + "..." 0725 + value.toByteArray().right(SQUEEZED_TEXT_SUFFIX) 0726 + '[' + QByteArray::number(value.toByteArray().length()) 0727 + " bytes]"); 0728 } 0729 break; 0730 default: 0731 break; 0732 } 0733 //! @todo add BitArray, Url, Hash, Map, Pixmap, Image? 0734 return value; 0735 }