Go(lang)解析电子邮件标题并保持顺序

I'm using net/mail library in Go, everything is great, however I want to pass in an original email and keep the order of the headers. This is important because the mail servers that pass the message on each add their headers in an order. Without order, its hard to know who received what, when and what headers each server added. The net/mail library stores the headers in a map, which by definition has no concept of order. Seems a strange choice as header order is based only on order in the email, but it is the case. Anyone got any suggestions as to how I can retain order the headers were read?

Thanks

The net/mail package uses the net/textproto package to parse the headers (see ReadMessage()). Specifically, it uses ReadMIMEHeader() for the headers, which is documented as:

The returned map m maps CanonicalMIMEHeaderKey(key) to a sequence of values in the same order encountered in the input.

You can view the full source if you want, but the basic process is:

headers = make(map[string][]string)
for {
    key, value := readNextHeader()
    if key == "" {
        return headers // End of headers
    }

    if headers[key] == nil {
        headers[key] = []string{value}
    } else {
        headers[key] = append(headers[key], value)
    }
}

It's true that the original order of the headers as they appeared in the message is lost, but I'm not aware of any scenario where this truly matters. What isn't lost is the order of the multi-values headers. The slice ensures they're in the same order as they appeared in the email.

You can verify this with a simple program which loops over the headers and compares the values (such as this one in the Playground).


However, matching Received and Received-SPF headers is a bit more complex, as:

  1. not every Received header may have a corresponding Received-SPF header;
  2. the Received-SPF header may not appear above the Received header; this is recommended but not mandated by the RFC (besides, many programs don't even follow the RFC, so this wouldn't be a guarantee anyway).

So you'll either need to parse the value of the headers and match them based on that, or use the net/textproto package for more low-level access to the headers. You can use the source of ReadMIMEHeader() as a starting point.