bug 589292 - add contentDisposition{Filename} props to nsIChannel. r=honzab sr=bzbarsky
authorNick Hurley <hurley@mozilla.com>
Fri, 09 Sep 2011 15:41:04 -0700
changeset 76854 3208bcc1eaf915991e9ea7a3c85c6c5e77b5a8db
parent 76853 d04d43c81294ce6073b9cb6c622886e575a27d1f
child 76855 f06272a4447f6dc59cf8cd5c76a299413694ea14
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewershonzab, bzbarsky
bugs589292
milestone9.0a1
bug 589292 - add contentDisposition{Filename} props to nsIChannel. r=honzab sr=bzbarsky
browser/components/feeds/src/nsFeedSniffer.cpp
content/base/src/nsDocument.cpp
dom/src/jsurl/nsJSProtocolHandler.cpp
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
modules/libjar/test/unit/data/test_bug589292.zip
modules/libjar/test/unit/test_bug589292.js
modules/libjar/test/unit/xpcshell.ini
modules/libpr0n/decoders/icon/mac/nsIconChannelCocoa.mm
modules/libpr0n/decoders/icon/os2/nsIconChannel.cpp
modules/libpr0n/decoders/icon/win/nsIconChannel.cpp
modules/libpr0n/src/imgRequest.cpp
netwerk/base/public/nsChannelProperties.h
netwerk/base/public/nsIChannel.idl
netwerk/base/public/nsIMultiPartChannel.idl
netwerk/base/public/nsNetStrings.h
netwerk/base/public/nsNetUtil.h
netwerk/base/src/nsBaseChannel.cpp
netwerk/base/src/nsNetStrings.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/viewsource/nsViewSourceChannel.cpp
netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
netwerk/streamconv/converters/nsMultiMixedConv.cpp
netwerk/streamconv/converters/nsMultiMixedConv.h
netwerk/test/unit/test_duplicate_headers.js
netwerk/test/unit/test_headers.js
netwerk/test/unit/xpcshell.ini
netwerk/test/unit_ipc/test_headers_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
uriloader/base/nsURILoader.cpp
uriloader/exthandler/ExternalHelperAppParent.cpp
uriloader/exthandler/ExternalHelperAppParent.h
uriloader/exthandler/nsExternalHelperAppService.cpp
uriloader/exthandler/nsExternalProtocolHandler.cpp
--- a/browser/components/feeds/src/nsFeedSniffer.cpp
+++ b/browser/components/feeds/src/nsFeedSniffer.cpp
@@ -124,61 +124,28 @@ nsFeedSniffer::ConvertEncodedData(nsIReq
 template<int N>
 static PRBool
 StringBeginsWithLowercaseLiteral(nsAString& aString,
                                  const char (&aSubstring)[N])
 {
   return StringHead(aString, N).LowerCaseEqualsLiteral(aSubstring);
 }
 
-// XXXsayrer put this in here to get on the branch with minimal delay.
-// Trunk really needs to factor this out. This is the third usage.
 PRBool
 HasAttachmentDisposition(nsIHttpChannel* httpChannel)
 {
   if (!httpChannel)
     return PR_FALSE;
-  
-  nsCAutoString contentDisposition;
-  nsresult rv = 
-    httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"),
-                                   contentDisposition);
-  
-  if (NS_SUCCEEDED(rv) && !contentDisposition.IsEmpty()) {
-    nsCOMPtr<nsIURI> uri;
-    httpChannel->GetURI(getter_AddRefs(uri));
-    nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
-      do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
-    if (NS_SUCCEEDED(rv))
-    {
-      nsCAutoString fallbackCharset;
-      if (uri)
-        uri->GetOriginCharset(fallbackCharset);
-      nsAutoString dispToken;
-      // Get the disposition type
-      rv = mimehdrpar->GetParameter(contentDisposition, "", fallbackCharset,
-                                    PR_TRUE, nsnull, dispToken);
-      // RFC 2183, section 2.8 says that an unknown disposition
-      // value should be treated as "attachment"
-      // XXXbz this code is duplicated in GetFilenameAndExtensionFromChannel in
-      // nsExternalHelperAppService.  Factor it out!
-      if (NS_FAILED(rv) || 
-          (!dispToken.IsEmpty() &&
-           !StringBeginsWithLowercaseLiteral(dispToken, "inline") &&
-           // Broken sites just send
-           // Content-Disposition: filename="file"
-           // without a disposition token... screen those out.
-           !StringBeginsWithLowercaseLiteral(dispToken, "filename") &&
-           // Also in use is Content-Disposition: name="file"
-           !StringBeginsWithLowercaseLiteral(dispToken, "name")))
-        // We have a content-disposition of "attachment" or unknown
-        return PR_TRUE;
-    }
-  } 
-  
+
+  PRUint32 disp;
+  nsresult rv = httpChannel->GetContentDisposition(&disp);
+
+  if (NS_SUCCEEDED(rv) && disp == nsIChannel::DISPOSITION_ATTACHMENT)
+    return PR_TRUE;
+
   return PR_FALSE;
 }
 
 /**
  * @return the first occurrence of a character within a string buffer,
  *         or nsnull if not found
  */
 static const char*
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -6744,24 +6744,21 @@ nsDocument::RetrieveRelevantHeaders(nsIC
 
         if (NS_SUCCEEDED(rv)) {
           PRInt64 intermediateValue;
           LL_I2L(intermediateValue, PR_USEC_PER_MSEC);
           LL_MUL(modDate, msecs, intermediateValue);
         }
       }
     } else {
-      nsCOMPtr<nsIMultiPartChannel> partChannel = do_QueryInterface(aChannel);
-      if (partChannel) {
-        nsCAutoString contentDisp;
-        rv = partChannel->GetContentDisposition(contentDisp);
-        if (NS_SUCCEEDED(rv) && !contentDisp.IsEmpty()) {
-          SetHeaderData(nsGkAtoms::headerContentDisposition,
-                        NS_ConvertASCIItoUTF16(contentDisp));
-        }
+      nsCAutoString contentDisp;
+      rv = aChannel->GetContentDispositionHeader(contentDisp);
+      if (NS_SUCCEEDED(rv)) {
+        SetHeaderData(nsGkAtoms::headerContentDisposition,
+                      NS_ConvertASCIItoUTF16(contentDisp));
       }
     }
   }
 
   if (LL_IS_ZERO(modDate)) {
     // We got nothing from our attempt to ask nsIFileChannel and
     // nsIHttpChannel for the last modified time. Return the current
     // time.
--- a/dom/src/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/src/jsurl/nsJSProtocolHandler.cpp
@@ -1023,16 +1023,34 @@ nsJSChannel::GetContentCharset(nsACStrin
 
 NS_IMETHODIMP
 nsJSChannel::SetContentCharset(const nsACString &aContentCharset)
 {
     return mStreamChannel->SetContentCharset(aContentCharset);
 }
 
 NS_IMETHODIMP
+nsJSChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+    return mStreamChannel->GetContentDisposition(aContentDisposition);
+}
+
+NS_IMETHODIMP
+nsJSChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+    return mStreamChannel->GetContentDispositionFilename(aContentDispositionFilename);
+}
+
+NS_IMETHODIMP
+nsJSChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+    return mStreamChannel->GetContentDispositionHeader(aContentDispositionHeader);
+}
+
+NS_IMETHODIMP
 nsJSChannel::GetContentLength(PRInt32 *aContentLength)
 {
     return mStreamChannel->GetContentLength(aContentLength);
 }
 
 NS_IMETHODIMP
 nsJSChannel::SetContentLength(PRInt32 aContentLength)
 {
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -662,16 +662,42 @@ nsJARChannel::GetContentCharset(nsACStri
 NS_IMETHODIMP
 nsJARChannel::SetContentCharset(const nsACString &aContentCharset)
 {
     mContentCharset = aContentCharset;
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsJARChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+    if (mContentDispositionHeader.IsEmpty())
+        return NS_ERROR_NOT_AVAILABLE;
+
+    *aContentDisposition = mContentDisposition;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+    return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+    if (mContentDispositionHeader.IsEmpty())
+        return NS_ERROR_NOT_AVAILABLE;
+
+    aContentDispositionHeader = mContentDispositionHeader;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsJARChannel::GetContentLength(PRInt32 *result)
 {
     // if content length is unknown, query mJarInput...
     if (mContentLength < 0 && mJarInput)
         mContentLength = mJarInput->GetContentLength();
 
     *result = mContentLength;
     return NS_OK;
@@ -801,51 +827,46 @@ nsJARChannel::OnDownloadComplete(nsIDown
             }
             if (NS_SUCCEEDED(status)) {
                 status = rv;
             }
         }
     }
 
     if (NS_SUCCEEDED(status) && channel) {
-        nsCAutoString header;
         // Grab the security info from our base channel
         channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
 
         nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
         if (httpChannel) {
             // We only want to run scripts if the server really intended to
             // send us a JAR file.  Check the server-supplied content type for
             // a JAR type.
+            nsCAutoString header;
             httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
                                            header);
             nsCAutoString contentType;
             nsCAutoString charset;
             NS_ParseContentType(header, contentType, charset);
             nsCAutoString channelContentType;
             channel->GetContentType(channelContentType);
             mIsUnsafe = !(contentType.Equals(channelContentType) &&
                           (contentType.EqualsLiteral("application/java-archive") ||
                            contentType.EqualsLiteral("application/x-jar")));
-            rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Disposition"),
-                                                header);
-            if (NS_SUCCEEDED(rv))
-                SetPropertyAsACString(NS_CHANNEL_PROP_CONTENT_DISPOSITION, header);
         } else {
             nsCOMPtr<nsIJARChannel> innerJARChannel(do_QueryInterface(channel));
             if (innerJARChannel) {
                 PRBool unsafe;
                 innerJARChannel->GetIsUnsafe(&unsafe);
                 mIsUnsafe = unsafe;
             }
-            // Soon-to-be common way to get Disposition: right now only nsIJARChannel
-            rv = NS_GetContentDisposition(request, header);
-            if (NS_SUCCEEDED(rv))
-                SetPropertyAsACString(NS_CHANNEL_PROP_CONTENT_DISPOSITION, header);
         }
+
+        channel->GetContentDispositionHeader(mContentDispositionHeader);
+        mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
     }
 
     if (NS_SUCCEEDED(status) && mIsUnsafe &&
         !Preferences::GetBool("network.jar.open-unsafe-types", PR_FALSE)) {
         status = NS_ERROR_UNSAFE_CONTENT_TYPE;
     }
 
     if (NS_SUCCEEDED(status)) {
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -91,16 +91,20 @@ private:
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
     nsCOMPtr<nsISupports>           mSecurityInfo;
     nsCOMPtr<nsIProgressEventSink>  mProgressSink;
     nsCOMPtr<nsILoadGroup>          mLoadGroup;
     nsCOMPtr<nsIStreamListener>     mListener;
     nsCOMPtr<nsISupports>           mListenerContext;
     nsCString                       mContentType;
     nsCString                       mContentCharset;
+    nsCString                       mContentDispositionHeader;
+    /* mContentDisposition is uninitialized if mContentDispositionHeader is
+     * empty */
+    PRUint32                        mContentDisposition;
     PRInt32                         mContentLength;
     PRUint32                        mLoadFlags;
     nsresult                        mStatus;
     PRPackedBool                    mIsPending;
     PRPackedBool                    mIsUnsafe;
 
     nsJARInputThunk                *mJarInput;
     nsCOMPtr<nsIStreamListener>     mDownloader;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b7b9e65b4758dd13e940b943d815208fc58a5c48
GIT binary patch
literal 168
zc$^FHW@h1H0D;cJY`YbyMs+MeHVCsb$S|bk=j)YJl!S(GGB7*NtMYXM;?fFk21b^z
zj6hW)KxIHBTmjyUOmfV)jFA9~1JyGuX=H%tfEdaOF%-?f0B=?{hz3RmUm)!Y)(!w@
CH5^F*
new file mode 100644
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug589292.js
@@ -0,0 +1,22 @@
+// Make sure we behave appropriately when asking for content-disposition
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const path = "data/test_bug589292.zip";
+
+function run_test() {
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+            getService(Ci.nsIIOService);
+  var spec = "jar:" + ios.newFileURI(do_get_file(path)).spec + "!/foo.txt";
+  var channel = ios.newChannel(spec, null, null);
+  instr = channel.open();
+  var val;
+  try {
+    val = channel.contentDisposition;
+    do_check_true(false, "The channel has content disposition?!");
+  } catch (e) {
+    // This is what we want to happen - there's no underlying channel, so no
+    // content-disposition header is available
+    do_check_true(true, "How are you reading this?!");
+  }
+}
--- a/modules/libjar/test/unit/xpcshell.ini
+++ b/modules/libjar/test/unit/xpcshell.ini
@@ -5,16 +5,17 @@ tail =
 [test_bug278262.js]
 [test_bug333423.js]
 [test_bug336691.js]
 [test_bug370103.js]
 [test_bug379841.js]
 [test_bug407303.js]
 [test_bug453254.js]
 [test_bug458158.js]
+[test_bug589292.js]
 [test_bug597702.js]
 [test_bug637286.js]
 [test_bug658093.js]
 [test_corrupt_536911.js]
 [test_corrupt_541828.js]
 [test_dirjar_bug525755.js]
 [test_jarinput_stream_zipreader_reference.js]
 [test_not_found.js]
--- a/modules/libpr0n/decoders/icon/mac/nsIconChannelCocoa.mm
+++ b/modules/libpr0n/decoders/icon/mac/nsIconChannelCocoa.mm
@@ -393,16 +393,34 @@ NS_IMETHODIMP nsIconChannel::GetContentC
 NS_IMETHODIMP
 nsIconChannel::SetContentCharset(const nsACString &aContentCharset)
 {
   //It doesn't make sense to set the content-type on this type
   // of channel...
   return NS_ERROR_FAILURE;
 }
 
+NS_IMETHODIMP
+nsIconChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 NS_IMETHODIMP nsIconChannel::GetContentLength(PRInt32 *aContentLength)
 {
   *aContentLength = mContentLength;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsIconChannel::SetContentLength(PRInt32 aContentLength)
 {
--- a/modules/libpr0n/decoders/icon/os2/nsIconChannel.cpp
+++ b/modules/libpr0n/decoders/icon/os2/nsIconChannel.cpp
@@ -630,16 +630,34 @@ NS_IMETHODIMP nsIconChannel::GetContentC
 NS_IMETHODIMP
 nsIconChannel::SetContentCharset(const nsACString &aContentCharset)
 {
   // It doesn't make sense to set the content-charset on this type
   // of channel...
   return NS_ERROR_FAILURE;
 }
 
+NS_IMETHODIMP
+nsIconChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 NS_IMETHODIMP nsIconChannel::GetContentLength(PRInt32 *aContentLength)
 {
   *aContentLength = mContentLength;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsIconChannel::SetContentLength(PRInt32 aContentLength)
 {
--- a/modules/libpr0n/decoders/icon/win/nsIconChannel.cpp
+++ b/modules/libpr0n/decoders/icon/win/nsIconChannel.cpp
@@ -649,16 +649,34 @@ NS_IMETHODIMP nsIconChannel::GetContentC
 NS_IMETHODIMP
 nsIconChannel::SetContentCharset(const nsACString &aContentCharset)
 {
   // It doesn't make sense to set the content-charset on this type
   // of channel...
   return NS_ERROR_FAILURE;
 }
 
+NS_IMETHODIMP
+nsIconChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 NS_IMETHODIMP nsIconChannel::GetContentLength(PRInt32 *aContentLength)
 {
   *aContentLength = mContentLength;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsIconChannel::SetContentLength(PRInt32 aContentLength)
 {
--- a/modules/libpr0n/src/imgRequest.cpp
+++ b/modules/libpr0n/src/imgRequest.cpp
@@ -989,21 +989,20 @@ NS_IMETHODIMP imgRequest::OnDataAvailabl
      */
     PRUint32 out;
     inStr->ReadSegments(sniff_mimetype_callback, this, count, &out);
 
 #ifdef NS_DEBUG
     /* NS_WARNING if the content type from the channel isn't the same if the sniffing */
 #endif
 
+    nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
     if (mContentType.IsEmpty()) {
       LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable |sniffing of mimetype failed|");
 
-      nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
-
       rv = NS_ERROR_FAILURE;
       if (chan) {
         rv = chan->GetContentType(mContentType);
       }
 
       if (NS_FAILED(rv)) {
         PR_LOG(gImgLog, PR_LOG_ERROR,
                ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n",
@@ -1036,24 +1035,18 @@ NS_IMETHODIMP imgRequest::OnDataAvailabl
     nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
     if (contentType) {
       contentType->SetData(mContentType);
       mProperties->Set("type", contentType);
     }
 
     /* set our content disposition as a property */
     nsCAutoString disposition;
-    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
-    if (httpChannel) {
-      httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"), disposition);
-    } else {
-      nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aRequest));
-      if (multiPartChannel) {
-        multiPartChannel->GetContentDisposition(disposition);
-      }
+    if (chan) {
+      chan->GetContentDispositionHeader(disposition);
     }
     if (!disposition.IsEmpty()) {
       nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1"));
       if (contentDisposition) {
         contentDisposition->SetData(disposition);
         mProperties->Set("content-disposition", contentDisposition);
       }
     }
@@ -1109,16 +1102,17 @@ NS_IMETHODIMP imgRequest::OnDataAvailabl
     if (NS_FAILED(rv)) { // Probably bad mimetype
 
       this->Cancel(rv);
       return NS_BINDING_ABORTED;
     }
 
     if (imageType == imgIContainer::TYPE_RASTER) {
       /* Use content-length as a size hint for http channels. */
+      nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
       if (httpChannel) {
         nsCAutoString contentLength;
         rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-length"),
                                             contentLength);
         if (NS_SUCCEEDED(rv)) {
           PRInt32 len = contentLength.ToInteger(&rv);
 
           // Pass anything usable on so that the RasterImage can preallocate
--- a/netwerk/base/public/nsChannelProperties.h
+++ b/netwerk/base/public/nsChannelProperties.h
@@ -53,36 +53,27 @@
 /**
  * Content-Length of a channel. Used instead of the nsIChannel.contentLength
  * property.
  * Not available before onStartRequest has been called.
  * Type: PRUint64
  */
 #define NS_CHANNEL_PROP_CONTENT_LENGTH_STR "content-length"
 
-/**
- * MIME Content-Disposition header of channel.  
- * Not available before onStartRequest. 
- * Type: nsACString
- */
-#define NS_CHANNEL_PROP_CONTENT_DISPOSITION_STR "content-disposition"
 
 /**
  * Exists to allow content policy mechanism to function properly during channel
  * redirects.  Contains security contextual information about the load.
  * Type: nsIChannelPolicy
  */
 #define NS_CHANNEL_PROP_CHANNEL_POLICY_STR "channel-policy"
 
 #ifdef IMPL_NS_NET
 #define NS_CHANNEL_PROP_CONTENT_LENGTH gNetStrings->kContentLength
-#define NS_CHANNEL_PROP_CONTENT_DISPOSITION gNetStrings->kContentDisposition
 #define NS_CHANNEL_PROP_CHANNEL_POLICY gNetStrings->kChannelPolicy
 #else
 #define NS_CHANNEL_PROP_CONTENT_LENGTH \
   NS_LITERAL_STRING(NS_CHANNEL_PROP_CONTENT_LENGTH_STR)
-#define NS_CHANNEL_PROP_CONTENT_DISPOSITION \
-  NS_LITERAL_STRING(NS_CHANNEL_PROP_CONTENT_DISPOSITION_STR)
 #define NS_CHANNEL_PROP_CHANNEL_POLICY \
   NS_LITERAL_STRING(NS_CHANNEL_PROP_CHANNEL_POLICY_STR)
 #endif
 
 #endif
--- a/netwerk/base/public/nsIChannel.idl
+++ b/netwerk/base/public/nsIChannel.idl
@@ -51,17 +51,17 @@ interface nsIStreamListener;
  * by calling nsIChannel::open or nsIChannel::asyncOpen.
  *
  * After a request has been completed, the channel is still valid for accessing
  * protocol-specific results.  For example, QI'ing to nsIHttpChannel allows
  * response headers to be retrieved for the corresponding http transaction.
  *
  * This interface must be used only from the XPCOM main thread.
  */
-[scriptable, uuid(c63a055a-a676-4e71-bf3c-6cfa11082018)]
+[scriptable, uuid(06f6ada3-7729-4e72-8d3f-bf8ba630ff9b)]
 interface nsIChannel : nsIRequest
 {
     /**
      * The original URI used to construct the channel. This is used in
      * the case of a redirect or URI "resolution" (e.g. resolving a
      * resource: URI to a file: URI) so that the original pre-redirect
      * URI can still be obtained.  This is never null.  Attempts to
      * set it to null must throw.
@@ -267,9 +267,44 @@ interface nsIChannel : nsIRequest
      */
     const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21;
 
     /**
      * This flag tells the channel to use URI classifier service to check
      * the URI when opening the channel.
      */
     const unsigned long LOAD_CLASSIFY_URI = 1 << 22;
+
+    /**
+     * Access to the type implied or stated by the Content-Disposition header
+     * if available and if applicable. This allows determining inline versus
+     * attachment.
+     *
+     * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+     * doesn't exist for this type of channel or is empty, and
+     * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present.
+     */
+    readonly attribute unsigned long contentDisposition;
+    const unsigned long DISPOSITION_INLINE = 0;
+    const unsigned long DISPOSITION_ATTACHMENT = 1;
+
+    /**
+     * Access to the filename portion of the Content-Disposition header if
+     * available and if applicable. This allows getting the preferred filename
+     * without having to parse it out yourself.
+     *
+     * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't
+     * exist for this type of channel, if the header is empty, if the header
+     * doesn't contain a filename portion, or the value of the filename
+     * attribute is empty/missing.
+     */
+    readonly attribute AString contentDispositionFilename;
+
+    /**
+     * Access to the raw Content-Disposition header if available and applicable.
+     *
+     * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+     * doesn't exist for this type of channel or is empty.
+     *
+     * @deprecated Use contentDisposition/contentDispositionFilename instead.
+     */
+    readonly attribute ACString contentDispositionHeader;
 };
--- a/netwerk/base/public/nsIMultiPartChannel.idl
+++ b/netwerk/base/public/nsIMultiPartChannel.idl
@@ -40,32 +40,25 @@
 
 interface nsIChannel;
 
 /**
  * An interface to access the the base channel 
  * associated with a MultiPartChannel.
  */
 
-[scriptable, uuid(ba78db7b-b88c-4b76-baf9-3c2296a585ae)]
+[scriptable, uuid(4b04e835-d131-42af-8bf0-74289f99374f)]
 interface nsIMultiPartChannel : nsISupports
 {
     /**
      * readonly attribute to access the underlying channel
      */
     readonly attribute nsIChannel baseChannel;
 
     /**
-     * Access to the Content-Disposition header field of this part of
-     * a multipart message.  This allows getting the preferred
-     * handling method, preferred filename, etc.  See RFC 2183.
-     */
-    attribute ACString contentDisposition;
-
-    /**
      * Attribute guaranteed to be different for different parts of
      * the same multipart document.
      */
     readonly attribute PRUint32 partID;
 
     /**
      * Set to true when onStopRequest is received from the base channel.
      * The listener can check this from its onStopRequest to determine
--- a/netwerk/base/public/nsNetStrings.h
+++ b/netwerk/base/public/nsNetStrings.h
@@ -44,16 +44,15 @@
  * wherever these strings are used.
  */
 class nsNetStrings {
 public:
   nsNetStrings();
 
   /** "content-length" */
   const nsLiteralString kContentLength;
-  const nsLiteralString kContentDisposition;
   const nsLiteralString kChannelPolicy;
 };
 
 extern NS_HIDDEN_(nsNetStrings*) gNetStrings;
 
 
 #endif
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -101,16 +101,17 @@
 #include "nsIMutable.h"
 #include "nsIPropertyBag2.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIIDNService.h"
 #include "nsIChannelEventSink.h"
 #include "nsIChannelPolicy.h"
 #include "nsISocketProviderService.h"
 #include "nsISocketProvider.h"
+#include "nsIMIMEHeaderParam.h"
 #include "mozilla/Services.h"
 
 #include "nsIRedirectChannelRegistrar.h"
 
 #ifdef MOZILLA_INTERNAL_API
 
 inline already_AddRefed<nsIIOService>
 do_GetIOService(nsresult* error = 0)
@@ -239,29 +240,16 @@ NS_NewChannel(nsIChannel           **res
             }
             if (NS_SUCCEEDED(rv))
                 chan.forget(result);
         }
     }
     return rv;
 }
 
-// For now, works only with JARChannel.  Future: with all channels that may
-// have Content-Disposition header (JAR, nsIHttpChannel, and nsIMultiPartChannel).
-inline nsresult
-NS_GetContentDisposition(nsIRequest     *channel,
-                         nsACString     &result)
-{
-    nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(channel));
-    if (props)
-        return props->GetPropertyAsACString(NS_CHANNEL_PROP_CONTENT_DISPOSITION,
-                                            result);
-    return NS_ERROR_NOT_AVAILABLE;
-}
-
 // Use this function with CAUTION. It creates a stream that blocks when you
 // Read() from it and blocking the UI thread is a bad idea. If you don't want
 // to implement a full blown asynchronous consumer (via nsIStreamListener) look
 // at nsIStreamLoader instead.
 inline nsresult
 NS_OpenURI(nsIInputStream       **result,
            nsIURI                *uri,
            nsIIOService          *ioService = nsnull,     // pass in nsIIOService to optimize callers
@@ -1902,16 +1890,115 @@ NS_CheckIsJavaCompatibleURLString(nsCStr
     compatible = PR_FALSE;
   }
 
   *result = compatible;
 
   return NS_OK;
 }
 
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+inline PRUint32
+NS_GetContentDispositionFromToken(const nsAString& aDispToken)
+{
+  // RFC 2183, section 2.8 says that an unknown disposition
+  // value should be treated as "attachment"
+  // If all of these tests eval to false, then we have a content-disposition of
+  // "attachment" or unknown
+  if (aDispToken.IsEmpty() ||
+      aDispToken.LowerCaseEqualsLiteral("inline") ||
+      // Broken sites just send
+      // Content-Disposition: filename="file"
+      // without a disposition token... screen those out.
+      StringHead(aDispToken, 8).LowerCaseEqualsLiteral("filename") ||
+      // Also in use is Content-Disposition: name="file"
+      StringHead(aDispToken, 4).LowerCaseEqualsLiteral("name"))
+    return nsIChannel::DISPOSITION_INLINE;
+
+  return nsIChannel::DISPOSITION_ATTACHMENT;
+}
+
+/** Determine the disposition (inline/attachment) of the content based on the
+ * Content-Disposition header
+ * @param aHeader the content-disposition header (full value)
+ * @param aChan the channel the header came from
+ */
+inline PRUint32
+NS_GetContentDispositionFromHeader(const nsACString& aHeader, nsIChannel *aChan = nsnull)
+{
+  nsresult rv;
+  nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+  if (NS_FAILED(rv))
+    return nsIChannel::DISPOSITION_ATTACHMENT;
+
+  nsCAutoString fallbackCharset;
+  if (aChan) {
+    nsCOMPtr<nsIURI> uri;
+    aChan->GetURI(getter_AddRefs(uri));
+    if (uri)
+      uri->GetOriginCharset(fallbackCharset);
+  }
+
+  nsAutoString dispToken;
+  rv = mimehdrpar->GetParameter(aHeader, "", fallbackCharset, PR_TRUE, nsnull,
+                                dispToken);
+  if (NS_FAILED(rv))
+    return nsIChannel::DISPOSITION_ATTACHMENT;
+
+  return NS_GetContentDispositionFromToken(dispToken);
+}
+
+/** Extracts the filename out of a content-disposition header
+ * @param aFilename [out] The filename. Can be empty on error.
+ * @param aDisposition Value of a Content-Disposition header
+ * @param aURI Optional. Will be used to get a fallback charset for the
+ *        filename, if it is QI'able to nsIURL
+ */
+inline nsresult
+NS_GetFilenameFromDisposition(nsAString& aFilename,
+                              const nsACString& aDisposition,
+                              nsIURI* aURI = nsnull)
+{
+  aFilename.Truncate();
+
+  nsresult rv;
+  nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+      do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+
+  nsCAutoString fallbackCharset;
+  if (url)
+    url->GetOriginCharset(fallbackCharset);
+  // Get the value of 'filename' parameter
+  rv = mimehdrpar->GetParameter(aDisposition, "filename",
+                                fallbackCharset, PR_TRUE, nsnull,
+                                aFilename);
+  if (NS_FAILED(rv) || aFilename.IsEmpty()) {
+    // Try 'name' parameter, instead.
+    rv = mimehdrpar->GetParameter(aDisposition, "name", fallbackCharset,
+                                  PR_TRUE, nsnull, aFilename);
+  }
+
+  if (NS_FAILED(rv)) {
+    aFilename.Truncate();
+    return rv;
+  }
+
+  if (aFilename.IsEmpty())
+    return NS_ERROR_NOT_AVAILABLE;
+
+  return NS_OK;
+}
+
 /**
  * Make sure Personal Security Manager is initialized
  */
 inline void
 net_EnsurePSMInit()
 {
     nsCOMPtr<nsISocketProviderService> spserv =
             do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID);
--- a/netwerk/base/src/nsBaseChannel.cpp
+++ b/netwerk/base/src/nsBaseChannel.cpp
@@ -515,16 +515,34 @@ nsBaseChannel::GetContentCharset(nsACStr
 NS_IMETHODIMP
 nsBaseChannel::SetContentCharset(const nsACString &aContentCharset)
 {
   mContentCharset = aContentCharset;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsBaseChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
 nsBaseChannel::GetContentLength(PRInt32 *aContentLength)
 {
   PRInt64 len = ContentLength64();
   if (len > PR_INT32_MAX || len < 0)
     *aContentLength = -1;
   else
     *aContentLength = (PRInt32) len;
   return NS_OK;
--- a/netwerk/base/src/nsNetStrings.cpp
+++ b/netwerk/base/src/nsNetStrings.cpp
@@ -36,13 +36,12 @@
 
 #include "nsNetStrings.h"
 #include "nsChannelProperties.h"
 
 NS_HIDDEN_(nsNetStrings*) gNetStrings;
 
 nsNetStrings::nsNetStrings()
   : NS_LITERAL_STRING_INIT(kContentLength, NS_CHANNEL_PROP_CONTENT_LENGTH_STR),
-    NS_LITERAL_STRING_INIT(kContentDisposition, NS_CHANNEL_PROP_CONTENT_DISPOSITION_STR),
     NS_LITERAL_STRING_INIT(kChannelPolicy, NS_CHANNEL_PROP_CHANNEL_POLICY_STR)
 {}
 
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -372,16 +372,61 @@ HttpBaseChannel::SetContentCharset(const
   } else {
     // Charset hint
     mContentCharsetHint = aContentCharset;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+HttpBaseChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  nsresult rv;
+  nsCString header;
+
+  rv = GetContentDispositionHeader(header);
+  if (NS_FAILED(rv))
+    return rv;
+
+  *aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+  aContentDispositionFilename.Truncate();
+
+  nsresult rv;
+  nsCString header;
+
+  rv = GetContentDispositionHeader(header);
+  if (NS_FAILED(rv))
+    return rv;
+
+  return NS_GetFilenameFromDisposition(aContentDispositionFilename,
+                                       header, mURI);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+  if (!mResponseHead)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
+                                         aContentDispositionHeader);
+  if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty())
+    return NS_ERROR_NOT_AVAILABLE;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 HttpBaseChannel::GetContentLength(PRInt32 *aContentLength)
 {
   NS_ENSURE_ARG_POINTER(aContentLength);
 
   if (!mResponseHead)
     return NS_ERROR_NOT_AVAILABLE;
 
   // XXX truncates to 32 bit
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -111,16 +111,19 @@ public:
   NS_IMETHOD GetOwner(nsISupports **aOwner);
   NS_IMETHOD SetOwner(nsISupports *aOwner);
   NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks);
   NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks);
   NS_IMETHOD GetContentType(nsACString& aContentType);
   NS_IMETHOD SetContentType(const nsACString& aContentType);
   NS_IMETHOD GetContentCharset(nsACString& aContentCharset);
   NS_IMETHOD SetContentCharset(const nsACString& aContentCharset);
+  NS_IMETHOD GetContentDisposition(PRUint32 *aContentDisposition);
+  NS_IMETHOD GetContentDispositionFilename(nsAString& aContentDispositionFilename);
+  NS_IMETHOD GetContentDispositionHeader(nsACString& aContentDispositionHeader);
   NS_IMETHOD GetContentLength(PRInt32 *aContentLength);
   NS_IMETHOD SetContentLength(PRInt32 aContentLength);
   NS_IMETHOD Open(nsIInputStream **aResult);
 
   // nsIEncodedChannel
   NS_IMETHOD GetApplyConversion(PRBool *value);
   NS_IMETHOD SetApplyConversion(PRBool value);
   NS_IMETHOD GetContentEncodings(nsIUTF8StringEnumerator** aEncodings);
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -367,16 +367,40 @@ NS_IMETHODIMP
 nsViewSourceChannel::SetContentCharset(const nsACString &aContentCharset)
 {
     NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
 
     return mChannel->SetContentCharset(aContentCharset);
 }
 
 NS_IMETHODIMP
+nsViewSourceChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+    NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+    return mChannel->GetContentDisposition(aContentDisposition);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+    NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+    return mChannel->GetContentDispositionFilename(aContentDispositionFilename);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+    NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
+
+    return mChannel->GetContentDispositionHeader(aContentDispositionHeader);
+}
+
+NS_IMETHODIMP
 nsViewSourceChannel::GetContentLength(PRInt32 *aContentLength)
 {
     NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);
 
     return mChannel->GetContentLength(aContentLength);
 }
 
 NS_IMETHODIMP
--- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
@@ -530,16 +530,34 @@ WyciwygChannelChild::GetContentCharset(n
   return NS_OK;
 }
 NS_IMETHODIMP
 WyciwygChannelChild::SetContentCharset(const nsACString & aContentCharset)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WyciwygChannelChild::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 /* attribute long contentLength; */
 NS_IMETHODIMP
 WyciwygChannelChild::GetContentLength(PRInt32 *aContentLength)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 NS_IMETHODIMP
 WyciwygChannelChild::SetContentLength(PRInt32 aContentLength)
--- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
+++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
@@ -339,16 +339,34 @@ nsWyciwygChannel::GetContentCharset(nsAC
 
 NS_IMETHODIMP
 nsWyciwygChannel::SetContentCharset(const nsACString &aContentCharset)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
+nsWyciwygChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsWyciwygChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
 nsWyciwygChannel::GetContentLength(PRInt32 *aContentLength)
 {
   *aContentLength = mContentLength;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWyciwygChannel::SetContentLength(PRInt32 aContentLength)
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -115,16 +115,26 @@ nsresult nsPartChannel::SendOnStopReques
                                           nsresult aStatus)
 {
     // Drop the listener
     nsCOMPtr<nsIStreamListener> listener;
     listener.swap(mListener);
     return listener->OnStopRequest(this, aContext, aStatus);
 }
 
+void nsPartChannel::SetContentDisposition(const nsACString& aContentDispositionHeader)
+{
+    mContentDispositionHeader = aContentDispositionHeader;
+    nsCOMPtr<nsIURI> uri;
+    GetURI(getter_AddRefs(uri));
+    NS_GetFilenameFromDisposition(mContentDispositionFilename,
+                                  mContentDispositionHeader, uri);
+    mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
+}
+
 //
 // nsISupports implementation...
 //
 
 NS_IMPL_ADDREF(nsPartChannel)
 NS_IMPL_RELEASE(nsPartChannel)
 
 NS_INTERFACE_MAP_BEGIN(nsPartChannel)
@@ -332,26 +342,42 @@ nsPartChannel::GetContentLength(PRInt32 
 NS_IMETHODIMP
 nsPartChannel::SetContentLength(PRInt32 aContentLength)
 {
     mContentLength = aContentLength;
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsPartChannel::GetContentDisposition(nsACString &aContentDisposition)
+nsPartChannel::GetContentDisposition(PRUint32 *aContentDisposition)
 {
-    aContentDisposition = mContentDisposition;
+    if (mContentDispositionHeader.IsEmpty())
+        return NS_ERROR_NOT_AVAILABLE;
+
+    *aContentDisposition = mContentDisposition;
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsPartChannel::SetContentDisposition(const nsACString &aContentDisposition)
+nsPartChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
 {
-    mContentDisposition = aContentDisposition;
+    if (mContentDispositionFilename.IsEmpty())
+        return NS_ERROR_NOT_AVAILABLE;
+
+    aContentDispositionFilename = mContentDispositionFilename;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPartChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+    if (mContentDispositionHeader.IsEmpty())
+        return NS_ERROR_NOT_AVAILABLE;
+
+    aContentDispositionHeader = mContentDispositionHeader;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPartChannel::GetPartID(PRUint32 *aPartID)
 {
     *aPartID = mPartID;
     return NS_OK;
@@ -794,18 +820,17 @@ nsMultiMixedConv::SendStart(nsIChannel *
     mPartChannel = newChannel;
 
     rv = mPartChannel->SetContentType(mContentType);
     if (NS_FAILED(rv)) return rv;
 
     rv = mPartChannel->SetContentLength(mContentLength); // XXX Truncates 64-bit!
     if (NS_FAILED(rv)) return rv;
 
-    rv = mPartChannel->SetContentDisposition(mContentDisposition);
-    if (NS_FAILED(rv)) return rv;
+    mPartChannel->SetContentDisposition(mContentDisposition);
 
     nsLoadFlags loadFlags = 0;
     mPartChannel->GetLoadFlags(&loadFlags);
     loadFlags |= nsIChannel::LOAD_REPLACE;
     mPartChannel->SetLoadFlags(loadFlags);
 
     nsCOMPtr<nsILoadGroup> loadGroup;
     (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
--- a/netwerk/streamconv/converters/nsMultiMixedConv.h
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.h
@@ -71,16 +71,19 @@ public:
                 nsIStreamListener* aListener);
 
   void InitializeByteRange(PRInt64 aStart, PRInt64 aEnd);
   void SetIsLastPart() { mIsLastPart = PR_TRUE; }
   nsresult SendOnStartRequest(nsISupports* aContext);
   nsresult SendOnDataAvailable(nsISupports* aContext, nsIInputStream* aStream,
                                PRUint32 aOffset, PRUint32 aLen);
   nsresult SendOnStopRequest(nsISupports* aContext, nsresult aStatus);
+  /* SetContentDisposition expects the full value of the Content-Disposition
+   * header */
+  void SetContentDisposition(const nsACString& aContentDispositionHeader);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUEST
   NS_DECL_NSICHANNEL
   NS_DECL_NSIBYTERANGEREQUEST
   NS_DECL_NSIMULTIPARTCHANNEL
 
 protected:
@@ -92,17 +95,19 @@ protected:
   
   nsresult                mStatus;
   nsLoadFlags             mLoadFlags;
 
   nsCOMPtr<nsILoadGroup>  mLoadGroup;
 
   nsCString               mContentType;
   nsCString               mContentCharset;
-  nsCString               mContentDisposition;
+  PRUint32                mContentDisposition;
+  nsString                mContentDispositionFilename;
+  nsCString               mContentDispositionHeader;
   PRUint64                mContentLength;
 
   PRBool                  mIsByteRangeRequest;
   PRInt64                 mByteRangeStart;
   PRInt64                 mByteRangeEnd;
 
   PRUint32                mPartID; // unique ID that can be used to identify
                                    // this part of the multipart document
--- a/netwerk/test/unit/test_duplicate_headers.js
+++ b/netwerk/test/unit/test_duplicate_headers.js
@@ -344,20 +344,20 @@ function handler11(metadata, response)
   response.finish();
 }
 
 function completeTest11(request, data, ctx)
 {
   do_check_eq(request.status, 0);
 
   try {
-    // TODO when bug XXX lands, also get channel C-D properties and make sure
-    // they're blank
-    dispo = request.getResponseHeader("Content-Disposition");
-    do_check_eq(dispo, "attachment; filename=foo");
+    var chan = request.QueryInterface(Ci.nsIChannel);
+    do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+    do_check_eq(chan.contentDispositionFilename, "foo");
+    do_check_eq(chan.contentDispositionHeader, "attachment; filename=foo");
   } catch (ex) {
-    do_throw("Content-Disposition should be present");
+    do_throw("error parsing Content-Disposition: " + ex);
   }
 
   endTests();
 }
 
 
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_headers.js
@@ -0,0 +1,179 @@
+//
+//  cleaner HTTP header test infrastructure
+//
+//  tests bugs:  589292, [add more here: see hg log for definitive list]
+//
+//  TO ADD NEW TESTS:
+//  1) Increment up 'lastTest' to new number (say, "99")
+//  2) Add new test 'handler99' and 'completeTest99' functions.
+//  3) If your test should fail the necko channel, set 
+//     test_flags[99] = CL_EXPECT_FAILURE.   
+//
+// TO DEBUG JUST ONE TEST: temporarily change firstTest and lastTest to equal
+//                         the test # you're interested in.
+//
+//  For tests that need duplicate copies of headers to be sent, see
+//  test_duplicate_headers.js
+
+var firstTest = 1;   // set to test of interest when debugging
+var lastTest = 4;    // set to test of interest when debugging
+////////////////////////////////////////////////////////////////////////////////
+
+// Note: sets Cc and Ci variables
+do_load_httpd_js();
+
+var httpserver = new nsHttpServer();
+var index = 0;
+var nextTest = firstTest; 
+var test_flags = new Array();
+var testPathBase = "/test_headers";
+
+function run_test()
+{
+  httpserver.start(4444);
+
+  do_test_pending();
+  run_test_number(nextTest);
+}
+
+function runNextTest()
+{
+  if (nextTest == lastTest) {
+    endTests();
+    return;
+  }
+  nextTest++;
+  // Make sure test functions exist
+  if (eval("handler" + nextTest) == undefined)
+    do_throw("handler" + nextTest + " undefined!");
+  if (eval("completeTest" + nextTest) == undefined)
+    do_throw("completeTest" + nextTest + " undefined!");
+  
+  run_test_number(nextTest);
+}
+
+function run_test_number(num)
+{
+  testPath = testPathBase + num;
+  httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+  var channel = setupChannel(testPath);
+  flags = test_flags[num];   // OK if flags undefined for test
+  channel.asyncOpen(new ChannelListener(eval("completeTest" + num),
+                                        channel, flags), null);
+}
+
+function setupChannel(url)
+{
+  var ios = Components.classes["@mozilla.org/network/io-service;1"].
+                       getService(Ci.nsIIOService);
+  var chan = ios.newChannel("http://localhost:4444" + url, "", null);
+  var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
+  return httpChan;
+}
+
+function endTests()
+{
+  httpserver.stop(do_test_finished);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 1: test Content-Disposition channel attributes
+function handler1(metadata, response)
+{
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Disposition", "attachment; filename=foo");
+  response.setHeader("Content-Type", "text/plain", false);
+  var body = "foo";
+}
+
+function completeTest1(request, data, ctx)
+{
+  try {
+    var chan = request.QueryInterface(Ci.nsIChannel);
+    do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+    do_check_eq(chan.contentDispositionFilename, "foo");
+    do_check_eq(chan.contentDispositionHeader, "attachment; filename=foo");
+  } catch (ex) {
+    do_throw("error parsing Content-Disposition: " + ex);
+  }
+  runNextTest();  
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 2: no filename 
+function handler2(metadata, response)
+{
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setHeader("Content-Disposition", "attachment");
+  var body = "foo";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function completeTest2(request, data, ctx)
+{
+  try {
+    var chan = request.QueryInterface(Ci.nsIChannel);
+    do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+    do_check_eq(chan.contentDispositionHeader, "attachment");
+
+    filename = chan.contentDispositionFilename;  // should barf
+    do_throw("Should have failed getting Content-Disposition filename");
+  } catch (ex) {
+    do_print("correctly ate exception");    
+  }
+  runNextTest();  
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 3: filename missing
+function handler3(metadata, response)
+{
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setHeader("Content-Disposition", "attachment; filename=");
+  var body = "foo";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function completeTest3(request, data, ctx)
+{
+  try {
+    var chan = request.QueryInterface(Ci.nsIChannel);
+    do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
+    do_check_eq(chan.contentDispositionHeader, "attachment; filename=");
+
+    filename = chan.contentDispositionFilename;  // should barf
+    do_throw("Should have failed getting Content-Disposition filename");
+  } catch (ex) {
+    do_print("correctly ate exception");    
+  }
+  runNextTest();  
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Test 4: inline
+function handler4(metadata, response)
+{
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setHeader("Content-Disposition", "inline");
+  var body = "foo";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function completeTest4(request, data, ctx)
+{
+  try {
+    var chan = request.QueryInterface(Ci.nsIChannel);
+    do_check_eq(chan.contentDisposition, chan.DISPOSITION_INLINE);
+    do_check_eq(chan.contentDispositionHeader, "inline");
+
+    filename = chan.contentDispositionFilename;  // should barf
+    do_throw("Should have failed getting Content-Disposition filename");
+  } catch (ex) {
+    do_print("correctly ate exception");    
+  }
+  runNextTest();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -121,16 +121,17 @@ skip-if = os == "android"
 # Bug 675039: test hangs consistently on Android 
 skip-if = os == "android"
 [test_file_partial_inputstream.js]
 [test_file_protocol.js]
 [test_filestreams.js]
 [test_gre_resources.js]
 [test_gzipped_206.js]
 [test_head.js]
+[test_headers.js]
 [test_http_headers.js]
 [test_httpcancel.js]
 [test_httpsuspend.js]
 [test_idnservice.js]
 [test_localstreams.js]
 [test_MIME_params.js]
 [test_multipart_streamconv.js]
 [test_nestedabout_serialize.js]
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_headers_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+  run_test_in_child("../unit/test_headers.js");
+}
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -2,16 +2,17 @@
 head = head_channels_clone.js
 tail = 
 
 [test_channel_close_wrap.js]
 [test_cookie_wrap.js]
 [test_duplicate_headers_wrap.js]
 [test_event_sink_wrap.js]
 [test_head_wrap.js]
+[test_headers_wrap.js]
 [test_httpcancel_wrap.js]
 [test_httpsuspend_wrap.js]
 [test_post_wrap.js]
 [test_progress_wrap.js]
 [test_redirect-caching_canceled_wrap.js]
 [test_redirect-caching_failure_wrap.js]
 [test_redirect-caching_passing_wrap.js]
 [test_redirect_canceled_wrap.js]
--- a/uriloader/base/nsURILoader.cpp
+++ b/uriloader/base/nsURILoader.cpp
@@ -380,68 +380,20 @@ nsresult nsDocumentOpenInfo::DispatchCon
     aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
   }
 
   // Check whether the data should be forced to be handled externally.  This
   // could happen because the Content-Disposition header is set so, or, in the
   // future, because the user has specified external handling for the MIME
   // type.
   PRBool forceExternalHandling = PR_FALSE;
-  nsCAutoString disposition;
-  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
-  nsCOMPtr<nsIURI> uri;
-  if (httpChannel)
-  {
-    rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"),
-                                        disposition);
-    httpChannel->GetURI(getter_AddRefs(uri));
-  }
-  else
-  {
-    nsCOMPtr<nsIMultiPartChannel> multipartChannel(do_QueryInterface(request));
-    if (multipartChannel)
-    {
-      rv = multipartChannel->GetContentDisposition(disposition);
-    } else {
-      // Soon-to-be common way to get Disposition: right now only JARChannel
-      rv = NS_GetContentDisposition(request, disposition);
-    }
-  }
-
-  LOG(("  Disposition header: '%s'", disposition.get()));
-
-  if (NS_SUCCEEDED(rv) && !disposition.IsEmpty())
-  {
-    nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
-    if (NS_SUCCEEDED(rv))
-    {
-      nsCAutoString fallbackCharset;
-      if (uri)
-        uri->GetOriginCharset(fallbackCharset);
-      nsAutoString dispToken;
-      // Get the disposition type
-      rv = mimehdrpar->GetParameter(disposition, "", fallbackCharset,
-                                    PR_TRUE, nsnull, dispToken);
-      // RFC 2183, section 2.8 says that an unknown disposition
-      // value should be treated as "attachment"
-      // XXXbz this code is duplicated in GetFilenameAndExtensionFromChannel in
-      // nsExternalHelperAppService.  Factor it out!
-      if (NS_FAILED(rv) || 
-          (!dispToken.IsEmpty() &&
-           !dispToken.LowerCaseEqualsLiteral("inline") &&
-           // Broken sites just send
-           // Content-Disposition: filename="file"
-           // without a disposition token... screen those out.
-           !dispToken.EqualsIgnoreCase("filename", 8) &&
-           // Also in use is Content-Disposition: name="file"
-           !dispToken.EqualsIgnoreCase("name", 4)))
-        // We have a content-disposition of "attachment" or unknown
-        forceExternalHandling = PR_TRUE;
-    }
-  }
+  PRUint32 disposition;
+  rv = aChannel->GetContentDisposition(&disposition);
+  if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT)
+    forceExternalHandling = PR_TRUE;
 
   LOG(("  forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
     
   // We're going to try to find a contentListener that can handle our data
   nsCOMPtr<nsIURIContentListener> contentListener;
   // The type or data the contentListener wants.
   nsXPIDLCString desiredContentType;
 
@@ -571,16 +523,17 @@ nsresult nsDocumentOpenInfo::DispatchCon
     LOG(("  External handling forced or (listener not interested and no "
          "stream converter exists), and retargeting disallowed -> aborting"));
     return NS_ERROR_WONT_HANDLE_CONTENT;
   }
 
   // Before dispatching to the external helper app service, check for an HTTP
   // error page.  If we got one, we don't want to handle it with a helper app,
   // really.
+  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
   if (httpChannel) {
     PRBool requestSucceeded;
     httpChannel->GetRequestSucceeded(&requestSucceeded);
     if (!requestSucceeded) {
       // returning error from OnStartRequest will cancel the channel
       return NS_ERROR_FILE_NOT_FOUND;
     }
   }
--- a/uriloader/exthandler/ExternalHelperAppParent.cpp
+++ b/uriloader/exthandler/ExternalHelperAppParent.cpp
@@ -67,30 +67,32 @@ ExternalHelperAppParent::ExternalHelperA
   , mStatus(NS_OK)
   , mContentLength(aContentLength)
 {
 }
 
 void
 ExternalHelperAppParent::Init(ContentParent *parent,
                               const nsCString& aMimeContentType,
-                              const nsCString& aContentDisposition,
+                              const nsCString& aContentDispositionHeader,
                               const PRBool& aForceSave,
                               const IPC::URI& aReferrer)
 {
   nsHashPropertyBag::Init();
 
   nsCOMPtr<nsIExternalHelperAppService> helperAppService =
     do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID);
   NS_ASSERTION(helperAppService, "No Helper App Service!");
 
   SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH, mContentLength);
   if (aReferrer)
     SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), aReferrer);
-  SetContentDisposition(aContentDisposition);
+  mContentDispositionHeader = aContentDispositionHeader;
+  NS_GetFilenameFromDisposition(mContentDispositionFilename, mContentDispositionHeader, mURI);
+  mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
   helperAppService->DoContent(aMimeContentType, this, nsnull,
                               aForceSave, getter_AddRefs(mListener));
 }
 
 bool
 ExternalHelperAppParent::RecvOnStartRequest(const nsCString& entityID)
 {
   mEntityID = entityID;
@@ -295,16 +297,46 @@ ExternalHelperAppParent::GetContentChars
 
 NS_IMETHODIMP
 ExternalHelperAppParent::SetContentCharset(const nsACString& aContentCharset)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  if (mContentDispositionHeader.IsEmpty())
+    return NS_ERROR_NOT_AVAILABLE;
+
+  *aContentDisposition = mContentDisposition;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+  if (mContentDispositionFilename.IsEmpty())
+    return NS_ERROR_NOT_AVAILABLE;
+
+  aContentDispositionFilename = mContentDispositionFilename;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalHelperAppParent::GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+  if (mContentDispositionHeader.IsEmpty())
+    return NS_ERROR_NOT_AVAILABLE;
+
+  aContentDispositionHeader = mContentDispositionHeader;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 ExternalHelperAppParent::GetContentLength(PRInt32 *aContentLength)
 {
   if (mContentLength > PR_INT32_MAX || mContentLength < 0)
     *aContentLength = -1;
   else
     *aContentLength = (PRInt32)mContentLength;
   return NS_OK;
 }
@@ -339,30 +371,16 @@ ExternalHelperAppParent::GetEntityID(nsA
 
 NS_IMETHODIMP
 ExternalHelperAppParent::GetBaseChannel(nsIChannel* *aChannel)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-ExternalHelperAppParent::GetContentDisposition(nsACString& aContentDisposition)
-{
-  aContentDisposition = mContentDisposition;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ExternalHelperAppParent::SetContentDisposition(const nsACString& aDisposition)
-{
-  mContentDisposition = aDisposition;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 ExternalHelperAppParent::GetPartID(PRUint32* aPartID)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 ExternalHelperAppParent::GetIsLastPart(PRBool* aIsLastPart)
 {
--- a/uriloader/exthandler/ExternalHelperAppParent.h
+++ b/uriloader/exthandler/ExternalHelperAppParent.h
@@ -78,14 +78,16 @@ public:
 
 private:
   nsCOMPtr<nsIStreamListener> mListener;
   nsCOMPtr<nsIURI> mURI;
   PRBool mPending;
   nsLoadFlags mLoadFlags;
   nsresult mStatus;
   PRInt64 mContentLength;
-  nsCString mContentDisposition;
+  PRUint32 mContentDisposition;
+  nsString mContentDispositionFilename;
+  nsCString mContentDispositionHeader;
   nsCString mEntityID;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -222,76 +222,16 @@ static nsresult UnescapeFragment(const n
 {
   nsAutoString result;
   nsresult rv = UnescapeFragment(aFragment, aURI, result);
   if (NS_SUCCEEDED(rv))
     CopyUTF16toUTF8(result, aResult);
   return rv;
 }
 
-/** Gets the content-disposition header from a channel, using nsIHttpChannel
- * or nsIMultipartChannel if available
- * @param aChannel The channel to extract the disposition header from
- * @param aDisposition Reference to a string where the header is to be stored
- */
-static void ExtractDisposition(nsIChannel* aChannel, nsACString& aDisposition)
-{
-  aDisposition.Truncate();
-  // First see whether this is an http channel
-  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
-  if (httpChannel) 
-  {
-    httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"), aDisposition);
-  }
-  if (aDisposition.IsEmpty())
-  {
-    nsCOMPtr<nsIMultiPartChannel> multipartChannel(do_QueryInterface(aChannel));
-    if (multipartChannel)
-    {
-      multipartChannel->GetContentDisposition(aDisposition);
-    }
-  }
-
-}
-
-/** Extracts the filename out of a content-disposition header
- * @param aFilename [out] The filename. Can be empty on error.
- * @param aDisposition Value of a Content-Disposition header
- * @param aURI Optional. Will be used to get a fallback charset for the
- *        filename, if it is QI'able to nsIURL
- * @param aMIMEHeaderParam Optional. Pointer to a nsIMIMEHeaderParam class, so
- *        that it doesn't need to be fetched by this function.
- */
-static void GetFilenameFromDisposition(nsAString& aFilename,
-                                       const nsACString& aDisposition,
-                                       nsIURI* aURI = nsnull,
-                                       nsIMIMEHeaderParam* aMIMEHeaderParam = nsnull)
-{
-  aFilename.Truncate();
-  nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar(aMIMEHeaderParam);
-  if (!mimehdrpar) {
-    mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID);
-    if (!mimehdrpar)
-      return;
-  }
-
-  nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
-
-  nsCAutoString fallbackCharset;
-  if (url)
-    url->GetOriginCharset(fallbackCharset);
-  // Get the value of 'filename' parameter
-  nsresult rv = mimehdrpar->GetParameter(aDisposition, "filename", fallbackCharset, 
-                                         PR_TRUE, nsnull, aFilename);
-  if (NS_FAILED(rv) || aFilename.IsEmpty())
-    // Try 'name' parameter, instead.
-    rv = mimehdrpar->GetParameter(aDisposition, "name", fallbackCharset, PR_TRUE, 
-                                  nsnull, aFilename);
-}
-
 /**
  * Given a channel, returns the filename and extension the channel has.
  * This uses the URL and other sources (nsIMultiPartChannel).
  * Also gives back whether the channel requested external handling (i.e.
  * whether Content-Disposition: attachment was sent)
  * @param aChannel The channel to extract the filename/extension from
  * @param aFileName [out] Reference to the string where the filename should be
  *        stored. Empty if it could not be retrieved.
@@ -313,61 +253,29 @@ static PRBool GetFilenameAndExtensionFro
   aExtension.Truncate();
   /*
    * If the channel is an http or part of a multipart channel and we
    * have a content disposition header set, then use the file name
    * suggested there as the preferred file name to SUGGEST to the
    * user.  we shouldn't actually use that without their
    * permission... otherwise just use our temp file
    */
-  nsCAutoString disp;
-  ExtractDisposition(aChannel, disp);
   PRBool handleExternally = PR_FALSE;
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv;
-  aChannel->GetURI(getter_AddRefs(uri));
-  // content-disposition: has format:
-  // disposition-type < ; name=value >* < ; filename=value > < ; name=value >*
-  if (!disp.IsEmpty()) 
+  PRUint32 disp;
+  nsresult rv = aChannel->GetContentDisposition(&disp);
+  if (NS_SUCCEEDED(rv))
   {
-    nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
-    if (NS_FAILED(rv))
-      return PR_FALSE;
-
-    nsCAutoString fallbackCharset;
-    uri->GetOriginCharset(fallbackCharset);
-    // Get the disposition type
-    nsAutoString dispToken;
-    rv = mimehdrpar->GetParameter(disp, "", fallbackCharset, PR_TRUE, 
-                                  nsnull, dispToken);
-    // RFC 2183, section 2.8 says that an unknown disposition
-    // value should be treated as "attachment"
-    // XXXbz this code is duplicated in nsDocumentOpenInfo::DispatchContent.
-    // Factor it out!  Maybe store it in the nsDocumentOpenInfo?
-    if (NS_FAILED(rv) || 
-        (!dispToken.IsEmpty() &&
-         !dispToken.LowerCaseEqualsLiteral("inline") &&
-         // Broken sites just send
-         // Content-Disposition: filename="file"
-         // without a disposition token... screen those out.
-         !dispToken.EqualsIgnoreCase("filename", 8) &&
-         // Also in use is Content-Disposition: name="file"
-         !dispToken.EqualsIgnoreCase("name", 4)))
-    {
-      // We have a content-disposition of "attachment" or unknown
+    aChannel->GetContentDispositionFilename(aFileName);
+    if (disp == nsIChannel::DISPOSITION_ATTACHMENT)
       handleExternally = PR_TRUE;
-    }
-
-    // We may not have a disposition type listed; some servers suck.
-    // But they could have listed a filename anyway.
-    GetFilenameFromDisposition(aFileName, disp, uri, mimehdrpar);
-
-  } // we had a disp header 
+  }
 
   // If the disposition header didn't work, try the filename from nsIURL
+  nsCOMPtr<nsIURI> uri;
+  aChannel->GetURI(getter_AddRefs(uri));
   nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
   if (url && aFileName.IsEmpty())
   {
     if (aAllowURLExtension) {
       url->GetFileExtension(aExtension);
       UnescapeFragment(aExtension, url, aExtension);
 
       // Windows ignores terminating dots. So we have to as well, so
@@ -710,17 +618,17 @@ NS_IMETHODIMP nsExternalHelperAppService
     using mozilla::dom::ContentChild;
     using mozilla::dom::ExternalHelperAppChild;
     ContentChild *child = ContentChild::GetSingleton();
     if (!child)
       return NS_ERROR_FAILURE;
 
     nsCString disp;
     if (channel)
-      ExtractDisposition(channel, disp);
+      channel->GetContentDispositionHeader(disp);
 
     nsCOMPtr<nsIURI> referrer;
     rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
 
     // Now we build a protocol for forwarding our data to the parent.  The
     // protocol will act as a listener on the child-side and create a "real"
     // helperAppService listener on the parent-side, via another call to
     // DoContent.
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -242,16 +242,31 @@ NS_IMETHODIMP nsExtProtocolChannel::GetC
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::SetContentCharset(const nsACString &aContentCharset)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDisposition(PRUint32 *aContentDisposition)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 NS_IMETHODIMP nsExtProtocolChannel::GetContentLength(PRInt32 * aContentLength)
 {
   *aContentLength = -1;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsExtProtocolChannel::SetContentLength(PRInt32 aContentLength)