Back to index

lightning-sunbird  0.9+nobinonly
SaveHeaderSniffer.mm
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 "SaveHeaderSniffer.h"
00040 
00041 #include "netCore.h"
00042 
00043 #include "nsIChannel.h"
00044 #include "nsIHttpChannel.h"
00045 #include "nsIURL.h"
00046 #include "nsIPrefService.h"
00047 #include "nsIMIMEService.h"
00048 #include "nsIMIMEInfo.h"
00049 #include "nsIDOMHTMLDocument.h"
00050 #include "nsIMIMEHeaderParam.h"
00051 #include "nsIDownload.h"
00052 #include "nsIStringEnumerator.h"
00053 
00054 const char* const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
00055 
00056 nsHeaderSniffer::nsHeaderSniffer(nsIWebBrowserPersist* aPersist, nsIFile* aFile, nsIURI* aURL,
00057                 nsIDOMDocument* aDocument, nsIInputStream* aPostData,
00058                 const nsCString& aSuggestedFilename, PRBool aBypassCache,
00059                 NSView* aFilterView, NSPopUpButton* aFilterList)
00060 : mPersist(aPersist)
00061 , mTmpFile(aFile)
00062 , mURL(aURL)
00063 , mDocument(aDocument)
00064 , mPostData(aPostData)
00065 , mDefaultFilename(aSuggestedFilename)
00066 , mBypassCache(aBypassCache)
00067 , mFilterView(aFilterView)
00068 , mFilterList(aFilterList)
00069 {
00070 }
00071 
00072 nsHeaderSniffer::~nsHeaderSniffer()
00073 {
00074 }
00075 
00076 NS_IMPL_ISUPPORTS1(nsHeaderSniffer, nsIWebProgressListener)
00077 
00078 #pragma mark -
00079 
00080 // Implementation of nsIWebProgressListener
00081 /* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aStateFlags, in unsigned long aStatus); */
00082 NS_IMETHODIMP 
00083 nsHeaderSniffer::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 aStateFlags, 
00084                                 PRUint32 aStatus)
00085 {  
00086   if (aStateFlags & nsIWebProgressListener::STATE_START)
00087   {
00088     nsCOMPtr<nsIWebBrowserPersist> kungFuDeathGrip(mPersist);   // be sure to keep it alive while we save
00089                                                                 // since it owns us as a listener
00090     nsCOMPtr<nsIWebProgressListener> kungFuSuicideGrip(this);   // and keep ourslves alive
00091     
00092     nsresult rv;
00093     nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv);
00094     if (!channel) return rv;
00095     channel->GetContentType(mContentType);
00096     
00097     nsCOMPtr<nsIURI> origURI;
00098     channel->GetOriginalURI(getter_AddRefs(origURI));
00099     
00100     // Get the content-disposition if we're an HTTP channel.
00101     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
00102     if (httpChannel)
00103       httpChannel->GetResponseHeader(nsCAutoString("content-disposition"), mContentDisposition);
00104     
00105     mPersist->CancelSave();
00106     PRBool exists;
00107     mTmpFile->Exists(&exists);
00108     if (exists)
00109         mTmpFile->Remove(PR_FALSE);
00110 
00111     rv = PerformSave(origURI);
00112     if (NS_FAILED(rv))
00113     {
00114       // put up some UI
00115       NSLog(@"Error saving web page");
00116     }
00117   }
00118   return NS_OK;
00119 }
00120 
00121 /* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */
00122 NS_IMETHODIMP 
00123 nsHeaderSniffer::OnProgressChange(nsIWebProgress *aWebProgress, 
00124            nsIRequest *aRequest, 
00125            PRInt32 aCurSelfProgress, 
00126            PRInt32 aMaxSelfProgress, 
00127            PRInt32 aCurTotalProgress, 
00128            PRInt32 aMaxTotalProgress)
00129 {
00130   return NS_OK;
00131 }
00132 
00133 /* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location); */
00134 NS_IMETHODIMP 
00135 nsHeaderSniffer::OnLocationChange(nsIWebProgress *aWebProgress, 
00136            nsIRequest *aRequest, 
00137            nsIURI *location)
00138 {
00139   return NS_OK;
00140 }
00141 
00142 /* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
00143 NS_IMETHODIMP 
00144 nsHeaderSniffer::OnStatusChange(nsIWebProgress *aWebProgress, 
00145                nsIRequest *aRequest, 
00146                nsresult aStatus, 
00147                const PRUnichar *aMessage)
00148 {
00149   return NS_OK;
00150 }
00151 
00152 /* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */
00153 NS_IMETHODIMP 
00154 nsHeaderSniffer::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 state)
00155 {
00156   return NS_OK;
00157 }
00158 
00159 #pragma mark -
00160 
00161 
00162 nsresult nsHeaderSniffer::PerformSave(nsIURI* inOriginalURI)
00163 {
00164     nsresult rv;
00165     // Are we an HTML document? If so, we will want to append an accessory view to
00166     // the save dialog to provide the user with the option of doing a complete
00167     // save vs. a single file save.
00168     PRBool isHTML = (mDocument && mContentType.Equals("text/html") ||
00169                      mContentType.Equals("text/xml") ||
00170                      mContentType.Equals("application/xhtml+xml"));
00171     
00172     // Next find out the directory that we should start in.
00173     nsCOMPtr<nsIPrefService> prefs(do_GetService("@mozilla.org/preferences-service;1", &rv));
00174     if (!prefs)
00175         return rv;
00176     nsCOMPtr<nsIPrefBranch> dirBranch;
00177     prefs->GetBranch("browser.download.", getter_AddRefs(dirBranch));
00178     PRInt32 filterIndex = 0;
00179     if (dirBranch) {
00180         nsresult rv = dirBranch->GetIntPref("save_converter_index", &filterIndex);
00181         if (NS_FAILED(rv))
00182             filterIndex = 0;
00183     }
00184     if (mFilterList)
00185         [mFilterList selectItemAtIndex: filterIndex];
00186         
00187     // We need to figure out what file name to use.
00188     nsCAutoString defaultFileName;
00189     // (1) Use the HTTP header suggestion.
00190     if (!mContentDisposition.IsEmpty()) {
00191         nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = 
00192             do_GetService("@mozilla.org/network/mime-hdrparam;1");
00193         if (mimehdrpar) {
00194             nsCAutoString fallbackCharset;
00195             if (mURL)
00196                 mURL->GetOriginCharset(fallbackCharset);
00197             nsAutoString fileName;
00198             rv = mimehdrpar->GetParameter(mContentDisposition, "filename",
00199                                           fallbackCharset, PR_TRUE, nsnull,
00200                                           fileName);
00201             if (NS_FAILED(rv) || fileName.IsEmpty())
00202                rv = mimehdrpar->GetParameter(mContentDisposition, "name",
00203                                              fallbackCharset, PR_TRUE, nsnull,
00204                                              fileName);
00205             if (NS_SUCCEEDED(rv) && !fileName.IsEmpty())
00206                 CopyUTF16toUTF8(fileName, defaultFileName);
00207         }
00208     }
00209     
00210     if (defaultFileName.IsEmpty()) {
00211         nsCOMPtr<nsIURL> url(do_QueryInterface(mURL));
00212         if (url)
00213             url->GetFileName(defaultFileName); // (2) For file URLs, use the file name.
00214     }
00215     
00216     if (defaultFileName.IsEmpty() && mDocument && isHTML) {
00217         nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(mDocument));
00218         nsAutoString title;
00219         if (htmlDoc)
00220             htmlDoc->GetTitle(title); // (3) Use the title of the document.
00221         CopyUTF16toUTF8(title, defaultFileName);
00222     }
00223     
00224     if (defaultFileName.IsEmpty()) {
00225         // (4) Use the caller provided name.
00226         defaultFileName = mDefaultFilename;
00227     }
00228 
00229     if (defaultFileName.IsEmpty() && mURL)
00230         // (5) Use the host.
00231         mURL->GetHost(defaultFileName);
00232     
00233     // One last case to handle about:blank and other fruity untitled pages.
00234     if (defaultFileName.IsEmpty())
00235         defaultFileName = "untitled";
00236         
00237     // Validate the file name to ensure legality.
00238     for (PRUint32 i = 0; i < defaultFileName.Length(); i++)
00239         if (defaultFileName[i] == ':' || defaultFileName[i] == '/')
00240             defaultFileName.SetCharAt(i, ' ');
00241             
00242     // Make sure the appropriate extension is appended to the suggested file name.
00243     nsCOMPtr<nsIURI> fileURI(do_CreateInstance("@mozilla.org/network/standard-url;1"));
00244     nsCOMPtr<nsIURL> fileURL(do_QueryInterface(fileURI, &rv));
00245     if (!fileURL)
00246         return rv;
00247     fileURL->SetFilePath(defaultFileName);
00248     
00249     nsCAutoString fileExtension;
00250     fileURL->GetFileExtension(fileExtension);
00251     
00252     PRBool setExtension = PR_FALSE;
00253     if (mContentType.Equals("text/html")) {
00254         if (fileExtension.IsEmpty() || (!fileExtension.Equals("htm") && !fileExtension.Equals("html"))) {
00255             defaultFileName += ".html";
00256             setExtension = PR_TRUE;
00257         }
00258     }
00259     
00260     if (!setExtension && fileExtension.IsEmpty()) {
00261         nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1", &rv));
00262         if (!mimeService)
00263             return rv;
00264         nsCOMPtr<nsIMIMEInfo> mimeInfo;
00265         rv = mimeService->GetFromTypeAndExtension(mContentType, EmptyCString(), getter_AddRefs(mimeInfo));
00266         if (!mimeInfo)
00267           return rv;
00268 
00269         nsCOMPtr<nsIUTF8StringEnumerator> extensions;
00270         mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
00271         
00272         PRBool hasMore;
00273         extensions->HasMore(&hasMore);
00274         if (hasMore) {
00275             nsCAutoString ext;
00276             extensions->GetNext(ext);
00277             defaultFileName.Append('.');
00278             defaultFileName.Append(ext);
00279         }
00280     }
00281     
00282     // Now it's time to pose the save dialog.
00283     NSSavePanel* savePanel = [NSSavePanel savePanel];
00284     NSString* file = nil;
00285     if (!defaultFileName.IsEmpty())
00286         file = [[NSString alloc] initWithCString: defaultFileName.get()];
00287         
00288     if (isHTML)
00289         [savePanel setAccessoryView: mFilterView];
00290         
00291     if ([savePanel runModalForDirectory: nil file: file] == NSFileHandlingPanelCancelButton)
00292         return NS_OK;
00293        
00294     // Release the file string.
00295     [file release];
00296     
00297     // Update the filter index.
00298     if (isHTML && mFilterList) {
00299         filterIndex = [mFilterList indexOfSelectedItem];
00300         dirBranch->SetIntPref("save_converter_index", filterIndex);
00301     }
00302     
00303     // Convert the content type to text/plain if it was selected in the filter.
00304     if (isHTML && filterIndex == 2)
00305         mContentType = "text/plain";
00306     
00307     nsCOMPtr<nsISupports> sourceData;
00308     if (isHTML && filterIndex != 1)
00309         sourceData = do_QueryInterface(mDocument);
00310     else
00311         sourceData = do_QueryInterface(mURL);
00312 
00313     NSString* destName = [savePanel filename];
00314     
00315     PRUint32 dstLen = [destName length];
00316     PRUnichar* tmp = new PRUnichar[dstLen + sizeof(PRUnichar)];
00317     tmp[dstLen] = (PRUnichar)'\0';
00318     [destName getCharacters:tmp];
00319     nsAutoString dstString(tmp);
00320     delete tmp;
00321 
00322     return InitiateDownload(sourceData, dstString, inOriginalURI);
00323 }
00324 
00325 // inOriginalURI is always a URI. inSourceData can be an nsIURI or an nsIDOMDocument, depending
00326 // on what we're saving. It's that way for nsIWebBrowserPersist.
00327 nsresult nsHeaderSniffer::InitiateDownload(nsISupports* inSourceData, nsString& inFileName, nsIURI* inOriginalURI)
00328 {
00329   nsresult rv = NS_OK;
00330 
00331   nsCOMPtr<nsIWebBrowserPersist> webPersist = do_CreateInstance(persistContractID, &rv);
00332   if (NS_FAILED(rv)) return rv;
00333   
00334   nsCOMPtr<nsIURI> sourceURI = do_QueryInterface(inSourceData);
00335 
00336   nsCOMPtr<nsILocalFile> destFile;
00337   rv = NS_NewLocalFile(inFileName, PR_FALSE, getter_AddRefs(destFile));
00338   if (NS_FAILED(rv)) return rv;
00339 
00340   PRInt64 timeNow = PR_Now();
00341   
00342   nsCOMPtr<nsIDownload> downloader = do_CreateInstance(NS_TRANSFER_CONTRACTID);
00343   // dlListener attaches to its progress dialog here, which gains ownership
00344   rv = downloader->Init(inOriginalURI, destFile, inFileName, nsnull, timeNow, webPersist);
00345   if (NS_FAILED(rv)) return rv;
00346 
00347   webPersist->SetProgressListener(downloader);
00348     
00349   PRInt32 flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | 
00350                   nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES;
00351   if (mBypassCache)
00352     flags |= nsIWebBrowserPersist::PERSIST_FLAGS_BYPASS_CACHE;
00353   else
00354     flags |= nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE;
00355   
00356   webPersist->SetPersistFlags(flags);
00357   
00358   if (sourceURI)
00359   {
00360     rv = webPersist->SaveURI(sourceURI, nsnull, nsnull, mPostData, nsnull, destFile);
00361   }
00362   else
00363   {
00364     nsCOMPtr<nsIDOMHTMLDocument> domDoc = do_QueryInterface(inSourceData, &rv);
00365     if (!domDoc) return rv;  // should never happen
00366     
00367     PRInt32 encodingFlags = 0;
00368     nsCOMPtr<nsILocalFile> filesFolder;
00369     
00370     if (!mContentType.Equals("text/plain")) {
00371       // Create a local directory in the same dir as our file.  It
00372       // will hold our associated files.
00373       filesFolder = do_CreateInstance("@mozilla.org/file/local;1");
00374       nsAutoString unicodePath;
00375       destFile->GetPath(unicodePath);
00376       filesFolder->InitWithPath(unicodePath);
00377       
00378       nsAutoString leafName;
00379       filesFolder->GetLeafName(leafName);
00380       nsAutoString nameMinusExt(leafName);
00381       PRInt32 index = nameMinusExt.RFind(".");
00382       if (index >= 0)
00383           nameMinusExt.Left(nameMinusExt, index);
00384       nameMinusExt += NS_LITERAL_STRING(" Files"); // XXXdwh needs to be localizable!
00385       filesFolder->SetLeafName(nameMinusExt);
00386       PRBool exists = PR_FALSE;
00387       filesFolder->Exists(&exists);
00388       if (!exists) {
00389           rv = filesFolder->Create(nsILocalFile::DIRECTORY_TYPE, 0755);
00390           if (NS_FAILED(rv))
00391             return rv;
00392       }
00393     }
00394     else
00395     {
00396       encodingFlags |= nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED |
00397                         nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS |
00398                         nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT;
00399     }
00400     rv = webPersist->SaveDocument(domDoc, destFile, filesFolder, mContentType.get(), encodingFlags, 80);
00401   }
00402   
00403   return rv;
00404 }