Logo Search packages:      
Sourcecode: galeon version File versions

HeaderSniffer.cpp

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Chimera code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   David Hyatt  <hyatt@netscape.com>
 *   Simon Fraser <sfraser@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK *****
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "ProgressListener.h"
#include "HeaderSniffer.h"
#include "netCore.h"

#include "galeon-embed-shell.h"
#include "gul-file-chooser.h"
#include "gul-gui.h"
#include "gul-x11.h"
#include "eel-gconf-extensions.h"
#include "galeon-debug.h"
#include "prefs-strings.h"
#include "hig-alert.h"

#include <nsIChannel.h>
#include <nsIHttpChannel.h>
#include <nsIEncodedChannel.h>
#include <nsIURIChecker.h>
#include <nsIURL.h>
#include <nsIStringEnumerator.h>
#include <nsIMIMEService.h>
#include <nsIMIMEInfo.h>
#include <nsIDOMHTMLDocument.h>
#include <nsIDownload.h>
#include <nsIWindowWatcher.h>
#include <nsNetCID.h>
#include <nsIDOMDocument.h>
#include <nsIDOMNSDocument.h>
#include <nsIMIMEHeaderParam.h>
#include <nsIExternalHelperAppService.h>
#include <nsCExternalHandlerService.h>
#include <nsILocalFile.h>
#include <nsIInputStream.h>
#include <nsMemory.h>

#include <glib/gi18n.h>
#include <libgnomevfs/gnome-vfs-utils.h>

HeaderSniffer::HeaderSniffer (GaleonEmbedPersist *aEmbedPersist,
                        nsIURI* aURL, nsIDOMDocument* aDocument, 
                        nsIInputStream* aPostData,
                        nsISupports *aCacheKey)
: mEmbedPersist(aEmbedPersist)
, mOriginalURL(aURL)
, mFinalURL (nsnull)
, mDocument(aDocument)
, mPostData(aPostData)
, mCacheKey(aCacheKey)
{
      nsCOMPtr<nsIWindowWatcher> watcher
            (do_GetService("@mozilla.org/embedcomp/window-watcher;1"));
      if (!watcher) return;

      watcher->GetNewAuthPrompter (nsnull, getter_AddRefs (mAuthPrompt));
      g_object_ref (mEmbedPersist);

      mShouldDecode = PR_FALSE;
      LOG ("HeaderSniffer ctor (%p)", this);
}

HeaderSniffer::~HeaderSniffer()
{
      LOG ("HeaderSniffer dtor (%p)", this);
      g_object_unref (mEmbedPersist);
}

NS_IMPL_ISUPPORTS2(HeaderSniffer, nsIRequestObserver, nsIAuthPrompt)

NS_IMETHODIMP
HeaderSniffer::OnStartRequest (nsIRequest *aRequest, nsISupports *aContext)
{
      LOG ("HeaderSniffer::OnStartRequest");
      return NS_OK;
}


