123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 | // Copyright (C) 2016 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include"qsettings.h"#include"qsettings_p.h"#include"qdatetime.h"#include"qdir.h"#include"qvarlengtharray.h"#include"private/qcore_mac_p.h"#ifndef QT_NO_QOBJECT#include"qcoreapplication.h"#endif// QT_NO_QOBJECT QT_BEGIN_NAMESPACE using namespaceQt::StringLiterals;static const CFStringRef hostNames[2] = { kCFPreferencesCurrentHost, kCFPreferencesAnyHost };static const int numHostNames =2;/* On the Mac, it is more natural to use '.' as the key separator than '/'. Therefore, it makes sense to replace '/' with '.' in keys. Then we replace '.' with middle dots (which we can't show here) and middle dots with '/'. A key like "4.0/BrowserCommand" becomes "4<middot>0.BrowserCommand".*/enum RotateShift { Macify =1, Qtify =2};static QString rotateSlashesDotsAndMiddots(const QString &key,int shift){static const int NumKnights =3;static const char knightsOfTheRoundTable[NumKnights] = {'/','.','\xb7'}; QString result = key;for(int i =0; i < result.size(); ++i) {for(int j =0; j < NumKnights; ++j) {if(result.at(i) ==QLatin1Char(knightsOfTheRoundTable[j])) { result[i] =QLatin1Char(knightsOfTheRoundTable[(j + shift) % NumKnights]).unicode();break;}}}return result;}static QCFType<CFStringRef>macKey(const QString &key){returnrotateSlashesDotsAndMiddots(key, Macify).toCFString();}static QString qtKey(CFStringRef cfkey){returnrotateSlashesDotsAndMiddots(QString::fromCFString(cfkey), Qtify);}static QCFType<CFPropertyListRef>macValue(const QVariant &value);static CFArrayRef macList(const QList<QVariant> &list){int n = list.size(); QVarLengthArray<QCFType<CFPropertyListRef>>cfvalues(n);for(int i =0; i < n; ++i) cfvalues[i] =macValue(list.at(i));returnCFArrayCreate(kCFAllocatorDefault,reinterpret_cast<const void**>(cfvalues.data()),CFIndex(n), &kCFTypeArrayCallBacks);}static QCFType<CFPropertyListRef>macValue(const QVariant &value){ CFPropertyListRef result =0;switch(value.metaType().id()) {caseQMetaType::QByteArray:{ QByteArray ba = value.toByteArray(); result =CFDataCreate(kCFAllocatorDefault,reinterpret_cast<const UInt8 *>(ba.data()),CFIndex(ba.size()));}break;// should be same as below (look for LIST)caseQMetaType::QVariantList:caseQMetaType::QStringList:caseQMetaType::QPolygon: result =macList(value.toList());break;caseQMetaType::QVariantMap:{const QVariantMap &map = value.toMap();const int mapSize = map.size(); QVarLengthArray<QCFType<CFPropertyListRef>> cfkeys; cfkeys.reserve(mapSize);std::transform(map.keyBegin(), map.keyEnd(),std::back_inserter(cfkeys),[](constauto&key) {return key.toCFString(); }); QVarLengthArray<QCFType<CFPropertyListRef>> cfvalues; cfvalues.reserve(mapSize);std::transform(map.begin(), map.end(),std::back_inserter(cfvalues),[](constauto&value) {returnmacValue(value); }); result =CFDictionaryCreate(kCFAllocatorDefault,reinterpret_cast<const void**>(cfkeys.data()),reinterpret_cast<const void**>(cfvalues.data()),CFIndex(mapSize),&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);}break;caseQMetaType::QDateTime:{ QDateTime dateTime = value.toDateTime();// CFDate, unlike QDateTime, doesn't store timezone informationif(dateTime.timeSpec() ==Qt::LocalTime) result = dateTime.toCFDate();elsegoto string_case;}break;caseQMetaType::Bool: result = value.toBool() ? kCFBooleanTrue : kCFBooleanFalse;break;caseQMetaType::Int:caseQMetaType::UInt:{int n = value.toInt(); result =CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &n);}break;caseQMetaType::Double:{double n = value.toDouble(); result =CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &n);}break;caseQMetaType::LongLong:caseQMetaType::ULongLong:{ qint64 n = value.toLongLong(); result =CFNumberCreate(0, kCFNumberLongLongType, &n);}break;caseQMetaType::QString: string_case:default: QString string =QSettingsPrivate::variantToString(value);if(string.contains(QChar::Null)) result =std::move(string).toUtf8().toCFData();else result = string.toCFString();}return result;}static QVariant qtValue(CFPropertyListRef cfvalue){if(!cfvalue)returnQVariant(); CFTypeID typeId =CFGetTypeID(cfvalue);/* Sorted grossly from most to least frequent type. */if(typeId ==CFStringGetTypeID()) {returnQSettingsPrivate::stringToVariant(QString::fromCFString(static_cast<CFStringRef>(cfvalue)));}else if(typeId ==CFNumberGetTypeID()) { CFNumberRef cfnumber =static_cast<CFNumberRef>(cfvalue);if(CFNumberIsFloatType(cfnumber)) {double d;CFNumberGetValue(cfnumber, kCFNumberDoubleType, &d);return d;}else{int i; qint64 ll;if(CFNumberGetType(cfnumber) == kCFNumberIntType) {CFNumberGetValue(cfnumber, kCFNumberIntType, &i);return i;}CFNumberGetValue(cfnumber, kCFNumberLongLongType, &ll);return ll;}}else if(typeId ==CFArrayGetTypeID()) { CFArrayRef cfarray =static_cast<CFArrayRef>(cfvalue); QList<QVariant> list; CFIndex size =CFArrayGetCount(cfarray);bool metNonString =false;for(CFIndex i =0; i < size; ++i) { QVariant value =qtValue(CFArrayGetValueAtIndex(cfarray, i));if(value.typeId() !=QMetaType::QString) metNonString =true; list << value;}if(metNonString)return list;elsereturnQVariant(list).toStringList();}else if(typeId ==CFBooleanGetTypeID()) {return(bool)CFBooleanGetValue(static_cast<CFBooleanRef>(cfvalue));}else if(typeId ==CFDataGetTypeID()) { QByteArray byteArray =QByteArray::fromRawCFData(static_cast<CFDataRef>(cfvalue));// Fast-path for QByteArray, so that we don't have to go// though the expensive and lossy conversion via UTF-8.if(!byteArray.startsWith('@')) { byteArray.detach();return byteArray;}const QString str =QString::fromUtf8(byteArray.constData(), byteArray.size()); QVariant variant =QSettingsPrivate::stringToVariant(str);if(variant ==QVariant(str)) {// We did not find an encoded variant in the string,// so return the raw byte array instead. byteArray.detach();return byteArray;}return variant;}else if(typeId ==CFDictionaryGetTypeID()) { CFDictionaryRef cfdict =static_cast<CFDictionaryRef>(cfvalue); CFTypeID arrayTypeId =CFArrayGetTypeID();int size = (int)CFDictionaryGetCount(cfdict); QVarLengthArray<CFPropertyListRef>keys(size); QVarLengthArray<CFPropertyListRef>values(size);CFDictionaryGetKeysAndValues(cfdict, keys.data(), values.data()); QVariantMap map;for(int i =0; i < size; ++i) { QString key =QString::fromCFString(static_cast<CFStringRef>(keys[i]));if(CFGetTypeID(values[i]) == arrayTypeId) { CFArrayRef cfarray =static_cast<CFArrayRef>(values[i]); CFIndex arraySize =CFArrayGetCount(cfarray); QVariantList list; list.reserve(arraySize);for(CFIndex j =0; j < arraySize; ++j) list.append(qtValue(CFArrayGetValueAtIndex(cfarray, j))); map.insert(key, list);}else{ map.insert(key,qtValue(values[i]));}}return map;}else if(typeId ==CFDateGetTypeID()) {returnQDateTime::fromCFDate(static_cast<CFDateRef>(cfvalue));}returnQVariant();}static QString comify(const QString &organization){for(int i = organization.size() -1; i >=0; --i) { QChar ch = organization.at(i);if(ch == u'.'|| ch ==QChar(0x3002) || ch ==QChar(0xff0e)|| ch ==QChar(0xff61)) { QString suffix = organization.mid(i +1).toLower();if(suffix.size() ==2|| suffix =="com"_L1 || suffix =="org"_L1 || suffix =="net"_L1 || suffix =="edu"_L1 || suffix =="gov"_L1 || suffix =="mil"_L1 || suffix =="biz"_L1 || suffix =="info"_L1 || suffix =="name"_L1 || suffix =="pro"_L1 || suffix =="aero"_L1 || suffix =="coop"_L1 || suffix =="museum"_L1) { QString result = organization; result.replace(u'/', u' ');return result;}break;}int uc = ch.unicode();if((uc <'a'|| uc >'z') && (uc <'A'|| uc >'Z'))break;} QString domain;for(int i =0; i < organization.size(); ++i) { QChar ch = organization.at(i);int uc = ch.unicode();if((uc >='a'&& uc <='z') || (uc >='0'&& uc <='9')) { domain += ch;}else if(uc >='A'&& uc <='Z') { domain += ch.toLower();}else{ domain += u' ';}} domain = domain.simplified(); domain.replace(u' ', u'-');if(!domain.isEmpty()) domain.append(".com"_L1);return domain;}class QMacSettingsPrivate :public QSettingsPrivate {public:QMacSettingsPrivate(QSettings::Scope scope,const QString &organization,const QString &application);~QMacSettingsPrivate();voidremove(const QString &key) override;voidset(const QString &key,const QVariant &value) override;std::optional<QVariant>get(const QString &key)const override; QStringList children(const QString &prefix, ChildSpec spec)const override;voidclear() override;voidsync() override;voidflush() override;boolisWritable()const override; QString fileName()const override;private:struct SearchDomain { CFStringRef userName; CFStringRef applicationOrSuiteId;}; QCFString applicationId; QCFString suiteId; QCFString hostName; SearchDomain domains[6];int numDomains;};QMacSettingsPrivate::QMacSettingsPrivate(QSettings::Scope scope,const QString &organization,const QString &application):QSettingsPrivate(QSettings::NativeFormat, scope, organization, application){ QString javaPackageName;int curPos =0;int nextDot;// attempt to use the organization parameter QString domainName =comify(organization);// if not found, attempt to use the bundle identifier.if(domainName.isEmpty()) { CFBundleRef main_bundle =CFBundleGetMainBundle();if(main_bundle != NULL) { CFStringRef main_bundle_identifier =CFBundleGetIdentifier(main_bundle);if(main_bundle_identifier != NULL) { QString bundle_identifier(qtKey(main_bundle_identifier));// CFBundleGetIdentifier returns identifier separated by slashes rather than periods. QStringList bundle_identifier_components = bundle_identifier.split(u'/');// pre-reverse them so that when they get reversed again below, they are in the com.company.product format. QStringList bundle_identifier_components_reversed;for(int i=0; i<bundle_identifier_components.size(); ++i) {const QString &bundle_identifier_component = bundle_identifier_components.at(i); bundle_identifier_components_reversed.push_front(bundle_identifier_component);} domainName = bundle_identifier_components_reversed.join(u'.');}}}// if no bundle identifier yet. use a hard coded string.if(domainName.isEmpty()) domainName ="unknown-organization.trolltech.com"_L1;while((nextDot = domainName.indexOf(u'.', curPos)) != -1) { javaPackageName.prepend(QStringView{domainName}.mid(curPos, nextDot - curPos)); javaPackageName.prepend(u'.'); curPos = nextDot +1;} javaPackageName.prepend(QStringView{domainName}.mid(curPos)); javaPackageName =std::move(javaPackageName).toLower();if(curPos ==0) javaPackageName.prepend("com."_L1); suiteId = javaPackageName;if(!application.isEmpty()) { javaPackageName += u'.'+ application; applicationId = javaPackageName;} numDomains =0;for(int i = (scope ==QSettings::SystemScope) ?1:0; i <2; ++i) {for(int j = (application.isEmpty()) ?1:0; j <3; ++j) { SearchDomain &domain = domains[numDomains++]; domain.userName = (i ==0) ? kCFPreferencesCurrentUser : kCFPreferencesAnyUser;if(j ==0) domain.applicationOrSuiteId = applicationId;else if(j ==1) domain.applicationOrSuiteId = suiteId;else domain.applicationOrSuiteId = kCFPreferencesAnyApplication;}} hostName = (scope ==QSettings::SystemScope) ? kCFPreferencesCurrentHost : kCFPreferencesAnyHost;sync();}QMacSettingsPrivate::~QMacSettingsPrivate(){}voidQMacSettingsPrivate::remove(const QString &key){ QStringList keys =children(key + u'/', AllKeys);// If i == -1, then delete "key" itself.for(int i = -1; i < keys.size(); ++i) { QString subKey = key;if(i >=0) { subKey += u'/'; subKey += keys.at(i);}CFPreferencesSetValue(macKey(subKey),0, domains[0].applicationOrSuiteId, domains[0].userName, hostName);}}voidQMacSettingsPrivate::set(const QString &key,const QVariant &value){CFPreferencesSetValue(macKey(key),macValue(value), domains[0].applicationOrSuiteId, domains[0].userName, hostName);}std::optional<QVariant>QMacSettingsPrivate::get(const QString &key)const{ QCFString k =macKey(key);for(int i =0; i < numDomains; ++i) {for(int j =0; j < numHostNames; ++j) { QCFType<CFPropertyListRef> ret =CFPreferencesCopyValue(k, domains[i].applicationOrSuiteId, domains[i].userName, hostNames[j]);if(ret)returnqtValue(ret);}if(!fallbacks)break;}returnstd::nullopt;} QStringList QMacSettingsPrivate::children(const QString &prefix, ChildSpec spec)const{ QStringList result;int startPos = prefix.size();for(int i =0; i < numDomains; ++i) {for(int j =0; j < numHostNames; ++j) { QCFType<CFArrayRef> cfarray =CFPreferencesCopyKeyList(domains[i].applicationOrSuiteId, domains[i].userName, hostNames[j]);if(cfarray) { CFIndex size =CFArrayGetCount(cfarray);for(CFIndex k =0; k < size; ++k) { QString currentKey =qtKey(static_cast<CFStringRef>(CFArrayGetValueAtIndex(cfarray, k)));if(currentKey.startsWith(prefix))processChild(QStringView{currentKey}.mid(startPos), spec, result);}}}if(!fallbacks)break;}std::sort(result.begin(), result.end()); result.erase(std::unique(result.begin(), result.end()), result.end());return result;}voidQMacSettingsPrivate::clear(){ QCFType<CFArrayRef> cfarray =CFPreferencesCopyKeyList(domains[0].applicationOrSuiteId, domains[0].userName, hostName);CFPreferencesSetMultiple(0, cfarray, domains[0].applicationOrSuiteId, domains[0].userName, hostName);}voidQMacSettingsPrivate::sync(){for(int i =0; i < numDomains; ++i) {for(int j =0; j < numHostNames; ++j) { Boolean ok =CFPreferencesSynchronize(domains[i].applicationOrSuiteId, domains[i].userName, hostNames[j]);// only report failures for the primary file (the one we write to)if(!ok && i ==0&& hostNames[j] == hostName && status ==QSettings::NoError) {setStatus(QSettings::AccessError);}}}}voidQMacSettingsPrivate::flush(){sync();}boolQMacSettingsPrivate::isWritable()const{ QMacSettingsPrivate *that =const_cast<QMacSettingsPrivate *>(this); QString impossibleKey("qt_internal/"_L1);QSettings::Status oldStatus = that->status; that->status =QSettings::NoError; that->set(impossibleKey,QVariant()); that->sync();bool writable = (status ==QSettings::NoError) && that->get(impossibleKey).has_value(); that->remove(impossibleKey); that->sync(); that->status = oldStatus;return writable;} QString QMacSettingsPrivate::fileName()const{ QString result;if(scope ==QSettings::UserScope) result =QDir::homePath(); result +="/Library/Preferences/"_L1; result +=QString::fromCFString(domains[0].applicationOrSuiteId); result +=".plist"_L1;return result;} QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format,QSettings::Scope scope,const QString &organization,const QString &application){#ifndef QT_BOOTSTRAPPEDif(organization =="Qt"_L1){ QString organizationDomain =QCoreApplication::organizationDomain(); QString applicationName =QCoreApplication::applicationName(); QSettingsPrivate *newSettings;if(format ==QSettings::NativeFormat) { newSettings =newQMacSettingsPrivate(scope, organizationDomain, applicationName);}else{ newSettings =newQConfFileSettingsPrivate(format, scope, organizationDomain, applicationName);} newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(organization)));if(!application.isEmpty()) newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(application)));return newSettings;}#endifif(format ==QSettings::NativeFormat) {return newQMacSettingsPrivate(scope, organization, application);}else{return newQConfFileSettingsPrivate(format, scope, organization, application);}}boolQConfFileSettingsPrivate::readPlistFile(const QByteArray &data, ParsedSettingsMap *map)const{ QCFType<CFDataRef> cfData = data.toRawCFData(); QCFType<CFPropertyListRef> propertyList =CFPropertyListCreateWithData(kCFAllocatorDefault, cfData, kCFPropertyListImmutable,nullptr,nullptr);if(!propertyList)return true;if(CFGetTypeID(propertyList) !=CFDictionaryGetTypeID())return false; CFDictionaryRef cfdict =static_cast<CFDictionaryRef>(static_cast<CFPropertyListRef>(propertyList));int size = (int)CFDictionaryGetCount(cfdict); QVarLengthArray<CFPropertyListRef>keys(size); QVarLengthArray<CFPropertyListRef>values(size);CFDictionaryGetKeysAndValues(cfdict, keys.data(), values.data());for(int i =0; i < size; ++i) { QString key =qtKey(static_cast<CFStringRef>(keys[i])); map->insert(QSettingsKey(key,Qt::CaseSensitive),qtValue(values[i]));}return true;}boolQConfFileSettingsPrivate::writePlistFile(QIODevice &file,const ParsedSettingsMap &map)const{ QVarLengthArray<QCFType<CFStringRef> >cfkeys(map.size()); QVarLengthArray<QCFType<CFPropertyListRef> >cfvalues(map.size());int i =0;ParsedSettingsMap::const_iterator j;for(j = map.constBegin(); j != map.constEnd(); ++j) { cfkeys[i] =macKey(j.key()); cfvalues[i] =macValue(j.value());++i;} QCFType<CFDictionaryRef> propertyList =CFDictionaryCreate(kCFAllocatorDefault,reinterpret_cast<const void**>(cfkeys.data()),reinterpret_cast<const void**>(cfvalues.data()),CFIndex(map.size()),&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks); QCFType<CFDataRef> xmlData =CFPropertyListCreateData( kCFAllocatorDefault, propertyList, kCFPropertyListXMLFormat_v1_0,0,0);return file.write(QByteArray::fromRawCFData(xmlData)) ==CFDataGetLength(xmlData);} QT_END_NAMESPACE
|