Archived
1
0

Merge pull request #169 from cathugger/master

srnd: custom email address formatter, some tweaks
This commit is contained in:
Jeff 2018-12-12 11:49:50 -05:00 committed by GitHub
commit a1d11c594c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 27 deletions

View File

@ -19,7 +19,6 @@ import (
"log" "log"
"mime" "mime"
"net/http" "net/http"
"net/mail"
"strings" "strings"
"time" "time"
) )
@ -785,10 +784,10 @@ func (self *httpFrontend) handle_postRequest(pr *postRequest, b bannedFunc, e er
return return
} }
subject := pr.Subject subject := strings.TrimSpace(pr.Subject)
// set subject // set subject
if len(subject) == 0 { if subject == "" {
subject = "None" subject = "None"
} else if len(subject) > 256 { } else if len(subject) > 256 {
// subject too big // subject too big
@ -796,29 +795,21 @@ func (self *httpFrontend) handle_postRequest(pr *postRequest, b bannedFunc, e er
return return
} }
nntp.headers.Set("Subject", subject) nntp.headers.Set("Subject", safeHeader(subject))
if isSage(subject) { if isSage(subject) {
nntp.headers.Set("X-Sage", "1") nntp.headers.Set("X-Sage", "1")
} }
name := pr.Name name := strings.TrimSpace(pr.Name)
var tripcode_privkey []byte var tripcode_privkey []byte
// set name
if len(name) == 0 {
name = "Anonymous"
} else {
idx := strings.Index(name, "#")
// tripcode // tripcode
if idx >= 0 { if idx := strings.IndexByte(name, '#'); idx >= 0 {
tripcode_privkey = parseTripcodeSecret(name[idx+1:]) tripcode_privkey = parseTripcodeSecret(name[idx+1:])
name = strings.Trim(name[:idx], "\t ") name = strings.TrimSpace(name[:idx])
}
if name == "" { if name == "" {
name = "Anonymous" name = "Anonymous"
} }
}
}
if len(name) > 128 { if len(name) > 128 {
// name too long // name too long
e(errors.New("name too long")) e(errors.New("name too long"))
@ -830,10 +821,7 @@ func (self *httpFrontend) handle_postRequest(pr *postRequest, b bannedFunc, e er
msgid = genMessageID(pr.Frontend) msgid = genMessageID(pr.Frontend)
} }
nntp.headers.Set("From", (&mail.Address{ nntp.headers.Set("From", formatAddress(safeHeader(name), "poster@" + pr.Frontend))
Name: name,
Address: "poster@" + pr.Frontend,
}).String())
nntp.headers.Set("Message-ID", msgid) nntp.headers.Set("Message-ID", msgid)
// set message // set message

View File

@ -156,10 +156,7 @@ func newPlaintextArticle(message, email, subject, name, instance, message_id, ne
nntp := &nntpArticle{ nntp := &nntpArticle{
headers: make(ArticleHeaders), headers: make(ArticleHeaders),
} }
nntp.headers.Set("From", (&mail.Address{ nntp.headers.Set("From", formatAddress(name, email))
Name: name,
Address: email,
}).String())
nntp.headers.Set("Subject", subject) nntp.headers.Set("Subject", subject)
if isSage(subject) { if isSage(subject) {
nntp.headers.Set("X-Sage", "1") nntp.headers.Set("X-Sage", "1")

View File

@ -15,6 +15,7 @@ import (
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"io" "io"
"log" "log"
"mime"
"net" "net"
"net/http" "net/http"
"net/mail" "net/mail"
@ -27,6 +28,7 @@ import (
"strings" "strings"
"time" "time"
"unicode" "unicode"
"unicode/utf8"
) )
func DelFile(fname string) { func DelFile(fname string) {
@ -174,6 +176,121 @@ func safeHeader(s string) string {
return strings.TrimSpace(safeHeaderReplacer.Replace(s)) return strings.TrimSpace(safeHeaderReplacer.Replace(s))
} }
func isVchar(r rune) bool {
// RFC 5234 B.1: VCHAR = %x21-7E ; visible (printing) characters
// RFC 6532 3.2: VCHAR =/ UTF8-non-ascii
return (r >= 0x21 && r <= 0x7E) || r >= 0x80
}
func isAtext(r rune) bool {
// RFC 5322: Printable US-ASCII characters not including specials. Used for atoms.
switch r {
case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"':
return false
}
return isVchar(r)
}
func isWSP(r rune) bool { return r == ' ' || r == '\t' }
func isQtext(r rune) bool {
if r == '\\' || r == '"' {
return false
}
return isVchar(r)
}
func writeQuoted(b *strings.Builder, s string) {
last := 0
b.WriteByte('"')
for i, r := range s {
if !isQtext(r) && !isWSP(r) {
if i > last {
b.WriteString(s[last:i])
}
b.WriteByte('\\')
b.WriteRune(r)
last = i + utf8.RuneLen(r)
}
}
if last < len(s) {
b.WriteString(s[last:])
}
b.WriteByte('"')
}
func formatAddress(name, email string) string {
// somewhat based on stdlib' mail.Address.String()
b := &strings.Builder{}
if name != "" {
needsEncoding := false
needsQuoting := false
for _, r := range name {
if r >= 0x80 || (!isWSP(r) && !isVchar(r)) {
needsEncoding = true
break
}
if !isAtext(r) {
needsQuoting = true
}
}
if needsEncoding {
// Text in an encoded-word in a display-name must not contain certain
// characters like quotes or parentheses (see RFC 2047 section 5.3).
// When this is the case encode the name using base64 encoding.
if strings.ContainsAny(name, "\"#$%&'(),.:;<>@[]^`{|}~") {
b.WriteString(mime.BEncoding.Encode("utf-8", name))
} else {
b.WriteString(mime.QEncoding.Encode("utf-8", name))
}
} else if needsQuoting {
writeQuoted(b, name)
} else {
b.WriteString(name)
}
b.WriteByte(' ')
}
at := strings.LastIndex(email, "@")
var local, domain string
if at >= 0 {
local, domain = email[:at], email[at+1:]
} else {
local = email
}
quoteLocal := false
for i, r := range local {
if isAtext(r) {
// if atom then okay
continue
}
if r == '.' && r > 0 && local[i-1] != '.' && i < len(local)-1 {
// dots are okay but only if surrounded by non-dots
continue
}
quoteLocal = true
break
}
b.WriteByte('<')
if !quoteLocal {
b.WriteString(local)
} else {
writeQuoted(b, local)
}
b.WriteByte('@')
b.WriteString(domain)
b.WriteByte('>')
return b.String()
}
type int64Sorter []int64 type int64Sorter []int64
func (self int64Sorter) Len() int { func (self int64Sorter) Len() int {