You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
10 KiB
377 lines
10 KiB
/***************************************************************************
|
|
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 <kdebug.h>
|
|
#include <tdeprotocolmanager.h>
|
|
#include <kmdcodec.h>
|
|
|
|
#include <tqstring.h>
|
|
#include <tqtimer.h>
|
|
#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, TQT_SIGNAL( error( int ) ), this, TQT_SLOT( connectError() ) );
|
|
connect( &m_sockRemote, TQT_SIGNAL( connected() ), this, TQT_SLOT( sendRequest() ) );
|
|
connect( &m_sockRemote, TQT_SIGNAL( readyRead() ), this, TQT_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, TQT_SIGNAL( connected( int ) ), this, TQT_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, TQT_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, TQT_SIGNAL( connectionClosed() ),
|
|
this, TQT_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"
|