QtPass Developer documentation
QtPass test coverage
Current view: top level - src/pathvalidator.cpp (source / functions) Coverage Total Hit
Test: QtPass Lines: 93.3 % 45 42
Test Date: 2026-11-14 13:19:18 Functions: 100.0 % 4 4

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Implements PathValidator; storeRelativePath() does the canonical
       6              :  * comparison the other checks build on.
       7              :  */
       8              : #include "pathvalidator.h"
       9              : #include <QDir>
      10              : #include <QFileInfo>
      11              : #include <QStringList>
      12              : 
      13           72 : auto PathValidator::isPathInStore(const QString &storeRoot,
      14              :                                   const QString &candidate) -> bool {
      15           72 :   return storeRelativePath(storeRoot, candidate).has_value();
      16              : }
      17              : 
      18          126 : auto PathValidator::storeRelativePath(const QString &storeRoot,
      19              :                                       const QString &candidate)
      20              :     -> std::optional<QString> {
      21          126 :   if (storeRoot.isEmpty() || candidate.isEmpty()) {
      22            3 :     return std::nullopt;
      23              :   }
      24          123 :   const QString rootCanon = QDir(storeRoot).canonicalPath();
      25          123 :   if (rootCanon.isEmpty()) {
      26            1 :     return std::nullopt;
      27              :   }
      28              : 
      29          122 :   QString resolved;
      30          244 :   const QFileInfo fi(QDir::cleanPath(QFileInfo(candidate).absoluteFilePath()));
      31          122 :   if (fi.exists()) {
      32          134 :     resolved = fi.canonicalFilePath();
      33              :   } else {
      34              :     // Walk up to the nearest existing ancestor so we can canonicalise it
      35              :     // (resolves any symlinks on the way to the store root), then re-append
      36              :     // the leaf components below that ancestor.
      37           55 :     QDir parent = fi.dir();
      38           55 :     QStringList tail;
      39           55 :     tail.prepend(fi.fileName());
      40           63 :     while (!parent.exists()) {
      41            8 :       tail.prepend(parent.dirName());
      42              :       // The filesystem root always exists, so the loop terminates there
      43              :       // before cdUp() can fail; this guard is defensive against a
      44              :       // pathological QDir that can neither exist nor ascend.
      45            8 :       if (!parent.cdUp()) {
      46            0 :         return std::nullopt;
      47              :       }
      48              :     }
      49           55 :     const QString parentCanon = parent.canonicalPath();
      50           55 :     if (parentCanon.isEmpty()) {
      51            0 :       return std::nullopt;
      52              :     }
      53          110 :     resolved = QDir::cleanPath(parentCanon + QLatin1Char('/') +
      54          110 :                                tail.join(QLatin1Char('/')));
      55           55 :   }
      56              : 
      57          122 :   if (resolved.isEmpty()) {
      58            0 :     return std::nullopt;
      59              :   }
      60          122 :   if (resolved == rootCanon) {
      61           31 :     return QString();
      62              :   }
      63          182 :   if (!resolved.startsWith(rootCanon + QLatin1Char('/'))) {
      64           22 :     return std::nullopt;
      65              :   }
      66          138 :   return resolved.mid(rootCanon.size() + 1);
      67          122 : }
      68              : 
      69          300 : auto PathValidator::isTargetSpelled(const QString &storeRoot,
      70              :                                     const QString &path) -> bool {
      71          600 :   if (storeRoot.isEmpty() || !QDir::isAbsolutePath(path)) {
      72            2 :     return false;
      73              :   }
      74              : #ifdef Q_OS_WIN
      75              :   constexpr auto cs = Qt::CaseInsensitive;
      76              : #else
      77              :   constexpr auto cs = Qt::CaseSensitive;
      78              : #endif
      79          298 :   const QString root = QDir::cleanPath(storeRoot);
      80          298 :   const QString canonical = QDir(root).canonicalPath();
      81          298 :   if (canonical.isEmpty() || canonical.compare(root, cs) == 0) {
      82          291 :     return false;
      83              :   }
      84           12 :   const auto atOrUnder = [cs](const QString &p, const QString &dir) {
      85              :     const QString prefix =
      86           12 :         dir.endsWith(QLatin1Char('/')) ? dir : dir + QLatin1Char('/');
      87           24 :     return p.compare(dir, cs) == 0 || p.startsWith(prefix, cs);
      88              :   };
      89            7 :   const QString cleaned = QDir::cleanPath(path);
      90            7 :   return !atOrUnder(cleaned, root) && atOrUnder(cleaned, canonical);
      91              : }
        

Generated by: LCOV version 2.0-1