2012-02-20 21:47:14 +00:00
|
|
|
/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org>
|
2009-01-28 20:55:45 +00:00
|
|
|
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
|
|
|
|
2009-04-29 23:18:26 +00:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
#include <stdint_msvc.h>
|
|
|
|
#else
|
2009-04-27 12:25:15 +00:00
|
|
|
#include <stdint.h>
|
2009-04-29 23:18:26 +00:00
|
|
|
#endif
|
|
|
|
|
2013-05-31 04:20:25 +00:00
|
|
|
#include <QUrl>
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
#include "article_netmgr.hh"
|
2009-04-18 17:20:12 +00:00
|
|
|
#include "wstring_qt.hh"
|
2013-11-16 18:34:09 +00:00
|
|
|
#include "gddebug.hh"
|
2013-05-30 13:24:21 +00:00
|
|
|
#include "qt4x5.hh"
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
using std::string;
|
|
|
|
|
2009-08-31 12:18:08 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
/// Uses some heuristics to chop off the first domain name from the host name,
|
|
|
|
/// but only if it's not too base. Returns the resulting host name.
|
|
|
|
QString getHostBase( QUrl const & url )
|
|
|
|
{
|
|
|
|
QString host = url.host();
|
|
|
|
|
|
|
|
QStringList domains = host.split( '.' );
|
|
|
|
|
|
|
|
int left = domains.size();
|
|
|
|
|
|
|
|
// Skip last <=3-letter domain name
|
|
|
|
if ( left && domains[ left - 1 ].size() <= 3 )
|
|
|
|
--left;
|
|
|
|
|
|
|
|
// Skip another <=3-letter domain name
|
|
|
|
if ( left && domains[ left - 1 ].size() <= 3 )
|
|
|
|
--left;
|
|
|
|
|
|
|
|
if ( left > 1 )
|
|
|
|
{
|
|
|
|
// We've got something like www.foobar.co.uk -- we can chop off the first
|
|
|
|
// domain
|
|
|
|
|
|
|
|
return host.mid( domains[ 0 ].size() + 1 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return host;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
QNetworkReply * ArticleNetworkAccessManager::createRequest( Operation op,
|
|
|
|
QNetworkRequest const & req,
|
|
|
|
QIODevice * outgoingData )
|
|
|
|
{
|
|
|
|
if ( op == GetOperation )
|
|
|
|
{
|
|
|
|
if ( req.url().scheme() == "qrcx" )
|
|
|
|
{
|
|
|
|
// We have to override the local load policy for the qrc scheme, hence
|
|
|
|
// we use qrcx and redirect it here back to qrc
|
2013-05-31 04:20:25 +00:00
|
|
|
QUrl newUrl( req.url() );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
newUrl.setScheme( "qrc" );
|
|
|
|
newUrl.setHost( "" );
|
|
|
|
|
|
|
|
QNetworkRequest newReq( req );
|
|
|
|
newReq.setUrl( newUrl );
|
|
|
|
|
|
|
|
return QNetworkAccessManager::createRequest( op, newReq, outgoingData );
|
|
|
|
}
|
|
|
|
|
|
|
|
QString contentType;
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
sptr< Dictionary::DataRequest > dr = getResource( req.url(), contentType );
|
|
|
|
|
|
|
|
if ( dr.get() )
|
|
|
|
return new ArticleResourceReply( this, req, dr, contentType );
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2009-08-31 12:18:08 +00:00
|
|
|
// Check the Referer. If the user has opted-in to block elements from external
|
|
|
|
// pages, we block them.
|
|
|
|
|
|
|
|
if ( disallowContentFromOtherSites && req.hasRawHeader( "Referer" ) )
|
|
|
|
{
|
|
|
|
QByteArray referer = req.rawHeader( "Referer" );
|
|
|
|
|
2011-06-19 18:50:11 +00:00
|
|
|
//DPRINTF( "Referer: %s\n", referer.data() );
|
2009-08-31 12:18:08 +00:00
|
|
|
|
2013-05-31 04:20:25 +00:00
|
|
|
QUrl refererUrl = QUrl::fromEncoded( referer );
|
2009-08-31 12:18:08 +00:00
|
|
|
|
2011-06-19 18:50:11 +00:00
|
|
|
//DPRINTF( "Considering %s vs %s\n", getHostBase( req.url() ).toUtf8().data(),
|
2009-08-31 12:18:08 +00:00
|
|
|
// getHostBase( refererUrl ).toUtf8().data() );
|
|
|
|
|
|
|
|
if ( !req.url().host().endsWith( refererUrl.host() ) &&
|
2011-06-02 11:15:39 +00:00
|
|
|
getHostBase( req.url() ) != getHostBase( refererUrl ) && !req.url().scheme().startsWith("data") )
|
2009-08-31 12:18:08 +00:00
|
|
|
{
|
2013-11-16 18:34:09 +00:00
|
|
|
gdWarning( "Blocking element \"%s\"\n", req.url().toEncoded().data() );
|
2009-08-31 12:18:08 +00:00
|
|
|
|
|
|
|
return new BlockedNetworkReply( this );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-13 20:21:33 +00:00
|
|
|
if( req.url().scheme() == "file" )
|
|
|
|
{
|
|
|
|
// Check file presence and adjust path if necessary
|
|
|
|
QString fileName = req.url().toLocalFile();
|
|
|
|
if( req.url().host().isEmpty() && articleMaker.adjustFilePath( fileName ) )
|
|
|
|
{
|
2013-05-31 04:20:25 +00:00
|
|
|
QUrl newUrl( req.url() );
|
|
|
|
newUrl.setPath( Qt4x5::Url::ensureLeadingSlash( QUrl::fromLocalFile( fileName ).path() ) );
|
2012-12-13 20:21:33 +00:00
|
|
|
|
|
|
|
QNetworkRequest newReq( req );
|
|
|
|
newReq.setUrl( newUrl );
|
|
|
|
|
|
|
|
return QNetworkAccessManager::createRequest( op, newReq, outgoingData );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-08 12:20:12 +00:00
|
|
|
// spoof User-Agent
|
|
|
|
if ( hideGoldenDictHeader && req.url().scheme().startsWith("http", Qt::CaseInsensitive))
|
|
|
|
{
|
|
|
|
QNetworkRequest newReq( req );
|
|
|
|
newReq.setRawHeader("User-Agent", req.rawHeader("User-Agent").replace(qApp->applicationName(), ""));
|
|
|
|
return QNetworkAccessManager::createRequest( op, newReq, outgoingData );
|
|
|
|
}
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
return QNetworkAccessManager::createRequest( op, req, outgoingData );
|
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
sptr< Dictionary::DataRequest > ArticleNetworkAccessManager::getResource(
|
|
|
|
QUrl const & url, QString & contentType )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2011-06-19 18:50:11 +00:00
|
|
|
DPRINTF( "getResource: %ls\n", url.toString().toStdWString().c_str() );
|
|
|
|
DPRINTF( "scheme: %ls\n", url.scheme().toStdWString().c_str() );
|
|
|
|
DPRINTF( "host: %ls\n", url.host().toStdWString().c_str() );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
if ( url.scheme() == "gdlookup" )
|
|
|
|
{
|
2009-05-11 15:33:57 +00:00
|
|
|
contentType = "text/html";
|
|
|
|
|
2013-05-30 13:24:21 +00:00
|
|
|
if ( Qt4x5::Url::queryItemValue( url, "blank" ) == "1" )
|
2009-05-11 15:33:57 +00:00
|
|
|
return articleMaker.makeEmptyPage();
|
|
|
|
|
2009-04-10 12:48:40 +00:00
|
|
|
bool groupIsValid = false;
|
|
|
|
|
2013-05-30 13:24:21 +00:00
|
|
|
QString word = Qt4x5::Url::queryItemValue( url, "word" );
|
2009-02-01 00:08:08 +00:00
|
|
|
|
|
|
|
QString word = url.queryItemValue( "word" );
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
QString dictIDs = url.queryItemValue( "dictionaries" );
|
|
|
|
if( !dictIDs.isEmpty() )
|
|
|
|
{
|
|
|
|
// Individual dictionaries set from full-text search
|
|
|
|
QStringList dictIDList = dictIDs.split( "," );
|
|
|
|
return articleMaker.makeDefinitionFor( word, 0, QMap< QString, QString >(), QSet< QString >(), dictIDList );
|
|
|
|
}
|
|
|
|
|
2014-04-23 14:16:06 +00:00
|
|
|
unsigned group = Qt4x5::Url::queryItemValue( url, "group" ).toUInt( &groupIsValid );
|
2009-09-23 18:44:38 +00:00
|
|
|
// See if we have some dictionaries muted
|
|
|
|
|
|
|
|
QSet< QString > mutedDicts =
|
2013-05-30 13:24:21 +00:00
|
|
|
QSet< QString >::fromList( Qt4x5::Url::queryItemValue( url, "muted" ).split( ',' ) );
|
2009-09-23 18:44:38 +00:00
|
|
|
|
2009-05-29 19:48:50 +00:00
|
|
|
// Unpack contexts
|
|
|
|
|
|
|
|
QMap< QString, QString > contexts;
|
|
|
|
|
2013-05-30 13:24:21 +00:00
|
|
|
QString contextsEncoded = Qt4x5::Url::queryItemValue( url, "contexts" );
|
2009-05-29 19:48:50 +00:00
|
|
|
|
|
|
|
if ( contextsEncoded.size() )
|
|
|
|
{
|
2013-02-03 20:19:55 +00:00
|
|
|
QByteArray ba = QByteArray::fromBase64( contextsEncoded.toLatin1() );
|
2009-05-29 19:48:50 +00:00
|
|
|
|
|
|
|
QBuffer buf( & ba );
|
|
|
|
|
|
|
|
buf.open( QBuffer::ReadOnly );
|
|
|
|
|
|
|
|
QDataStream stream( &buf );
|
|
|
|
|
|
|
|
stream >> contexts;
|
|
|
|
}
|
|
|
|
|
2009-04-10 12:48:40 +00:00
|
|
|
if ( groupIsValid && word.size() ) // Require group and word to be passed
|
2009-09-23 18:44:38 +00:00
|
|
|
return articleMaker.makeDefinitionFor( word, group, contexts, mutedDicts );
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2013-06-22 16:36:25 +00:00
|
|
|
if ( ( url.scheme() == "bres" || url.scheme() == "gdau" || url.scheme() == "gdvideo" || url.scheme() == "gico" ) &&
|
2009-01-28 20:55:45 +00:00
|
|
|
url.path().size() )
|
|
|
|
{
|
2011-06-19 18:50:11 +00:00
|
|
|
//DPRINTF( "Get %s\n", req.url().host().toLocal8Bit().data() );
|
|
|
|
//DPRINTF( "Get %s\n", req.url().path().toLocal8Bit().data() );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
string id = url.host().toStdString();
|
|
|
|
|
|
|
|
bool search = ( id == "search" );
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( !search )
|
|
|
|
{
|
|
|
|
for( unsigned x = 0; x < dictionaries.size(); ++x )
|
|
|
|
if ( dictionaries[ x ]->getId() == id )
|
2012-01-28 03:07:23 +00:00
|
|
|
{
|
|
|
|
if( url.scheme() == "gico" )
|
|
|
|
{
|
|
|
|
QByteArray bytes;
|
|
|
|
QBuffer buffer(&bytes);
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
dictionaries[ x ]->getIcon().pixmap( 16 ).save(&buffer, "PNG");
|
|
|
|
buffer.close();
|
|
|
|
sptr< Dictionary::DataRequestInstant > ico = new Dictionary::DataRequestInstant( true );
|
|
|
|
ico->getData().resize( bytes.size() );
|
|
|
|
memcpy( &( ico->getData().front() ), bytes.data(), bytes.size() );
|
|
|
|
return ico;
|
|
|
|
}
|
2013-09-19 19:43:16 +00:00
|
|
|
try
|
|
|
|
{
|
2013-09-27 13:04:25 +00:00
|
|
|
return dictionaries[ x ]->getResource( Qt4x5::Url::path( url ).mid( 1 ).toUtf8().data() );
|
2013-09-19 19:43:16 +00:00
|
|
|
}
|
|
|
|
catch( std::exception & e )
|
|
|
|
{
|
2013-11-16 18:34:09 +00:00
|
|
|
gdWarning( "getResource request error (%s) in \"%s\"\n", e.what(),
|
|
|
|
dictionaries[ x ]->getName().c_str() );
|
2013-09-19 19:43:16 +00:00
|
|
|
return sptr< Dictionary::DataRequest >();
|
|
|
|
}
|
2012-01-28 03:07:23 +00:00
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
}
|
|
|
|
else
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
// We don't do search requests for now
|
|
|
|
#if 0
|
|
|
|
for( unsigned x = 0; x < dictionaries.size(); ++x )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( search || dictionaries[ x ]->getId() == id )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
dictionaries[ x ]->getResource( url.path().mid( 1 ).toUtf8().data(),
|
|
|
|
data );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch( Dictionary::exNoSuchResource & )
|
|
|
|
{
|
|
|
|
if ( !search )
|
|
|
|
break;
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
#endif
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-07 11:59:29 +00:00
|
|
|
if ( url.scheme() == "gdpicture" )
|
|
|
|
{
|
|
|
|
contentType = "text/html";
|
2013-05-31 04:20:25 +00:00
|
|
|
QUrl imgUrl ( url );
|
2012-12-07 11:59:29 +00:00
|
|
|
imgUrl.setScheme( "bres" );
|
|
|
|
return articleMaker.makePicturePage( imgUrl.toEncoded().data() );
|
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
return sptr< Dictionary::DataRequest >();
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ArticleResourceReply::ArticleResourceReply( QObject * parent,
|
2009-03-26 19:00:08 +00:00
|
|
|
QNetworkRequest const & netReq,
|
|
|
|
sptr< Dictionary::DataRequest > const & req_,
|
2009-01-28 20:55:45 +00:00
|
|
|
QString const & contentType ):
|
2009-03-26 19:00:08 +00:00
|
|
|
QNetworkReply( parent ), req( req_ ), alreadyRead( 0 )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
setRequest( netReq );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
setOpenMode( ReadOnly );
|
|
|
|
|
|
|
|
if ( contentType.size() )
|
|
|
|
setHeader( QNetworkRequest::ContentTypeHeader, contentType );
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
connect( req.get(), SIGNAL( updated() ),
|
|
|
|
this, SLOT( reqUpdated() ) );
|
|
|
|
|
|
|
|
connect( req.get(), SIGNAL( finished() ),
|
|
|
|
this, SLOT( reqFinished() ) );
|
|
|
|
|
|
|
|
if ( req->isFinished() || req->dataSize() > 0 )
|
|
|
|
{
|
|
|
|
connect( this, SIGNAL( readyReadSignal() ),
|
|
|
|
this, SLOT( readyReadSlot() ), Qt::QueuedConnection );
|
|
|
|
connect( this, SIGNAL( finishedSignal() ),
|
|
|
|
this, SLOT( finishedSlot() ), Qt::QueuedConnection );
|
|
|
|
|
|
|
|
emit readyReadSignal();
|
|
|
|
|
|
|
|
if ( req->isFinished() )
|
|
|
|
{
|
|
|
|
emit finishedSignal();
|
2011-06-19 18:50:11 +00:00
|
|
|
DPRINTF( "In-place finish.\n" );
|
2009-03-26 19:00:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-28 15:59:39 +00:00
|
|
|
ArticleResourceReply::~ArticleResourceReply()
|
|
|
|
{
|
|
|
|
req->cancel();
|
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
void ArticleResourceReply::reqUpdated()
|
|
|
|
{
|
|
|
|
emit readyRead();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArticleResourceReply::reqFinished()
|
|
|
|
{
|
|
|
|
emit readyRead();
|
|
|
|
finishedSlot();
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
qint64 ArticleResourceReply::bytesAvailable() const
|
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
long avail = req->dataSize();
|
|
|
|
|
|
|
|
if ( avail < 0 )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return (size_t) avail - alreadyRead + QNetworkReply::bytesAvailable();
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
qint64 ArticleResourceReply::readData( char * out, qint64 maxSize )
|
|
|
|
{
|
2013-08-04 19:19:57 +00:00
|
|
|
// From the doc: "This function might be called with a maxSize of 0,
|
|
|
|
// which can be used to perform post-reading operations".
|
|
|
|
if ( maxSize == 0 )
|
|
|
|
return 0;
|
|
|
|
|
2011-06-19 18:50:11 +00:00
|
|
|
DPRINTF( "====reading %d bytes\n", (int)maxSize );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
bool finished = req->isFinished();
|
|
|
|
|
|
|
|
long avail = req->dataSize();
|
|
|
|
|
|
|
|
if ( avail < 0 )
|
|
|
|
return finished ? -1 : 0;
|
|
|
|
|
|
|
|
size_t left = (size_t) avail - alreadyRead;
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
size_t toRead = maxSize < left ? maxSize : left;
|
|
|
|
|
2013-09-19 19:43:16 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
req->getDataSlice( alreadyRead, toRead, out );
|
|
|
|
}
|
|
|
|
catch( std::exception & e )
|
|
|
|
{
|
2013-09-20 14:25:44 +00:00
|
|
|
qWarning( "getDataSlice error: %s\n", e.what() );
|
2013-09-19 19:43:16 +00:00
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
alreadyRead += toRead;
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( !toRead && finished )
|
2009-01-28 20:55:45 +00:00
|
|
|
return -1;
|
|
|
|
else
|
|
|
|
return toRead;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArticleResourceReply::readyReadSlot()
|
|
|
|
{
|
|
|
|
readyRead();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArticleResourceReply::finishedSlot()
|
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( req->dataSize() < 0 )
|
|
|
|
error( ContentNotFoundError );
|
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
finished();
|
|
|
|
}
|
|
|
|
|
2009-08-31 12:18:08 +00:00
|
|
|
BlockedNetworkReply::BlockedNetworkReply( QObject * parent ): QNetworkReply( parent )
|
|
|
|
{
|
|
|
|
setError( QNetworkReply::ContentOperationNotPermittedError, "Content Blocked" );
|
|
|
|
|
|
|
|
connect( this, SIGNAL( finishedSignal() ), this, SLOT( finishedSlot() ),
|
|
|
|
Qt::QueuedConnection );
|
|
|
|
|
|
|
|
emit finishedSignal(); // This way we call readyRead()/finished() sometime later
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BlockedNetworkReply::finishedSlot()
|
|
|
|
{
|
|
|
|
emit readyRead();
|
|
|
|
emit finished();
|
|
|
|
}
|