Bug 458159 -- [Vista only] Drag'n'drop attachment out of Shredder (desktop or other folder) creates 0 byte file. The fix is to set the content length correctly for mailnews URLs. Also move a lot of progress code over to 64-bit integers. r=bienvenu, sr=Standard8
authorSiddharth Agarwal <sid.bugzilla@gmail.com>
Thu, 21 May 2009 12:22:51 +0530
changeset 2809 af67b109386e987f723d6c1660ea803f34115dc7
parent 2808 61d5f8fd0fe2d3bca890b4b7e69af89c270df8f0
child 2810 7075e80889c6ef4626e6f537f196e1ccd2a5cf9d
push idunknown
push userunknown
push dateunknown
reviewersbienvenu, Standard8
bugs458159
Bug 458159 -- [Vista only] Drag'n'drop attachment out of Shredder (desktop or other folder) creates 0 byte file. The fix is to set the content length correctly for mailnews URLs. Also move a lot of progress code over to 64-bit integers. r=bienvenu, sr=Standard8
mailnews/base/public/nsIMsgMailNewsUrl.idl
mailnews/base/src/nsMessenger.cpp
mailnews/base/util/nsMsgMailNewsUrl.cpp
mailnews/base/util/nsMsgMailNewsUrl.h
mailnews/base/util/nsMsgProtocol.cpp
mailnews/base/util/nsMsgProtocol.h
mailnews/imap/public/nsIImapMailFolderSink.idl
mailnews/imap/src/nsImapMailFolder.cpp
mailnews/imap/src/nsImapProtocol.cpp
mailnews/imap/src/nsImapProtocol.h
mailnews/imap/test/unit/test_imapContentLength.js
mailnews/local/src/nsMailboxProtocol.cpp
mailnews/local/src/nsMailboxProtocol.h
mailnews/local/test/unit/test_mailboxContentLength.js
mailnews/news/src/nsNNTPProtocol.cpp
mailnews/news/src/nsNntpUrl.cpp
mailnews/news/test/unit/test_nntpContentLength.js
mailnews/test/data/multipart-complex2
--- a/mailnews/base/public/nsIMsgMailNewsUrl.idl
+++ b/mailnews/base/public/nsIMsgMailNewsUrl.idl
@@ -48,17 +48,17 @@ interface nsIMsgSearchSession;
 interface nsICacheEntryDescriptor;
 interface nsICacheSession;
 interface nsIMimeHeaders;
 interface nsIStreamListener;
 interface nsIMsgFolder;
 interface nsIMsgHeaderSink;
 interface nsIMsgDBHdr;
 
