Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(832)

Unified Diff: base/file_util_unittest.cc

Issue 2088006: Give the extension unpacker process a junction/symlink free path to the unpack directory. (Closed)
Patch Set: Rebase for commit. Created 10 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/file_util_posix.cc ('k') | base/file_util_win.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/file_util_unittest.cc
diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc
index 05a1aeedc09e47c3a1dc8f304fa4871074bb5802..d66a4261e50fb5be620713fa67c5e1a7bfbafb59 100644
--- a/base/file_util_unittest.cc
+++ b/base/file_util_unittest.cc
@@ -6,6 +6,7 @@
#if defined(OS_WIN)
#include <windows.h>
+#include <winioctl.h>
#include <shellapi.h>
#include <shlobj.h>
#include <tchar.h>
@@ -21,6 +22,7 @@
#include "base/logging.h"
#include "base/path_service.h"
#include "base/platform_thread.h"
+#include "base/scoped_handle.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -31,6 +33,81 @@
namespace {
+// To test that file_util::Normalize FilePath() deals with NTFS reparse points
+// correctly, we need functions to create and delete reparse points.
+#if defined(OS_WIN)
+typedef struct _REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
+// Sets a reparse point. |source| will now point to |target|. Returns true if
+// the call succeeds, false otherwise.
+bool SetReparsePoint(HANDLE source, const FilePath& target_path) {
+ std::wstring kPathPrefix = L"\\??\\";
+ std::wstring target_str;
+ // The juction will not work if the target path does not start with \??\ .
+ if (kPathPrefix != target_path.value().substr(0, kPathPrefix.size()))
+ target_str += kPathPrefix;
+ target_str += target_path.value();
+ const wchar_t* target = target_str.c_str();
+ USHORT size_target = static_cast<USHORT>(wcslen(target)) * sizeof(target[0]);
+ char buffer[2000] = {0};
+ DWORD returned;
+
+ REPARSE_DATA_BUFFER* data = reinterpret_cast<REPARSE_DATA_BUFFER*>(buffer);
+
+ data->ReparseTag = 0xa0000003;
+ memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2);
+
+ data->MountPointReparseBuffer.SubstituteNameLength = size_target;
+ data->MountPointReparseBuffer.PrintNameOffset = size_target + 2;
+ data->ReparseDataLength = size_target + 4 + 8;
+
+ int data_size = data->ReparseDataLength + 8;
+
+ if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size,
+ NULL, 0, &returned, NULL)) {
+ return false;
+ }
+ return true;
+}
+
+// Delete the reparse point referenced by |source|. Returns true if the call
+// succeeds, false otherwise.
+bool DeleteReparsePoint(HANDLE source) {
+ DWORD returned;
+ REPARSE_DATA_BUFFER data = {0};
+ data.ReparseTag = 0xa0000003;
+ if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0,
+ &returned, NULL)) {
+ return false;
+ }
+ return true;
+}
+#endif
+
const wchar_t bogus_content[] = L"I'm cannon fodder.";
const file_util::FileEnumerator::FILE_TYPE FILES_AND_DIRECTORIES =
@@ -387,54 +464,230 @@ TEST_F(FileUtilTest, FileAndDirectorySize) {
EXPECT_EQ(size_f1 + size_f2 + 3, computed_size);
}
+TEST_F(FileUtilTest, NormalizeFilePathBasic) {
+ // Create a directory under the test dir. Because we create it,
+ // we know it is not a link.
+ FilePath file_a_path = test_dir_.Append(FPL("file_a"));
+ FilePath dir_path = test_dir_.Append(FPL("dir"));
+ FilePath file_b_path = dir_path.Append(FPL("file_b"));
+ file_util::CreateDirectory(dir_path);
+
+ FilePath normalized_file_a_path, normalized_file_b_path;
+ ASSERT_FALSE(file_util::PathExists(file_a_path));
+ ASSERT_FALSE(file_util::NormalizeFilePath(file_a_path,
+ &normalized_file_a_path))
+ << "NormalizeFilePath() should fail on nonexistant paths.";
+
+ CreateTextFile(file_a_path, bogus_content);
+ ASSERT_TRUE(file_util::PathExists(file_a_path));
+ ASSERT_TRUE(file_util::NormalizeFilePath(file_a_path,
+ &normalized_file_a_path));
+
+ CreateTextFile(file_b_path, bogus_content);
+ ASSERT_TRUE(file_util::PathExists(file_b_path));
+ ASSERT_TRUE(file_util::NormalizeFilePath(file_b_path,
+ &normalized_file_b_path));
+
+ // Beacuse this test created |dir_path|, we know it is not a link
+ // or junction. So, the real path of the directory holding file a
+ // must be the parent of the path holding file b.
+ ASSERT_TRUE(normalized_file_a_path.DirName()
+ .IsParent(normalized_file_b_path.DirName()));
+}
+
+#if defined(OS_WIN)
+
+TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) {
+ // Build the following directory structure:
+ //
+ // test_dir_
+ // |-> base_a
+ // | |-> sub_a
+ // | |-> file.txt
+ // | |-> long_name___... (Very long name.)
+ // | |-> sub_long
+ // | |-> deep.txt
+ // |-> base_b
+ // |-> to_sub_a (reparse point to test_dir_\base_a\sub_a)
+ // |-> to_base_b (reparse point to test_dir_\base_b)
+ // |-> to_sub_long (reparse point to test_dir_\sub_a\long_name_\sub_long)
+
+ FilePath base_a = test_dir_.Append(FPL("base_a"));
+ ASSERT_TRUE(file_util::CreateDirectory(base_a));
+
+ FilePath sub_a = base_a.Append(FPL("sub_a"));
+ ASSERT_TRUE(file_util::CreateDirectory(sub_a));
+
+ FilePath file_txt = sub_a.Append(FPL("file.txt"));
+ CreateTextFile(file_txt, bogus_content);
+
+ // Want a directory whose name is long enough to make the path to the file
+ // inside just under MAX_PATH chars. This will be used to test that when
+ // a junction expands to a path over MAX_PATH chars in length,
+ // NormalizeFilePath() fails without crashing.
+ FilePath sub_long_rel(FPL("sub_long"));
+ FilePath deep_txt(FPL("deep.txt"));
+
+ int target_length = MAX_PATH;
+ target_length -= (sub_a.value().length() + 1); // +1 for the sepperator '\'.
+ target_length -= (sub_long_rel.Append(deep_txt).value().length() + 1);
+ // Without making the path a bit shorter, CreateDirectory() fails.
+ // the resulting path is still long enough to hit the failing case in
+ // NormalizePath().
+ const int kCreateDirLimit = 4;
+ target_length -= kCreateDirLimit;
+ FilePath::StringType long_name_str = FPL("long_name_");
+ long_name_str.resize(target_length, '_');
+
+ FilePath long_name = sub_a.Append(FilePath(long_name_str));
+ FilePath deep_file = long_name.Append(sub_long_rel).Append(deep_txt);
+ ASSERT_EQ(MAX_PATH - kCreateDirLimit, deep_file.value().length());
+
+ FilePath sub_long = deep_file.DirName();
+ ASSERT_TRUE(file_util::CreateDirectory(sub_long));
+ CreateTextFile(deep_file, bogus_content);
+
+ FilePath base_b = test_dir_.Append(FPL("base_b"));
+ ASSERT_TRUE(file_util::CreateDirectory(base_b));
+
+ FilePath to_sub_a = base_b.Append(FPL("to_sub_a"));
+ ASSERT_TRUE(file_util::CreateDirectory(to_sub_a));
+ ScopedHandle reparse_to_sub_a(
+ ::CreateFile(to_sub_a.value().c_str(),
+ FILE_ALL_ACCESS,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory.
+ NULL));
+ ASSERT_NE(INVALID_HANDLE_VALUE, reparse_to_sub_a.Get());
+ ASSERT_TRUE(SetReparsePoint(reparse_to_sub_a, sub_a));
+
+ FilePath to_base_b = base_b.Append(FPL("to_base_b"));
+ ASSERT_TRUE(file_util::CreateDirectory(to_base_b));
+ ScopedHandle reparse_to_base_b(
+ ::CreateFile(to_base_b.value().c_str(),
+ FILE_ALL_ACCESS,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory.
+ NULL));
+ ASSERT_NE(INVALID_HANDLE_VALUE, reparse_to_base_b.Get());
+ ASSERT_TRUE(SetReparsePoint(reparse_to_base_b, base_b));
+
+ FilePath to_sub_long = base_b.Append(FPL("to_sub_long"));
+ ASSERT_TRUE(file_util::CreateDirectory(to_sub_long));
+ ScopedHandle reparse_to_sub_long(
+ ::CreateFile(to_sub_long.value().c_str(),
+ FILE_ALL_ACCESS,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory.
+ NULL));
+ ASSERT_NE(INVALID_HANDLE_VALUE, reparse_to_sub_long.Get());
+ ASSERT_TRUE(SetReparsePoint(reparse_to_sub_long, sub_long));
+
+ // Normalize a junction free path: base_a\sub_a\file.txt .
+ FilePath normalized_path;
+ ASSERT_TRUE(file_util::NormalizeFilePath(file_txt, &normalized_path));
+ ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str());
+
+ // Check that the path base_b\to_sub_a\file.txt can be normalized to exclude
+ // the junction to_sub_a.
+ ASSERT_TRUE(file_util::NormalizeFilePath(to_sub_a.Append(FPL("file.txt")),
+ &normalized_path));
+ ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str());
+
+ // Check that the path base_b\to_base_b\to_base_b\to_sub_a\file.txt can be
+ // normalized to exclude junctions to_base_b and to_sub_a .
+ ASSERT_TRUE(file_util::NormalizeFilePath(base_b.Append(FPL("to_base_b"))
+ .Append(FPL("to_base_b"))
+ .Append(FPL("to_sub_a"))
+ .Append(FPL("file.txt")),
+ &normalized_path));
+ ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str());
+
+ // A long enough path will cause NormalizeFilePath() to fail. Make a long
+ // path using to_base_b many times, and check that paths long enough to fail
+ // do not cause a crash.
+ FilePath long_path = base_b;
+ const int kLengthLimit = MAX_PATH + 200;
+ while (long_path.value().length() <= kLengthLimit) {
+ long_path = long_path.Append(FPL("to_base_b"));
+ }
+ long_path = long_path.Append(FPL("to_sub_a"))
+ .Append(FPL("file.txt"));
+
+ ASSERT_FALSE(file_util::NormalizeFilePath(long_path, &normalized_path));
+
+ // Normalizing the junction to deep.txt should fail, because the expanded
+ // path to deep.txt is longer than MAX_PATH.
+ ASSERT_FALSE(file_util::NormalizeFilePath(to_sub_long.Append(deep_txt),
+ &normalized_path));
+
+ // Delete the reparse points, and see that NormalizeFilePath() fails
+ // to traverse them.
+ ASSERT_TRUE(DeleteReparsePoint(reparse_to_sub_a));
+ ASSERT_TRUE(DeleteReparsePoint(reparse_to_base_b));
+ ASSERT_TRUE(DeleteReparsePoint(reparse_to_sub_long));
+
+ ASSERT_FALSE(file_util::NormalizeFilePath(to_sub_a.Append(FPL("file.txt")),
+ &normalized_path));
+}
+
+#endif // defined(OS_WIN)
+
+// The following test of NormalizeFilePath() require that we create a symlink.
+// This can not be done on windows before vista. On vista, creating a symlink
+// requires privilege "SeCreateSymbolicLinkPrivilege".
+// TODO(skerner): Investigate the possibility of giving base_unittests the
+// privileges required to create a symlink.
#if defined(OS_POSIX)
-TEST_F(FileUtilTest, RealPath) {
- // Get the real test directory, in case some future change to the
- // test setup makes the path to test_dir_ include a symlink.
- FilePath real_test_dir;
- ASSERT_TRUE(file_util::RealPath(test_dir_, &real_test_dir));
- FilePath real_path;
- ASSERT_TRUE(file_util::RealPath(real_test_dir, &real_path));
- ASSERT_TRUE(real_test_dir == real_path);
+bool MakeSymlink(const FilePath& link_to, const FilePath& link_from) {
+ return (symlink(link_to.value().c_str(), link_from.value().c_str()) == 0);
+}
+
+TEST_F(FileUtilTest, NormalizeFilePathSymlinks) {
+ FilePath normalized_path;
// Link one file to another.
- FilePath link_from = real_test_dir.Append(FPL("from_file"));
- FilePath link_to = real_test_dir.Append(FPL("to_file"));
+ FilePath link_from = test_dir_.Append(FPL("from_file"));
+ FilePath link_to = test_dir_.Append(FPL("to_file"));
CreateTextFile(link_to, bogus_content);
- ASSERT_EQ(0, symlink(link_to.value().c_str(), link_from.value().c_str()))
+ ASSERT_TRUE(MakeSymlink(link_to, link_from))
<< "Failed to create file symlink.";
- // Check that RealPath sees the link.
- ASSERT_TRUE(file_util::RealPath(link_from, &real_path));
+ // Check that NormalizeFilePath sees the link.
+ ASSERT_TRUE(file_util::NormalizeFilePath(link_from, &normalized_path));
ASSERT_TRUE(link_to != link_from);
- ASSERT_TRUE(link_to == real_path);
-
+ ASSERT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value());
+ ASSERT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value());
// Link to a directory.
- link_from = real_test_dir.Append(FPL("from_dir"));
- link_to = real_test_dir.Append(FPL("to_dir"));
+ link_from = test_dir_.Append(FPL("from_dir"));
+ link_to = test_dir_.Append(FPL("to_dir"));
file_util::CreateDirectory(link_to);
- ASSERT_EQ(0, symlink(link_to.value().c_str(), link_from.value().c_str()))
+ ASSERT_TRUE(MakeSymlink(link_to, link_from))
<< "Failed to create directory symlink.";
- ASSERT_TRUE(file_util::RealPath(link_from, &real_path));
- ASSERT_TRUE(link_to != link_from);
- ASSERT_TRUE(link_to == real_path);
-
+ ASSERT_FALSE(file_util::NormalizeFilePath(link_from, &normalized_path))
+ << "Links to directories should return false.";
- // Test that a loop in the links causes RealPath() to return false.
- link_from = real_test_dir.Append(FPL("link_a"));
- link_to = real_test_dir.Append(FPL("link_b"));
- ASSERT_EQ(0, symlink(link_to.value().c_str(), link_from.value().c_str()))
+ // Test that a loop in the links causes NormalizeFilePath() to return false.
+ link_from = test_dir_.Append(FPL("link_a"));
+ link_to = test_dir_.Append(FPL("link_b"));
+ ASSERT_TRUE(MakeSymlink(link_to, link_from))
<< "Failed to create loop symlink a.";
- ASSERT_EQ(0, symlink(link_from.value().c_str(), link_to.value().c_str()))
+ ASSERT_TRUE(MakeSymlink(link_from, link_to))
<< "Failed to create loop symlink b.";
// Infinite loop!
- ASSERT_FALSE(file_util::RealPath(link_from, &real_path));
+ ASSERT_FALSE(file_util::NormalizeFilePath(link_from, &normalized_path));
}
#endif // defined(OS_POSIX)
« no previous file with comments | « base/file_util_posix.cc ('k') | base/file_util_win.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698