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" | ||||
| 	"mime" | ||||
| 	"net/http" | ||||
| 	"net/mail" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -785,10 +784,10 @@ func (self *httpFrontend) handle_postRequest(pr *postRequest, b bannedFunc, e er | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	subject := pr.Subject | ||||
| 	subject := strings.TrimSpace(pr.Subject) | ||||
|  | ||||
| 	// set subject | ||||
| 	if len(subject) == 0 { | ||||
| 	if subject == "" { | ||||
| 		subject = "None" | ||||
| 	} else if len(subject) > 256 { | ||||
| 		// subject too big | ||||
| @@ -796,28 +795,20 @@ func (self *httpFrontend) handle_postRequest(pr *postRequest, b bannedFunc, e er | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	nntp.headers.Set("Subject", subject) | ||||
| 	nntp.headers.Set("Subject", safeHeader(subject)) | ||||
| 	if isSage(subject) { | ||||
| 		nntp.headers.Set("X-Sage", "1") | ||||
| 	} | ||||
|  | ||||
| 	name := pr.Name | ||||
|  | ||||
| 	name := strings.TrimSpace(pr.Name) | ||||
| 	var tripcode_privkey []byte | ||||
|  | ||||
| 	// set name | ||||
| 	if len(name) == 0 { | ||||
| 	// tripcode | ||||
| 	if idx := strings.IndexByte(name, '#'); idx >= 0 { | ||||
| 		tripcode_privkey = parseTripcodeSecret(name[idx+1:]) | ||||
| 		name = strings.TrimSpace(name[:idx]) | ||||
| 	} | ||||
| 	if name == "" { | ||||
| 		name = "Anonymous" | ||||
| 	} else { | ||||
| 		idx := strings.Index(name, "#") | ||||
| 		// tripcode | ||||
| 		if idx >= 0 { | ||||
| 			tripcode_privkey = parseTripcodeSecret(name[idx+1:]) | ||||
| 			name = strings.Trim(name[:idx], "\t ") | ||||
| 			if name == "" { | ||||
| 				name = "Anonymous" | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if len(name) > 128 { | ||||
| 		// name too long | ||||
| @@ -830,10 +821,7 @@ func (self *httpFrontend) handle_postRequest(pr *postRequest, b bannedFunc, e er | ||||
| 		msgid = genMessageID(pr.Frontend) | ||||
| 	} | ||||
|  | ||||
| 	nntp.headers.Set("From", (&mail.Address{ | ||||
| 		Name:    name, | ||||
| 		Address: "poster@" + pr.Frontend, | ||||
| 	}).String()) | ||||
| 	nntp.headers.Set("From", formatAddress(safeHeader(name), "poster@" + pr.Frontend)) | ||||
| 	nntp.headers.Set("Message-ID", msgid) | ||||
|  | ||||
| 	// set message | ||||
|   | ||||
| @@ -156,10 +156,7 @@ func newPlaintextArticle(message, email, subject, name, instance, message_id, ne | ||||
| 	nntp := &nntpArticle{ | ||||
| 		headers: make(ArticleHeaders), | ||||
| 	} | ||||
| 	nntp.headers.Set("From", (&mail.Address{ | ||||
| 		Name:    name, | ||||
| 		Address: email, | ||||
| 	}).String()) | ||||
| 	nntp.headers.Set("From", formatAddress(name, email)) | ||||
| 	nntp.headers.Set("Subject", subject) | ||||
| 	if isSage(subject) { | ||||
| 		nntp.headers.Set("X-Sage", "1") | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import ( | ||||
| 	"golang.org/x/crypto/ed25519" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"mime" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/mail" | ||||
| @@ -27,6 +28,7 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| func DelFile(fname string) { | ||||
| @@ -174,6 +176,121 @@ func safeHeader(s string) string { | ||||
| 	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 | ||||
|  | ||||
| func (self int64Sorter) Len() int { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user