/* ***** 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 *****
 *
 * $Id: HeaderSniffer.cpp,v 1.3 2004/05/23 21:19:46 crispin Exp $
 */

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

#include "MozillaPrivate.h"

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

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

#include "nsReadableUtils.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"
#if MOZILLA_SNAPSHOT > 10
#include  "nsIMIMEHeaderParam.h"
#endif

#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;

		galeon_embed_persist_get_fc_parent (mEmbedPersist, &parent);

		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));

	nsCString contentDisposition;
	if (httpChannel)
	{
		httpChannel->GetResponseHeader(nsCAutoString("content-disposition"),
					       contentDisposition);
	}

	/* Get the document encoding */
	nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(channel));
	nsCString 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 */
	nsCString contentType;
	channel->GetContentType(contentType);
	
	if (contentType == NS_LITERAL_CSTRING ("application/x-unknown-content-type"))
	{
		contentType.SetLength( 0 );
	}

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

		}
	}

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

#if MOZILLA_SNAPSHOT < 17
		nsXPIDLCString type;
		rv = mimeService->GetTypeFromURI (mFinalURL, getter_Copies(type));
		if (NS_SUCCEEDED (rv))
		{
			contentType = type;
		}
#else
		mimeService->GetTypeFromURI (mFinalURL, contentType);
#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)
		{
			nsCString extension;
			resultURL->GetFileExtension (extension);

#if MOZILLA_SNAPSHOT < 17
			rv = helperService->ApplyDecodingForExtension (extension.get(),
								       contentEncoding.get(),
								       &mShouldDecode);
#else
			rv = helperService->ApplyDecodingForExtension (extension,
								       contentEncoding,
								       &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;
	}


	nsCString 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 nsCString &aFilename,
				    const nsCString &aContentType,
				    PRBool get_text_mime,
				    nsCString &_retval)
				    
{
	nsresult rv;

	nsCString scheme;
	mFinalURL->GetScheme (scheme);

	/* Don't return default extensions for random files, or ftp */
	if (aContentType == NS_LITERAL_CSTRING("application/octet-stream") ||
	    scheme == NS_LITERAL_CSTRING ("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 == NS_LITERAL_CSTRING("text/plain"))
	{
		return NS_OK;
	}

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

	nsCString 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;
#if MOZILLA_SNAPSHOT < 17
	mimeService->GetFromTypeAndExtension (aContentType.get(), extension.get(), 
					      getter_AddRefs (mimeInfo));
#else
	mimeService->GetFromTypeAndExtension (aContentType, extension, 
					      getter_AddRefs (mimeInfo));
#endif
	if (extension.Length() && mimeInfo)
	{
		PRBool exists = PR_FALSE;
#if MOZILLA_SNAPSHOT < 17
		mimeInfo->ExtensionExists (extension.get(), &exists);
#else
		mimeInfo->ExtensionExists (extension, &exists);
#endif
		if (exists)
		{
			_retval = extension;
			return NS_OK;
		}
	}

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

	if (urlExtension.Length() && mimeInfo)
	{
		PRBool exists = PR_FALSE;
#if MOZILLA_SNAPSHOT < 17
		mimeInfo->ExtensionExists (urlExtension.get(), &exists);
#else
		mimeInfo->ExtensionExists (urlExtension, &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 )
	{
#if MOZILLA_SNAPSHOT < 17
		nsXPIDLCString ext;
		rv = mimeInfo->GetPrimaryExtension (getter_Copies(ext));
		if (NS_SUCCEEDED (rv))
		{
			_retval = ext;
			return NS_OK;
		}
#else
		rv = mimeInfo->GetPrimaryExtension (_retval);
		if (NS_SUCCEEDED (rv))
		{
			return NS_OK;
		}
#endif
	}

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

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

	if (!contentDisposition.IsEmpty())
	{
		/* 1 Use the HTTP header suggestion. */
#if MOZILLA_SNAPSHOT > 10
		nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
			do_GetService(NS_MIMEHEADERPARAM_CONTRACTID);
		
		if (mimehdrpar)
		{
			nsCAutoString fallbackCharset;
			if (mFinalURL)
			{
				mFinalURL->GetOriginCharset(fallbackCharset);
			}
			
			nsAutoString 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 = 
					NS_ConvertUCS2toUTF8 (fileName);
			}
		}
#else
		PRInt32 index = contentDisposition.Find("filename=");
		if (index >= 0)
		{
			/* Take the substring following the prefix. */
			index += strlen ("filename=");
			nsCAutoString filename;
			contentDisposition.Right (defaultFileName,
						  contentDisposition.Length() - index);
		}
#endif
	}
    
	/* 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)
		{
			nsAutoString title;
			htmlDoc->GetTitle (title);
			defaultFileName = NS_ConvertUCS2toUTF8 (title);
			fix_extension = PR_TRUE;
		}
	}
    
	/* 4 Use the directory */
	if (defaultFileName.IsEmpty())
	{
		nsCString path;
		mFinalURL->GetPath (path);
		if (path.Length() && path.CharAt (path.Length()-1) == '/' )
		{
			path.SetLength (path.Length() - 1);
			PRInt32 index = path.RFindChar ('/');

			if (index != -1 )
			{
				index++;
				path.Right (defaultFileName,
					    path.Length() - index);
				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 = nsDependentCString (_("Untitled"));
		fix_extension = PR_TRUE;
	}

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

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

		nsCString current;
		url->GetFileExtension( current );
		if (current != 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 (nsDependentCString (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;

	galeon_embed_persist_get_flags (persist, (EmbedPersistFlags*)&flags);

	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 nsCString &defaultFileName)
{
	EmbedPersistFlags flags;
	PRBool askDownloadDest;

	galeon_embed_persist_get_flags (mEmbedPersist, &flags);
	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;
	galeon_embed_persist_get_fc_parent (mEmbedPersist, &parent);

	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 = NULL;
	galeon_embed_persist_get_fc_title (mEmbedPersist, &title);

	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);
	
	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);
}
