1 | /********** |
2 | This library is free software; you can redistribute it and/or modify it under |
3 | the terms of the GNU Lesser General Public License as published by the |
4 | Free Software Foundation; either version 3 of the License, or (at your |
5 | option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
6 | |
7 | This library is distributed in the hope that it will be useful, but WITHOUT |
8 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
9 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
10 | more details. |
11 | |
12 | You should have received a copy of the GNU Lesser General Public License |
13 | along with this library; if not, write to the Free Software Foundation, Inc., |
14 | 51 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 | |
29 | RTSPServer* |
30 | RTSPServer::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 | |
39 | Boolean 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 | |
56 | char* 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 | |
68 | char* 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 | |
93 | UserAuthenticationDatabase* RTSPServer::setAuthenticationDatabase(UserAuthenticationDatabase* newDB) { |
94 | UserAuthenticationDatabase* oldDB = fAuthDB; |
95 | fAuthDB = newDB; |
96 | |
97 | return oldDB; |
98 | } |
99 | |
100 | Boolean 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 | |
112 | portNumBits RTSPServer::httpServerPortNum() const { |
113 | return ntohs(fHTTPServerPort.num()); |
114 | } |
115 | |
116 | char const* RTSPServer::allowedCommandNames() { |
117 | return "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER" ; |
118 | } |
119 | |
120 | UserAuthenticationDatabase* RTSPServer::getAuthenticationDatabaseForCommand(char const* /*cmdName*/) { |
121 | // default implementation |
122 | return fAuthDB; |
123 | } |
124 | |
125 | Boolean RTSPServer::specialClientAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/, char const* /*urlSuffix*/) { |
126 | // default implementation |
127 | return True; |
128 | } |
129 | |
130 | Boolean 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 | |
137 | RTSPServer::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): |
151 | class streamingOverTCPRecord { |
152 | public: |
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 | |
165 | RTSPServer::~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 | |
188 | Boolean RTSPServer::isRTSPServer() const { |
189 | return True; |
190 | } |
191 | |
192 | void RTSPServer::incomingConnectionHandlerHTTP(void* instance, int /*mask*/) { |
193 | RTSPServer* server = (RTSPServer*)instance; |
194 | server->incomingConnectionHandlerHTTP(); |
195 | } |
196 | void RTSPServer::incomingConnectionHandlerHTTP() { |
197 | incomingConnectionHandlerOnSocket(fHTTPServerSocket); |
198 | } |
199 | |
200 | void 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 | |
209 | void 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 | |
247 | void 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 | |
271 | RTSPServer::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 | |
279 | RTSPServer::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 | |
291 | void 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 | |
297 | void 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 | |
305 | void 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 | |
313 | void 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 | |
384 | static void (char const* , char const* source, unsigned sourceLen, char* resultStr, unsigned resultMaxSize) { |
385 | resultStr[0] = '\0'; // by default, return an empty string |
386 | unsigned = 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 | |
406 | void 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 | |
413 | void 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 | |
419 | void RTSPServer::RTSPClientConnection::handleCmd_notFound() { |
420 | setRTSPResponse("404 Stream Not Found" ); |
421 | } |
422 | |
423 | void RTSPServer::RTSPClientConnection::handleCmd_sessionNotFound() { |
424 | setRTSPResponse("454 Session Not Found" ); |
425 | } |
426 | |
427 | void RTSPServer::RTSPClientConnection::handleCmd_unsupportedTransport() { |
428 | setRTSPResponse("461 Unsupported Transport" ); |
429 | } |
430 | |
431 | Boolean 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 | |
484 | void 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 | |
490 | void 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 | |
496 | void 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 | |
512 | void 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 | |
535 | Boolean RTSPServer::RTSPClientConnection |
536 | ::handleHTTPCmd_TunnelingPOST(char const* sessionCookie, unsigned char const* , unsigned ) { |
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 | |
560 | void 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 | |
565 | void 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 | |
572 | void 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 | |
586 | void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte(void* instance, u_int8_t requestByte) { |
587 | RTSPClientConnection* connection = (RTSPClientConnection*)instance; |
588 | connection->handleAlternativeRequestByte1(requestByte); |
589 | } |
590 | |
591 | void 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 | |
607 | void 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* = fLastCRLF+4; |
862 | unsigned = &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 | |
916 | static Boolean (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 | |
974 | Boolean 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 | |
1053 | void 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 | |
1064 | void 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 | |
1077 | void 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 | |
1095 | void 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 | |
1115 | void RTSPServer::RTSPClientConnection |
1116 | ::changeClientInputSocket(int newSocketNum, unsigned char const* , unsigned ) { |
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 | |
1135 | RTSPServer::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 | |
1142 | RTSPServer::RTSPClientSession::~RTSPClientSession() { |
1143 | reclaimStreamStates(); |
1144 | } |
1145 | |
1146 | void 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 | |
1164 | void 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 | |
1175 | typedef enum StreamingMode { |
1176 | RTP_UDP, |
1177 | RTP_TCP, |
1178 | RAW_UDP |
1179 | } StreamingMode; |
1180 | |
1181 | static void (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 | |
1245 | static Boolean (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 | |
1256 | void 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 | |
1532 | void 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 | |
1593 | void 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 | |
1622 | void 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 = 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* ; |
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 |
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* = 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 | |
1833 | void 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 | |
1848 | void 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 | |
1857 | void 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 | |
1866 | GenericMediaServer::ClientConnection* |
1867 | RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) { |
1868 | return new RTSPClientConnection(*this, clientSocket, clientAddr); |
1869 | } |
1870 | |
1871 | GenericMediaServer::ClientSession* |
1872 | RTSPServer::createNewClientSession(u_int32_t sessionId) { |
1873 | return new RTSPClientSession(*this, sessionId); |
1874 | } |
1875 | |