/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010, 2011, 2012 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 <QDialog>
#include <QWebFrame>

#include "dftp.h"
#include "dmisc.h"
#include "dooble.h"
#include "dwebpage.h"
#include "dnetworkcache.h"
#include "dnetworkaccessmanager.h"

/*
** The dnetworkdirreply, dnetworkerrorreply, and dnetworkftpreply classes were
** created in order to inject entries into a QWebPage's history.
*/

dnetworkdirreply::dnetworkdirreply
(QObject *parent, const QUrl &url):QNetworkReply(parent)
{
  qRegisterMetaType<dnetworkdirreply *> ("dnetworkdirreply *");
  setUrl(url);
  open(ReadOnly | Unbuffered);
  setHeader(QNetworkRequest::ContentTypeHeader, "text/html; charset=UTF-8");
  setHeader(QNetworkRequest::ContentLengthHeader, 0);
  setHeader(QNetworkRequest::LocationHeader, url);
  setHeader(QNetworkRequest::LastModifiedHeader, QDateTime::currentDateTime());
  setOperation(QNetworkAccessManager::GetOperation);
  connect(this,
	  SIGNAL(finished(dnetworkdirreply *)),
	  this,
	  SIGNAL(finished(void)));
}

void dnetworkdirreply::load(void)
{
  QMetaObject::invokeMethod(this,
			    "readyRead",
			    Qt::QueuedConnection);
  QMetaObject::invokeMethod(this,
			    "finished",
			    Qt::QueuedConnection,
			    Q_ARG(dnetworkdirreply *, this));
}

void dnetworkdirreply::abort(void)
{
}

bool dnetworkdirreply::isSequential(void) const
{
  return true;
}

qint64 dnetworkdirreply::readData(char *data, qint64 maxSize)
{
  Q_UNUSED(data);
  Q_UNUSED(maxSize);
  return 0;
}

qint64 dnetworkdirreply::bytesAvailable(void) const
{
  return 0;
}

dnetworkerrorreply::dnetworkerrorreply
(QObject *parent, const QNetworkRequest &request):QNetworkReply(parent)
{
  qRegisterMetaType<dnetworkerrorreply *> ("dnetworkerrorreply *");

  QUrl url(request.url());
  QString scheme(url.scheme());

  url.setScheme(scheme.mid(qstrlen("dooble")));
  m_content.append("<html>");
  m_content.append("<head>");
  m_content.append(QString("<title>%1</title>").
		   arg(url.toString(QUrl::StripTrailingSlash)));
  m_content.append("<style type=\"text/css\">");
  m_content.append("html"
		   "{"
		   "margin: 0;"
		   "padding: 0;"
		   "}");
  m_content.append("body"
		   "{"
		   "background: lightgray;"
		   "margin: 0;"
		   "padding: 0;"
		   "font-family: sans-serif;"
		   "font-size: 100%;"
		   "}");
  m_content.append("#block"
		   "{"
		   "background: #fff;"
		   "border: 1px solid black;"
		   "padding: 40px;"
		   "width: 800px;"
		   "margin: 60px auto;"
		   "color: #444;"
		   "-webkit-border-radius: 10px;"
		   "}");
  m_content.append("h1"
		   "{"
		   "font-size: 130%;"
		   "font-weight: bold;"
		   "border-bottom: 1px solid lightgray;"
		   "margin-left: 40px;"
		   "}");
  m_content.append("h2"
		   "{"
		   "font-size: 90%;"
		   "font-weight: normal;"
		   "border-bottom: 1px solid lightgray;"
		   "margin-left: 40px;"
		   "}");
  m_content.append("h3"
		   "{"
		   "font-size: 50%;"
		   "font-weight: normal;"
		   "margin-top: 0px;"
		   "margin-left: 4px;"
		   "}");
  m_content.append("ul"
		   "{"
		   "padding: 12px 128px;"
		   "margin: 0;"
		   "}");
  m_content.append("</style>");
  m_content.append("</head>");
  m_content.append("<body>");

  QUrl fileUrl
    (QUrl::fromLocalFile(QString("%1/Icons/64x64/dooble.png").
			 arg(QDir::currentPath())));

  if(dooble::s_settings.value("mainWindow/offlineMode", false).toBool())
    m_content.append
      (QString("<div id=\"block\">"
	       "<table><tbody><tr><td>"
	       "<img src=\"%1\">"
	       "</td><td><h1>Offline Mode</h1>").
       arg(fileUrl.toString(QUrl::StripTrailingSlash)));
  else
    m_content.append
      (QString("<div id=\"block\">"
	       "<table><tbody><tr><td>"
	       "<img src=\"%1\">"
	       "</td><td><h1>Load Error</h1>").
       arg(fileUrl.toString(QUrl::StripTrailingSlash)));

  if(url.host().isEmpty())
    m_content.append("<h2>Dooble cannot establish a connection to "
		     "the requested site.</h2>"
		     "</td>");
  else
    m_content.append
      (QString("<h2>Dooble cannot establish a connection to "
	       "%1.</h2>"
	       "</td>").arg(url.host()));

  m_content.append("</tr></tbody></table>");

  if(dooble::s_settings.value("mainWindow/offlineMode", false).toBool())
    m_content.append("<h3><ul>"
		     "<li>You may disable the offline mode via the File menu.</li>"
		     "</ul></h3>");
  else
    m_content.append("<h3><ul>"
		     "<li>If you have the cache enabled, please "
		     "try reloading the page.</li>"
		     "<li>Please review your configuration settings as "
		     "Dooble may be intentionally restricting access.</li>"
		     "<li>Please verify that your firewall settings "
		     "are not preventing Dooble from accessing the "
		     "site.</li>"
		     "<li>Please verify that your proxy settings "
		     "are correct.</li>"
		     "<li>The site may be unavailable.</li>"
		     "</ul></h3>");

  m_content.append("</div>");
  m_content.append("</body></html>");
  setUrl(url);
  open(ReadOnly | Unbuffered);
  setHeader(QNetworkRequest::ContentTypeHeader, "text/html; charset=UTF-8");
  setHeader(QNetworkRequest::ContentLengthHeader, m_content.size());
  setHeader(QNetworkRequest::LocationHeader, url);
  setHeader(QNetworkRequest::LastModifiedHeader, QDateTime::currentDateTime());
  setOperation(QNetworkAccessManager::GetOperation);
  connect(this,
	  SIGNAL(finished(dnetworkerrorreply *)),
	  this,
	  SIGNAL(finished(void)));
}

