@ -10,6 +10,7 @@
# include <exceptions.hxx>
# include <webpage.hxx>
# include <QNetworkReply>
# include <QAuthenticator>
# include <QCoreApplication>
# include <QStringList>
# include <QWebFrame>
@ -32,6 +33,20 @@
# include <cassert>
# include <xml-cxx/xml.hxx>
# ifdef HAVE_CXXABI_H
# include <cxxabi.h>
inline QString demangle ( const char * mangled ) {
int status ;
std : : unique_ptr < char [ ] , void ( * ) ( void * ) > result (
abi : : __cxa_demangle ( mangled , 0 , 0 , & status ) , free ) ;
return QString ( result . get ( ) ? result . get ( ) : mangled ) ;
}
# else
inline QString demangle ( const char * mangled ) {
return QString ( mangled ) ;
}
# endif
namespace std {
template < typename T > class optional {
private :
@ -99,7 +114,7 @@ class Command: public QObject {
QStringList & , QString , int , int ) = 0 ;
virtual bool execute ( Script * , QWebFrame * ) = 0 ;
static void error ( Logger & log , const Exception & e ) {
log ( QString ( " FAILED: " ) + e . what ( ) ) ;
log ( QString ( " FAILED[ " ) + demangle ( typeid ( e ) . name ( ) ) + " ]: " + e . what ( ) ) ;
throw e ;
}
void line ( int linenr ) {
@ -187,20 +202,38 @@ class Command: public QObject {
}
return commands ;
}
QStringList commaSeparatedList ( QString value ) {
value = value . trimmed ( ) ;
if ( ! value . size ( ) ) return QStringList ( ) ;
switch ( value . size ( ) > 1 & & value . at ( 0 ) = = value . at ( value . size ( ) - 1 )
? value . at ( 0 ) . toLatin1 ( ) : ' \0 ' ) {
case ' " ' : case ' \' ' : {
return value . mid ( 1 , value . size ( ) - 2 )
. split ( QRegularExpression ( QString ( value [ 0 ] ) + " , * "
+ QString ( value [ 0 ] ) ) ) ;
} break ;
default : {
return value . split ( QRegularExpression ( " , * " ) ) ;
QStringList quotedStrings ( QString value ,
QString delimiter = " " ,
bool keepDelimiters = false ) {
QStringList res ;
QString quot ( " ' \" " ) ;
while ( value = value . trimmed ( ) , value . size ( ) ) {
QRegularExpression re ;
int start ( 0 ) ;
if ( quot . contains ( value [ 0 ] ) ) {
re = QRegularExpression ( value [ 0 ] + " *(( " + delimiter + " *)|$) " ) ;
start = 1 ;
} else {
re = QRegularExpression ( " ( * " + delimiter + " *)|$ " ) ;
}
int pos ( value . indexOf ( re , start ) ) ;
if ( pos < start ) throw BadArgument ( " quote missmatch in " + value ) ;
QRegularExpressionMatch m ( re . match ( value , start ) ) ;
res + = value . mid ( start , m . capturedStart ( ) - start ) ;
value . remove ( 0 , m . capturedEnd ( ) ) ;
if ( keepDelimiters & & m . capturedLength ( ) ) res + = m . captured ( ) . mid ( start ) . trimmed ( ) ;
std : : cout < < " REMOVE: \" " < < m . captured ( ) < < " \" 0 - " < < ( m . capturedEnd ( ) + start )
< < " start= " < < start < < " pos= " < < pos < < std : : endl
< < " REMAINING: \" " < < value < < " \" " < < std : : endl ;
}
std : : cout < < " FOUND " < < std : : endl ;
Q_FOREACH ( QString tag , res ) {
std : : cout < < " - \" " < < tag < < " \" " < < std : : endl ;
}
return res ;
}
QStringList commaSeparatedList ( QString value ) {
return quotedStrings ( value , " , " ) ;
}
static QWebElement find ( QWebFrame * frame , QString selector ,
int repeat = 2 , int sleepsec = 1 ) {
@ -376,15 +409,15 @@ class RunDownload: public QObject {
public :
RunDownload ( QNetworkReply * reply , QString filename ) :
_reply ( reply ) , _file ( filename ) {
connect ( _reply , SIGNAL ( finished ( ) ) , SLOT ( finished ( ) ) ) ;
connect ( _reply , SIGNAL ( downloadProgress ( qint64 , qint64 ) ) ,
SLOT ( downloadProgress ( qint64 , qint64 ) ) ) ;
assert ( connect ( _reply , SIGNAL ( finished ( ) ) , SLOT ( finished ( ) ) ) ) ;
assert ( connect ( _reply , SIGNAL ( downloadProgress ( qint64 , qint64 ) ) ,
SLOT ( downloadProgress ( qint64 , qint64 ) ) ) ) ;
_file . open ( QIODevice : : WriteOnly ) ;
}
~ RunDownload ( ) {
disconnect ( _reply , SIGNAL ( finished ( ) ) , this , SLOT ( finished ( ) ) ) ;
disconnect ( _reply , SIGNAL ( downloadProgress ( qint64 , qint64 ) ) ,
this , SLOT ( downloadProgress ( qint64 , qint64 ) ) ) ;
assert ( disconnect ( _reply , SIGNAL ( finished ( ) ) , this , SLOT ( finished ( ) ) ) ) ;
assert ( disconnect ( _reply , SIGNAL ( downloadProgress ( qint64 , qint64 ) ) ,
this , SLOT ( downloadProgress ( qint64 , qint64 ) ) ) ) ;
delete _reply ;
}
Q_SIGNALS :
@ -454,7 +487,8 @@ class Script: public QObject {
. replace ( " " , " " ) . replace ( " & " , " & " ) ;
}
public :
Script ( ) : _clicktype ( JAVASCRIPT_CLICK ) , _command ( 0 ) , _screenshots ( true ) {
Script ( ) : _clicktype ( JAVASCRIPT_CLICK ) , _command ( 0 ) , _screenshots ( true ) ,
_defaultTimeout ( 20 ) {
initPrototypes ( ) ;
}
Script ( const Script & o ) :
@ -535,7 +569,7 @@ class Script: public QObject {
_variables . clear ( ) ;
_rvariables . clear ( ) ;
_functions . clear ( ) ;
_timeout = 20 ;
_timeout = _defaultTimeout ;
_clicktype = JAVASCRIPT_CLICK ;
}
std : : shared_ptr < Command > parseLine ( QStringList & in ,
@ -586,7 +620,7 @@ class Script: public QObject {
int maxretries = 0 ) {
bool res ( true ) ;
_testsuites = testsuites ;
_timeout = 20 ; // defaults to 20s
_timeout = _defaultTimeout ; // defaults to 20s
_ignoreSignalsUntil . clear ( ) ;
addSignals ( frame ) ;
_screenshots = screenshots ;
@ -815,6 +849,7 @@ class Script: public QObject {
_variables = o . _variables ;
_rvariables = o . _rvariables ;
_timeout = o . _timeout ;
_defaultTimeout = o . _timeout ;
_clicktype = o . _clicktype ;
_testsuites = o . _testsuites ;
_testclass = o . _testclass ;
@ -845,6 +880,15 @@ class Script: public QObject {
void timeout ( int t ) {
_timeout = t ;
}
void defaultTimeout ( int t ) {
_defaultTimeout = t ;
}
void auth ( const QString & realm , const QString & username , const QString & password ) {
if ( ! username . isEmpty ( ) & & ! password . isEmpty ( ) )
_auth [ realm ] = { username , password } ;
else if ( _auth . contains ( realm ) )
_auth . erase ( _auth . find ( realm ) ) ;
}
void clicktype ( ClickType c ) {
_clicktype = c ;
}
@ -876,33 +920,39 @@ class Script: public QObject {
return QString ( ) ;
}
void addSignals ( QWebFrame * frame ) {
connect ( dynamic_cast < NetworkAccessManager * >
assert ( connect ( dynamic_cast < NetworkAccessManager * >
( frame - > page ( ) - > networkAccessManager ( ) ) ,
SIGNAL ( log ( QString ) ) ,
SLOT ( log ( QString ) ) ) ;
connect ( frame , SIGNAL ( contentsSizeChanged ( const QSize & ) ) ,
SLOT ( contentsSizeChanged ( const QSize & ) ) ) ;
connect ( frame , SIGNAL ( iconChanged ( ) ) ,
SLOT ( iconChanged ( ) ) ) ;
connect ( frame , SIGNAL ( initialLayoutCompleted ( ) ) ,
SLOT ( initialLayoutCompleted ( ) ) ) ;
connect ( frame , SIGNAL ( javaScriptWindowObjectCleared ( ) ) ,
SLOT ( javaScriptWindowObjectCleared ( ) ) ) ;
connect ( frame , SIGNAL ( loadFinished ( bool ) ) ,
SLOT ( loadFinished ( bool ) ) ) ;
connect ( frame , SIGNAL ( loadStarted ( ) ) ,
SLOT ( loadStarted ( ) ) ) ;
connect ( frame , SIGNAL ( titleChanged ( const QString & ) ) ,
SLOT ( titleChanged ( const QString & ) ) ) ;
connect ( frame , SIGNAL ( urlChanged ( const QUrl & ) ) ,
SLOT ( urlChanged ( const QUrl & ) ) ) ;
connect ( & _timer , SIGNAL ( timeout ( ) ) , SLOT ( timeout ( ) ) ) ;
SLOT ( log ( QString ) ) ) ) ;
assert ( connect ( frame - > page ( ) - > networkAccessManager ( ) ,
SIGNAL ( authenticationRequired ( QNetworkReply * , QAuthenticator * ) ) ,
SLOT ( authenticationRequired ( QNetworkReply * , QAuthenticator * ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( contentsSizeChanged ( const QSize & ) ) ,
SLOT ( contentsSizeChanged ( const QSize & ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( iconChanged ( ) ) ,
SLOT ( iconChanged ( ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( initialLayoutCompleted ( ) ) ,
SLOT ( initialLayoutCompleted ( ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( javaScriptWindowObjectCleared ( ) ) ,
SLOT ( javaScriptWindowObjectCleared ( ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( loadFinished ( bool ) ) ,
SLOT ( loadFinished ( bool ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( loadStarted ( ) ) ,
SLOT ( loadStarted ( ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( titleChanged ( const QString & ) ) ,
SLOT ( titleChanged ( const QString & ) ) ) ) ;
assert ( connect ( frame , SIGNAL ( urlChanged ( const QUrl & ) ) ,
SLOT ( urlChanged ( const QUrl & ) ) ) ) ;
assert ( connect ( & _timer , SIGNAL ( timeout ( ) ) , SLOT ( timeout ( ) ) ) ) ;
}
void removeSignals ( QWebFrame * frame ) {
disconnect ( dynamic_cast < NetworkAccessManager * >
( frame - > page ( ) - > networkAccessManager ( ) ) ,
SIGNAL ( log ( QString ) ) ,
this , SLOT ( log ( QString ) ) ) ;
disconnect ( frame - > page ( ) - > networkAccessManager ( ) ,
SIGNAL ( authenticationRequired ( QNetworkReply * , QAuthenticator * ) ) ,
this , SLOT ( authenticationRequired ( QNetworkReply * , QAuthenticator * ) ) ) ;
disconnect ( frame , SIGNAL ( contentsSizeChanged ( const QSize & ) ) ,
this , SLOT ( contentsSizeChanged ( const QSize & ) ) ) ;
disconnect ( frame , SIGNAL ( iconChanged ( ) ) ,
@ -931,9 +981,11 @@ class Script: public QObject {
for ( QChar & c : text ) if ( c < 32 & & c ! = ' \n ' ) c = ' ? ' ;
if ( cmd )
prefix + = QString ( " %2:%3%1 " )
. arg ( QString ( cmd - > indent ( ) , QChar ( ' ' ) ) )
. arg ( QString ( cmd - > indent ( ) + 2 , QChar ( ' ' ) ) )
. arg ( cmd - > file ( ) , 20 , QChar ( ' ' ) )
. arg ( cmd - > line ( ) , - 4 , 10 , QChar ( ' ' ) ) ;
else
prefix + = " .... " ;
text = prefix + text . split ( ' \n ' ) . join ( " \n " + prefix + " " ) ;
logging ( text ) ;
std : : cout < < text < < std : : endl < < std : : flush ;
@ -950,7 +1002,7 @@ class Script: public QObject {
}
private :
void error ( const Exception & e ) {
log ( QString ( " FAILED: " ) + e . what ( ) ) ;
log ( QString ( " FAILED[ " ) + demangle ( typeid ( e ) . name ( ) ) + " ]: " + e . what ( ) ) ;
throw e ;
}
std : : shared_ptr < Command > unknown ( QString command ) {
@ -965,6 +1017,15 @@ class Script: public QObject {
_prototypes [ c - > tag ( ) ] = std : : shared_ptr < Command > ( c ) ;
}
private Q_SLOTS :
void authenticationRequired ( QNetworkReply * , QAuthenticator * a ) {
if ( _auth . contains ( a - > realm ( ) ) ) {
log ( " network: login to " + a - > realm ( ) ) ;
a - > setUser ( _auth [ a - > realm ( ) ] . username ) ;
a - > setPassword ( _auth [ a - > realm ( ) ] . password ) ;
} else {
log ( " network: no credentials for " + a - > realm ( ) ) ;
}
}
void contentsSizeChanged ( const QSize & ) {
}
void iconChanged ( ) {
@ -1012,6 +1073,10 @@ class Script: public QObject {
error ( TimeOut ( ) ) ;
}
private :
struct AuthRealm {
QString username ;
QString password ;
} ;
typedef std : : map < QString , std : : shared_ptr < Command > > Prototypes ;
typedef std : : vector < std : : shared_ptr < Command > > Commands ;
Prototypes _prototypes ;
@ -1028,12 +1093,14 @@ class Script: public QObject {
QMap < LenString , LenString > _rvariables ; ///< reverse variable mapping
QMap < QString , std : : shared_ptr < Function > > _functions ;
int _timeout ;
int _defaultTimeout ;
ClickType _clicktype ;
QString _targetdir ;
std : : shared_ptr < xml : : Node > _testsuites ; ///< only valid within run
QString _testclass ;
Command * _command ;
QString _path ;
QMap < QString , AuthRealm > _auth ;
} ;
class Do : public Command {
@ -1587,8 +1654,8 @@ class Download: public Command {
_realfilename = script - > replacevars ( _filename ) ;
log ( " REALFILENAME= " + _realfilename ) ;
frame - > page ( ) - > setForwardUnsupportedContent ( true ) ;
connect ( frame - > page ( ) , SIGNAL ( unsupportedContent ( QNetworkReply * ) ) ,
this , SLOT ( unsupportedContent ( QNetworkReply * ) ) ) ;
assert ( connect ( frame - > page ( ) , SIGNAL ( unsupportedContent ( QNetworkReply * ) ) ,
this , SLOT ( unsupportedContent ( QNetworkReply * ) ) ) ) ;
try {
bool res ( _next - > execute ( script , frame ) ) ; // start download
script - > timer ( ) . stop ( ) ; // no timeout during download
@ -1626,8 +1693,8 @@ class Download: public Command {
}
}
}
connect ( new RunDownload ( reply , _realfilename ) ,
SIGNAL ( completed ( bool , bool ) ) , SLOT ( completed ( bool , bool ) ) ) ;
assert ( connect ( new RunDownload ( reply , _realfilename ) ,
SIGNAL ( completed ( bool , bool ) ) , SLOT ( completed ( bool , bool ) ) ) ) ;
}
private :
QString _filename ;
@ -2312,14 +2379,22 @@ class Check: public Command {
int indent ) {
std : : shared_ptr < Check > cmd ( new Check ( ) ) ;
cmd - > _next = 0 ;
int pos ( args . indexOf ( QRegularExpression ( " [=!.^~<>] " ) ) ) ;
if ( pos < 0 ) throw BadArgument ( tag ( ) + " needs a comparision, not: " + args ) ;
cmd - > _value1 = args . left ( pos ) . trimmed ( ) ;
cmd - > _cmp = args [ pos ] . toLatin1 ( ) ;
cmd - > _value2 = args . mid ( pos + 1 ) . trimmed ( ) ;
if ( in . size ( ) & & in . first ( ) . contains ( QRegularExpression ( " ^ " ) ) ) {
cmd - > _next = script - > parseLine ( in , file , line + 1 , indent + 1 ) ;
cmd - > _next - > log ( false ) ; // suppress logging of subcommand
QString comp ( " [=!.^~<>] " ) ;
QStringList allargs = quotedStrings ( args , comp , true ) ;
if ( allargs . size ( ) < 2 | | allargs [ 1 ] . size ( ) ! = 1 | |
! QRegularExpression ( " ^ " + comp + " $ " ) . match ( allargs [ 1 ] ) . hasMatch ( ) )
throw BadArgument ( tag ( ) + " needs a comparision, not: " + args ) ;
if ( allargs . size ( ) > 3 )
throw BadArgument ( tag ( ) + " has at most three arguments " ) ;
cmd - > _value1 = allargs [ 0 ] ;
cmd - > _cmp = allargs [ 1 ] [ 0 ] . toLatin1 ( ) ;
if ( allargs . size ( ) = = 3 ) {
cmd - > _value2 = allargs [ 2 ] ;
} else {
if ( in . size ( ) & & in . first ( ) . contains ( QRegularExpression ( " ^ " ) ) ) {
cmd - > _next = script - > parseLine ( in , file , line + 1 , indent + 1 ) ;
cmd - > _next - > log ( false ) ; // suppress logging of subcommand
} else throw BadArgument ( tag ( ) + " needs a third argument or a following command " ) ;
}
return cmd ;
}
@ -2704,6 +2779,49 @@ class Fail: public Command {
QString _text ;
} ;
class Auth : public Command {
public :
QString tag ( ) const {
return " auth " ;
}
QString description ( ) const {
return
tag ( ) + " <realm> <username> <password> "
" \n \n " +
tag ( ) + " <realm> "
" \n \n "
" Set basic authentication credentials for <realm> to "
" <username> and <password>. If no realm is given, "
" the credentials for the given realm are removed. " ;
}
QString command ( ) const {
return tag ( ) + " " + _username + " " + _password ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Auth > cmd ( new Auth ( ) ) ;
QStringList allargs = args . split ( " " ) ;
if ( ! allargs . size ( ) ) throw BadArgument ( " requires at least a <realm> " ) ;
cmd - > _realm = allargs . takeFirst ( ) ;
if ( allargs . size ( ) & & allargs . size ( ) = = 2 ) {
cmd - > _username = allargs [ 0 ] ;
cmd - > _password = allargs [ 1 ] ;
} else {
throw BadArgument ( QString ( " requires <username> and <password>, but %1 was given " )
. arg ( args ) ) ;
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
script - > auth ( _realm , _username , _password ) ;
return true ;
}
private :
QString _realm ;
QString _username ;
QString _password ;
} ;
/* Template:
class : public Command {
@ -2786,8 +2904,8 @@ inline bool Command::runScript(Logger& log, Command* parentCommand,
scriptCopy . set ( * var , parent - > replacevars ( * arg ) ) ;
}
try {
connect ( & scriptCopy , SIGNAL ( logging ( QString ) ) ,
parent , SLOT ( parentlog ( QString ) ) ) ;
assert ( connect ( & scriptCopy , SIGNAL ( logging ( QString ) ) ,
parent , SLOT ( parentlog ( QString ) ) ) ) ;
parent - > removeSignals ( frame ) ;
bool res ( scriptCopy . run ( frame ) ) ;
parent - > addSignals ( frame ) ;
@ -2845,6 +2963,7 @@ inline void Script::initPrototypes() {
add ( new Include ) ;
add ( new Case ) ;
add ( new Fail ) ;
add ( new Auth ) ;
}
# endif