Support posting.

This commit is contained in:
TJP 2023-02-09 12:36:37 -07:00
parent fe171d9fa9
commit 0f39286db5
2 changed files with 151 additions and 12 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
iris-news
tmp/*

161
main.go
View File

@ -2,10 +2,14 @@ package main
import (
"bytes"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/mail"
"net/textproto"
"os"
"os/exec"
@ -60,7 +64,7 @@ func writeLocation(location string) error {
var group = &nntp.Group{
Name: "ctrl-c.iris",
Description: "The iris message board.",
Posting: nntp.PostingNotPermitted,
Posting: nntp.PostingPermitted,
Low: 1,
}
@ -156,25 +160,96 @@ func (b backend) Authenticate(user, pass string) (nntpserver.Backend, error) {
}
func (b backend) AllowPost() bool {
return false
return true
}
func (b backend) Post(article *nntp.Article) error {
return nntpserver.ErrPostingNotPermitted
msg, err := msgToIris(article)
if err != nil {
return err
}
return appendMessage(msg)
}
type irisDump []struct {
Hash string `json:"hash"`
EditHash string `json:"edit_hash"`
IsDeleted *bool `json:"is_deleted"`
type irisMsg struct {
Hash string `json:"hash"`
EditHash *string `json:"edit_hash"`
IsDeleted *bool `json:"is_deleted"`
Data struct {
Author string `json:"author"`
Parent *string `json:"parent"`
Timestamp time.Time `json:"timestamp"`
Message string `json:"message"`
Author string `json:"author"`
Parent *string `json:"parent"`
Timestamp string `json:"timestamp"`
Message string `json:"message"`
} `json:"data"`
}
func (m irisMsg) calcHash() (string, error) {
/*
Careful coding here to match ruby's hash calculation:
```
Base64.encode64(Digest::SHA1.digest(m["data"].to_json))
```
* have to use an encoder rather than json.Marshal so we can
turn off the default HTML escaping (ruby doesn't do this)
* strip trailing newline from JSON encoding output
* add a trailing newline to base64 encoded form
*/
b := &bytes.Buffer{}
enc := json.NewEncoder(b)
enc.SetEscapeHTML(false)
if err := enc.Encode(m.Data); err != nil {
return "", err
}
arr := sha1.Sum(bytes.TrimSuffix(b.Bytes(), []byte("\n")))
s := base64.StdEncoding.EncodeToString(arr[:])
if !strings.HasSuffix(s, "\n") {
s += "\n"
}
return s, nil
}
func msgToIris(article *nntp.Article) (*irisMsg, error) {
postTime := time.Now().UTC().Format(time.RFC3339)
body, err := io.ReadAll(article.Body)
if err != nil {
return nil, err
}
var msg irisMsg
msg.Data.Author = irisAuthor(article.Header.Get("From"))
msg.Data.Timestamp = postTime
msg.Data.Message = string(body)
refs := article.Header.Get("References")
if refs != "" {
spl := strings.Split(refs, " ")
ref := fromMsgID(spl[len(spl)-1])
msg.Data.Parent = &ref
}
hash, err := msg.calcHash()
if err != nil {
return nil, err
}
msg.Hash = hash
return &msg, nil
}
func irisAuthor(nntpAuthor string) string {
addr, err := mail.ParseAddress(nntpAuthor)
if err != nil {
return nntpAuthor
}
return addr.Address
}
type irisDump []irisMsg
func (id irisDump) Articles() []*nntp.Article {
out := make([]*nntp.Article, len(id))
subjects := make(map[string]string)
@ -186,7 +261,7 @@ func (id irisDump) Articles() []*nntp.Article {
"Message-Id": []string{msgID},
"From": []string{d.Data.Author},
"Newsgroups": []string{group.Name},
"Date": []string{d.Data.Timestamp.Format(time.RFC1123Z)},
"Date": []string{d.Data.Timestamp},
},
Body: bytes.NewBufferString(d.Data.Message),
Bytes: len(d.Data.Message),
@ -207,6 +282,68 @@ func (id irisDump) Articles() []*nntp.Article {
return out
}
func (id irisDump) MarshalJSON() ([]byte, error) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
out := bytes.NewBufferString("[\n ")
for i, msg := range id {
if err := enc.Encode(msg); err != nil {
return nil, err
}
if i > 0 {
_, _ = out.WriteString(",\n ")
}
_, _ = out.Write(bytes.TrimSuffix(buf.Bytes(), []byte("\n")))
buf.Reset()
}
_, _ = out.WriteString("\n]")
return out.Bytes(), nil
}
func toMsgID(irisID string) string {
return "<" + strings.TrimSuffix(irisID, "\n") + ">"
}
func fromMsgID(nntpID string) string {
return strings.TrimSuffix(strings.TrimPrefix(nntpID, "<"), ">") + "\n"
}
func appendMessage(msg *irisMsg) error {
home, err := os.UserHomeDir()
if err != nil {
return err
}
msgFile, err := os.Open(path.Join(home, msgfile))
if err != nil {
return err
}
var msgs irisDump
if err := json.NewDecoder(msgFile).Decode(&msgs); err != nil {
_ = msgFile.Close()
return err
}
_ = msgFile.Close()
msgs = append(msgs, *msg)
msgFile, err = os.Create(path.Join(home, msgfile))
if err != nil {
return err
}
defer func() { _ = msgFile.Close() }()
out, err := msgs.MarshalJSON()
if err != nil {
return err
}
_, err = msgFile.Write(out)
return err
}
const msgfile = ".iris.messages"