void dnetworkerrorreply::load(void)
{
  QMetaObject::invokeMethod(this,
			    "readyRead",
			    Qt::QueuedConnection);
  QMetaObject::invokeMethod(this,
			    "finished",
			    Qt::QueuedConnection,
			    Q_ARG(dnetworkerrorreply *, this));
}

void dnetworkerrorreply::abort(void)
{
}

bool dnetworkerrorreply::isSequential(void) const
{
  return true;
}

qint64 dnetworkerrorreply::readData(char *data, qint64 maxSize)
{
  Q_UNUSED(data);
  Q_UNUSED(maxSize);
  return 0;
}

qint64 dnetworkerrorreply::bytesAvailable(void) const
{
  return 0;
}

QByteArray dnetworkerrorreply::html(void) const
{
  return m_content;
}

dnetworkftpreply::dnetworkftpreply
(QObject *parent, const QUrl &url):QNetworkReply(parent)
{
  qRegisterMetaType<dnetworkftpreply *> ("dnetworkftpreply *");
  m_ftp = new dftp(this);
  connect(m_ftp,
	  SIGNAL(finished(bool)),
	  this,
	  SLOT(slotFtpFinished(bool)));
  connect(m_ftp,
	  SIGNAL(unsupportedContent(const QUrl &)),
	  this,
	  SLOT(slotUnsupportedContent(const QUrl &)));
  m_ftp->fetchList(url);
  setUrl(url);
  open(ReadOnly | Unbuffered);
  setHeader(QNetworkRequest::ContentTypeHeader, "text/html; charset=UTF-8");
  setHeader(QNetworkRequest::ContentLengthHeader, 0);
  setHeader(QNetworkRequest::LocationHeader, url);
  setHeader(QNetworkRequest::LastModifiedHeader, QDateTime::currentDateTime());
  setOperation(QNetworkAccessManager::GetOperation);
  connect(this,
	  SIGNAL(finished(dnetworkftpreply *)),
	  this,
	  SIGNAL(finished(void)));
}

void dnetworkftpreply::slotFtpFinished(bool ok)
{
  if(!ok)
    if(error() == QNetworkReply::NoError)
      setError
	(QNetworkReply::ProtocolFailure, "QNetworkReply::ProtocolFailure");

  QMetaObject::invokeMethod(this,
			    "finished",
			    Qt::QueuedConnection,
			    Q_ARG(dnetworkftpreply *, this));
}

void dnetworkftpreply::slotUnsupportedContent(const QUrl &url)
{
  Q_UNUSED(url);
  setError
    (QNetworkReply::UnknownContentError, "QNetworkReply::UnknownContentError");
}

void dnetworkftpreply::load(void)
{
  QMetaObject::invokeMethod(this,
			    "readyRead",
			    Qt::QueuedConnection);
}

void dnetworkftpreply::abort(void)
{
}

bool dnetworkftpreply::isSequential(void) const
{
  return true;
}

qint64 dnetworkftpreply::readData(char *data, qint64 maxSize)
{
  Q_UNUSED(data);
  Q_UNUSED(maxSize);
  return 0;
}