-[scriptable, uuid(2FB327C2-00A3-4e3f-8CD6-93BED40BD621)]
+[scriptable, uuid(01850820-e382-4b0f-a109-23d69280876b)]
 interface nsIMsgMailNewsUrl : nsIURL {
   ///////////////////////////////////////////////////////////////////////////////
   // Eventually we'd like to push this type of functionality up into nsIURI.
   // The idea is to allow the "application" (the part of the code which wants to 
   // run a url in order to perform some action) to register itself as a listener
   // on url. As a url listener, the app will be informed when the url begins to run
   // and when the url is finished. 
   ////////////////////////////////////////////////////////////////////////////////
@@ -80,16 +80,22 @@ interface nsIMsgMailNewsUrl : nsIURL {
    * @exception NS_ERROR_FAILURE    May be thrown if the url does not
    *                                relate to a folder, e.g. standalone
    *                                .eml messages.
    */
   attribute nsIMsgFolder folder;
 
   attribute nsIMsgStatusFeedback statusFeedback;
 
+  /**
+   * The maximum progress for this URL. This might be a count, or it might
+   * be a number of bytes. A value of -1 indicates that this is unknown.
+   */
+  attribute long long maxProgress;
+
   attribute nsIMsgWindow msgWindow;
 
   // current mime headers if reading message
   attribute nsIMimeHeaders mimeHeaders;
 
   // the load group is computed from the msgWindow
   readonly attribute nsILoadGroup loadGroup;
 
--- a/mailnews/base/src/nsMessenger.cpp
+++ b/mailnews/base/src/nsMessenger.cpp
@@ -253,23 +253,23 @@ public:
   }             m_outputFormat;
   nsString      m_msgBuffer;
 
   nsCString     m_contentType;    // used only when saving attachment
 
   nsCOMPtr<nsITransfer> mTransfer;
   nsCOMPtr<nsIUrlListener> mListener;
   nsCOMPtr<nsIURI> mListenerUri;
-  PRInt32 mProgress;
-  PRInt32 mContentLength;
+  PRInt64 mProgress;
+  PRInt64 mMaxProgress;
   PRBool  mCanceled;
   PRBool  mInitialized;
   PRBool  mUrlHasStopped;
   PRBool  mRequestHasStopped;
-  nsresult InitializeDownload(nsIRequest * aRequest, PRInt32 aBytesDownloaded);
+  nsresult InitializeDownload(nsIRequest * aRequest, PRUint32 aBytesDownloaded);
 };
 
 class nsSaveAllAttachmentsState
 {
 public:
   nsSaveAllAttachmentsState(PRUint32 count,
                             const char **contentTypeArray,
                             const char **urlArray,
@@ -1432,17 +1432,17 @@ nsSaveMsgListener::nsSaveMsgListener(nsI
   mListener = aListener;
   mUrlHasStopped = PR_FALSE;
   mRequestHasStopped = PR_FALSE;
   
     // rhp: for charset handling
   m_doCharsetConversion = PR_FALSE;
   m_saveAllAttachmentsState = nsnull;
   mProgress = 0;
-  mContentLength = -1;
+  mMaxProgress = -1;
   mCanceled = PR_FALSE;
   m_outputFormat = eUnknown;
   mInitialized = PR_FALSE;
   m_dataBuffer = new char[FOUR_K];
 }
 
 nsSaveMsgListener::~nsSaveMsgListener()
 {
@@ -1559,44 +1559,51 @@ nsSaveMsgListener::OnStopCopy(nsresult a
   if (m_file)
     m_file->Remove(PR_FALSE);
   Release(); // all done kill ourself
   return aStatus;
 }
 
 // initializes the progress window if we are going to show one
 // and for OSX, sets creator flags on the output file
-nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest, PRInt32 aBytesDownloaded)
+nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest, PRUint32 aBytesDownloaded)
 {
   nsresult rv = NS_OK;
   
   mInitialized = PR_TRUE;
   nsCOMPtr<nsIChannel> channel (do_QueryInterface(aRequest));
   
   if (!channel)
     return rv;
   
-  // Set content length if we haven't already got it.
-  if (mContentLength == -1)
-    channel->GetContentLength(&mContentLength);
+  // Get the max progress from the URL if we haven't already got it.
+  if (mMaxProgress == -1)
+  {
+    nsCOMPtr<nsIURI> uri;
+    channel->GetURI(getter_AddRefs(uri));
+    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
+    if (mailnewsUrl)
+      mailnewsUrl->GetMaxProgress(&mMaxProgress);
+  }
   
   if (!m_contentType.IsEmpty())
   {
     nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID));
     nsCOMPtr<nsIMIMEInfo> mimeinfo;
     
     mimeService->GetFromTypeAndExtension(m_contentType, EmptyCString(), getter_AddRefs(mimeinfo));
     
     nsCOMPtr<nsILocalFile> outputFile = do_QueryInterface(m_file);
     
-      // create a download progress window
-      // XXX: we don't want to show the progress dialog if the download is really small.
-      // but what is a small download? Well that's kind of arbitrary
-      // so make an arbitrary decision based on the content length of the attachment
-    if (mContentLength != -1 && mContentLength > aBytesDownloaded * 2)
+    // create a download progress window
+    // We don't want to show the progress dialog if the download is really small.
+    // but what is a small download? Well that's kind of arbitrary
+    // so make an arbitrary decision based on the content length of the
+    // attachment -- show it if less than half of the download has completed
+    if (mMaxProgress != -1 && mMaxProgress > aBytesDownloaded * 2)
     {
       nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
       if (tr && outputFile)
       {
         PRTime timeDownloadStarted = PR_Now();
         
         nsCOMPtr<nsIURI> outputURI;
         NS_NewFileURI(getter_AddRefs(outputURI), outputFile);
@@ -1753,17 +1760,17 @@ nsSaveMsgListener::OnStopRequest(nsIRequ
       
       delete m_saveAllAttachmentsState;
       m_saveAllAttachmentsState = nsnull;
     }
   }
 
   if(mTransfer)
   {
-    mTransfer->OnProgressChange(nsnull, nsnull, mContentLength, mContentLength, mContentLength, mContentLength);
+    mTransfer->OnProgressChange64(nsnull, nsnull, mMaxProgress, mMaxProgress, mMaxProgress, mMaxProgress);
     mTransfer->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_STOP |
       nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
     mTransfer = nsnull; // break any circular dependencies between the progress dialog and use
   }
   
   if (mUrlHasStopped && mListener)
     mListener->OnStopRunningUrl(mListenerUri, rv);
 
@@ -1811,17 +1818,17 @@ nsSaveMsgListener::OnDataAvailable(nsIRe
         else
           rv = m_outputStream->Write(m_dataBuffer, readCount, &writeCount);
 
         available -= readCount;
       }
     }
 
     if (NS_SUCCEEDED(rv) && mTransfer) // Send progress notification.
-      mTransfer->OnProgressChange(nsnull, request, mProgress, mContentLength, mProgress, mContentLength);
+      mTransfer->OnProgressChange64(nsnull, request, mProgress, mMaxProgress, mProgress, mMaxProgress);
   }
   return rv;
 }
 
 #define MESSENGER_STRING_URL       "chrome://messenger/locale/messenger.properties"
 
 nsresult
 nsMessenger::InitStringBundle()
