╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ ssh_helper.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
24 25 pub fn run( 26 io: std.Io, 27 allocator: std.mem.Allocator, 28 options: Options, 29 environ_map: *std.process.Environ.Map, 30 ) !void { 31 if (builtin.os.tag == .windows) { 32 return error.WindowsNotSupported; 33 } 34 35 const command = try resolveCommand(allocator, options, environ_map); 36 defer command.deinit(allocator); 37 38 const connect_address = try parseConnectAddress(options.ssh_connect); 39 const address = try std.Io.net.IpAddress.parseIp4(connect_address.host, connect_address.port); 40 const stream = try address.connect(io, .{ .mode = .stream }); 41 defer stream.close(io); 42 43 var send_buffer = [_]u8{0} ** 1024; 44 var stream_writer = stream.writer(io, &send_buffer); 45 46 try stream_writer.interface.print( 47 "haxy-ssh-helper-v1\nservice={s}\nprotocol={s}\nrepo-length={d}\n\n", 48 .{ serviceName(command.service), @tagName(xit.net_server_common.detectProtocolVersion(environ_map)), command.dir.len }, 49 ); 50 try stream_writer.interface.writeAll(command.dir); 51 try stream_writer.interface.flush(); 52 53 const CopyIn = struct { 54 io: std.Io, 55 stream: std.Io.net.Stream, 56 57 fn run(ctx: @This()) void { 58 copyFd(std.posix.STDIN_FILENO, ctx.stream.socket.handle) catch {}; 59 ctx.stream.shutdown(ctx.io, .send) catch {}; 60 } 61 }; 62 const stdin_thread = try std.Thread.spawn(.{}, CopyIn.run, .{CopyIn{ .io = io, .stream = stream }}); 63 stdin_thread.detach(); 64 65 try copyFd(stream.socket.handle, std.posix.STDOUT_FILENO); 66 } 67 68 const Command = struct { 69 service: Service, 70 dir: []const u8, 71 72 fn deinit(self: Command, allocator: std.mem.Allocator) void { 73 allocator.free(self.dir); 74 } 75 }; 76 77 fn resolveCommand( 78 allocator: std.mem.Allocator, 79 options: Options, 80 environ_map: *std.process.Environ.Map, 81 ) !Command { 82 if (options.service) |service_name| { 83 const dir = options.dir orelse return error.InvalidSshCommand; 84 return .{ 85 .service = try parseService(service_name), 86 .dir = try allocator.dupe(u8, dir), 87 }; 88 } 89 90 const original_command = environ_map.get("SSH_ORIGINAL_COMMAND") orelse return error.InvalidSshCommand; 91 var tokens = try std.process.Args.IteratorGeneral(.{ .single_quotes = true }).init(allocator, original_command); 92 defer tokens.deinit(); 93 94 const service_name = tokens.next() orelse return error.InvalidSshCommand; 95 const dir = tokens.next() orelse return error.InvalidSshCommand; 96 97 return .{ 98 .service = try parseService(service_name), 99 .dir = try allocator.dupe(u8, dir), 100 }; 101 } 102 103 fn parseService(value: []const u8) !Service { 104 if (std.mem.eql(u8, value, "upload-pack") or std.mem.eql(u8, value, "git-upload-pack")) return .upload_pack; 105 if (std.mem.eql(u8, value, "receive-pack") or std.mem.eql(u8, value, "git-receive-pack")) return .receive_pack; 106 return error.InvalidSshCommand; 107 } 108 109 fn serviceName(service: Service) []const u8 { 110 return switch (service) { 111 .upload_pack => "upload-pack", 112 .receive_pack => "receive-pack", 113 }; 114 } 115 116 fn parseConnectAddress(value: []const u8) !ConnectAddress { 117 const colon = std.mem.lastIndexOfScalar(u8, value, ':') orelse return error.InvalidConnectAddress; 118 if (colon == 0 or colon + 1 >= value.len) return error.InvalidConnectAddress; 119 const port = try std.fmt.parseInt(u16, value[colon + 1 ..], 10); 120 return .{ .host = value[0..colon], .port = port }; 121 } 122 123 fn copyFd(src: std.posix.fd_t, dst: std.posix.fd_t) !void { 124 var buffer: [4096]u8 = undefined; 125 while (true) { 126 const len = try std.posix.read(src, &buffer); 127 if (len == 0) break; 128 try writeAllFd(dst, buffer[0..len]); 129 } 130 } 131 132 fn writeAllFd(fd: std.posix.fd_t, bytes: []const u8) !void { 133 var written: usize = 0; 134 while (written < bytes.len) { 135 const rc = std.posix.system.write(fd, bytes[written..].ptr, bytes.len - written); 136 switch (std.posix.errno(rc)) { 137 .SUCCESS => written += @intCast(rc), 138 .INTR => continue, 139 .PIPE => return error.BrokenPipe, 140 else => return error.WriteFailed, 141 } 142 } 143 } 144