/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010 Alexis Megas,
** Gunther van Dooble, and the Dooble Team.
** All rights reserved.
**
** License: GPL2 only:
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; version 2 of the License only.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
** or see here: http://www.gnu.org/licenses/gpl.html
**
** For the WebKit library, please see: http://webkit.org.
**
** THE CODE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY
** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
** IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS APPLICATION, EVEN IF ADVISED
** OF THE POSSIBILITY OF SUCH DAMAGE.
**
** Please report all praise, requests, bugs, and problems to the project
** team and administrators: http://sf.net/projects/dooble.
**
** You can find us listed at our project page. New team members are welcome.
** The name of the authors should not be used to endorse or promote products
** derived from Dooble without specific prior written permission.
** If you use this code for other projects, please let us know.
**
** Web sites:
**   http://sf.net/projects/dooble
**   http://dooble.sf.net
****************************************************************************/

#include <QDir>
#include <QtDebug>
#include <QSqlQuery>
#include <QSqlDatabase>

#include "dooble.h"
#include "dcookies.h"

dcookies::dcookies(void):QNetworkCookieJar()
{
  /*
  ** settingsWindow/cookiesShouldBe
  ** 0 - deleted upon exit
  ** 1 - kept forever (that's a long time)
  ** 2 - kept until they expire
  */

  m_timer = new QTimer(this);
  connect(m_timer,
	  SIGNAL(timeout(void)),
	  this,
	  SLOT(slotRemoveDomains(void)));

  int interval = computedTimerInterval();

  if(interval > 0)
    m_timer->start(interval);
}

dcookies::~dcookies()
{
  if(!dooble::s_settings.value("settingsWindow/cookiesEnabled",
			       true).toBool() ||
     dooble::s_settings.value("settingsWindow/cookiesShouldBe",
			      0).toInt() == 0)
    removeAllCookies(true);
  else if(dooble::s_settings.value("settingsWindow/cookiesShouldBe",
				   0).toInt() == 2)
    deleteExpiredCookies();
}

QList<QNetworkCookie> dcookies::allCookies(void) const
{
  /*
  ** If cookies are disabled, should this method return an empty
  ** list or the current list? For now, it will return the
  ** current list of cookies.
  */

  return QNetworkCookieJar::allCookies();
}

QList<QNetworkCookie> dcookies::cookiesForUrl(const QUrl &url) const
{
  /*
  ** If cookies are disabled, should this method return an empty
  ** list or the current list? For now, it will return the
  ** current list of cookies minus the expired cookies.
  */

  QList<QNetworkCookie> list(QNetworkCookieJar::cookiesForUrl(url));

  if(dooble::s_settings.value("settingsWindow/cookiesShouldBe",
			      0).toInt() == 2)
    {
      QDateTime now(QDateTime::currentDateTime());

      /*
      ** Remove expired cookies.
      */

      for(int i = list.size() - 1; i >= 0; i--)
	if(list.at(i).isSessionCookie())
	  continue;
	else if(list.at(i).expirationDate().toLocalTime() < now)
	  list.removeAt(i);
    }

  return list;
}

bool dcookies::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList,
				 const QUrl &url)
{
  if(!dooble::s_settings.value("settingsWindow/cookiesEnabled",
			       true).toBool())
    return false;
  else if(cookieList.isEmpty())
    return false;

  if(!QNetworkCookieJar::setCookiesFromUrl(cookieList, url))
    {
      QList<QNetworkCookie> all(allCookies());

      all.append(cookieList);
      setAllCookies(all);
      all.clear();

      /*
      ** These cookies may have originated from a
      ** different url.
      */

      for(int i = 0; i < cookieList.size(); i++)
	saveCookie(cookieList.at(i));

      emit cookiesChanged(cookieList);
    }

  /*
  ** Save the cookies for the current url.
  ** Session cookies will not be saved.
  */

  QList<QNetworkCookie> list(cookiesForUrl(url));

  for(int i = 0; i < list.size(); i++)
    saveCookie(list.at(i));

  emit cookiesChanged(list);

  /*
  ** Now, submit a request to the Cookies window to remove any
  ** entries that have been removed for url.
  */

  emit cookiesChangedForUrl(list, url);
  return true;
}

