1 /*
2 * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
3 * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 #include "config.h"
20
21 #include "common/utility.h"
22 #include "common/filesystembase.h"
23 #include "version.h"
24
25 // Note: This file must compile without QtGui
26 #include <QCoreApplication>
27 #include <QSettings>
28 #include <QTextStream>
29 #include <QDir>
30 #include <QFile>
31 #include <QUrl>
32 #include <QProcess>
33 #include <QObject>
34 #include <QThread>
35 #include <QDateTime>
36 #include <QSysInfo>
37 #include <QStandardPaths>
38 #include <QCollator>
39 #include <QSysInfo>
40 #include <qrandom.h>
41
42
43 #ifdef Q_OS_UNIX
44 #include <sys/statvfs.h>
45 #include <sys/types.h>
46 #include <unistd.h>
47 #endif
48
49 #include <cmath>
50 #include <cstdarg>
51 #include <cstring>
52
53 #if defined(Q_OS_WIN)
54 #include "utility_win.cpp"
55 #elif defined(Q_OS_MAC)
56 #include "utility_mac.cpp"
57 #else
58 #include "utility_unix.cpp"
59 #endif
60
61 namespace OCC {
62
63 Q_LOGGING_CATEGORY(lcUtility, "nextcloud.sync.utility", QtInfoMsg)
64
writeRandomFile(const QString & fname,int size)65 bool Utility::writeRandomFile(const QString &fname, int size)
66 {
67 int maxSize = 10 * 10 * 1024;
68
69 if (size == -1)
70 size = rand() % maxSize;
71
72 QString randString;
73 for (int i = 0; i < size; i++) {
74 int r = rand() % 128;
75 randString.append(QChar(r));
76 }
77
78 QFile file(fname);
79 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
80 QTextStream out(&file);
81 out << randString;
82 // optional, as QFile destructor will already do it:
83 file.close();
84 return true;
85 }
86 return false;
87 }
88
formatFingerprint(const QByteArray & fmhash,bool colonSeparated)89 QString Utility::formatFingerprint(const QByteArray &fmhash, bool colonSeparated)
90 {
91 QByteArray hash;
92 int steps = fmhash.length() / 2;
93 for (int i = 0; i < steps; i++) {
94 hash.append(fmhash[i * 2]);
95 hash.append(fmhash[i * 2 + 1]);
96 hash.append(' ');
97 }
98
99 QString fp = QString::fromLatin1(hash.trimmed());
100 if (colonSeparated) {
101 fp.replace(QLatin1Char(' '), QLatin1Char(':'));
102 }
103
104 return fp;
105 }
106
setupFavLink(const QString & folder)107 void Utility::setupFavLink(const QString &folder)
108 {
109 setupFavLink_private(folder);
110 }
111
removeFavLink(const QString & folder)112 void Utility::removeFavLink(const QString &folder)
113 {
114 removeFavLink_private(folder);
115 }
116
octetsToString(qint64 octets)117 QString Utility::octetsToString(qint64 octets)
118 {
119 #define THE_FACTOR 1024
120 static const qint64 kb = THE_FACTOR;
121 static const qint64 mb = THE_FACTOR * kb;
122 static const qint64 gb = THE_FACTOR * mb;
123
124 QString s;
125 qreal value = octets;
126
127 // Whether we care about decimals: only for GB/MB and only
128 // if it's less than 10 units.
129 bool round = true;
130
131 // do not display terra byte with the current units, as when
132 // the MB, GB and KB units were made, there was no TB,
133 // see the JEDEC standard
134 // https://en.wikipedia.org/wiki/JEDEC_memory_standards
135 if (octets >= gb) {
136 s = QCoreApplication::translate("Utility", "%L1 GB");
137 value /= gb;
138 round = false;
139 } else if (octets >= mb) {
140 s = QCoreApplication::translate("Utility", "%L1 MB");
141 value /= mb;
142 round = false;
143 } else if (octets >= kb) {
144 s = QCoreApplication::translate("Utility", "%L1 KB");
145 value /= kb;
146 } else {
147 s = QCoreApplication::translate("Utility", "%L1 B");
148 }
149
150 if (value > 9.95)
151 round = true;
152
153 if (round)
154 return s.arg(qRound(value));
155
156 return s.arg(value, 0, 'g', 2);
157 }
158
159 // Qtified version of get_platforms() in csync_owncloud.c
platform()160 static QLatin1String platform()
161 {
162 #if defined(Q_OS_WIN)
163 return QLatin1String("Windows");
164 #elif defined(Q_OS_MAC)
165 return QLatin1String("Macintosh");
166 #elif defined(Q_OS_LINUX)
167 return QLatin1String("Linux");
168 #elif defined(__DragonFly__) // Q_OS_FREEBSD also defined
169 return QLatin1String("DragonFlyBSD");
170 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_FREEBSD_KERNEL)
171 return QLatin1String("FreeBSD");
172 #elif defined(Q_OS_NETBSD)
173 return QLatin1String("NetBSD");
174 #elif defined(Q_OS_OPENBSD)
175 return QLatin1String("OpenBSD");
176 #elif defined(Q_OS_SOLARIS)
177 return QLatin1String("Solaris");
178 #else
179 return QSysInfo::productType();
180 #endif
181 }
182
userAgentString()183 QByteArray Utility::userAgentString()
184 {
185 return QStringLiteral("Mozilla/5.0 (%1) mirall/%2 (%3, %4-%5 ClientArchitecture: %6 OsArchitecture: %7)")
186 .arg(platform(),
187 QStringLiteral(MIRALL_VERSION_STRING),
188 qApp->applicationName(),
189 QSysInfo::productType(),
190 QSysInfo::kernelVersion(),
191 QSysInfo::buildCpuArchitecture(),
192 QSysInfo::currentCpuArchitecture())
193 .toLatin1();
194 }
195
friendlyUserAgentString()196 QByteArray Utility::friendlyUserAgentString()
197 {
198 const auto pattern = QStringLiteral("%1 (Desktop Client - %2)");
199 const auto userAgent = pattern.arg(QSysInfo::machineHostName(), platform());
200 return userAgent.toUtf8();
201 }
202
hasSystemLaunchOnStartup(const QString & appName)203 bool Utility::hasSystemLaunchOnStartup(const QString &appName)
204 {
205 #if defined(Q_OS_WIN)
206 return hasSystemLaunchOnStartup_private(appName);
207 #else
208 Q_UNUSED(appName)
209 return false;
210 #endif
211 }
212
hasLaunchOnStartup(const QString & appName)213 bool Utility::hasLaunchOnStartup(const QString &appName)
214 {
215 return hasLaunchOnStartup_private(appName);
216 }
217
setLaunchOnStartup(const QString & appName,const QString & guiName,bool enable)218 void Utility::setLaunchOnStartup(const QString &appName, const QString &guiName, bool enable)
219 {
220 setLaunchOnStartup_private(appName, guiName, enable);
221 }
222
freeDiskSpace(const QString & path)223 qint64 Utility::freeDiskSpace(const QString &path)
224 {
225 #if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_FREEBSD_KERNEL) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD)
226 struct statvfs stat;
227 if (statvfs(path.toLocal8Bit().data(), &stat) == 0) {
228 return (qint64)stat.f_bavail * stat.f_frsize;
229 }
230 #elif defined(Q_OS_UNIX)
231 struct statvfs64 stat;
232 if (statvfs64(path.toLocal8Bit().data(), &stat) == 0) {
233 return (qint64)stat.f_bavail * stat.f_frsize;
234 }
235 #elif defined(Q_OS_WIN)
236 ULARGE_INTEGER freeBytes;
237 freeBytes.QuadPart = 0L;
238 if (GetDiskFreeSpaceEx(reinterpret_cast<const wchar_t *>(FileSystem::longWinPath(path).utf16()), &freeBytes, nullptr, nullptr)) {
239 return freeBytes.QuadPart;
240 }
241 #endif
242 return -1;
243 }
244
compactFormatDouble(double value,int prec,const QString & unit)245 QString Utility::compactFormatDouble(double value, int prec, const QString &unit)
246 {
247 QLocale locale = QLocale::system();
248 QChar decPoint = locale.decimalPoint();
249 QString str = locale.toString(value, 'f', prec);
250 while (str.endsWith(QLatin1Char('0')) || str.endsWith(decPoint)) {
251 if (str.endsWith(decPoint)) {
252 str.chop(1);
253 break;
254 }
255 str.chop(1);
256 }
257 if (!unit.isEmpty())
258 str += (QLatin1Char(' ') + unit);
259 return str;
260 }
261
escape(const QString & in)262 QString Utility::escape(const QString &in)
263 {
264 return in.toHtmlEscaped();
265 }
266
rand()267 int Utility::rand()
268 {
269 return QRandomGenerator::global()->bounded(0, RAND_MAX);
270 }
271
sleep(int sec)272 void Utility::sleep(int sec)
273 {
274 QThread::sleep(sec);
275 }
276
usleep(int usec)277 void Utility::usleep(int usec)
278 {
279 QThread::usleep(usec);
280 }
281
282 // This can be overriden from the tests
__anonbc40b8130102() 283 OCSYNC_EXPORT bool fsCasePreserving_override = []() -> bool {
284 QByteArray env = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING");
285 if (!env.isEmpty())
286 return env.toInt();
287 return Utility::isWindows() || Utility::isMac();
288 }();
289
fsCasePreserving()290 bool Utility::fsCasePreserving()
291 {
292 return fsCasePreserving_override;
293 }
294
fileNamesEqual(const QString & fn1,const QString & fn2)295 bool Utility::fileNamesEqual(const QString &fn1, const QString &fn2)
296 {
297 const QDir fd1(fn1);
298 const QDir fd2(fn2);
299
300 // Attention: If the path does not exist, canonicalPath returns ""
301 // ONLY use this function with existing pathes.
302 const QString a = fd1.canonicalPath();
303 const QString b = fd2.canonicalPath();
304 bool re = !a.isEmpty() && QString::compare(a, b, fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive) == 0;
305 return re;
306 }
307
qDateTimeFromTime_t(qint64 t)308 QDateTime Utility::qDateTimeFromTime_t(qint64 t)
309 {
310 return QDateTime::fromMSecsSinceEpoch(t * 1000);
311 }
312
qDateTimeToTime_t(const QDateTime & t)313 qint64 Utility::qDateTimeToTime_t(const QDateTime &t)
314 {
315 return t.toMSecsSinceEpoch() / 1000;
316 }
317
318 namespace {
319 struct Period
320 {
321 const char *name;
322 quint64 msec;
323
descriptionOCC::__anonbc40b8130211::Period324 QString description(quint64 value) const
325 {
326 return QCoreApplication::translate("Utility", name, nullptr, value);
327 }
328 };
329 // QTBUG-3945 and issue #4855: QT_TRANSLATE_NOOP does not work with plural form because lupdate
330 // limitation unless we fake more arguments
331 // (it must be in the form ("context", "source", "comment", n)
332 #undef QT_TRANSLATE_NOOP
333 #define QT_TRANSLATE_NOOP(ctx, str, ...) str
334 Q_DECL_CONSTEXPR Period periods[] = {
335 { QT_TRANSLATE_NOOP("Utility", "%n year(s)", 0, _), 365 * 24 * 3600 * 1000LL },
336 { QT_TRANSLATE_NOOP("Utility", "%n month(s)", 0, _), 30 * 24 * 3600 * 1000LL },
337 { QT_TRANSLATE_NOOP("Utility", "%n day(s)", 0, _), 24 * 3600 * 1000LL },
338 { QT_TRANSLATE_NOOP("Utility", "%n hour(s)", 0, _), 3600 * 1000LL },
339 { QT_TRANSLATE_NOOP("Utility", "%n minute(s)", 0, _), 60 * 1000LL },
340 { QT_TRANSLATE_NOOP("Utility", "%n second(s)", 0, _), 1000LL },
341 { nullptr, 0 }
342 };
343 } // anonymous namespace
344
durationToDescriptiveString2(quint64 msecs)345 QString Utility::durationToDescriptiveString2(quint64 msecs)
346 {
347 int p = 0;
348 while (periods[p + 1].name && msecs < periods[p].msec) {
349 p++;
350 }
351
352 auto firstPart = periods[p].description(int(msecs / periods[p].msec));
353
354 if (!periods[p + 1].name) {
355 return firstPart;
356 }
357
358 quint64 secondPartNum = qRound(double(msecs % periods[p].msec) / periods[p + 1].msec);
359
360 if (secondPartNum == 0) {
361 return firstPart;
362 }
363
364 return QCoreApplication::translate("Utility", "%1 %2").arg(firstPart, periods[p + 1].description(secondPartNum));
365 }
366
durationToDescriptiveString1(quint64 msecs)367 QString Utility::durationToDescriptiveString1(quint64 msecs)
368 {
369 int p = 0;
370 while (periods[p + 1].name && msecs < periods[p].msec) {
371 p++;
372 }
373
374 quint64 amount = qRound(double(msecs) / periods[p].msec);
375 return periods[p].description(amount);
376 }
377
fileNameForGuiUse(const QString & fName)378 QString Utility::fileNameForGuiUse(const QString &fName)
379 {
380 if (isMac()) {
381 QString n(fName);
382 return n.replace(QLatin1Char(':'), QLatin1Char('/'));
383 }
384 return fName;
385 }
386
normalizeEtag(QByteArray etag)387 QByteArray Utility::normalizeEtag(QByteArray etag)
388 {
389 /* strip "XXXX-gzip" */
390 if (etag.startsWith('"') && etag.endsWith("-gzip\"")) {
391 etag.chop(6);
392 etag.remove(0, 1);
393 }
394 /* strip trailing -gzip */
395 if (etag.endsWith("-gzip")) {
396 etag.chop(5);
397 }
398 /* strip normal quotes */
399 if (etag.startsWith('"') && etag.endsWith('"')) {
400 etag.chop(1);
401 etag.remove(0, 1);
402 }
403 etag.squeeze();
404 return etag;
405 }
406
hasDarkSystray()407 bool Utility::hasDarkSystray()
408 {
409 return hasDarkSystray_private();
410 }
411
412
platformName()413 QString Utility::platformName()
414 {
415 return QSysInfo::prettyProductName();
416 }
417
crash()418 void Utility::crash()
419 {
420 volatile int *a = (int *)nullptr;
421 *a = 1;
422 }
423
424 // Use this functions to retrieve uint/int (often required by Qt and WIN32) from size_t
425 // without compiler warnings about possible truncation
convertSizeToUint(size_t & convertVar)426 uint Utility::convertSizeToUint(size_t &convertVar)
427 {
428 if (convertVar > UINT_MAX) {
429 //throw std::bad_cast();
430 convertVar = UINT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
431 }
432 return static_cast<uint>(convertVar);
433 }
434
convertSizeToInt(size_t & convertVar)435 int Utility::convertSizeToInt(size_t &convertVar)
436 {
437 if (convertVar > INT_MAX) {
438 //throw std::bad_cast();
439 convertVar = INT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
440 }
441 return static_cast<int>(convertVar);
442 }
443
444 // read the output of the owncloud --version command from the owncloud
445 // version that is on disk. This works for most versions of the client,
446 // because clients that do not yet know the --version flag return the
447 // version in the first line of the help output :-)
448 //
449 // This version only delivers output on linux, as Mac and Win get their
450 // restarting from the installer.
versionOfInstalledBinary(const QString & command)451 QByteArray Utility::versionOfInstalledBinary(const QString &command)
452 {
453 QByteArray re;
454 if (isLinux()) {
455 QString binary(command);
456 if (binary.isEmpty()) {
457 binary = qApp->arguments()[0];
458 }
459 QStringList params;
460 params << QStringLiteral("--version");
461 QProcess process;
462 process.start(binary, params);
463 process.waitForFinished(); // sets current thread to sleep and waits for pingProcess end
464 re = process.readAllStandardOutput();
465 int newline = re.indexOf('\n');
466 if (newline > 0) {
467 re.truncate(newline);
468 }
469 }
470 return re;
471 }
472
timeAgoInWords(const QDateTime & dt,const QDateTime & from)473 QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from)
474 {
475 QDateTime now = QDateTime::currentDateTimeUtc();
476
477 if (from.isValid()) {
478 now = from;
479 }
480
481 if (dt.daysTo(now) == 1) {
482 return QObject::tr("%n day ago", "", dt.daysTo(now));
483 } else if (dt.daysTo(now) > 1) {
484 return QObject::tr("%n days ago", "", dt.daysTo(now));
485 } else {
486 qint64 secs = dt.secsTo(now);
487 if (secs < 0) {
488 return QObject::tr("in the future");
489 }
490
491 if (floor(secs / 3600.0) > 0) {
492 int hours = floor(secs / 3600.0);
493 if (hours == 1) {
494 return (QObject::tr("%n hour ago", "", hours));
495 } else {
496 return (QObject::tr("%n hours ago", "", hours));
497 }
498 } else {
499 int minutes = qRound(secs / 60.0);
500
501 if (minutes == 0) {
502 if (secs < 5) {
503 return QObject::tr("now");
504 } else {
505 return QObject::tr("Less than a minute ago");
506 }
507
508 } else if (minutes == 1) {
509 return (QObject::tr("%n minute ago", "", minutes));
510 } else {
511 return (QObject::tr("%n minutes ago", "", minutes));
512 }
513 }
514 }
515 return QObject::tr("Some time ago");
516 }
517
518 /* --------------------------------------------------------------------------- */
519
520 static const char STOPWATCH_END_TAG[] = "_STOPWATCH_END";
521
start()522 void Utility::StopWatch::start()
523 {
524 _startTime = QDateTime::currentDateTimeUtc();
525 _timer.start();
526 }
527
stop()528 quint64 Utility::StopWatch::stop()
529 {
530 addLapTime(QLatin1String(STOPWATCH_END_TAG));
531 quint64 duration = _timer.elapsed();
532 _timer.invalidate();
533 return duration;
534 }
535
reset()536 void Utility::StopWatch::reset()
537 {
538 _timer.invalidate();
539 _startTime.setMSecsSinceEpoch(0);
540 _lapTimes.clear();
541 }
542
addLapTime(const QString & lapName)543 quint64 Utility::StopWatch::addLapTime(const QString &lapName)
544 {
545 if (!_timer.isValid()) {
546 start();
547 }
548 quint64 re = _timer.elapsed();
549 _lapTimes[lapName] = re;
550 return re;
551 }
552
startTime() const553 QDateTime Utility::StopWatch::startTime() const
554 {
555 return _startTime;
556 }
557
timeOfLap(const QString & lapName) const558 QDateTime Utility::StopWatch::timeOfLap(const QString &lapName) const
559 {
560 quint64 t = durationOfLap(lapName);
561 if (t) {
562 QDateTime re(_startTime);
563 return re.addMSecs(t);
564 }
565
566 return QDateTime();
567 }
568
durationOfLap(const QString & lapName) const569 quint64 Utility::StopWatch::durationOfLap(const QString &lapName) const
570 {
571 return _lapTimes.value(lapName, 0);
572 }
573
sortFilenames(QStringList & fileNames)574 void Utility::sortFilenames(QStringList &fileNames)
575 {
576 QCollator collator;
577 collator.setNumericMode(true);
578 collator.setCaseSensitivity(Qt::CaseInsensitive);
579 std::sort(fileNames.begin(), fileNames.end(), collator);
580 }
581
concatUrlPath(const QUrl & url,const QString & concatPath,const QUrlQuery & queryItems)582 QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath,
583 const QUrlQuery &queryItems)
584 {
585 QString path = url.path();
586 if (!concatPath.isEmpty()) {
587 // avoid '//'
588 if (path.endsWith(QLatin1Char('/')) && concatPath.startsWith(QLatin1Char('/'))) {
589 path.chop(1);
590 } // avoid missing '/'
591 else if (!path.endsWith(QLatin1Char('/')) && !concatPath.startsWith(QLatin1Char('/'))) {
592 path += QLatin1Char('/');
593 }
594 path += concatPath; // put the complete path together
595 }
596
597 QUrl tmpUrl = url;
598 tmpUrl.setPath(path);
599 tmpUrl.setQuery(queryItems);
600 return tmpUrl;
601 }
602
makeConflictFileName(const QString & fn,const QDateTime & dt,const QString & user)603 QString Utility::makeConflictFileName(
604 const QString &fn, const QDateTime &dt, const QString &user)
605 {
606 QString conflictFileName(fn);
607 // Add conflict tag before the extension.
608 int dotLocation = conflictFileName.lastIndexOf(QLatin1Char('.'));
609 // If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file)
610 if (dotLocation <= conflictFileName.lastIndexOf(QLatin1Char('/')) + 1) {
611 dotLocation = conflictFileName.size();
612 }
613
614 QString conflictMarker = QStringLiteral(" (conflicted copy ");
615 if (!user.isEmpty()) {
616 // Don't allow parens in the user name, to ensure
617 // we can find the beginning and end of the conflict tag.
618 const auto userName = sanitizeForFileName(user).replace(QLatin1Char('('), QLatin1Char('_')).replace(QLatin1Char(')'), QLatin1Char('_'));;
619 conflictMarker += userName + QLatin1Char(' ');
620 }
621 conflictMarker += dt.toString(QStringLiteral("yyyy-MM-dd hhmmss")) + QLatin1Char(')');
622
623 conflictFileName.insert(dotLocation, conflictMarker);
624 return conflictFileName;
625 }
626
isConflictFile(const char * name)627 bool Utility::isConflictFile(const char *name)
628 {
629 const char *bname = std::strrchr(name, '/');
630 if (bname) {
631 bname += 1;
632 } else {
633 bname = name;
634 }
635
636 // Old pattern
637 if (std::strstr(bname, "_conflict-"))
638 return true;
639
640 // New pattern
641 if (std::strstr(bname, "(conflicted copy"))
642 return true;
643
644 return false;
645 }
646
isConflictFile(const QString & name)647 bool Utility::isConflictFile(const QString &name)
648 {
649 auto bname = name.midRef(name.lastIndexOf(QLatin1Char('/')) + 1);
650
651 if (bname.contains(QStringLiteral("_conflict-")))
652 return true;
653
654 if (bname.contains(QStringLiteral("(conflicted copy")))
655 return true;
656
657 return false;
658 }
659
conflictFileBaseNameFromPattern(const QByteArray & conflictName)660 QByteArray Utility::conflictFileBaseNameFromPattern(const QByteArray &conflictName)
661 {
662 // This function must be able to deal with conflict files for conflict files.
663 // To do this, we scan backwards, for the outermost conflict marker and
664 // strip only that to generate the conflict file base name.
665 auto startOld = conflictName.lastIndexOf("_conflict-");
666
667 // A single space before "(conflicted copy" is considered part of the tag
668 auto startNew = conflictName.lastIndexOf("(conflicted copy");
669 if (startNew > 0 && conflictName[startNew - 1] == ' ')
670 startNew -= 1;
671
672 // The rightmost tag is relevant
673 auto tagStart = qMax(startOld, startNew);
674 if (tagStart == -1)
675 return "";
676
677 // Find the end of the tag
678 auto tagEnd = conflictName.size();
679 auto dot = conflictName.lastIndexOf('.'); // dot could be part of user name for new tag!
680 if (dot > tagStart)
681 tagEnd = dot;
682 if (tagStart == startNew) {
683 auto paren = conflictName.indexOf(')', tagStart);
684 if (paren != -1)
685 tagEnd = paren + 1;
686 }
687 return conflictName.left(tagStart) + conflictName.mid(tagEnd);
688 }
689
isPathWindowsDrivePartitionRoot(const QString & path)690 bool Utility::isPathWindowsDrivePartitionRoot(const QString &path)
691 {
692 Q_UNUSED(path)
693 #ifdef Q_OS_WIN
694 // should be 2 or 3 characters length
695 if (!(path.size() >= 2 && path.size() <= 3)) {
696 return false;
697 }
698
699 // must mutch a pattern "[A-Za-z]:"
700 if (!(path.at(1) == QLatin1Char(':') && path.at(0).isLetter())) {
701 return false;
702 }
703
704 // final check - last character should be either slash/backslash, or, it should be missing
705 return path.size() < 3 || path.at(2) == QLatin1Char('/') || path.at(2) == QLatin1Char('\\');
706 #endif
707 return false;
708 }
709
sanitizeForFileName(const QString & name)710 QString Utility::sanitizeForFileName(const QString &name)
711 {
712 const auto invalid = QStringLiteral(R"(/?<>\:*|")");
713 QString result;
714 result.reserve(name.size());
715 for (const auto c : name) {
716 if (!invalid.contains(c)
717 && c.category() != QChar::Other_Control
718 && c.category() != QChar::Other_Format) {
719 result.append(c);
720 }
721 }
722 return result;
723 }
724
725 } // namespace OCC
726