--- a/mailnews/base/util/nsMsgMailNewsUrl.cpp
+++ b/mailnews/base/util/nsMsgMailNewsUrl.cpp
@@ -60,16 +60,17 @@ nsMsgMailNewsUrl::nsMsgMailNewsUrl()
 {
   // nsIURI specific state
   m_errorMessage = nsnull;
   m_runningUrl = PR_FALSE;
   m_updatingFolder = PR_FALSE;
   m_addContentToCache = PR_FALSE;
   m_msgIsInLocalCache = PR_FALSE;
   m_suppressErrorMsgs = PR_FALSE;
+  mMaxProgress = -1;
   
   m_baseURL = do_CreateInstance(NS_STANDARDURL_CONTRACTID);
 }
 
 #define NOTIFY_URL_LISTENERS(propertyfunc_, params_)                   \
   PR_BEGIN_MACRO                                                       \
   nsTObserverArray<nsCOMPtr<nsIUrlListener> >::ForwardIterator iter(mUrlListeners); \
   while (iter.HasMore()) {                                             \
@@ -248,16 +249,28 @@ NS_IMETHODIMP nsMsgMailNewsUrl::GetStatu
 
 NS_IMETHODIMP nsMsgMailNewsUrl::SetStatusFeedback(nsIMsgStatusFeedback *aMsgFeedback)
 {
   if (aMsgFeedback)
     m_statusFeedbackWeak = do_GetWeakReference(aMsgFeedback);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMaxProgress(PRInt64 *aMaxProgress)
+{
+  *aMaxProgress = mMaxProgress;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMaxProgress(PRInt64 aMaxProgress)
+{
+  mMaxProgress = aMaxProgress;
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsMsgMailNewsUrl::GetLoadGroup(nsILoadGroup **aLoadGroup)
 {
   *aLoadGroup = nsnull;
   // note: it is okay to return a null load group and not return an error
   // it's possible the url really doesn't have load group
   nsCOMPtr<nsILoadGroup> loadGroup (do_QueryReferent(m_loadGroupWeak));
   if (!loadGroup)
   {
--- a/mailnews/base/util/nsMsgMailNewsUrl.h
+++ b/mailnews/base/util/nsMsgMailNewsUrl.h
@@ -87,16 +87,17 @@ protected:
   nsWeakPtr m_loadGroupWeak;
   nsCOMPtr<nsIMimeHeaders> mMimeHeaders;
   nsCOMPtr<nsIMsgSearchSession> m_searchSession;
   nsCOMPtr<nsICacheEntryDescriptor> m_memCacheEntry;
   nsCOMPtr<nsICacheSession> m_imageCacheSession;
   nsCOMPtr<nsISupportsArray> m_cachedMemCacheEntries;
   nsCOMPtr<nsIMsgHeaderSink> mMsgHeaderSink;
   char *m_errorMessage;
+  PRInt64 mMaxProgress;
   PRBool m_runningUrl;
   PRBool m_updatingFolder;
   PRBool m_addContentToCache;
   PRBool m_msgIsInLocalCache;
   PRBool m_suppressErrorMsgs;
 
   // the following field is really a bit of a hack to make 
   // open attachments work. The external applications code sometimes tries to figure out the right
--- a/mailnews/base/util/nsMsgProtocol.cpp
+++ b/mailnews/base/util/nsMsgProtocol.cpp
@@ -82,16 +82,17 @@ static PRUnichar *FormatStringWithHostNa
 
 
 nsMsgProtocol::nsMsgProtocol(nsIURI * aURL)
 {
   m_flags = 0;
   m_readCount = 0;
   mLoadFlags = 0;
   m_socketIsOpen = PR_FALSE;
+  mContentLength = -1;
 
   GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "tempMessage.eml",
                                   getter_AddRefs(m_tempMsgFile));
 
   mSuppressListenerNotifications = PR_FALSE;
   InitFromURI(aURL);
 }
 
@@ -634,17 +635,23 @@ NS_IMETHODIMP nsMsgProtocol::GetContentC
 NS_IMETHODIMP nsMsgProtocol::SetContentCharset(const nsACString &aContentCharset)
 {
   NS_WARNING("nsMsgProtocol::SetContentCharset() not implemented");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsMsgProtocol::GetContentLength(PRInt32 * aContentLength)
 {
-  *aContentLength = -1;
+  *aContentLength = mContentLength;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentLength(PRInt32 aContentLength)
+{
+  mContentLength = aContentLength;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(nsISupports * *aSecurityInfo)
 {
   *aSecurityInfo = nsnull;
   return NS_ERROR_NOT_IMPLEMENTED;
 }
@@ -652,23 +659,16 @@ NS_IMETHODIMP nsMsgProtocol::GetSecurity
 NS_IMETHODIMP nsMsgProtocol::GetName(nsACString &result)
 {
   if (m_url)
     return m_url->GetSpec(result);
   result.Truncate();
   return NS_OK;
 }
 
-
-NS_IMETHODIMP
-nsMsgProtocol::SetContentLength(PRInt32 aContentLength)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
 NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports * *aPrincipal)
 {
   *aPrincipal = mOwner;
   NS_IF_ADDREF(*aPrincipal);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports * aPrincipal)
--- a/mailnews/base/util/nsMsgProtocol.h
+++ b/mailnews/base/util/nsMsgProtocol.h
@@ -176,16 +176,17 @@ protected:
   nsCOMPtr<nsIStreamListener> m_channelListener;
   nsCOMPtr<nsISupports>        m_channelContext;
   nsCOMPtr<nsILoadGroup>      m_loadGroup;
   nsLoadFlags                 mLoadFlags;
   nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
   nsCOMPtr<nsISupports>       mOwner;
   nsCString                   m_ContentType;
+  PRInt32                     mContentLength;
 
   nsCString m_lastPasswordSent; // used to prefill the password prompt
 
   // private helper routine used by subclasses to quickly get a reference to the correct prompt dialog
   // for a mailnews url. 
   nsresult GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl, nsIPrompt ** aPromptDialog);
 
   // if a url isn't going to result in any content then we want to suppress calls to
--- a/mailnews/imap/public/nsIImapMailFolderSink.idl
+++ b/mailnews/imap/public/nsIImapMailFolderSink.idl
@@ -59,17 +59,17 @@ interface ImapOnlineCopyStateType
    const long kFailedDelete = 4;
    const long kReadyForAppendData = 5;
    const long kFailedAppend = 6;
    const long kInterruptedState = 7;
    const long kFailedCopy = 8;
    const long kFailedMove = 9;
 };
 
-[scriptable, uuid(471adbb6-95c2-4a5f-b98e-0055804fce48)]
+[scriptable, uuid(531d4a3a-4921-4b7d-a46d-c66be8bec781)]
 interface nsIImapMailFolderSink : nsISupports {
   attribute boolean folderNeedsACLListed;
   attribute boolean folderNeedsSubscribing;
   attribute boolean folderNeedsAdded;
   attribute unsigned long aclFlags;
   attribute long uidValidity;
   /**
    * Whether we have asked the server for this folder's quota information.
@@ -108,17 +108,17 @@ interface nsIImapMailFolderSink : nsISup
   void closeMockChannel(in nsIImapMockChannel aChannel);
   void setUrlState(in nsIImapProtocol aProtocol, in nsIMsgMailNewsUrl aUrl, in boolean isRunning, in nsresult status);
   void releaseUrlCacheEntry(in nsIMsgMailNewsUrl aUrl);
 
   void headerFetchCompleted(in nsIImapProtocol aProtocol);
   void setBiffStateAndUpdate(in long biffState);
   void progressStatus(in nsIImapProtocol aProtocol, in unsigned long aMsgId, in wstring extraInfo);
   void percentProgress(in nsIImapProtocol aProtocol, in wstring aMessage, 
-                       in long aCurrentProgress, in long aMaxProgressProgressInfo);
+                       in long long aCurrentProgress, in long long aMaxProgressProgressInfo);
 
   void clearFolderRights();
   void setCopyResponseUid(in string msgIdString,
                                 in nsIImapUrl aUrl);
   void setAppendMsgUid(in nsMsgKey newKey,
                              in nsIImapUrl aUrl);
   ACString getMessageId(in nsIImapUrl aUrl);
 };
--- a/mailnews/imap/src/nsImapMailFolder.cpp
+++ b/mailnews/imap/src/nsImapMailFolder.cpp
@@ -6059,17 +6059,17 @@ nsImapMailFolder::ProgressStatus(nsIImap
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol,
                                   const PRUnichar * aMessage,
-                                  PRInt32 aCurrentProgress, PRInt32 aMaxProgress)
+                                  PRInt64 aCurrentProgress, PRInt64 aMaxProgress)
 {
   if (aProtocol)
   {
     nsCOMPtr <nsIImapUrl> imapUrl;
     aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
     if (imapUrl)
     {
       nsCOMPtr<nsIImapMockChannel> mockChannel;
@@ -6077,20 +6077,19 @@ nsImapMailFolder::PercentProgress(nsIIma
       if (mockChannel)
       {
         nsCOMPtr<nsIProgressEventSink> progressSink;
         mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
         if (progressSink)
         {
             nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
             if (!request) return NS_ERROR_FAILURE;
-            // XXX handle 64-bit ints for real
             progressSink->OnProgress(request, nsnull,
-                                     nsUint64(aCurrentProgress),
-                                     nsUint64(aMaxProgress));
+                                     aCurrentProgress,
+                                     aMaxProgress);
             if (aMessage)
               progressSink->OnStatus(request, nsnull, NS_OK, aMessage); // XXX i18n message
         }
       }
     }
   }
   return NS_OK;
 }
--- a/mailnews/imap/src/nsImapProtocol.cpp
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -4979,19 +4979,18 @@ nsImapProtocol::ProgressEventFunctionUsi
     nsString unicodeStr;
     nsresult rv = CopyMUTF7toUTF16(nsDependentCString(aExtraInfo), unicodeStr);
     if (NS_SUCCEEDED(rv))
       m_imapMailFolderSink->ProgressStatus(this, aMsgId, unicodeStr.get());
   }
 }
 
 void
-nsImapProtocol::PercentProgressUpdateEvent(PRUnichar *message, PRInt32 currentProgress, PRInt32 maxProgress)
-{
-
+nsImapProtocol::PercentProgressUpdateEvent(PRUnichar *message, PRInt64 currentProgress, PRInt64 maxProgress)
+{
   PRInt64 nowMS = LL_ZERO;
   PRInt32 percent = (100 * currentProgress) / maxProgress;
   if (percent == m_lastPercent)
     return; // hasn't changed, right? So just return. Do we need to clear this anywhere?
 
   if (percent < 100)  // always need to do 100%
   {
     int64 minIntervalBetweenProgress;
@@ -5003,23 +5002,25 @@ nsImapProtocol::PercentProgressUpdateEve
     LL_SUB(diffSinceLastProgress, diffSinceLastProgress, minIntervalBetweenProgress); // r = a - b
     if (!LL_GE_ZERO(diffSinceLastProgress))
       return;
   }
 
   m_lastPercent = percent;
   m_lastProgressTime = nowMS;
 
-  // set our max progress as the content length on the mock channel
-  if (m_mockChannel)
-      m_mockChannel->SetContentLength(maxProgress);
-
+  // set our max progress on the running URL
+  if (m_runningUrl)
+  {
+    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+    mailnewsUrl->SetMaxProgress(maxProgress);
+  }
 
   if (m_imapMailFolderSink)
-      m_imapMailFolderSink->PercentProgress(this, message, currentProgress, maxProgress);
+    m_imapMailFolderSink->PercentProgress(this, message, currentProgress, maxProgress);
 }
 
   // imap commands issued by the parser
 void
 nsImapProtocol::Store(const nsCString &messageList, const char * messageData,
                       PRBool idsAreUid)
 {
 
@@ -5683,17 +5684,17 @@ void nsImapProtocol::UploadMessageFromFi
                                             PRTime date,
                                             imapMessageFlagsType flags,
                                             nsCString &keywords)
 {
   if (!file || !mailboxName) return;
   IncrementCommandTagNumber();
 
   PRInt64 fileSize = 0;
-  PRInt32 totalSize;
+  PRInt64 totalSize;
   PRUint32 readCount;
   char *dataBuffer = nsnull;
   nsCString command(GetServerCommandTag());
   nsCString escapedName;
   CreateEscapedMailboxName(mailboxName, escapedName);
   nsresult rv;
   PRBool eof = PR_FALSE;
   nsCString flagString;
@@ -8245,17 +8246,16 @@ NS_INTERFACE_MAP_END_THREADSAFE
 nsImapMockChannel::nsImapMockChannel()
 {
   m_channelContext = nsnull;
   m_cancelStatus = NS_OK;
   mLoadFlags = 0;
   mChannelClosed = PR_FALSE;
   mReadingFromCache = PR_FALSE;
   mTryingToReadPart = PR_FALSE;
-  mContentLength = -1;
 }
 
 nsImapMockChannel::~nsImapMockChannel()
 {
   // if we're offline, we may not get to close the channel correctly.
   // we need to do this to send the url state change notification in
   // the case of mem and disk cache reads.
   NS_WARN_IF_FALSE(NS_IsMainThread(), "should only access mock channel on ui thread");
@@ -8411,16 +8411,39 @@ NS_IMETHODIMP nsImapMockChannel::SetURI(
     // if we don't have a progress event sink yet, get it from the url for now...
     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
     if (mailnewsUrl && !mProgressEventSink)
     {
       nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
       mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
       mProgressEventSink = do_QueryInterface(statusFeedback);
     }
+    // If this is a fetch URL and we can, get the message size from the message
+    // header and set it to be the content length.
+    // Note that for an attachment URL, this will set the content length to be
+    // equal to the size of the entire message.
+    nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
+    nsImapAction imapAction;
+    imapUrl->GetImapAction(&imapAction);
+    if (imapAction == nsIImapUrl::nsImapMsgFetch)
+    {
+      nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+      if (msgUrl)
+      {
+        nsCOMPtr<nsIMsgDBHdr> msgHdr;
+        // A failure to get a message header isn't an error
+        msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+        if (msgHdr)
+        {
+          PRUint32 messageSize;
+          if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)))
+            SetContentLength(messageSize);
+        }
+      }
+    }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream **_retval)
 {
   return NS_ImplementChannelOpen(this, _retval);
 }
--- a/mailnews/imap/src/nsImapProtocol.h
+++ b/mailnews/imap/src/nsImapProtocol.h
@@ -261,17 +261,17 @@ public:
   void DiscoverMailboxSpec(nsImapMailboxSpec * adoptedBoxSpec);
   void AlertUserEventUsingId(PRUint32 aMessageId);
   void AlertUserEvent(const char * message);
   void AlertUserEventFromServer(const char * aServerEvent);
 
   void ProgressEventFunctionUsingId(PRUint32 aMsgId);
   void ProgressEventFunctionUsingIdWithString(PRUint32 aMsgId, const char *
     aExtraInfo);
-  void PercentProgressUpdateEvent(PRUnichar *message, PRInt32 currentProgress, PRInt32 maxProgress);
+  void PercentProgressUpdateEvent(PRUnichar *message, PRInt64 currentProgress, PRInt64 maxProgress);
   void ShowProgress();
 
   // utility function calls made by the server
 
   void Copy(const char * messageList, const char *destinationMailbox,
     PRBool idsAreUid);
   void Search(const char * searchCriteria,  PRBool useUID,
     PRBool notifyHit = PR_TRUE);
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_imapContentLength.js
@@ -0,0 +1,127 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the IMAP protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+// Take a multipart message as we're testing attachment URLs as well
+const gFile = do_get_file("../../mailnews/data/multipart-complex2");
+var gIMAPDaemon, gIMAPServer, gIMAPIncomingServer, gIMAPInbox;
+const gMFNService = Cc["@mozilla.org/messenger/msgnotificationservice;1"]
+                      .getService(Ci.nsIMsgFolderNotificationService);
+                   
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessageToServer(file, mailbox)
+{
+  let ioService = Cc["@mozilla.org/network/io-service;1"]
+                    .getService(Ci.nsIIOService);
+
+  let URI = ioService.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+  mailbox.addMessage(new imapMessage(URI.spec, mailbox.uidnext++, []));
+
+  gIMAPInbox.updateFolder(null);
+}
+
+var msgFolderListener =
+{
+  msgAdded: function(aMsgHdr)
+  {
+    do_timeout_function(0, verifyContentLength, null, [aMsgHdr]);
+  }
+};
+
+
+function run_test()
+{
+  // Disable new mail notifications
+  let prefSvc = Cc["@mozilla.org/preferences-service;1"]
+                  .getService(Ci.nsIPrefBranch);
+
+  prefSvc.setBoolPref("mail.biff.play_sound", false);
+  prefSvc.setBoolPref("mail.biff.show_alert", false);
+  prefSvc.setBoolPref("mail.biff.show_tray_icon", false);
+  prefSvc.setBoolPref("mail.biff.animate_dock_icon", false);
+
+  // Set up nsIMsgFolderListener to get the header when it's received
+  gMFNService.addListener(msgFolderListener, gMFNService.msgAdded);
+
+  // set up IMAP fakeserver and incoming server
+  gIMAPDaemon = new imapDaemon();
+  gIMAPServer = makeServer(gIMAPDaemon, "");
+  gIMAPIncomingServer = createLocalIMAPServer();
+
+  // we need a local account for the IMAP server to have its sent messages in
+  loadLocalMailAccount();
+
+  // We need an identity so that updateFolder doesn't fail
+  let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
+                  .getService(Ci.nsIMsgAccountManager);
+  let imapAccount = acctMgr.createAccount();
+  let identity = acctMgr.createIdentity();
+  imapAccount.addIdentity(identity);
+  imapAccount.defaultIdentity = identity;
+  imapAccount.incomingServer = gIMAPIncomingServer;
+  acctMgr.defaultAccount = imapAccount;
+
+  // The server doesn't support more than one connection
+  prefSvc.setIntPref("mail.server.server1.max_cached_connections", 1);
+  // We aren't interested in downloading messages automatically
+  prefSvc.setBoolPref("mail.server.server1.download_on_biff", false);
+
+  gIMAPInbox = gIMAPIncomingServer.rootFolder.getChildNamed("Inbox");
+  gIMAPInbox.flags &= ~Ci.nsMsgFolderFlags.Offline;
+
+  do_test_pending();
+
+  // Add a message to the IMAP server
+  addMessageToServer(gFile, gIMAPDaemon.getMailbox("INBOX"));
+
+  gIMAPInbox.updateFolder(null);
+}
+
+function verifyContentLength(aMsgHdr)
+{
+  let messageUri = gIMAPInbox.getUriForMsg(aMsgHdr);
+  // Convert this to a URI that necko can run
+  let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+  let neckoURL = {};
+  let messageService = messenger.messageServiceFromURI(messageUri);
+  messageService.GetUrlForUri(messageUri, neckoURL, null);
+  // Don't use the necko URL directly. Instead, get the spec and create a new
+  // URL using the IO service
+  let urlToRun = gIOService.newURI(neckoURL.value.spec, null, null);
+
+  // Get a channel from this URI, and check its content length
+  let channel = gIOService.newChannelFromURI(urlToRun);
+  do_check_eq(channel.contentLength, gFile.fileSize);
+
+  // Now try an attachment. &part=1.2
+  let attachmentURL = gIOService.newURI(neckoURL.value.spec + "&part=1.2",
+                                        null, null);
+  let attachmentChannel = gIOService.newChannelFromURI(attachmentURL);
+  // Currently attachments have their content length set to the length of the
+  // entire message
+  do_check_eq(channel.contentLength, gFile.fileSize);
+
+  do_timeout_function(1000, endTest);
+}
+
+function endTest()
+{
+  gIMAPServer.resetTest();
+  gIMAPIncomingServer.closeCachedConnections();
+  gIMAPServer.performTest();
+  gIMAPServer.stop();
+  let thread = gThreadManager.currentThread;
+  while (thread.hasPendingEvents())
+    thread.processNextEvent(true);
+
+  do_test_finished(); // for the one in run_test()
+}
--- a/mailnews/local/src/nsMailboxProtocol.cpp
+++ b/mailnews/local/src/nsMailboxProtocol.cpp
@@ -92,41 +92,16 @@ nsMailboxProtocol::nsMailboxProtocol(nsI
 }
 
 nsMailboxProtocol::~nsMailboxProtocol()
 {
   // free our local state 
   delete m_lineStreamBuffer;
 }
 
-NS_IMETHODIMP nsMailboxProtocol::GetContentLength(PRInt32 * aContentLength)
-{
-  *aContentLength = -1;
-  if (m_mailboxAction == nsIMailboxUrl::ActionParseMailbox)
-  {
-    // our file transport knows the entire length of the berkley mail folder
-    // so get it from there.
-    if (!m_request)
-      return NS_OK;
-
-    nsCOMPtr<nsIChannel> info = do_QueryInterface(m_request);
-    if (info) info->GetContentLength(aContentLength);
-    return NS_OK;
-
-  }
-  else if (m_runningUrl)
-  {
-    PRUint32 msgSize = 0;
-    m_runningUrl->GetMessageSize(&msgSize);
-    *aContentLength = (PRInt32) msgSize;
-  }
-
-  return NS_OK;
-}
-
 nsresult nsMailboxProtocol::OpenMultipleMsgTransport(PRUint32 offset, PRInt32 size)
 {
   nsresult rv;
 
   nsCOMPtr<nsIStreamTransportService> serv =
       do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -178,29 +153,45 @@ nsresult nsMailboxProtocol::Initialize(n
       // clear stopped flag on msg window, because we care.
       nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
       if (mailnewsUrl)
       {
         mailnewsUrl->GetMsgWindow(getter_AddRefs(window));
         if (window)
           window->SetStopped(PR_FALSE);
       }
+
       if (m_mailboxAction == nsIMailboxUrl::ActionParseMailbox)
+      {
+        // Set the length of the file equal to the max progress
+        nsCOMPtr<nsIFile> file;
+        GetFileFromURL(aURL, getter_AddRefs(file));
+        if (file)
+        {
+          PRInt64 fileSize = 0;
+          file->GetFileSize(&fileSize);
+          mailnewsUrl->SetMaxProgress(fileSize);
+        }
+
         rv = OpenFileSocket(aURL, 0, -1 /* read in all the bytes in the file */);
+      }
       else
       {
         // we need to specify a byte range to read in so we read in JUST the message we want.
         rv = SetupMessageExtraction();
         if (NS_FAILED(rv)) return rv;
         nsMsgKey aMsgKey;
         PRUint32 aMsgSize = 0;
         rv = m_runningUrl->GetMessageKey(&aMsgKey);
         NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up");
         rv = m_runningUrl->GetMessageSize(&aMsgSize);
         NS_ASSERTION(NS_SUCCEEDED(rv), "oops....i messed something up");
+        SetContentLength(aMsgSize);
+        mailnewsUrl->SetMaxProgress(aMsgSize);
+
         if (RunningMultipleMsgUrl())
         {
           rv = OpenFileSocketForReuse(aURL, (PRUint32) aMsgKey, aMsgSize);
           // if we're running multiple msg url, we clear the event sink because the multiple
           // msg urls will handle setting the progress.
           mProgressEventSink = nsnull;
         }
         else
@@ -647,24 +638,24 @@ PRInt32 nsMailboxProtocol::ReadMessageRe
           SetFlag(MAILBOX_MSG_PARSE_FIRST_LINE);
       } 
       PR_Free(saveLine);
     }
     while (line && !pauseForMoreData);
   }
   
   SetFlag(MAILBOX_PAUSE_FOR_READ); // wait for more data to become available...
-  if (mProgressEventSink)
+  if (mProgressEventSink && m_runningUrl)
   {
-    PRInt32 contentLength = 0;
-    GetContentLength(&contentLength);
-    // XXX 64-bit
+    PRInt64 maxProgress;
+    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+    mailnewsUrl->GetMaxProgress(&maxProgress);
     mProgressEventSink->OnProgress(this, m_channelContext,
-                                   nsUint64(mCurrentProgress),
-                                   nsUint64(contentLength));
+                                   mCurrentProgress,
+                                   maxProgress);
   }
   
   if (NS_FAILED(rv)) return -1;
   
   return 0;
 }
 
 
