| 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 generic RTSP client |
| 19 | // Implementation |
| 20 | |
| 21 | #include "RTSPClient.hh" |
| 22 | #include "RTSPCommon.hh" |
| 23 | #include "Base64.hh" |
| 24 | #include "Locale.hh" |
| 25 | #include <GroupsockHelper.hh> |
| 26 | #include "ourMD5.hh" |
| 27 | |
| 28 | RTSPClient* RTSPClient::createNew(UsageEnvironment& env, char const* rtspURL, |
| 29 | int verbosityLevel, |
| 30 | char const* applicationName, |
| 31 | portNumBits tunnelOverHTTPPortNum, |
| 32 | int socketNumToServer) { |
| 33 | return new RTSPClient(env, rtspURL, |
| 34 | verbosityLevel, applicationName, tunnelOverHTTPPortNum, socketNumToServer); |
| 35 | } |
| 36 | |
| 37 | unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) { |
| 38 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 39 | return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE" , responseHandler)); |
| 40 | } |
| 41 | |
| 42 | unsigned RTSPClient::sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator) { |
| 43 | if (authenticator != NULL) fCurrentAuthenticator = *authenticator; |
| 44 | return sendRequest(new RequestRecord(++fCSeq, "OPTIONS" , responseHandler)); |
| 45 | } |
| 46 | |
| 47 | unsigned RTSPClient::sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator) { |
| 48 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 49 | return sendRequest(new RequestRecord(++fCSeq, "ANNOUNCE" , responseHandler, NULL, NULL, False, 0.0, 0.0, 0.0, sdpDescription)); |
| 50 | } |
| 51 | |
| 52 | unsigned RTSPClient::sendSetupCommand(MediaSubsession& subsession, responseHandler* responseHandler, |
| 53 | Boolean streamOutgoing, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified, |
| 54 | Authenticator* authenticator) { |
| 55 | if (fTunnelOverHTTPPortNum != 0) streamUsingTCP = True; // RTSP-over-HTTP tunneling uses TCP (by definition) |
| 56 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 57 | |
| 58 | u_int32_t booleanFlags = 0; |
| 59 | if (streamUsingTCP) booleanFlags |= 0x1; |
| 60 | if (streamOutgoing) booleanFlags |= 0x2; |
| 61 | if (forceMulticastOnUnspecified) booleanFlags |= 0x4; |
| 62 | return sendRequest(new RequestRecord(++fCSeq, "SETUP" , responseHandler, NULL, &subsession, booleanFlags)); |
| 63 | } |
| 64 | |
| 65 | unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler, |
| 66 | double start, double end, float scale, |
| 67 | Authenticator* authenticator) { |
| 68 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 69 | sendDummyUDPPackets(session); // hack to improve NAT traversal |
| 70 | return sendRequest(new RequestRecord(++fCSeq, "PLAY" , responseHandler, &session, NULL, 0, start, end, scale)); |
| 71 | } |
| 72 | |
| 73 | unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler, |
| 74 | double start, double end, float scale, |
| 75 | Authenticator* authenticator) { |
| 76 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 77 | sendDummyUDPPackets(subsession); // hack to improve NAT traversal |
| 78 | return sendRequest(new RequestRecord(++fCSeq, "PLAY" , responseHandler, NULL, &subsession, 0, start, end, scale)); |
| 79 | } |
| 80 | |
| 81 | unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler, |
| 82 | char const* absStartTime, char const* absEndTime, float scale, |
| 83 | Authenticator* authenticator) { |
| 84 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 85 | sendDummyUDPPackets(session); // hack to improve NAT traversal |
| 86 | return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, &session, NULL)); |
| 87 | } |
| 88 | |
| 89 | unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler, |
| 90 | char const* absStartTime, char const* absEndTime, float scale, |
| 91 | Authenticator* authenticator) { |
| 92 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 93 | sendDummyUDPPackets(subsession); // hack to improve NAT traversal |
| 94 | return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, NULL, &subsession)); |
| 95 | } |
| 96 | |
| 97 | unsigned RTSPClient::sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) { |
| 98 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 99 | return sendRequest(new RequestRecord(++fCSeq, "PAUSE" , responseHandler, &session)); |
| 100 | } |
| 101 | |
| 102 | unsigned RTSPClient::sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) { |
| 103 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 104 | return sendRequest(new RequestRecord(++fCSeq, "PAUSE" , responseHandler, NULL, &subsession)); |
| 105 | } |
| 106 | |
| 107 | unsigned RTSPClient::sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) { |
| 108 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 109 | return sendRequest(new RequestRecord(++fCSeq, "RECORD" , responseHandler, &session)); |
| 110 | } |
| 111 | |
| 112 | unsigned RTSPClient::sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) { |
| 113 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 114 | return sendRequest(new RequestRecord(++fCSeq, "RECORD" , responseHandler, NULL, &subsession)); |
| 115 | } |
| 116 | |
| 117 | unsigned RTSPClient::sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) { |
| 118 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 119 | return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN" , responseHandler, &session)); |
| 120 | } |
| 121 | |
| 122 | unsigned RTSPClient::sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) { |
| 123 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 124 | return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN" , responseHandler, NULL, &subsession)); |
| 125 | } |
| 126 | |
| 127 | unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler, |
| 128 | char const* parameterName, char const* parameterValue, |
| 129 | Authenticator* authenticator) { |
| 130 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 131 | char* paramString = new char[strlen(parameterName) + strlen(parameterValue) + 10]; |
| 132 | sprintf(paramString, "%s: %s\r\n" , parameterName, parameterValue); |
| 133 | unsigned result = sendRequest(new RequestRecord(++fCSeq, "SET_PARAMETER" , responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString)); |
| 134 | delete[] paramString; |
| 135 | return result; |
| 136 | } |
| 137 | |
| 138 | unsigned RTSPClient::sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName, |
| 139 | Authenticator* authenticator) { |
| 140 | if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator; |
| 141 | |
| 142 | // We assume that: |
| 143 | // parameterName is NULL or "" means: Send no body in the request. |
| 144 | // parameterName is non-empty means: Send "<parameterName>\r\n" as the request body. |
| 145 | unsigned parameterNameLen = parameterName == NULL ? 0 : strlen(parameterName); |
| 146 | char* paramString = new char[parameterNameLen + 3]; // the 3 is for \r\n + the '\0' byte |
| 147 | if (parameterName == NULL || parameterName[0] == '\0') { |
| 148 | paramString[0] = '\0'; |
| 149 | } else { |
| 150 | sprintf(paramString, "%s\r\n" , parameterName); |
| 151 | } |
| 152 | unsigned result = sendRequest(new RequestRecord(++fCSeq, "GET_PARAMETER" , responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString)); |
| 153 | delete[] paramString; |
| 154 | return result; |
| 155 | } |
| 156 | |
| 157 | void RTSPClient::sendDummyUDPPackets(MediaSession& session, unsigned numDummyPackets) { |
| 158 | MediaSubsessionIterator iter(session); |
| 159 | MediaSubsession* subsession; |
| 160 | |
| 161 | while ((subsession = iter.next()) != NULL) { |
| 162 | sendDummyUDPPackets(*subsession, numDummyPackets); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | void RTSPClient::sendDummyUDPPackets(MediaSubsession& subsession, unsigned numDummyPackets) { |
| 167 | // Hack: To increase the likelihood of UDP packets from the server reaching us, |
| 168 | // if we're behind a NAT, send a few 'dummy' UDP packets to the server now. |
| 169 | // (We do this on both our RTP port and our RTCP port.) |
| 170 | Groupsock* gs1 = NULL; Groupsock* gs2 = NULL; |
| 171 | if (subsession.rtpSource() != NULL) gs1 = subsession.rtpSource()->RTPgs(); |
| 172 | if (subsession.rtcpInstance() != NULL) gs2 = subsession.rtcpInstance()->RTCPgs(); |
| 173 | u_int32_t const dummy = 0xFEEDFACE; |
| 174 | for (unsigned i = 0; i < numDummyPackets; ++i) { |
| 175 | if (gs1 != NULL) gs1->output(envir(), (unsigned char*)&dummy, sizeof dummy); |
| 176 | if (gs2 != NULL) gs2->output(envir(), (unsigned char*)&dummy, sizeof dummy); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | void RTSPClient::setSpeed(MediaSession& session, float speed) { |
| 181 | // Optionally set download speed for session to be used later on PLAY command: |
| 182 | // The user should call this function after the MediaSession is instantiated, but before the |
| 183 | // first "sendPlayCommand()" is called. |
| 184 | session.speed() = speed; |
| 185 | MediaSubsessionIterator iter(session); |
| 186 | MediaSubsession* subsession; |
| 187 | |
| 188 | while ((subsession = iter.next()) != NULL) { |
| 189 | subsession->speed() = speed; |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | Boolean RTSPClient::changeResponseHandler(unsigned cseq, responseHandler* newResponseHandler) { |
| 194 | // Look for the matching request record in each of our 'pending requests' queues: |
| 195 | RequestRecord* request; |
| 196 | if ((request = fRequestsAwaitingConnection.findByCSeq(cseq)) != NULL |
| 197 | || (request = fRequestsAwaitingHTTPTunneling.findByCSeq(cseq)) != NULL |
| 198 | || (request = fRequestsAwaitingResponse.findByCSeq(cseq)) != NULL) { |
| 199 | request->handler() = newResponseHandler; |
| 200 | return True; |
| 201 | } |
| 202 | |
| 203 | return False; |
| 204 | } |
| 205 | |
| 206 | Boolean RTSPClient::lookupByName(UsageEnvironment& env, |
| 207 | char const* instanceName, |
| 208 | RTSPClient*& resultClient) { |
| 209 | resultClient = NULL; // unless we succeed |
| 210 | |
| 211 | Medium* medium; |
| 212 | if (!Medium::lookupByName(env, instanceName, medium)) return False; |
| 213 | |
| 214 | if (!medium->isRTSPClient()) { |
| 215 | env.setResultMsg(instanceName, " is not a RTSP client" ); |
| 216 | return False; |
| 217 | } |
| 218 | |
| 219 | resultClient = (RTSPClient*)medium; |
| 220 | return True; |
| 221 | } |
| 222 | |
| 223 | static void copyUsernameOrPasswordStringFromURL(char* dest, char const* src, unsigned len) { |
| 224 | // Normally, we just copy from the source to the destination. However, if the source contains |
| 225 | // %-encoded characters, then we decode them while doing the copy: |
| 226 | while (len > 0) { |
| 227 | int nBefore = 0; |
| 228 | int nAfter = 0; |
| 229 | |
| 230 | if (*src == '%' && len >= 3 && sscanf(src+1, "%n%2hhx%n" , &nBefore, dest, &nAfter) == 1) { |
| 231 | unsigned codeSize = nAfter - nBefore; // should be 1 or 2 |
| 232 | |
| 233 | ++dest; |
| 234 | src += (1 + codeSize); |
| 235 | len -= (1 + codeSize); |
| 236 | } else { |
| 237 | *dest++ = *src++; |
| 238 | --len; |
| 239 | } |
| 240 | } |
| 241 | *dest = '\0'; |
| 242 | } |
| 243 | |
| 244 | Boolean RTSPClient::parseRTSPURL(char const* url, |
| 245 | char*& username, char*& password, |
| 246 | NetAddress& address, |
| 247 | portNumBits& portNum, |
| 248 | char const** urlSuffix) { |
| 249 | do { |
| 250 | // Parse the URL as "rtsp://[<username>[:<password>]@]<server-address-or-name>[:<port>][/<stream-name>]" (or "rtsps://...") |
| 251 | char const* prefix1 = "rtsp://" ; |
| 252 | unsigned const prefix1Length = 7; |
| 253 | char const* prefix2 = "rtsps://" ; |
| 254 | unsigned const prefix2Length = 8; |
| 255 | |
| 256 | portNumBits defaultPortNumber; |
| 257 | char const* from; |
| 258 | if (_strncasecmp(url, prefix1, prefix1Length) == 0) { |
| 259 | defaultPortNumber = 554; |
| 260 | from = &url[prefix1Length]; |
| 261 | } else if (_strncasecmp(url, prefix2, prefix2Length) == 0) { |
| 262 | useTLS(); |
| 263 | defaultPortNumber = 322; |
| 264 | from = &url[prefix2Length]; |
| 265 | } else { |
| 266 | envir().setResultMsg("URL does not begin with \"rtsp://\" or \"rtsps://\"" ); |
| 267 | break; |
| 268 | } |
| 269 | |
| 270 | unsigned const parseBufferSize = 100; |
| 271 | char parseBuffer[parseBufferSize]; |
| 272 | |
| 273 | // Check whether "<username>[:<password>]@" occurs next. |
| 274 | // We do this by checking whether '@' appears before the end of the URL, or before the first '/'. |
| 275 | username = password = NULL; // default return values |
| 276 | char const* colonPasswordStart = NULL; |
| 277 | char const* lastAtPtr = NULL; |
| 278 | for (char const* p = from; *p != '\0' && *p != '/'; ++p) { |
| 279 | if (*p == ':' && colonPasswordStart == NULL) { |
| 280 | colonPasswordStart = p; |
| 281 | } else if (*p == '@') { |
| 282 | lastAtPtr = p; |
| 283 | } |
| 284 | } |
| 285 | if (lastAtPtr != NULL) { |
| 286 | // We found <username> (and perhaps <password>). Copy them into newly-allocated result strings: |
| 287 | if (colonPasswordStart == NULL || colonPasswordStart > lastAtPtr) colonPasswordStart = lastAtPtr; |
| 288 | |
| 289 | char const* usernameStart = from; |
| 290 | unsigned usernameLen = colonPasswordStart - usernameStart; |
| 291 | username = new char[usernameLen + 1] ; // allow for the trailing '\0' |
| 292 | copyUsernameOrPasswordStringFromURL(username, usernameStart, usernameLen); |
| 293 | |
| 294 | char const* passwordStart = colonPasswordStart; |
| 295 | if (passwordStart < lastAtPtr) ++passwordStart; // skip over the ':' |
| 296 | unsigned passwordLen = lastAtPtr - passwordStart; |
| 297 | password = new char[passwordLen + 1]; // allow for the trailing '\0' |
| 298 | copyUsernameOrPasswordStringFromURL(password, passwordStart, passwordLen); |
| 299 | |
| 300 | from = lastAtPtr + 1; // skip over the '@' |
| 301 | } |
| 302 | |
| 303 | // Next, parse <server-address-or-name> |
| 304 | char* to = &parseBuffer[0]; |
| 305 | unsigned i; |
| 306 | for (i = 0; i < parseBufferSize; ++i) { |
| 307 | if (*from == '\0' || *from == ':' || *from == '/') { |
| 308 | // We've completed parsing the address |
| 309 | *to = '\0'; |
| 310 | break; |
| 311 | } |
| 312 | *to++ = *from++; |
| 313 | } |
| 314 | if (i == parseBufferSize) { |
| 315 | envir().setResultMsg("URL is too long" ); |
| 316 | break; |
| 317 | } |
| 318 | |
| 319 | NetAddressList addresses(parseBuffer); |
| 320 | if (addresses.numAddresses() == 0) { |
| 321 | envir().setResultMsg("Failed to find network address for \"" , |
| 322 | parseBuffer, "\"" ); |
| 323 | break; |
| 324 | } |
| 325 | address = *(addresses.firstAddress()); |
| 326 | |
| 327 | portNum = defaultPortNumber; // unless it's specified explicitly in the URL |
| 328 | char nextChar = *from; |
| 329 | if (nextChar == ':') { |
| 330 | int portNumInt; |
| 331 | if (sscanf(++from, "%d" , &portNumInt) != 1) { |
| 332 | envir().setResultMsg("No port number follows ':'" ); |
| 333 | break; |
| 334 | } |
| 335 | if (portNumInt < 1 || portNumInt > 65535) { |
| 336 | envir().setResultMsg("Bad port number" ); |
| 337 | break; |
| 338 | } |
| 339 | portNum = (portNumBits)portNumInt; |
| 340 | while (*from >= '0' && *from <= '9') ++from; // skip over port number |
| 341 | } |
| 342 | |
| 343 | // The remainder of the URL is the suffix: |
| 344 | if (urlSuffix != NULL) *urlSuffix = from; |
| 345 | |
| 346 | return True; |
| 347 | } while (0); |
| 348 | |
| 349 | // An error occurred in the parsing: |
| 350 | return False; |
| 351 | } |
| 352 | |
| 353 | void RTSPClient::setUserAgentString(char const* userAgentName) { |
| 354 | if (userAgentName == NULL) return; |
| 355 | |
| 356 | // Change the existing user agent header string: |
| 357 | char const* const formatStr = "User-Agent: %s\r\n" ; |
| 358 | unsigned const = strlen(formatStr) + strlen(userAgentName); |
| 359 | delete[] fUserAgentHeaderStr; |
| 360 | fUserAgentHeaderStr = new char[headerSize]; |
| 361 | sprintf(fUserAgentHeaderStr, formatStr, userAgentName); |
| 362 | fUserAgentHeaderStrLen = strlen(fUserAgentHeaderStr); |
| 363 | } |
| 364 | |
| 365 | unsigned RTSPClient::responseBufferSize = 20000; // default value; you can reassign this in your application if you need to |
| 366 | |
| 367 | RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL, |
| 368 | int verbosityLevel, char const* applicationName, |
| 369 | portNumBits tunnelOverHTTPPortNum, int socketNumToServer) |
| 370 | : Medium(env), |
| 371 | desiredMaxIncomingPacketSize(0), fVerbosityLevel(verbosityLevel), fCSeq(1), |
| 372 | fAllowBasicAuthentication(True), fServerAddress(0), |
| 373 | fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum), |
| 374 | fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0), |
| 375 | fInputSocketNum(-1), fOutputSocketNum(-1), fBaseURL(NULL), fTCPStreamIdCount(0), |
| 376 | fLastSessionId(NULL), fSessionTimeoutParameter(0), fSessionCookieCounter(0), fHTTPTunnelingConnectionIsPending(False), |
| 377 | fTLS(*this) { |
| 378 | setBaseURL(rtspURL); |
| 379 | |
| 380 | fResponseBuffer = new char[responseBufferSize+1]; |
| 381 | resetResponseBuffer(); |
| 382 | |
| 383 | if (socketNumToServer >= 0) { |
| 384 | // This socket number is (assumed to be) already connected to the server. |
| 385 | // Use it, and arrange to handle responses to requests sent on it: |
| 386 | fInputSocketNum = fOutputSocketNum = socketNumToServer; |
| 387 | env.taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION, |
| 388 | (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this); |
| 389 | } |
| 390 | |
| 391 | // Set the "User-Agent:" header to use in each request: |
| 392 | char const* const libName = "LIVE555 Streaming Media v" ; |
| 393 | char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING; |
| 394 | char const* libPrefix; char const* libSuffix; |
| 395 | if (applicationName == NULL || applicationName[0] == '\0') { |
| 396 | applicationName = libPrefix = libSuffix = "" ; |
| 397 | } else { |
| 398 | libPrefix = " (" ; |
| 399 | libSuffix = ")" ; |
| 400 | } |
| 401 | unsigned userAgentNameSize |
| 402 | = strlen(applicationName) + strlen(libPrefix) + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix) + 1; |
| 403 | char* userAgentName = new char[userAgentNameSize]; |
| 404 | sprintf(userAgentName, "%s%s%s%s%s" , applicationName, libPrefix, libName, libVersionStr, libSuffix); |
| 405 | setUserAgentString(userAgentName); |
| 406 | delete[] userAgentName; |
| 407 | } |
| 408 | |
| 409 | RTSPClient::~RTSPClient() { |
| 410 | reset(); |
| 411 | |
| 412 | delete[] fResponseBuffer; |
| 413 | delete[] fUserAgentHeaderStr; |
| 414 | } |
| 415 | |
| 416 | void RTSPClient::reset() { |
| 417 | resetTCPSockets(); |
| 418 | resetResponseBuffer(); |
| 419 | fRequestsAwaitingConnection.reset(); |
| 420 | fRequestsAwaitingHTTPTunneling.reset(); |
| 421 | fRequestsAwaitingResponse.reset(); |
| 422 | fServerAddress = 0; |
| 423 | |
| 424 | setBaseURL(NULL); |
| 425 | |
| 426 | fCurrentAuthenticator.reset(); |
| 427 | |
| 428 | delete[] fLastSessionId; fLastSessionId = NULL; |
| 429 | } |
| 430 | |
| 431 | void RTSPClient::setBaseURL(char const* url) { |
| 432 | delete[] fBaseURL; fBaseURL = strDup(url); |
| 433 | } |
| 434 | |
| 435 | int RTSPClient::grabSocket() { |
| 436 | int inputSocket = fInputSocketNum; |
| 437 | RTPInterface::clearServerRequestAlternativeByteHandler(envir(), fInputSocketNum); // in case we were receiving RTP-over-TCP |
| 438 | fInputSocketNum = -1; |
| 439 | |
| 440 | return inputSocket; |
| 441 | } |
| 442 | |
| 443 | unsigned RTSPClient::sendRequest(RequestRecord* request) { |
| 444 | char* cmd = NULL; |
| 445 | do { |
| 446 | Boolean connectionIsPending = False; |
| 447 | if (!fRequestsAwaitingConnection.isEmpty()) { |
| 448 | // A connection is currently pending (with at least one enqueued request). Enqueue this request also: |
| 449 | connectionIsPending = True; |
| 450 | } else if (fInputSocketNum < 0) { // we need to open a connection |
| 451 | int connectResult = openConnection(); |
| 452 | if (connectResult < 0) break; // an error occurred |
| 453 | else if (connectResult == 0) { |
| 454 | // A connection is pending |
| 455 | connectionIsPending = True; |
| 456 | } // else the connection succeeded. Continue sending the command. |
| 457 | } |
| 458 | if (connectionIsPending) { |
| 459 | fRequestsAwaitingConnection.enqueue(request); |
| 460 | return request->cseq(); |
| 461 | } |
| 462 | |
| 463 | // If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP: |
| 464 | if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET" ) != 0 && fOutputSocketNum == fInputSocketNum) { |
| 465 | if (!setupHTTPTunneling1()) break; |
| 466 | fRequestsAwaitingHTTPTunneling.enqueue(request); |
| 467 | return request->cseq(); |
| 468 | } |
| 469 | |
| 470 | // Construct and send the command: |
| 471 | |
| 472 | // First, construct command-specific headers that we need: |
| 473 | |
| 474 | char* cmdURL = fBaseURL; // by default |
| 475 | Boolean cmdURLWasAllocated = False; |
| 476 | |
| 477 | char const* protocolStr = "RTSP/1.0" ; // by default |
| 478 | |
| 479 | char* = (char*)"" ; // by default |
| 480 | Boolean = False; |
| 481 | |
| 482 | char* = (char*)"" ; // by default |
| 483 | Boolean = False; |
| 484 | |
| 485 | if (!setRequestFields(request, |
| 486 | cmdURL, cmdURLWasAllocated, |
| 487 | protocolStr, |
| 488 | extraHeaders, extraHeadersWereAllocated)) { |
| 489 | break; |
| 490 | } |
| 491 | |
| 492 | char const* contentStr = request->contentStr(); // by default |
| 493 | if (contentStr == NULL) contentStr = "" ; |
| 494 | unsigned contentStrLen = strlen(contentStr); |
| 495 | if (contentStrLen > 0) { |
| 496 | char const* = |
| 497 | "Content-Length: %d\r\n" ; |
| 498 | unsigned = strlen(contentLengthHeaderFmt) |
| 499 | + 20 /* max int len */; |
| 500 | contentLengthHeader = new char[contentLengthHeaderSize]; |
| 501 | sprintf(contentLengthHeader, contentLengthHeaderFmt, contentStrLen); |
| 502 | contentLengthHeaderWasAllocated = True; |
| 503 | } |
| 504 | |
| 505 | char* authenticatorStr = createAuthenticatorString(request->commandName(), fBaseURL); |
| 506 | |
| 507 | char const* const cmdFmt = |
| 508 | "%s %s %s\r\n" |
| 509 | "CSeq: %d\r\n" |
| 510 | "%s" |
| 511 | "%s" |
| 512 | "%s" |
| 513 | "%s" |
| 514 | "\r\n" |
| 515 | "%s" ; |
| 516 | unsigned cmdSize = strlen(cmdFmt) |
| 517 | + strlen(request->commandName()) + strlen(cmdURL) + strlen(protocolStr) |
| 518 | + 20 /* max int len */ |
| 519 | + strlen(authenticatorStr) |
| 520 | + fUserAgentHeaderStrLen |
| 521 | + strlen(extraHeaders) |
| 522 | + strlen(contentLengthHeader) |
| 523 | + contentStrLen; |
| 524 | cmd = new char[cmdSize]; |
| 525 | sprintf(cmd, cmdFmt, |
| 526 | request->commandName(), cmdURL, protocolStr, |
| 527 | request->cseq(), |
| 528 | authenticatorStr, |
| 529 | fUserAgentHeaderStr, |
| 530 | extraHeaders, |
| 531 | contentLengthHeader, |
| 532 | contentStr); |
| 533 | delete[] authenticatorStr; |
| 534 | if (cmdURLWasAllocated) delete[] cmdURL; |
| 535 | if (extraHeadersWereAllocated) delete[] extraHeaders; |
| 536 | if (contentLengthHeaderWasAllocated) delete[] contentLengthHeader; |
| 537 | |
| 538 | if (fVerbosityLevel >= 1) envir() << "Sending request: " << cmd << "\n" ; |
| 539 | |
| 540 | if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET" ) != 0 && strcmp(request->commandName(), "POST" ) != 0) { |
| 541 | // When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it. |
| 542 | // (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.) |
| 543 | char* origCmd = cmd; |
| 544 | cmd = base64Encode(origCmd, strlen(cmd)); |
| 545 | if (fVerbosityLevel >= 1) envir() << "\tThe request was base-64 encoded to: " << cmd << "\n\n" ; |
| 546 | delete[] origCmd; |
| 547 | } |
| 548 | |
| 549 | if (write(cmd, strlen(cmd)) < 0) { |
| 550 | char const* errFmt = "%s write() failed: " ; |
| 551 | unsigned const errLength = strlen(errFmt) + strlen(request->commandName()); |
| 552 | char* err = new char[errLength]; |
| 553 | sprintf(err, errFmt, request->commandName()); |
| 554 | envir().setResultErrMsg(err); |
| 555 | delete[] err; |
| 556 | break; |
| 557 | } |
| 558 | |
| 559 | // The command send succeeded, so enqueue the request record, so that its response (when it comes) can be handled. |
| 560 | // However, note that we do not expect a response to a POST command with RTSP-over-HTTP, so don't enqueue that. |
| 561 | int cseq = request->cseq(); |
| 562 | |
| 563 | if (fTunnelOverHTTPPortNum == 0 || strcmp(request->commandName(), "POST" ) != 0) { |
| 564 | fRequestsAwaitingResponse.enqueue(request); |
| 565 | } else { |
| 566 | delete request; |
| 567 | } |
| 568 | |
| 569 | delete[] cmd; |
| 570 | return cseq; |
| 571 | } while (0); |
| 572 | |
| 573 | // An error occurred, so call the response handler immediately (indicating the error): |
| 574 | delete[] cmd; |
| 575 | handleRequestError(request); |
| 576 | delete request; |
| 577 | return 0; |
| 578 | } |
| 579 | |
| 580 | static char* createSessionString(char const* sessionId) { |
| 581 | char* sessionStr; |
| 582 | if (sessionId != NULL) { |
| 583 | sessionStr = new char[20+strlen(sessionId)]; |
| 584 | sprintf(sessionStr, "Session: %s\r\n" , sessionId); |
| 585 | } else { |
| 586 | sessionStr = strDup("" ); |
| 587 | } |
| 588 | return sessionStr; |
| 589 | } |
| 590 | |
| 591 | // Add support for faster download thru "speed:" option on PLAY |
| 592 | static char* createSpeedString(float speed) { |
| 593 | char buf[100]; |
| 594 | if (speed == 1.0f ) { |
| 595 | // This is the default value; we don't need a "Speed:" header: |
| 596 | buf[0] = '\0'; |
| 597 | } else { |
| 598 | sprintf(buf, "Speed: %.3f\r\n" ,speed); |
| 599 | } |
| 600 | |
| 601 | return strDup(buf); |
| 602 | } |
| 603 | |
| 604 | static char* createScaleString(float scale, float currentScale) { |
| 605 | char buf[100]; |
| 606 | if (scale == 1.0f && currentScale == 1.0f) { |
| 607 | // This is the default value; we don't need a "Scale:" header: |
| 608 | buf[0] = '\0'; |
| 609 | } else { |
| 610 | Locale l("C" , Numeric); |
| 611 | sprintf(buf, "Scale: %f\r\n" , scale); |
| 612 | } |
| 613 | |
| 614 | return strDup(buf); |
| 615 | } |
| 616 | |
| 617 | static char* createRangeString(double start, double end, char const* absStartTime, char const* absEndTime) { |
| 618 | char buf[100]; |
| 619 | |
| 620 | if (absStartTime != NULL) { |
| 621 | // Create a "Range:" header that specifies 'absolute' time values: |
| 622 | |
| 623 | if (absEndTime == NULL) { |
| 624 | // There's no end time: |
| 625 | snprintf(buf, sizeof buf, "Range: clock=%s-\r\n" , absStartTime); |
| 626 | } else { |
| 627 | // There's both a start and an end time; include them both in the "Range:" hdr |
| 628 | snprintf(buf, sizeof buf, "Range: clock=%s-%s\r\n" , absStartTime, absEndTime); |
| 629 | } |
| 630 | } else { |
| 631 | // Create a "Range:" header that specifies relative (i.e., NPT) time values: |
| 632 | |
| 633 | if (start < 0) { |
| 634 | // We're resuming from a PAUSE; there's no "Range:" header at all |
| 635 | buf[0] = '\0'; |
| 636 | } else if (end < 0) { |
| 637 | // There's no end time: |
| 638 | Locale l("C" , Numeric); |
| 639 | sprintf(buf, "Range: npt=%.3f-\r\n" , start); |
| 640 | } else { |
| 641 | // There's both a start and an end time; include them both in the "Range:" hdr |
| 642 | Locale l("C" , Numeric); |
| 643 | sprintf(buf, "Range: npt=%.3f-%.3f\r\n" , start, end); |
| 644 | } |
| 645 | } |
| 646 | |
| 647 | return strDup(buf); |
| 648 | } |
| 649 | |
| 650 | Boolean RTSPClient::setRequestFields(RequestRecord* request, |
| 651 | char*& cmdURL, Boolean& cmdURLWasAllocated, |
| 652 | char const*& protocolStr, |
| 653 | char*& , Boolean& |
| 654 | ) { |
| 655 | // Set various fields that will appear in our outgoing request, depending upon the particular command that we are sending. |
| 656 | |
| 657 | if (strcmp(request->commandName(), "DESCRIBE" ) == 0) { |
| 658 | extraHeaders = (char*)"Accept: application/sdp\r\n" ; |
| 659 | } else if (strcmp(request->commandName(), "OPTIONS" ) == 0) { |
| 660 | // If we're currently part of a session, create a "Session:" header (in case the server wants this to indicate |
| 661 | // client 'liveness); this makes up our 'extra headers': |
| 662 | extraHeaders = createSessionString(fLastSessionId); |
| 663 | extraHeadersWereAllocated = True; |
| 664 | } else if (strcmp(request->commandName(), "ANNOUNCE" ) == 0) { |
| 665 | extraHeaders = (char*)"Content-Type: application/sdp\r\n" ; |
| 666 | } else if (strcmp(request->commandName(), "SETUP" ) == 0) { |
| 667 | MediaSubsession& subsession = *request->subsession(); |
| 668 | Boolean streamUsingTCP = (request->booleanFlags()&0x1) != 0; |
| 669 | Boolean streamOutgoing = (request->booleanFlags()&0x2) != 0; |
| 670 | Boolean forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0; |
| 671 | |
| 672 | char const *prefix, *separator, *suffix; |
| 673 | constructSubsessionURL(subsession, prefix, separator, suffix); |
| 674 | |
| 675 | char const* transportFmt; |
| 676 | if (strcmp(subsession.protocolName(), "UDP" ) == 0) { |
| 677 | suffix = "" ; |
| 678 | transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n" ; |
| 679 | } else { |
| 680 | transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n" ; |
| 681 | } |
| 682 | |
| 683 | cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1]; |
| 684 | cmdURLWasAllocated = True; |
| 685 | sprintf(cmdURL, "%s%s%s" , prefix, separator, suffix); |
| 686 | |
| 687 | // Construct a "Transport:" header. |
| 688 | char const* transportTypeStr; |
| 689 | char const* modeStr = streamOutgoing ? ";mode=receive" : "" ; |
| 690 | // Note: I think the above is nonstandard, but DSS wants it this way |
| 691 | char const* portTypeStr; |
| 692 | portNumBits rtpNumber, rtcpNumber; |
| 693 | if (streamUsingTCP) { // streaming over the RTSP connection |
| 694 | transportTypeStr = "/TCP;unicast" ; |
| 695 | portTypeStr = ";interleaved" ; |
| 696 | rtpNumber = fTCPStreamIdCount++; |
| 697 | rtcpNumber = fTCPStreamIdCount++; |
| 698 | } else { // normal RTP streaming |
| 699 | unsigned connectionAddress = subsession.connectionEndpointAddress(); |
| 700 | Boolean requestMulticastStreaming |
| 701 | = IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified); |
| 702 | transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast" ; |
| 703 | portTypeStr = requestMulticastStreaming ? ";port" : ";client_port" ; |
| 704 | rtpNumber = subsession.clientPortNum(); |
| 705 | if (rtpNumber == 0) { |
| 706 | envir().setResultMsg("Client port number unknown\n" ); |
| 707 | delete[] cmdURL; |
| 708 | return False; |
| 709 | } |
| 710 | rtcpNumber = subsession.rtcpIsMuxed() ? rtpNumber : rtpNumber + 1; |
| 711 | } |
| 712 | unsigned transportSize = strlen(transportFmt) |
| 713 | + strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */; |
| 714 | char* transportStr = new char[transportSize]; |
| 715 | sprintf(transportStr, transportFmt, |
| 716 | transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber); |
| 717 | |
| 718 | // When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands: |
| 719 | char* sessionStr = createSessionString(fLastSessionId); |
| 720 | |
| 721 | // Optionally include a "Blocksize:" string: |
| 722 | char* blocksizeStr = createBlocksizeString(streamUsingTCP); |
| 723 | |
| 724 | // The "Transport:" and "Session:" (if present) and "Blocksize:" (if present) headers |
| 725 | // make up the 'extra headers': |
| 726 | extraHeaders = new char[transportSize + strlen(sessionStr) + strlen(blocksizeStr)]; |
| 727 | extraHeadersWereAllocated = True; |
| 728 | sprintf(extraHeaders, "%s%s%s" , transportStr, sessionStr, blocksizeStr); |
| 729 | delete[] transportStr; delete[] sessionStr; delete[] blocksizeStr; |
| 730 | } else if (strcmp(request->commandName(), "GET" ) == 0 || strcmp(request->commandName(), "POST" ) == 0) { |
| 731 | // We will be sending a HTTP (not a RTSP) request. |
| 732 | // Begin by re-parsing our RTSP URL, to get the stream name (which we'll use as our 'cmdURL' |
| 733 | // in the subsequent request), and the server address (which we'll use in a "Host:" header): |
| 734 | char* username; |
| 735 | char* password; |
| 736 | NetAddress destAddress; |
| 737 | portNumBits urlPortNum; |
| 738 | if (!parseRTSPURL(fBaseURL, username, password, destAddress, urlPortNum, (char const**)&cmdURL)) return False; |
| 739 | if (cmdURL[0] == '\0') cmdURL = (char*)"/" ; |
| 740 | delete[] username; |
| 741 | delete[] password; |
| 742 | netAddressBits serverAddress = *(netAddressBits*)(destAddress.data()); |
| 743 | AddressString serverAddressString(serverAddress); |
| 744 | |
| 745 | protocolStr = "HTTP/1.1" ; |
| 746 | |
| 747 | if (strcmp(request->commandName(), "GET" ) == 0) { |
| 748 | // Create a 'session cookie' string, using MD5: |
| 749 | struct { |
| 750 | struct timeval timestamp; |
| 751 | unsigned counter; |
| 752 | } seedData; |
| 753 | gettimeofday(&seedData.timestamp, NULL); |
| 754 | seedData.counter = ++fSessionCookieCounter; |
| 755 | our_MD5Data((unsigned char*)(&seedData), sizeof seedData, fSessionCookie); |
| 756 | // DSS seems to require that the 'session cookie' string be 22 bytes long: |
| 757 | fSessionCookie[23] = '\0'; |
| 758 | |
| 759 | char const* const = |
| 760 | "Host: %s\r\n" |
| 761 | "x-sessioncookie: %s\r\n" |
| 762 | "Accept: application/x-rtsp-tunnelled\r\n" |
| 763 | "Pragma: no-cache\r\n" |
| 764 | "Cache-Control: no-cache\r\n" ; |
| 765 | unsigned = strlen(extraHeadersFmt) |
| 766 | + strlen(serverAddressString.val()) |
| 767 | + strlen(fSessionCookie); |
| 768 | extraHeaders = new char[extraHeadersSize]; |
| 769 | extraHeadersWereAllocated = True; |
| 770 | sprintf(extraHeaders, extraHeadersFmt, |
| 771 | serverAddressString.val(), |
| 772 | fSessionCookie); |
| 773 | } else { // "POST" |
| 774 | char const* const = |
| 775 | "Host: %s\r\n" |
| 776 | "x-sessioncookie: %s\r\n" |
| 777 | "Content-Type: application/x-rtsp-tunnelled\r\n" |
| 778 | "Pragma: no-cache\r\n" |
| 779 | "Cache-Control: no-cache\r\n" |
| 780 | "Content-Length: 32767\r\n" |
| 781 | "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n" ; |
| 782 | unsigned = strlen(extraHeadersFmt) |
| 783 | + strlen(serverAddressString.val()) |
| 784 | + strlen(fSessionCookie); |
| 785 | extraHeaders = new char[extraHeadersSize]; |
| 786 | extraHeadersWereAllocated = True; |
| 787 | sprintf(extraHeaders, extraHeadersFmt, |
| 788 | serverAddressString.val(), |
| 789 | fSessionCookie); |
| 790 | } |
| 791 | } else { // "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER" |
| 792 | // First, make sure that we have a RTSP session in progress |
| 793 | if (fLastSessionId == NULL) { |
| 794 | envir().setResultMsg("No RTSP session is currently in progress\n" ); |
| 795 | return False; |
| 796 | } |
| 797 | |
| 798 | char const* sessionId; |
| 799 | float originalScale; |
| 800 | if (request->session() != NULL) { |
| 801 | // Session-level operation |
| 802 | cmdURL = (char*)sessionURL(*request->session()); |
| 803 | |
| 804 | sessionId = fLastSessionId; |
| 805 | originalScale = request->session()->scale(); |
| 806 | } else { |
| 807 | // Media-level operation |
| 808 | char const *prefix, *separator, *suffix; |
| 809 | constructSubsessionURL(*request->subsession(), prefix, separator, suffix); |
| 810 | cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1]; |
| 811 | cmdURLWasAllocated = True; |
| 812 | sprintf(cmdURL, "%s%s%s" , prefix, separator, suffix); |
| 813 | |
| 814 | sessionId = request->subsession()->sessionId(); |
| 815 | originalScale = request->subsession()->scale(); |
| 816 | } |
| 817 | |
| 818 | if (strcmp(request->commandName(), "PLAY" ) == 0) { |
| 819 | // Create possible "Session:", "Scale:", "Speed:", and "Range:" headers; |
| 820 | // these make up the 'extra headers': |
| 821 | char* sessionStr = createSessionString(sessionId); |
| 822 | char* scaleStr = createScaleString(request->scale(), originalScale); |
| 823 | float speed = request->session() != NULL ? request->session()->speed() : request->subsession()->speed(); |
| 824 | char* speedStr = createSpeedString(speed); |
| 825 | char* rangeStr = createRangeString(request->start(), request->end(), request->absStartTime(), request->absEndTime()); |
| 826 | extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(speedStr) + strlen(rangeStr) + 1]; |
| 827 | extraHeadersWereAllocated = True; |
| 828 | sprintf(extraHeaders, "%s%s%s%s" , sessionStr, scaleStr, speedStr, rangeStr); |
| 829 | delete[] sessionStr; delete[] scaleStr; delete[] speedStr; delete[] rangeStr; |
| 830 | } else { |
| 831 | // Create a "Session:" header; this makes up our 'extra headers': |
| 832 | extraHeaders = createSessionString(sessionId); |
| 833 | extraHeadersWereAllocated = True; |
| 834 | } |
| 835 | } |
| 836 | |
| 837 | return True; |
| 838 | } |
| 839 | |
| 840 | Boolean RTSPClient::isRTSPClient() const { |
| 841 | return True; |
| 842 | } |
| 843 | |
| 844 | void RTSPClient::resetTCPSockets() { |
| 845 | if (fInputSocketNum >= 0) { |
| 846 | RTPInterface::clearServerRequestAlternativeByteHandler(envir(), fInputSocketNum); // in case we were receiving RTP-over-TCP |
| 847 | envir().taskScheduler().disableBackgroundHandling(fInputSocketNum); |
| 848 | ::closeSocket(fInputSocketNum); |
| 849 | if (fOutputSocketNum != fInputSocketNum) { |
| 850 | envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum); |
| 851 | ::closeSocket(fOutputSocketNum); |
| 852 | } |
| 853 | } |
| 854 | fInputSocketNum = fOutputSocketNum = -1; |
| 855 | } |
| 856 | |
| 857 | void RTSPClient::resetResponseBuffer() { |
| 858 | fResponseBytesAlreadySeen = 0; |
| 859 | fResponseBufferBytesLeft = responseBufferSize; |
| 860 | } |
| 861 | |
| 862 | int RTSPClient::openConnection() { |
| 863 | do { |
| 864 | // Set up a connection to the server. Begin by parsing the URL: |
| 865 | |
| 866 | char* username; |
| 867 | char* password; |
| 868 | NetAddress destAddress; |
| 869 | portNumBits urlPortNum; |
| 870 | char const* urlSuffix; |
| 871 | if (!parseRTSPURL(fBaseURL, username, password, destAddress, urlPortNum, &urlSuffix)) break; |
| 872 | portNumBits destPortNum = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum; |
| 873 | if (destPortNum == 322) useTLS(); // port 322 is a special case: "rtsps" |
| 874 | |
| 875 | if (username != NULL || password != NULL) { |
| 876 | fCurrentAuthenticator.setUsernameAndPassword(username, password); |
| 877 | delete[] username; |
| 878 | delete[] password; |
| 879 | } |
| 880 | |
| 881 | // We don't yet have a TCP socket (or we used to have one, but it got closed). Set it up now. |
| 882 | fInputSocketNum = setupStreamSocket(envir(), 0); |
| 883 | if (fInputSocketNum < 0) break; |
| 884 | ignoreSigPipeOnSocket(fInputSocketNum); // so that servers on the same host that get killed don't also kill us |
| 885 | if (fOutputSocketNum < 0) fOutputSocketNum = fInputSocketNum; |
| 886 | envir() << "Created new TCP socket " << fInputSocketNum << " for connection\n" ; |
| 887 | |
| 888 | // Connect to the remote endpoint: |
| 889 | fServerAddress = *(netAddressBits*)(destAddress.data()); |
| 890 | int connectResult = connectToServer(fInputSocketNum, destPortNum); |
| 891 | if (connectResult < 0) break; |
| 892 | else if (connectResult > 0) { |
| 893 | if (fTLS.isNeeded) { |
| 894 | // We need to complete an additional TLS connection: |
| 895 | connectResult = fTLS.connect(fInputSocketNum); |
| 896 | if (connectResult < 0) break; |
| 897 | if (connectResult > 0 && fVerbosityLevel >= 1) envir() << "...TLS connection completed\n" ; |
| 898 | } |
| 899 | |
| 900 | if (connectResult > 0 && fVerbosityLevel >= 1) envir() << "...local connection opened\n" ; |
| 901 | } |
| 902 | |
| 903 | return connectResult; |
| 904 | } while (0); |
| 905 | |
| 906 | resetTCPSockets(); |
| 907 | return -1; |
| 908 | } |
| 909 | |
| 910 | int RTSPClient::connectToServer(int socketNum, portNumBits remotePortNum) { |
| 911 | MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(remotePortNum)); |
| 912 | if (fVerbosityLevel >= 1) { |
| 913 | envir() << "Connecting to " << AddressString(remoteName).val() << ", port " << remotePortNum << " on socket " << socketNum << "...\n" ; |
| 914 | } |
| 915 | if (connect(socketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) { |
| 916 | int const err = envir().getErrno(); |
| 917 | if (err == EINPROGRESS || err == EWOULDBLOCK) { |
| 918 | // The connection is pending; we'll need to handle it later. Wait for our socket to be 'writable', or have an exception. |
| 919 | envir().taskScheduler().setBackgroundHandling(socketNum, SOCKET_WRITABLE|SOCKET_EXCEPTION, |
| 920 | (TaskScheduler::BackgroundHandlerProc*)&connectionHandler, this); |
| 921 | return 0; |
| 922 | } |
| 923 | envir().setResultErrMsg("connect() failed: " ); |
| 924 | if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n" ; |
| 925 | return -1; |
| 926 | } |
| 927 | |
| 928 | // The connection succeeded. Arrange to handle responses to requests sent on it: |
| 929 | envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION, |
| 930 | (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this); |
| 931 | |
| 932 | return 1; |
| 933 | } |
| 934 | |
| 935 | char* RTSPClient::createAuthenticatorString(char const* cmd, char const* url) { |
| 936 | Authenticator& auth = fCurrentAuthenticator; // alias, for brevity |
| 937 | if (auth.realm() != NULL && auth.username() != NULL && auth.password() != NULL) { |
| 938 | // We have a filled-in authenticator, so use it: |
| 939 | char* authenticatorStr; |
| 940 | if (auth.nonce() != NULL) { // Digest authentication |
| 941 | char const* const authFmt = |
| 942 | "Authorization: Digest username=\"%s\", realm=\"%s\", " |
| 943 | "nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n" ; |
| 944 | char const* response = auth.computeDigestResponse(cmd, url); |
| 945 | unsigned authBufSize = strlen(authFmt) |
| 946 | + strlen(auth.username()) + strlen(auth.realm()) |
| 947 | + strlen(auth.nonce()) + strlen(url) + strlen(response); |
| 948 | authenticatorStr = new char[authBufSize]; |
| 949 | sprintf(authenticatorStr, authFmt, |
| 950 | auth.username(), auth.realm(), |
| 951 | auth.nonce(), url, response); |
| 952 | auth.reclaimDigestResponse(response); |
| 953 | } else { // Basic authentication |
| 954 | char const* const authFmt = "Authorization: Basic %s\r\n" ; |
| 955 | |
| 956 | unsigned usernamePasswordLength = strlen(auth.username()) + 1 + strlen(auth.password()); |
| 957 | char* usernamePassword = new char[usernamePasswordLength+1]; |
| 958 | sprintf(usernamePassword, "%s:%s" , auth.username(), auth.password()); |
| 959 | |
| 960 | char* response = base64Encode(usernamePassword, usernamePasswordLength); |
| 961 | unsigned const authBufSize = strlen(authFmt) + strlen(response) + 1; |
| 962 | authenticatorStr = new char[authBufSize]; |
| 963 | sprintf(authenticatorStr, authFmt, response); |
| 964 | delete[] response; delete[] usernamePassword; |
| 965 | } |
| 966 | |
| 967 | return authenticatorStr; |
| 968 | } |
| 969 | |
| 970 | // We don't have a (filled-in) authenticator. |
| 971 | return strDup("" ); |
| 972 | } |
| 973 | |
| 974 | char* RTSPClient::createBlocksizeString(Boolean streamUsingTCP) { |
| 975 | char* blocksizeStr; |
| 976 | u_int16_t maxPacketSize = desiredMaxIncomingPacketSize; |
| 977 | |
| 978 | // Allow for the RTP header (if streaming over TCP) |
| 979 | // or the IP/UDP/RTP headers (if streaming over UDP): |
| 980 | u_int16_t const = streamUsingTCP ? 12 : 50/*conservative*/; |
| 981 | if (maxPacketSize < headerAllowance) { |
| 982 | maxPacketSize = 0; |
| 983 | } else { |
| 984 | maxPacketSize -= headerAllowance; |
| 985 | } |
| 986 | |
| 987 | if (maxPacketSize > 0) { |
| 988 | blocksizeStr = new char[25]; // more than enough space |
| 989 | sprintf(blocksizeStr, "Blocksize: %u\r\n" , maxPacketSize); |
| 990 | } else { |
| 991 | blocksizeStr = strDup("" ); |
| 992 | } |
| 993 | return blocksizeStr; |
| 994 | } |
| 995 | |
| 996 | void RTSPClient::handleRequestError(RequestRecord* request) { |
| 997 | int resultCode = -envir().getErrno(); |
| 998 | if (resultCode == 0) { |
| 999 | // Choose some generic error code instead: |
| 1000 | #if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4) |
| 1001 | resultCode = -WSAENOTCONN; |
| 1002 | #else |
| 1003 | resultCode = -ENOTCONN; |
| 1004 | #endif |
| 1005 | } |
| 1006 | if (request->handler() != NULL) (*request->handler())(this, resultCode, strDup(envir().getResultMsg())); |
| 1007 | } |
| 1008 | |
| 1009 | Boolean RTSPClient |
| 1010 | ::parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString) { |
| 1011 | if (sscanf(line, "RTSP/%*s%u" , &responseCode) != 1 && |
| 1012 | sscanf(line, "HTTP/%*s%u" , &responseCode) != 1) return False; |
| 1013 | // Note: We check for HTTP responses as well as RTSP responses, both in order to setup RTSP-over-HTTP tunneling, |
| 1014 | // and so that we get back a meaningful error if the client tried to mistakenly send a RTSP command to a HTTP-only server. |
| 1015 | |
| 1016 | // Use everything after the RTSP/* (or HTTP/*) as the response string: |
| 1017 | responseString = line; |
| 1018 | while (responseString[0] != '\0' && responseString[0] != ' ' && responseString[0] != '\t') ++responseString; |
| 1019 | while (responseString[0] != '\0' && (responseString[0] == ' ' || responseString[0] == '\t')) ++responseString; // skip whitespace |
| 1020 | |
| 1021 | return True; |
| 1022 | } |
| 1023 | |
| 1024 | void RTSPClient::handleIncomingRequest() { |
| 1025 | // Parse the request string into command name and 'CSeq', then 'handle' the command (by responding that we don't support it): |
| 1026 | char cmdName[RTSP_PARAM_STRING_MAX]; |
| 1027 | char urlPreSuffix[RTSP_PARAM_STRING_MAX]; |
| 1028 | char urlSuffix[RTSP_PARAM_STRING_MAX]; |
| 1029 | char cseq[RTSP_PARAM_STRING_MAX]; |
| 1030 | char sessionId[RTSP_PARAM_STRING_MAX]; |
| 1031 | unsigned contentLength; |
| 1032 | if (!parseRTSPRequestString(fResponseBuffer, fResponseBytesAlreadySeen, |
| 1033 | cmdName, sizeof cmdName, |
| 1034 | urlPreSuffix, sizeof urlPreSuffix, |
| 1035 | urlSuffix, sizeof urlSuffix, |
| 1036 | cseq, sizeof cseq, |
| 1037 | sessionId, sizeof sessionId, |
| 1038 | contentLength)) { |
| 1039 | return; |
| 1040 | } else { |
| 1041 | if (fVerbosityLevel >= 1) { |
| 1042 | envir() << "Received incoming RTSP request: " << fResponseBuffer << "\n" ; |
| 1043 | } |
| 1044 | char tmpBuf[2*RTSP_PARAM_STRING_MAX]; |
| 1045 | snprintf(tmpBuf, sizeof tmpBuf, |
| 1046 | "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n\r\n" , cseq); |
| 1047 | write(tmpBuf, strlen(tmpBuf)); |
| 1048 | } |
| 1049 | } |
| 1050 | |
| 1051 | Boolean RTSPClient::(char const* line, char const* , unsigned , char const*& ) { |
| 1052 | if (_strncasecmp(line, headerName, headerNameLength) != 0) return False; |
| 1053 | |
| 1054 | // The line begins with the desired header name. Trim off any whitespace, and return the header parameters: |
| 1055 | unsigned paramIndex = headerNameLength; |
| 1056 | while (line[paramIndex] != '\0' && (line[paramIndex] == ' ' || line[paramIndex] == '\t')) ++paramIndex; |
| 1057 | if (line[paramIndex] == '\0') return False; // the header is assumed to be bad if it has no parameters |
| 1058 | |
| 1059 | headerParams = &line[paramIndex]; |
| 1060 | return True; |
| 1061 | } |
| 1062 | |
| 1063 | Boolean RTSPClient::parseTransportParams(char const* paramsStr, |
| 1064 | char*& serverAddressStr, portNumBits& serverPortNum, |
| 1065 | unsigned char& rtpChannelId, unsigned char& rtcpChannelId) { |
| 1066 | // Initialize the return parameters to 'not found' values: |
| 1067 | serverAddressStr = NULL; |
| 1068 | serverPortNum = 0; |
| 1069 | rtpChannelId = rtcpChannelId = 0xFF; |
| 1070 | if (paramsStr == NULL) return False; |
| 1071 | |
| 1072 | char* foundServerAddressStr = NULL; |
| 1073 | Boolean foundServerPortNum = False; |
| 1074 | portNumBits clientPortNum = 0; |
| 1075 | Boolean foundClientPortNum = False; |
| 1076 | Boolean foundChannelIds = False; |
| 1077 | unsigned rtpCid, rtcpCid; |
| 1078 | Boolean isMulticast = True; // by default |
| 1079 | char* foundDestinationStr = NULL; |
| 1080 | portNumBits multicastPortNumRTP, multicastPortNumRTCP; |
| 1081 | Boolean foundMulticastPortNum = False; |
| 1082 | |
| 1083 | // Run through each of the parameters, looking for ones that we handle: |
| 1084 | char const* fields = paramsStr; |
| 1085 | char* field = strDupSize(fields); |
| 1086 | while (sscanf(fields, "%[^;]" , field) == 1) { |
| 1087 | if (sscanf(field, "server_port=%hu" , &serverPortNum) == 1) { |
| 1088 | foundServerPortNum = True; |
| 1089 | } else if (sscanf(field, "client_port=%hu" , &clientPortNum) == 1) { |
| 1090 | foundClientPortNum = True; |
| 1091 | } else if (_strncasecmp(field, "source=" , 7) == 0) { |
| 1092 | delete[] foundServerAddressStr; |
| 1093 | foundServerAddressStr = strDup(field+7); |
| 1094 | } else if (sscanf(field, "interleaved=%u-%u" , &rtpCid, &rtcpCid) == 2) { |
| 1095 | rtpChannelId = (unsigned char)rtpCid; |
| 1096 | rtcpChannelId = (unsigned char)rtcpCid; |
| 1097 | foundChannelIds = True; |
| 1098 | } else if (strcmp(field, "unicast" ) == 0) { |
| 1099 | isMulticast = False; |
| 1100 | } else if (_strncasecmp(field, "destination=" , 12) == 0) { |
| 1101 | delete[] foundDestinationStr; |
| 1102 | foundDestinationStr = strDup(field+12); |
| 1103 | } else if (sscanf(field, "port=%hu-%hu" , &multicastPortNumRTP, &multicastPortNumRTCP) == 2 || |
| 1104 | sscanf(field, "port=%hu" , &multicastPortNumRTP) == 1) { |
| 1105 | foundMulticastPortNum = True; |
| 1106 | } |
| 1107 | |
| 1108 | fields += strlen(field); |
| 1109 | while (fields[0] == ';') ++fields; // skip over all leading ';' chars |
| 1110 | if (fields[0] == '\0') break; |
| 1111 | } |
| 1112 | delete[] field; |
| 1113 | |
| 1114 | // If we're multicast, and have a "destination=" (multicast) address, then use this |
| 1115 | // as the 'server' address (because some weird servers don't specify the multicast |
| 1116 | // address earlier, in the "DESCRIBE" response's SDP: |
| 1117 | if (isMulticast && foundDestinationStr != NULL && foundMulticastPortNum) { |
| 1118 | delete[] foundServerAddressStr; |
| 1119 | serverAddressStr = foundDestinationStr; |
| 1120 | serverPortNum = multicastPortNumRTP; |
| 1121 | return True; |
| 1122 | } |
| 1123 | delete[] foundDestinationStr; |
| 1124 | |
| 1125 | // We have a valid "Transport:" header if any of the following are true: |
| 1126 | // - We saw a "interleaved=" field, indicating RTP/RTCP-over-TCP streaming, or |
| 1127 | // - We saw a "server_port=" field, or |
| 1128 | // - We saw a "client_port=" field. |
| 1129 | // If we didn't also see a "server_port=" field, then the server port is assumed to be the same as the client port. |
| 1130 | if (foundChannelIds || foundServerPortNum || foundClientPortNum) { |
| 1131 | if (foundClientPortNum && !foundServerPortNum) { |
| 1132 | serverPortNum = clientPortNum; |
| 1133 | } |
| 1134 | serverAddressStr = foundServerAddressStr; |
| 1135 | return True; |
| 1136 | } |
| 1137 | |
| 1138 | delete[] foundServerAddressStr; |
| 1139 | return False; |
| 1140 | } |
| 1141 | |
| 1142 | Boolean RTSPClient::parseScaleParam(char const* paramStr, float& scale) { |
| 1143 | Locale l("C" , Numeric); |
| 1144 | return sscanf(paramStr, "%f" , &scale) == 1; |
| 1145 | } |
| 1146 | |
| 1147 | Boolean RTSPClient::parseSpeedParam(char const* paramStr, float& speed) { |
| 1148 | Locale l("C" , Numeric); |
| 1149 | return sscanf(paramStr, "%f" , &speed) >= 1; |
| 1150 | } |
| 1151 | |
| 1152 | Boolean RTSPClient::parseRTPInfoParams(char const*& paramsStr, u_int16_t& seqNum, u_int32_t& timestamp) { |
| 1153 | if (paramsStr == NULL || paramsStr[0] == '\0') return False; |
| 1154 | while (paramsStr[0] == ',') ++paramsStr; |
| 1155 | |
| 1156 | // "paramsStr" now consists of a ';'-separated list of parameters, ending with ',' or '\0'. |
| 1157 | char* field = strDupSize(paramsStr); |
| 1158 | |
| 1159 | Boolean sawSeq = False, sawRtptime = False; |
| 1160 | while (sscanf(paramsStr, "%[^;,]" , field) == 1) { |
| 1161 | if (sscanf(field, "seq=%hu" , &seqNum) == 1) { |
| 1162 | sawSeq = True; |
| 1163 | } else if (sscanf(field, "rtptime=%u" , ×tamp) == 1) { |
| 1164 | sawRtptime = True; |
| 1165 | } |
| 1166 | |
| 1167 | paramsStr += strlen(field); |
| 1168 | if (paramsStr[0] == '\0' || paramsStr[0] == ',') break; |
| 1169 | // ASSERT: paramsStr[0] == ';' |
| 1170 | ++paramsStr; // skip over the ';' |
| 1171 | } |
| 1172 | |
| 1173 | delete[] field; |
| 1174 | // For the "RTP-Info:" parameters to be useful to us, we need to have seen both the "seq=" and "rtptime=" parameters: |
| 1175 | return sawSeq && sawRtptime; |
| 1176 | } |
| 1177 | |
| 1178 | Boolean RTSPClient::handleSETUPResponse(MediaSubsession& subsession, char const* sessionParamsStr, char const* transportParamsStr, |
| 1179 | Boolean streamUsingTCP) { |
| 1180 | char* sessionId = new char[responseBufferSize]; // ensures we have enough space |
| 1181 | Boolean success = False; |
| 1182 | do { |
| 1183 | // Check for a session id: |
| 1184 | if (sessionParamsStr == NULL || sscanf(sessionParamsStr, "%[^;]" , sessionId) != 1) { |
| 1185 | envir().setResultMsg("Missing or bad \"Session:\" header" ); |
| 1186 | break; |
| 1187 | } |
| 1188 | subsession.setSessionId(sessionId); |
| 1189 | delete[] fLastSessionId; fLastSessionId = strDup(sessionId); |
| 1190 | |
| 1191 | // Also look for an optional "; timeout = " parameter following this: |
| 1192 | char const* afterSessionId = sessionParamsStr + strlen(sessionId); |
| 1193 | int timeoutVal; |
| 1194 | if (sscanf(afterSessionId, "; timeout = %d" , &timeoutVal) == 1) { |
| 1195 | fSessionTimeoutParameter = timeoutVal; |
| 1196 | } |
| 1197 | |
| 1198 | // Parse the "Transport:" header parameters: |
| 1199 | char* serverAddressStr; |
| 1200 | portNumBits serverPortNum; |
| 1201 | unsigned char rtpChannelId, rtcpChannelId; |
| 1202 | if (!parseTransportParams(transportParamsStr, serverAddressStr, serverPortNum, rtpChannelId, rtcpChannelId)) { |
| 1203 | envir().setResultMsg("Missing or bad \"Transport:\" header" ); |
| 1204 | break; |
| 1205 | } |
| 1206 | delete[] subsession.connectionEndpointName(); |
| 1207 | subsession.connectionEndpointName() = serverAddressStr; |
| 1208 | subsession.serverPortNum = serverPortNum; |
| 1209 | subsession.rtpChannelId = rtpChannelId; |
| 1210 | subsession.rtcpChannelId = rtcpChannelId; |
| 1211 | |
| 1212 | if (streamUsingTCP) { |
| 1213 | // Tell the subsession to receive RTP (and send/receive RTCP) over the RTSP stream: |
| 1214 | if (subsession.rtpSource() != NULL) { |
| 1215 | subsession.rtpSource()->setStreamSocket(fInputSocketNum, subsession.rtpChannelId); |
| 1216 | // So that we continue to receive & handle RTSP commands and responses from the server |
| 1217 | subsession.rtpSource()->enableRTCPReports() = False; |
| 1218 | // To avoid confusing the server (which won't start handling RTP/RTCP-over-TCP until "PLAY"), don't send RTCP "RR"s yet |
| 1219 | increaseReceiveBufferTo(envir(), fInputSocketNum, 50*1024); |
| 1220 | } |
| 1221 | if (subsession.rtcpInstance() != NULL) subsession.rtcpInstance()->setStreamSocket(fInputSocketNum, subsession.rtcpChannelId); |
| 1222 | RTPInterface::setServerRequestAlternativeByteHandler(envir(), fInputSocketNum, handleAlternativeRequestByte, this); |
| 1223 | } else { |
| 1224 | // Normal case. |
| 1225 | // Set the RTP and RTCP sockets' destination address and port from the information in the SETUP response (if present): |
| 1226 | netAddressBits destAddress = subsession.connectionEndpointAddress(); |
| 1227 | if (destAddress == 0) destAddress = fServerAddress; |
| 1228 | subsession.setDestinations(destAddress); |
| 1229 | } |
| 1230 | |
| 1231 | success = True; |
| 1232 | } while (0); |
| 1233 | |
| 1234 | delete[] sessionId; |
| 1235 | return success; |
| 1236 | } |
| 1237 | |
| 1238 | Boolean RTSPClient::handlePLAYResponse(MediaSession* session, MediaSubsession* subsession, |
| 1239 | char const* scaleParamsStr, char const* speedParamsStr, |
| 1240 | char const* rangeParamsStr, char const* rtpInfoParamsStr) { |
| 1241 | Boolean scaleOK = False, rangeOK = False, speedOK = False; |
| 1242 | do { |
| 1243 | if (session != NULL) { |
| 1244 | // The command was on the whole session |
| 1245 | if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, session->scale())) break; |
| 1246 | scaleOK = True; |
| 1247 | if (speedParamsStr != NULL && !parseSpeedParam(speedParamsStr, session->speed())) break; |
| 1248 | speedOK = True; |
| 1249 | Boolean startTimeIsNow; |
| 1250 | if (rangeParamsStr != NULL && |
| 1251 | !parseRangeParam(rangeParamsStr, |
| 1252 | session->playStartTime(), session->playEndTime(), |
| 1253 | session->_absStartTime(), session->_absEndTime(), |
| 1254 | startTimeIsNow)) break; |
| 1255 | rangeOK = True; |
| 1256 | |
| 1257 | MediaSubsessionIterator iter(*session); |
| 1258 | MediaSubsession* subsession; |
| 1259 | while ((subsession = iter.next()) != NULL) { |
| 1260 | u_int16_t seqNum; u_int32_t timestamp; |
| 1261 | subsession->rtpInfo.infoIsNew = False; |
| 1262 | if (parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) { |
| 1263 | subsession->rtpInfo.seqNum = seqNum; |
| 1264 | subsession->rtpInfo.timestamp = timestamp; |
| 1265 | subsession->rtpInfo.infoIsNew = True; |
| 1266 | } |
| 1267 | |
| 1268 | if (subsession->rtpSource() != NULL) subsession->rtpSource()->enableRTCPReports() = True; // start sending RTCP "RR"s now |
| 1269 | } |
| 1270 | } else { |
| 1271 | // The command was on a subsession |
| 1272 | if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, subsession->scale())) break; |
| 1273 | scaleOK = True; |
| 1274 | if (speedParamsStr != NULL && !parseSpeedParam(speedParamsStr, subsession->speed())) break; |
| 1275 | speedOK = True; |
| 1276 | Boolean startTimeIsNow; |
| 1277 | if (rangeParamsStr != NULL && |
| 1278 | !parseRangeParam(rangeParamsStr, |
| 1279 | subsession->_playStartTime(), subsession->_playEndTime(), |
| 1280 | subsession->_absStartTime(), subsession->_absEndTime(), |
| 1281 | startTimeIsNow)) break; |
| 1282 | rangeOK = True; |
| 1283 | |
| 1284 | u_int16_t seqNum; u_int32_t timestamp; |
| 1285 | subsession->rtpInfo.infoIsNew = False; |
| 1286 | if (parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) { |
| 1287 | subsession->rtpInfo.seqNum = seqNum; |
| 1288 | subsession->rtpInfo.timestamp = timestamp; |
| 1289 | subsession->rtpInfo.infoIsNew = True; |
| 1290 | } |
| 1291 | |
| 1292 | if (subsession->rtpSource() != NULL) subsession->rtpSource()->enableRTCPReports() = True; // start sending RTCP "RR"s now |
| 1293 | } |
| 1294 | |
| 1295 | return True; |
| 1296 | } while (0); |
| 1297 | |
| 1298 | // An error occurred: |
| 1299 | if (!scaleOK) { |
| 1300 | envir().setResultMsg("Bad \"Scale:\" header" ); |
| 1301 | } else if (!speedOK) { |
| 1302 | envir().setResultMsg("Bad \"Speed:\" header" ); |
| 1303 | } else if (!rangeOK) { |
| 1304 | envir().setResultMsg("Bad \"Range:\" header" ); |
| 1305 | } else { |
| 1306 | envir().setResultMsg("Bad \"RTP-Info:\" header" ); |
| 1307 | } |
| 1308 | return False; |
| 1309 | } |
| 1310 | |
| 1311 | Boolean RTSPClient::handleTEARDOWNResponse(MediaSession& /*session*/, MediaSubsession& /*subsession*/) { |
| 1312 | // Because we don't expect to always get a response to "TEARDOWN", we don't need to do anything if we do get one: |
| 1313 | return True; |
| 1314 | } |
| 1315 | |
| 1316 | Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString, char* resultValueStringEnd) { |
| 1317 | do { |
| 1318 | // If "parameterName" is non-empty, it may be (possibly followed by ':' and whitespace) at the start of the result string: |
| 1319 | if (parameterName != NULL && parameterName[0] != '\0') { |
| 1320 | if (parameterName[1] == '\0') break; // sanity check; there should have been \r\n at the end of "parameterName" |
| 1321 | |
| 1322 | unsigned parameterNameLen = strlen(parameterName); |
| 1323 | // ASSERT: parameterNameLen >= 2; |
| 1324 | parameterNameLen -= 2; // because of the trailing \r\n |
| 1325 | if (resultValueString + parameterNameLen > resultValueStringEnd) break; // not enough space |
| 1326 | if (parameterNameLen > 0 && _strncasecmp(resultValueString, parameterName, parameterNameLen) == 0) { |
| 1327 | resultValueString += parameterNameLen; |
| 1328 | // ASSERT: resultValueString <= resultValueStringEnd |
| 1329 | if (resultValueString == resultValueStringEnd) break; |
| 1330 | |
| 1331 | if (resultValueString[0] == ':') ++resultValueString; |
| 1332 | while (resultValueString < resultValueStringEnd |
| 1333 | && (resultValueString[0] == ' ' || resultValueString[0] == '\t')) { |
| 1334 | ++resultValueString; |
| 1335 | } |
| 1336 | } |
| 1337 | } |
| 1338 | |
| 1339 | // The rest of "resultValueStr" should be our desired result, but first trim off any \r and/or \n characters at the end: |
| 1340 | char saved = *resultValueStringEnd; |
| 1341 | *resultValueStringEnd = '\0'; |
| 1342 | unsigned resultLen = strlen(resultValueString); |
| 1343 | *resultValueStringEnd = saved; |
| 1344 | |
| 1345 | while (resultLen > 0 && (resultValueString[resultLen-1] == '\r' || resultValueString[resultLen-1] == '\n')) --resultLen; |
| 1346 | resultValueString[resultLen] = '\0'; |
| 1347 | |
| 1348 | return True; |
| 1349 | } while (0); |
| 1350 | |
| 1351 | // An error occurred: |
| 1352 | envir().setResultMsg("Bad \"GET_PARAMETER\" response" ); |
| 1353 | return False; |
| 1354 | } |
| 1355 | |
| 1356 | Boolean RTSPClient::handleAuthenticationFailure(char const* paramsStr) { |
| 1357 | if (paramsStr == NULL) return False; // There was no "WWW-Authenticate:" header; we can't proceed. |
| 1358 | |
| 1359 | // Fill in "fCurrentAuthenticator" with the information from the "WWW-Authenticate:" header: |
| 1360 | Boolean realmHasChanged = False; // by default |
| 1361 | Boolean isStale = False; // by default |
| 1362 | char* realm = strDupSize(paramsStr); |
| 1363 | char* nonce = strDupSize(paramsStr); |
| 1364 | char* stale = strDupSize(paramsStr); |
| 1365 | Boolean success = True; |
| 1366 | if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\", stale=%[a-zA-Z]" , realm, nonce, stale) == 3) { |
| 1367 | realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0; |
| 1368 | isStale = _strncasecmp(stale, "true" , 4) == 0; |
| 1369 | fCurrentAuthenticator.setRealmAndNonce(realm, nonce); |
| 1370 | } else if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"" , realm, nonce) == 2) { |
| 1371 | realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0; |
| 1372 | fCurrentAuthenticator.setRealmAndNonce(realm, nonce); |
| 1373 | } else if (sscanf(paramsStr, "Basic realm=\"%[^\"]\"" , realm) == 1 && fAllowBasicAuthentication) { |
| 1374 | realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0; |
| 1375 | fCurrentAuthenticator.setRealmAndNonce(realm, NULL); // Basic authentication |
| 1376 | } else { |
| 1377 | success = False; // bad "WWW-Authenticate:" header |
| 1378 | } |
| 1379 | delete[] realm; delete[] nonce; delete[] stale; |
| 1380 | |
| 1381 | if (success) { |
| 1382 | if ((!realmHasChanged && !isStale) || fCurrentAuthenticator.username() == NULL || fCurrentAuthenticator.password() == NULL) { |
| 1383 | // We already tried with the same realm (and a non-stale nonce), |
| 1384 | // or don't have a username and/or password, so the new "WWW-Authenticate:" header |
| 1385 | // information won't help us. We remain unauthenticated. |
| 1386 | success = False; |
| 1387 | } |
| 1388 | } |
| 1389 | |
| 1390 | return success; |
| 1391 | } |
| 1392 | |
| 1393 | Boolean RTSPClient::resendCommand(RequestRecord* request) { |
| 1394 | if (fVerbosityLevel >= 1) envir() << "Resending...\n" ; |
| 1395 | if (request != NULL && strcmp(request->commandName(), "GET" ) != 0) request->cseq() = ++fCSeq; |
| 1396 | return sendRequest(request) != 0; |
| 1397 | } |
| 1398 | |
| 1399 | char const* RTSPClient::sessionURL(MediaSession const& session) const { |
| 1400 | char const* url = session.controlPath(); |
| 1401 | if (url == NULL || strcmp(url, "*" ) == 0) url = fBaseURL; |
| 1402 | |
| 1403 | return url; |
| 1404 | } |
| 1405 | |
| 1406 | void RTSPClient::handleAlternativeRequestByte(void* rtspClient, u_int8_t requestByte) { |
| 1407 | ((RTSPClient*)rtspClient)->handleAlternativeRequestByte1(requestByte); |
| 1408 | } |
| 1409 | |
| 1410 | void RTSPClient::handleAlternativeRequestByte1(u_int8_t requestByte) { |
| 1411 | if (requestByte == 0xFF) { |
| 1412 | // Hack: The new handler of the input TCP socket encountered an error reading it. Indicate this: |
| 1413 | handleResponseBytes(-1); |
| 1414 | } else if (requestByte == 0xFE) { |
| 1415 | // Another hack: The new handler of the input TCP socket no longer needs it, so take back control: |
| 1416 | envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION, |
| 1417 | (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this); |
| 1418 | } else { |
| 1419 | // Normal case: |
| 1420 | fResponseBuffer[fResponseBytesAlreadySeen] = requestByte; |
| 1421 | handleResponseBytes(1); |
| 1422 | } |
| 1423 | } |
| 1424 | |
| 1425 | static Boolean isAbsoluteURL(char const* url) { |
| 1426 | // Assumption: "url" is absolute if it contains a ':', before any |
| 1427 | // occurrence of '/' |
| 1428 | while (*url != '\0' && *url != '/') { |
| 1429 | if (*url == ':') return True; |
| 1430 | ++url; |
| 1431 | } |
| 1432 | |
| 1433 | return False; |
| 1434 | } |
| 1435 | |
| 1436 | void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession, |
| 1437 | char const*& prefix, |
| 1438 | char const*& separator, |
| 1439 | char const*& suffix) { |
| 1440 | // Figure out what the URL describing "subsession" will look like. |
| 1441 | // The URL is returned in three parts: prefix; separator; suffix |
| 1442 | //##### NOTE: This code doesn't really do the right thing if "sessionURL()" |
| 1443 | // doesn't end with a "/", and "subsession.controlPath()" is relative. |
| 1444 | // The right thing would have been to truncate "sessionURL()" back to the |
| 1445 | // rightmost "/", and then add "subsession.controlPath()". |
| 1446 | // In practice, though, each "DESCRIBE" response typically contains |
| 1447 | // a "Content-Base:" header that consists of "sessionURL()" followed by |
| 1448 | // a "/", in which case this code ends up giving the correct result. |
| 1449 | // However, we should really fix this code to do the right thing, and |
| 1450 | // also check for and use the "Content-Base:" header appropriately. ##### |
| 1451 | prefix = sessionURL(subsession.parentSession()); |
| 1452 | if (prefix == NULL) prefix = "" ; |
| 1453 | |
| 1454 | suffix = subsession.controlPath(); |
| 1455 | if (suffix == NULL) suffix = "" ; |
| 1456 | |
| 1457 | if (isAbsoluteURL(suffix)) { |
| 1458 | prefix = separator = "" ; |
| 1459 | } else { |
| 1460 | unsigned prefixLen = strlen(prefix); |
| 1461 | separator = (prefixLen == 0 || prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/" ; |
| 1462 | } |
| 1463 | } |
| 1464 | |
| 1465 | Boolean RTSPClient::setupHTTPTunneling1() { |
| 1466 | // Set up RTSP-over-HTTP tunneling, as described in |
| 1467 | // http://mirror.informatimago.com/next/developer.apple.com/quicktime/icefloe/dispatch028.html |
| 1468 | // and http://images.apple.com/br/quicktime/pdf/QTSS_Modules.pdf |
| 1469 | if (fVerbosityLevel >= 1) { |
| 1470 | envir() << "Requesting RTSP-over-HTTP tunneling (on port " << fTunnelOverHTTPPortNum << ")\n\n" ; |
| 1471 | } |
| 1472 | |
| 1473 | // Begin by sending a HTTP "GET", to set up the server->client link. Continue when we handle the response: |
| 1474 | return sendRequest(new RequestRecord(1, "GET" , responseHandlerForHTTP_GET)) != 0; |
| 1475 | } |
| 1476 | |
| 1477 | void RTSPClient::responseHandlerForHTTP_GET(RTSPClient* rtspClient, int responseCode, char* responseString) { |
| 1478 | if (rtspClient != NULL) rtspClient->responseHandlerForHTTP_GET1(responseCode, responseString); |
| 1479 | } |
| 1480 | |
| 1481 | void RTSPClient::responseHandlerForHTTP_GET1(int responseCode, char* responseString) { |
| 1482 | RequestRecord* request; |
| 1483 | do { |
| 1484 | delete[] responseString; // we don't need it (but are responsible for deleting it) |
| 1485 | if (responseCode != 0) break; // The HTTP "GET" failed. |
| 1486 | |
| 1487 | // Having successfully set up (using the HTTP "GET" command) the server->client link, set up a second TCP connection |
| 1488 | // (to the same server & port as before) for the client->server link. All future output will be to this new socket. |
| 1489 | fOutputSocketNum = setupStreamSocket(envir(), 0); |
| 1490 | if (fOutputSocketNum < 0) break; |
| 1491 | ignoreSigPipeOnSocket(fOutputSocketNum); // so that servers on the same host that killed don't also kill us |
| 1492 | |
| 1493 | fHTTPTunnelingConnectionIsPending = True; |
| 1494 | int connectResult = connectToServer(fOutputSocketNum, fTunnelOverHTTPPortNum); |
| 1495 | if (connectResult < 0) break; // an error occurred |
| 1496 | else if (connectResult == 0) { |
| 1497 | // A connection is pending. Continue setting up RTSP-over-HTTP when the connection completes. |
| 1498 | // First, move the pending requests to the 'awaiting connection' queue: |
| 1499 | while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) { |
| 1500 | fRequestsAwaitingConnection.enqueue(request); |
| 1501 | } |
| 1502 | return; |
| 1503 | } |
| 1504 | |
| 1505 | // The connection succeeded. Continue setting up RTSP-over-HTTP: |
| 1506 | if (!setupHTTPTunneling2()) break; |
| 1507 | |
| 1508 | // RTSP-over-HTTP tunneling succeeded. Resume the pending request(s): |
| 1509 | while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) { |
| 1510 | sendRequest(request); |
| 1511 | } |
| 1512 | return; |
| 1513 | } while (0); |
| 1514 | |
| 1515 | // An error occurred. Dequeue the pending request(s), and tell them about the error: |
| 1516 | fHTTPTunnelingConnectionIsPending = False; |
| 1517 | resetTCPSockets(); // do this now, in case an error handler deletes "this" |
| 1518 | RequestQueue requestQueue(fRequestsAwaitingHTTPTunneling); |
| 1519 | while ((request = requestQueue.dequeue()) != NULL) { |
| 1520 | handleRequestError(request); |
| 1521 | delete request; |
| 1522 | } |
| 1523 | } |
| 1524 | |
| 1525 | Boolean RTSPClient::setupHTTPTunneling2() { |
| 1526 | fHTTPTunnelingConnectionIsPending = False; |
| 1527 | |
| 1528 | // Send a HTTP "POST", to set up the client->server link. (Note that we won't see a reply to the "POST".) |
| 1529 | return sendRequest(new RequestRecord(1, "POST" , NULL)) != 0; |
| 1530 | } |
| 1531 | |
| 1532 | void RTSPClient::connectionHandler(void* instance, int /*mask*/) { |
| 1533 | RTSPClient* client = (RTSPClient*)instance; |
| 1534 | client->connectionHandler1(); |
| 1535 | } |
| 1536 | |
| 1537 | void RTSPClient::connectionHandler1() { |
| 1538 | // Restore normal handling on our sockets: |
| 1539 | envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum); |
| 1540 | envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION, |
| 1541 | (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this); |
| 1542 | |
| 1543 | // Move all requests awaiting connection into a new, temporary queue, to clear "fRequestsAwaitingConnection" |
| 1544 | // (so that "sendRequest()" doesn't get confused by "fRequestsAwaitingConnection" being nonempty, and enqueue them all over again). |
| 1545 | RequestQueue tmpRequestQueue(fRequestsAwaitingConnection); |
| 1546 | RequestRecord* request; |
| 1547 | |
| 1548 | // Find out whether the connection succeeded or failed: |
| 1549 | do { |
| 1550 | int err = 0; |
| 1551 | SOCKLEN_T len = sizeof err; |
| 1552 | if (getsockopt(fInputSocketNum, SOL_SOCKET, SO_ERROR, (char*)&err, &len) < 0 || err != 0) { |
| 1553 | envir().setResultErrMsg("Connection to server failed: " , err); |
| 1554 | if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n" ; |
| 1555 | break; |
| 1556 | } |
| 1557 | |
| 1558 | // The connection succeeded. If the connection came about from an attempt to set up RTSP-over-HTTP, finish this now: |
| 1559 | if (fHTTPTunnelingConnectionIsPending && !setupHTTPTunneling2()) break; |
| 1560 | |
| 1561 | if (fTLS.isNeeded) { |
| 1562 | // We need to complete an additional TLS connection: |
| 1563 | int tlsConnectResult = fTLS.connect(fInputSocketNum); |
| 1564 | if (tlsConnectResult < 0) break; // error in TLS connection |
| 1565 | if (tlsConnectResult > 0 && fVerbosityLevel >= 1) envir() << "...TLS connection completed\n" ; |
| 1566 | if (tlsConnectResult == 0) { |
| 1567 | // The connection is still pending. Continue deferring... |
| 1568 | while ((request = tmpRequestQueue.dequeue()) != NULL) { |
| 1569 | fRequestsAwaitingConnection.enqueue(request); |
| 1570 | } |
| 1571 | return; |
| 1572 | } |
| 1573 | } |
| 1574 | |
| 1575 | // The connection is complete. Resume sending all pending requests: |
| 1576 | if (fVerbosityLevel >= 1) envir() << "...remote connection opened\n" ; |
| 1577 | while ((request = tmpRequestQueue.dequeue()) != NULL) { |
| 1578 | sendRequest(request); |
| 1579 | } |
| 1580 | return; |
| 1581 | } while (0); |
| 1582 | |
| 1583 | // An error occurred. Tell all pending requests about the error: |
| 1584 | resetTCPSockets(); // do this now, in case an error handler deletes "this" |
| 1585 | while ((request = tmpRequestQueue.dequeue()) != NULL) { |
| 1586 | handleRequestError(request); |
| 1587 | delete request; |
| 1588 | } |
| 1589 | } |
| 1590 | |
| 1591 | void RTSPClient::incomingDataHandler(void* instance, int /*mask*/) { |
| 1592 | RTSPClient* client = (RTSPClient*)instance; |
| 1593 | client->incomingDataHandler1(); |
| 1594 | } |
| 1595 | |
| 1596 | void RTSPClient::incomingDataHandler1() { |
| 1597 | int bytesRead = read((u_int8_t*)&fResponseBuffer[fResponseBytesAlreadySeen], fResponseBufferBytesLeft); |
| 1598 | handleResponseBytes(bytesRead); |
| 1599 | } |
| 1600 | |
| 1601 | static char* getLine(char* startOfLine) { |
| 1602 | // returns the start of the next line, or NULL if none. Note that this modifies the input string to add '\0' characters. |
| 1603 | for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) { |
| 1604 | // Check for the end of line: \r\n (but also accept \r or \n by itself): |
| 1605 | if (*ptr == '\r' || *ptr == '\n') { |
| 1606 | // We found the end of the line |
| 1607 | if (*ptr == '\r') { |
| 1608 | *ptr++ = '\0'; |
| 1609 | if (*ptr == '\n') ++ptr; |
| 1610 | } else { |
| 1611 | *ptr++ = '\0'; |
| 1612 | } |
| 1613 | return ptr; |
| 1614 | } |
| 1615 | } |
| 1616 | |
| 1617 | return NULL; |
| 1618 | } |
| 1619 | |
| 1620 | void RTSPClient::handleResponseBytes(int newBytesRead) { |
| 1621 | do { |
| 1622 | if (newBytesRead >= 0 && (unsigned)newBytesRead < fResponseBufferBytesLeft) break; // data was read OK; process it below |
| 1623 | |
| 1624 | if (newBytesRead >= (int)fResponseBufferBytesLeft) { |
| 1625 | // We filled up our response buffer. Treat this as an error (for the first response handler): |
| 1626 | envir().setResultMsg("RTSP response was truncated. Increase \"RTSPClient::responseBufferSize\"" ); |
| 1627 | } |
| 1628 | |
| 1629 | // An error occurred while reading our TCP socket. Call all pending response handlers, indicating this error. |
| 1630 | // (However, the "RTSP response was truncated" error is applied to the first response handler only.) |
| 1631 | resetResponseBuffer(); |
| 1632 | RequestRecord* request; |
| 1633 | if (newBytesRead > 0) { // The "RTSP response was truncated" error |
| 1634 | if ((request = fRequestsAwaitingResponse.dequeue()) != NULL) { |
| 1635 | handleRequestError(request); |
| 1636 | delete request; |
| 1637 | } |
| 1638 | } else { |
| 1639 | RequestQueue requestQueue(fRequestsAwaitingResponse); |
| 1640 | resetTCPSockets(); // do this now, in case an error handler deletes "this" |
| 1641 | |
| 1642 | while ((request = requestQueue.dequeue()) != NULL) { |
| 1643 | handleRequestError(request); |
| 1644 | delete request; |
| 1645 | } |
| 1646 | } |
| 1647 | return; |
| 1648 | } while (0); |
| 1649 | |
| 1650 | fResponseBufferBytesLeft -= newBytesRead; |
| 1651 | fResponseBytesAlreadySeen += newBytesRead; |
| 1652 | fResponseBuffer[fResponseBytesAlreadySeen] = '\0'; |
| 1653 | if (fVerbosityLevel >= 1 && newBytesRead > 1) envir() << "Received " << newBytesRead << " new bytes of response data.\n" ; |
| 1654 | |
| 1655 | unsigned = 0; |
| 1656 | Boolean responseSuccess = False; // by default |
| 1657 | do { |
| 1658 | // Data was read OK. Look through the data that we've read so far, to see if it contains <CR><LF><CR><LF>. |
| 1659 | // (If not, wait for more data to arrive.) |
| 1660 | Boolean = False; |
| 1661 | char const* ptr = fResponseBuffer; |
| 1662 | if (fResponseBytesAlreadySeen > 3) { |
| 1663 | char const* const ptrEnd = &fResponseBuffer[fResponseBytesAlreadySeen-3]; |
| 1664 | while (ptr < ptrEnd) { |
| 1665 | if (*ptr++ == '\r' && *ptr++ == '\n' && *ptr++ == '\r' && *ptr++ == '\n') { |
| 1666 | // This is it |
| 1667 | endOfHeaders = True; |
| 1668 | break; |
| 1669 | } |
| 1670 | } |
| 1671 | } |
| 1672 | |
| 1673 | if (!endOfHeaders) return; // subsequent reads will be needed to get the complete response |
| 1674 | |
| 1675 | // Now that we have the complete response headers (ending with <CR><LF><CR><LF>), parse them to get the response code, CSeq, |
| 1676 | // and various other header parameters. To do this, we first make a copy of the received header data, because we'll be |
| 1677 | // modifying it by adding '\0' bytes. |
| 1678 | char* ; |
| 1679 | unsigned responseCode = 200; |
| 1680 | char const* responseStr = NULL; |
| 1681 | RequestRecord* foundRequest = NULL; |
| 1682 | char const* sessionParamsStr = NULL; |
| 1683 | char const* transportParamsStr = NULL; |
| 1684 | char const* scaleParamsStr = NULL; |
| 1685 | char const* speedParamsStr = NULL; |
| 1686 | char const* rangeParamsStr = NULL; |
| 1687 | char const* rtpInfoParamsStr = NULL; |
| 1688 | char const* wwwAuthenticateParamsStr = NULL; |
| 1689 | char const* publicParamsStr = NULL; |
| 1690 | char* bodyStart = NULL; |
| 1691 | unsigned numBodyBytes = 0; |
| 1692 | responseSuccess = False; |
| 1693 | do { |
| 1694 | headerDataCopy = new char[responseBufferSize]; |
| 1695 | strncpy(headerDataCopy, fResponseBuffer, fResponseBytesAlreadySeen); |
| 1696 | headerDataCopy[fResponseBytesAlreadySeen] = '\0'; |
| 1697 | |
| 1698 | char* lineStart; |
| 1699 | char* nextLineStart = headerDataCopy; |
| 1700 | do { |
| 1701 | lineStart = nextLineStart; |
| 1702 | nextLineStart = getLine(lineStart); |
| 1703 | } while (lineStart[0] == '\0' && nextLineStart != NULL); // skip over any blank lines at the start |
| 1704 | if (!parseResponseCode(lineStart, responseCode, responseStr)) { |
| 1705 | // This does not appear to be a RTSP response; perhaps it's a RTSP request instead? |
| 1706 | handleIncomingRequest(); |
| 1707 | break; // we're done with this data |
| 1708 | } |
| 1709 | |
| 1710 | // Scan through the headers, handling the ones that we're interested in: |
| 1711 | Boolean ; |
| 1712 | unsigned cseq = 0; |
| 1713 | unsigned contentLength = 0; |
| 1714 | |
| 1715 | while (1) { |
| 1716 | reachedEndOfHeaders = True; // by default; may get changed below |
| 1717 | lineStart = nextLineStart; |
| 1718 | if (lineStart == NULL) break; |
| 1719 | |
| 1720 | nextLineStart = getLine(lineStart); |
| 1721 | if (lineStart[0] == '\0') break; // this is a blank line |
| 1722 | reachedEndOfHeaders = False; |
| 1723 | |
| 1724 | char const* ; |
| 1725 | if (checkForHeader(lineStart, "CSeq:" , 5, headerParamsStr)) { |
| 1726 | if (sscanf(headerParamsStr, "%u" , &cseq) != 1 || cseq <= 0) { |
| 1727 | envir().setResultMsg("Bad \"CSeq:\" header: \"" , lineStart, "\"" ); |
| 1728 | break; |
| 1729 | } |
| 1730 | // Find the handler function for "cseq": |
| 1731 | RequestRecord* request; |
| 1732 | while ((request = fRequestsAwaitingResponse.dequeue()) != NULL) { |
| 1733 | if (request->cseq() < cseq) { // assumes that the CSeq counter will never wrap around |
| 1734 | // We never received (and will never receive) a response for this handler, so delete it: |
| 1735 | if (fVerbosityLevel >= 1 && strcmp(request->commandName(), "POST" ) != 0) { |
| 1736 | envir() << "WARNING: The server did not respond to our \"" << request->commandName() << "\" request (CSeq: " |
| 1737 | << request->cseq() << "). The server appears to be buggy (perhaps not handling pipelined requests properly).\n" ; |
| 1738 | } |
| 1739 | delete request; |
| 1740 | } else if (request->cseq() == cseq) { |
| 1741 | // This is the handler that we want. Remove its record, but remember it, so that we can later call its handler: |
| 1742 | foundRequest = request; |
| 1743 | break; |
| 1744 | } else { // request->cseq() > cseq |
| 1745 | // No handler was registered for this response, so ignore it. |
| 1746 | break; |
| 1747 | } |
| 1748 | } |
| 1749 | } else if (checkForHeader(lineStart, "Content-Length:" , 15, headerParamsStr)) { |
| 1750 | if (sscanf(headerParamsStr, "%u" , &contentLength) != 1) { |
| 1751 | envir().setResultMsg("Bad \"Content-Length:\" header: \"" , lineStart, "\"" ); |
| 1752 | break; |
| 1753 | } |
| 1754 | } else if (checkForHeader(lineStart, "Content-Base:" , 13, headerParamsStr)) { |
| 1755 | setBaseURL(headerParamsStr); |
| 1756 | } else if (checkForHeader(lineStart, "Session:" , 8, sessionParamsStr)) { |
| 1757 | } else if (checkForHeader(lineStart, "Transport:" , 10, transportParamsStr)) { |
| 1758 | } else if (checkForHeader(lineStart, "Scale:" , 6, scaleParamsStr)) { |
| 1759 | } else if (checkForHeader(lineStart, "Speed:" , |
| 1760 | // NOTE: Should you feel the need to modify this code, |
| 1761 | 6, |
| 1762 | // please first email the "live-devel" mailing list |
| 1763 | speedParamsStr |
| 1764 | // (see http://live555.com/liveMedia/faq.html#mailing-list-address for details), |
| 1765 | )) { |
| 1766 | // to check whether your proposed modification is appropriate/correct, |
| 1767 | } else if (checkForHeader(lineStart, "Range:" , |
| 1768 | // and, if so, whether instead it could be included in |
| 1769 | 6, |
| 1770 | // a future release of the "LIVE555 Streaming Media" software, |
| 1771 | rangeParamsStr |
| 1772 | // so that other projects that use the code could benefit (not just your own project). |
| 1773 | )) { |
| 1774 | } else if (checkForHeader(lineStart, "RTP-Info:" , 9, rtpInfoParamsStr)) { |
| 1775 | } else if (checkForHeader(lineStart, "WWW-Authenticate:" , 17, headerParamsStr)) { |
| 1776 | // If we've already seen a "WWW-Authenticate:" header, then we replace it with this new one only if |
| 1777 | // the new one specifies "Digest" authentication: |
| 1778 | if (wwwAuthenticateParamsStr == NULL || _strncasecmp(headerParamsStr, "Digest" , 6) == 0) { |
| 1779 | wwwAuthenticateParamsStr = headerParamsStr; |
| 1780 | } |
| 1781 | } else if (checkForHeader(lineStart, "Public:" , 7, publicParamsStr)) { |
| 1782 | } else if (checkForHeader(lineStart, "Allow:" , 6, publicParamsStr)) { |
| 1783 | // Note: we accept "Allow:" instead of "Public:", so that "OPTIONS" requests made to HTTP servers will work. |
| 1784 | } else if (checkForHeader(lineStart, "Location:" , 9, headerParamsStr)) { |
| 1785 | setBaseURL(headerParamsStr); |
| 1786 | } else if (checkForHeader(lineStart, "com.ses.streamID:" , 17, headerParamsStr)) { |
| 1787 | // Replace the tail of the 'base URL' with the value of this header parameter: |
| 1788 | char* oldBaseURLTail = strrchr(fBaseURL, '/'); |
| 1789 | if (oldBaseURLTail != NULL) { |
| 1790 | unsigned newBaseURLLen |
| 1791 | = (oldBaseURLTail - fBaseURL) + 8/* for "/stream=" */ + strlen(headerParamsStr); |
| 1792 | char* newBaseURL = new char[newBaseURLLen + 1]; |
| 1793 | // Note: We couldn't use "asprintf()", because some compilers don't support it |
| 1794 | sprintf(newBaseURL, "%.*s/stream=%s" , |
| 1795 | (int)(oldBaseURLTail - fBaseURL), fBaseURL, headerParamsStr); |
| 1796 | setBaseURL(newBaseURL); |
| 1797 | delete[] newBaseURL; |
| 1798 | } |
| 1799 | } else if (checkForHeader(lineStart, "Connection:" , 11, headerParamsStr)) { |
| 1800 | if (fTunnelOverHTTPPortNum == 0 && _strncasecmp(headerParamsStr, "Close" , 5) == 0) { |
| 1801 | resetTCPSockets(); |
| 1802 | } |
| 1803 | } |
| 1804 | } |
| 1805 | if (!reachedEndOfHeaders) break; // an error occurred |
| 1806 | |
| 1807 | if (foundRequest == NULL) { |
| 1808 | // Hack: The response didn't have a "CSeq:" header; assume it's for our most recent request: |
| 1809 | foundRequest = fRequestsAwaitingResponse.dequeue(); |
| 1810 | } |
| 1811 | |
| 1812 | // If we saw a "Content-Length:" header, then make sure that we have the amount of data that it specified: |
| 1813 | unsigned bodyOffset = nextLineStart == NULL ? fResponseBytesAlreadySeen : nextLineStart - headerDataCopy; |
| 1814 | bodyStart = &fResponseBuffer[bodyOffset]; |
| 1815 | numBodyBytes = fResponseBytesAlreadySeen - bodyOffset; |
| 1816 | if (contentLength > numBodyBytes) { |
| 1817 | // We need to read more data. First, make sure we have enough space for it: |
| 1818 | unsigned = contentLength - numBodyBytes; |
| 1819 | unsigned remainingBufferSize = responseBufferSize - fResponseBytesAlreadySeen; |
| 1820 | if (numExtraBytesNeeded > remainingBufferSize) { |
| 1821 | char tmpBuf[200]; |
| 1822 | sprintf(tmpBuf, "Response buffer size (%d) is too small for \"Content-Length:\" %d (need a buffer size of >= %d bytes\n" , |
| 1823 | responseBufferSize, contentLength, fResponseBytesAlreadySeen + numExtraBytesNeeded); |
| 1824 | envir().setResultMsg(tmpBuf); |
| 1825 | break; |
| 1826 | } |
| 1827 | |
| 1828 | if (fVerbosityLevel >= 1) { |
| 1829 | envir() << "Have received " << fResponseBytesAlreadySeen << " total bytes of a " |
| 1830 | << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)" ) |
| 1831 | << " RTSP response; awaiting " << numExtraBytesNeeded << " bytes more.\n" ; |
| 1832 | } |
| 1833 | delete[] headerDataCopy; |
| 1834 | if (foundRequest != NULL) fRequestsAwaitingResponse.putAtHead(foundRequest);// put our request record back; we need it again |
| 1835 | return; // We need to read more data |
| 1836 | } |
| 1837 | |
| 1838 | // We now have a complete response (including all bytes specified by the "Content-Length:" header, if any). |
| 1839 | char* responseEnd = bodyStart + contentLength; |
| 1840 | numExtraBytesAfterResponse = &fResponseBuffer[fResponseBytesAlreadySeen] - responseEnd; |
| 1841 | |
| 1842 | if (fVerbosityLevel >= 1) { |
| 1843 | char saved = *responseEnd; |
| 1844 | *responseEnd = '\0'; |
| 1845 | envir() << "Received a complete " |
| 1846 | << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)" ) |
| 1847 | << " response:\n" << fResponseBuffer << "\n" ; |
| 1848 | if (numExtraBytesAfterResponse > 0) envir() << "\t(plus " << numExtraBytesAfterResponse << " additional bytes)\n" ; |
| 1849 | *responseEnd = saved; |
| 1850 | } |
| 1851 | |
| 1852 | if (foundRequest != NULL) { |
| 1853 | Boolean needToResendCommand = False; // by default... |
| 1854 | if (responseCode == 200) { |
| 1855 | // Do special-case response handling for some commands: |
| 1856 | if (strcmp(foundRequest->commandName(), "SETUP" ) == 0) { |
| 1857 | if (!handleSETUPResponse(*foundRequest->subsession(), sessionParamsStr, transportParamsStr, foundRequest->booleanFlags()&0x1)) break; |
| 1858 | } else if (strcmp(foundRequest->commandName(), "PLAY" ) == 0) { |
| 1859 | if (!handlePLAYResponse(foundRequest->session(), foundRequest->subsession(), scaleParamsStr, speedParamsStr, rangeParamsStr, rtpInfoParamsStr)) break; |
| 1860 | } else if (strcmp(foundRequest->commandName(), "TEARDOWN" ) == 0) { |
| 1861 | if (!handleTEARDOWNResponse(*foundRequest->session(), *foundRequest->subsession())) break; |
| 1862 | } else if (strcmp(foundRequest->commandName(), "GET_PARAMETER" ) == 0) { |
| 1863 | if (!handleGET_PARAMETERResponse(foundRequest->contentStr(), bodyStart, responseEnd)) break; |
| 1864 | } |
| 1865 | } else if (responseCode == 401 && handleAuthenticationFailure(wwwAuthenticateParamsStr)) { |
| 1866 | // We need to resend the command, with an "Authorization:" header: |
| 1867 | needToResendCommand = True; |
| 1868 | |
| 1869 | if (strcmp(foundRequest->commandName(), "GET" ) == 0) { |
| 1870 | // Note: If a HTTP "GET" command (for RTSP-over-HTTP tunneling) returns "401 Unauthorized", then we resend it |
| 1871 | // (with an "Authorization:" header), just as we would for a RTSP command. However, we do so using a new TCP connection, |
| 1872 | // because some servers close the original connection after returning the "401 Unauthorized". |
| 1873 | resetTCPSockets(); // forces the opening of a new connection for the resent command |
| 1874 | } |
| 1875 | } else if (responseCode == 301 || responseCode == 302) { // redirection |
| 1876 | resetTCPSockets(); // because we need to connect somewhere else next |
| 1877 | needToResendCommand = True; |
| 1878 | } |
| 1879 | |
| 1880 | if (needToResendCommand) { |
| 1881 | resetResponseBuffer(); |
| 1882 | (void)resendCommand(foundRequest); |
| 1883 | delete[] headerDataCopy; |
| 1884 | return; // without calling our response handler; the response to the resent command will do that |
| 1885 | } |
| 1886 | } |
| 1887 | |
| 1888 | responseSuccess = True; |
| 1889 | } while (0); |
| 1890 | |
| 1891 | // If we have a handler function for this response, call it. |
| 1892 | // But first, reset our response buffer, in case the handler goes to the event loop, and we end up getting called recursively: |
| 1893 | if (numExtraBytesAfterResponse > 0) { |
| 1894 | // An unusual case; usually due to having received pipelined responses. Move the extra bytes to the front of the buffer: |
| 1895 | char* responseEnd = &fResponseBuffer[fResponseBytesAlreadySeen - numExtraBytesAfterResponse]; |
| 1896 | |
| 1897 | // But first: A hack to save a copy of the response 'body', in case it's needed below for "resultString": |
| 1898 | numBodyBytes -= numExtraBytesAfterResponse; |
| 1899 | if (numBodyBytes > 0) { |
| 1900 | char saved = *responseEnd; |
| 1901 | *responseEnd = '\0'; |
| 1902 | bodyStart = strDup(bodyStart); |
| 1903 | *responseEnd = saved; |
| 1904 | } |
| 1905 | |
| 1906 | memmove(fResponseBuffer, responseEnd, numExtraBytesAfterResponse); |
| 1907 | fResponseBytesAlreadySeen = numExtraBytesAfterResponse; |
| 1908 | fResponseBufferBytesLeft = responseBufferSize - numExtraBytesAfterResponse; |
| 1909 | fResponseBuffer[numExtraBytesAfterResponse] = '\0'; |
| 1910 | } else { |
| 1911 | resetResponseBuffer(); |
| 1912 | } |
| 1913 | if (foundRequest != NULL && foundRequest->handler() != NULL) { |
| 1914 | int resultCode; |
| 1915 | char* resultString; |
| 1916 | if (responseSuccess) { |
| 1917 | if (responseCode == 200) { |
| 1918 | resultCode = 0; |
| 1919 | resultString = numBodyBytes > 0 ? strDup(bodyStart) : strDup(publicParamsStr); |
| 1920 | // Note: The "strDup(bodyStart)" call assumes that the body is encoded without interior '\0' bytes |
| 1921 | } else { |
| 1922 | resultCode = responseCode; |
| 1923 | resultString = strDup(responseStr); |
| 1924 | envir().setResultMsg(responseStr); |
| 1925 | } |
| 1926 | (*foundRequest->handler())(this, resultCode, resultString); |
| 1927 | } else { |
| 1928 | // An error occurred parsing the response, so call the handler, indicating an error: |
| 1929 | handleRequestError(foundRequest); |
| 1930 | } |
| 1931 | } |
| 1932 | delete foundRequest; |
| 1933 | delete[] headerDataCopy; |
| 1934 | if (numExtraBytesAfterResponse > 0 && numBodyBytes > 0) delete[] bodyStart; |
| 1935 | } while (numExtraBytesAfterResponse > 0 && responseSuccess); |
| 1936 | } |
| 1937 | |
| 1938 | int RTSPClient::write(const char* data, unsigned count) { |
| 1939 | if (fTLS.isNeeded) { |
| 1940 | return fTLS.write(data, count); |
| 1941 | } else { |
| 1942 | return send(fOutputSocketNum, (const u_int8_t*)data, count, 0); |
| 1943 | } |
| 1944 | } |
| 1945 | |
| 1946 | int RTSPClient::read(u_int8_t* buffer, unsigned bufferSize) { |
| 1947 | if (fTLS.isNeeded) { |
| 1948 | return fTLS.read(buffer, bufferSize); |
| 1949 | } else { |
| 1950 | struct sockaddr_in dummy; // 'from' address - not used |
| 1951 | return readSocket(envir(), fInputSocketNum, buffer, bufferSize, dummy); |
| 1952 | } |
| 1953 | } |
| 1954 | |
| 1955 | |
| 1956 | ////////// RTSPClient::RequestRecord implementation ////////// |
| 1957 | |
| 1958 | RTSPClient::RequestRecord::RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler, |
| 1959 | MediaSession* session, MediaSubsession* subsession, u_int32_t booleanFlags, |
| 1960 | double start, double end, float scale, char const* contentStr) |
| 1961 | : fNext(NULL), fCSeq(cseq), fCommandName(commandName), fSession(session), fSubsession(subsession), fBooleanFlags(booleanFlags), |
| 1962 | fStart(start), fEnd(end), fAbsStartTime(NULL), fAbsEndTime(NULL), fScale(scale), fContentStr(strDup(contentStr)), fHandler(handler) { |
| 1963 | } |
| 1964 | |
| 1965 | RTSPClient::RequestRecord::RequestRecord(unsigned cseq, responseHandler* handler, |
| 1966 | char const* absStartTime, char const* absEndTime, float scale, |
| 1967 | MediaSession* session, MediaSubsession* subsession) |
| 1968 | : fNext(NULL), fCSeq(cseq), fCommandName("PLAY" ), fSession(session), fSubsession(subsession), fBooleanFlags(0), |
| 1969 | fStart(0.0f), fEnd(-1.0f), fAbsStartTime(strDup(absStartTime)), fAbsEndTime(strDup(absEndTime)), fScale(scale), |
| 1970 | fContentStr(NULL), fHandler(handler) { |
| 1971 | } |
| 1972 | |
| 1973 | RTSPClient::RequestRecord::~RequestRecord() { |
| 1974 | // Delete the rest of the list first: |
| 1975 | delete fNext; |
| 1976 | |
| 1977 | delete[] fAbsStartTime; delete[] fAbsEndTime; |
| 1978 | delete[] fContentStr; |
| 1979 | } |
| 1980 | |
| 1981 | |
| 1982 | ////////// RTSPClient::RequestQueue implementation ////////// |
| 1983 | |
| 1984 | RTSPClient::RequestQueue::RequestQueue() |
| 1985 | : fHead(NULL), fTail(NULL) { |
| 1986 | } |
| 1987 | |
| 1988 | RTSPClient::RequestQueue::RequestQueue(RequestQueue& origQueue) |
| 1989 | : fHead(NULL), fTail(NULL) { |
| 1990 | RequestRecord* request; |
| 1991 | while ((request = origQueue.dequeue()) != NULL) { |
| 1992 | enqueue(request); |
| 1993 | } |
| 1994 | } |
| 1995 | |
| 1996 | RTSPClient::RequestQueue::~RequestQueue() { |
| 1997 | reset(); |
| 1998 | } |
| 1999 | |
| 2000 | void RTSPClient::RequestQueue::enqueue(RequestRecord* request) { |
| 2001 | if (fTail == NULL) { |
| 2002 | fHead = request; |
| 2003 | } else { |
| 2004 | fTail->next() = request; |
| 2005 | } |
| 2006 | fTail = request; |
| 2007 | } |
| 2008 | |
| 2009 | RTSPClient::RequestRecord* RTSPClient::RequestQueue::dequeue() { |
| 2010 | RequestRecord* request = fHead; |
| 2011 | if (fHead == fTail) { |
| 2012 | fHead = NULL; |
| 2013 | fTail = NULL; |
| 2014 | } else { |
| 2015 | fHead = fHead->next(); |
| 2016 | } |
| 2017 | if (request != NULL) request->next() = NULL; |
| 2018 | return request; |
| 2019 | } |
| 2020 | |
| 2021 | void RTSPClient::RequestQueue::putAtHead(RequestRecord* request) { |
| 2022 | request->next() = fHead; |
| 2023 | fHead = request; |
| 2024 | if (fTail == NULL) { |
| 2025 | fTail = request; |
| 2026 | } |
| 2027 | } |
| 2028 | |
| 2029 | RTSPClient::RequestRecord* RTSPClient::RequestQueue::findByCSeq(unsigned cseq) { |
| 2030 | RequestRecord* request; |
| 2031 | for (request = fHead; request != NULL; request = request->next()) { |
| 2032 | if (request->cseq() == cseq) return request; |
| 2033 | } |
| 2034 | return NULL; |
| 2035 | } |
| 2036 | |
| 2037 | void RTSPClient::RequestQueue::reset() { |
| 2038 | delete fHead; |
| 2039 | fHead = fTail = NULL; |
| 2040 | } |
| 2041 | |
| 2042 | |
| 2043 | #ifndef OMIT_REGISTER_HANDLING |
| 2044 | ////////// HandlerServerForREGISTERCommand implementation ///////// |
| 2045 | |
| 2046 | HandlerServerForREGISTERCommand* HandlerServerForREGISTERCommand |
| 2047 | ::createNew(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, Port ourPort, |
| 2048 | UserAuthenticationDatabase* authDatabase, int verbosityLevel, char const* applicationName) { |
| 2049 | int ourSocket = setUpOurSocket(env, ourPort); |
| 2050 | if (ourSocket == -1) return NULL; |
| 2051 | |
| 2052 | return new HandlerServerForREGISTERCommand(env, creationFunc, ourSocket, ourPort, authDatabase, verbosityLevel, applicationName); |
| 2053 | } |
| 2054 | |
| 2055 | HandlerServerForREGISTERCommand |
| 2056 | ::HandlerServerForREGISTERCommand(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, int ourSocket, Port ourPort, |
| 2057 | UserAuthenticationDatabase* authDatabase, int verbosityLevel, char const* applicationName) |
| 2058 | : RTSPServer(env, ourSocket, ourPort, authDatabase, 30/*small reclamationTestSeconds*/), |
| 2059 | fCreationFunc(creationFunc), fVerbosityLevel(verbosityLevel), fApplicationName(strDup(applicationName)) { |
| 2060 | } |
| 2061 | |
| 2062 | HandlerServerForREGISTERCommand::~HandlerServerForREGISTERCommand() { |
| 2063 | delete[] fApplicationName; |
| 2064 | } |
| 2065 | |
| 2066 | RTSPClient* HandlerServerForREGISTERCommand |
| 2067 | ::createNewRTSPClient(char const* rtspURL, int verbosityLevel, char const* applicationName, int socketNumToServer) { |
| 2068 | // Default implementation: create a basic "RTSPClient": |
| 2069 | return RTSPClient::createNew(envir(), rtspURL, verbosityLevel, applicationName, 0, socketNumToServer); |
| 2070 | } |
| 2071 | |
| 2072 | char const* HandlerServerForREGISTERCommand::allowedCommandNames() { |
| 2073 | return "OPTIONS, REGISTER" ; |
| 2074 | } |
| 2075 | |
| 2076 | Boolean HandlerServerForREGISTERCommand |
| 2077 | ::weImplementREGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, |
| 2078 | char const* /*proxyURLSuffix*/, char*& responseStr) { |
| 2079 | responseStr = NULL; |
| 2080 | // By default, we implement only "REGISTER"; not "DEREGISTER". Subclass to implement "DEREGISTER" |
| 2081 | return strcmp(cmd, "REGISTER" ) == 0; |
| 2082 | } |
| 2083 | |
| 2084 | void HandlerServerForREGISTERCommand |
| 2085 | ::implementCmd_REGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, |
| 2086 | char const* url, char const* urlSuffix, int socketToRemoteServer, |
| 2087 | Boolean deliverViaTCP, char const* /*proxyURLSuffix*/) { |
| 2088 | if (strcmp(cmd, "REGISTER" ) == 0) { // By default, we don't implement "DEREGISTER" |
| 2089 | // Create a new "RTSPClient" object, and call our 'creation function' with it: |
| 2090 | RTSPClient* newRTSPClient = createNewRTSPClient(url, fVerbosityLevel, fApplicationName, socketToRemoteServer); |
| 2091 | |
| 2092 | if (fCreationFunc != NULL) (*fCreationFunc)(newRTSPClient, deliverViaTCP); |
| 2093 | } |
| 2094 | } |
| 2095 | #endif |
| 2096 | |