//add a new header and set its value internal void InternalAdd(string name, string value) { if (MailHeaderInfo.IsSingleton(name)) { base.Set(name, value); } else { base.Add(name, value); } }
/// <summary> /// Extract headers from the text stream into the Headers collection of the ReceivedMessage object provided. /// </summary> /// <param name="reader">TextReader providing the data stream to be parsed</param> /// <param name="messageHeaders">Collection of message headers.</param> /// <param name="rawheaders">Raw text of headers as received from server.</param> /// <returns>Returns false if no headers were found. Otherwise returns true.</returns> private static bool LoadHeaders(TextReader reader, NameValueCollection messageHeaders, StringBuilder rawheaders) { Debug.WriteLine("LoadHeaders()"); string line = null; string lastHeaderKey = null; //multiple values with the same header key will be delimited by semi-colon. //semi-colon is reserved as a special character for header encoding in RFC 2047, so this should be safe Dictionary <string, StringBuilder> headersBuilders = new Dictionary <string, StringBuilder>(StringComparer.InvariantCultureIgnoreCase); Dictionary <string, string[]> headers; Debug.WriteLine("Raw:"); while (null != (line = reader.ReadLine())) { Debug.WriteLine(line); if (rawheaders != null) { rawheaders.AppendLine(line); } //headers end with an empty line if (line == string.Empty || line.Trim() == string.Empty) { break; } //some agents malform the key: value expression so that there is no space after the colon. //for example this BlackBerry Message-ID header //Message-ID:<*****@*****.**> Regex regex = new Regex(@"^([^:]+):\s?(.*)$"); Match match = regex.Match(line); //if a line does not contain a colon it is either a continuation of a previous line or an error if (match.Success) { //split header key from RFC 2047 encoded value string headerkey = match.Groups[1].Value; string value = match.Groups[2].Value; if (!headersBuilders.ContainsKey(headerkey)) { headersBuilders.Add(headerkey, new StringBuilder(value)); } else { headersBuilders[headerkey].AppendFormat("|~|{0}", value); } lastHeaderKey = headerkey; } else { //continuation line should start with whitespace if (!string.IsNullOrEmpty(lastHeaderKey) && Regex.IsMatch(line, "^\\s")) { string h = line.TrimStart('\t', ' '); headersBuilders[lastHeaderKey].AppendFormat("|~|{0}", h); } else //error in message format, skip ahead and attempt to continue parsing { lastHeaderKey = null; continue; } } } if (headersBuilders.Count == 0) { return(false); } headers = new Dictionary <string, string[]>(headersBuilders.Count, StringComparer.InvariantCultureIgnoreCase); foreach (string key in headersBuilders.Keys) { List <string> list = new List <string>(headersBuilders[key].ToString().Split(new[] { "|~|" }, StringSplitOptions.RemoveEmptyEntries)); for (int i = 0; i < list.Count; i++) { if (string.IsNullOrEmpty(list[i])) { list.RemoveAt(i); } } headers.Add(key, list.ToArray()); } InPlaceDecodeExtendedHeaders(headers); //add decoded headers to the ReceivedMessage parameter passed in to this method. /************************************************************************** * NOTE: * The NameValueCollction in MailMessage.Headers is actually an internal Type * called HeaderCollection. HeaderCollection has different behavior for header keys * that are defined as singleton. MailHeaderMsft.IsSingleton replicates the internal * test used by HeaderCollection. */ Debug.WriteLine("Decoded:"); foreach (string key in headers.Keys) { Debug.WriteLine("Key: " + key); //I believe IsSingleton is meant to indicate that the field should occur only once in the output //headers when the message is encoded for transport. The side-effect of the implementation is that it doesn't //allow adding multiple values to a single key in the NameValueCollection. if (MailHeaderInfo.IsSingleton(key)) { //HeaderCollection will use Set internally when Add is called //need to join multiple values into a single string before adding or //the last value to be added will be the final value and the others will be lost. //therefore, components need to be combined into a single comma-separated string //before being added. string[] v = headers[key]; string value = string.Join(string.Empty, headers[key]); //HeaderCollection.Add(string,string) throws an undocumented ArgumentException when being passed //an empty string as a value. This is quite contrary to the behavior documented for NameValueCollection.Add(string,string) //which explicitly permits null and empty strings to be added. //the most common cause of this problem is a BCC: field with no value. if (value != string.Empty) { messageHeaders.Add(key, value); } } else { for (int i = 0; i < headers[key].Length; i++) { if (!string.IsNullOrEmpty(headers[key][i])) { Debug.WriteLine(key + ": " + headers[key][i]); messageHeaders.Add(key, headers[key][i]); } } } Debug.WriteLine(key + ": " + messageHeaders[key]); } return(true); }