--- a/mailnews/local/src/nsMailboxProtocol.h
+++ b/mailnews/local/src/nsMailboxProtocol.h
@@ -90,17 +90,16 @@ public:
   virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer);
 
   ////////////////////////////////////////////////////////////////////////////////////////
   // we suppport the nsIStreamListener interface
   ////////////////////////////////////////////////////////////////////////////////////////
 
   NS_IMETHOD OnStartRequest(nsIRequest *request, nsISupports *ctxt);
   NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus);
-  NS_IMETHOD GetContentLength(PRInt32 * aContentLength);
 
 private:
   nsCOMPtr<nsIMailboxUrl>  m_runningUrl; // the nsIMailboxURL that is currently running
   nsMailboxAction m_mailboxAction; // current mailbox action associated with this connnection...
   PRInt32      m_originalContentLength; /* the content length at the time of calling graph progress */
 
   // Event sink handles
   nsCOMPtr<nsIStreamListener> m_mailboxParser;
@@ -108,17 +107,17 @@ private:
   // Local state for the current operation
   nsMsgLineStreamBuffer   * m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream
 
   // Generic state information -- What state are we in? What state do we want to go to
   // after the next response? What was the last response code? etc.
   MailboxStatesEnum  m_nextState;
   MailboxStatesEnum  m_initialState;
 
