Back to index

lightning-sunbird  0.9+nobinonly
CHeaderSniffer.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is Chimera code.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * Netscape Communications Corporation.
00018  * Portions created by the Initial Developer are Copyright (C) 2002
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   David Hyatt  <hyatt@netscape.com>
00023  *   Simon Fraser <sfraser@netscape.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "CHeaderSniffer.h"
00040 #include "UMacUnicode.h"
00041 
00042 #include "UCustomNavServicesDialogs.h"
00043 
00044 #include "netCore.h"
00045 
00046 #include "nsIChannel.h"
00047 #include "nsIHttpChannel.h"
00048 #include "nsIURL.h"
00049 #include "nsIStringEnumerator.h"
00050 #include "nsIPrefService.h"
00051 #include "nsIMIMEService.h"
00052 #include "nsIMIMEInfo.h"
00053 #include "nsIDOMHTMLDocument.h"
00054 #include "nsIDownload.h"
00055 #include "nsILocalFileMac.h"
00056 
00057 const char* const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
00058 
00059 CHeaderSniffer::CHeaderSniffer(nsIWebBrowserPersist* aPersist, nsIFile* aFile, nsIURI* aURL,
00060                 nsIDOMDocument* aDocument, nsIInputStream* aPostData,
00061                 const nsAString& aSuggestedFilename, PRBool aBypassCache, ESaveFormat aSaveFormat)
00062 : mPersist(aPersist)
00063 , mTmpFile(aFile)
00064 , mURL(aURL)
00065 , mDocument(aDocument)
00066 , mPostData(aPostData)
00067 , mDefaultFilename(aSuggestedFilename)
00068 , mBypassCache(aBypassCache)
00069 , mSaveFormat(aSaveFormat)
00070 {
00071 }
00072 
00073 CHeaderSniffer::~CHeaderSniffer()
00074 {
00075 }
00076 
00077 NS_IMPL_ISUPPORTS1(CHeaderSniffer, nsIWebProgressListener)
00078 
00079 #pragma mark -
00080 
00081 // Implementation of nsIWebProgressListener
00082 /* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aStateFlags, in unsigned long aStatus); */
00083 NS_IMETHODIMP 
00084 CHeaderSniffer::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 aStateFlags, 
00085                                 PRUint32 aStatus)
00086 {  
00087   if (aStateFlags & nsIWebProgressListener::STATE_START)
00088   {
00089     nsCOMPtr<nsIWebBrowserPersist> kungFuDeathGrip(mPersist);   // be sure to keep it alive while we save
00090                                                                 // since it owns us as a listener
00091     nsCOMPtr<nsIWebProgressListener> kungFuSuicideGrip(this);   // and keep ourselves alive
00092     
00093     nsresult rv;
00094     nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv);
00095     if (!channel) return rv;
00096     channel->GetContentType(mContentType);
00097     
00098     nsCOMPtr<nsIURI> origURI;
00099     channel->GetOriginalURI(getter_AddRefs(origURI));
00100     
00101     // Get the content-disposition if we're an HTTP channel.
00102     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
00103     if (httpChannel)
00104       httpChannel->GetResponseHeader(nsCAutoString("content-disposition"), mContentDisposition);
00105     
00106     mPersist->CancelSave();
00107     PRBool exists;
00108     mTmpFile->Exists(&exists);
00109     if (exists)
00110         mTmpFile->Remove(PR_FALSE);
00111 
00112     rv = PerformSave(origURI, mSaveFormat);
00113     if (NS_FAILED(rv))
00114     {
00115       // put up some UI
00116       
00117     }
00118   }
00119   return NS_OK;
00120 }
00121 
00122 /* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */
00123 NS_IMETHODIMP 
00124 CHeaderSniffer::OnProgressChange(nsIWebProgress *aWebProgress, 
00125            nsIRequest *aRequest, 
00126            PRInt32 aCurSelfProgress, 
00127            PRInt32 aMaxSelfProgress, 
00128            PRInt32 aCurTotalProgress, 
00129            PRInt32 aMaxTotalProgress)
00130 {
00131   return NS_OK;
00132 }
00133 
00134 /* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location); */
00135 NS_IMETHODIMP 
00136 CHeaderSniffer::OnLocationChange(nsIWebProgress *aWebProgress, 
00137            nsIRequest *aRequest, 
00138            nsIURI *location)
00139 {
00140   return NS_OK;
00141 }
00142 
00143 /* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
00144 NS_IMETHODIMP 
00145 CHeaderSniffer::OnStatusChange(nsIWebProgress *aWebProgress, 
00146                nsIRequest *aRequest, 
00147                nsresult aStatus, 
00148                const PRUnichar *aMessage)
00149 {
00150   return NS_OK;
00151 }
00152 
00153 /* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */
00154 NS_IMETHODIMP 
00155 CHeaderSniffer::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 state)
00156 {
00157   return NS_OK;
00158 }
00159 
00160 #pragma mark -
00161 
00162 static ESaveFormat SaveFormatFromPrefValue(PRInt32 inPrefValue)
00163 {
00164   switch (inPrefValue)
00165   {
00166     case 0:   return eSaveFormatHTMLComplete;
00167     default:  // fall through
00168     case 1:   return eSaveFormatHTML;
00169     case 2:   return eSaveFormatPlainText;
00170   }
00171 }
00172 
00173 static PRInt32 PrefValueFromSaveFormat(ESaveFormat inSaveFormat)
00174 {
00175   switch (inSaveFormat)
00176   {
00177     case eSaveFormatPlainText:    return 2;
00178     default:  // fall through
00179     case eSaveFormatHTML:         return 1;
00180     case eSaveFormatHTMLComplete: return 0;
00181   }
00182 }
00183 
00184 nsresult CHeaderSniffer::PerformSave(nsIURI* inOriginalURI, const ESaveFormat inSaveFormat)
00185 {
00186     nsresult rv;
00187     // Are we an HTML document? If so, we will want to append an accessory view to
00188     // the save dialog to provide the user with the option of doing a complete
00189     // save vs. a single file save.
00190     PRBool isHTML = (mDocument && mContentType.Equals("text/html") ||
00191                      mContentType.Equals("text/xml") ||
00192                      mContentType.Equals("application/xhtml+xml"));
00193     
00194     // Next find out the directory that we should start in.
00195     nsCOMPtr<nsIPrefService> prefs(do_GetService("@mozilla.org/preferences-service;1", &rv));
00196     if (!prefs)
00197         return rv;
00198     nsCOMPtr<nsIPrefBranch> dirBranch;
00199     prefs->GetBranch("browser.download.", getter_AddRefs(dirBranch));
00200     PRInt32 filterIndex = 0;
00201     if (inSaveFormat != eSaveFormatUnspecified) {
00202       filterIndex = PrefValueFromSaveFormat(inSaveFormat);
00203     }
00204     else if (dirBranch) {
00205         nsresult rv = dirBranch->GetIntPref("save_converter_index", &filterIndex);
00206         if (NS_FAILED(rv))
00207             filterIndex = 0;
00208     }
00209 
00210     // We need to figure out what file name to use.
00211     nsAutoString defaultFileName;
00212     if (!mContentDisposition.IsEmpty()) {
00213         // (1) Use the HTTP header suggestion.
00214         PRInt32 index = mContentDisposition.Find("filename=");
00215         if (index >= 0) {
00216             // Take the substring following the prefix.
00217             index += 9;
00218             AppendUTF8toUTF16(Substring(mContentDisposition, index),
00219                               defaultFileName);
00220         }
00221     }
00222     
00223     if (defaultFileName.IsEmpty()) {
00224         nsCOMPtr<nsIURL> url(do_QueryInterface(mURL));
00225         if (url) {
00226             nsCAutoString fileNameCString;
00227             url->GetFileName(fileNameCString); // (2) For file URLs, use the file name.
00228             AppendUTF8toUTF16(fileNameCString, defaultFileName);
00229         }
00230     }
00231     
00232     if (defaultFileName.IsEmpty() && mDocument && isHTML) {
00233         nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(mDocument));
00234         if (htmlDoc)
00235             htmlDoc->GetTitle(defaultFileName); // (3) Use the title of the document.
00236     }
00237     
00238     if (defaultFileName.IsEmpty()) {
00239         // (4) Use the caller provided name.
00240         defaultFileName = mDefaultFilename;
00241     }
00242 
00243     if (defaultFileName.IsEmpty() && mURL) {
00244         // (5) Use the host.
00245         nsCAutoString hostName;
00246         mURL->GetHost(hostName);
00247         AppendUTF8toUTF16(hostName, defaultFileName);
00248     }
00249     
00250     // One last case to handle about:blank and other fruity untitled pages.
00251     if (defaultFileName.IsEmpty())
00252         defaultFileName.AssignLiteral("untitled");
00253         
00254     // Validate the file name to ensure legality.
00255     for (PRUint32 i = 0; i < defaultFileName.Length(); i++)
00256         if (defaultFileName[i] == ':' || defaultFileName[i] == '/')
00257             defaultFileName.SetCharAt(i, PRUnichar(' '));
00258             
00259     // Make sure the appropriate extension is appended to the suggested file name.
00260     nsCOMPtr<nsIURI> fileURI(do_CreateInstance("@mozilla.org/network/standard-url;1"));
00261     nsCOMPtr<nsIURL> fileURL(do_QueryInterface(fileURI, &rv));
00262     if (!fileURL)
00263         return rv;
00264      
00265     fileURL->SetFilePath(NS_ConvertUCS2toUTF8(defaultFileName));
00266     
00267     nsCAutoString fileExtension;
00268     fileURL->GetFileExtension(fileExtension);
00269     
00270     PRBool setExtension = PR_FALSE;
00271     if (mContentType.Equals("text/html")) {
00272         if (fileExtension.IsEmpty() || (!fileExtension.Equals("htm") && !fileExtension.Equals("html"))) {
00273             defaultFileName.AppendLiteral(".html");
00274             setExtension = PR_TRUE;
00275         }
00276     }
00277     
00278     if (!setExtension && fileExtension.IsEmpty()) {
00279         nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1", &rv));
00280         if (!mimeService)
00281             return rv;
00282         nsCOMPtr<nsIMIMEInfo> mimeInfo;
00283         rv = mimeService->GetFromTypeAndExtension(mContentType, EmptyCString(), getter_AddRefs(mimeInfo));
00284         if (!mimeInfo)
00285           return rv;
00286 
00287         nsCOMPtr<nsIUTF8StringEnumerator> extensions;
00288         mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
00289         
00290         PRBool hasMore;
00291         extensions->HasMore(&hasMore);
00292         if (hasMore) {
00293             nsCAutoString ext;
00294             extensions->GetNext(ext);
00295             defaultFileName.Append(PRUnichar('.'));
00296             AppendUTF8toUTF16(ext, defaultFileName);
00297         }
00298     }
00299 
00300     // Now it's time to pose the save dialog.
00301     FSSpec       destFileSpec;
00302     bool         isReplacing = false;
00303 
00304     {
00305         Str255          defaultName;
00306         bool            result;
00307 
00308         CPlatformUCSConversion::GetInstance()->UCSToPlatform(defaultFileName, defaultName);
00309 #ifndef XP_MACOSX
00310         char            tempBuf1[256], tempBuf2[64];
00311         ::CopyPascalStringToC(defaultName, tempBuf1);        
00312         ::CopyCStringToPascal(NS_TruncNodeName(tempBuf1, tempBuf2), defaultName);
00313 #endif
00314         if (isHTML) {
00315             ESaveFormat saveFormat = SaveFormatFromPrefValue(filterIndex);
00316             UNavServicesDialogs::LCustomFileDesignator customDesignator;
00317             result = customDesignator.AskDesignateFile(defaultName, saveFormat);
00318             if (!result)
00319                 return NS_OK;       // user canceled
00320             filterIndex = PrefValueFromSaveFormat(saveFormat);
00321             customDesignator.GetFileSpec(destFileSpec);
00322             isReplacing = customDesignator.IsReplacing();
00323         }
00324         else {
00325             UNavServicesDialogs::LFileDesignator stdDesignator;
00326             result = stdDesignator.AskDesignateFile(defaultName);
00327             if (!result)
00328                 return NS_OK;       // user canceled
00329             stdDesignator.GetFileSpec(destFileSpec);
00330             isReplacing = stdDesignator.IsReplacing();        
00331         }
00332 
00333         // After the dialog is dismissed, process all activation an update events right away.
00334         // The save dialog code calls UDesktop::Activate after dismissing the dialog. All that
00335         // does is activate the now frontmost LWindow which was behind the dialog. It does not
00336         // remove the activate event from the queue. If that event is not processed and removed
00337         // before we show the progress window, bad things happen. Specifically, the progress
00338         // dialog will show in front and then, shortly thereafter, the window which was behind this save
00339         // dialog will be moved to the front.
00340         
00341         if (LEventDispatcher::GetCurrentEventDispatcher()) { // Can this ever be NULL?
00342             EventRecord theEvent;
00343             while (::WaitNextEvent(updateMask | activMask, &theEvent, 0, nil))
00344                 LEventDispatcher::GetCurrentEventDispatcher()->DispatchEvent(theEvent);
00345         }
00346     }
00347 
00348     // only save the pref if the frontend didn't specify a format
00349     if (inSaveFormat == eSaveFormatUnspecified && isHTML)
00350       dirBranch->SetIntPref("save_converter_index", filterIndex);
00351 
00352     nsCOMPtr<nsILocalFileMac> destFile;
00353     
00354     rv = NS_NewLocalFileWithFSSpec(&destFileSpec, PR_TRUE, getter_AddRefs(destFile));
00355     if (NS_FAILED(rv))
00356       return rv;
00357     
00358     if (isReplacing) {
00359       PRBool exists;
00360       rv = destFile->Exists(&exists);
00361       if (NS_SUCCEEDED(rv) && exists)
00362         rv = destFile->Remove(PR_TRUE);
00363       if (NS_FAILED(rv))
00364         return rv;
00365     }
00366 
00367     // if the user chose plain text, set the content type
00368     if (isHTML && filterIndex == 2)
00369       mContentType = "text/plain";
00370     
00371     nsCOMPtr<nsISupports> sourceData;
00372     if (isHTML && filterIndex != 1)
00373         sourceData = do_QueryInterface(mDocument);    // HTML complete
00374     else
00375         sourceData = do_QueryInterface(mURL);         // HTML or text only
00376 
00377     return InitiateDownload(sourceData, destFile, inOriginalURI);
00378 }
00379 
00380 // inOriginalURI is always a URI. inSourceData can be an nsIURI or an nsIDOMDocument, depending
00381 // on what we're saving. It's that way for nsIWebBrowserPersist.
00382 nsresult CHeaderSniffer::InitiateDownload(nsISupports* inSourceData, nsILocalFile* inDestFile, nsIURI* inOriginalURI)
00383 {
00384   nsresult rv = NS_OK;
00385 
00386   nsCOMPtr<nsIWebBrowserPersist> webPersist = do_CreateInstance(persistContractID, &rv);
00387   if (NS_FAILED(rv)) return rv;
00388   
00389   nsCOMPtr<nsIURI> sourceURI = do_QueryInterface(inSourceData);
00390 
00391   PRInt64 timeNow = PR_Now();
00392   
00393   nsAutoString fileDisplayName;
00394   inDestFile->GetLeafName(fileDisplayName);
00395   
00396   nsCOMPtr<nsIDownload> downloader = do_CreateInstance(NS_TRANSFER_CONTRACTID);
00397   // dlListener attaches to its progress dialog here, which gains ownership
00398   rv = downloader->Init(inOriginalURI, inDestFile, fileDisplayName.get(), nsnull, timeNow, webPersist);
00399   if (NS_FAILED(rv)) return rv;
00400     
00401   PRInt32 flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | 
00402                   nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES;
00403   if (mBypassCache)
00404     flags |= nsIWebBrowserPersist::PERSIST_FLAGS_BYPASS_CACHE;
00405   else
00406     flags |= nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE;
00407 
00408   webPersist->SetPersistFlags(flags);
00409     
00410   if (sourceURI)
00411   {
00412     rv = webPersist->SaveURI(sourceURI, nsnull, nsnull, mPostData, nsnull, inDestFile);
00413   }
00414   else
00415   {
00416     nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(inSourceData, &rv);
00417     if (!domDoc) return rv;  // should never happen
00418     
00419     PRInt32 encodingFlags = 0;
00420     nsCOMPtr<nsILocalFile> filesFolder;
00421     
00422     if (!mContentType.Equals("text/plain")) {
00423       // Create a local directory in the same dir as our file.  It
00424       // will hold our associated files.
00425       filesFolder = do_CreateInstance("@mozilla.org/file/local;1");
00426       nsAutoString unicodePath;
00427       inDestFile->GetPath(unicodePath);
00428       filesFolder->InitWithPath(unicodePath);
00429       
00430       nsAutoString leafName;
00431       filesFolder->GetLeafName(leafName);
00432       nsAutoString nameMinusExt(leafName);
00433       PRInt32 index = nameMinusExt.RFind(".");
00434       if (index >= 0)
00435           nameMinusExt.Left(nameMinusExt, index);
00436       nameMinusExt.AppendLiteral(" Files"); // XXXdwh needs to be localizable!
00437       filesFolder->SetLeafName(nameMinusExt);
00438       PRBool exists = PR_FALSE;
00439       filesFolder->Exists(&exists);
00440       if (!exists) {
00441           rv = filesFolder->Create(nsILocalFile::DIRECTORY_TYPE, 0755);
00442           if (NS_FAILED(rv))
00443             return rv;
00444       }
00445     }
00446     else
00447     {
00448       encodingFlags |= nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED |
00449                         nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS |
00450                         nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT;
00451     }
00452     rv = webPersist->SaveDocument(domDoc, inDestFile, filesFolder, mContentType.get(), encodingFlags, 80);
00453   }
00454   
00455   return rv;
00456 }