package mailx import ( "cmp" "net/textproto" "pkg.jfrech.com/brief/internal/crumbs" ) // Field represents an RFC 822 header field (cf. [RFC 822, 3.2]) // // [RawField.Decode] does not canonicalize: // Field.Name may not be in textproto.CanonicalMIMEHeaderKey form // and neither in "fixed" form (i.e. it may contain odd characters; // cf. [Field.CanonicalName]). // // [Field.Equal] and [Field.Compare] compare according to [Field.CanonicalName]. // Use [Field.Is] rather than testing Field.Name as a string. // // [RFC 822, 3.2]: https://www.rfc-editor.org/rfc/rfc822.html#section-3.2 type Field struct { Name, Body string } // Field.CanonicalName returns the canoncialized, // fixed (that is, bytes !(c>'\x20'&&c!=':'&&c<='~') become '-') name. func (field Field) CanonicalName() string { return textproto.CanonicalMIMEHeaderKey(fix(field.Name)) } // Equality in the /[Field.CanonicalName] coset. func (field Field) Equal(gield Field) bool { return field.Compare(gield) == 0 } // Is reports whether field's [Field.CanonicalName] is name modulo canoncialization. // // (Field{"\xfe", "xfe"}).Is("") == true func (field Field) Is(name string) bool { return field.Equal(Field{name, field.Body}) } // Order in the /[Field.CanonicalName] coset. // // Body bytes are ordered according to their bytes // (matters only for non-UTF8-valid bodies). // // Field.Compare returns {-1,0,+1} with semantics of [cmp.Compare]. func (field Field) Compare(gield Field) int { return cmp.Or( cmp.Compare(field.CanonicalName(), gield.CanonicalName()), cmp.Compare(field.Body, gield.Body), ) } // fix maps the empty string to "-" and // replaces ever byte !(c>'\x20'&&c!=':'&&c<='~') by '-'. func fix(s string) string { if s == "" { return "-" } valid := func(c byte) bool { return c > ' ' && c != ':' && c <= '~' } // fast path if crumbs.All(valid, []byte(s)) { return s } // slow path buf := make([]byte, len(s)) for j, c := range []byte(s) { if !valid(c) { c = '-' } buf[j] = c } return string(buf) } // fixedName returns fix(field.Name). func (field Field) fixedName() string { return fix(field.Name) }