1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15**********/
16// "liveMedia"
17// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
18// A RTSP server
19// Implementation
20
21#include "RTSPServer.hh"
22#include "RTSPCommon.hh"
23#include "RTSPRegisterSender.hh"
24#include "Base64.hh"
25#include <GroupsockHelper.hh>
26
27////////// RTSPServer implementation //////////
28
29RTSPServer*
30RTSPServer::createNew(UsageEnvironment& env, Port ourPort,
31 UserAuthenticationDatabase* authDatabase,
32 unsigned reclamationSeconds) {
33 int ourSocket = setUpOurSocket(env, ourPort);
34 if (ourSocket == -1) return NULL;
35
36 return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationSeconds);
37}
38
39Boolean RTSPServer::lookupByName(UsageEnvironment& env,
40 char const* name,
41 RTSPServer*& resultServer) {
42 resultServer = NULL; // unless we succeed
43
44 Medium* medium;
45 if (!Medium::lookupByName(env, name, medium)) return False;
46
47 if (!medium->isRTSPServer()) {
48 env.setResultMsg(name, " is not a RTSP server");
49 return False;
50 }
51
52 resultServer = (RTSPServer*)medium;
53 return True;
54}
55
56char* RTSPServer
57::rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket) const {
58 char* urlPrefix = rtspURLPrefix(clientSocket);
59 char const* sessionName = serverMediaSession->streamName();
60
61 char* resultURL = new char[strlen(urlPrefix) + strlen(sessionName) + 1];
62 sprintf(resultURL, "%s%s", urlPrefix, sessionName);
63
64 delete[] urlPrefix;
65 return resultURL;
66}
67
68char* RTSPServer::rtspURLPrefix(int clientSocket) const {
69 struct sockaddr_in ourAddress;
70 if (clientSocket < 0) {
71 // Use our default IP address in the URL:
72 ourAddress.sin_addr.s_addr = ReceivingInterfaceAddr != 0
73 ? ReceivingInterfaceAddr
74 : ourIPAddress(envir()); // hack
75 } else {
76 SOCKLEN_T namelen = sizeof ourAddress;
77 getsockname(clientSocket, (struct sockaddr*)&ourAddress, &namelen);
78 }
79
80 char urlBuffer[100]; // more than big enough for "rtsp://<ip-address>:<port>/"
81
82 portNumBits portNumHostOrder = ntohs(fServerPort.num());
83 if (portNumHostOrder == 554 /* the default port number */) {
84 sprintf(urlBuffer, "rtsp://%s/", AddressString(ourAddress).val());
85 } else {
86 sprintf(urlBuffer, "rtsp://%s:%hu/",
87 AddressString(ourAddress).val(), portNumHostOrder);
88 }
89
90 return strDup(urlBuffer);
91}
92
93UserAuthenticationDatabase* RTSPServer::setAuthenticationDatabase(UserAuthenticationDatabase* newDB) {
94 UserAuthenticationDatabase* oldDB = fAuthDB;
95 fAuthDB = newDB;
96
97 return oldDB;
98}
99
100Boolean RTSPServer::setUpTunnelingOverHTTP(Port httpPort) {
101 fHTTPServerSocket = setUpOurSocket(envir(), httpPort);
102 if (fHTTPServerSocket >= 0) {
103 fHTTPServerPort = httpPort;
104 envir().taskScheduler().turnOnBackgroundReadHandling(fHTTPServerSocket,
105 incomingConnectionHandlerHTTP, this);
106 return True;
107 }
108
109 return False;
110}
111
112portNumBits RTSPServer::httpServerPortNum() const {
113 return ntohs(fHTTPServerPort.num());
114}
115
116char const* RTSPServer::allowedCommandNames() {
117 return "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER";
118}
119
120UserAuthenticationDatabase* RTSPServer::getAuthenticationDatabaseForCommand(char const* /*cmdName*/) {
121 // default implementation
122 return fAuthDB;
123}
124
125Boolean RTSPServer::specialClientAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/, char const* /*urlSuffix*/) {
126 // default implementation
127 return True;
128}
129
130Boolean RTSPServer::specialClientUserAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/,
131 char const* /*urlSuffix*/, char const * /*username*/) {
132 // default implementation; no further access restrictions:
133 return True;
134}
135
136
137RTSPServer::RTSPServer(UsageEnvironment& env,
138 int ourSocket, Port ourPort,
139 UserAuthenticationDatabase* authDatabase,
140 unsigned reclamationSeconds)
141 : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds),
142 fHTTPServerSocket(-1), fHTTPServerPort(0),
143 fClientConnectionsForHTTPTunneling(NULL), // will get created if needed
144 fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),
145 fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),
146 fRegisterOrDeregisterRequestCounter(0), fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(True) {
147}
148
149// A data structure that is used to implement "fTCPStreamingDatabase"
150// (and the "noteTCPStreamingOnSocket()" and "stopTCPStreamingOnSocket()" member functions):
151class streamingOverTCPRecord {
152public:
153 streamingOverTCPRecord(u_int32_t sessionId, unsigned trackNum, streamingOverTCPRecord* next)
154 : fNext(next), fSessionId(sessionId), fTrackNum(trackNum) {
155 }
156 virtual ~streamingOverTCPRecord() {
157 delete fNext;
158 }
159
160 streamingOverTCPRecord* fNext;
161 u_int32_t fSessionId;
162 unsigned fTrackNum;
163};
164
165RTSPServer::~RTSPServer() {
166 // Turn off background HTTP read handling (if any):
167 envir().taskScheduler().turnOffBackgroundReadHandling(fHTTPServerSocket);
168 ::closeSocket(fHTTPServerSocket);
169
170 cleanup(); // Removes all "ClientSession" and "ClientConnection" objects, and their tables.
171 delete fClientConnectionsForHTTPTunneling;
172
173 // Delete any pending REGISTER requests:
174 RTSPRegisterOrDeregisterSender* r;
175 while ((r = (RTSPRegisterOrDeregisterSender*)fPendingRegisterOrDeregisterRequests->getFirst()) != NULL) {
176 delete r;
177 }
178 delete fPendingRegisterOrDeregisterRequests;
179
180 // Empty out and close "fTCPStreamingDatabase":
181 streamingOverTCPRecord* sotcp;
182 while ((sotcp = (streamingOverTCPRecord*)fTCPStreamingDatabase->getFirst()) != NULL) {
183 delete sotcp;
184 }
185 delete fTCPStreamingDatabase;
186}
187
188Boolean RTSPServer::isRTSPServer() const {
189 return True;
190}
191
192void RTSPServer::incomingConnectionHandlerHTTP(void* instance, int /*mask*/) {
193 RTSPServer* server = (RTSPServer*)instance;
194 server->incomingConnectionHandlerHTTP();
195}
196void RTSPServer::incomingConnectionHandlerHTTP() {
197 incomingConnectionHandlerOnSocket(fHTTPServerSocket);
198}
199
200void RTSPServer
201::noteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum) {
202 streamingOverTCPRecord* sotcpCur
203 = (streamingOverTCPRecord*)fTCPStreamingDatabase->Lookup((char const*)socketNum);
204 streamingOverTCPRecord* sotcpNew
205 = new streamingOverTCPRecord(clientSession->fOurSessionId, trackNum, sotcpCur);
206 fTCPStreamingDatabase->Add((char const*)socketNum, sotcpNew);
207}
208
209void RTSPServer
210::unnoteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum) {
211 if (socketNum < 0) return;
212 streamingOverTCPRecord* sotcpHead
213 = (streamingOverTCPRecord*)fTCPStreamingDatabase->Lookup((char const*)socketNum);
214 if (sotcpHead == NULL) return;
215
216 // Look for a record of the (session,track); remove it if found:
217 streamingOverTCPRecord* sotcp = sotcpHead;
218 streamingOverTCPRecord* sotcpPrev = sotcpHead;
219 do {
220 if (sotcp->fSessionId == clientSession->fOurSessionId && sotcp->fTrackNum == trackNum) break;
221 sotcpPrev = sotcp;
222 sotcp = sotcp->fNext;
223 } while (sotcp != NULL);
224 if (sotcp == NULL) return; // not found
225
226 if (sotcp == sotcpHead) {
227 // We found it at the head of the list. Remove it and reinsert the tail into the hash table:
228 sotcpHead = sotcp->fNext;
229 sotcp->fNext = NULL;
230 delete sotcp;
231
232 if (sotcpHead == NULL) {
233 // There were no more entries on the list. Remove the original entry from the hash table:
234 fTCPStreamingDatabase->Remove((char const*)socketNum);
235 } else {
236 // Add the rest of the list into the hash table (replacing the original):
237 fTCPStreamingDatabase->Add((char const*)socketNum, sotcpHead);
238 }
239 } else {
240 // We found it on the list, but not at the head. Unlink it:
241 sotcpPrev->fNext = sotcp->fNext;
242 sotcp->fNext = NULL;
243 delete sotcp;
244 }
245}
246
247void RTSPServer::stopTCPStreamingOnSocket(int socketNum) {
248 // Close any stream that is streaming over "socketNum" (using RTP/RTCP-over-TCP streaming):
249 streamingOverTCPRecord* sotcp
250 = (streamingOverTCPRecord*)fTCPStreamingDatabase->Lookup((char const*)socketNum);
251 if (sotcp != NULL) {
252 do {
253 RTSPClientSession* clientSession
254 = (RTSPServer::RTSPClientSession*)lookupClientSession(sotcp->fSessionId);
255 if (clientSession != NULL) {
256 clientSession->deleteStreamByTrack(sotcp->fTrackNum);
257 }
258
259 streamingOverTCPRecord* sotcpNext = sotcp->fNext;
260 sotcp->fNext = NULL;
261 delete sotcp;
262 sotcp = sotcpNext;
263 } while (sotcp != NULL);
264 fTCPStreamingDatabase->Remove((char const*)socketNum);
265 }
266}
267
268
269////////// RTSPServer::RTSPClientConnection implementation //////////
270
271RTSPServer::RTSPClientConnection
272::RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
273 : GenericMediaServer::ClientConnection(ourServer, clientSocket, clientAddr),
274 fOurRTSPServer(ourServer), fClientInputSocket(fOurSocket), fClientOutputSocket(fOurSocket),
275 fIsActive(True), fRecursionCount(0), fOurSessionCookie(NULL) {
276 resetRequestBuffer();
277}
278
279RTSPServer::RTSPClientConnection::~RTSPClientConnection() {
280 if (fOurSessionCookie != NULL) {
281 // We were being used for RTSP-over-HTTP tunneling. Also remove ourselves from the 'session cookie' hash table before we go:
282 fOurRTSPServer.fClientConnectionsForHTTPTunneling->Remove(fOurSessionCookie);
283 delete[] fOurSessionCookie;
284 }
285
286 closeSocketsRTSP();
287}
288
289// Handler routines for specific RTSP commands:
290
291void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {
292 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
293 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",
294 fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
295}
296
297void RTSPServer::RTSPClientConnection
298::handleCmd_GET_PARAMETER(char const* /*fullRequestStr*/) {
299 // By default, we implement "GET_PARAMETER" (on the entire server) just as a 'no op', and send back a dummy response.
300 // (If you want to handle this type of "GET_PARAMETER" differently, you can do so by defining a subclass of "RTSPServer"
301 // and "RTSPServer::RTSPClientConnection", and then reimplement this virtual function in your subclass.)
302 setRTSPResponse("200 OK", LIVEMEDIA_LIBRARY_VERSION_STRING);
303}
304
305void RTSPServer::RTSPClientConnection
306::handleCmd_SET_PARAMETER(char const* /*fullRequestStr*/) {
307 // By default, we implement "SET_PARAMETER" (on the entire server) just as a 'no op', and send back an empty response.
308 // (If you want to handle this type of "SET_PARAMETER" differently, you can do so by defining a subclass of "RTSPServer"
309 // and "RTSPServer::RTSPClientConnection", and then reimplement this virtual function in your subclass.)
310 setRTSPResponse("200 OK");
311}
312
313void RTSPServer::RTSPClientConnection
314::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
315 ServerMediaSession* session = NULL;
316 char* sdpDescription = NULL;
317 char* rtspURL = NULL;
318 do {
319 char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
320 // enough space for urlPreSuffix/urlSuffix'\0'
321 urlTotalSuffix[0] = '\0';
322 if (urlPreSuffix[0] != '\0') {
323 strcat(urlTotalSuffix, urlPreSuffix);
324 strcat(urlTotalSuffix, "/");
325 }
326 strcat(urlTotalSuffix, urlSuffix);
327
328 if (!authenticationOK("DESCRIBE", urlTotalSuffix, fullRequestStr)) break;
329
330 // We should really check that the request contains an "Accept:" #####
331 // for "application/sdp", because that's what we're sending back #####
332
333 // Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
334 session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
335 if (session == NULL) {
336 handleCmd_notFound();
337 break;
338 }
339
340 // Increment the "ServerMediaSession" object's reference count, in case someone removes it
341 // while we're using it:
342 session->incrementReferenceCount();
343
344 // Then, assemble a SDP description for this session:
345 sdpDescription = session->generateSDPDescription();
346 if (sdpDescription == NULL) {
347 // This usually means that a file name that was specified for a
348 // "ServerMediaSubsession" does not exist.
349 setRTSPResponse("404 File Not Found, Or In Incorrect Format");
350 break;
351 }
352 unsigned sdpDescriptionSize = strlen(sdpDescription);
353
354 // Also, generate our RTSP URL, for the "Content-Base:" header
355 // (which is necessary to ensure that the correct URL gets used in subsequent "SETUP" requests).
356 rtspURL = fOurRTSPServer.rtspURL(session, fClientInputSocket);
357
358 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
359 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
360 "%s"
361 "Content-Base: %s/\r\n"
362 "Content-Type: application/sdp\r\n"
363 "Content-Length: %d\r\n\r\n"
364 "%s",
365 fCurrentCSeq,
366 dateHeader(),
367 rtspURL,
368 sdpDescriptionSize,
369 sdpDescription);
370 } while (0);
371
372 if (session != NULL) {
373 // Decrement its reference count, now that we're done using it:
374 session->decrementReferenceCount();
375 if (session->referenceCount() == 0 && session->deleteWhenUnreferenced()) {
376 fOurServer.removeServerMediaSession(session);
377 }
378 }
379
380 delete[] sdpDescription;
381 delete[] rtspURL;
382}
383
384static void lookForHeader(char const* headerName, char const* source, unsigned sourceLen, char* resultStr, unsigned resultMaxSize) {
385 resultStr[0] = '\0'; // by default, return an empty string
386 unsigned headerNameLen = strlen(headerName);
387 for (int i = 0; i < (int)(sourceLen-headerNameLen); ++i) {
388 if (strncmp(&source[i], headerName, headerNameLen) == 0 && source[i+headerNameLen] == ':') {
389 // We found the header. Skip over any whitespace, then copy the rest of the line to "resultStr":
390 for (i += headerNameLen+1; i < (int)sourceLen && (source[i] == ' ' || source[i] == '\t'); ++i) {}
391 for (unsigned j = i; j < sourceLen; ++j) {
392 if (source[j] == '\r' || source[j] == '\n') {
393 // We've found the end of the line. Copy it to the result (if it will fit):
394 if (j-i+1 > resultMaxSize) return; // it wouldn't fit
395 char const* resultSource = &source[i];
396 char const* resultSourceEnd = &source[j];
397 while (resultSource < resultSourceEnd) *resultStr++ = *resultSource++;
398 *resultStr = '\0';
399 return;
400 }
401 }
402 }
403 }
404}
405
406void RTSPServer::RTSPClientConnection::handleCmd_bad() {
407 // Don't do anything with "fCurrentCSeq", because it might be nonsense
408 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
409 "RTSP/1.0 400 Bad Request\r\n%sAllow: %s\r\n\r\n",
410 dateHeader(), fOurRTSPServer.allowedCommandNames());
411}
412
413void RTSPServer::RTSPClientConnection::handleCmd_notSupported() {
414 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
415 "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n%sAllow: %s\r\n\r\n",
416 fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
417}
418
419void RTSPServer::RTSPClientConnection::handleCmd_notFound() {
420 setRTSPResponse("404 Stream Not Found");
421}
422
423void RTSPServer::RTSPClientConnection::handleCmd_sessionNotFound() {
424 setRTSPResponse("454 Session Not Found");
425}
426
427void RTSPServer::RTSPClientConnection::handleCmd_unsupportedTransport() {
428 setRTSPResponse("461 Unsupported Transport");
429}
430
431Boolean RTSPServer::RTSPClientConnection::parseHTTPRequestString(char* resultCmdName, unsigned resultCmdNameMaxSize,
432 char* urlSuffix, unsigned urlSuffixMaxSize,
433 char* sessionCookie, unsigned sessionCookieMaxSize,
434 char* acceptStr, unsigned acceptStrMaxSize) {
435 // Check for the limited HTTP requests that we expect for specifying RTSP-over-HTTP tunneling.
436 // This parser is currently rather dumb; it should be made smarter #####
437 char const* reqStr = (char const*)fRequestBuffer;
438 unsigned const reqStrSize = fRequestBytesAlreadySeen;
439
440 // Read everything up to the first space as the command name:
441 Boolean parseSucceeded = False;
442 unsigned i;
443 for (i = 0; i < resultCmdNameMaxSize-1 && i < reqStrSize; ++i) {
444 char c = reqStr[i];
445 if (c == ' ' || c == '\t') {
446 parseSucceeded = True;
447 break;
448 }
449
450 resultCmdName[i] = c;
451 }
452 resultCmdName[i] = '\0';
453 if (!parseSucceeded) return False;
454
455 // Look for the string "HTTP/", before the first \r or \n:
456 parseSucceeded = False;
457 for (; i < reqStrSize-5 && reqStr[i] != '\r' && reqStr[i] != '\n'; ++i) {
458 if (reqStr[i] == 'H' && reqStr[i+1] == 'T' && reqStr[i+2]== 'T' && reqStr[i+3]== 'P' && reqStr[i+4]== '/') {
459 i += 5; // to advance past the "HTTP/"
460 parseSucceeded = True;
461 break;
462 }
463 }
464 if (!parseSucceeded) return False;
465
466 // Get the 'URL suffix' that occurred before this:
467 unsigned k = i-6;
468 while (k > 0 && reqStr[k] == ' ') --k; // back up over white space
469 unsigned j = k;
470 while (j > 0 && reqStr[j] != ' ' && reqStr[j] != '/') --j;
471 // The URL suffix is in position (j,k]:
472 if (k - j + 1 > urlSuffixMaxSize) return False; // there's no room>
473 unsigned n = 0;
474 while (++j <= k) urlSuffix[n++] = reqStr[j];
475 urlSuffix[n] = '\0';
476
477 // Look for various headers that we're interested in:
478 lookForHeader("x-sessioncookie", &reqStr[i], reqStrSize-i, sessionCookie, sessionCookieMaxSize);
479 lookForHeader("Accept", &reqStr[i], reqStrSize-i, acceptStr, acceptStrMaxSize);
480
481 return True;
482}
483
484void RTSPServer::RTSPClientConnection::handleHTTPCmd_notSupported() {
485 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
486 "HTTP/1.1 405 Method Not Allowed\r\n%s\r\n\r\n",
487 dateHeader());
488}
489
490void RTSPServer::RTSPClientConnection::handleHTTPCmd_notFound() {
491 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
492 "HTTP/1.1 404 Not Found\r\n%s\r\n\r\n",
493 dateHeader());
494}
495
496void RTSPServer::RTSPClientConnection::handleHTTPCmd_OPTIONS() {
497#ifdef DEBUG
498 fprintf(stderr, "Handled HTTP \"OPTIONS\" request\n");
499#endif
500 // Construct a response to the "OPTIONS" command that notes that our special headers (for RTSP-over-HTTP tunneling) are allowed:
501 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
502 "HTTP/1.1 200 OK\r\n"
503 "%s"
504 "Access-Control-Allow-Origin: *\r\n"
505 "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
506 "Access-Control-Allow-Headers: x-sessioncookie, Pragma, Cache-Control\r\n"
507 "Access-Control-Max-Age: 1728000\r\n"
508 "\r\n",
509 dateHeader());
510}
511
512void RTSPServer::RTSPClientConnection::handleHTTPCmd_TunnelingGET(char const* sessionCookie) {
513 // Record ourself as having this 'session cookie', so that a subsequent HTTP "POST" command (with the same 'session cookie')
514 // can find us:
515 if (fOurRTSPServer.fClientConnectionsForHTTPTunneling == NULL) {
516 fOurRTSPServer.fClientConnectionsForHTTPTunneling = HashTable::create(STRING_HASH_KEYS);
517 }
518 delete[] fOurSessionCookie; fOurSessionCookie = strDup(sessionCookie);
519 fOurRTSPServer.fClientConnectionsForHTTPTunneling->Add(sessionCookie, (void*)this);
520#ifdef DEBUG
521 fprintf(stderr, "Handled HTTP \"GET\" request (client output socket: %d)\n", fClientOutputSocket);
522#endif
523
524 // Construct our response:
525 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
526 "HTTP/1.1 200 OK\r\n"
527 "%s"
528 "Cache-Control: no-cache\r\n"
529 "Pragma: no-cache\r\n"
530 "Content-Type: application/x-rtsp-tunnelled\r\n"
531 "\r\n",
532 dateHeader());
533}
534
535Boolean RTSPServer::RTSPClientConnection
536::handleHTTPCmd_TunnelingPOST(char const* sessionCookie, unsigned char const* extraData, unsigned extraDataSize) {
537 // Use the "sessionCookie" string to look up the separate "RTSPClientConnection" object that should have been used to handle
538 // an earlier HTTP "GET" request:
539 if (fOurRTSPServer.fClientConnectionsForHTTPTunneling == NULL) {
540 fOurRTSPServer.fClientConnectionsForHTTPTunneling = HashTable::create(STRING_HASH_KEYS);
541 }
542 RTSPServer::RTSPClientConnection* prevClientConnection
543 = (RTSPServer::RTSPClientConnection*)(fOurRTSPServer.fClientConnectionsForHTTPTunneling->Lookup(sessionCookie));
544 if (prevClientConnection == NULL || prevClientConnection == this) {
545 // Either there was no previous HTTP "GET" request, or it was on the same connection; treat this "POST" request as bad:
546 handleHTTPCmd_notSupported();
547 fIsActive = False; // triggers deletion of ourself
548 return False;
549 }
550#ifdef DEBUG
551 fprintf(stderr, "Handled HTTP \"POST\" request (client input socket: %d)\n", fClientInputSocket);
552#endif
553
554 // Change the previous "RTSPClientSession" object's input socket to ours. It will be used for subsequent requests:
555 prevClientConnection->changeClientInputSocket(fClientInputSocket, extraData, extraDataSize);
556 fClientInputSocket = fClientOutputSocket = -1; // so the socket doesn't get closed when we get deleted
557 return True;
558}
559
560void RTSPServer::RTSPClientConnection::handleHTTPCmd_StreamingGET(char const* /*urlSuffix*/, char const* /*fullRequestStr*/) {
561 // By default, we don't support requests to access streams via HTTP:
562 handleHTTPCmd_notSupported();
563}
564
565void RTSPServer::RTSPClientConnection::resetRequestBuffer() {
566 ClientConnection::resetRequestBuffer();
567
568 fLastCRLF = &fRequestBuffer[-3]; // hack: Ensures that we don't think we have end-of-msg if the data starts with <CR><LF>
569 fBase64RemainderCount = 0;
570}
571
572void RTSPServer::RTSPClientConnection::closeSocketsRTSP() {
573 // First, tell our server to stop any streaming that it might be doing over our output socket:
574 fOurRTSPServer.stopTCPStreamingOnSocket(fClientOutputSocket);
575
576 // Turn off background handling on our input socket (and output socket, if different); then close it (or them):
577 if (fClientOutputSocket != fClientInputSocket) {
578 envir().taskScheduler().disableBackgroundHandling(fClientOutputSocket);
579 ::closeSocket(fClientOutputSocket);
580 }
581 fClientOutputSocket = -1;
582
583 closeSockets(); // closes fClientInputSocket
584}
585
586void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte(void* instance, u_int8_t requestByte) {
587 RTSPClientConnection* connection = (RTSPClientConnection*)instance;
588 connection->handleAlternativeRequestByte1(requestByte);
589}
590
591void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte1(u_int8_t requestByte) {
592 if (requestByte == 0xFF) {
593 // Hack: The new handler of the input TCP socket encountered an error reading it. Indicate this:
594 handleRequestBytes(-1);
595 } else if (requestByte == 0xFE) {
596 // Another hack: The new handler of the input TCP socket no longer needs it, so take back control of it:
597 envir().taskScheduler().setBackgroundHandling(fClientInputSocket, SOCKET_READABLE|SOCKET_EXCEPTION,
598 incomingRequestHandler, this);
599 } else {
600 // Normal case: Add this character to our buffer; then try to handle the data that we have buffered so far:
601 if (fRequestBufferBytesLeft == 0 || fRequestBytesAlreadySeen >= REQUEST_BUFFER_SIZE) return;
602 fRequestBuffer[fRequestBytesAlreadySeen] = requestByte;
603 handleRequestBytes(1);
604 }
605}
606
607void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
608 int numBytesRemaining = 0;
609 ++fRecursionCount;
610
611 do {
612 RTSPServer::RTSPClientSession* clientSession = NULL;
613
614 if (newBytesRead < 0 || (unsigned)newBytesRead >= fRequestBufferBytesLeft) {
615 // Either the client socket has died, or the request was too big for us.
616 // Terminate this connection:
617#ifdef DEBUG
618 fprintf(stderr, "RTSPClientConnection[%p]::handleRequestBytes() read %d new bytes (of %d); terminating connection!\n", this, newBytesRead, fRequestBufferBytesLeft);
619#endif
620 fIsActive = False;
621 break;
622 }
623
624 Boolean endOfMsg = False;
625 unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen];
626#ifdef DEBUG
627 ptr[newBytesRead] = '\0';
628 fprintf(stderr, "RTSPClientConnection[%p]::handleRequestBytes() %s %d new bytes:%s\n",
629 this, numBytesRemaining > 0 ? "processing" : "read", newBytesRead, ptr);
630#endif
631
632 if (fClientOutputSocket != fClientInputSocket && numBytesRemaining == 0) {
633 // We're doing RTSP-over-HTTP tunneling, and input commands are assumed to have been Base64-encoded.
634 // We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes).
635
636 // But first, we remove any whitespace that may be in the input data:
637 unsigned toIndex = 0;
638 for (int fromIndex = 0; fromIndex < newBytesRead; ++fromIndex) {
639 char c = ptr[fromIndex];
640 if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { // not 'whitespace': space,tab,CR,NL
641 ptr[toIndex++] = c;
642 }
643 }
644 newBytesRead = toIndex;
645
646 unsigned numBytesToDecode = fBase64RemainderCount + newBytesRead;
647 unsigned newBase64RemainderCount = numBytesToDecode%4;
648 numBytesToDecode -= newBase64RemainderCount;
649 if (numBytesToDecode > 0) {
650 ptr[newBytesRead] = '\0';
651 unsigned decodedSize;
652 unsigned char* decodedBytes = base64Decode((char const*)(ptr-fBase64RemainderCount), numBytesToDecode, decodedSize);
653#ifdef DEBUG
654 fprintf(stderr, "Base64-decoded %d input bytes into %d new bytes:", numBytesToDecode, decodedSize);
655 for (unsigned k = 0; k < decodedSize; ++k) fprintf(stderr, "%c", decodedBytes[k]);
656 fprintf(stderr, "\n");
657#endif
658
659 // Copy the new decoded bytes in place of the old ones (we can do this because there are fewer decoded bytes than original):
660 unsigned char* to = ptr-fBase64RemainderCount;
661 for (unsigned i = 0; i < decodedSize; ++i) *to++ = decodedBytes[i];
662
663 // Then copy any remaining (undecoded) bytes to the end:
664 for (unsigned j = 0; j < newBase64RemainderCount; ++j) *to++ = (ptr-fBase64RemainderCount+numBytesToDecode)[j];
665
666 newBytesRead = decodedSize - fBase64RemainderCount + newBase64RemainderCount;
667 // adjust to allow for the size of the new decoded data (+ remainder)
668 delete[] decodedBytes;
669 }
670 fBase64RemainderCount = newBase64RemainderCount;
671 }
672
673 unsigned char* tmpPtr = fLastCRLF + 2;
674 if (fBase64RemainderCount == 0) { // no more Base-64 bytes remain to be read/decoded
675 // Look for the end of the message: <CR><LF><CR><LF>
676 if (tmpPtr < fRequestBuffer) tmpPtr = fRequestBuffer;
677 while (tmpPtr < &ptr[newBytesRead-1]) {
678 if (*tmpPtr == '\r' && *(tmpPtr+1) == '\n') {
679 if (tmpPtr - fLastCRLF == 2) { // This is it:
680 endOfMsg = True;
681 break;
682 }
683 fLastCRLF = tmpPtr;
684 }
685 ++tmpPtr;
686 }
687 }
688
689 fRequestBufferBytesLeft -= newBytesRead;
690 fRequestBytesAlreadySeen += newBytesRead;
691
692 if (!endOfMsg) break; // subsequent reads will be needed to complete the request
693
694 // Parse the request string into command name and 'CSeq', then handle the command:
695 fRequestBuffer[fRequestBytesAlreadySeen] = '\0';
696 char cmdName[RTSP_PARAM_STRING_MAX];
697 char urlPreSuffix[RTSP_PARAM_STRING_MAX];
698 char urlSuffix[RTSP_PARAM_STRING_MAX];
699 char cseq[RTSP_PARAM_STRING_MAX];
700 char sessionIdStr[RTSP_PARAM_STRING_MAX];
701 unsigned contentLength = 0;
702 Boolean playAfterSetup = False;
703 fLastCRLF[2] = '\0'; // temporarily, for parsing
704 Boolean parseSucceeded = parseRTSPRequestString((char*)fRequestBuffer, fLastCRLF+2 - fRequestBuffer,
705 cmdName, sizeof cmdName,
706 urlPreSuffix, sizeof urlPreSuffix,
707 urlSuffix, sizeof urlSuffix,
708 cseq, sizeof cseq,
709 sessionIdStr, sizeof sessionIdStr,
710 contentLength);
711 fLastCRLF[2] = '\r'; // restore its value
712 // Check first for a bogus "Content-Length" value that would cause a pointer wraparound:
713 if (tmpPtr + 2 + contentLength < tmpPtr + 2) {
714#ifdef DEBUG
715 fprintf(stderr, "parseRTSPRequestString() returned a bogus \"Content-Length:\" value: 0x%x (%d)\n", contentLength, (int)contentLength);
716#endif
717 contentLength = 0;
718 parseSucceeded = False;
719 }
720 if (parseSucceeded) {
721#ifdef DEBUG
722 fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\", CSeq \"%s\", Content-Length %u, with %d bytes following the message.\n", cmdName, urlPreSuffix, urlSuffix, cseq, contentLength, ptr + newBytesRead - (tmpPtr + 2));
723#endif
724 // If there was a "Content-Length:" header, then make sure we've received all of the data that it specified:
725 if (ptr + newBytesRead < tmpPtr + 2 + contentLength) break; // we still need more data; subsequent reads will give it to us
726
727 // If the request included a "Session:" id, and it refers to a client session that's
728 // current ongoing, then use this command to indicate 'liveness' on that client session:
729 Boolean const requestIncludedSessionId = sessionIdStr[0] != '\0';
730 if (requestIncludedSessionId) {
731 clientSession
732 = (RTSPServer::RTSPClientSession*)(fOurRTSPServer.lookupClientSession(sessionIdStr));
733 if (clientSession != NULL) clientSession->noteLiveness();
734 }
735
736 // We now have a complete RTSP request.
737 // Handle the specified command (beginning with commands that are session-independent):
738 fCurrentCSeq = cseq;
739 if (strcmp(cmdName, "OPTIONS") == 0) {
740 // If the "OPTIONS" command included a "Session:" id for a session that doesn't exist,
741 // then treat this as an error:
742 if (requestIncludedSessionId && clientSession == NULL) {
743#ifdef DEBUG
744 fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 1)\n");
745#endif
746 handleCmd_sessionNotFound();
747 } else {
748 // Normal case:
749 handleCmd_OPTIONS();
750 }
751 } else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0') {
752 // The special "*" URL means: an operation on the entire server. This works only for GET_PARAMETER and SET_PARAMETER:
753 if (strcmp(cmdName, "GET_PARAMETER") == 0) {
754 handleCmd_GET_PARAMETER((char const*)fRequestBuffer);
755 } else if (strcmp(cmdName, "SET_PARAMETER") == 0) {
756 handleCmd_SET_PARAMETER((char const*)fRequestBuffer);
757 } else {
758 handleCmd_notSupported();
759 }
760 } else if (strcmp(cmdName, "DESCRIBE") == 0) {
761 handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
762 } else if (strcmp(cmdName, "SETUP") == 0) {
763 Boolean areAuthenticated = True;
764
765 if (!requestIncludedSessionId) {
766 // No session id was present in the request.
767 // So create a new "RTSPClientSession" object for this request.
768
769 // But first, make sure that we're authenticated to perform this command:
770 char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
771 // enough space for urlPreSuffix/urlSuffix'\0'
772 urlTotalSuffix[0] = '\0';
773 if (urlPreSuffix[0] != '\0') {
774 strcat(urlTotalSuffix, urlPreSuffix);
775 strcat(urlTotalSuffix, "/");
776 }
777 strcat(urlTotalSuffix, urlSuffix);
778 if (authenticationOK("SETUP", urlTotalSuffix, (char const*)fRequestBuffer)) {
779 clientSession
780 = (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();
781 } else {
782 areAuthenticated = False;
783 }
784 }
785 if (clientSession != NULL) {
786 clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
787 playAfterSetup = clientSession->fStreamAfterSETUP;
788 } else if (areAuthenticated) {
789#ifdef DEBUG
790 fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 2)\n");
791#endif
792 handleCmd_sessionNotFound();
793 }
794 } else if (strcmp(cmdName, "TEARDOWN") == 0
795 || strcmp(cmdName, "PLAY") == 0
796 || strcmp(cmdName, "PAUSE") == 0
797 || strcmp(cmdName, "GET_PARAMETER") == 0
798 || strcmp(cmdName, "SET_PARAMETER") == 0) {
799 if (clientSession != NULL) {
800 clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
801 } else {
802#ifdef DEBUG
803 fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 3)\n");
804#endif
805 handleCmd_sessionNotFound();
806 }
807 } else if (strcmp(cmdName, "REGISTER") == 0 || strcmp(cmdName, "DEREGISTER") == 0) {
808 // Because - unlike other commands - an implementation of this command needs
809 // the entire URL, we re-parse the command to get it:
810 char* url = strDupSize((char*)fRequestBuffer);
811 if (sscanf((char*)fRequestBuffer, "%*s %s", url) == 1) {
812 // Check for special command-specific parameters in a "Transport:" header:
813 Boolean reuseConnection, deliverViaTCP;
814 char* proxyURLSuffix;
815 parseTransportHeaderForREGISTER((const char*)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);
816
817 handleCmd_REGISTER(cmdName, url, urlSuffix, (char const*)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);
818 delete[] proxyURLSuffix;
819 } else {
820 handleCmd_bad();
821 }
822 delete[] url;
823 } else {
824 // The command is one that we don't handle:
825 handleCmd_notSupported();
826 }
827 } else {
828#ifdef DEBUG
829 fprintf(stderr, "parseRTSPRequestString() failed; checking now for HTTP commands (for RTSP-over-HTTP tunneling)...\n");
830#endif
831 // The request was not (valid) RTSP, but check for a special case: HTTP commands (for setting up RTSP-over-HTTP tunneling):
832 char sessionCookie[RTSP_PARAM_STRING_MAX];
833 char acceptStr[RTSP_PARAM_STRING_MAX];
834 *fLastCRLF = '\0'; // temporarily, for parsing
835 parseSucceeded = parseHTTPRequestString(cmdName, sizeof cmdName,
836 urlSuffix, sizeof urlPreSuffix,
837 sessionCookie, sizeof sessionCookie,
838 acceptStr, sizeof acceptStr);
839 *fLastCRLF = '\r';
840 if (parseSucceeded) {
841#ifdef DEBUG
842 fprintf(stderr, "parseHTTPRequestString() succeeded, returning cmdName \"%s\", urlSuffix \"%s\", sessionCookie \"%s\", acceptStr \"%s\"\n", cmdName, urlSuffix, sessionCookie, acceptStr);
843#endif
844 // Check that the HTTP command is valid for RTSP-over-HTTP tunneling: There must be a 'session cookie'.
845 Boolean isValidHTTPCmd = True;
846 if (strcmp(cmdName, "OPTIONS") == 0) {
847 handleHTTPCmd_OPTIONS();
848 } else if (sessionCookie[0] == '\0') {
849 // There was no "x-sessioncookie:" header. If there was an "Accept: application/x-rtsp-tunnelled" header,
850 // then this is a bad tunneling request. Otherwise, assume that it's an attempt to access the stream via HTTP.
851 if (strcmp(acceptStr, "application/x-rtsp-tunnelled") == 0) {
852 isValidHTTPCmd = False;
853 } else {
854 handleHTTPCmd_StreamingGET(urlSuffix, (char const*)fRequestBuffer);
855 }
856 } else if (strcmp(cmdName, "GET") == 0) {
857 handleHTTPCmd_TunnelingGET(sessionCookie);
858 } else if (strcmp(cmdName, "POST") == 0) {
859 // We might have received additional data following the HTTP "POST" command - i.e., the first Base64-encoded RTSP command.
860 // Check for this, and handle it if it exists:
861 unsigned char const* extraData = fLastCRLF+4;
862 unsigned extraDataSize = &fRequestBuffer[fRequestBytesAlreadySeen] - extraData;
863 if (handleHTTPCmd_TunnelingPOST(sessionCookie, extraData, extraDataSize)) {
864 // We don't respond to the "POST" command, and we go away:
865 fIsActive = False;
866 break;
867 }
868 } else {
869 isValidHTTPCmd = False;
870 }
871 if (!isValidHTTPCmd) {
872 handleHTTPCmd_notSupported();
873 }
874 } else {
875#ifdef DEBUG
876 fprintf(stderr, "parseHTTPRequestString() failed!\n");
877#endif
878 handleCmd_bad();
879 }
880 }
881
882#ifdef DEBUG
883 fprintf(stderr, "sending response: %s", fResponseBuffer);
884#endif
885 send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
886
887 if (playAfterSetup) {
888 // The client has asked for streaming to commence now, rather than after a
889 // subsequent "PLAY" command. So, simulate the effect of a "PLAY" command:
890 clientSession->handleCmd_withinSession(this, "PLAY", urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
891 }
892
893 // Check whether there are extra bytes remaining in the buffer, after the end of the request (a rare case).
894 // If so, move them to the front of our buffer, and keep processing it, because it might be a following, pipelined request.
895 unsigned requestSize = (fLastCRLF+4-fRequestBuffer) + contentLength;
896 numBytesRemaining = fRequestBytesAlreadySeen - requestSize;
897 resetRequestBuffer(); // to prepare for any subsequent request
898
899 if (numBytesRemaining > 0) {
900 memmove(fRequestBuffer, &fRequestBuffer[requestSize], numBytesRemaining);
901 newBytesRead = numBytesRemaining;
902 }
903 } while (numBytesRemaining > 0);
904
905 --fRecursionCount;
906 if (!fIsActive) {
907 if (fRecursionCount > 0) closeSockets(); else delete this;
908 // Note: The "fRecursionCount" test is for a pathological situation where we reenter the event loop and get called recursively
909 // while handling a command (e.g., while handling a "DESCRIBE", to get a SDP description).
910 // In such a case we don't want to actually delete ourself until we leave the outermost call.
911 }
912}
913
914#define SKIP_WHITESPACE while (*fields != '\0' && (*fields == ' ' || *fields == '\t')) ++fields
915
916static Boolean parseAuthorizationHeader(char const* buf,
917 char const*& username,
918 char const*& realm,
919 char const*& nonce, char const*& uri,
920 char const*& response) {
921 // Initialize the result parameters to default values:
922 username = realm = nonce = uri = response = NULL;
923
924 // First, find "Authorization:"
925 while (1) {
926 if (*buf == '\0') return False; // not found
927 if (_strncasecmp(buf, "Authorization: Digest ", 22) == 0) break;
928 ++buf;
929 }
930
931 // Then, run through each of the fields, looking for ones we handle:
932 char const* fields = buf + 22;
933 char* parameter = strDupSize(fields);
934 char* value = strDupSize(fields);
935 char* p;
936 Boolean success;
937 do {
938 // Parse: <parameter>="<value>"
939 success = False;
940 parameter[0] = value[0] = '\0';
941 SKIP_WHITESPACE;
942 for (p = parameter; *fields != '\0' && *fields != ' ' && *fields != '\t' && *fields != '='; ) *p++ = *fields++;
943 SKIP_WHITESPACE;
944 if (*fields++ != '=') break; // parsing failed
945 *p = '\0'; // complete parsing <parameter>
946 SKIP_WHITESPACE;
947 if (*fields++ != '"') break; // parsing failed
948 for (p = value; *fields != '\0' && *fields != '"'; ) *p++ = *fields++;
949 if (*fields++ != '"') break; // parsing failed
950 *p = '\0'; // complete parsing <value>
951 SKIP_WHITESPACE;
952 success = True;
953
954 // Copy values for parameters that we understand:
955 if (strcmp(parameter, "username") == 0) {
956 username = strDup(value);
957 } else if (strcmp(parameter, "realm") == 0) {
958 realm = strDup(value);
959 } else if (strcmp(parameter, "nonce") == 0) {
960 nonce = strDup(value);
961 } else if (strcmp(parameter, "uri") == 0) {
962 uri = strDup(value);
963 } else if (strcmp(parameter, "response") == 0) {
964 response = strDup(value);
965 }
966
967 // Check for a ',', indicating that more <parameter>="<value>" pairs follow:
968 } while (*fields++ == ',');
969
970 delete[] parameter; delete[] value;
971 return success;
972}
973
974Boolean RTSPServer::RTSPClientConnection
975::authenticationOK(char const* cmdName, char const* urlSuffix, char const* fullRequestStr) {
976 if (!fOurRTSPServer.specialClientAccessCheck(fClientInputSocket, fClientAddr, urlSuffix)) {
977 setRTSPResponse("401 Unauthorized");
978 return False;
979 }
980
981 // If we weren't set up with an authentication database, we're OK:
982 UserAuthenticationDatabase* authDB = fOurRTSPServer.getAuthenticationDatabaseForCommand(cmdName);
983 if (authDB == NULL) return True;
984
985 char const* username = NULL; char const* realm = NULL; char const* nonce = NULL;
986 char const* uri = NULL; char const* response = NULL;
987 Boolean success = False;
988
989 do {
990 // To authenticate, we first need to have a nonce set up
991 // from a previous attempt:
992 if (fCurrentAuthenticator.nonce() == NULL) break;
993
994 // Next, the request needs to contain an "Authorization:" header,
995 // containing a username, (our) realm, (our) nonce, uri,
996 // and response string:
997 if (!parseAuthorizationHeader(fullRequestStr,
998 username, realm, nonce, uri, response)
999 || username == NULL
1000 || realm == NULL || strcmp(realm, fCurrentAuthenticator.realm()) != 0
1001 || nonce == NULL || strcmp(nonce, fCurrentAuthenticator.nonce()) != 0
1002 || uri == NULL || response == NULL) {
1003 break;
1004 }
1005
1006 // Next, the username has to be known to us:
1007 char const* password = authDB->lookupPassword(username);
1008#ifdef DEBUG
1009 fprintf(stderr, "lookupPassword(%s) returned password %s\n", username, password);
1010#endif
1011 if (password == NULL) break;
1012 fCurrentAuthenticator.setUsernameAndPassword(username, password, authDB->passwordsAreMD5());
1013
1014 // Finally, compute a digest response from the information that we have,
1015 // and compare it to the one that we were given:
1016 char const* ourResponse
1017 = fCurrentAuthenticator.computeDigestResponse(cmdName, uri);
1018 success = (strcmp(ourResponse, response) == 0);
1019 fCurrentAuthenticator.reclaimDigestResponse(ourResponse);
1020 } while (0);
1021
1022 delete[] (char*)realm; delete[] (char*)nonce;
1023 delete[] (char*)uri; delete[] (char*)response;
1024
1025 if (success) {
1026 // The user has been authenticated.
1027 // Now allow subclasses a chance to validate the user against the IP address and/or URL suffix.
1028 if (!fOurRTSPServer.specialClientUserAccessCheck(fClientInputSocket, fClientAddr, urlSuffix, username)) {
1029 // Note: We don't return a "WWW-Authenticate" header here, because the user is valid,
1030 // even though the server has decided that they should not have access.
1031 setRTSPResponse("401 Unauthorized");
1032 delete[] (char*)username;
1033 return False;
1034 }
1035 }
1036 delete[] (char*)username;
1037 if (success) return True;
1038
1039 // If we get here, we failed to authenticate the user.
1040 // Send back a "401 Unauthorized" response, with a new random nonce:
1041 fCurrentAuthenticator.setRealmAndRandomNonce(authDB->realm());
1042 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
1043 "RTSP/1.0 401 Unauthorized\r\n"
1044 "CSeq: %s\r\n"
1045 "%s"
1046 "WWW-Authenticate: Digest realm=\"%s\", nonce=\"%s\"\r\n\r\n",
1047 fCurrentCSeq,
1048 dateHeader(),
1049 fCurrentAuthenticator.realm(), fCurrentAuthenticator.nonce());
1050 return False;
1051}
1052
1053void RTSPServer::RTSPClientConnection
1054::setRTSPResponse(char const* responseStr) {
1055 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
1056 "RTSP/1.0 %s\r\n"
1057 "CSeq: %s\r\n"
1058 "%s\r\n",
1059 responseStr,
1060 fCurrentCSeq,
1061 dateHeader());
1062}
1063
1064void RTSPServer::RTSPClientConnection
1065::setRTSPResponse(char const* responseStr, u_int32_t sessionId) {
1066 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
1067 "RTSP/1.0 %s\r\n"
1068 "CSeq: %s\r\n"
1069 "%s"
1070 "Session: %08X\r\n\r\n",
1071 responseStr,
1072 fCurrentCSeq,
1073 dateHeader(),
1074 sessionId);
1075}
1076
1077void RTSPServer::RTSPClientConnection
1078::setRTSPResponse(char const* responseStr, char const* contentStr) {
1079 if (contentStr == NULL) contentStr = "";
1080 unsigned const contentLen = strlen(contentStr);
1081
1082 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
1083 "RTSP/1.0 %s\r\n"
1084 "CSeq: %s\r\n"
1085 "%s"
1086 "Content-Length: %d\r\n\r\n"
1087 "%s",
1088 responseStr,
1089 fCurrentCSeq,
1090 dateHeader(),
1091 contentLen,
1092 contentStr);
1093}
1094
1095void RTSPServer::RTSPClientConnection
1096::setRTSPResponse(char const* responseStr, u_int32_t sessionId, char const* contentStr) {
1097 if (contentStr == NULL) contentStr = "";
1098 unsigned const contentLen = strlen(contentStr);
1099
1100 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
1101 "RTSP/1.0 %s\r\n"
1102 "CSeq: %s\r\n"
1103 "%s"
1104 "Session: %08X\r\n"
1105 "Content-Length: %d\r\n\r\n"
1106 "%s",
1107 responseStr,
1108 fCurrentCSeq,
1109 dateHeader(),
1110 sessionId,
1111 contentLen,
1112 contentStr);
1113}
1114
1115void RTSPServer::RTSPClientConnection
1116::changeClientInputSocket(int newSocketNum, unsigned char const* extraData, unsigned extraDataSize) {
1117 envir().taskScheduler().disableBackgroundHandling(fClientInputSocket);
1118 fClientInputSocket = newSocketNum;
1119 envir().taskScheduler().setBackgroundHandling(fClientInputSocket, SOCKET_READABLE|SOCKET_EXCEPTION,
1120 incomingRequestHandler, this);
1121
1122 // Also write any extra data to our buffer, and handle it:
1123 if (extraDataSize > 0 && extraDataSize <= fRequestBufferBytesLeft/*sanity check; should always be true*/) {
1124 unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen];
1125 for (unsigned i = 0; i < extraDataSize; ++i) {
1126 ptr[i] = extraData[i];
1127 }
1128 handleRequestBytes(extraDataSize);
1129 }
1130}
1131
1132
1133////////// RTSPServer::RTSPClientSession implementation //////////
1134
1135RTSPServer::RTSPClientSession
1136::RTSPClientSession(RTSPServer& ourServer, u_int32_t sessionId)
1137 : GenericMediaServer::ClientSession(ourServer, sessionId),
1138 fOurRTSPServer(ourServer), fIsMulticast(False), fStreamAfterSETUP(False),
1139 fTCPStreamIdCount(0), fNumStreamStates(0), fStreamStates(NULL) {
1140}
1141
1142RTSPServer::RTSPClientSession::~RTSPClientSession() {
1143 reclaimStreamStates();
1144}
1145
1146void RTSPServer::RTSPClientSession::deleteStreamByTrack(unsigned trackNum) {
1147 if (trackNum >= fNumStreamStates) return; // sanity check; shouldn't happen
1148 if (fStreamStates[trackNum].subsession != NULL) {
1149 fStreamStates[trackNum].subsession->deleteStream(fOurSessionId, fStreamStates[trackNum].streamToken);
1150 fStreamStates[trackNum].subsession = NULL;
1151 }
1152
1153 // Optimization: If all subsessions have now been deleted, then we can delete ourself now:
1154 Boolean noSubsessionsRemain = True;
1155 for (unsigned i = 0; i < fNumStreamStates; ++i) {
1156 if (fStreamStates[i].subsession != NULL) {
1157 noSubsessionsRemain = False;
1158 break;
1159 }
1160 }
1161 if (noSubsessionsRemain) delete this;
1162}
1163
1164void RTSPServer::RTSPClientSession::reclaimStreamStates() {
1165 for (unsigned i = 0; i < fNumStreamStates; ++i) {
1166 if (fStreamStates[i].subsession != NULL) {
1167 fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[i].tcpSocketNum, this, i);
1168 fStreamStates[i].subsession->deleteStream(fOurSessionId, fStreamStates[i].streamToken);
1169 }
1170 }
1171 delete[] fStreamStates; fStreamStates = NULL;
1172 fNumStreamStates = 0;
1173}
1174
1175typedef enum StreamingMode {
1176 RTP_UDP,
1177 RTP_TCP,
1178 RAW_UDP
1179} StreamingMode;
1180
1181static void parseTransportHeader(char const* buf,
1182 StreamingMode& streamingMode,
1183 char*& streamingModeString,
1184 char*& destinationAddressStr,
1185 u_int8_t& destinationTTL,
1186 portNumBits& clientRTPPortNum, // if UDP
1187 portNumBits& clientRTCPPortNum, // if UDP
1188 unsigned char& rtpChannelId, // if TCP
1189 unsigned char& rtcpChannelId // if TCP
1190 ) {
1191 // Initialize the result parameters to default values:
1192 streamingMode = RTP_UDP;
1193 streamingModeString = NULL;
1194 destinationAddressStr = NULL;
1195 destinationTTL = 255;
1196 clientRTPPortNum = 0;
1197 clientRTCPPortNum = 1;
1198 rtpChannelId = rtcpChannelId = 0xFF;
1199
1200 portNumBits p1, p2;
1201 unsigned ttl, rtpCid, rtcpCid;
1202
1203 // First, find "Transport:"
1204 while (1) {
1205 if (*buf == '\0') return; // not found
1206 if (*buf == '\r' && *(buf+1) == '\n' && *(buf+2) == '\r') return; // end of the headers => not found
1207 if (_strncasecmp(buf, "Transport:", 10) == 0) break;
1208 ++buf;
1209 }
1210
1211 // Then, run through each of the fields, looking for ones we handle:
1212 char const* fields = buf + 10;
1213 while (*fields == ' ') ++fields;
1214 char* field = strDupSize(fields);
1215 while (sscanf(fields, "%[^;\r\n]", field) == 1) {
1216 if (strcmp(field, "RTP/AVP/TCP") == 0) {
1217 streamingMode = RTP_TCP;
1218 } else if (strcmp(field, "RAW/RAW/UDP") == 0 ||
1219 strcmp(field, "MP2T/H2221/UDP") == 0) {
1220 streamingMode = RAW_UDP;
1221 streamingModeString = strDup(field);
1222 } else if (_strncasecmp(field, "destination=", 12) == 0) {
1223 delete[] destinationAddressStr;
1224 destinationAddressStr = strDup(field+12);
1225 } else if (sscanf(field, "ttl%u", &ttl) == 1) {
1226 destinationTTL = (u_int8_t)ttl;
1227 } else if (sscanf(field, "client_port=%hu-%hu", &p1, &p2) == 2) {
1228 clientRTPPortNum = p1;
1229 clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p2; // ignore the second port number if the client asked for raw UDP
1230 } else if (sscanf(field, "client_port=%hu", &p1) == 1) {
1231 clientRTPPortNum = p1;
1232 clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p1 + 1;
1233 } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
1234 rtpChannelId = (unsigned char)rtpCid;
1235 rtcpChannelId = (unsigned char)rtcpCid;
1236 }
1237
1238 fields += strlen(field);
1239 while (*fields == ';' || *fields == ' ' || *fields == '\t') ++fields; // skip over separating ';' chars or whitespace
1240 if (*fields == '\0' || *fields == '\r' || *fields == '\n') break;
1241 }
1242 delete[] field;
1243}
1244
1245static Boolean parsePlayNowHeader(char const* buf) {
1246 // Find "x-playNow:" header, if present
1247 while (1) {
1248 if (*buf == '\0') return False; // not found
1249 if (_strncasecmp(buf, "x-playNow:", 10) == 0) break;
1250 ++buf;
1251 }
1252
1253 return True;
1254}
1255
1256void RTSPServer::RTSPClientSession
1257::handleCmd_SETUP(RTSPServer::RTSPClientConnection* ourClientConnection,
1258 char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
1259 // Normally, "urlPreSuffix" should be the session (stream) name, and "urlSuffix" should be the subsession (track) name.
1260 // However (being "liberal in what we accept"), we also handle 'aggregate' SETUP requests (i.e., without a track name),
1261 // in the special case where we have only a single track. I.e., in this case, we also handle:
1262 // "urlPreSuffix" is empty and "urlSuffix" is the session (stream) name, or
1263 // "urlPreSuffix" concatenated with "urlSuffix" (with "/" inbetween) is the session (stream) name.
1264 char const* streamName = urlPreSuffix; // in the normal case
1265 char const* trackId = urlSuffix; // in the normal case
1266 char* concatenatedStreamName = NULL; // in the normal case
1267
1268 do {
1269 // First, make sure the specified stream name exists:
1270 ServerMediaSession* sms
1271 = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
1272 if (sms == NULL) {
1273 // Check for the special case (noted above), before we give up:
1274 if (urlPreSuffix[0] == '\0') {
1275 streamName = urlSuffix;
1276 } else {
1277 concatenatedStreamName = new char[strlen(urlPreSuffix) + strlen(urlSuffix) + 2]; // allow for the "/" and the trailing '\0'
1278 sprintf(concatenatedStreamName, "%s/%s", urlPreSuffix, urlSuffix);
1279 streamName = concatenatedStreamName;
1280 }
1281 trackId = NULL;
1282
1283 // Check again:
1284 sms = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
1285 }
1286 if (sms == NULL) {
1287 if (fOurServerMediaSession == NULL) {
1288 // The client asked for a stream that doesn't exist (and this session descriptor has not been used before):
1289 ourClientConnection->handleCmd_notFound();
1290 } else {
1291 // The client asked for a stream that doesn't exist, but using a stream id for a stream that does exist. Bad request:
1292 ourClientConnection->handleCmd_bad();
1293 }
1294 break;
1295 } else {
1296 if (fOurServerMediaSession == NULL) {
1297 // We're accessing the "ServerMediaSession" for the first time.
1298 fOurServerMediaSession = sms;
1299 fOurServerMediaSession->incrementReferenceCount();
1300 } else if (sms != fOurServerMediaSession) {
1301 // The client asked for a stream that's different from the one originally requested for this stream id. Bad request:
1302 ourClientConnection->handleCmd_bad();
1303 break;
1304 }
1305 }
1306
1307 if (fStreamStates == NULL) {
1308 // This is the first "SETUP" for this session. Set up our array of states for all of this session's subsessions (tracks):
1309 fNumStreamStates = fOurServerMediaSession->numSubsessions();
1310 fStreamStates = new struct streamState[fNumStreamStates];
1311
1312 ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
1313 ServerMediaSubsession* subsession;
1314 for (unsigned i = 0; i < fNumStreamStates; ++i) {
1315 subsession = iter.next();
1316 fStreamStates[i].subsession = subsession;
1317 fStreamStates[i].tcpSocketNum = -1; // for now; may get set for RTP-over-TCP streaming
1318 fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later
1319 }
1320 }
1321
1322 // Look up information for the specified subsession (track):
1323 ServerMediaSubsession* subsession = NULL;
1324 unsigned trackNum;
1325 if (trackId != NULL && trackId[0] != '\0') { // normal case
1326 for (trackNum = 0; trackNum < fNumStreamStates; ++trackNum) {
1327 subsession = fStreamStates[trackNum].subsession;
1328 if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
1329 }
1330 if (trackNum >= fNumStreamStates) {
1331 // The specified track id doesn't exist, so this request fails:
1332 ourClientConnection->handleCmd_notFound();
1333 break;
1334 }
1335 } else {
1336 // Weird case: there was no track id in the URL.
1337 // This works only if we have only one subsession:
1338 if (fNumStreamStates != 1 || fStreamStates[0].subsession == NULL) {
1339 ourClientConnection->handleCmd_bad();
1340 break;
1341 }
1342 trackNum = 0;
1343 subsession = fStreamStates[trackNum].subsession;
1344 }
1345 // ASSERT: subsession != NULL
1346
1347 void*& token = fStreamStates[trackNum].streamToken; // alias
1348 if (token != NULL) {
1349 // We already handled a "SETUP" for this track (to the same client),
1350 // so stop any existing streaming of it, before we set it up again:
1351 subsession->pauseStream(fOurSessionId, token);
1352 fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
1353 subsession->deleteStream(fOurSessionId, token);
1354 }
1355
1356 // Look for a "Transport:" header in the request string, to extract client parameters:
1357 StreamingMode streamingMode;
1358 char* streamingModeString = NULL; // set when RAW_UDP streaming is specified
1359 char* clientsDestinationAddressStr;
1360 u_int8_t clientsDestinationTTL;
1361 portNumBits clientRTPPortNum, clientRTCPPortNum;
1362 unsigned char rtpChannelId, rtcpChannelId;
1363 parseTransportHeader(fullRequestStr, streamingMode, streamingModeString,
1364 clientsDestinationAddressStr, clientsDestinationTTL,
1365 clientRTPPortNum, clientRTCPPortNum,
1366 rtpChannelId, rtcpChannelId);
1367 if ((streamingMode == RTP_TCP && rtpChannelId == 0xFF) ||
1368 (streamingMode != RTP_TCP && ourClientConnection->fClientOutputSocket != ourClientConnection->fClientInputSocket)) {
1369 // An anomolous situation, caused by a buggy client. Either:
1370 // 1/ TCP streaming was requested, but with no "interleaving=" fields. (QuickTime Player sometimes does this.), or
1371 // 2/ TCP streaming was not requested, but we're doing RTSP-over-HTTP tunneling (which implies TCP streaming).
1372 // In either case, we assume TCP streaming, and set the RTP and RTCP channel ids to proper values:
1373 streamingMode = RTP_TCP;
1374 rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount+1;
1375 }
1376 if (streamingMode == RTP_TCP) fTCPStreamIdCount += 2;
1377
1378 Port clientRTPPort(clientRTPPortNum);
1379 Port clientRTCPPort(clientRTCPPortNum);
1380
1381 // Next, check whether a "Range:" or "x-playNow:" header is present in the request.
1382 // This isn't legal, but some clients do this to combine "SETUP" and "PLAY":
1383 double rangeStart = 0.0, rangeEnd = 0.0;
1384 char* absStart = NULL; char* absEnd = NULL;
1385 Boolean startTimeIsNow;
1386 if (parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow)) {
1387 delete[] absStart; delete[] absEnd;
1388 fStreamAfterSETUP = True;
1389 } else if (parsePlayNowHeader(fullRequestStr)) {
1390 fStreamAfterSETUP = True;
1391 } else {
1392 fStreamAfterSETUP = False;
1393 }
1394
1395 // Then, get server parameters from the 'subsession':
1396 if (streamingMode == RTP_TCP) {
1397 // Note that we'll be streaming over the RTSP TCP connection:
1398 fStreamStates[trackNum].tcpSocketNum = ourClientConnection->fClientOutputSocket;
1399 fOurRTSPServer.noteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
1400 }
1401 netAddressBits destinationAddress = 0;
1402 u_int8_t destinationTTL = 255;
1403#ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING
1404 if (clientsDestinationAddressStr != NULL) {
1405 // Use the client-provided "destination" address.
1406 // Note: This potentially allows the server to be used in denial-of-service
1407 // attacks, so don't enable this code unless you're sure that clients are
1408 // trusted.
1409 destinationAddress = our_inet_addr(clientsDestinationAddressStr);
1410 }
1411 // Also use the client-provided TTL.
1412 destinationTTL = clientsDestinationTTL;
1413#endif
1414 delete[] clientsDestinationAddressStr;
1415 Port serverRTPPort(0);
1416 Port serverRTCPPort(0);
1417
1418 // Make sure that we transmit on the same interface that's used by the client (in case we're a multi-homed server):
1419 struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
1420 getsockname(ourClientConnection->fClientInputSocket, (struct sockaddr*)&sourceAddr, &namelen);
1421 netAddressBits origSendingInterfaceAddr = SendingInterfaceAddr;
1422 netAddressBits origReceivingInterfaceAddr = ReceivingInterfaceAddr;
1423 // NOTE: The following might not work properly, so we ifdef it out for now:
1424#ifdef HACK_FOR_MULTIHOMED_SERVERS
1425 ReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;
1426#endif
1427
1428 subsession->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
1429 clientRTPPort, clientRTCPPort,
1430 fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
1431 destinationAddress, destinationTTL, fIsMulticast,
1432 serverRTPPort, serverRTCPPort,
1433 fStreamStates[trackNum].streamToken);
1434 SendingInterfaceAddr = origSendingInterfaceAddr;
1435 ReceivingInterfaceAddr = origReceivingInterfaceAddr;
1436
1437 AddressString destAddrStr(destinationAddress);
1438 AddressString sourceAddrStr(sourceAddr);
1439 char timeoutParameterString[100];
1440 if (fOurRTSPServer.fReclamationSeconds > 0) {
1441 sprintf(timeoutParameterString, ";timeout=%u", fOurRTSPServer.fReclamationSeconds);
1442 } else {
1443 timeoutParameterString[0] = '\0';
1444 }
1445 if (fIsMulticast) {
1446 switch (streamingMode) {
1447 case RTP_UDP: {
1448 snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
1449 "RTSP/1.0 200 OK\r\n"
1450 "CSeq: %s\r\n"
1451 "%s"
1452 "Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n"
1453 "Session: %08X%s\r\n\r\n",
1454 ourClientConnection->fCurrentCSeq,
1455 dateHeader(),
1456 destAddrStr.val(), sourceAddrStr.val(), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), destinationTTL,
1457 fOurSessionId, timeoutParameterString);
1458 break;
1459 }
1460 case RTP_TCP: {
1461 // multicast streams can't be sent via TCP
1462 ourClientConnection->handleCmd_unsupportedTransport();
1463 break;
1464 }
1465 case RAW_UDP: {
1466 snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
1467 "RTSP/1.0 200 OK\r\n"
1468 "CSeq: %s\r\n"
1469 "%s"
1470 "Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n"
1471 "Session: %08X%s\r\n\r\n",
1472 ourClientConnection->fCurrentCSeq,
1473 dateHeader(),
1474 streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(serverRTPPort.num()), destinationTTL,
1475 fOurSessionId, timeoutParameterString);
1476 break;
1477 }
1478 }
1479 } else {
1480 switch (streamingMode) {
1481 case RTP_UDP: {
1482 snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
1483 "RTSP/1.0 200 OK\r\n"
1484 "CSeq: %s\r\n"
1485 "%s"
1486 "Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"
1487 "Session: %08X%s\r\n\r\n",
1488 ourClientConnection->fCurrentCSeq,
1489 dateHeader(),
1490 destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()),
1491 fOurSessionId, timeoutParameterString);
1492 break;
1493 }
1494 case RTP_TCP: {
1495 if (!fOurRTSPServer.fAllowStreamingRTPOverTCP) {
1496 ourClientConnection->handleCmd_unsupportedTransport();
1497 } else {
1498 snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
1499 "RTSP/1.0 200 OK\r\n"
1500 "CSeq: %s\r\n"
1501 "%s"
1502 "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
1503 "Session: %08X%s\r\n\r\n",
1504 ourClientConnection->fCurrentCSeq,
1505 dateHeader(),
1506 destAddrStr.val(), sourceAddrStr.val(), rtpChannelId, rtcpChannelId,
1507 fOurSessionId, timeoutParameterString);
1508 }
1509 break;
1510 }
1511 case RAW_UDP: {
1512 snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
1513 "RTSP/1.0 200 OK\r\n"
1514 "CSeq: %s\r\n"
1515 "%s"
1516 "Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n"
1517 "Session: %08X%s\r\n\r\n",
1518 ourClientConnection->fCurrentCSeq,
1519 dateHeader(),
1520 streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()),
1521 fOurSessionId, timeoutParameterString);
1522 break;
1523 }
1524 }
1525 }
1526 delete[] streamingModeString;
1527 } while (0);
1528
1529 delete[] concatenatedStreamName;
1530}
1531
1532void RTSPServer::RTSPClientSession
1533::handleCmd_withinSession(RTSPServer::RTSPClientConnection* ourClientConnection,
1534 char const* cmdName,
1535 char const* urlPreSuffix, char const* urlSuffix,
1536 char const* fullRequestStr) {
1537 // This will either be:
1538 // - a non-aggregated operation, if "urlPreSuffix" is the session (stream)
1539 // name and "urlSuffix" is the subsession (track) name, or
1540 // - an aggregated operation, if "urlSuffix" is the session (stream) name,
1541 // or "urlPreSuffix" is the session (stream) name, and "urlSuffix" is empty,
1542 // or "urlPreSuffix" and "urlSuffix" are both nonempty, but when concatenated, (with "/") form the session (stream) name.
1543 // Begin by figuring out which of these it is:
1544 ServerMediaSubsession* subsession;
1545
1546 if (fOurServerMediaSession == NULL) { // There wasn't a previous SETUP!
1547 ourClientConnection->handleCmd_notSupported();
1548 return;
1549 } else if (urlSuffix[0] != '\0' && strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0) {
1550 // Non-aggregated operation.
1551 // Look up the media subsession whose track id is "urlSuffix":
1552 ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
1553 while ((subsession = iter.next()) != NULL) {
1554 if (strcmp(subsession->trackId(), urlSuffix) == 0) break; // success
1555 }
1556 if (subsession == NULL) { // no such track!
1557 ourClientConnection->handleCmd_notFound();
1558 return;
1559 }
1560 } else if (strcmp(fOurServerMediaSession->streamName(), urlSuffix) == 0 ||
1561 (urlSuffix[0] == '\0' && strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0)) {
1562 // Aggregated operation
1563 subsession = NULL;
1564 } else if (urlPreSuffix[0] != '\0' && urlSuffix[0] != '\0') {
1565 // Aggregated operation, if <urlPreSuffix>/<urlSuffix> is the session (stream) name:
1566 unsigned const urlPreSuffixLen = strlen(urlPreSuffix);
1567 if (strncmp(fOurServerMediaSession->streamName(), urlPreSuffix, urlPreSuffixLen) == 0 &&
1568 fOurServerMediaSession->streamName()[urlPreSuffixLen] == '/' &&
1569 strcmp(&(fOurServerMediaSession->streamName())[urlPreSuffixLen+1], urlSuffix) == 0) {
1570 subsession = NULL;
1571 } else {
1572 ourClientConnection->handleCmd_notFound();
1573 return;
1574 }
1575 } else { // the request doesn't match a known stream and/or track at all!
1576 ourClientConnection->handleCmd_notFound();
1577 return;
1578 }
1579
1580 if (strcmp(cmdName, "TEARDOWN") == 0) {
1581 handleCmd_TEARDOWN(ourClientConnection, subsession);
1582 } else if (strcmp(cmdName, "PLAY") == 0) {
1583 handleCmd_PLAY(ourClientConnection, subsession, fullRequestStr);
1584 } else if (strcmp(cmdName, "PAUSE") == 0) {
1585 handleCmd_PAUSE(ourClientConnection, subsession);
1586 } else if (strcmp(cmdName, "GET_PARAMETER") == 0) {
1587 handleCmd_GET_PARAMETER(ourClientConnection, subsession, fullRequestStr);
1588 } else if (strcmp(cmdName, "SET_PARAMETER") == 0) {
1589 handleCmd_SET_PARAMETER(ourClientConnection, subsession, fullRequestStr);
1590 }
1591}
1592
1593void RTSPServer::RTSPClientSession
1594::handleCmd_TEARDOWN(RTSPServer::RTSPClientConnection* ourClientConnection,
1595 ServerMediaSubsession* subsession) {
1596 unsigned i;
1597 for (i = 0; i < fNumStreamStates; ++i) {
1598 if (subsession == NULL /* means: aggregated operation */
1599 || subsession == fStreamStates[i].subsession) {
1600 if (fStreamStates[i].subsession != NULL) {
1601 fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[i].tcpSocketNum, this, i);
1602 fStreamStates[i].subsession->deleteStream(fOurSessionId, fStreamStates[i].streamToken);
1603 fStreamStates[i].subsession = NULL;
1604 }
1605 }
1606 }
1607
1608 setRTSPResponse(ourClientConnection, "200 OK");
1609
1610 // Optimization: If all subsessions have now been torn down, then we know that we can reclaim our object now.
1611 // (Without this optimization, however, this object would still get reclaimed later, as a result of a 'liveness' timeout.)
1612 Boolean noSubsessionsRemain = True;
1613 for (i = 0; i < fNumStreamStates; ++i) {
1614 if (fStreamStates[i].subsession != NULL) {
1615 noSubsessionsRemain = False;
1616 break;
1617 }
1618 }
1619 if (noSubsessionsRemain) delete this;
1620}
1621
1622void RTSPServer::RTSPClientSession
1623::handleCmd_PLAY(RTSPServer::RTSPClientConnection* ourClientConnection,
1624 ServerMediaSubsession* subsession, char const* fullRequestStr) {
1625 char* rtspURL
1626 = fOurRTSPServer.rtspURL(fOurServerMediaSession, ourClientConnection->fClientInputSocket);
1627 unsigned rtspURLSize = strlen(rtspURL);
1628
1629 // Parse the client's "Scale:" header, if any:
1630 float scale;
1631 Boolean sawScaleHeader = parseScaleHeader(fullRequestStr, scale);
1632
1633 // Try to set the stream's scale factor to this value:
1634 if (subsession == NULL /*aggregate op*/) {
1635 fOurServerMediaSession->testScaleFactor(scale);
1636 } else {
1637 subsession->testScaleFactor(scale);
1638 }
1639
1640 char buf[100];
1641 char* scaleHeader;
1642 if (!sawScaleHeader) {
1643 buf[0] = '\0'; // Because we didn't see a Scale: header, don't send one back
1644 } else {
1645 sprintf(buf, "Scale: %f\r\n", scale);
1646 }
1647 scaleHeader = strDup(buf);
1648
1649 // Parse the client's "Range:" header, if any:
1650 float duration = 0.0;
1651 double rangeStart = 0.0, rangeEnd = 0.0;
1652 char* absStart = NULL; char* absEnd = NULL;
1653 Boolean startTimeIsNow;
1654 Boolean sawRangeHeader
1655 = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow);
1656
1657 if (sawRangeHeader && absStart == NULL/*not seeking by 'absolute' time*/) {
1658 // Use this information, plus the stream's duration (if known), to create our own "Range:" header, for the response:
1659 duration = subsession == NULL /*aggregate op*/
1660 ? fOurServerMediaSession->duration() : subsession->duration();
1661 if (duration < 0.0) {
1662 // We're an aggregate PLAY, but the subsessions have different durations.
1663 // Use the largest of these durations in our header
1664 duration = -duration;
1665 }
1666
1667 // Make sure that "rangeStart" and "rangeEnd" (from the client's "Range:" header)
1668 // have sane values, before we send back our own "Range:" header in our response:
1669 if (rangeStart < 0.0) rangeStart = 0.0;
1670 else if (rangeStart > duration) rangeStart = duration;
1671 if (rangeEnd < 0.0) rangeEnd = 0.0;
1672 else if (rangeEnd > duration) rangeEnd = duration;
1673 if ((scale > 0.0 && rangeStart > rangeEnd && rangeEnd > 0.0) ||
1674 (scale < 0.0 && rangeStart < rangeEnd)) {
1675 // "rangeStart" and "rangeEnd" were the wrong way around; swap them:
1676 double tmp = rangeStart;
1677 rangeStart = rangeEnd;
1678 rangeEnd = tmp;
1679 }
1680 }
1681
1682 // Create a "RTP-Info:" line. It will get filled in from each subsession's state:
1683 char const* rtpInfoFmt =
1684 "%s" // "RTP-Info:", plus any preceding rtpInfo items
1685 "%s" // comma separator, if needed
1686 "url=%s/%s"
1687 ";seq=%d"
1688 ";rtptime=%u"
1689 ;
1690 unsigned rtpInfoFmtSize = strlen(rtpInfoFmt);
1691 char* rtpInfo = strDup("RTP-Info: ");
1692 unsigned i, numRTPInfoItems = 0;
1693
1694 // Do any required seeking/scaling on each subsession, before starting streaming.
1695 // (However, we don't do this if the "PLAY" request was for just a single subsession
1696 // of a multiple-subsession stream; for such streams, seeking/scaling can be done
1697 // only with an aggregate "PLAY".)
1698 for (i = 0; i < fNumStreamStates; ++i) {
1699 if (subsession == NULL /* means: aggregated operation */ || fNumStreamStates == 1) {
1700 if (fStreamStates[i].subsession != NULL) {
1701 if (sawScaleHeader) {
1702 fStreamStates[i].subsession->setStreamScale(fOurSessionId, fStreamStates[i].streamToken, scale);
1703 }
1704 if (absStart != NULL) {
1705 // Special case handling for seeking by 'absolute' time:
1706
1707 fStreamStates[i].subsession->seekStream(fOurSessionId, fStreamStates[i].streamToken, absStart, absEnd);
1708 } else {
1709 // Seeking by relative (NPT) time:
1710
1711 u_int64_t numBytes;
1712 if (!sawRangeHeader || startTimeIsNow) {
1713 // We're resuming streaming without seeking, so we just do a 'null' seek
1714 // (to get our NPT, and to specify when to end streaming):
1715 fStreamStates[i].subsession->nullSeekStream(fOurSessionId, fStreamStates[i].streamToken,
1716 rangeEnd, numBytes);
1717 } else {
1718 // We do a real 'seek':
1719 double streamDuration = 0.0; // by default; means: stream until the end of the media
1720 if (rangeEnd > 0.0 && (rangeEnd+0.001) < duration) {
1721 // the 0.001 is because we limited the values to 3 decimal places
1722 // We want the stream to end early. Set the duration we want:
1723 streamDuration = rangeEnd - rangeStart;
1724 if (streamDuration < 0.0) streamDuration = -streamDuration;
1725 // should happen only if scale < 0.0
1726 }
1727 fStreamStates[i].subsession->seekStream(fOurSessionId, fStreamStates[i].streamToken,
1728 rangeStart, streamDuration, numBytes);
1729 }
1730 }
1731 }
1732 }
1733 }
1734
1735 // Create the "Range:" header that we'll send back in our response.
1736 // (Note that we do this after seeking, in case the seeking operation changed the range start time.)
1737 if (absStart != NULL) {
1738 // We're seeking by 'absolute' time:
1739 if (absEnd == NULL) {
1740 sprintf(buf, "Range: clock=%s-\r\n", absStart);
1741 } else {
1742 sprintf(buf, "Range: clock=%s-%s\r\n", absStart, absEnd);
1743 }
1744 delete[] absStart; delete[] absEnd;
1745 } else {
1746 // We're seeking by relative (NPT) time:
1747 if (!sawRangeHeader || startTimeIsNow) {
1748 // We didn't seek, so in our response, begin the range with the current NPT (normal play time):
1749 float curNPT = 0.0;
1750 for (i = 0; i < fNumStreamStates; ++i) {
1751 if (subsession == NULL /* means: aggregated operation */
1752 || subsession == fStreamStates[i].subsession) {
1753 if (fStreamStates[i].subsession == NULL) continue;
1754 float npt = fStreamStates[i].subsession->getCurrentNPT(fStreamStates[i].streamToken);
1755 if (npt > curNPT) curNPT = npt;
1756 // Note: If this is an aggregate "PLAY" on a multi-subsession stream,
1757 // then it's conceivable that the NPTs of each subsession may differ
1758 // (if there has been a previous seek on just one subsession).
1759 // In this (unusual) case, we just return the largest NPT; I hope that turns out OK...
1760 }
1761 }
1762 rangeStart = curNPT;
1763 }
1764
1765 if (rangeEnd == 0.0 && scale >= 0.0) {
1766 sprintf(buf, "Range: npt=%.3f-\r\n", rangeStart);
1767 } else {
1768 sprintf(buf, "Range: npt=%.3f-%.3f\r\n", rangeStart, rangeEnd);
1769 }
1770 }
1771 char* rangeHeader = strDup(buf);
1772
1773 // Now, start streaming:
1774 for (i = 0; i < fNumStreamStates; ++i) {
1775 if (subsession == NULL /* means: aggregated operation */
1776 || subsession == fStreamStates[i].subsession) {
1777 unsigned short rtpSeqNum = 0;
1778 unsigned rtpTimestamp = 0;
1779 if (fStreamStates[i].subsession == NULL) continue;
1780 fStreamStates[i].subsession->startStream(fOurSessionId,
1781 fStreamStates[i].streamToken,
1782 (TaskFunc*)noteClientLiveness, this,
1783 rtpSeqNum, rtpTimestamp,
1784 RTSPServer::RTSPClientConnection::handleAlternativeRequestByte, ourClientConnection);
1785 const char *urlSuffix = fStreamStates[i].subsession->trackId();
1786 char* prevRTPInfo = rtpInfo;
1787 unsigned rtpInfoSize = rtpInfoFmtSize
1788 + strlen(prevRTPInfo)
1789 + 1
1790 + rtspURLSize + strlen(urlSuffix)
1791 + 5 /*max unsigned short len*/
1792 + 10 /*max unsigned (32-bit) len*/
1793 + 2 /*allows for trailing \r\n at final end of string*/;
1794 rtpInfo = new char[rtpInfoSize];
1795 sprintf(rtpInfo, rtpInfoFmt,
1796 prevRTPInfo,
1797 numRTPInfoItems++ == 0 ? "" : ",",
1798 rtspURL, urlSuffix,
1799 rtpSeqNum,
1800 rtpTimestamp
1801 );
1802 delete[] prevRTPInfo;
1803 }
1804 }
1805 if (numRTPInfoItems == 0) {
1806 rtpInfo[0] = '\0';
1807 } else {
1808 unsigned rtpInfoLen = strlen(rtpInfo);
1809 rtpInfo[rtpInfoLen] = '\r';
1810 rtpInfo[rtpInfoLen+1] = '\n';
1811 rtpInfo[rtpInfoLen+2] = '\0';
1812 }
1813
1814 // Fill in the response:
1815 snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
1816 "RTSP/1.0 200 OK\r\n"
1817 "CSeq: %s\r\n"
1818 "%s"
1819 "%s"
1820 "%s"
1821 "Session: %08X\r\n"
1822 "%s\r\n",
1823 ourClientConnection->fCurrentCSeq,
1824 dateHeader(),
1825 scaleHeader,
1826 rangeHeader,
1827 fOurSessionId,
1828 rtpInfo);
1829 delete[] rtpInfo; delete[] rangeHeader;
1830 delete[] scaleHeader; delete[] rtspURL;
1831}
1832
1833void RTSPServer::RTSPClientSession
1834::handleCmd_PAUSE(RTSPServer::RTSPClientConnection* ourClientConnection,
1835 ServerMediaSubsession* subsession) {
1836 for (unsigned i = 0; i < fNumStreamStates; ++i) {
1837 if (subsession == NULL /* means: aggregated operation */
1838 || subsession == fStreamStates[i].subsession) {
1839 if (fStreamStates[i].subsession != NULL) {
1840 fStreamStates[i].subsession->pauseStream(fOurSessionId, fStreamStates[i].streamToken);
1841 }
1842 }
1843 }
1844
1845 setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId);
1846}
1847
1848void RTSPServer::RTSPClientSession
1849::handleCmd_GET_PARAMETER(RTSPServer::RTSPClientConnection* ourClientConnection,
1850 ServerMediaSubsession* /*subsession*/, char const* /*fullRequestStr*/) {
1851 // By default, we implement "GET_PARAMETER" just as a 'keep alive', and send back a dummy response.
1852 // (If you want to handle "GET_PARAMETER" properly, you can do so by defining a subclass of "RTSPServer"
1853 // and "RTSPServer::RTSPClientSession", and then reimplement this virtual function in your subclass.)
1854 setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId, LIVEMEDIA_LIBRARY_VERSION_STRING);
1855}
1856
1857void RTSPServer::RTSPClientSession
1858::handleCmd_SET_PARAMETER(RTSPServer::RTSPClientConnection* ourClientConnection,
1859 ServerMediaSubsession* /*subsession*/, char const* /*fullRequestStr*/) {
1860 // By default, we implement "SET_PARAMETER" just as a 'keep alive', and send back an empty response.
1861 // (If you want to handle "SET_PARAMETER" properly, you can do so by defining a subclass of "RTSPServer"
1862 // and "RTSPServer::RTSPClientSession", and then reimplement this virtual function in your subclass.)
1863 setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId);
1864}
1865
1866GenericMediaServer::ClientConnection*
1867RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
1868 return new RTSPClientConnection(*this, clientSocket, clientAddr);
1869}
1870
1871GenericMediaServer::ClientSession*
1872RTSPServer::createNewClientSession(u_int32_t sessionId) {
1873 return new RTSPClientSession(*this, sessionId);
1874}
1875