/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. #include "Api_v1.h"
  21. #include "utils/Logger.h"
  22. #include "utils/TomahawkUtils.h"
  23. #include "database/Database.h"
  24. #include "database/DatabaseCommand_AddClientAuth.h"
  25. #include "database/DatabaseCommand_ClientAuthValid.h"
  26. #include "network/Servent.h"
  27. #include "Pipeline.h"
  28. #include "Source.h"
  29. #include <QHash>
  30. using namespace Tomahawk;
  31. Api_v1::Api_v1(QxtAbstractWebSessionManager* sm, QObject* parent)
  32. : QxtWebSlotService(sm, parent)
  33. {
  34. }
  35. void
  36. Api_v1::auth_1( QxtWebRequestEvent* event, QString arg )
  37. {
  38. qDebug() << "AUTH_1 HTTP" << event->url.toString() << arg;
  39. if ( !event->url.hasQueryItem( "website" ) || !event->url.hasQueryItem( "name" ) )
  40. {
  41. qDebug() << "Malformed HTTP resolve request";
  42. send404( event );
  43. return;
  44. }
  45. QString formToken = uuid();
  46. if ( event->url.hasQueryItem( "json" ) )
  47. {
  48. // JSON response
  49. QVariantMap m;
  50. m[ "formtoken" ] = formToken;
  51. sendJSON( m, event );
  52. }
  53. else
  54. {
  55. // webpage request
  56. QString authPage = RESPATH "www/auth.html";
  57. QHash< QString, QString > args;
  58. if ( event->url.hasQueryItem( "receiverurl" ) )
  59. args[ "url" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "receiverurl" ).toUtf8() );
  60. args[ "formtoken" ] = formToken;
  61. args[ "website" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "website" ).toUtf8() );
  62. args[ "name" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "name" ).toUtf8() );
  63. sendWebpageWithArgs( event, authPage, args );
  64. }
  65. }
  66. void
  67. Api_v1::auth_2( QxtWebRequestEvent* event, QString arg )
  68. {
  69. qDebug() << "AUTH_2 HTTP" << event->url.toString() << arg;
  70. if ( event->content.isNull() )
  71. {
  72. qDebug() << "Null content";
  73. send404( event );
  74. return;
  75. }
  76. QString params = QUrl::fromPercentEncoding( event->content->readAll() );
  77. params = params.mid( params.indexOf( '?' ) );
  78. QStringList pieces = params.split( '&' );
  79. QHash< QString, QString > queryItems;
  80. foreach ( const QString& part, pieces )
  81. {
  82. QStringList keyval = part.split( '=' );
  83. if ( keyval.size() == 2 )
  84. queryItems.insert( keyval.first(), keyval.last() );
  85. else
  86. qDebug() << "Failed parsing url parameters:" << part;
  87. }
  88. qDebug() << "has query items:" << pieces;
  89. if ( !params.contains( "website" ) || !params.contains( "name" ) || !params.contains( "formtoken" ) )
  90. {
  91. qDebug() << "Malformed HTTP resolve request";
  92. send404( event );
  93. return;
  94. }
  95. QString website = queryItems[ "website" ];
  96. QString name = queryItems[ "name" ];
  97. QByteArray authtoken = uuid().toLatin1();
  98. qDebug() << "HEADERS:" << event->headers;
  99. if ( !queryItems.contains( "receiverurl" ) || queryItems.value( "receiverurl" ).isEmpty() )
  100. {
  101. //no receiver url, so do it ourselves
  102. if ( queryItems.contains( "json" ) )
  103. {
  104. QVariantMap m;
  105. m[ "authtoken" ] = authtoken;
  106. sendJSON( m, event );
  107. }
  108. else
  109. {
  110. QString authPage = RESPATH "www/auth.na.html";
  111. QHash< QString, QString > args;
  112. args[ "authcode" ] = authtoken;
  113. args[ "website" ] = website;
  114. args[ "name" ] = name;
  115. sendWebpageWithArgs( event, authPage, args );
  116. }
  117. }
  118. else
  119. {
  120. // do what the client wants
  121. QUrl receiverurl = QUrl( queryItems.value( "receiverurl" ), QUrl::TolerantMode );
  122. receiverurl.addEncodedQueryItem( "authtoken", "#" + authtoken );
  123. qDebug() << "Got receiver url:" << receiverurl.toString();
  124. QxtWebRedirectEvent* e = new QxtWebRedirectEvent( event->sessionID, event->requestID, receiverurl.toString() );
  125. postEvent( e );
  126. // TODO validation of receiverurl?
  127. }
  128. DatabaseCommand_AddClientAuth* dbcmd = new DatabaseCommand_AddClientAuth( authtoken, website, name, event->headers.key( "ua" ) );
  129. Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(dbcmd) );
  130. }
  131. // all v1 api calls go to /api/
  132. void
  133. Api_v1::api( QxtWebRequestEvent* event )
  134. {
  135. qDebug() << "HTTP" << event->url.toString();
  136. const QUrl& url = event->url;
  137. if ( url.hasQueryItem( "method" ) )
  138. {
  139. const QString method = url.queryItemValue( "method" );
  140. if ( method == "stat" ) return stat( event );
  141. if ( method == "resolve" ) return resolve( event );
  142. if ( method == "get_results" ) return get_results( event );
  143. }
  144. send404( event );
  145. }
  146. // request for stream: /sid/<id>
  147. void
  148. Api_v1::sid( QxtWebRequestEvent* event, QString unused )
  149. {
  150. Q_UNUSED( unused );
  151. RID rid = event->url.path().mid( 5 );
  152. qDebug() << "Request for sid" << rid;
  153. result_ptr rp = Pipeline::instance()->result( rid );
  154. if ( rp.isNull() )
  155. {
  156. return send404( event );
  157. }
  158. QSharedPointer<QIODevice> iodev = Servent::instance()->getIODeviceForUrl( rp );
  159. if ( iodev.isNull() )
  160. {
  161. return send404( event ); // 503?
  162. }
  163. QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, iodev );
  164. e->streaming = iodev->isSequential();
  165. e->contentType = rp->mimetype().toAscii();
  166. if ( rp->size() > 0 )
  167. e->headers.insert( "Content-Length", QString::number( rp->size() ) );
  168. postEvent( e );
  169. }
  170. void
  171. Api_v1::send404( QxtWebRequestEvent* event )
  172. {
  173. qDebug() << "404" << event->url.toString();
  174. QxtWebPageEvent* wpe = new QxtWebPageEvent( event->sessionID, event->requestID, "<h1>Not Found</h1>" );
  175. wpe->status = 404;
  176. wpe->statusMessage = "no event found";
  177. postEvent( wpe );
  178. }
  179. void
  180. Api_v1::stat( QxtWebRequestEvent* event )
  181. {
  182. qDebug() << "Got Stat request:" << event->url.toString();
  183. m_storedEvent = event;
  184. if ( !event->content.isNull() )
  185. qDebug() << "BODY:" << event->content->readAll();
  186. if ( event->url.hasQueryItem( "auth" ) )
  187. {
  188. // check for auth status
  189. DatabaseCommand_ClientAuthValid* dbcmd = new DatabaseCommand_ClientAuthValid( event->url.queryItemValue( "auth" ) );
  190. connect( dbcmd, SIGNAL( authValid( QString, QString, bool ) ), this, SLOT( statResult( QString, QString, bool ) ) );
  191. Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(dbcmd) );
  192. }
  193. else
  194. {
  195. statResult( QString(), QString(), false );
  196. }
  197. }
  198. void
  199. Api_v1::statResult( const QString& clientToken, const QString& name, bool valid )
  200. {
  201. Q_UNUSED( clientToken )
  202. Q_UNUSED( name )
  203. Q_ASSERT( m_storedEvent );
  204. if ( !m_storedEvent )
  205. return;
  206. QVariantMap m;
  207. m.insert( "name", "playdar" );
  208. m.insert( "version", "0.1.1" ); // TODO (needs to be >=0.1.1 for JS to work)
  209. m.insert( "authenticated", valid ); // TODO
  210. m.insert( "capabilities", QVariantList() );
  211. sendJSON( m, m_storedEvent );
  212. m_storedEvent = 0;
  213. }
  214. void
  215. Api_v1::resolve( QxtWebRequestEvent* event )
  216. {
  217. if ( !event->url.hasQueryItem( "artist" ) ||
  218. !event->url.hasQueryItem( "track" ) )
  219. {
  220. qDebug() << "Malformed HTTP resolve request";
  221. return send404( event );
  222. }
  223. const QString artist = QUrl::fromPercentEncoding( event->url.queryItemValue( "artist" ).toUtf8() );
  224. const QString track = QUrl::fromPercentEncoding( event->url.queryItemValue( "track" ).toUtf8() );
  225. const QString album = QUrl::fromPercentEncoding( event->url.queryItemValue( "album" ).toUtf8() );
  226. if ( artist.trimmed().isEmpty() ||
  227. track.trimmed().isEmpty() )
  228. {
  229. qDebug() << "Malformed HTTP resolve request";
  230. return send404( event );
  231. }
  232. QString qid;
  233. if ( event->url.hasQueryItem( "qid" ) )
  234. qid = event->url.queryItemValue( "qid" );
  235. else
  236. qid = uuid();
  237. query_ptr qry = Query::get( artist, track, album, qid, false );
  238. if ( qry.isNull() )
  239. {
  240. return send404( event );
  241. }
  242. Pipeline::instance()->resolve( qry, true, true );
  243. QVariantMap r;
  244. r.insert( "qid", qid );
  245. sendJSON( r, event );
  246. }
  247. void
  248. Api_v1::staticdata( QxtWebRequestEvent* event, const QString& str )
  249. {
  250. qDebug() << "STATIC request:" << event << str;
  251. if ( str.contains( "tomahawk_auth_logo.png" ) )
  252. {
  253. QFile f( RESPATH "www/tomahawk_banner_small.png" );
  254. f.open( QIODevice::ReadOnly );
  255. QByteArray data = f.readAll();
  256. QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, data );
  257. e->contentType = "image/png";
  258. postEvent( e );
  259. }
  260. }
  261. void
  262. Api_v1::get_results( QxtWebRequestEvent* event )
  263. {
  264. if ( !event->url.hasQueryItem( "qid" ) )
  265. {
  266. tDebug() << "Malformed HTTP get_results request";
  267. send404( event );
  268. return;
  269. }
  270. query_ptr qry = Pipeline::instance()->query( event->url.queryItemValue( "qid" ) );
  271. if ( qry.isNull() )
  272. {
  273. send404( event );
  274. return;
  275. }
  276. QVariantMap r;
  277. r.insert( "qid", qry->id() );
  278. r.insert( "poll_interval", 1300 );
  279. r.insert( "refresh_interval", 1000 );
  280. r.insert( "poll_limit", 14 );
  281. r.insert( "solved", qry->playable() );
  282. r.insert( "query", qry->toVariant() );
  283. QVariantList res;
  284. foreach( const result_ptr& rp, qry->results() )
  285. {
  286. if ( rp->isOnline() )
  287. res << rp->toVariant();
  288. }
  289. r.insert( "results", res );
  290. sendJSON( r, event );
  291. }
  292. void
  293. Api_v1::sendJSON( const QVariantMap& m, QxtWebRequestEvent* event )
  294. {
  295. QJson::Serializer ser;
  296. QByteArray ctype;
  297. QByteArray body = ser.serialize( m );
  298. if ( event->url.hasQueryItem("jsonp") && !event->url.queryItemValue( "jsonp" ).isEmpty() )
  299. {
  300. ctype = "text/javascript; charset=utf-8";
  301. body.prepend( QString("%1( ").arg( event->url.queryItemValue( "jsonp" ) ).toAscii() );
  302. body.append( " );" );
  303. }
  304. else
  305. {
  306. ctype = "appplication/json; charset=utf-8";
  307. }
  308. QxtWebPageEvent * e = new QxtWebPageEvent( event->sessionID, event->requestID, body );
  309. e->contentType = ctype;
  310. e->headers.insert( "Content-Length", QString::number( body.length() ) );
  311. postEvent( e );
  312. qDebug() << "JSON response" << event->url.toString() << body;
  313. }
  314. // load an html template from a file, replace args from map
  315. // then serve
  316. void
  317. Api_v1::sendWebpageWithArgs( QxtWebRequestEvent* event, const QString& filenameSource, const QHash< QString, QString >& args )
  318. {
  319. if ( !QFile::exists( filenameSource ) )
  320. qWarning() << "Passed invalid file for html source:" << filenameSource;
  321. QFile f( filenameSource );
  322. f.open( QIODevice::ReadOnly );
  323. QByteArray html = f.readAll();
  324. foreach( const QString& param, args.keys() )
  325. {
  326. html.replace( QString( "<%%1%>" ).arg( param.toUpper() ), args.value( param ).toUtf8() );
  327. }
  328. // workaround for receiverurl
  329. if ( !args.keys().contains( "URL" ) )
  330. html.replace( QString( "<%URL%>" ).toLatin1(), QByteArray() );
  331. QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, html );
  332. postEvent( e );
  333. }
  334. void
  335. Api_v1::index( QxtWebRequestEvent* event )
  336. {
  337. send404( event );
  338. }