commit - 8b8eab1e23b712caf0560716ad255220160d2bf1
commit + 4ef66cd9dfe84823e5777b2c8d68680ea78d5298
blob - 06b86c200ca5387446c79fd8b54565b212507f7d
blob + b2026fa867121a4422b1862579a262a2f52d58be
--- client/client.go
+++ client/client.go
-// NNTP Client library.
+// Package nntpclient provides an NNTP Client.
package nntpclient
import (
"github.com/dustin/go-nntp"
)
-// The client
+// Client is an NNTP client.
type Client struct {
conn *textproto.Conn
Banner string
}
-// Connect a client to an NNTP server.
+// New connects a client to an NNTP server.
func New(net, addr string) (*Client, error) {
conn, err := textproto.Dial(net, addr)
if err != nil {
return
}
-// Select a group.
+// Group selects a group.
func (c *Client) Group(name string) (rv nntp.Group, err error) {
var msg string
_, msg, err = c.Command("GROUP "+name, 211)
return
}
-// Grab an article
+// Article grabs an article
func (c *Client) Article(specifier string) (int64, string, io.Reader, error) {
err := c.conn.PrintfLine("ARTICLE %s", specifier)
if err != nil {
return c.articleish(220)
}
-// Get the headers for an article
+// Head gets the headers for an article
func (c *Client) Head(specifier string) (int64, string, io.Reader, error) {
err := c.conn.PrintfLine("HEAD %s", specifier)
if err != nil {
return c.articleish(221)
}
-// Get the body of an article
+// Body gets the body of an article
func (c *Client) Body(specifier string) (int64, string, io.Reader, error) {
err := c.conn.PrintfLine("BODY %s", specifier)
if err != nil {
return err
}
-// Send a low-level command and get a response.
+// Command sends a low-level command and get a response.
//
// This will return an error if the code doesn't match the expectCode
// prefix. For example, if you specify "200", the response code MUST
blob - 29ab008f6a16519bb5f1ff2b5a6a5906bca4533d
blob + 41430094f467ef2062b05ee9d2db69e29fd383fa
--- examples/couchserver/couchserver.go
+++ examples/couchserver/couchserver.go
}
g, exists := cb.groups[name]
if !exists {
- return nil, nntpserver.NoSuchGroup
+ return nil, nntpserver.ErrNoSuchGroup
}
return g, nil
}
}, &results)
if len(results.Rows) != 1 {
- return nil, nntpserver.InvalidArticleNumber
+ return nil, nntpserver.ErrInvalidArticleNumber
}
ar = results.Rows[0].Article
} else {
err := cb.db.Retrieve(cleanupId(id, false), &ar)
if err != nil {
- return nil, nntpserver.InvalidMessageId
+ return nil, nntpserver.ErrInvalidMessageID
}
}
if len(a.Nums) == 0 {
log.Printf("Found no matching groups in %v",
article.Header["Newsgroups"])
- return nntpserver.PostingFailed
+ return nntpserver.ErrPostingFailed
}
if *optimisticPost {
_, _, err = cb.db.Insert(&a)
if err != nil {
log.Printf("error posting article: %v", err)
- return nntpserver.PostingFailed
+ return nntpserver.ErrPostingFailed
}
}
}
func (tb *couchBackend) Authenticate(user, pass string) (nntpserver.Backend, error) {
- return nil, nntpserver.AuthRejected
+ return nil, nntpserver.ErrAuthRejected
}
func maybefatal(err error, f string, a ...interface{}) {
blob - 09734a75b8b7d4188640743b90255e31cd1a1e51
blob + a6aebe3555dc5d6d2e46131770637c8c1ae6e87e
--- examples/server/exampleserver.go
+++ examples/server/exampleserver.go
}
if group == nil {
- return nil, nntpserver.NoSuchGroup
+ return nil, nntpserver.ErrNoSuchGroup
}
return group, nil
blob - e9d06c94738fa3c3253d28936d68c3cd26cb010c
blob + dedc4e7c011437367d7f7101c057fbdc85bd635e
--- nntp.go
+++ nntp.go
-// NNTP definitions.
+// Package nntp provides base NNTP definitions.
package nntp
import (
"net/textproto"
)
-// Posting status type for groups.
+// PostingStatus type for groups.
type PostingStatus byte
+// PostingStatus values.
const (
Unknown = PostingStatus(0)
PostingPermitted = PostingStatus('y')
return fmt.Sprintf("%c", ps)
}
-// A usenet newsgroup.
+// Group represents a usenet newsgroup.
type Group struct {
Name string
Description string
Posting PostingStatus
}
-// An article that may appear in one or more groups.
+// An Article that may appear in one or more groups.
type Article struct {
// The article's headers
Header textproto.MIMEHeader
Lines int
}
-// Convenient access to the article's Message ID.
-func (a *Article) MessageId() string {
+// MessageID provides convenient access to the article's Message ID.
+func (a *Article) MessageID() string {
return a.Header.Get("Message-Id")
}
blob - 41c71d3705aeb77c4cf1a3ac7022dd6feae17dcc
blob + bde4869b9d4b8f2b1af79ef472d7515a59ec7f3d
--- server/server.go
+++ server/server.go
-// Everything you need for your own NNTP server.
+// Package nnntpserver provides everything you need for your own NNTP server.
package nntpserver
import (
"github.com/dustin/go-nntp"
)
-// Coded NNTP error message.
+// An NNTPError is a coded NNTP error message.
type NNTPError struct {
Code int
Msg string
}
-var NoSuchGroup = &NNTPError{411, "No such newsgroup"}
+// ErrNoSuchGroup is returned for a request for a group that can't be found.
+var ErrNoSuchGroup = &NNTPError{411, "No such newsgroup"}
-var NoGroupSelected = &NNTPError{412, "No newsgroup selected"}
+// ErrNoSuchGroup is returned for a request that requires a current
+// group when none has been selected.
+var ErrNoGroupSelected = &NNTPError{412, "No newsgroup selected"}
-var InvalidMessageId = &NNTPError{430, "No article with that message-id"}
-var InvalidArticleNumber = &NNTPError{423, "No article with that number"}
-var NoCurrentArticle = &NNTPError{420, "Current article number is invalid"}
+// ErrInvalidMessageID is returned when a message is requested that can't be found.
+var ErrInvalidMessageID = &NNTPError{430, "No article with that message-id"}
-var UnknownCommand = &NNTPError{500, "Unknown command"}
-var SyntaxError = &NNTPError{501, "not supported, or syntax error"}
+// ErrInvalidArticleNumber is returned when an article is requested that can't be found.
+var ErrInvalidArticleNumber = &NNTPError{423, "No article with that number"}
-var PostingNotPermitted = &NNTPError{440, "Posting not permitted"}
-var PostingFailed = &NNTPError{441, "posting failed"}
-var NotWanted = &NNTPError{435, "Article not wanted"}
+// ErrNoCurrentArticle is returned when a command is executed that
+// requires a current article when one has not been selected.
+var ErrNoCurrentArticle = &NNTPError{420, "Current article number is invalid"}
-var AuthRequired = &NNTPError{450, "authorization required"}
-var AuthRejected = &NNTPError{452, "authorization rejected"}
-var NotAuthenticated = &NNTPError{480, "authentication required"}
+// ErrUnknownCommand is returned for unknown comands.
+var ErrUnknownCommand = &NNTPError{500, "Unknown command"}
-// Low-level protocol handler
+// ErrSyntax is returned when a command can't be parsed.
+var ErrSyntax = &NNTPError{501, "not supported, or syntax error"}
+
+// ErrPostingNotPermitted is returned as the response to an attempt to
+// post an article where posting is not permitted.
+var ErrPostingNotPermitted = &NNTPError{440, "Posting not permitted"}
+
+// ErrPostingFailed is returned when an attempt to post an article fails.
+var ErrPostingFailed = &NNTPError{441, "posting failed"}
+
+// ErrNotWanted is returned when an attempt to post an article is
+// rejected due the server not wanting the article.
+var ErrNotWanted = &NNTPError{435, "Article not wanted"}
+
+// ErrAuthRequired is returned to indicate authentication is required
+// to proceed.
+var ErrAuthRequired = &NNTPError{450, "authorization required"}
+
+// ErrAuthRejected is returned for invalid authentication.
+var ErrAuthRejected = &NNTPError{452, "authorization rejected"}
+
+// ErrNotAuthenticated is returned when a command is issued that requires
+// authentication, but authentication was not provided.
+var ErrNotAuthenticated = &NNTPError{480, "authentication required"}
+
+// Handler is a low-level protocol handler
type Handler func(args []string, s *session, c *textproto.Conn) error
-// When listing articles in a group, this provides local sequence
-// numbers to articles.
+// A NumberedArticle provides local sequence nubers to articles When
+// listing articles in a group.
type NumberedArticle struct {
Num int64
Article *nntp.Article
}
-// The backend that provides the things and does the stuff.
+// The Backend that provides the things and does the stuff.
type Backend interface {
ListGroups(max int) ([]*nntp.Group, error)
GetGroup(name string) (*nntp.Group, error)
group *nntp.Group
}
-// The server handle.
+// The Server handle.
type Server struct {
// Handlers are dispatched by command name.
Handlers map[string]Handler
group *nntp.Group
}
-// Build a new server handle request to a backend.
+// NewServer builds a new server handle request to a backend.
func NewServer(backend Backend) *Server {
rv := Server{
Handlers: make(map[string]Handler),
func handleOver(args []string, s *session, c *textproto.Conn) error {
if s.group == nil {
- return NoGroupSelected
+ return ErrNoGroupSelected
}
from, to := parseRange(args[0])
articles, err := s.backend.GetArticles(s.group, from, to)
}
func handleDefault(args []string, s *session, c *textproto.Conn) error {
- return UnknownCommand
+ return ErrUnknownCommand
}
func handleQuit(args []string, s *session, c *textproto.Conn) error {
func handleGroup(args []string, s *session, c *textproto.Conn) error {
if len(args) < 1 {
- return NoSuchGroup
+ return ErrNoSuchGroup
}
group, err := s.backend.GetGroup(args[0])
func (s *session) getArticle(args []string) (*nntp.Article, error) {
if s.group == nil {
- return nil, NoGroupSelected
+ return nil, ErrNoGroupSelected
}
return s.backend.GetArticle(s.group, args[0])
}
if err != nil {
return err
}
- c.PrintfLine("221 1 %s", article.MessageId())
+ c.PrintfLine("221 1 %s", article.MessageID())
dw := c.DotWriter()
defer dw.Close()
for k, v := range article.Header {
if err != nil {
return err
}
- c.PrintfLine("222 1 %s", article.MessageId())
+ c.PrintfLine("222 1 %s", article.MessageID())
dw := c.DotWriter()
defer dw.Close()
_, err = io.Copy(dw, article.Body)
if err != nil {
return err
}
- c.PrintfLine("220 1 %s", article.MessageId())
+ c.PrintfLine("220 1 %s", article.MessageID())
dw := c.DotWriter()
defer dw.Close()
func handlePost(args []string, s *session, c *textproto.Conn) error {
if !s.backend.AllowPost() {
- return PostingNotPermitted
+ return ErrPostingNotPermitted
}
c.PrintfLine("340 Go ahead")
var article nntp.Article
article.Header, err = c.ReadMIMEHeader()
if err != nil {
- return PostingFailed
+ return ErrPostingFailed
}
article.Body = c.DotReader()
err = s.backend.Post(&article)
func handleIHave(args []string, s *session, c *textproto.Conn) error {
if !s.backend.AllowPost() {
- return NotWanted
+ return ErrNotWanted
}
// XXX: See if we have it.
article, err := s.backend.GetArticle(nil, args[0])
if article != nil {
- return NotWanted
+ return ErrNotWanted
}
c.PrintfLine("335 send it")
article = &nntp.Article{}
article.Header, err = c.ReadMIMEHeader()
if err != nil {
- return PostingFailed
+ return ErrPostingFailed
}
article.Body = c.DotReader()
err = s.backend.Post(article)
func handleAuthInfo(args []string, s *session, c *textproto.Conn) error {
if len(args) < 2 {
- return SyntaxError
+ return ErrSyntax
}
if strings.ToLower(args[0]) != "user" {
- return SyntaxError
+ return ErrSyntax
}
if s.backend.Authorized() {
a, err := c.ReadLine()
parts := strings.SplitN(a, " ", 3)
if strings.ToLower(parts[0]) != "authinfo" || strings.ToLower(parts[1]) != "pass" {
- return SyntaxError
+ return ErrSyntax
}
b, err := s.backend.Authenticate(args[1], parts[2])
if err == nil {