Merge pull request #169 from cathugger/master
srnd: custom email address formatter, some tweaks
This commit is contained in:
commit
a1d11c594c
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user