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 | |