NS_IMETHODIMP 
HeaderSniffer::OnStopRequest (nsIRequest *aRequest, 
                        nsISupports *aContext, nsresult aStatusCode)
{  
      nsresult rv;
      LOG ("HeaderSniffer::OnStopRequest");

      if (aStatusCode != NS_BINDING_SUCCEEDED)
      {
            GtkWidget *parent, *dialog;

            parent = galeon_embed_persist_get_fc_parent (mEmbedPersist);

            dialog = hig_alert_new (parent ? GTK_WINDOW (parent) : NULL,
                              GTK_DIALOG_DESTROY_WITH_PARENT,
                              HIG_ALERT_ERROR,
                              _("Unable to save link."),
                              _("The web page might have been removed "
                                "or had its name changed."),
                              GTK_STOCK_OK,
                              GTK_RESPONSE_OK,
                              NULL);
            g_signal_connect (dialog, "response",
                          (GCallback)gtk_widget_destroy, NULL);

            gtk_widget_show (dialog);
            return NS_OK;
      }

      nsCOMPtr<nsIURIChecker> checker = do_QueryInterface (aRequest);
      NS_ENSURE_TRUE (checker, NS_ERROR_FAILURE);

      nsCOMPtr<nsIChannel> channel;
      checker->GetBaseChannel (getter_AddRefs(channel));
      NS_ENSURE_TRUE (channel, NS_ERROR_FAILURE);

      /* Get the final URL of the request */
      channel->GetURI (getter_AddRefs(mFinalURL));

      /* Get the Content-Disposition header, it might give us a 
       * hint on the filename */
      nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));

      GulCString contentDisposition;
      if (httpChannel)
      {
            httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"),
                                     contentDisposition);
      }

      /* Get the document encoding */
      nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(channel));
      GulCString contentEncoding;
      if (encodedChannel)
      {
            nsCOMPtr<nsIUTF8StringEnumerator> enumerator;
            encodedChannel->GetContentEncodings (getter_AddRefs (enumerator));

            if (enumerator)
            {
                  PRBool more = PR_FALSE;
                  enumerator->HasMore (&more);
                  
                  if (more)
                  {
                        enumerator->GetNext (contentEncoding);
                  }
            }
      }

      /* Get the Content-Type header */
      GulCString contentType;
      channel->GetContentType(contentType);
      
      if (contentType.Equals ("application/x-unknown-content-type"))
      {
            contentType = "";
      }

      /* If no Content-Type, try and get it from the document */
      if (contentType.IsEmpty() && mDocument)
      {
            nsCOMPtr<nsIDOMNSDocument> doc = do_QueryInterface(mDocument);
            if (doc)
            {
                  GulString type;
                  doc->GetContentType (type);
                  contentType = type;
            }
      }

      /* Failing that, guess from the url */
      if (contentType.IsEmpty())
      {
            nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID));

#ifdef HAVE_NSIMIMEINFO_NSASTRING
            mimeService->GetTypeFromURI (mFinalURL, contentType);
#else
            char *type = nsnull;
            rv = mimeService->GetTypeFromURI (mFinalURL, &type);
            if (NS_SUCCEEDED (rv))
            {
                  contentType = type;
                  nsMemory::Free (type);
            }
#endif
      }

      /* Calculate whether we whould decode */
      mShouldDecode = PR_FALSE;

      if (contentEncoding.Length ())
      {
            nsCOMPtr<nsIExternalHelperAppService> helperService =
                  do_GetService (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID);

            nsCOMPtr<nsIURL> resultURL = do_QueryInterface (mFinalURL);
            if (resultURL)
            {
                  GulCString extension;
                  resultURL->GetFileExtension (extension);

#ifdef HAVE_NSIMIMEINFO_NSASTRING
                  rv = helperService->ApplyDecodingForExtension (extension,
                                                       contentEncoding,
                                                       &mShouldDecode);
#else
                  rv = helperService->ApplyDecodingForExtension (extension.get(),
                                                       contentEncoding.get(),
                                                       &mShouldDecode);
#endif

                  if (NS_FAILED (rv))
                  {
                        mShouldDecode = PR_FALSE;
                  }
            }
      }

      if (!mDocument && !mShouldDecode && contentEncoding.Length())
      {
            // The data is encoded, we are not going to decode it,
            // and this is not a document save so just set our
            // content type to correspond to the outermost
            // encoding so we get extensions and the like right.
            contentType = contentEncoding;
      }


      GulCString filename;
      rv = GetFilename (contentDisposition, contentType, filename);
      NS_ENSURE_TRUE (NS_SUCCEEDED (rv), NS_ERROR_FAILURE);

      PerformSave (filename);
      return NS_OK;
}


/* This function is copied almost verbatim from
 * 
 *  mozilla/xpfe/communicator/resources/content/contentAreaUtils.js
 *
 * It is designed to get the most appropriate file extension
 * for a saved file.
 */