qint64 dnetworkftpreply::bytesAvailable(void) const
{
  return 0;
}

QPointer<dftp> dnetworkftpreply::ftp(void) const
{
  return m_ftp;
}

dnetworkaccessmanager::dnetworkaccessmanager(QObject *parent):
  QNetworkAccessManager(parent)
{
  connect(this,
	  SIGNAL(finished(QNetworkReply *)),
	  this,
	  SLOT(slotFinished(QNetworkReply *)));

  if(dooble::s_networkCache)
    {
      setCache(dooble::s_networkCache);
      dooble::s_networkCache->setParent(0);
    }
}

QNetworkReply *dnetworkaccessmanager::createRequest
(Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
{
  QNetworkRequest request(req);

  if(dooble::s_settings.value("mainWindow/offlineMode", false).toBool())
    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
			 QNetworkRequest::AlwaysCache);
    
  if(dooble::s_settings.value("settingsWindow/automaticallyLoadImages",
			      true).toBool())
    {
      QString path(req.url().encodedPath().toLower().trimmed());

      if(path.endsWith(".bmp") ||
	 path.endsWith(".gif") ||
	 path.endsWith(".jpeg") ||
	 path.endsWith(".jpg") ||
	 path.endsWith(".png"))
	{
	  /*
	  ** Let's not block images that are requested by the user.
	  */

	  bool userRequested = false;
	  QWebFrame *frame = qobject_cast<QWebFrame *>
	    (req.originatingObject());

	  if(frame)
	    {
	      dwebpage *page = qobject_cast<dwebpage *> (frame->parent());

	      if(page &&
		 req.url().toString(QUrl::StripTrailingSlash) ==
		 page->mainFrame()->requestedUrl().
		 toString(QUrl::StripTrailingSlash))
		userRequested = true;
	    }

	  if(!userRequested)
	    if(dooble::s_imageBlockWindow->allowed(req.url().host()))
	      {
		emit exceptionRaised(dooble::s_adBlockWindow, req.url());
		emit loadImageRequest(req.url().host(), req.url(),
				      QDateTime::currentDateTime());

		QPointer<QNetworkReply> reply =
		  QNetworkAccessManager::createRequest
		  (op, QNetworkRequest(), outgoingData);

		reply->ignoreSslErrors();
		return reply;
	      }
	}
    }

  QString scheme(req.url().scheme().toLower().trimmed());

  if(scheme.startsWith("dooble"))
    {
      QPointer<dnetworkerrorreply> reply = new dnetworkerrorreply
	(this, req);

      connect(reply,
	      SIGNAL(finished(dnetworkerrorreply *)),
	      this,
	      SIGNAL(finished(dnetworkerrorreply *)));
      reply->load();
      return reply;
    }
  else if(scheme == "ftp")
    {
      QPointer<dnetworkftpreply> reply = new dnetworkftpreply(this, req.url());

      connect(reply,
	      SIGNAL(finished(dnetworkftpreply *)),
	      this,
	      SIGNAL(finished(dnetworkftpreply *)));
      reply->load();
      return reply;
    }
  else if(QFileInfo(req.url().toLocalFile()).isDir())
    {
      QPointer<dnetworkdirreply> reply = new dnetworkdirreply(this, req.url());

      connect(reply,
	      SIGNAL(finished(dnetworkdirreply *)),
	      this,
	      SIGNAL(finished(dnetworkdirreply *)));
      reply->load();
      return reply;
    }

  if(scheme == "http" || scheme == "https")
    {
      if(op == QNetworkAccessManager::PostOperation)
	if(request.header(QNetworkRequest::ContentTypeHeader).isNull())
	  request.setHeader(QNetworkRequest::ContentTypeHeader,
			    "application/x-www-form-urlencoded");

      request.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute,
			   true);

      if(request.attribute(QNetworkRequest::User) != "dooble-favicon" &&
	 dooble::s_settings.value("settingsWindow/blockThirdPartyContent",
				  false).toBool())
	{
	  QWebFrame *frame = qobject_cast<QWebFrame *> (req.
							originatingObject());

	  if(frame && !qobject_cast<dwebpage *> (frame->parent()))
	    {
	      /*
	      ** If the frame's parent is not a dwebpage, we'll
	      ** assume that the request should be blocked unless
	      ** the frame's URL is consistent with the request's
	      ** referer.
	      */

	      QString domain1(req.url().host());

	      if(!dooble::s_adBlockWindow->allowed(domain1))
		{
		  QString domain2
		    (QUrl::fromUserInput(req.rawHeader("Referer")).host());
		  QStringList list1
		    (domain1.split(".", QString::SkipEmptyParts));
		  QStringList list2
		    (domain2.split(".", QString::SkipEmptyParts));

		  if(list1.size() > 1)
		    domain1 = list1.at(qMax(0, list1.size() - 2)) + "." +
		      list1.at(qMax(0, list1.size() - 1));

		  if(list2.size() > 1)
		    domain2 = list2.at(qMax(0, list2.size() - 2)) + "." +
		      list2.at(qMax(0, list2.size() - 1));

		  if(!(domain1.contains(domain2) || domain2.contains(domain1)))
		    {
		      QUrl url(QUrl::fromUserInput(req.rawHeader("Referer")));

		      url = QUrl::fromEncoded
			(url.toEncoded(QUrl::StripTrailingSlash));
		      emit exceptionRaised(dooble::s_adBlockWindow, req.url());
		      emit blockThirdPartyHost
			(req.url().host(), url, QDateTime::currentDateTime());
		      request = QNetworkRequest();
		    }
		}
	    }
	}

      if(dooble::s_settings.value("settingsWindow/suppressHttpReferrer1",
				  false).toBool())
	if(!dooble::s_httpReferrerWindow->allowed(req.url().host()))
	  {
	    emit exceptionRaised(dooble::s_httpReferrerWindow, req.url());
	    emit suppressHttpReferrer(req.url().host(), req.url(),
				      QDateTime::currentDateTime());
	    request.setRawHeader("Origin", 0);
	    request.setRawHeader("Referer", 0);
	  }

      if(dooble::s_settings.value("settingsWindow/doNotTrack", false).toBool())
	if(!dooble::s_dntWindow->allowed(req.url().host()))
	  {
	    emit exceptionRaised(dooble::s_dntWindow, req.url());
	    emit doNotTrack(req.url().host(), req.url(),
			    QDateTime::currentDateTime());
	    request.setRawHeader("DNT", "1");
	  }
    }

  QPointer<QNetworkReply> reply = QNetworkAccessManager::createRequest
    (op, request, outgoingData);

  reply->ignoreSslErrors();
  return reply;
}

