// Parse the delivery status byte array to downgrade // the Original-Recipient and Final-Recipient header fields internal static byte[] DowngradeDeliveryStatus(byte[] bytes) { var sb = new StringBuilder(); var index = 0; int endIndex = bytes.Length; var lastIndex = -1; ArrayWriter writer = null; while (index < endIndex) { sb.Remove(0, sb.Length); var first = true; int headerNameStart = index; int headerNameEnd = index; // lineCount = 0; var endOfHeaders = false; while (true) { if (index >= endIndex) { // All headers read endOfHeaders = true; break; } int c = (index < endIndex) ? (((int)bytes[index]) & 0xff) : -1; // ++lineCount; ++index; if (c == '\r') { c = (index < endIndex) ? (((int)bytes[index]) & 0xff) : -1; ++index; if (c == '\n') { // lineCount = 0; headerNameStart = index; } else { --index; headerNameEnd = index; } continue; } if ((c >= 0x21 && c <= 57) || (c >= 59 && c <= 0x7e)) { first = false; if (c >= 'A' && c <= 'Z') { c += 0x20; } sb.Append((char)c); } else if (!first && c == ':') { break; } else { first &= c != 0x20 && c != 0x09; } if (c != 0x20 && c != 0x09) { headerNameEnd = index; } } if (endOfHeaders) { break; } int headerValueStart = index; int headerValueEnd = index; string origFieldName = DataUtilities.GetUtf8String( bytes, headerNameStart, headerValueStart - headerNameStart, true); string fieldName = DataUtilities.ToLowerCaseAscii( DataUtilities.GetUtf8String( bytes, headerNameStart, headerNameEnd - headerNameStart, true)); bool origRecipient = fieldName.Equals("original-recipient"); bool finalRecipient = fieldName.Equals("final-recipient"); // Read the header field value using UTF-8 characters // rather than bytes while (true) { if (index >= endIndex) { // All headers read headerValueEnd = index; break; } int c = (index < endIndex) ? (((int)bytes[index]) & 0xff) : -1; ++index; if (c == '\r') { c = (index < endIndex) ? (((int)bytes[index]) & 0xff) : -1; ++index; if (c == '\n') { // lineCount = 0; // Parse obsolete folding whitespace (obs-fws) under RFC5322 // (parsed according to errata), same as LWSP in RFC5234 var fwsFirst = true; var haveFWS = false; var lineStart = true; while (true) { // Skip the CRLF pair, if any (except if iterating for // the first time, since CRLF was already parsed) if (!fwsFirst) { c = (index < endIndex) ? (((int)bytes[index]) & 0xff) : -1; ++index; if (c == '\r') { c = (index < endIndex) ? (((int)bytes[index]) & 0xff) : -1; ++index; if (c == '\n') { // CRLF was read lineStart = true; } else { // It's the first part of the line, where the header name // should be, so the CR here is illegal throw new MessageDataException("CR not followed by LF"); } } else { // anything else, unget --index; } } fwsFirst = false; // Use ReadByte here since we're just looking for the single // byte characters space and tab int c2 = (index < endIndex) ? (((int)bytes[index]) & 0xff) : -1; ++index; if (c2 == 0x20 || c2 == 0x09) { lineStart = false; haveFWS = true; } else { --index; // this isn't space or tab; if this is the start // of the line, this is no longer FWS if (lineStart) { haveFWS = false; } break; } } if (haveFWS) { // We have folding whitespace, line // count found as above continue; } // This ends the header field // (the last two characters will be CRLF) headerValueEnd = index - 2; break; } --index; // ++lineCount; } // ++lineCount; } if (origRecipient || finalRecipient) { string headerValue = DataUtilities.GetUtf8String( bytes, headerValueStart, headerValueEnd - headerValueStart, true); var status = new int[1]; headerValue = DowngradeRecipientHeaderValue(headerValue, status); if (status[0] == 2 || status[0] == 1) { // Downgraded or encapsulated if (writer == null) { writer = new ArrayWriter(); writer.Write(bytes, 0, headerNameStart); } else { writer.Write(bytes, lastIndex, headerNameStart - lastIndex); } var encoder = new WordWrapEncoder(true); string field = (origRecipient ? "Downgraded-Original-Recipient" : "Downgraded-Final-Recipient") + ": "; if (status[0] != 2) { field = origFieldName + " "; } encoder.AddString(field + headerValue); byte[] newBytes = DataUtilities.GetUtf8Bytes( encoder.ToString(), true); writer.Write(newBytes, 0, newBytes.Length); lastIndex = headerValueEnd; } } } if (writer != null) { writer.Write(bytes, lastIndex, bytes.Length - lastIndex); bytes = writer.ToArray(); } return bytes; }
private void Generate(IWriter output, int depth) { var haveMimeVersion = false; var haveContentEncoding = false; var haveContentType = false; var haveContentDisp = false; var haveMsgId = false; var haveFrom = false; var haveDate = false; var haveHeaders = new bool[11]; byte[] bodyToWrite = this.body; var builder = new MediaTypeBuilder(this.ContentType); string contentDisp = (this.ContentDisposition == null) ? null : this.ContentDisposition.ToString(); var transferEnc = 0; var isMultipart = false; string boundary = String.Empty; if (builder.IsMultipart) { boundary = GenerateBoundary(depth); builder.SetParameter("boundary", boundary); isMultipart = true; } if (!isMultipart) { if (builder.TopLevelType.Equals("message")) { if (builder.SubType.Equals("delivery-status") || builder.SubType.Equals("global-delivery-status")) { bodyToWrite = DowngradeDeliveryStatus(bodyToWrite); } bool msgCanBeUnencoded = CanBeUnencoded(bodyToWrite, depth > 0); if ((builder.SubType.Equals("rfc822") || builder.SubType.Equals( "news")) && !msgCanBeUnencoded) { builder.SetSubType("global"); } else if (builder.SubType.Equals("disposition-notification") && !msgCanBeUnencoded) { builder.SetSubType("global-disposition-notification"); } else if (builder.SubType.Equals("delivery-status") && !msgCanBeUnencoded) { builder.SetSubType("global-delivery-status"); } else if (!msgCanBeUnencoded && !builder.SubType.Equals("global") && !builder.SubType.Equals("global-disposition-notification") && !builder.SubType.Equals("global-delivery-status") && !builder.SubType.Equals("global-headers")) { #if DEBUG throw new MessageDataException("Message body can't be encoded: " + builder.ToString() + ", " + this.ContentType); #else { throw new MessageDataException("Message body can't be encoded"); } #endif } } } string topLevel = builder.TopLevelType; transferEnc = topLevel.Equals("message") || topLevel.Equals("multipart") ? (topLevel.Equals("multipart") || ( !builder.SubType.Equals("global") && !builder.SubType.Equals("global-headers") && !builder.SubType.Equals("global-disposition-notification") && !builder.SubType.Equals("global-delivery-status"))) ? EncodingSevenBit : TransferEncodingToUse( bodyToWrite, depth > 0) : TransferEncodingToUse(bodyToWrite, depth > 0); string encodingString = "7bit"; if (transferEnc == EncodingBase64) { encodingString = "base64"; } else if (transferEnc == EncodingQuotedPrintable) { encodingString = "quoted-printable"; } // Write the header fields for (int i = 0; i < this.headers.Count; i += 2) { string name = this.headers[i]; string value = this.headers[i + 1]; if (name.Equals("content-type")) { if (haveContentType) { // Already outputted, continue continue; } haveContentType = true; value = builder.ToString(); } if (name.Equals("content-disposition")) { if (haveContentDisp || contentDisp == null) { // Already outputted, continue continue; } haveContentDisp = true; value = contentDisp; } else if (name.Equals("content-transfer-encoding")) { if (haveContentEncoding) { // Already outputted, continue continue; } haveContentEncoding = true; value = encodingString; } else if (name.Equals("date")) { if (haveDate) { continue; } haveDate = true; } else if (name.Equals("from")) { if (haveFrom) { // Already outputted, continue continue; } haveFrom = true; } if ( depth > 0 && ( name.Length < 8 || !name.Substring( 0, 8).Equals("content-"))) { // don't generate header fields not starting with "Content-" // in body parts continue; } if (name.Equals("mime-version")) { haveMimeVersion = true; } else if (name.Equals("message-id")) { if (haveMsgId) { // Already outputted, continue continue; } haveMsgId = true; } else { if (ValueHeaderIndices.ContainsKey(name)) { int headerIndex = ValueHeaderIndices[name]; if (headerIndex < 9) { if (haveHeaders[headerIndex]) { // Already outputted, continue continue; } haveHeaders[headerIndex] = true; if (!this.IsValidAddressingField(name)) { value = GenerateAddressList( ParseAddresses(this.GetMultipleHeaders(name))); if (value.Length == 0) { // No addresses, synthesize a field value = this.SynthesizeField(name); } } } if (headerIndex == 9 || headerIndex == 10) { // Resent-From/Resent-Sender, can appear // more than once value = GenerateAddressList(ParseAddresses(value)); if (value.Length == 0) { // No addresses, synthesize a field value = this.SynthesizeField(name); } } } } string rawField = Capitalize(name) + ":" + (StartsWithWhitespace(value) ? String.Empty : " ") + value; if (CanOutputRaw(rawField)) { AppendAscii(output, rawField); if (rawField.IndexOf(": ", StringComparison.Ordinal) < 0) { throw new MessageDataException("No colon+space: " + rawField); } } else if (HasTextToEscape(value)) { string downgraded = HeaderFieldParsers.GetParser(name).DowngradeFieldValue(value); if ( HasTextToEscapeIgnoreEncodedWords( downgraded, 0, downgraded.Length)) { if (name.Equals("message-id") || name.Equals("resent-message-id") || name.Equals( "in-reply-to") || name.Equals("references") || name.Equals( "original-recipient") || name.Equals("final-recipient")) { // Header field still contains invalid characters (such // as non-ASCII characters in 7-bit messages), convert // to a downgraded field name = "downgraded-" + name; downgraded = Rfc2047.EncodeString(ParserUtility.TrimSpaceAndTab(value)); } else { #if DEBUG throw new MessageDataException("Header field still has non-Ascii or controls: " + name + " " + value); #else throw new MessageDataException( "Header field still has non-Ascii or controls"); #endif } } bool haveDquote = downgraded.IndexOf('"') >= 0; var encoder = new WordWrapEncoder(!haveDquote); encoder.AddString(Capitalize(name) + ": " + downgraded); string newValue = encoder.ToString(); AppendAscii(output, newValue); } else { bool haveDquote = value.IndexOf('"') >= 0; var encoder = new WordWrapEncoder(!haveDquote); encoder.AddString(Capitalize(name) + ": " + value); string newValue = encoder.ToString(); AppendAscii(output, newValue); } AppendAscii(output, "\r\n"); } if (!haveFrom && depth == 0) { // Output a synthetic From field if it doesn't // exist and this isn't a body part AppendAscii(output, "From: [email protected]\r\n"); } if (!haveDate && depth == 0) { AppendAscii(output, "Date: "); AppendAscii( output, GetDateString(DateTimeUtilities.GetCurrentLocalTime())); AppendAscii(output, "\r\n"); } if (!haveMsgId && depth == 0) { AppendAscii(output, "Message-ID:\r\n "); AppendAscii(output, this.GenerateMessageID()); AppendAscii(output, "\r\n"); } if (!haveMimeVersion && depth == 0) { AppendAscii(output, "MIME-Version: 1.0\r\n"); } if (!haveContentType) { AppendAscii(output, "Content-Type: " + builder + "\r\n"); } if (!haveContentEncoding) { AppendAscii( output, "Content-Transfer-Encoding: " + encodingString + "\r\n"); } ICharacterEncoder bodyEncoder = null; switch (transferEnc) { case EncodingBase64: bodyEncoder = new Base64Encoder(true, builder.IsText, false); break; case EncodingQuotedPrintable: bodyEncoder = new QuotedPrintableEncoder( builder.IsText ? 2 : 0, false); break; default: bodyEncoder = new IdentityEncoder(); break; } // Write the body AppendAscii(output, "\r\n"); if (!isMultipart) { var index = 0; while (true) { int c = (index >= bodyToWrite.Length) ? -1 : ((int)bodyToWrite[index++]) & 0xff; int count = bodyEncoder.Encode(c, output); if (count == -2) { throw new MessageDataException("encoding error"); } if (count == -1) { break; } } } else { foreach (Message part in this.Parts) { AppendAscii(output, "\r\n--" + boundary + "\r\n"); part.Generate(output, depth + 1); } AppendAscii(output, "\r\n--" + boundary + "--"); } }