PageRenderTime 141ms CodeModel.GetById 35ms app.highlight 68ms RepoModel.GetById 33ms app.codeStats 0ms

/src/web/api_v1.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 409 lines | 308 code | 71 blank | 30 comment | 38 complexity | 69f966ecc30ca87381d33738788e8ec0 MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
  5 *   Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
  6 *
  7 *   Tomahawk is free software: you can redistribute it and/or modify
  8 *   it under the terms of the GNU General Public License as published by
  9 *   the Free Software Foundation, either version 3 of the License, or
 10 *   (at your option) any later version.
 11 *
 12 *   Tomahawk is distributed in the hope that it will be useful,
 13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 *   GNU General Public License for more details.
 16 *
 17 *   You should have received a copy of the GNU General Public License
 18 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20
 21#include "Api_v1.h"
 22
 23#include "utils/Logger.h"
 24
 25#include "utils/TomahawkUtils.h"
 26#include "database/Database.h"
 27#include "database/DatabaseCommand_AddClientAuth.h"
 28#include "database/DatabaseCommand_ClientAuthValid.h"
 29#include "network/Servent.h"
 30#include "Pipeline.h"
 31#include "Source.h"
 32
 33#include <QHash>
 34
 35using namespace Tomahawk;
 36
 37Api_v1::Api_v1(QxtAbstractWebSessionManager* sm, QObject* parent)
 38    : QxtWebSlotService(sm, parent)
 39{
 40}
 41
 42
 43void
 44Api_v1::auth_1( QxtWebRequestEvent* event, QString arg )
 45{
 46    qDebug() << "AUTH_1 HTTP" << event->url.toString() << arg;
 47
 48    if ( !event->url.hasQueryItem( "website" ) || !event->url.hasQueryItem( "name" ) )
 49    {
 50        qDebug() << "Malformed HTTP resolve request";
 51        send404( event );
 52        return;
 53    }
 54
 55    QString formToken = uuid();
 56
 57    if ( event->url.hasQueryItem( "json" ) )
 58    {
 59        // JSON response
 60        QVariantMap m;
 61        m[ "formtoken" ] = formToken;
 62        sendJSON( m, event );
 63    }
 64    else
 65    {
 66        // webpage request
 67        QString authPage = RESPATH "www/auth.html";
 68        QHash< QString, QString > args;
 69        if ( event->url.hasQueryItem( "receiverurl" ) )
 70            args[ "url" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "receiverurl" ).toUtf8() );
 71
 72        args[ "formtoken" ] = formToken;
 73        args[ "website" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "website" ).toUtf8() );
 74        args[ "name" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "name" ).toUtf8() );
 75        sendWebpageWithArgs( event, authPage, args );
 76    }
 77}
 78
 79
 80void
 81Api_v1::auth_2( QxtWebRequestEvent* event, QString arg )
 82{
 83    qDebug() << "AUTH_2 HTTP" << event->url.toString() << arg;
 84    if ( event->content.isNull() )
 85    {
 86        qDebug() << "Null content";
 87        send404( event );
 88        return;
 89    }
 90
 91    QString params = QUrl::fromPercentEncoding( event->content->readAll() );
 92    params = params.mid( params.indexOf( '?' ) );
 93    QStringList pieces = params.split( '&' );
 94    QHash< QString, QString > queryItems;
 95
 96    foreach ( const QString& part, pieces )
 97    {
 98        QStringList keyval = part.split( '=' );
 99        if ( keyval.size() == 2 )
100            queryItems.insert( keyval.first(), keyval.last() );
101        else
102            qDebug() << "Failed parsing url parameters:" << part;
103    }
104
105    qDebug() << "has query items:" << pieces;
106    if ( !params.contains( "website" ) || !params.contains( "name" ) || !params.contains( "formtoken" ) )
107    {
108        qDebug() << "Malformed HTTP resolve request";
109        send404( event );
110        return;
111    }
112
113    QString website = queryItems[ "website" ];
114    QString name  = queryItems[ "name" ];
115    QByteArray authtoken = uuid().toLatin1();
116    qDebug() << "HEADERS:" << event->headers;
117    if ( !queryItems.contains( "receiverurl" ) || queryItems.value( "receiverurl" ).isEmpty() )
118    {
119        //no receiver url, so do it ourselves
120        if ( queryItems.contains( "json" ) )
121        {
122            QVariantMap m;
123            m[ "authtoken" ] = authtoken;
124
125            sendJSON( m, event );
126        }
127        else
128        {
129            QString authPage = RESPATH "www/auth.na.html";
130            QHash< QString, QString > args;
131            args[ "authcode" ] = authtoken;
132            args[ "website" ] = website;
133            args[ "name" ] = name;
134            sendWebpageWithArgs( event, authPage, args );
135        }
136    }
137    else
138    {
139        // do what the client wants
140        QUrl receiverurl = QUrl( queryItems.value( "receiverurl" ), QUrl::TolerantMode );
141        receiverurl.addEncodedQueryItem( "authtoken", "#" + authtoken );
142        qDebug() << "Got receiver url:" << receiverurl.toString();
143
144        QxtWebRedirectEvent* e = new QxtWebRedirectEvent( event->sessionID, event->requestID, receiverurl.toString() );
145        postEvent( e );
146        // TODO validation of receiverurl?
147    }
148
149    DatabaseCommand_AddClientAuth* dbcmd = new DatabaseCommand_AddClientAuth( authtoken, website, name, event->headers.key( "ua" ) );
150    Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(dbcmd) );
151}
152
153
154// all v1 api calls go to /api/
155void
156Api_v1::api( QxtWebRequestEvent* event )
157{
158    qDebug() << "HTTP" << event->url.toString();
159
160    const QUrl& url = event->url;
161    if ( url.hasQueryItem( "method" ) )
162    {
163        const QString method = url.queryItemValue( "method" );
164
165        if ( method == "stat" )        return stat( event );
166        if ( method == "resolve" )     return resolve( event );
167        if ( method == "get_results" ) return get_results( event );
168    }
169
170    send404( event );
171}
172
173
174// request for stream: /sid/<id>
175void
176Api_v1::sid( QxtWebRequestEvent* event, QString unused )
177{
178    Q_UNUSED( unused );
179
180    RID rid = event->url.path().mid( 5 );
181    qDebug() << "Request for sid" << rid;
182
183    result_ptr rp = Pipeline::instance()->result( rid );
184    if ( rp.isNull() )
185    {
186        return send404( event );
187    }
188
189    QSharedPointer<QIODevice> iodev = Servent::instance()->getIODeviceForUrl( rp );
190    if ( iodev.isNull() )
191    {
192        return send404( event ); // 503?
193    }
194
195    QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, iodev );
196    e->streaming = iodev->isSequential();
197    e->contentType = rp->mimetype().toAscii();
198    if ( rp->size() > 0 )
199        e->headers.insert( "Content-Length", QString::number( rp->size() ) );
200    postEvent( e );
201}
202
203
204void
205Api_v1::send404( QxtWebRequestEvent* event )
206{
207    qDebug() << "404" << event->url.toString();
208    QxtWebPageEvent* wpe = new QxtWebPageEvent( event->sessionID, event->requestID, "<h1>Not Found</h1>" );
209    wpe->status = 404;
210    wpe->statusMessage = "no event found";
211    postEvent( wpe );
212}
213
214
215void
216Api_v1::stat( QxtWebRequestEvent* event )
217{
218    qDebug() << "Got Stat request:" << event->url.toString();
219    m_storedEvent = event;
220
221    if ( !event->content.isNull() )
222        qDebug() << "BODY:" << event->content->readAll();
223
224    if ( event->url.hasQueryItem( "auth" ) )
225    {
226        // check for auth status
227        DatabaseCommand_ClientAuthValid* dbcmd = new DatabaseCommand_ClientAuthValid( event->url.queryItemValue( "auth" ) );
228        connect( dbcmd, SIGNAL( authValid( QString, QString, bool ) ), this, SLOT( statResult( QString, QString, bool ) ) );
229        Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(dbcmd) );
230    }
231    else
232    {
233        statResult( QString(), QString(), false );
234    }
235}
236
237
238void
239Api_v1::statResult( const QString& clientToken, const QString& name, bool valid )
240{
241    Q_UNUSED( clientToken )
242    Q_UNUSED( name )
243
244    Q_ASSERT( m_storedEvent );
245    if ( !m_storedEvent )
246        return;
247
248    QVariantMap m;
249    m.insert( "name", "playdar" );
250    m.insert( "version", "0.1.1" ); // TODO (needs to be >=0.1.1 for JS to work)
251    m.insert( "authenticated", valid ); // TODO
252    m.insert( "capabilities", QVariantList() );
253    sendJSON( m, m_storedEvent );
254
255    m_storedEvent = 0;
256}
257
258
259void
260Api_v1::resolve( QxtWebRequestEvent* event )
261{
262    if ( !event->url.hasQueryItem( "artist" ) ||
263         !event->url.hasQueryItem( "track" ) )
264    {
265        qDebug() << "Malformed HTTP resolve request";
266        return send404( event );
267    }
268
269    const QString artist = QUrl::fromPercentEncoding( event->url.queryItemValue( "artist" ).toUtf8() );
270    const QString track = QUrl::fromPercentEncoding( event->url.queryItemValue( "track" ).toUtf8() );
271    const QString album = QUrl::fromPercentEncoding( event->url.queryItemValue( "album" ).toUtf8() );
272
273    if ( artist.trimmed().isEmpty() ||
274         track.trimmed().isEmpty() )
275    {
276        qDebug() << "Malformed HTTP resolve request";
277        return send404( event );
278    }
279
280    QString qid;
281    if ( event->url.hasQueryItem( "qid" ) )
282        qid = event->url.queryItemValue( "qid" );
283    else
284        qid = uuid();
285
286    query_ptr qry = Query::get( artist, track, album, qid, false );
287    if ( qry.isNull() )
288    {
289        return send404( event );
290    }
291
292    Pipeline::instance()->resolve( qry, true, true );
293
294    QVariantMap r;
295    r.insert( "qid", qid );
296    sendJSON( r, event );
297}
298
299
300void
301Api_v1::staticdata( QxtWebRequestEvent* event, const QString& str )
302{
303    qDebug() << "STATIC request:" << event << str;
304    if ( str.contains( "tomahawk_auth_logo.png" ) )
305    {
306        QFile f( RESPATH "www/tomahawk_banner_small.png" );
307        f.open( QIODevice::ReadOnly );
308        QByteArray data = f.readAll();
309        QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, data );
310        e->contentType = "image/png";
311        postEvent( e );
312    }
313}
314
315
316void
317Api_v1::get_results( QxtWebRequestEvent* event )
318{
319    if ( !event->url.hasQueryItem( "qid" ) )
320    {
321        tDebug() << "Malformed HTTP get_results request";
322        send404( event );
323        return;
324    }
325
326    query_ptr qry = Pipeline::instance()->query( event->url.queryItemValue( "qid" ) );
327    if ( qry.isNull() )
328    {
329        send404( event );
330        return;
331    }
332
333    QVariantMap r;
334    r.insert( "qid", qry->id() );
335    r.insert( "poll_interval", 1300 );
336    r.insert( "refresh_interval", 1000 );
337    r.insert( "poll_limit", 14 );
338    r.insert( "solved", qry->playable() );
339    r.insert( "query", qry->toVariant() );
340
341    QVariantList res;
342    foreach( const result_ptr& rp, qry->results() )
343    {
344        if ( rp->isOnline() )
345            res << rp->toVariant();
346    }
347    r.insert( "results", res );
348
349    sendJSON( r, event );
350}
351
352
353void
354Api_v1::sendJSON( const QVariantMap& m, QxtWebRequestEvent* event )
355{
356    QJson::Serializer ser;
357    QByteArray ctype;
358    QByteArray body = ser.serialize( m );
359
360    if ( event->url.hasQueryItem("jsonp") && !event->url.queryItemValue( "jsonp" ).isEmpty() )
361    {
362        ctype = "text/javascript; charset=utf-8";
363        body.prepend( QString("%1( ").arg( event->url.queryItemValue( "jsonp" ) ).toAscii() );
364        body.append( " );" );
365    }
366    else
367    {
368        ctype = "appplication/json; charset=utf-8";
369    }
370
371    QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, body );
372    e->contentType = ctype;
373    e->headers.insert( "Content-Length", QString::number( body.length() ) );
374    postEvent( e );
375    qDebug() << "JSON response" << event->url.toString() << body;
376}
377
378
379// load an html template from a file, replace args from map
380// then serve
381void
382Api_v1::sendWebpageWithArgs( QxtWebRequestEvent* event, const QString& filenameSource, const QHash< QString, QString >& args )
383{
384    if ( !QFile::exists( filenameSource ) )
385        qWarning() << "Passed invalid file for html source:" << filenameSource;
386
387    QFile f( filenameSource );
388    f.open( QIODevice::ReadOnly );
389    QByteArray html = f.readAll();
390
391    foreach( const QString& param, args.keys() )
392    {
393        html.replace( QString( "<%%1%>" ).arg( param.toUpper() ), args.value( param ).toUtf8() );
394    }
395    // workaround for receiverurl
396    if ( !args.keys().contains( "URL" ) )
397        html.replace( QString( "<%URL%>" ).toLatin1(), QByteArray() );
398
399
400    QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, html );
401    postEvent( e );
402}
403
404
405void
406Api_v1::index( QxtWebRequestEvent* event )
407{
408    send404( event );
409}