void dcookies::removeCookie(const QString &domain)
{
  QList<QNetworkCookie> all(allCookies());

  for(int i = all.size() - 1; i >= 0; i--)
    if(all.at(i).domain() == domain)
      /*
      ** Remove all cookies from the specified domain.
      */

      all.removeAt(i);

  setAllCookies(all);
  all.clear();
  m_favorites.remove(domain);

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec("PRAGMA synchronous = OFF");
	query.prepare("DELETE FROM cookies WHERE domain = ?");
	query.bindValue(0, domain);
	query.exec();
	query.prepare("DELETE FROM favorites WHERE domain = ?");
	query.bindValue(0, domain);
	query.exec();
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("cookies");
}

void dcookies::removeCookie(const QNetworkCookie &cookie)
{
  removeCookie(QVariant(cookie.name()).toString(),
	       cookie.domain(),
	       cookie.path());
}

void dcookies::removeCookie(const QString &name, const QString &domain,
			    const QString &path)
{
  QList<QNetworkCookie> all(allCookies());

  for(int i = all.size() - 1; i >= 0; i--)
    if(all.at(i).domain() == domain &&
       QVariant(all.at(i).name()).toString() == name &&
       all.at(i).path() == path)
      {
	/*
	** Remove exactly one cookie.
	*/

	all.removeAt(i);
	break;
      }

  setAllCookies(all);
  all.clear();

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec("PRAGMA synchronous = OFF");
	query.prepare
	  ("DELETE FROM cookies WHERE "
	   "domain = ? AND "
	   "name = ? AND "
	   "path = ?");
	query.bindValue(0, domain);
	query.bindValue(1, QVariant(name).toByteArray().toBase64());
	query.bindValue(2, path);
	query.exec();
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("cookies");
}

void dcookies::removeAllCookies(const bool force)
{
  if(force ||
     dooble::s_settings.value("cookiesWindow/deleteAllPreservedChecked",
			      false).toBool())
    m_favorites.clear();

  setAllCookies(QList<QNetworkCookie> ());

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec("PRAGMA synchronous = OFF");
	query.exec("DELETE FROM cookies");

	if(force ||
	   dooble::s_settings.value("cookiesWindow/deleteAllPreservedChecked",
				    false).toBool())
	  query.exec("DELETE FROM favorites");
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("cookies");
}

void dcookies::createCookieDatabase(void)
{
  if(!dooble::s_settings.value("settingsWindow/cookiesEnabled",
			       true).toBool())
    return;

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec("CREATE TABLE IF NOT EXISTS cookies ("
		   "domain TEXT NOT NULL, "
		   "name BLOB NOT NULL, "
		   "path TEXT NOT NULL, "
		   "expiration_date TEXT, "
		   "http INTEGER NOT NULL, "
		   "secure INTEGER NOT NULL, "
		   "value BLOB, "
		   "PRIMARY KEY (domain, name, path))");
	query.exec("CREATE TABLE favorites ("
		   "domain PRIMARY KEY NOT NULL)");
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("cookies");
}

void dcookies::allowDomain(const QString &domain,
			   const bool allowed)
{
  if(allowed)
    m_favorites[domain] = true;
  else
    m_favorites.remove(domain);

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);

	if(allowed)
	  {
	    query.exec("PRAGMA synchronous = OFF");
	    query.prepare("INSERT OR REPLACE INTO favorites "
			  "(domain)"
			  "VALUES (?)");
	    query.bindValue(0, domain);
	    query.exec();
	  }
	else
	  {
	    query.exec("PRAGMA synchronous = OFF");
	    query.prepare("DELETE FROM favorites WHERE domain = ?");
	    query.bindValue(0, domain);
	    query.exec();
	  }
      }
  }

  QSqlDatabase::removeDatabase("cookies");
}

void dcookies::populate(void)
{
  m_favorites.clear();
  deleteExpiredCookies();
  setAllCookies(QList<QNetworkCookie> ());

  QList<QNetworkCookie> favorites;

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);
	QList<QNetworkCookie> all;

	if(query.exec("SELECT domain, name, path, expiration_date, "
		      "http, secure, value "
		      "FROM cookies"))
	  while(query.next())
	    {
	      QNetworkCookie cookie;

	      cookie.setDomain(query.value(0).toString());
	      cookie.setName
		(QByteArray::fromBase64(query.value(1).toByteArray()));
	      cookie.setPath(query.value(2).toString());
	      cookie.setExpirationDate
		(QDateTime::fromString(query.value(3).toString(),
				       Qt::ISODate));
	      cookie.setHttpOnly(query.value(4).toBool());
	      cookie.setSecure(query.value(5).toBool());
	      cookie.setValue
		(QByteArray::fromBase64(query.value(6).toByteArray()));
	      all.append(cookie);
	    }

	setAllCookies(all);
	all.clear();

	if(query.exec("SELECT domain FROM favorites"))
	  while(query.next())
	    {
	      m_favorites[query.value(0).toString()] = true;

	      QNetworkCookie cookie;

	      cookie.setDomain(query.value(0).toString());
	      favorites.append(cookie);
	    }
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("cookies");
  emit cookiesChanged(favorites);
  emit cookiesChanged(allCookies());
}

