/// <summary> /// Extract the header part and body part of a message.<br/> /// The headers are then parsed to a strongly typed <see cref="MessageHeader"/> object. /// </summary> /// <param name="fullRawMessage">The full message in bytes where header and body needs to be extracted from</param> /// <param name="headers">The extracted header parts of the message</param> /// <param name="body">The body part of the message</param> /// <exception cref="ArgumentNullException">If <paramref name="fullRawMessage"/> is <see langword="null"/></exception> public static void ExtractHeadersAndBody(Byte[] fullRawMessage, out MessageHeader headers, out Byte[] body) { if (fullRawMessage == null) throw new ArgumentNullException("fullRawMessage"); // Find the end location of the headers Int32 endOfHeaderLocation = FindHeaderEndPosition(fullRawMessage); // The headers are always in ASCII - therefore we can convert the header part into a String // using US-ASCII encoding String headersString = Encoding.ASCII.GetString(fullRawMessage, 0, endOfHeaderLocation); // Now parse the headers to a NameValueCollection NameValueCollection headersUnparsedCollection = ExtractHeaders(headersString); // Use the NameValueCollection to parse it into a strongly-typed MessageHeader header headers = new MessageHeader(headersUnparsedCollection); // Since we know where the headers end, we also know where the body is // Copy the body part into the body parameter body = new Byte[fullRawMessage.Length - endOfHeaderLocation]; Array.Copy(fullRawMessage, endOfHeaderLocation, body, 0, body.Length); }
/// <summary> /// Used to construct the topmost message part /// </summary> /// <param name="rawBody">The body that needs to be parsed</param> /// <param name="headers">The headers that should be used from the message</param> /// <exception cref="ArgumentNullException">If <paramref name="rawBody"/> or <paramref name="headers"/> is <see langword="null"/></exception> public MessagePart(Byte[] rawBody, MessageHeader headers) { if (rawBody == null) throw new ArgumentNullException("rawBody"); if (headers == null) throw new ArgumentNullException("headers"); this.Header = headers; if (headers.ContentType != null) ContentType = headers.ContentType; else ContentType = new ContentType("text/plain; charset=us-ascii"); ContentDescription = headers.ContentDescription; ContentTransferEncoding = headers.ContentTransferEncoding; ContentId = headers.ContentId; ContentDisposition = headers.ContentDisposition; FileName = FindFileName(ContentType, ContentDisposition, "(no name)"); BodyEncoding = ParseBodyEncoding(ContentType.CharSet); ParseBody(rawBody); }
/// <summary> /// Parses a single header and sets member variables according to it. /// </summary> /// <param name="headerName">The name of the header</param> /// <param name="headerValue">The value of the header in unfolded state (only one line)</param> /// <exception cref="ArgumentNullException">If <paramref name="headerName"/> or <paramref name="headerValue"/> is <see langword="null"/></exception> public void ParseHeader(String headerName, String headerValue) { if (headerName == null) { throw new ArgumentNullException("headerName"); } if (headerValue == null) { throw new ArgumentNullException("headerValue"); } HeaderValueChanged(headerName, headerValue); switch (headerName.ToUpperInvariant()) { // See http://tools.ietf.org/html/rfc5322#section-3.6.3 case "TO": this.To = MessageHeader.ParseAddresses(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.3 case "CC": this.Cc = MessageHeader.ParseAddresses(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.3 case "BCC": this.Bcc = MessageHeader.ParseAddresses(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.2 case "FROM": // There is only one MailAddress in the from field this.From = MessageAddress.ParseAddress(headerValue); break; // http://tools.ietf.org/html/rfc5322#section-3.6.2 // The implementation here might be wrong case "REPLY-TO": // This field may actually be a list of addresses, but no // such case has been encountered this.ReplyTo = MessageAddress.ParseAddress(headerValue); break; // http://tools.ietf.org/html/rfc5322#section-3.6.2 case "SENDER": this.Sender = MessageAddress.ParseAddress(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.5 // RFC 5322: // The "Keywords:" field contains a comma-separated list of one or more // words or quoted-strings. // The field are intended to have only human-readable content // with information about the message case "KEYWORDS": var keywordsTemp = headerValue.Split(","); foreach (String keyword in keywordsTemp) { // Remove the quotes if there is any Keywords.Add(Utility.RemoveQuotesIfAny(keyword.Trim())); } break; // See http://tools.ietf.org/html/rfc5322#section-3.6.7 case "RECEIVED": // Simply add the value to the list Received.Add(headerValue.Trim()); break; case "IMPORTANCE": Importance = HeaderFieldParser.ParseImportance(headerValue.Trim()); break; // See http://tools.ietf.org/html/rfc3798#section-2.1 case "DISPOSITION-NOTIFICATION-TO": this.DispositionNotificationTo = MessageHeader.ParseAddresses(headerValue); break; case "MIME-VERSION": MimeVersion = headerValue.Trim(); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.5 case "SUBJECT": case "THREAD-TOPIC": Subject = EncodedWord.Decode(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.7 case "RETURN-PATH": // Return-paths does not include a username, but we // may still use the address parser this.ReturnPath = MessageAddress.ParseAddress(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.4 // Example Message-ID // <*****@*****.**> case "MESSAGE-ID": MessageId = HeaderFieldParser.ParseId(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.4 case "IN-REPLY-TO": InReplyTo = HeaderFieldParser.ParseMultipleIDs(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.4 case "REFERENCES": References = HeaderFieldParser.ParseMultipleIDs(headerValue); break; // See http://tools.ietf.org/html/rfc5322#section-3.6.1)) case "DATE": Date = headerValue.Trim(); DateSent = Rfc2822DateTime.StringToDate(headerValue); break; // See http://tools.ietf.org/html/rfc2045#section-6 // See ContentTransferEncoding class for more details case "CONTENT-TRANSFER-ENCODING": ContentTransferEncoding = HeaderFieldParser.ParseContentTransferEncoding(headerValue.Trim()); break; // See http://tools.ietf.org/html/rfc2045#section-8 case "CONTENT-DESCRIPTION": // Human description of for example a file. Can be encoded ContentDescription = EncodedWord.Decode(headerValue.Trim()); break; // See http://tools.ietf.org/html/rfc2045#section-5.1 // Example: Content-type: text/plain; charset="us-ascii" case "CONTENT-TYPE": ContentType = HeaderFieldParser.ParseContentType(headerValue); break; // See http://tools.ietf.org/html/rfc2183 case "CONTENT-DISPOSITION": ContentDisposition = HeaderFieldParser.ParseContentDisposition(headerValue); break; // See http://tools.ietf.org/html/rfc2045#section-7 // Example: <foo4*[email protected]> case "CONTENT-ID": ContentId = HeaderFieldParser.ParseId(headerValue); break; case "_REQUEST-LINE_": // header could contain request-line that could not be parsed as simple key value pair, so store entire line // Example: POST /odata/$batch HTTP/1.1 RequestLine = headerValue; break; default: // This is an unknown header // Custom headers are allowed. That means headers // that are not mentionen in the RFC. // Such headers start with the letter "X" // We do not have any special parsing of such // Add it to unknown headers UnknownHeaders.Add(headerName, headerValue); break; } }