• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2012 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifdef UNSAFE_BUFFERS_BUILD
6// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7#pragma allow_unsafe_buffers
8#endif
9
10#include "base/mac/mac_util.h"
11
12#import <Cocoa/Cocoa.h>
13#include <errno.h>
14#include <stddef.h>
15#include <stdint.h>
16#include <sys/xattr.h>
17
18#include "base/apple/foundation_util.h"
19#include "base/apple/scoped_cftyperef.h"
20#include "base/files/file_path.h"
21#include "base/files/file_util.h"
22#include "base/files/scoped_temp_dir.h"
23#include "base/system/sys_info.h"
24#include "testing/gtest/include/gtest/gtest.h"
25#include "testing/platform_test.h"
26
27namespace base::mac {
28
29namespace {
30
31using MacUtilTest = PlatformTest;
32
33TEST_F(MacUtilTest, GetUserDirectoryTest) {
34  // Try a few keys, make sure they come back with non-empty paths.
35  FilePath caches_dir;
36  EXPECT_TRUE(apple::GetUserDirectory(NSCachesDirectory, &caches_dir));
37  EXPECT_FALSE(caches_dir.empty());
38
39  FilePath application_support_dir;
40  EXPECT_TRUE(apple::GetUserDirectory(NSApplicationSupportDirectory,
41                                      &application_support_dir));
42  EXPECT_FALSE(application_support_dir.empty());
43
44  FilePath library_dir;
45  EXPECT_TRUE(apple::GetUserDirectory(NSLibraryDirectory, &library_dir));
46  EXPECT_FALSE(library_dir.empty());
47}
48
49TEST_F(MacUtilTest, TestLibraryPath) {
50  FilePath library_dir = apple::GetUserLibraryPath();
51  // Make sure the string isn't empty.
52  EXPECT_FALSE(library_dir.value().empty());
53}
54
55TEST_F(MacUtilTest, TestGetAppBundlePath) {
56  FilePath out;
57
58  // Make sure it doesn't crash.
59  out = apple::GetAppBundlePath(FilePath());
60  EXPECT_TRUE(out.empty());
61
62  // Some more invalid inputs.
63  const char* const invalid_inputs[] = {
64    "/", "/foo", "foo", "/foo/bar.", "foo/bar.", "/foo/bar./bazquux",
65    "foo/bar./bazquux", "foo/.app", "//foo",
66  };
67  for (size_t i = 0; i < std::size(invalid_inputs); i++) {
68    out = apple::GetAppBundlePath(FilePath(invalid_inputs[i]));
69    EXPECT_TRUE(out.empty()) << "loop: " << i;
70  }
71
72  // Some valid inputs; this and |expected_outputs| should be in sync.
73  struct {
74    const char *in;
75    const char *expected_out;
76  } valid_inputs[] = {
77    { "FooBar.app/", "FooBar.app" },
78    { "/FooBar.app", "/FooBar.app" },
79    { "/FooBar.app/", "/FooBar.app" },
80    { "//FooBar.app", "//FooBar.app" },
81    { "/Foo/Bar.app", "/Foo/Bar.app" },
82    { "/Foo/Bar.app/", "/Foo/Bar.app" },
83    { "/F/B.app", "/F/B.app" },
84    { "/F/B.app/", "/F/B.app" },
85    { "/Foo/Bar.app/baz", "/Foo/Bar.app" },
86    { "/Foo/Bar.app/baz/", "/Foo/Bar.app" },
87    { "/Foo/Bar.app/baz/quux.app/quuux", "/Foo/Bar.app" },
88    { "/Applications/Google Foo.app/bar/Foo Helper.app/quux/Foo Helper",
89        "/Applications/Google Foo.app" },
90  };
91  for (size_t i = 0; i < std::size(valid_inputs); i++) {
92    out = apple::GetAppBundlePath(FilePath(valid_inputs[i].in));
93    EXPECT_FALSE(out.empty()) << "loop: " << i;
94    EXPECT_STREQ(valid_inputs[i].expected_out,
95        out.value().c_str()) << "loop: " << i;
96  }
97}
98
99TEST_F(MacUtilTest, TestGetInnermostAppBundlePath) {
100  FilePath out;
101
102  // Make sure it doesn't crash.
103  out = apple::GetInnermostAppBundlePath(FilePath());
104  EXPECT_TRUE(out.empty());
105
106  // Some more invalid inputs.
107  const char* const invalid_inputs[] = {
108      "/",
109      "/foo",
110      "foo",
111      "/foo/bar.",
112      "foo/bar.",
113      "/foo/bar./bazquux",
114      "foo/bar./bazquux",
115      "foo/.app",
116      "//foo",
117  };
118  for (size_t i = 0; i < std::size(invalid_inputs); i++) {
119    SCOPED_TRACE(testing::Message()
120                 << "case #" << i << ", input: " << invalid_inputs[i]);
121    out = apple::GetInnermostAppBundlePath(FilePath(invalid_inputs[i]));
122    EXPECT_TRUE(out.empty());
123  }
124
125  // Some valid inputs; this and |expected_outputs| should be in sync.
126  struct {
127    const char* in;
128    const char* expected_out;
129  } valid_inputs[] = {
130      {"FooBar.app/", "FooBar.app"},
131      {"/FooBar.app", "/FooBar.app"},
132      {"/FooBar.app/", "/FooBar.app"},
133      {"//FooBar.app", "//FooBar.app"},
134      {"/Foo/Bar.app", "/Foo/Bar.app"},
135      {"/Foo/Bar.app/", "/Foo/Bar.app"},
136      {"/F/B.app", "/F/B.app"},
137      {"/F/B.app/", "/F/B.app"},
138      {"/Foo/Bar.app/baz", "/Foo/Bar.app"},
139      {"/Foo/Bar.app/baz/", "/Foo/Bar.app"},
140      {"/Foo/Bar.app/baz/quux.app/quuux", "/Foo/Bar.app/baz/quux.app"},
141      {"/Applications/Google Foo.app/bar/Foo Helper.app/quux/Foo Helper",
142       "/Applications/Google Foo.app/bar/Foo Helper.app"},
143  };
144  for (size_t i = 0; i < std::size(valid_inputs); i++) {
145    SCOPED_TRACE(testing::Message()
146                 << "case #" << i << ", input " << valid_inputs[i].in);
147    out = apple::GetInnermostAppBundlePath(FilePath(valid_inputs[i].in));
148    EXPECT_FALSE(out.empty());
149    EXPECT_STREQ(valid_inputs[i].expected_out, out.value().c_str());
150  }
151}
152
153TEST_F(MacUtilTest, MacOSVersion) {
154  int32_t major, minor, bugfix;
155  base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
156
157  EXPECT_EQ(major * 1'00'00 + minor * 1'00 + bugfix, MacOSVersion());
158  EXPECT_EQ(major, MacOSMajorVersion());
159}
160
161TEST_F(MacUtilTest, ParseOSProductVersion) {
162  // Various strings in shapes that would be expected to be returned from the
163  // API that would need to be parsed.
164  EXPECT_EQ(10'06'02, ParseOSProductVersionForTesting("10.6.2"));
165  EXPECT_EQ(10'15'00, ParseOSProductVersionForTesting("10.15"));
166  EXPECT_EQ(13'05'01, ParseOSProductVersionForTesting("13.5.1"));
167  EXPECT_EQ(14'00'00, ParseOSProductVersionForTesting("14.0"));
168
169  // Various strings in shapes that would not be expected, but that should parse
170  // without CHECKing.
171  EXPECT_EQ(13'04'01, ParseOSProductVersionForTesting("13.4.1 (c)"));
172  EXPECT_EQ(14'00'00, ParseOSProductVersionForTesting("14.0.0"));
173  EXPECT_EQ(18'00'00, ParseOSProductVersionForTesting("18"));
174  EXPECT_EQ(18'03'04, ParseOSProductVersionForTesting("18.3.4.3.2.5"));
175
176  // Various strings in shapes that are so unexpected that they should not
177  // parse.
178  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("Mac OS X 10.0"),
179                            "");
180  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting(""), "");
181  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("  "), "");
182  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("."), "");
183  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("10.a.5"), "");
184  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("१०.१५.७"), "");
185  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("7.6.1"), "");
186  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("10.16"), "");
187}
188
189TEST_F(MacUtilTest, TestRemoveQuarantineAttribute) {
190  ScopedTempDir temp_dir_;
191  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
192  FilePath dummy_folder_path = temp_dir_.GetPath().Append("DummyFolder");
193  ASSERT_TRUE(base::CreateDirectory(dummy_folder_path));
194  const char* quarantine_str = "0000;4b392bb2;Chromium;|org.chromium.Chromium";
195  const char* file_path_str = dummy_folder_path.value().c_str();
196  EXPECT_EQ(0, setxattr(file_path_str, "com.apple.quarantine",
197      quarantine_str, strlen(quarantine_str), 0, 0));
198  EXPECT_EQ(static_cast<long>(strlen(quarantine_str)),
199            getxattr(file_path_str, "com.apple.quarantine", /*value=*/nullptr,
200                     /*size=*/0, /*position=*/0, /*options=*/0));
201  EXPECT_TRUE(RemoveQuarantineAttribute(dummy_folder_path));
202  EXPECT_EQ(-1,
203            getxattr(file_path_str, "com.apple.quarantine", /*value=*/nullptr,
204                     /*size=*/0, /*position=*/0, /*options=*/0));
205  EXPECT_EQ(ENOATTR, errno);
206}
207
208TEST_F(MacUtilTest, TestRemoveQuarantineAttributeTwice) {
209  ScopedTempDir temp_dir_;
210  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
211  FilePath dummy_folder_path = temp_dir_.GetPath().Append("DummyFolder");
212  const char* file_path_str = dummy_folder_path.value().c_str();
213  ASSERT_TRUE(base::CreateDirectory(dummy_folder_path));
214  EXPECT_EQ(-1,
215            getxattr(file_path_str, "com.apple.quarantine", /*value=*/nullptr,
216                     /*size=*/0, /*position=*/0, /*options=*/0));
217  // No quarantine attribute to begin with, but RemoveQuarantineAttribute still
218  // succeeds because in the end the folder still doesn't have the quarantine
219  // attribute set.
220  EXPECT_TRUE(RemoveQuarantineAttribute(dummy_folder_path));
221  EXPECT_TRUE(RemoveQuarantineAttribute(dummy_folder_path));
222  EXPECT_EQ(ENOATTR, errno);
223}
224
225TEST_F(MacUtilTest, TestRemoveQuarantineAttributeNonExistentPath) {
226  ScopedTempDir temp_dir_;
227  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
228  FilePath non_existent_path = temp_dir_.GetPath().Append("DummyPath");
229  ASSERT_FALSE(PathExists(non_existent_path));
230  EXPECT_FALSE(RemoveQuarantineAttribute(non_existent_path));
231}
232
233}  // namespace
234
235}  // namespace base::mac
236