Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/remote/connutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"golang.org/x/crypto/ssh"
)

var userHostRe = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9._@\\-]*@)?([a-zA-Z0-9][a-zA-Z0-9.-]*)(?::([0-9]+))?$`)
var userHostRe = regexp.MustCompile(`^([a-zA-Z0-9\p{L}\p{N}][a-zA-Z0-9._@\p{L}\p{N}\\-]*@)?([a-zA-Z0-9\p{L}\p{N}][a-zA-Z0-9.\p{L}\p{N}-]*)(?::([0-9]+))?$`)

func ParseOpts(input string) (*SSHOpts, error) {
m := userHostRe.FindStringSubmatch(input)
Expand Down
53 changes: 53 additions & 0 deletions pkg/remote/connutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package remote

import (
"testing"
)

func TestParseOpts(t *testing.T) {
tests := []struct {
name string
input string
wantUser string
wantHost string
wantPort string
wantErr bool
}{
{"user@host:port", "user@myserver:22", "user", "myserver", "22", false},
{"host only", "myserver", "", "myserver", "", false},
{"chinese host alias", "PROD-服务器", "", "PROD-服务器", "", false},
{"mixed ascii and chinese with user and port", "user@PROD-阿里云:22", "user", "PROD-阿里云", "22", false},
{"unicode user and host", "用户@服务器:22", "用户", "服务器", "22", false},
{"unicode only host", "服务器", "", "服务器", "", false},
{"japanese host", "サーバー", "", "サーバー", "", false},
{"empty string", "", "", "", "", true},
{"just colon", ":", "", "", "", true},
{"just at", "@", "", "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opts, err := ParseOpts(tt.input)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if opts.SSHUser != tt.wantUser {
t.Errorf("user: got %q, want %q", opts.SSHUser, tt.wantUser)
}
if opts.SSHHost != tt.wantHost {
t.Errorf("host: got %q, want %q", opts.SSHHost, tt.wantHost)
}
if opts.SSHPort != tt.wantPort {
t.Errorf("port: got %q, want %q", opts.SSHPort, tt.wantPort)
}
})
}
}