-  PRInt32 mCurrentProgress;
+  PRInt64   mCurrentProgress;
   PRUint32  m_messageID;
 
         // can we just use the base class m_tempMsgFile?
   nsCOMPtr<nsIFile> m_tempMessageFile;
         nsCOMPtr<nsIOutputStream> m_msgFileOutputStream;
 
   // this is used to hold the source mailbox file open when move/copying
   // multiple messages.
new file mode 100644
--- /dev/null
+++ b/mailnews/local/test/unit/test_mailboxContentLength.js
@@ -0,0 +1,73 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the mailbox protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+// Take a multipart message as we're testing attachment URLs as well
+var gFile = do_get_file("../../mailnews/data/multipart-complex2");
+var gMessageKey;
+
+function run_test()
+{
+  // Set up local folders
+  loadLocalMailAccount();
+
+  // Copy a message into the local folder
+  Cc["@mozilla.org/messenger/messagecopyservice;1"]
+    .getService(Ci.nsIMsgCopyService)
+    .CopyFileMessage(gFile, gLocalInboxFolder, null, false, 0, "",
+                     gCopyListener, null);
+
+  do_test_pending();
+}
+
+var gCopyListener =
+{
+  OnStartCopy: function() {},
+  OnProgress: function(aProgress, aProgressMax) {},
+  SetMessageKey: function(aKey) { gMessageKey = aKey; },
+  GetMessageId: function(aMessageId) {},
+  OnStopCopy: function(aStatus) 
+  {
+    do_timeout_function(0, verifyContentLength);
+  }
+};
+
+function verifyContentLength()
+{
+  // First get the message URI
+  let msgHdr = gLocalInboxFolder.GetMessageHeader(gMessageKey);
+  let messageUri = gLocalInboxFolder.getUriForMsg(msgHdr);
+  // Convert this to a URI that necko can run
+  let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+  let neckoURL = {};
+  let messageService = messenger.messageServiceFromURI(messageUri);
+  messageService.GetUrlForUri(messageUri, neckoURL, null);
+  // Don't use the necko URL directly. Instead, get the spec and create a new
+  // URL using the IO service
+  let ioService = Cc["@mozilla.org/network/io-service;1"]
+                    .getService(Ci.nsIIOService);
+  let urlToRun = ioService.newURI(neckoURL.value.spec, null, null);
+
+  // Get a channel from this URI, and check its content length
+  let channel = ioService.newChannelFromURI(urlToRun);
+  do_check_eq(channel.contentLength, gFile.fileSize);
+
+  // Now try an attachment. &part=1.2
+  let attachmentURL = ioService.newURI(neckoURL.value.spec + "&part=1.2",
+                                       null, null);
+  let attachmentChannel = ioService.newChannelFromURI(attachmentURL);
+  // Currently attachments have their content length set to the length of the
+  // entire message
+  do_check_eq(channel.contentLength, gFile.fileSize);
+
+  do_test_finished();
+}
--- a/mailnews/news/src/nsNNTPProtocol.cpp
+++ b/mailnews/news/src/nsNNTPProtocol.cpp
@@ -460,17 +460,34 @@ NS_IMETHODIMP nsNNTPProtocol::Initialize
     nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
     if (mailnewsUrl)
     {
       if (aMsgWindow)
         mailnewsUrl->SetMsgWindow(aMsgWindow);
 
       m_runningURL->GetNewsAction(&m_newsAction);
       if (m_newsAction == nsINntpUrl::ActionFetchArticle || m_newsAction == nsINntpUrl::ActionFetchPart
-        || m_newsAction == nsINntpUrl::ActionSaveMessageToDisk) {
+          || m_newsAction == nsINntpUrl::ActionSaveMessageToDisk)
+      {
+        // Look for the content length
+        nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_runningURL));
+        if (msgUrl)
+        {
+          nsCOMPtr<nsIMsgDBHdr> msgHdr;
+          msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+          if (msgHdr)
+          {
+            // Note that for attachments, the messageSize is going to be the
+            // size of the entire message
+            PRUint32 messageSize;
+            msgHdr->GetMessageSize(&messageSize);
+            SetContentLength(messageSize);
+          }
+        }
+
         PRBool msgIsInLocalCache = PR_FALSE;
         mailnewsUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
         if (msgIsInLocalCache || WeAreOffline())
           return NS_OK; // probably don't need to do anything else - definitely don't want
         // to open the socket.
       }
     }
   }
