╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ serve_ssh.zig ────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
246 247 test "parseGitCommand rejects malformed input" { 248 const allocator = std.testing.allocator; 249 try std.testing.expectError(error.InvalidCommand, parseGitCommand(allocator, "git-upload-pack")); 250 try std.testing.expectError(error.UnsupportedService, parseGitCommand(allocator, "ls -la")); 251 try std.testing.expectError(error.UnsupportedService, parseGitCommand(allocator, "git-fake-pack 'repo'")); 252 } 253 254 test "parseGitCommand happy paths" { 255 const allocator = std.testing.allocator; 256 257 { 258 const parsed = try parseGitCommand(allocator, "git-upload-pack 'some-repo'"); 259 defer parsed.deinit(allocator); 260 try std.testing.expectEqual(GitService.upload_pack, parsed.service); 261 try std.testing.expectEqualStrings("some-repo", parsed.dir); 262 } 263 { 264 const parsed = try parseGitCommand(allocator, "git-receive-pack 'user/proj'"); 265 defer parsed.deinit(allocator); 266 try std.testing.expectEqual(GitService.receive_pack, parsed.service); 267 try std.testing.expectEqualStrings("user/proj", parsed.dir); 268 } 269 { 270 const parsed = try parseGitCommand(allocator, "git-upload-pack repo"); 271 defer parsed.deinit(allocator); 272 try std.testing.expectEqual(GitService.upload_pack, parsed.service); 273 try std.testing.expectEqualStrings("repo", parsed.dir); 274 } 275 } 276 277 test "isSubPath rejects prefix collisions and traversal" { 278 const sep = std.fs.path.sep_str; 279 const parent = sep ++ "srv" ++ sep ++ "git"; 280 281 try std.testing.expect(isSubPath(parent, parent ++ sep ++ "repo")); 282 try std.testing.expect(isSubPath(parent, parent)); 283 284 // common-prefix-but-not-subpath: "/srv/git" should NOT swallow 285 // "/srv/git2" or "/srv/gitignore" just because the bytes start the same. 286 try std.testing.expect(!isSubPath(parent, parent ++ "2")); 287 try std.testing.expect(!isSubPath(parent, parent ++ "ignore")); 288 289 // resolveRepoPath normalizes `..` first, so `/srv/git/../etc/passwd` 290 // becomes `/etc/passwd` — isSubPath then rejects it. 291 try std.testing.expect(!isSubPath(parent, sep ++ "etc" ++ sep ++ "passwd")); 292 293 const builtin = @import("builtin"); 294 295 if (.windows != builtin.os.tag) { 296 try std.testing.expect(isSubPath(sep, sep ++ "anything")); 297 try std.testing.expect(isSubPath(sep, sep)); 298 } 299 } 300