1 #include "clar_libgit2.h"
2 #include "checkout_helpers.h"
3 
4 #include "git2/checkout.h"
5 #include "repository.h"
6 #include "buffer.h"
7 #include "futils.h"
8 
9 static const char *repo_name = "nasty";
10 static git_repository *repo;
11 static git_checkout_options checkout_opts;
12 
test_checkout_nasty__initialize(void)13 void test_checkout_nasty__initialize(void)
14 {
15 	repo = cl_git_sandbox_init(repo_name);
16 
17 	GIT_INIT_STRUCTURE(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION);
18 	checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
19 }
20 
test_checkout_nasty__cleanup(void)21 void test_checkout_nasty__cleanup(void)
22 {
23 	cl_git_sandbox_cleanup();
24 }
25 
test_checkout_passes(const char * refname,const char * filename)26 static void test_checkout_passes(const char *refname, const char *filename)
27 {
28 	git_oid commit_id;
29 	git_commit *commit;
30 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
31 	git_buf path = GIT_BUF_INIT;
32 
33 	cl_git_pass(git_buf_joinpath(&path, repo_name, filename));
34 
35 	cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname));
36 	cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
37 
38 	opts.checkout_strategy = GIT_CHECKOUT_FORCE |
39 		GIT_CHECKOUT_DONT_UPDATE_INDEX;
40 
41 	cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts));
42 	cl_assert(!git_path_exists(path.ptr));
43 
44 	git_commit_free(commit);
45 	git_buf_dispose(&path);
46 }
47 
test_checkout_fails(const char * refname,const char * filename)48 static void test_checkout_fails(const char *refname, const char *filename)
49 {
50 	git_oid commit_id;
51 	git_commit *commit;
52 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
53 	git_buf path = GIT_BUF_INIT;
54 
55 	cl_git_pass(git_buf_joinpath(&path, repo_name, filename));
56 
57 	cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname));
58 	cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
59 
60 	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
61 
62 	cl_git_fail(git_checkout_tree(repo, (const git_object *)commit, &opts));
63 	cl_assert(!git_path_exists(path.ptr));
64 
65 	git_commit_free(commit);
66 	git_buf_dispose(&path);
67 }
68 
69 /* A tree that contains ".git" as a tree, with a blob inside
70  * (".git/foobar").
71  */
test_checkout_nasty__dotgit_tree(void)72 void test_checkout_nasty__dotgit_tree(void)
73 {
74 	test_checkout_fails("refs/heads/dotgit_tree", ".git/foobar");
75 }
76 
77 /* A tree that contains ".GIT" as a tree, with a blob inside
78  * (".GIT/foobar").
79  */
test_checkout_nasty__dotcapitalgit_tree(void)80 void test_checkout_nasty__dotcapitalgit_tree(void)
81 {
82 	test_checkout_fails("refs/heads/dotcapitalgit_tree", ".GIT/foobar");
83 }
84 
85 /* A tree that contains a tree ".", with a blob inside ("./foobar").
86  */
test_checkout_nasty__dot_tree(void)87 void test_checkout_nasty__dot_tree(void)
88 {
89 	test_checkout_fails("refs/heads/dot_tree", "foobar");
90 }
91 
92 /* A tree that contains a tree ".", with a tree ".git", with a blob
93  * inside ("./.git/foobar").
94  */
test_checkout_nasty__dot_dotgit_tree(void)95 void test_checkout_nasty__dot_dotgit_tree(void)
96 {
97 	test_checkout_fails("refs/heads/dot_dotgit_tree", ".git/foobar");
98 }
99 
100 /* A tree that contains a tree, with a tree "..", with a tree ".git", with a
101  * blob inside ("foo/../.git/foobar").
102  */
test_checkout_nasty__dotdot_dotgit_tree(void)103 void test_checkout_nasty__dotdot_dotgit_tree(void)
104 {
105 	test_checkout_fails("refs/heads/dotdot_dotgit_tree", ".git/foobar");
106 }
107 
108 /* A tree that contains a tree, with a tree "..", with a blob inside
109  * ("foo/../foobar").
110  */
test_checkout_nasty__dotdot_tree(void)111 void test_checkout_nasty__dotdot_tree(void)
112 {
113 	test_checkout_fails("refs/heads/dotdot_tree", "foobar");
114 }
115 
116 /* A tree that contains a blob with the rogue name ".git/foobar" */
test_checkout_nasty__dotgit_path(void)117 void test_checkout_nasty__dotgit_path(void)
118 {
119 	test_checkout_fails("refs/heads/dotgit_path", ".git/foobar");
120 }
121 
122 /* A tree that contains a blob with the rogue name ".GIT/foobar" */
test_checkout_nasty__dotcapitalgit_path(void)123 void test_checkout_nasty__dotcapitalgit_path(void)
124 {
125 	test_checkout_fails("refs/heads/dotcapitalgit_path", ".GIT/foobar");
126 }
127 
128 /* A tree that contains a blob with the rogue name "./.git/foobar" */
test_checkout_nasty__dot_dotgit_path(void)129 void test_checkout_nasty__dot_dotgit_path(void)
130 {
131 	test_checkout_fails("refs/heads/dot_dotgit_path", ".git/foobar");
132 }
133 
134 /* A tree that contains a blob with the rogue name "./.GIT/foobar" */
test_checkout_nasty__dot_dotcapitalgit_path(void)135 void test_checkout_nasty__dot_dotcapitalgit_path(void)
136 {
137 	test_checkout_fails("refs/heads/dot_dotcapitalgit_path", ".GIT/foobar");
138 }
139 
140 /* A tree that contains a blob with the rogue name "foo/../.git/foobar" */
test_checkout_nasty__dotdot_dotgit_path(void)141 void test_checkout_nasty__dotdot_dotgit_path(void)
142 {
143 	test_checkout_fails("refs/heads/dotdot_dotgit_path", ".git/foobar");
144 }
145 
146 /* A tree that contains a blob with the rogue name "foo/../.GIT/foobar" */
test_checkout_nasty__dotdot_dotcapitalgit_path(void)147 void test_checkout_nasty__dotdot_dotcapitalgit_path(void)
148 {
149 	test_checkout_fails("refs/heads/dotdot_dotcapitalgit_path", ".GIT/foobar");
150 }
151 
152 /* A tree that contains a blob with the rogue name "foo/." */
test_checkout_nasty__dot_path(void)153 void test_checkout_nasty__dot_path(void)
154 {
155 	test_checkout_fails("refs/heads/dot_path", "./foobar");
156 }
157 
158 /* A tree that contains a blob with the rogue name "foo/." */
test_checkout_nasty__dot_path_two(void)159 void test_checkout_nasty__dot_path_two(void)
160 {
161 	test_checkout_fails("refs/heads/dot_path_two", "foo/.");
162 }
163 
164 /* A tree that contains a blob with the rogue name "foo/../foobar" */
test_checkout_nasty__dotdot_path(void)165 void test_checkout_nasty__dotdot_path(void)
166 {
167 	test_checkout_fails("refs/heads/dotdot_path", "foobar");
168 }
169 
170 /* A tree that contains an entry with a backslash ".git\foobar"  */
test_checkout_nasty__dotgit_backslash_path(void)171 void test_checkout_nasty__dotgit_backslash_path(void)
172 {
173 #ifdef GIT_WIN32
174 	test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar");
175 #endif
176 }
177 
178 /* A tree that contains an entry with a backslash ".GIT\foobar"  */
test_checkout_nasty__dotcapitalgit_backslash_path(void)179 void test_checkout_nasty__dotcapitalgit_backslash_path(void)
180 {
181 #ifdef GIT_WIN32
182 	test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar");
183 #endif
184 }
185 
186 /* A tree that contains an entry with a backslash ".\.GIT\foobar"  */
test_checkout_nasty__dot_backslash_dotcapitalgit_path(void)187 void test_checkout_nasty__dot_backslash_dotcapitalgit_path(void)
188 {
189 #ifdef GIT_WIN32
190 	test_checkout_fails("refs/heads/dot_backslash_dotcapitalgit_path", ".GIT/foobar");
191 #endif
192 }
193 
194 /* A tree that contains an entry ".git.", because Win32 APIs will drop the
195  * trailing slash.
196  */
test_checkout_nasty__dot_git_dot(void)197 void test_checkout_nasty__dot_git_dot(void)
198 {
199 #ifdef GIT_WIN32
200 	test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar");
201 #endif
202 }
203 
204 /* A tree that contains an entry "git~1", because that is typically the
205  * short name for ".git".
206  */
test_checkout_nasty__git_tilde1(void)207 void test_checkout_nasty__git_tilde1(void)
208 {
209 	test_checkout_fails("refs/heads/git_tilde1", ".git/foobar");
210 	test_checkout_fails("refs/heads/git_tilde1", "git~1/foobar");
211 }
212 
213 /* A tree that contains an entry "git~2", when we have forced the short
214  * name for ".git" into "GIT~2".
215  */
test_checkout_nasty__git_custom_shortname(void)216 void test_checkout_nasty__git_custom_shortname(void)
217 {
218 #ifdef GIT_WIN32
219 	if (!cl_sandbox_supports_8dot3())
220 		clar__skip();
221 
222 	cl_must_pass(p_rename("nasty/.git", "nasty/_temp"));
223 	cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666);
224 	cl_must_pass(p_rename("nasty/_temp", "nasty/.git"));
225 	test_checkout_fails("refs/heads/git_tilde2", ".git/foobar");
226 #endif
227 }
228 
229 /* A tree that contains an entry "git~3", which should be allowed, since
230  * it is not the typical short name ("GIT~1") or the actual short name
231  * ("GIT~2") for ".git".
232  */
test_checkout_nasty__only_looks_like_a_git_shortname(void)233 void test_checkout_nasty__only_looks_like_a_git_shortname(void)
234 {
235 #ifdef GIT_WIN32
236 	git_oid commit_id;
237 	git_commit *commit;
238 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
239 
240 	cl_must_pass(p_rename("nasty/.git", "nasty/_temp"));
241 	cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666);
242 	cl_must_pass(p_rename("nasty/_temp", "nasty/.git"));
243 
244 	cl_git_pass(git_reference_name_to_id(&commit_id, repo, "refs/heads/git_tilde3"));
245 	cl_git_pass(git_commit_lookup(&commit, repo, &commit_id));
246 
247 	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
248 
249 	cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts));
250 	cl_assert(git_path_exists("nasty/git~3/foobar"));
251 
252 	git_commit_free(commit);
253 #endif
254 }
255 
256 /* A tree that contains an entry "git:", because Win32 APIs will reject
257  * that as looking too similar to a drive letter.
258  */
test_checkout_nasty__dot_git_colon(void)259 void test_checkout_nasty__dot_git_colon(void)
260 {
261 #ifdef GIT_WIN32
262 	test_checkout_fails("refs/heads/dot_git_colon", ".git/foobar");
263 #endif
264 }
265 
266 /* A tree that contains an entry "git:foo", because Win32 APIs will turn
267  * that into ".git".
268  */
test_checkout_nasty__dot_git_colon_stuff(void)269 void test_checkout_nasty__dot_git_colon_stuff(void)
270 {
271 #ifdef GIT_WIN32
272 	test_checkout_fails("refs/heads/dot_git_colon_stuff", ".git/foobar");
273 #endif
274 }
275 
276 /* A tree that contains an entry ".git::$INDEX_ALLOCATION" because NTFS
277  * will interpret that as a synonym to ".git", even when mounted via SMB
278  * on macOS.
279  */
test_checkout_nasty__dotgit_alternate_data_stream(void)280 void test_checkout_nasty__dotgit_alternate_data_stream(void)
281 {
282 	test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git/dummy-file");
283 	test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git::$INDEX_ALLOCATION/dummy-file");
284 }
285 
286 /* Trees that contains entries with a tree ".git" that contain
287  * byte sequences:
288  * { 0xe2, 0x80, 0x8c }
289  * { 0xe2, 0x80, 0x8d }
290  * { 0xe2, 0x80, 0x8e }
291  * { 0xe2, 0x80, 0x8f }
292  * { 0xe2, 0x80, 0xaa }
293  * { 0xe2, 0x80, 0xab }
294  * { 0xe2, 0x80, 0xac }
295  * { 0xe2, 0x80, 0xad }
296  * { 0xe2, 0x81, 0xae }
297  * { 0xe2, 0x81, 0xaa }
298  * { 0xe2, 0x81, 0xab }
299  * { 0xe2, 0x81, 0xac }
300  * { 0xe2, 0x81, 0xad }
301  * { 0xe2, 0x81, 0xae }
302  * { 0xe2, 0x81, 0xaf }
303  * { 0xef, 0xbb, 0xbf }
304  * Because these map to characters that HFS filesystems "ignore".  Thus
305  * ".git<U+200C>" will map to ".git".
306  */
test_checkout_nasty__dot_git_hfs_ignorable(void)307 void test_checkout_nasty__dot_git_hfs_ignorable(void)
308 {
309 #ifdef __APPLE__
310 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar");
311 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar");
312 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar");
313 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar");
314 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar");
315 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar");
316 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar");
317 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar");
318 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar");
319 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar");
320 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar");
321 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar");
322 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar");
323 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar");
324 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar");
325 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar");
326 #endif
327 }
328 
test_checkout_nasty__honors_core_protecthfs(void)329 void test_checkout_nasty__honors_core_protecthfs(void)
330 {
331 	cl_repo_set_bool(repo, "core.protectHFS", true);
332 
333 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar");
334 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar");
335 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar");
336 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar");
337 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar");
338 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar");
339 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar");
340 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar");
341 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar");
342 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar");
343 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar");
344 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar");
345 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar");
346 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar");
347 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar");
348 	test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar");
349 }
350 
test_checkout_nasty__honors_core_protectntfs(void)351 void test_checkout_nasty__honors_core_protectntfs(void)
352 {
353 	cl_repo_set_bool(repo, "core.protectNTFS", true);
354 
355 	test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar");
356 	test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar");
357 	test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar");
358 	test_checkout_fails("refs/heads/git_tilde1", ".git/foobar");
359 }
360 
test_checkout_nasty__symlink1(void)361 void test_checkout_nasty__symlink1(void)
362 {
363 	test_checkout_passes("refs/heads/symlink1", ".git/foobar");
364 }
365 
test_checkout_nasty__symlink2(void)366 void test_checkout_nasty__symlink2(void)
367 {
368 	test_checkout_passes("refs/heads/symlink2", ".git/foobar");
369 }
370 
test_checkout_nasty__symlink3(void)371 void test_checkout_nasty__symlink3(void)
372 {
373 	test_checkout_passes("refs/heads/symlink3", ".git/foobar");
374 }
375 
test_checkout_nasty__gitmodules_symlink(void)376 void test_checkout_nasty__gitmodules_symlink(void)
377 {
378 	cl_repo_set_bool(repo, "core.protectHFS", true);
379 	test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules");
380 	cl_repo_set_bool(repo, "core.protectHFS", false);
381 
382 	cl_repo_set_bool(repo, "core.protectNTFS", true);
383 	test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules");
384 	cl_repo_set_bool(repo, "core.protectNTFS", false);
385 
386 	test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules");
387 }
388