--- a/mailnews/news/src/nsNntpUrl.cpp
+++ b/mailnews/news/src/nsNntpUrl.cpp
@@ -264,25 +264,24 @@ NS_IMETHODIMP nsNntpUrl::GetMessageHeade
   nsresult rv;
 
   nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv,rv);
 
   nsCOMPtr <nsIMsgMessageService> msgService = do_QueryInterface(nntpService, &rv);
   NS_ENSURE_SUCCESS(rv,rv);
 
-  if (mOriginalSpec.IsEmpty()) {
-    // this can happen when viewing a news://host/message-id url
-    return NS_ERROR_FAILURE;
-  }
+  nsCAutoString spec(mOriginalSpec);
+  if (spec.IsEmpty())
+    // Handle the case where necko directly runs an internal news:// URL,
+    // one that looks like news://host/message-id?group=mozilla.announce&key=15
+    // Other sorts of URLs -- e.g. news://host/message-id -- will not succeed.
+    GetSpec(spec);
 
-  rv = msgService->MessageURIToMsgHdr(mOriginalSpec.get(), aMsgHdr);
-  NS_ENSURE_SUCCESS(rv,rv);
-
-  return NS_OK;
+  return msgService->MessageURIToMsgHdr(spec.get(), aMsgHdr);
 }
 
 NS_IMETHODIMP nsNntpUrl::IsUrlType(PRUint32 type, PRBool *isType)
 {
   NS_ENSURE_ARG(isType);
 
   switch(type)
   {
new file mode 100644
--- /dev/null
+++ b/mailnews/news/test/unit/test_nntpContentLength.js
@@ -0,0 +1,74 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the news protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+// The basic daemon to use for testing nntpd.js implementations
+var daemon = setupNNTPDaemon();
+
+var server;
+var localserver;
+
+function run_test() {
+  // XXX The server doesn't support returning sizes!
+  return;
+
+  type = "RFC 977";
+  var handler = new NNTP_RFC977_handler(daemon);
+  localserver = setupLocalServer(NNTP_PORT);
+  server = new nsMailServer(handler);
+  server.start(NNTP_PORT);
+
+  try {
+    // Get the folder and new mail
+    let folder = localserver.rootFolder.getChildNamed("test.subscribe.simple");
+    folder.clearFlag(Ci.nsMsgFolderFlags.Offline);
+    folder.getNewMessages(null, {
+      OnStopRunningUrl: function () { localserver.closeCachedConnections(); }});
+    server.performTest();
+
+    do_check_eq(folder.getTotalMessages(false), 1);
+    do_check_true(folder.hasNewMessages);
+
+    server.resetTest();
+
+    // Get the message URI
+    let msgHdr = folder.firstNewMessage;
+    let messageUri = folder.getUriForMsg(msgHdr);
+    // Convert this to a URI that necko can run
+    let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+    let neckoURL = {};
+    let messageService = messenger.messageServiceFromURI(messageUri);
+    messageService.GetUrlForUri(messageUri, neckoURL, null);
+    // Don't use the necko URL directly. Instead, get the spec and create a new
+    // URL using the IO service
+    let ioService = Cc["@mozilla.org/network/io-service;1"]
+                      .getService(Ci.nsIIOService);
+    let urlToRun = ioService.newURI(neckoURL.value.spec, null, null);
+
+    // Get a channel from this URI, and check its content length
+    let channel = ioService.newChannelFromURI(urlToRun);
+    do_check_eq(channel.contentLength, kSimpleNewsArticle.length);
+
+    // Now try an attachment. &part=1.2
+    // XXX the message doesn't really have an attachment
+    let attachmentURL = ioService.newURI(neckoURL.value.spec + "&part=1.2",
+                                         null, null);
+    let attachmentChannel = ioService.newChannelFromURI(attachmentURL);
+    // Currently attachments have their content length set to the length of the
+    // entire message
+    do_check_eq(channel.contentLength, kSimpleNewsArticle.length);
+  }
+  catch (e) {
+    server.stop();
+    do_throw(e);
+  }
+};
--- a/mailnews/test/data/multipart-complex2
+++ b/mailnews/test/data/multipart-complex2
@@ -1,9 +1,11 @@
+From - Mon Jun 02 19:00:00 2008
 Content-Type: multipart/mixed; boundary="bou"
+Message-Id: <123456@example.com>
 
 Part 1
 --bou                       
 Content-Type: multipart/related; boundary="bound"
 
 Part 2
 --bound
 Content-Type: multipart/digest; boundary="boundar"