/*************************************************************************** Proxy.cpp - description ------------------- begin : Nov 20 14:35:18 CEST 2003 copyright : (C) 2003 by Mark Kretschmann email : markey@web.de ***************************************************************************/ /*************************************************************************** * * * 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; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "titleproxy.h" #include #include #include #include #include #include "noatun/app.h" using namespace TitleProxy; static const uint MIN_PROXYPORT = 6700; static const uint MAX_PROXYPORT = 7777; static const int BUFSIZE = 32768; Proxy::Proxy( KURL url ) : TQObject() , m_url( url ) , m_initSuccess( true ) , m_metaInt( 0 ) , m_byteCount( 0 ) , m_metaLen( 0 ) , m_usedPort( 0 ) , m_pBuf( 0 ) { kdDebug(66666) << k_funcinfo << endl; m_pBuf = new char[ BUFSIZE ]; // Don't try to get metdata for ogg streams (different protocol) m_icyMode = url.path().endsWith( ".ogg" ) ? false : true; // If no port is specified, use default shoutcast port if ( m_url.port() < 1 ) m_url.setPort( 80 ); connect( &m_sockRemote, TQ_SIGNAL( error( int ) ), this, TQ_SLOT( connectError() ) ); connect( &m_sockRemote, TQ_SIGNAL( connected() ), this, TQ_SLOT( sendRequest() ) ); connect( &m_sockRemote, TQ_SIGNAL( readyRead() ), this, TQ_SLOT( readRemote() ) ); uint i = 0; Server* server = 0; for ( i = MIN_PROXYPORT; i <= MAX_PROXYPORT; i++ ) { server = new Server( i, this ); kdDebug(66666) << k_funcinfo << "Trying to bind to port: " << i << endl; if ( server->ok() ) // found a free port break; delete server; } if ( i > MAX_PROXYPORT ) { kdWarning(66666) << k_funcinfo << "Unable to find a free local port. Aborting." << endl; m_initSuccess = false; return; } m_usedPort = i; connect( server, TQ_SIGNAL( connected( int ) ), this, TQ_SLOT( accept( int ) ) ); } Proxy::~Proxy() { kdDebug(66666) << k_funcinfo << endl; delete[] m_pBuf; } ////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC ////////////////////////////////////////////////////////////////////////////////////////// KURL Proxy::proxyUrl() { if ( m_initSuccess ) { KURL url; url.setPort( m_usedPort ); url.setHost( "localhost" ); url.setProtocol( "http" ); return url; } else return m_url; } ////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE SLOTS ////////////////////////////////////////////////////////////////////////////////////////// void Proxy::accept( int socket ) //SLOT { //kdDebug(66666) << "BEGIN " << k_funcinfo << endl; m_sockProxy.setSocket( socket ); m_sockProxy.waitForMore( KProtocolManager::readTimeout() * 1000 ); connectToHost(); //kdDebug(66666) << "END " << k_funcinfo << endl; } void Proxy::connectToHost() //SLOT { //kdDebug(66666) << "BEGIN " << k_funcinfo << endl; { //initialisations m_connectSuccess = false; m_headerFinished = false; m_headerStr = ""; } { //connect to server TQTimer::singleShot( KProtocolManager::connectTimeout() * 1000, this, TQ_SLOT( connectError() ) ); kdDebug(66666) << k_funcinfo << "Connecting to " << m_url.host() << ":" << m_url.port() << endl; m_sockRemote.connectToHost( m_url.host(), m_url.port() ); } //kdDebug(66666) << "END " << k_funcinfo << endl; } void Proxy::sendRequest() //SLOT { //kdDebug(66666) << "BEGIN " << k_funcinfo << endl; TQCString username = m_url.user().utf8(); TQCString password = m_url.pass().utf8(); TQCString authString = KCodecs::base64Encode( username + ":" + password ); bool auth = !( username.isEmpty() && password.isEmpty() ); TQString request = TQString( "GET %1 HTTP/1.0\r\n" "Host: %2\r\n" "User-Agent: Noatun/%5\r\n" "%3" "%4" "\r\n" ) .arg( m_url.path( -1 ).isEmpty() ? "/" : m_url.path( -1 ) ) .arg( m_url.host() ) .arg( m_icyMode ? TQString( "Icy-MetaData:1\r\n" ) : TQString() ) .arg( auth ? TQString( "Authorization: Basic " ).append( authString ) : TQString() ) .arg( NOATUN_VERSION ); m_sockRemote.writeBlock( request.latin1(), request.length() ); //kdDebug(66666) << "END " << k_funcinfo << endl; } void Proxy::readRemote() //SLOT { m_connectSuccess = true; TQ_LONG index = 0; TQ_LONG bytesWrite = 0; TQ_LONG bytesRead = m_sockRemote.readBlock( m_pBuf, BUFSIZE ); if ( bytesRead == -1 ) { kdDebug(66666) << k_funcinfo << "Could not read remote data from socket, aborting" << endl; m_sockRemote.close(); emit proxyError(); return; } if ( !m_headerFinished ) { if ( !processHeader( index, bytesRead ) ) return; } //This is the main loop which processes the stream data while ( index < bytesRead ) { if ( m_icyMode && m_metaInt && ( m_byteCount == m_metaInt ) ) { m_byteCount = 0; m_metaLen = m_pBuf[ index++ ] << 4; } else if ( m_icyMode && m_metaLen ) { m_metaData.append( m_pBuf[ index++ ] ); --m_metaLen; if ( !m_metaLen ) { transmitData( m_metaData ); m_metaData = ""; } } else { bytesWrite = bytesRead - index; if ( m_icyMode && bytesWrite > m_metaInt - m_byteCount ) bytesWrite = m_metaInt - m_byteCount; bytesWrite = m_sockProxy.writeBlock( m_pBuf + index, bytesWrite ); if ( bytesWrite == -1 ) { error(); return; } index += bytesWrite; m_byteCount += bytesWrite; } } } void Proxy::connectError() //SLOT { if ( !m_connectSuccess ) { kdWarning(66666) << "TitleProxy error: Unable to connect to this stream " << "server. Can't play the stream!" << endl; emit proxyError(); } } ////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////////////////////// bool Proxy::processHeader( TQ_LONG &index, TQ_LONG bytesRead ) { while ( index < bytesRead ) { m_headerStr.append( m_pBuf[ index++ ] ); if ( m_headerStr.endsWith( "\r\n\r\n" ) ) { /*kdDebug(66666) << k_funcinfo << "Got shoutcast header: '" << m_headerStr << "'" << endl;*/ // Handle redirection TQString loc( "Location: " ); int index = m_headerStr.find( loc ); if ( index >= 0 ) { int start = index + loc.length(); int end = m_headerStr.find( "\n", index ); m_url = m_headerStr.mid( start, end - start - 1 ); kdDebug(66666) << k_funcinfo << "Stream redirected to: " << m_url << endl; m_sockRemote.close(); connectToHost(); return false; } if (m_headerStr.startsWith("ICY")) { m_metaInt = m_headerStr.section( "icy-metaint:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ).toInt(); m_bitRate = m_headerStr.section( "icy-br:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamName = m_headerStr.section( "icy-name:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamGenre = m_headerStr.section( "icy-genre:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamUrl = m_headerStr.section( "icy-url:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); } else // not ShoutCast { TQString serverName = m_headerStr.section( "Server:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); kdDebug(66666) << k_funcinfo << "Server name: " << serverName << endl; if (serverName == "Icecast") { m_metaInt = 0; m_streamName = m_headerStr.section( "ice-name:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamGenre = m_headerStr.section( "ice-genre:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamUrl = m_headerStr.section( "ice-url:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); } else if (serverName.startsWith("icecast/1.")) { m_metaInt = 0; m_bitRate = m_headerStr.section( "x-audiocast-bitrate:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamName = m_headerStr.section( "x-audiocast-name:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamGenre = m_headerStr.section( "x-audiocast-genre:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); m_streamUrl = m_headerStr.section( "x-audiocast-url:", 1, 1, TQString::SectionCaseInsensitiveSeps ).section( "\r", 0, 0 ); } } if ( m_streamUrl.startsWith( "www.", true ) ) m_streamUrl.prepend( "http://" ); m_sockProxy.writeBlock( m_headerStr.latin1(), m_headerStr.length() ); m_headerFinished = true; if ( m_icyMode && !m_metaInt ) { error(); return false; } connect( &m_sockRemote, TQ_SIGNAL( connectionClosed() ), this, TQ_SLOT( connectError() ) ); return true; } } return false; } void Proxy::transmitData( const TQString &data ) { /*kdDebug(66666) << k_funcinfo << " received new metadata: '" << data << "'" << endl;*/ //prevent metadata spam by ignoring repeated identical data //(some servers repeat it every 10 seconds) if ( data == m_lastMetadata ) return; m_lastMetadata = data; emit metaData( m_streamName, m_streamGenre, m_streamUrl, m_bitRate, extractStr(data, TQString::fromLatin1("StreamTitle")), extractStr(data, TQString::fromLatin1("StreamUrl"))); } void Proxy::error() { kdDebug(66666) << "TitleProxy error: Stream does not support shoutcast metadata. " << "Restarting in non-metadata mode." << endl; m_sockRemote.close(); m_icyMode = false; //open stream again, but this time without metadata, please connectToHost(); } TQString Proxy::extractStr( const TQString &str, const TQString &key ) { int index = str.find( key, 0, true ); if ( index == -1 ) { return TQString(); } else { index = str.find( "'", index ) + 1; int indexEnd = str.find( "'", index ); return str.mid( index, indexEnd - index ); } } #include "titleproxy.moc"