2
* rtspbase.cpp - very basic RTSP client/server
3
* Copyright (C) 2004 Justin Karneges
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Lesser General Public
7
* License as published by the Free Software Foundation; either
8
* version 2.1 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Lesser General Public License for more details.
15
* You should have received a copy of the GNU Lesser General Public
16
* License along with this library; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
#include <qguardedptr.h>
31
# include <netinet/in.h>
34
#define MAX_CONTENT_LENGTH 32767
38
//----------------------------------------------------------------------------
40
//----------------------------------------------------------------------------
41
HeaderList::HeaderList()
46
bool HeaderList::has(const QString &var) const
48
QString low = var.lower();
49
for(ConstIterator it = begin(); it != end(); ++it)
51
if((*it).name.lower() == low)
57
QString HeaderList::get(const QString &var) const
59
QString low = var.lower();
60
for(ConstIterator it = begin(); it != end(); ++it)
62
if((*it).name.lower() == low)
68
void HeaderList::set(const QString &var, const QString &val)
70
QString low = var.lower();
71
for(Iterator it = begin(); it != end(); ++it)
73
if((*it).name.lower() == low)
85
void HeaderList::remove(const QString &var)
87
QString low = var.lower();
88
for(Iterator it = begin(); it != end(); ++it)
90
if((*it).name.lower() == low)
92
QValueList<Var>::remove(it);
98
//----------------------------------------------------------------------------
100
//----------------------------------------------------------------------------
101
static int findOneOf(const QString &s, const QString &list, int i, QChar *found)
103
for(int n = i; n < (int)s.length(); ++n)
105
for(int k = 0; k < (int)list.length(); ++k)
118
Transport::Transport()
122
QString Transport::name() const
127
TransportArgs Transport::arguments() const
132
QString Transport::argument(const QString &name) const
134
for(TransportArgs::ConstIterator it = _args.begin(); it != _args.end(); ++it)
140
return QString::null;
143
void Transport::setArguments(const TransportArgs &list)
148
void Transport::setArgument(const QString &name, const QString &value)
150
for(TransportArgs::Iterator it = _args.begin(); it != _args.end(); ++it)
161
QString Transport::toString() const
167
for(TransportArgs::ConstIterator it = _args.begin(); it != _args.end(); ++it)
171
if(!(*it).value.isNull())
181
Transport::ReadStatus Transport::readFromString(QString *sp)
183
const QString &s = *sp;
185
QValueList<Var> targs;
187
// first read the name
190
int n = findOneOf(s, ",;", at, &c);
194
// empty at end means nothing left
202
tname = s.mid(at, n - at);
203
// empty with separator is bad
211
tname = s.mid(at, n - at);
212
// empty with separator is bad
224
n = findOneOf(s, ",;=", at, &c);
228
// empty at end means nothing left
237
targ = s.mid(at, n - at);
238
// empty with separator is bad
247
targ = s.mid(at, n - at);
248
// empty with separator is bad
256
// this arg has a value
257
targ = s.mid(at, n - at);
258
// empty with value is bad
264
// is next char a quote?
265
if(at < (int)s.length() && s[at] == '\"')
267
n = s.find('\"', at + 1);
270
tval = s.mid(at, n - at + 1);
273
if(at < (int)s.length())
275
// if not at end, the next char better be a separator
276
if(s[at] != ',' && s[at] != ';')
290
// find next separator
291
n = findOneOf(s, ",;", at, &c);
300
tval = s.mid(at, n - at);
306
tval = s.mid(at, n - at);
322
// remove what we just parsed
327
//----------------------------------------------------------------------------
329
//----------------------------------------------------------------------------
330
TransportList::TransportList()
331
:QValueList<Transport>()
335
QString TransportList::toString() const
338
for(ConstIterator it = begin(); it != end(); ++it)
339
list.append((*it).toString());
340
return list.join(",");
343
bool TransportList::fromString(const QString &s)
351
Transport::ReadStatus r = t.readFromString(&tmp);
352
if(r == Transport::Error)
354
if(r == Transport::Done)
362
//----------------------------------------------------------------------------
364
//----------------------------------------------------------------------------
370
Packet::Packet(const QString &command, const QString &resource, const HeaderList &headers)
378
Packet::Packet(int responseCode, const QString &responseString, const HeaderList &headers)
380
rcode = responseCode;
381
rstr = responseString;
390
bool Packet::isNull() const
395
Packet::Type Packet::type() const
400
QString Packet::version() const
405
QString Packet::command() const
410
QString Packet::resource() const
415
int Packet::responseCode() const
420
QString Packet::responseString() const
425
int Packet::channel() const
430
QByteArray Packet::data() const
435
HeaderList & Packet::headers()
440
const HeaderList & Packet::headers() const
445
TransportList Packet::transports() const
448
list.fromString(_headers.get("Transport"));
452
void Packet::setTransports(const TransportList &list)
454
_headers.set("Transport", list.toString());
457
void Packet::setResource(const QString &s)
462
QByteArray Packet::toArray() const
468
buf.resize(4 + _data.size());
472
ushort ssa = _data.size();
473
ushort ssb = htons(ssa);
474
memcpy(buf.data() + 2, &ssb, 2);
475
memcpy(buf.data() + 4, _data.data(), _data.size());
483
str += cmd + ' ' + res + " RTSP/" + ver + '\n';
484
for(HeaderList::ConstIterator it = _headers.begin(); it != _headers.end(); ++it)
485
str += (*it).name + ": " + (*it).value + '\n';
488
else if(t == Response)
490
str += "RTSP/" + ver + ' ' + QString::number(rcode) + ' ' + rstr + '\n';
491
for(HeaderList::ConstIterator it = _headers.begin(); it != _headers.end(); ++it)
492
str += (*it).name + ": " + (*it).value + '\n';
496
QCString cs = str.utf8();
497
buf.resize(cs.length());
498
memcpy(buf.data(), cs.data(), buf.size());
499
ByteStream::appendArray(&buf, _data);
505
//----------------------------------------------------------------------------
507
//----------------------------------------------------------------------------
512
void Parser::reset(Mode _mode)
521
void Parser::appendData(const QByteArray &a)
523
ByteStream::appendArray(&in, a);
526
Packet Parser::read(bool *ok)
544
list.remove(list.begin());
549
bool Parser::readPacket()
551
bool interleaved = false;
554
// don't know what it is yet? let's have a look
555
if(tmp.t == Packet::Empty)
557
// need at least 1 byte
567
memcpy(&ss, in.data() + 2, 2);
568
int size = ntohs(ss);
569
if((int)in.size() < 4 + size)
572
QByteArray buf = ByteStream::takeArray(&in, 4 + size);
573
tmp.t = Packet::Data;
575
tmp._data.resize(size);
576
memcpy(tmp._data.data(), buf.data() + 4, size);
583
// regular request/response
585
tmp.t = Packet::Request;
587
tmp.t = Packet::Response;
588
readingContent = false;
597
QStringList lines = readPacketLines();
600
if(!packetFromLines(lines))
602
if(tmp._headers.has("Content-length"))
604
clen = tmp._headers.get("Content-length").toInt();
605
if(clen > MAX_CONTENT_LENGTH)
610
readingContent = true;
617
int need = clen - tmp._data.size();
619
if((int)in.size() >= need)
620
a = ByteStream::takeArray(&in, need);
622
a = ByteStream::takeArray(&in);
623
ByteStream::appendArray(&tmp._data, a);
626
if(clen == 0 || (int)tmp._data.size() == clen)
640
bool Parser::packetFromLines(const QStringList &lines)
646
QString str = lines[0];
647
int n = str.find(' ');
650
QString c = str.mid(0, n);
652
n = str.find(' ', at);
655
QString r = str.mid(at, n - at);
656
QString v = str.mid(n + 1);
658
if(v.left(5) != "RTSP/")
662
if(!readHeaders(lines, &headers))
668
tmp._headers = headers;
675
QString str = lines[0];
676
int n = str.find(' ');
679
QString v = str.mid(0, n);
681
n = str.find(' ', at);
684
QString c = str.mid(at, n - at);
685
QString s = str.mid(n + 1);
686
if(v.left(5) != "RTSP/")
690
if(!readHeaders(lines, &headers))
694
tmp.rcode = c.toInt();
696
tmp._headers = headers;
701
bool Parser::readHeaders(const QStringList &lines, HeaderList *headers)
703
// skip the first line
704
QStringList::ConstIterator it = lines.begin();
708
for(; it != lines.end(); ++it)
710
const QString &s = *it;
715
v.name = s.mid(0, n).stripWhiteSpace();
716
v.value = s.mid(n + 1).stripWhiteSpace();
722
QStringList Parser::readPacketLines()
727
QString str = tryReadLine();
743
QString Parser::tryReadLine()
745
for(int n = 0; n < (int)in.size(); ++n)
750
if(n > 0 && in[n-1] == '\r')
752
QByteArray buf = ByteStream::takeArray(&in, eat);
755
memcpy(cs.data(), buf.data(), n);
756
return QString::fromUtf8(cs);
759
return QString::null;
762
//----------------------------------------------------------------------------
764
//----------------------------------------------------------------------------
765
class Client::Private
781
QValueList<int> trackQueue;
784
Client::Client(QObject *parent)
790
Client::Client(int socket)
793
d->using_sock = true;
794
BSocket *sock = new BSocket;
798
d->parser.reset(Parser::Client); // as a server, we want to parse client requests
799
sock->setSocket(socket);
810
connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed()));
811
connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead()));
812
connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int)));
813
connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int)));
816
void Client::processPackets()
818
QGuardedPtr<QObject> self = this;
822
Packet p = d->parser.read(&ok);
840
void Client::connectToHost(const QString &host, int port)
842
BSocket *sock = new BSocket;
843
connect(sock, SIGNAL(connected()), SLOT(sock_connected()));
847
sock->connectToHost(host, port);
850
void Client::setByteStream(ByteStream *bs, Mode mode)
855
d->parser.reset(mode == MClient ? Parser::Server : Parser::Client);
864
void Client::write(const Packet &p)
866
QByteArray buf = p.toArray();
867
d->trackQueue.append(buf.size());
871
QHostAddress Client::peerAddress() const
875
addr = ((BSocket *)d->bs)->peerAddress();
879
void Client::sock_connected()
881
d->using_sock = true;
883
d->parser.reset(Parser::Server); // as a client, we want to parse server responses
887
void Client::bs_connectionClosed()
892
void Client::bs_readyRead()
894
QByteArray buf = d->bs->read();
895
d->parser.appendData(buf);
900
void Client::bs_bytesWritten(int bytes)
903
for(QValueList<int>::Iterator it = d->trackQueue.begin(); it != d->trackQueue.end();)
913
it = d->trackQueue.remove(it);
917
for(int n = 0; n < written; ++n)
921
void Client::bs_error(int x)
928
if(x == BSocket::ErrConnectionRefused || x == BSocket::ErrHostNotFound)
945
//----------------------------------------------------------------------------
947
//----------------------------------------------------------------------------
948
class Server::Private
954
QPtrList<Client> incomingConns;
957
Server::Server(QObject *parent)
961
connect(&d->serv, SIGNAL(connectionReady(int)), SLOT(connectionReady(int)));
966
d->incomingConns.setAutoDelete(true);
967
d->incomingConns.clear();
971
bool Server::isActive() const
973
return d->serv.isActive();
976
bool Server::start(int port)
978
return d->serv.listen(port);
986
int Server::port() const
988
return d->serv.port();
991
QHostAddress Server::address() const
993
return d->serv.address();
996
Client *Server::takeIncoming()
998
if(d->incomingConns.isEmpty())
1001
Client *c = d->incomingConns.getFirst();
1002
d->incomingConns.removeRef(c);
1004
// we don't care about errors anymore
1005
disconnect(c, SIGNAL(error(int)), this, SLOT(connectionError()));
1007
// don't serve the connection until the event loop, to give the caller a chance to map signals
1008
QTimer::singleShot(0, c, SLOT(serve()));
1013
void Server::connectionReady(int s)
1015
Client *c = new Client(s);
1016
connect(c, SIGNAL(error(int)), this, SLOT(connectionError()));
1017
d->incomingConns.append(c);
1021
void Server::connectionError()
1023
Client *c = (Client *)sender();
1024
d->incomingConns.removeRef(c);