diff options
author | Eike Ziller <[email protected]> | 2025-08-27 15:28:44 +0200 |
---|---|---|
committer | Eike Ziller <[email protected]> | 2025-09-17 09:04:26 +0000 |
commit | 968135b74b660ac5849329c68276ded691c2f501 (patch) | |
tree | e7e793dde4eb10193be8683679b55609f9df0513 | |
parent | 38f83bad7e718a65d520cef9462d149d73634ad7 (diff) |
FilePath: Fix resolution of symlinks with directory links
In the situation
/a/file3.txt
/x/linktest1.txt -> ../a/file3.txt
/a/dirlink -> ../x
unfortunately symLinkTarget("/a/dirlink/linktest1.txt")
returns "/a/a/file3.txt", because readlink of that file returns
"../a/file3.txt" and that relative path is then resolved via the original
path, "/a/dirlink/../a/file3.txt".
It does not recognize that a directory link is involved that changes the
depth of the hierarchy.
That also breaks FilePath::resolveSymLinks that relies on symLinkTarget.
Fix it by checking the whole parent hierarchy of the path for symlinks
instead of just the file itself. If a link is found, that counts towards
the max 16 jumps that it does.
Adds some tests for resolveSymLinks.
Fixes: QTCREATORBUG-33335
Change-Id: Id55ae44cc4e8fabaf6ae37e87b0bc7d07e032d21
Reviewed-by: Marcus Tillmanns <[email protected]>
-rw-r--r-- | src/libs/utils/filepath.cpp | 18 | ||||
-rw-r--r-- | tests/auto/utils/filepath/tst_filepath.cpp | 52 |
2 files changed, 67 insertions, 3 deletions
diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 37e36030253..1df6878e0b6 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -2493,10 +2493,22 @@ FilePath FilePath::resolveSymlinks() const FilePath current = *this; int links = 16; while (links--) { - const FilePath target = current.symLinkTarget(); - if (target.isEmpty()) + const QList<QStringView> components = current.pathComponents(); + FilePath pathToTest = current.withNewPath(""); + bool resolved = false; + for (const QStringView &path : components) { + pathToTest = pathToTest / QString(path); + if (!resolved) { + const FilePath target = pathToTest.symLinkTarget(); + if (!target.isEmpty()) { + resolved = true; + pathToTest = target; + } + } + } + if (!resolved) return current; - current = target; + current = pathToTest; } return current; } diff --git a/tests/auto/utils/filepath/tst_filepath.cpp b/tests/auto/utils/filepath/tst_filepath.cpp index 9f64517e1d5..1770c219ed2 100644 --- a/tests/auto/utils/filepath/tst_filepath.cpp +++ b/tests/auto/utils/filepath/tst_filepath.cpp @@ -140,6 +140,7 @@ private slots: void pathComponents_data(); void symLinks(); + void resolveSymLinks(); void ensureWritableDirectory(); void ensureWritableDirectoryPermissions(); @@ -2017,6 +2018,57 @@ void tst_filepath::symLinks() QCOMPARE(link.symLinkTarget(), orig); } +void tst_filepath::resolveSymLinks() +{ + if (HostOsInfo::isWindowsHost()) + QSKIP("Creating symbolic links requires special privileges on Windows"); + + // relative link chain to existing file + const FilePath orig1 = FilePath::fromString(rootPath).pathAppended("a/file3.txt"); + QVERIFY(orig1.exists()); + const FilePath link1 = FilePath::fromString(rootPath).pathAppended("x/linktest1.txt"); + const Result<> res1 = FilePath::fromString("../a/file3.txt").createSymLink(link1); + QVERIFY_RESULT(res1); + QVERIFY(link1.isSymLink()); + QCOMPARE(link1.symLinkTarget(), orig1); + const FilePath link2 = FilePath::fromString(rootPath).pathAppended("x/linktest2.txt"); + const Result<> res2 = FilePath::fromString("linktest1.txt").createSymLink(link2); + QVERIFY_RESULT(res2); + QVERIFY(link2.isSymLink()); + QCOMPARE(link2.symLinkTarget(), link1); + QCOMPARE(link2.resolveSymlinks(), orig1); + + // relative link chain to non-existing file + const FilePath orig2 = FilePath::fromString(rootPath).pathAppended("a/broken.txt"); + QVERIFY(!orig2.exists()); + const FilePath link3 = FilePath::fromString(rootPath).pathAppended("x/linktest3.txt"); + const Result<> res3 = FilePath::fromString("../a/broken.txt").createSymLink(link3); + QVERIFY_RESULT(res3); + QVERIFY(link3.isSymLink()); + QCOMPARE(link3.symLinkTarget(), orig2); + const FilePath link4 = FilePath::fromString(rootPath).pathAppended("x/linktest4.txt"); + const Result<> res4 = FilePath::fromString("linktest3.txt").createSymLink(link4); + QVERIFY_RESULT(res4); + QVERIFY(link4.isSymLink()); + QCOMPARE(link4.symLinkTarget(), link3); + QCOMPARE(link4.resolveSymlinks(), orig2); + + // relative links in directory links + // currently: + // <tmp>/a/file3.txt + // <tmp>/x/linktest1.txt -> ../a/file3.txt + // if now: + // <tmp>/a/dirlink -> ../x + // then resolution should happen like this: + // <tmp>/a/dirlink/linktest1.txt -> <tmp>/a/file3.txt + const FilePath dirlink = FilePath::fromString(rootPath).pathAppended("a/dirlink"); + const Result<> res5 = FilePath::fromString("../x").createSymLink(dirlink); + QVERIFY_RESULT(res5); + QVERIFY(dirlink.isSymLink()); + QCOMPARE(dirlink.symLinkTarget(), FilePath::fromString(rootPath).pathAppended("x")); + QCOMPARE(dirlink.pathAppended("linktest1.txt").resolveSymlinks(), orig1); +} + void tst_filepath::ensureWritableDirectory() { const FilePath dir = FilePath::fromString(rootPath).pathAppended("x/y/writableDir"); |