NS_IMETHODIMP
HeaderSniffer::GetDefaultExtension (const GulCString &aFilename,
                            const GulCString &aContentType,
                            PRBool get_text_mime,
                            GulCString &_retval)
                            
{
      nsresult rv;

      GulCString scheme;
      mFinalURL->GetScheme (scheme);

      /* Don't return default extensions for random files, or ftp */
      if (aContentType.Equals ("application/octet-stream") ||
          scheme.Equals ("ftp"))
      {
            return NS_OK;
      }

      /* Unless asked, don't return one for text/plain, it is the
       * web server's default mime type */
      if (!get_text_mime && aContentType.Equals ("text/plain"))
      {
            return NS_OK;
      }

      /* First, extact the current extension from the filename */
      nsCOMPtr<nsIURL> url (do_CreateInstance(NS_STANDARDURL_CONTRACTID));
      url->SetFilePath( aFilename );

      GulCString extension;
      url->GetFileExtension( extension );

      /* See if this is an appropriate extension for this mime
       * type, this mirros some code in mozilla's
       * nsExternalHelperAppService::DoContent */
      nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID));

      nsCOMPtr<nsIMIMEInfo> mimeInfo;
#ifdef HAVE_NSIMIMEINFO_NSASTRING
      mimeService->GetFromTypeAndExtension (aContentType, extension, 
                                    getter_AddRefs (mimeInfo));
#else
      mimeService->GetFromTypeAndExtension (aContentType.get(), extension.get(), 
                                    getter_AddRefs (mimeInfo));
#endif
      if (extension.Length() && mimeInfo)
      {
            PRBool exists = PR_FALSE;
#ifdef HAVE_NSIMIMEINFO_NSASTRING
            mimeInfo->ExtensionExists (extension, &exists);
#else
            mimeInfo->ExtensionExists (extension.get(), &exists);
#endif
            if (exists)
            {
                  _retval = extension;
                  return NS_OK;
            }
      }

      /* If that didn't work, try the URI */
      url = do_QueryInterface (mFinalURL);
      GulCString urlExtension;
      if (url)
      {
            url->GetFileExtension (urlExtension);
      }

      if (urlExtension.Length() && mimeInfo)
      {
            PRBool exists = PR_FALSE;
#ifdef HAVE_NSIMIMEINFO_NSASTRING
            mimeInfo->ExtensionExists (urlExtension, &exists);
#else
            mimeInfo->ExtensionExists (urlExtension.get(), &exists);
#endif
            if (exists)
            {
                  _retval = urlExtension;
                  return NS_OK;
            }
      }

      /* Well, that didn't work, so just return the 
       * primary extension if the mime type has one */
      if( mimeInfo )
      {
#ifdef HAVE_NSIMIMEINFO_NSASTRING
            rv = mimeInfo->GetPrimaryExtension (_retval);
            if (NS_SUCCEEDED (rv))
            {
                  return NS_OK;
            }
#else
            char *ext = nsnull;
            rv = mimeInfo->GetPrimaryExtension (&ext);
            if (NS_SUCCEEDED (rv))
            {
                  _retval = ext;
                  nsMemory::Free (ext);
                  return NS_OK;
            }
#endif
      }

      _retval = extension.Length() ? extension : urlExtension;
      return NS_OK;
}