void dcookies::saveCookie(const QNetworkCookie &cookie)
{
  if(cookie.isSessionCookie())
    return;

  if(dooble::s_settings.value("settingsWindow/cookiesShouldBe",
			      0).toInt() == 2)
    {
      QDateTime now(QDateTime::currentDateTime());

      if(cookie.expirationDate().toLocalTime() < now)
	return;
    }

  createCookieDatabase();

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec("PRAGMA synchronous = OFF");
	query.prepare("INSERT OR REPLACE INTO cookies "
		      "(domain, name, path, "
		      "expiration_date, http, secure, "
		      "value) "
		      "VALUES (?, ?, ?, ?, ?, ?, ?)");
	query.bindValue(0, cookie.domain());
	query.bindValue(1, cookie.name().toBase64());
	query.bindValue(2, cookie.path());
	query.bindValue
	  (3, cookie.expirationDate().toString((Qt::ISODate)));
	query.bindValue(4, QVariant(cookie.isHttpOnly()).toInt());
	query.bindValue(5, QVariant(cookie.isSecure()).toInt());
	query.bindValue(6, cookie.value().toBase64());
	query.exec();
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("cookies");
}

void dcookies::deleteExpiredCookies(void)
{
  if(dooble::s_settings.value("settingsWindow/cookiesShouldBe",
			      0).toInt() != 2)
    return;

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "cookies");

    db.setDatabaseName(QDir::homePath() + QDir::separator() +
		       ".dooble" + QDir::separator() + "cookies.db");

    if(db.open())
      {
	QSqlQuery query(db);
	QDateTime now(QDateTime::currentDateTime());

	query.exec("PRAGMA synchronous = OFF");
	query.prepare("DELETE FROM cookies WHERE "
		      "datetime(expiration_date) < ?");
	query.bindValue(0, now.toString(Qt::ISODate).replace("T", " "));
	query.exec();
      }
  }

  QSqlDatabase::removeDatabase("cookies");
}

int dcookies::computedTimerInterval(void) const
{
  int interval = 0;

  if(dooble::s_settings.value("settingsWindow/cookiesEnabled",
			      true).toBool() &&
     dooble::s_settings.value("settingsWindow/cookieTimerEnabled",
			      false).toBool())
    {
      int unit = dooble::s_settings.value
	("settingsWindow/cookieTimerUnit", 0).toInt();

      if(!(unit == 0 || unit == 1))
	unit = 0;

      interval = dooble::s_settings.value
	("settingsWindow/cookieTimerInterval", 1).toInt();

      if(unit == 0)
	{
	  if(interval < 1 || interval > 60)
	    interval = 1;

	  interval = 3600000 * interval;
	}
      else
	{
	  if(interval < 1 || interval > 3600)
	    interval = 1;

	  interval = 60000 * interval;
	}
    }

  return interval;
}

void dcookies::slotCookieTimerChanged(void)
{
  int interval = computedTimerInterval();

  if(interval > 0)
    {
      if(interval != m_timer->interval())
	m_timer->start(interval);
    }
  else
    m_timer->stop();
}

bool dcookies::isFavorite(const QString &domain) const
{
  if(m_favorites.contains(domain))
    return m_favorites.value(domain);
  else
    return false;
}

void dcookies::slotRemoveDomains(void)
{
  QStringList domains;
  QList<QNetworkCookie> all(allCookies());

  /*
  ** Locate the domains that are not in the
  ** m_favorites container.
  */

  for(int i = 0; i < all.size(); i++)
    if(m_favorites.contains(all.at(i).domain()))
      {
	if(!m_favorites.value(all.at(i).domain()))
	  if(!domains.contains(all.at(i).domain()))
	    domains.append(all.at(i).domain());
      }
    else if(!domains.contains(all.at(i).domain()))
      domains.append(all.at(i).domain());

  all.clear();

  /*
  ** Remove all cookies that are located in the
  ** domains container.
  */

  for(int i = 0; i < domains.size(); i++)
    removeCookie(domains.at(i));

  emit domainsRemoved(domains);
}

int dcookies::count(void) const
{
  return allCookies().size();
}
