commit 7b30ed992ec2cb2c5689449b07be0f6af3cb9e21 from: J.P. Neverwas via: Dustin Sallings date: Fri Jul 23 00:52:58 2021 UTC Add client helper for multi-line roundtrip handling commit - 3c37ea2157d302402d0c4611b09270185e028290 commit + 7b30ed992ec2cb2c5689449b07be0f6af3cb9e21 blob - 6c07dd06521d1729753dfba313f0ee1e9f1462ae blob + 5f0fc1c735bebf55cc4a688f626c9c8d27aa961b --- client/client.go +++ client/client.go @@ -2,7 +2,6 @@ package nntpclient import ( - "bytes" "crypto/tls" "errors" "io" @@ -242,33 +241,44 @@ func (c *Client) Command(cmd string, expectCode int) ( return c.conn.ReadCodeLine(expectCode) } -// Capabilities retrieves a list of supported caps. -// -// See https://datatracker.ietf.org/doc/html/rfc3977#section-5.2.2 -func (c *Client) Capabilities() ([]string, error) { - err := c.conn.PrintfLine("CAPABILITIES") +// asLines issues a command and returns the response's data block as lines. +func (c *Client) asLines(cmd string, expectCode int) ([]string, error) { + _, _, err := c.Command(cmd, expectCode) if err != nil { return nil, err } - _, _, err = c.conn.ReadCodeLine(101) + return c.conn.ReadDotLines() +} + +// Capabilities retrieves a list of supported capabilities. +// +// See https://datatracker.ietf.org/doc/html/rfc3977#section-5.2.2 +func (c *Client) Capabilities() ([]string, error) { + caps, err := c.asLines("CAPABILITIES", 101) if err != nil { return nil, err } - b, err := io.ReadAll(c.conn.DotReader()) - if err != nil { - return nil, err + for i, line := range caps { + caps[i] = strings.ToUpper(line) } - caps := strings.Split(string(bytes.TrimSpace(b)), "\n") c.capabilities = caps return caps, nil } -// GetCapability returns a complete capbility line. +// GetCapability returns a complete capability line. // -// See https://datatracker.ietf.org/doc/html/rfc3977#section-9.5 +// "Each capability line consists of one or more tokens, which MUST be +// separated by one or more space or TAB characters." +// +// From https://datatracker.ietf.org/doc/html/rfc3977#section-3.3.1 func (c *Client) GetCapability(capability string) string { + capability = strings.ToUpper(capability) for _, capa := range c.capabilities { - if strings.SplitN(capa, " ", 2)[0] == capability { + i := strings.IndexAny(capa, "\t ") + if i != -1 && capa[:i] == capability { + return capa + } + if capa == capability { return capa } } @@ -277,6 +287,10 @@ func (c *Client) GetCapability(capability string) stri // HasCapabilityArgument indicates whether a capability arg is supported. // +// Here, "argument" means any token after the label in a capabilities response +// line. Some, like "ACTIVE" in "LIST ACTIVE", are not command arguments but +// rather "keyword" components of compound commands called "variants." +// // See https://datatracker.ietf.org/doc/html/rfc3977#section-9.5 func (c *Client) HasCapabilityArgument( capability, argument string, @@ -288,7 +302,8 @@ func (c *Client) HasCapabilityArgument( if capLine == "" { return false, errors.New("No such capability") } - for _, capArg := range strings.Split(capLine, " ") { + argument = strings.ToUpper(argument) + for _, capArg := range strings.Fields(capLine)[1:] { if capArg == argument { return true, nil } @@ -300,41 +315,23 @@ func (c *Client) HasCapabilityArgument( // // According to the spec, the presence of an "OVER" line in the capabilities // response means this LIST variant is supported, so there's no reason to -// check for it among the keywords in the LIST line, strictly speaking. +// check for it among the keywords in the "LIST" line, strictly speaking. // // See https://datatracker.ietf.org/doc/html/rfc3977#section-3.3.2 func (c *Client) ListOverviewFmt() ([]string, error) { - err := c.conn.PrintfLine("LIST OVERVIEW.FMT") + fields, err := c.asLines("LIST OVERVIEW.FMT", 215) if err != nil { return nil, err } - _, _, err = c.conn.ReadCodeLine(215) - if err != nil { - return nil, err - } - b, err := io.ReadAll(c.conn.DotReader()) - if err != nil { - return nil, err - } - fields := strings.Split(string(bytes.TrimSpace(b)), "\n") return fields, nil } // Over returns a list of raw overview lines with tab-separated fields. func (c *Client) Over(specifier string) ([]string, error) { - err := c.conn.PrintfLine("OVER %s", specifier) + lines, err := c.asLines("OVER "+specifier, 224) if err != nil { return nil, err } - _, _, err = c.conn.ReadCodeLine(224) - if err != nil { - return nil, err - } - b, err := io.ReadAll(c.conn.DotReader()) - if err != nil { - return nil, err - } - lines := strings.Split(string(bytes.TrimSpace(b)), "\n") return lines, nil } @@ -350,8 +347,7 @@ func (c *Client) StartTLS(config *tls.Config) error { if c.tls { return errors.New("TLS already active") } - err := c.conn.PrintfLine("STARTTLS") - _, _, err = c.conn.ReadCodeLine(382) + _, _, err := c.Command("STARTTLS", 382) if err != nil { return err }