nsresult 
HeaderSniffer::GetFilename (const GulCString &contentDisposition,
                      const GulCString &contentType,
                      GulCString &defaultFileName)
{
      PRBool fix_extension = PR_FALSE;
      PRBool use_text_mime = PR_TRUE;

      if (!contentDisposition.IsEmpty())
      {
            /* 1 Use the HTTP header suggestion. */
            nsresult rv;
            nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
                  do_GetService(NS_MIMEHEADERPARAM_CONTRACTID);
            
            if (mimehdrpar)
            {
                  GulCString fallbackCharset;
                  if (mFinalURL)
                  {
                        mFinalURL->GetOriginCharset(fallbackCharset);
                  }
                  
                  GulString fileName;
                  
                  rv = mimehdrpar->GetParameter (contentDisposition, "filename",
                                           fallbackCharset, PR_TRUE, nsnull,
                                           fileName);
                  if (NS_FAILED(rv) || fileName.IsEmpty())
                  {
                        rv = mimehdrpar->GetParameter (contentDisposition, "name",
                                                 fallbackCharset, PR_TRUE, nsnull,
                                                 fileName);
                  }

                  if (NS_SUCCEEDED(rv) && !fileName.IsEmpty())
                  {
                        defaultFileName = fileName;
                  }
            }
      }
    
      /* 2 For file URLs, use the file name. */
      if (defaultFileName.IsEmpty())
      {
            nsCOMPtr<nsIURL> url(do_QueryInterface(mFinalURL));
            if (url)
            {
                  url->GetFileName (defaultFileName);
                  fix_extension = PR_TRUE;
                  use_text_mime = PR_FALSE;
            }
      }
    
      /* 3 Use the title of the document. */
      if (defaultFileName.IsEmpty() && mDocument)
      {
            nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(mDocument));
            if (htmlDoc)
            {
                  GulString title;
                  htmlDoc->GetTitle (title);
                  defaultFileName = title;
                  fix_extension = PR_TRUE;
            }
      }
    
      /* 4 Use the directory */
      if (defaultFileName.IsEmpty())
      {
            GulCString pathStr;
            mFinalURL->GetPath (pathStr);

            if (pathStr.Length() && pathStr.get()[pathStr.Length()-1] == '/' )
            {
                  pathStr.Cut (pathStr.Length()-1, 1);
                  const char *path = pathStr.get();
                  const char *sep = strrchr (path, '/');
                  if (sep)
                  {
                        defaultFileName = sep+1;
                        fix_extension = PR_TRUE;
                  }
            }
      }

      /* 5 Use the host. */
      if (defaultFileName.IsEmpty() && mOriginalURL)
      {
            mOriginalURL->GetHost (defaultFileName);
            fix_extension = PR_TRUE;
      }
    
      /* 6 One last case to handle about:blank and other untitled pages. */
      if (defaultFileName.IsEmpty())
      {
            defaultFileName = _("Untitled");
            fix_extension = PR_TRUE;
      }

      /* Finally, add the extension on if need be */
      if (!fix_extension)
      {
            return NS_OK;
      }

      /* Get the extension */
      GulCString extension;
      GetDefaultExtension (defaultFileName, contentType,
                       use_text_mime, extension );
      if (extension.Length())
      {
            nsCOMPtr<nsIURL> url (do_CreateInstance(NS_STANDARDURL_CONTRACTID));
            url->SetFilePath( defaultFileName );

            GulCString current;
            url->GetFileExtension( current );
            if (!current.Equals (extension))
            {
                  defaultFileName += ".";
                  defaultFileName += extension;
            }
      }
      return NS_OK;
}

static void
filechooser_response_cb (GulFileChooser *dialog, gint response, HeaderSniffer* sniffer)
{
      if (response == GTK_RESPONSE_ACCEPT)
      {
            char *filename;

            filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
            if (!filename)
            {
                  return;
            }

            if (gul_gui_confirm_overwrite_file (GTK_WIDGET (dialog),
                                        filename) == FALSE)
            {
                  g_free (filename);
                  return;
            }

            nsCOMPtr<nsILocalFile> destFile = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID);
            if (destFile)
            {
                  destFile->InitWithNativePath (GulDependentCString (filename));

                  sniffer->InitiateDownload (destFile);
            }

            g_free (filename);
      }

      NS_RELEASE (sniffer); /* remember to remove the ref */
      gtk_widget_destroy (GTK_WIDGET (dialog));
}


