Skip to content

Commit f6ed638

Browse files
authored
Fix IMAP IPv6 host parsing in add-imap (#217)
**_This was written by Cursor. I don't know Go at all, so I am not claiming it's correct or not._** ## What this fixes `add-imap` failed with IPv6 hosts passed to `--host` due to invalid address construction (`::1:1993`), causing: - `dial tcp: address ::1:1993: too many colons in address` ## Root cause IMAP config built `host:port` with string formatting instead of proper host/port joining, so raw IPv6 literals were not bracketed. ## Change - Use Go's `net.JoinHostPort` when building IMAP addresses/identifiers. - Normalize optional IPv6 brackets in `--host` input so both forms are accepted: - `::1` - `[::1]` ## Tests Added/updated unit tests to verify: - IPv6 unbracketed host works. - IPv6 bracketed host works. - Both normalize to the same valid output.
1 parent b82fa49 commit f6ed638

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

internal/imap/config.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ package imap
44
import (
55
"encoding/json"
66
"fmt"
7+
"net"
78
"net/url"
89
"strconv"
10+
"strings"
911
)
1012

1113
// Config holds connection settings for an IMAP server.
@@ -27,7 +29,7 @@ func (c *Config) Addr() string {
2729
port = 143
2830
}
2931
}
30-
return fmt.Sprintf("%s:%d", c.Host, port)
32+
return net.JoinHostPort(normalizeHost(c.Host), strconv.Itoa(port))
3133
}
3234

3335
// Identifier returns a canonical string like "imaps://user@host:port".
@@ -48,7 +50,18 @@ func (c *Config) Identifier() string {
4850
port = 143
4951
}
5052
}
51-
return fmt.Sprintf("%s://%s@%s:%d", scheme, url.PathEscape(c.Username), c.Host, port)
53+
return fmt.Sprintf(
54+
"%s://%s@%s",
55+
scheme,
56+
url.PathEscape(c.Username),
57+
net.JoinHostPort(normalizeHost(c.Host), strconv.Itoa(port)),
58+
)
59+
}
60+
61+
// normalizeHost strips surrounding IPv6 brackets so callers can pass either
62+
// "::1" or "[::1]" and still get a valid host:port from net.JoinHostPort.
63+
func normalizeHost(host string) string {
64+
return strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
5265
}
5366

5467
// ToJSON serializes the config to JSON.

internal/imap/config_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ func TestIdentifier(t *testing.T) {
3333
cfg: Config{Host: "mail.example.com", Username: "user"},
3434
want: "imap://user@mail.example.com:143",
3535
},
36+
{
37+
name: "IPv6 host unbracketed",
38+
cfg: Config{Host: "::1", Port: 1993, Username: "user"},
39+
want: "imap://user@[::1]:1993",
40+
},
41+
{
42+
name: "IPv6 host bracketed",
43+
cfg: Config{Host: "[::1]", Port: 1993, Username: "user"},
44+
want: "imap://user@[::1]:1993",
45+
},
3646
}
3747
for _, tt := range tests {
3848
t.Run(tt.name, func(t *testing.T) {
@@ -44,6 +54,34 @@ func TestIdentifier(t *testing.T) {
4454
}
4555
}
4656

57+
func TestAddr_IPv6(t *testing.T) {
58+
tests := []struct {
59+
name string
60+
cfg Config
61+
want string
62+
}{
63+
{
64+
name: "unbracketed",
65+
cfg: Config{Host: "::1", Port: 1993},
66+
want: "[::1]:1993",
67+
},
68+
{
69+
name: "bracketed",
70+
cfg: Config{Host: "[::1]", Port: 1993},
71+
want: "[::1]:1993",
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
got := tt.cfg.Addr()
78+
if got != tt.want {
79+
t.Errorf("Addr() = %q, want %q", got, tt.want)
80+
}
81+
})
82+
}
83+
}
84+
4785
func TestIdentifier_STARTTLSDistinctFromPlaintext(t *testing.T) {
4886
starttls := Config{
4987
Host: "mail.example.com", Port: 143,

0 commit comments

Comments
 (0)