Merge pull request #169 from cathugger/master
srnd: custom email address formatter, some tweaks
This commit is contained in:
		| @@ -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