From 79e0296bed62dad1399e888112ea5c6bd1d8c6bd Mon Sep 17 00:00:00 2001 From: Adnan Maolood Date: Tue, 9 Feb 2021 15:59:45 -0500 Subject: [PATCH] client: Support IDNs Convert IDNs to punycode before performing DNS lookups. --- client.go | 37 ++++++++++++++++++------------------- go.mod | 2 ++ go.sum | 7 +++++++ punycode.go | 27 +++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 go.sum create mode 100644 punycode.go diff --git a/client.go b/client.go index 0c92e1d..820e4c3 100644 --- a/client.go +++ b/client.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "net" - "strings" "time" ) @@ -44,11 +43,14 @@ func (c *Client) Get(url string) (*Response, error) { // Do performs a Gemini request and returns a Gemini response. func (c *Client) Do(req *Request) (*Response, error) { // Extract hostname - colonPos := strings.LastIndex(req.Host, ":") - if colonPos == -1 { - colonPos = len(req.Host) + hostname, port, err := net.SplitHostPort(req.Host) + if err != nil { + return nil, err + } + punycode, err := punycodeHostname(hostname) + if err != nil { + return nil, err } - hostname := req.Host[:colonPos] // Connect to the host config := &tls.Config{ @@ -61,11 +63,11 @@ func (c *Client) Do(req *Request) (*Response, error) { return &tls.Certificate{}, nil }, VerifyConnection: func(cs tls.ConnectionState) error { - return c.verifyConnection(req, cs) + return c.verifyConnection(hostname, punycode, cs) }, - ServerName: hostname, + ServerName: punycode, } - // Set connection context + ctx := req.Context if ctx == nil { ctx = context.Background() @@ -76,7 +78,8 @@ func (c *Client) Do(req *Request) (*Response, error) { Timeout: c.Timeout, } - netConn, err := dialer.DialContext(ctx, "tcp", req.Host) + address := net.JoinHostPort(punycode, port) + netConn, err := dialer.DialContext(ctx, "tcp", address) if err != nil { return nil, err } @@ -129,17 +132,13 @@ func (c *Client) do(conn *tls.Conn, req *Request) (*Response, error) { return resp, nil } -func (c *Client) verifyConnection(req *Request, cs tls.ConnectionState) error { - // Verify the hostname - var hostname string - if host, _, err := net.SplitHostPort(req.Host); err == nil { - hostname = host - } else { - hostname = req.Host - } +func (c *Client) verifyConnection(hostname, punycode string, cs tls.ConnectionState) error { cert := cs.PeerCertificates[0] - if err := verifyHostname(cert, hostname); err != nil { - return err + // Try punycode and then hostname + if err := verifyHostname(cert, punycode); err != nil { + if err := verifyHostname(cert, hostname); err != nil { + return err + } } // Check expiration date if !time.Now().Before(cert.NotAfter) { diff --git a/go.mod b/go.mod index f850117..2eb6847 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.sr.ht/~adnano/go-gemini go 1.15 + +require golang.org/x/net v0.0.0-20210119194325-5f4716e94777 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c512294 --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/punycode.go b/punycode.go new file mode 100644 index 0000000..565790c --- /dev/null +++ b/punycode.go @@ -0,0 +1,27 @@ +package gemini + +import ( + "net" + "unicode/utf8" + + "golang.org/x/net/idna" +) + +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} + +func punycodeHostname(hostname string) (string, error) { + if net.ParseIP(hostname) != nil { + return hostname, nil + } + if isASCII(hostname) { + return hostname, nil + } + return idna.Lookup.ToASCII(hostname) +}