╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │323 fn run(conn: @This()) void { │ │324 defer conn.stream.close(conn.io); │ │345 - handleSshGitConnection(repo_kind, any_repo_opts, conn.io, conn.allocator, conn.repo_root_path, conn.stream) catch |request_err| { │ │346 - logError(conn.err, "ssh request failed: {s}\n", .{@errorName(request_err)}); │ │325 + serve_ssh_protocol.handleConnection(conn.io, conn.allocator, conn.stream, conn.host_key, conn.session_handler) catch |session_err| {│ │326 + logError(conn.err, "ssh session failed: {s}\n", .{@errorName(session_err)}); │ │327 }; │ │328 } │ │329 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │331 ctx.tasks.async(ctx.io, Connection.run, .{Connection{│ │332 .io = ctx.io, │ │333 .allocator = ctx.allocator, │ │354 - .repo_root_path = ctx.repo_root_path, │ │334 + .host_key = ctx.host_key, │ │335 + .session_handler = ctx.session_handler, │ │336 .stream = stream, │ │337 .err = ctx.err, │ │338 }}); │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────╮ │343 tasks.async(io, Listener.run, .{Listener{│ │344 .io = io, │ │345 .allocator = allocator, │ │365 - .repo_root_path = repo_root_path, │ │346 + .host_key = host_key, │ │347 + .session_handler = session_handler, │ │348 .net_server = net_server, │ │349 .tasks = tasks, │ │350 .err = err, │ ╰──────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │351 }}); │ │352 } │ │371 - │ │372 -fn runTuiListener( │ │373 - io: std.Io, │ │374 - allocator: std.mem.Allocator, │ │375 - admin_repo_path: []const u8, │ │376 - net_server: *std.Io.net.Server, │ │377 - tasks: *std.Io.Group, │ │378 - err: *std.Io.Writer, │ │379 -) void { │ │380 - const Listener = struct { │ │381 - io: std.Io, │ │382 - allocator: std.mem.Allocator, │ │383 - admin_repo_path: []const u8, │ │384 - net_server: *std.Io.net.Server, │ │385 - tasks: *std.Io.Group, │ │386 - err: *std.Io.Writer, │ │387 - │ │388 - fn run(ctx: @This()) void { │ │389 - while (true) { │ │390 - const stream = ctx.net_server.accept(ctx.io) catch |accept_err| { │ │391 - if (accept_err == error.Canceled) return; │ │392 - logError(ctx.err, "tui accept failed: {s}\n", .{@errorName(accept_err)}); │ │393 - continue; │ │394 - }; │ │353 │ │396 - const Connection = struct { │ │397 - io: std.Io, │ │398 - allocator: std.mem.Allocator, │ │399 - admin_repo_path: []const u8, │ │400 - stream: std.Io.net.Stream, │ │401 - err: *std.Io.Writer, │ │402 - │ │403 - fn run(conn: @This()) void { │ │404 - defer conn.stream.close(conn.io); │ │405 - serve_ssh_tui.handleConnection(conn.io, conn.allocator, conn.stream, conn.admin_repo_path) catch |session_err| {│ │406 - logError(conn.err, "tui session failed: {s}\n", .{@errorName(session_err)}); │ │407 - }; │ │408 - } │ │409 - }; │ │410 - │ │411 - ctx.tasks.async(ctx.io, Connection.run, .{Connection{ │ │412 - .io = ctx.io, │ │413 - .allocator = ctx.allocator, │ │414 - .admin_repo_path = ctx.admin_repo_path, │ │415 - .stream = stream, │ │416 - .err = ctx.err, │ │417 - }}); │ │418 - } │ │419 - } │ │420 - }; │ │421 - │ │422 - tasks.async(io, Listener.run, .{Listener{ │ │423 - .io = io, │ │424 - .allocator = allocator, │ │425 - .admin_repo_path = admin_repo_path, │ │426 - .net_server = net_server, │ │427 - .tasks = tasks, │ │428 - .err = err, │ │429 - }}); │ │430 -} │ │431 - │ │432 -fn handleSshGitConnection( │ │433 - comptime repo_kind: rp.RepoKind, │ │434 - comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ │435 - io: std.Io, │ │436 - allocator: std.mem.Allocator, │ │437 - repo_root_path: []const u8, │ │438 - stream: std.Io.net.Stream, │ │439 -) !void { │ │440 - var send_buffer = [_]u8{0} ** any_repo_opts.net_buffer_size; │ │441 - var recv_buffer = [_]u8{0} ** any_repo_opts.net_buffer_size; │ │442 - var reader = stream.reader(io, &recv_buffer); │ │443 - var writer = stream.writer(io, &send_buffer); │ │444 - │ │445 - const request = try readSshRequest(allocator, &reader.interface); │ │446 - defer request.deinit(allocator); │ │447 - │ │448 - const repo_path = try resolveSshRepoPath(allocator, repo_root_path, request.repo); │ │449 - defer allocator.free(repo_path); │ │450 - │ │451 - if (!isSubPath(repo_root_path, repo_path)) return error.Forbidden; │ │452 - │ │453 - try openRepoAndServe(repo_kind, any_repo_opts, io, allocator, repo_path, request.service == .receive_pack, SshGitService{ │ │454 - .reader = &reader.interface, │ │455 - .writer = &writer.interface, │ │456 - .request = request, │ │457 - }); │ │458 -} │ │459 - │ │354 fn openRepoAndServe( │ │355 comptime repo_kind: rp.RepoKind, │ │356 comptime any_repo_opts: rp.AnyRepoOpts(repo_kind), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │422 allocator: std.mem.Allocator, │ │423 ) !void { │ │424 try repo.httpBackend(io, allocator, self.body_reader, self.writer, .http, self.options);│ │531 - } │ │532 -}; │ │533 - │ │534 -const SshGitService = struct { │ │535 - reader: *std.Io.Reader, │ │536 - writer: *std.Io.Writer, │ │537 - request: SshRequest, │ │538 - │ │539 - fn serve( │ │540 - self: @This(), │ │541 - comptime repo_kind: rp.RepoKind, │ │542 - comptime repo_opts: rp.RepoOpts(repo_kind), │ │543 - repo: *rp.Repo(repo_kind, repo_opts), │ │544 - io: std.Io, │ │545 - allocator: std.mem.Allocator, │ │546 - ) !void { │ │547 - switch (self.request.service) { │ │548 - .upload_pack => try repo.uploadPack(io, allocator, self.reader, self.writer, .{ │ │549 - .protocol_version = self.request.protocol_version, │ │550 - }), │ │551 - .receive_pack => try repo.receivePack(io, allocator, self.reader, self.writer, .{ │ │552 - .protocol_version = self.request.protocol_version, │ │553 - }), │ │554 - } │ │425 } │ │426 }; │ │427 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │436 std.mem.startsWith(u8, q.percent_encoded, "service=git-receive-pack") │ │437 else │ │438 false, │ │569 - }; │ │570 -} │ │571 - │ │572 -fn readSshRequest(allocator: std.mem.Allocator, reader: *std.Io.Reader) !SshRequest { │ │573 - const magic = try readSshPreludeLine(reader); │ │574 - if (!std.mem.eql(u8, magic, "haxy-ssh-git-v1")) return error.InvalidSshMagic; │ │575 - │ │576 - const service_line = try readSshPreludeLine(reader); │ │577 - const service = parseSshService(stripPrefix(service_line, "service=") orelse return error.InvalidSshServiceLine) orelse return error.InvalidSshService;│ │578 - │ │579 - const protocol_line = try readSshPreludeLine(reader); │ │580 - const protocol_version = std.meta.stringToEnum( │ │581 - xit.net_server_common.ProtocolVersion, │ │582 - stripPrefix(protocol_line, "protocol=") orelse return error.InvalidSshProtocolLine, │ │583 - ) orelse return error.InvalidSshProtocol; │ │584 - │ │585 - const repo_length_line = try readSshPreludeLine(reader); │ │586 - const repo_len = try std.fmt.parseInt(usize, stripPrefix(repo_length_line, "repo-length=") orelse return error.InvalidSshRepoLengthLine, 10); │ │587 - │ │588 - const empty_line = try readSshPreludeLine(reader); │ │589 - if (empty_line.len != 0) return error.InvalidSshPreludeTerminator; │ │590 - │ │591 - const repo = try reader.readAlloc(allocator, repo_len); │ │592 - errdefer allocator.free(repo); │ │593 - │ │594 - return .{ │ │595 - .service = service, │ │596 - .protocol_version = protocol_version, │ │597 - .repo = repo, │ │439 }; │ │599 -} │ │600 - │ │601 -fn readSshPreludeLine(reader: *std.Io.Reader) ![]const u8 { │ │602 - return (try reader.takeDelimiter('\n')) orelse error.InvalidSshHelperRequest; │ │440 } │ │441 │ │605 -fn parseSshService(value: []const u8) ?SshService { │ │606 - if (std.mem.eql(u8, value, "upload-pack")) return .upload_pack; │ │607 - if (std.mem.eql(u8, value, "receive-pack")) return .receive_pack; │ │608 - return null; │ │609 -} │ │610 - │ │611 -fn stripPrefix(value: []const u8, prefix: []const u8) ?[]const u8 { │ │612 - if (!std.mem.startsWith(u8, value, prefix)) return null; │ │613 - return value[prefix.len..]; │ │614 -} │ │615 - │ │616 -fn resolveSshRepoPath( │ │617 - allocator: std.mem.Allocator, │ │618 - repo_root_path: []const u8, │ │619 - repo: []const u8, │ │620 -) ![]const u8 { │ │621 - if (std.fs.path.isAbsolute(repo)) { │ │622 - return try std.fs.path.resolve(allocator, &.{repo}); │ │623 - } │ │624 - return try std.fs.path.resolve(allocator, &.{ repo_root_path, repo }); │ │625 -} │ │626 - │ │442 fn parseListenAddress(value: []const u8) !ListenAddress { │ │443 const colon = std.mem.lastIndexOfScalar(u8, value, ':') orelse return error.InvalidListenAddress; │ │444 if (colon == 0 or colon + 1 >= value.len) return error.InvalidListenAddress; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ removed ────────────╮ │src/serve_ssh_tui.zig│ ╰─────────────────────╯ ╭ removed ──╮ │src/ssh.zig│ ╰───────────╯ ╭ removed ──────╮ │src/ssh_git.zig│ ╰───────────────╯ ╭ removed ──────╮ │src/ssh_tui.zig│ ╰───────────────╯ ╭──────╮ │next →│ ╰──────╯