static void
content_toggled_cb (GtkWidget *toggle, GaleonEmbedPersist *persist)
{
      gint flags;

      flags = (gint)galeon_embed_persist_get_flags (persist);

      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)))
      {
            flags |= EMBED_PERSIST_SAVE_CONTENT;
      }
      else
      {
            flags &= ~EMBED_PERSIST_SAVE_CONTENT;
      }
      
      galeon_embed_persist_set_flags (persist, (EmbedPersistFlags)flags);
}

nsresult
HeaderSniffer::PerformSave (const GulCString &defaultFileName)
{
      EmbedPersistFlags flags;
      PRBool askDownloadDest;

      flags = galeon_embed_persist_get_flags (mEmbedPersist);
      askDownloadDest = (flags & EMBED_PERSIST_ASK_DESTINATION) ||
                          eel_gconf_get_boolean (CONF_DOWNLOADING_ASK_DIR);
            
      /* Validate the file name to ensure legality. */
      char *default_name = g_strdup (defaultFileName.get());
      default_name = g_strdelimit (default_name, ":/", ' ');

        char *filename;
        filename = gnome_vfs_unescape_string (default_name, NULL);

        if (!g_utf8_validate (filename, -1, NULL))
        {
                g_free (filename);
                filename = g_strdup (default_name);
        }

      g_free (default_name);

      GtkWidget *parent;
      parent = galeon_embed_persist_get_fc_parent (mEmbedPersist);

      if (!askDownloadDest)
      {
            nsCOMPtr<nsILocalFile> destFile;
            nsresult rv = BuildDownloadPath (filename, parent,
                                      getter_AddRefs (destFile));
            if (NS_SUCCEEDED (rv))
            {
                  g_free (filename);
                  return InitiateDownload (destFile);
            }
      }

      GulFileChooser *dialog;
      const char *title;
      title = galeon_embed_persist_get_fc_title (mEmbedPersist);

      dialog = gul_file_chooser_new (title ? title: _("Save"),
                               GTK_WIDGET (parent),
                               GTK_FILE_CHOOSER_ACTION_SAVE,
                               CONF_STATE_LAST_DOWNLOAD_DIR);

      if (mDocument && (flags & EMBED_PERSIST_SAVE_CONTENT))
      {
            GtkWidget *toggle;

            /* Reset the flags to turn off content, the user toggles
             * if they want to turn it on */
            (gint&)flags &= ~EMBED_PERSIST_SAVE_CONTENT;
            galeon_embed_persist_set_flags (mEmbedPersist, flags);

            toggle = gtk_check_button_new_with_mnemonic (_("Save _with content"));
            gtk_widget_show (toggle);
            gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), toggle);
            g_signal_connect (toggle, "toggled",
                          G_CALLBACK (content_toggled_cb), mEmbedPersist);

      }

      gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog),
                                 filename);
      
      NS_ADDREF (this); /* add a ref to this object, so it doesn't dissappear */
      g_signal_connect (dialog, "response",
                    G_CALLBACK (filechooser_response_cb), this);

      guint32 user_time;
      g_object_get (mEmbedPersist, "user_time", &user_time, NULL);

      if (!user_time)
      {
            g_warning ("No user time specified in embed persist for file save" );
      }
      gul_x11_window_update_user_time (GTK_WIDGET (dialog), user_time);

      gtk_widget_show (GTK_WIDGET (dialog));
      
      g_free (filename);
      return NS_OK;
}

nsresult HeaderSniffer::InitiateDownload (nsILocalFile *aDestFile)
{
      LOG ("Initiating download");
      return InitiateMozillaDownload (mOriginalURL, aDestFile,
                              mEmbedPersist,
                              mDocument,
                              mCacheKey, mPostData,
                              mShouldDecode,
                              mFinalURL);
}

Generated by  Doxygen 1.6.0   Back to index