void dnetworkaccessmanager::slotFinished(QNetworkReply *reply)
{
  if(!reply)
    return;

  if(qobject_cast<dnetworkdirreply *> (reply) ||
     qobject_cast<dnetworkftpreply *> (reply) ||
     qobject_cast<dnetworkerrorreply *> (reply))
    {
      reply->deleteLater();
      return;
    }

  /*
  ** Let favicon replies be. Please see dwebpage.cc.
  */

  if(reply->property("dooble-favicon").toBool())
    return;

  if(reply->error() == QNetworkReply::NoError &&
     dooble::s_settings.value("settingsWindow/suppressHttpRedirect1",
			      false).toBool())
    {
      QUrl url(reply->attribute(QNetworkRequest::RedirectionTargetAttribute).
	       toUrl());

      /*
      ** The URL may be relative.
      */

      url = reply->url().resolved(url);

      if(url.isEmpty() || !url.isValid() ||
	 url.toString(QUrl::StripTrailingSlash) ==
	 reply->url().toString(QUrl::StripTrailingSlash))
	{
	  /*
	  ** What's up?
	  */
	}
      else
	{
	  /*
	  ** Alright. Here we are. We have a redirect.
	  ** We can either completely stop the current load or
	  ** continue with this redirect if it intends
	  ** to retrieve secondary data for the originating object.
	  */

	  /*
	  ** Is WebKit aware of the redirect before this method is
	  ** reached? How so? The reply includes information containing
	  ** redirection information. WebKit must act upon that information.
	  ** But when? Does it matter? I wish createRequest()'s request
	  ** contained some information that would suggest that it's a
	  ** redirect. If it does, where is it?
	  */

	  /*
	  ** You may have a URL that redirects to some other URL
	  ** just as the originating object is about to fully load.
	  ** Stopping the load then would seem strange.
	  */

	  /*
	  ** Let's keep it simple and abort the current load
	  ** if any prohibited redirect occurs.
	  */

	  if(!dooble::s_httpRedirectWindow->allowed(url.host()))
	    {
	      QWebFrame *frame = qobject_cast<QWebFrame *> 
		 (reply->request().originatingObject());

	      if(frame && frame->page() &&
		 frame == frame->page()->mainFrame())
		{
		  frame->page()->triggerAction
		    (QWebPage::StopScheduledPageRefresh);
		  frame->page()->triggerAction(QWebPage::Stop);
		}

	      emit exceptionRaised(dooble::s_httpRedirectWindow, url);
	      emit urlRedirectionRequest(url.host(),
					 reply->url(),
					 QDateTime::currentDateTime());
	    }
	}
    }

  /*
  ** Deleting the reply object may cause Dooble to terminate if
  ** a new event loop is initiated.
  */

  if(this != reply->parent())
    /*
    ** How so?
    */

    reply->setParent(this);
}
