363
369
void RTSPServer::RTSPClientSession::handleRequestBytes(int newBytesRead) {
366
if (newBytesRead <= 0 || (unsigned)newBytesRead >= fRequestBufferBytesLeft) {
367
// Either the client socket has died, or the request was too big for us.
368
// Terminate this connection:
370
fprintf(stderr, "RTSPClientSession[%p]::handleRequestBytes() read %d new bytes (of %d); terminating connection!\n", this, newBytesRead, fRequestBufferBytesLeft);
376
Boolean endOfMsg = False;
377
unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen];
379
ptr[newBytesRead] = '\0';
380
fprintf(stderr, "RTSPClientSession[%p]::handleRequestBytes() read %d new bytes:%s\n", this, newBytesRead, ptr);
383
if (fClientOutputSocket != fClientInputSocket) {
384
// We're doing RTSP-over-HTTP tunneling, and input commands are assumed to have been Base64-encoded.
385
// We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes):
386
unsigned numBytesToDecode = fBase64RemainderCount + newBytesRead;
387
unsigned newBase64RemainderCount = numBytesToDecode%4;
388
numBytesToDecode -= newBase64RemainderCount;
389
if (numBytesToDecode > 0) {
390
ptr[newBytesRead] = '\0';
391
unsigned decodedSize;
392
unsigned char* decodedBytes = base64Decode((char*)(ptr-fBase64RemainderCount), decodedSize);
394
fprintf(stderr, "Base64-decoded %d input bytes into %d new bytes:", numBytesToDecode, decodedSize);
395
for (unsigned k = 0; k < decodedSize; ++k) fprintf(stderr, "%c", decodedBytes[k]);
396
fprintf(stderr, "\n");
399
// Copy the new decoded bytes in place of the old ones (we can do this because there are fewer decoded bytes than original):
400
unsigned char* to = ptr-fBase64RemainderCount;
401
for (unsigned i = 0; i < decodedSize; ++i) *to++ = decodedBytes[i];
375
if (newBytesRead <= 0 || (unsigned)newBytesRead >= fRequestBufferBytesLeft) {
376
// Either the client socket has died, or the request was too big for us.
377
// Terminate this connection:
379
fprintf(stderr, "RTSPClientSession[%p]::handleRequestBytes() read %d new bytes (of %d); terminating connection!\n", this, newBytesRead, fRequestBufferBytesLeft);
381
fSessionIsActive = False;
385
Boolean endOfMsg = False;
386
unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen];
388
ptr[newBytesRead] = '\0';
389
fprintf(stderr, "RTSPClientSession[%p]::handleRequestBytes() read %d new bytes:%s\n", this, newBytesRead, ptr);
392
if (fClientOutputSocket != fClientInputSocket) {
393
// We're doing RTSP-over-HTTP tunneling, and input commands are assumed to have been Base64-encoded.
394
// We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes):
395
unsigned numBytesToDecode = fBase64RemainderCount + newBytesRead;
396
unsigned newBase64RemainderCount = numBytesToDecode%4;
397
numBytesToDecode -= newBase64RemainderCount;
398
if (numBytesToDecode > 0) {
399
ptr[newBytesRead] = '\0';
400
unsigned decodedSize;
401
unsigned char* decodedBytes = base64Decode((char*)(ptr-fBase64RemainderCount), decodedSize);
403
fprintf(stderr, "Base64-decoded %d input bytes into %d new bytes:", numBytesToDecode, decodedSize);
404
for (unsigned k = 0; k < decodedSize; ++k) fprintf(stderr, "%c", decodedBytes[k]);
405
fprintf(stderr, "\n");
408
// Copy the new decoded bytes in place of the old ones (we can do this because there are fewer decoded bytes than original):
409
unsigned char* to = ptr-fBase64RemainderCount;
410
for (unsigned i = 0; i < decodedSize; ++i) *to++ = decodedBytes[i];
412
// Then copy any remaining (undecoded) bytes to the end:
413
for (unsigned j = 0; j < newBase64RemainderCount; ++j) *to++ = (ptr-fBase64RemainderCount+numBytesToDecode)[j];
415
newBytesRead = decodedSize + newBase64RemainderCount; // adjust to allow for the size of the new decoded data (+ remainder)
416
delete[] decodedBytes;
418
fBase64RemainderCount = newBase64RemainderCount;
419
if (fBase64RemainderCount > 0) break; // because we know that we have more input bytes still to receive
422
// Look for the end of the message: <CR><LF><CR><LF>
423
unsigned char *tmpPtr = ptr;
424
if (fRequestBytesAlreadySeen > 0) --tmpPtr;
425
// in case the last read ended with a <CR>
426
while (tmpPtr < &ptr[newBytesRead-1]) {
427
if (*tmpPtr == '\r' && *(tmpPtr+1) == '\n') {
428
if (tmpPtr - fLastCRLF == 2) { // This is it:
437
fRequestBufferBytesLeft -= newBytesRead;
438
fRequestBytesAlreadySeen += newBytesRead;
440
if (!endOfMsg) break; // subsequent reads will be needed to complete the request
442
// Parse the request string into command name and 'CSeq', then handle the command:
443
fRequestBuffer[fRequestBytesAlreadySeen] = '\0';
444
char cmdName[RTSP_PARAM_STRING_MAX];
445
char urlPreSuffix[RTSP_PARAM_STRING_MAX];
446
char urlSuffix[RTSP_PARAM_STRING_MAX];
447
char cseq[RTSP_PARAM_STRING_MAX];
448
unsigned contentLength;
449
if (parseRTSPRequestString((char*)fRequestBuffer, fRequestBytesAlreadySeen,
450
cmdName, sizeof cmdName,
451
urlPreSuffix, sizeof urlPreSuffix,
452
urlSuffix, sizeof urlSuffix,
456
fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\", CSeq \"%s\", Content-Length %u\n", cmdName, urlPreSuffix, urlSuffix, cseq, contentLength);
458
// If there was a "Content-Length:" header, then make sure we've received all of the data that it specified:
459
if (ptr + newBytesRead < tmpPtr + 2 + contentLength) break; // we still need more data; subsequent reads will give it to us
403
// Then copy any remaining (undecoded) bytes to the end:
404
for (unsigned j = 0; j < newBase64RemainderCount; ++j) *to++ = (ptr-fBase64RemainderCount+numBytesToDecode)[j];
406
newBytesRead = decodedSize + newBase64RemainderCount; // adjust to allow for the size of the new decoded data (+ remainder)
407
delete[] decodedBytes;
409
fBase64RemainderCount = newBase64RemainderCount;
410
if (fBase64RemainderCount > 0) return; // because we know that we have more input bytes still to receive
413
// Look for the end of the message: <CR><LF><CR><LF>
414
unsigned char *tmpPtr = ptr;
415
if (fRequestBytesAlreadySeen > 0) --tmpPtr;
416
// in case the last read ended with a <CR>
417
while (tmpPtr < &ptr[newBytesRead-1]) {
418
if (*tmpPtr == '\r' && *(tmpPtr+1) == '\n') {
419
if (tmpPtr - fLastCRLF == 2) { // This is it:
428
fRequestBufferBytesLeft -= newBytesRead;
429
fRequestBytesAlreadySeen += newBytesRead;
431
if (!endOfMsg) return; // subsequent reads will be needed to complete the request
433
// Parse the request string into command name and 'CSeq', then handle the command:
434
fRequestBuffer[fRequestBytesAlreadySeen] = '\0';
435
char cmdName[RTSP_PARAM_STRING_MAX];
436
char urlPreSuffix[RTSP_PARAM_STRING_MAX];
437
char urlSuffix[RTSP_PARAM_STRING_MAX];
438
char cseq[RTSP_PARAM_STRING_MAX];
439
unsigned contentLength;
440
if (parseRTSPRequestString((char*)fRequestBuffer, fRequestBytesAlreadySeen,
441
cmdName, sizeof cmdName,
442
urlPreSuffix, sizeof urlPreSuffix,
443
urlSuffix, sizeof urlSuffix,
447
fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\", CSeq \"%s\", Content-Length %u\n", cmdName, urlPreSuffix, urlSuffix, cseq, contentLength);
449
// If there was a "Content-Length:" header, then make sure we've received all of the data that it specified:
450
if (ptr + newBytesRead < tmpPtr + 2 + contentLength) return; // we still need more data; subsequent reads will give it to us
452
if (strcmp(cmdName, "OPTIONS") == 0) {
453
handleCmd_OPTIONS(cseq);
454
} else if (strcmp(cmdName, "DESCRIBE") == 0) {
455
handleCmd_DESCRIBE(cseq, urlSuffix, (char const*)fRequestBuffer);
456
} else if (strcmp(cmdName, "SETUP") == 0) {
457
handleCmd_SETUP(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
458
} else if (strcmp(cmdName, "TEARDOWN") == 0
459
|| strcmp(cmdName, "PLAY") == 0
460
|| strcmp(cmdName, "PAUSE") == 0
461
|| strcmp(cmdName, "GET_PARAMETER") == 0
462
|| strcmp(cmdName, "SET_PARAMETER") == 0) {
463
handleCmd_withinSession(cmdName, urlPreSuffix, urlSuffix, cseq,
461
if (strcmp(cmdName, "OPTIONS") == 0) {
462
handleCmd_OPTIONS(cseq);
463
} else if (strcmp(cmdName, "DESCRIBE") == 0) {
464
handleCmd_DESCRIBE(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
465
} else if (strcmp(cmdName, "SETUP") == 0) {
466
handleCmd_SETUP(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
467
} else if (strcmp(cmdName, "TEARDOWN") == 0
468
|| strcmp(cmdName, "PLAY") == 0
469
|| strcmp(cmdName, "PAUSE") == 0
470
|| strcmp(cmdName, "GET_PARAMETER") == 0
471
|| strcmp(cmdName, "SET_PARAMETER") == 0) {
472
handleCmd_withinSession(cmdName, urlPreSuffix, urlSuffix, cseq, (char const*)fRequestBuffer);
474
handleCmd_notSupported(cseq);
478
fprintf(stderr, "parseRTSPRequestString() failed\n");
480
// The request was not (valid) RTSP, but check for a special case: HTTP commands (for setting up RTSP-over-HTTP tunneling):
481
char sessionCookie[RTSP_PARAM_STRING_MAX];
482
char acceptStr[RTSP_PARAM_STRING_MAX];
483
if (parseHTTPRequestString(cmdName, sizeof cmdName,
484
urlSuffix, sizeof urlPreSuffix,
485
sessionCookie, sizeof sessionCookie,
486
acceptStr, sizeof acceptStr)) {
488
fprintf(stderr, "parseHTTPRequestString() succeeded, returning cmdName \"%s\", urlSuffix \"%s\", sessionCookie \"%s\", acceptStr \"%s\"\n", cmdName, urlSuffix, sessionCookie, acceptStr);
490
// Check that the HTTP command is valid for RTSP-over-HTTP tunneling: There must be a 'session cookie'.
491
Boolean isValidHTTPCmd = True;
492
if (sessionCookie[0] == '\0') {
493
// There was no "x-sessionCookie:" header. If there was an "Accept: application/x-rtsp-tunnelled" header,
494
// then this is a bad tunneling request. Otherwise, assume that it's an attempt to access the stream via HTTP.
495
if (strcmp(acceptStr, "application/x-rtsp-tunnelled") == 0) {
496
isValidHTTPCmd = False;
498
handleHTTPCmd_StreamingGET(urlSuffix, (char const*)fRequestBuffer);
500
} else if (strcmp(cmdName, "GET") == 0) {
501
handleHTTPCmd_TunnelingGET(sessionCookie);
502
} else if (strcmp(cmdName, "POST") == 0) {
503
// We might have received additional data following the HTTP "POST" command - i.e., the first Base64-encoded RTSP command.
504
// Check for this, and handle it if it exists:
505
unsigned char const* extraData = fLastCRLF+4;
506
unsigned extraDataSize = &fRequestBuffer[fRequestBytesAlreadySeen] - extraData;
507
if (handleHTTPCmd_TunnelingPOST(sessionCookie, extraData, extraDataSize)) {
508
// We don't respond to the "POST" command, and we go away:
509
fSessionIsActive = False;
513
isValidHTTPCmd = False;
515
if (!isValidHTTPCmd) {
516
handleHTTPCmd_notSupported();
520
fprintf(stderr, "parseHTTPRequestString() failed!\n");
527
fprintf(stderr, "sending response: %s", fResponseBuffer);
529
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
531
if (strcmp(cmdName, "SETUP") == 0 && fStreamAfterSETUP) {
532
// The client has asked for streaming to commence now, rather than after a
533
// subsequent "PLAY" command. So, simulate the effect of a "PLAY" command:
534
handleCmd_withinSession("PLAY", urlPreSuffix, urlSuffix, cseq,
464
535
(char const*)fRequestBuffer);
466
handleCmd_notSupported(cseq);
470
fprintf(stderr, "parseRTSPRequestString() failed\n");
472
// The request was not (valid) RTSP, but check for a special case: HTTP commands (for setting up RTSP-over-HTTP tunneling):
473
char sessionCookie[RTSP_PARAM_STRING_MAX];
474
char acceptStr[RTSP_PARAM_STRING_MAX];
475
if (parseHTTPRequestString(cmdName, sizeof cmdName,
476
urlSuffix, sizeof urlPreSuffix,
477
sessionCookie, sizeof sessionCookie,
478
acceptStr, sizeof acceptStr)) {
480
fprintf(stderr, "parseHTTPRequestString() succeeded, returning cmdName \"%s\", urlSuffix \"%s\", sessionCookie \"%s\", acceptStr \"%s\"\n", cmdName, urlSuffix, sessionCookie, acceptStr);
482
// Check that the HTTP command is valid for RTSP-over-HTTP tunneling: There must be a 'session cookie'.
483
Boolean isValidHTTPCmd = True;
484
if (sessionCookie[0] == '\0') {
485
// There was no "x-sessionCookie:" header. If there was an "Accept: application/x-rtsp-tunnelled" header,
486
// then this is a bad tunneling request. Otherwise, assume that it's an attempt to access the stream via HTTP.
487
if (strcmp(acceptStr, "application/x-rtsp-tunnelled") == 0) {
488
isValidHTTPCmd = False;
490
handleHTTPCmd_StreamingGET(urlSuffix, (char const*)fRequestBuffer);
492
} else if (strcmp(cmdName, "GET") == 0) {
493
handleHTTPCmd_TunnelingGET(sessionCookie);
494
} else if (strcmp(cmdName, "POST") == 0) {
495
// We might have received additional data following the HTTP "POST" command - i.e., the first Base64-encoded RTSP command.
496
// Check for this, and handle it if it exists:
497
unsigned char const* extraData = fLastCRLF+4;
498
unsigned extraDataSize = &fRequestBuffer[fRequestBytesAlreadySeen] - extraData;
499
if (handleHTTPCmd_TunnelingPOST(sessionCookie, extraData, extraDataSize)) {
500
// We don't respond to the "POST" command, and we go away:
505
isValidHTTPCmd = False;
507
if (!isValidHTTPCmd) {
508
handleHTTPCmd_notSupported();
512
fprintf(stderr, "parseHTTPRequestString() failed!\n");
519
fprintf(stderr, "sending response: %s", fResponseBuffer);
521
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
523
if (strcmp(cmdName, "SETUP") == 0 && fStreamAfterSETUP) {
524
// The client has asked for streaming to commence now, rather than after a
525
// subsequent "PLAY" command. So, simulate the effect of a "PLAY" command:
526
handleCmd_withinSession("PLAY", urlPreSuffix, urlSuffix, cseq,
527
(char const*)fRequestBuffer);
530
resetRequestBuffer(); // to prepare for any subsequent request
531
if (!fSessionIsActive) delete this;
538
resetRequestBuffer(); // to prepare for any subsequent request
542
if (!fSessionIsActive) {
543
if (fRecursionCount > 0) closeSockets(); else delete this;
544
// Note: The "fRecursionCount" test is for a pathological situation where we got called recursively while handling a command.
545
// In such a case we don't want to actually delete ourself until we leave the outermost call.
534
549
// Handler routines for specific RTSP commands: