1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15**********/
16// "liveMedia"
17// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
18// Common routines used by both RTSP clients and servers
19// Implementation
20
21#include "RTSPCommon.hh"
22#include "Locale.hh"
23#include <string.h>
24#include <stdio.h>
25#include <ctype.h> // for "isxdigit()
26#include <time.h> // for "strftime()" and "gmtime()"
27
28static void decodeURL(char* url) {
29 // Replace (in place) any %<hex><hex> sequences with the appropriate 8-bit character.
30 char* cursor = url;
31 while (*cursor) {
32 if ((cursor[0] == '%') &&
33 cursor[1] && isxdigit(cursor[1]) &&
34 cursor[2] && isxdigit(cursor[2])) {
35 // We saw a % followed by 2 hex digits, so we copy the literal hex value into the URL, then advance the cursor past it:
36 char hex[3];
37 hex[0] = cursor[1];
38 hex[1] = cursor[2];
39 hex[2] = '\0';
40 *url++ = (char)strtol(hex, NULL, 16);
41 cursor += 3;
42 } else {
43 // Common case: This is a normal character or a bogus % expression, so just copy it
44 *url++ = *cursor++;
45 }
46 }
47
48 *url = '\0';
49}
50
51Boolean parseRTSPRequestString(char const* reqStr,
52 unsigned reqStrSize,
53 char* resultCmdName,
54 unsigned resultCmdNameMaxSize,
55 char* resultURLPreSuffix,
56 unsigned resultURLPreSuffixMaxSize,
57 char* resultURLSuffix,
58 unsigned resultURLSuffixMaxSize,
59 char* resultCSeq,
60 unsigned resultCSeqMaxSize,
61 char* resultSessionIdStr,
62 unsigned resultSessionIdStrMaxSize,
63 unsigned& contentLength) {
64 // This parser is currently rather dumb; it should be made smarter #####
65
66 // "Be liberal in what you accept": Skip over any whitespace at the start of the request:
67 unsigned i;
68 for (i = 0; i < reqStrSize; ++i) {
69 char c = reqStr[i];
70 if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0')) break;
71 }
72 if (i == reqStrSize) return False; // The request consisted of nothing but whitespace!
73
74 // Then read everything up to the next space (or tab) as the command name:
75 Boolean parseSucceeded = False;
76 unsigned i1 = 0;
77 for (; i1 < resultCmdNameMaxSize-1 && i < reqStrSize; ++i,++i1) {
78 char c = reqStr[i];
79 if (c == ' ' || c == '\t') {
80 parseSucceeded = True;
81 break;
82 }
83
84 resultCmdName[i1] = c;
85 }
86 resultCmdName[i1] = '\0';
87 if (!parseSucceeded) return False;
88
89 // Skip over the prefix of any "rtsp://" or "rtsp:/" URL that follows:
90 unsigned j = i+1;
91 while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j; // skip over any additional white space
92 for (; (int)j < (int)(reqStrSize-8); ++j) {
93 if ((reqStr[j] == 'r' || reqStr[j] == 'R')
94 && (reqStr[j+1] == 't' || reqStr[j+1] == 'T')
95 && (reqStr[j+2] == 's' || reqStr[j+2] == 'S')
96 && (reqStr[j+3] == 'p' || reqStr[j+3] == 'P')
97 && reqStr[j+4] == ':' && reqStr[j+5] == '/') {
98 j += 6;
99 if (reqStr[j] == '/') {
100 // This is a "rtsp://" URL; skip over the host:port part that follows:
101 ++j;
102 while (j < reqStrSize && reqStr[j] != '/' && reqStr[j] != ' ') ++j;
103 } else {
104 // This is a "rtsp:/" URL; back up to the "/":
105 --j;
106 }
107 i = j;
108 break;
109 }
110 }
111
112 // Look for the URL suffix (before the following "RTSP/"):
113 parseSucceeded = False;
114 for (unsigned k = i+1; (int)k < (int)(reqStrSize-5); ++k) {
115 if (reqStr[k] == 'R' && reqStr[k+1] == 'T' &&
116 reqStr[k+2] == 'S' && reqStr[k+3] == 'P' && reqStr[k+4] == '/') {
117 while (--k >= i && reqStr[k] == ' ') {} // go back over all spaces before "RTSP/"
118 unsigned k1 = k;
119 while (k1 > i && reqStr[k1] != '/') --k1;
120
121 // ASSERT: At this point
122 // i: first space or slash after "host" or "host:port"
123 // k: last non-space before "RTSP/"
124 // k1: last slash in the range [i,k]
125
126 // The URL suffix comes from [k1+1,k]
127 // Copy "resultURLSuffix":
128 unsigned n = 0, k2 = k1+1;
129 if (k2 <= k) {
130 if (k - k1 + 1 > resultURLSuffixMaxSize) return False; // there's no room
131 while (k2 <= k) resultURLSuffix[n++] = reqStr[k2++];
132 }
133 resultURLSuffix[n] = '\0';
134
135 // The URL 'pre-suffix' comes from [i+1,k1-1]
136 // Copy "resultURLPreSuffix":
137 n = 0; k2 = i+1;
138 if (k2+1 <= k1) {
139 if (k1 - i > resultURLPreSuffixMaxSize) return False; // there's no room
140 while (k2 <= k1-1) resultURLPreSuffix[n++] = reqStr[k2++];
141 }
142 resultURLPreSuffix[n] = '\0';
143 decodeURL(resultURLPreSuffix);
144
145 i = k + 7; // to go past " RTSP/"
146 parseSucceeded = True;
147 break;
148 }
149 }
150 if (!parseSucceeded) return False;
151
152 // Look for "CSeq:" (mandatory, case insensitive), skip whitespace,
153 // then read everything up to the next \r or \n as 'CSeq':
154 parseSucceeded = False;
155 for (j = i; (int)j < (int)(reqStrSize-5); ++j) {
156 if (_strncasecmp("CSeq:", &reqStr[j], 5) == 0) {
157 j += 5;
158 while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j;
159 unsigned n;
160 for (n = 0; n < resultCSeqMaxSize-1 && j < reqStrSize; ++n,++j) {
161 char c = reqStr[j];
162 if (c == '\r' || c == '\n') {
163 parseSucceeded = True;
164 break;
165 }
166
167 resultCSeq[n] = c;
168 }
169 resultCSeq[n] = '\0';
170 break;
171 }
172 }
173 if (!parseSucceeded) return False;
174
175 // Look for "Session:" (optional, case insensitive), skip whitespace,
176 // then read everything up to the next \r or \n as 'Session':
177 resultSessionIdStr[0] = '\0'; // default value (empty string)
178 for (j = i; (int)j < (int)(reqStrSize-8); ++j) {
179 if (_strncasecmp("Session:", &reqStr[j], 8) == 0) {
180 j += 8;
181 while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j;
182 unsigned n;
183 for (n = 0; n < resultSessionIdStrMaxSize-1 && j < reqStrSize; ++n,++j) {
184 char c = reqStr[j];
185 if (c == '\r' || c == '\n') {
186 break;
187 }
188
189 resultSessionIdStr[n] = c;
190 }
191 resultSessionIdStr[n] = '\0';
192 break;
193 }
194 }
195
196 // Also: Look for "Content-Length:" (optional, case insensitive)
197 contentLength = 0; // default value
198 for (j = i; (int)j < (int)(reqStrSize-15); ++j) {
199 if (_strncasecmp("Content-Length:", &(reqStr[j]), 15) == 0) {
200 j += 15;
201 while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j;
202 unsigned num;
203 if (sscanf(&reqStr[j], "%u", &num) == 1) {
204 contentLength = num;
205 }
206 }
207 }
208 return True;
209}
210
211Boolean parseRangeParam(char const* paramStr,
212 double& rangeStart, double& rangeEnd,
213 char*& absStartTime, char*& absEndTime,
214 Boolean& startTimeIsNow) {
215 delete[] absStartTime; delete[] absEndTime;
216 absStartTime = absEndTime = NULL; // by default, unless "paramStr" is a "clock=..." string
217 startTimeIsNow = False; // by default
218 double start, end;
219 int numCharsMatched1 = 0, numCharsMatched2 = 0, numCharsMatched3 = 0, numCharsMatched4 = 0;
220 int startHour = 0, startMin = 0, endHour = 0, endMin = 0;
221 double startSec = 0.0, endSec = 0.0;
222 Locale l("C", Numeric);
223 if (sscanf(paramStr, "npt = %d:%d:%lf - %d:%d:%lf", &startHour, &startMin, &startSec, &endHour, &endMin, &endSec) == 6) {
224 rangeStart = startHour*3600 + startMin*60 + startSec;
225 rangeEnd = endHour*3600 + endMin*60 + endSec;
226 } else if (sscanf(paramStr, "npt =%lf - %d:%d:%lf", &start, &endHour, &endMin, &endSec) == 4) {
227 rangeStart = start;
228 rangeEnd = endHour*3600 + endMin*60 + endSec;
229 } else if (sscanf(paramStr, "npt = %d:%d:%lf -", &startHour, &startMin, &startSec) == 3) {
230 rangeStart = startHour*3600 + startMin*60 + startSec;
231 rangeEnd = 0.0;
232 } else if (sscanf(paramStr, "npt = %lf - %lf", &start, &end) == 2) {
233 rangeStart = start;
234 rangeEnd = end;
235 } else if (sscanf(paramStr, "npt = %n%lf -", &numCharsMatched1, &start) == 1) {
236 if (paramStr[numCharsMatched1] == '-') {
237 // special case for "npt = -<endtime>", which matches here:
238 rangeStart = 0.0; startTimeIsNow = True;
239 rangeEnd = -start;
240 } else {
241 rangeStart = start;
242 rangeEnd = 0.0;
243 }
244 } else if (sscanf(paramStr, "npt = now - %lf", &end) == 1) {
245 rangeStart = 0.0; startTimeIsNow = True;
246 rangeEnd = end;
247 } else if (sscanf(paramStr, "npt = now -%n", &numCharsMatched2) == 0 && numCharsMatched2 > 0) {
248 rangeStart = 0.0; startTimeIsNow = True;
249 rangeEnd = 0.0;
250 } else if (sscanf(paramStr, "clock = %n", &numCharsMatched3) == 0 && numCharsMatched3 > 0) {
251 rangeStart = rangeEnd = 0.0;
252
253 char const* utcTimes = &paramStr[numCharsMatched3];
254 size_t len = strlen(utcTimes) + 1;
255 char* as = new char[len];
256 char* ae = new char[len];
257 int sscanfResult = sscanf(utcTimes, "%[^-]-%[^\r\n]", as, ae);
258 if (sscanfResult == 2) {
259 absStartTime = as;
260 absEndTime = ae;
261 } else if (sscanfResult == 1) {
262 absStartTime = as;
263 delete[] ae;
264 } else {
265 delete[] as; delete[] ae;
266 return False;
267 }
268 } else if (sscanf(paramStr, "smtpe = %n", &numCharsMatched4) == 0 && numCharsMatched4 > 0) {
269 // We accept "smtpe=" parameters, but currently do not interpret them.
270 } else {
271 return False; // The header is malformed
272 }
273
274 return True;
275}
276
277Boolean parseRangeHeader(char const* buf,
278 double& rangeStart, double& rangeEnd,
279 char*& absStartTime, char*& absEndTime,
280 Boolean& startTimeIsNow) {
281 // First, find "Range:"
282 while (1) {
283 if (*buf == '\0') return False; // not found
284 if (_strncasecmp(buf, "Range: ", 7) == 0) break;
285 ++buf;
286 }
287
288 char const* fields = buf + 7;
289 while (*fields == ' ') ++fields;
290 return parseRangeParam(fields, rangeStart, rangeEnd, absStartTime, absEndTime, startTimeIsNow);
291}
292
293Boolean parseScaleHeader(char const* buf, float& scale) {
294 // Initialize the result parameter to a default value:
295 scale = 1.0;
296
297 // First, find "Scale:"
298 while (1) {
299 if (*buf == '\0') return False; // not found
300 if (_strncasecmp(buf, "Scale:", 6) == 0) break;
301 ++buf;
302 }
303
304 char const* fields = buf + 6;
305 while (*fields == ' ') ++fields;
306 float sc;
307 if (sscanf(fields, "%f", &sc) == 1) {
308 scale = sc;
309 } else {
310 return False; // The header is malformed
311 }
312
313 return True;
314}
315
316// Used to implement "RTSPOptionIsSupported()":
317static Boolean isSeparator(char c) { return c == ' ' || c == ',' || c == ';' || c == ':'; }
318
319Boolean RTSPOptionIsSupported(char const* commandName, char const* optionsResponseString) {
320 do {
321 if (commandName == NULL || optionsResponseString == NULL) break;
322
323 unsigned const commandNameLen = strlen(commandName);
324 if (commandNameLen == 0) break;
325
326 // "optionsResponseString" is assumed to be a list of command names, separated by " " and/or ",", ";", or ":"
327 // Scan through these, looking for "commandName".
328 while (1) {
329 // Skip over separators:
330 while (*optionsResponseString != '\0' && isSeparator(*optionsResponseString)) ++optionsResponseString;
331 if (*optionsResponseString == '\0') break;
332
333 // At this point, "optionsResponseString" begins with a command name (with perhaps a separator afterwads).
334 if (strncmp(commandName, optionsResponseString, commandNameLen) == 0) {
335 // We have at least a partial match here.
336 optionsResponseString += commandNameLen;
337 if (*optionsResponseString == '\0' || isSeparator(*optionsResponseString)) return True;
338 }
339
340 // No match. Skip over the rest of the command name:
341 while (*optionsResponseString != '\0' && !isSeparator(*optionsResponseString)) ++optionsResponseString;
342 }
343 } while (0);
344
345 return False;
346}
347
348char const* dateHeader() {
349 static char buf[200];
350#if !defined(_WIN32_WCE)
351 time_t tt = time(NULL);
352 strftime(buf, sizeof buf, "Date: %a, %b %d %Y %H:%M:%S GMT\r\n", gmtime(&tt));
353#else
354 // WinCE apparently doesn't have "time()", "strftime()", or "gmtime()",
355 // so generate the "Date:" header a different, WinCE-specific way.
356 // (Thanks to Pierre l'Hussiez for this code)
357 // RSF: But where is the "Date: " string? This code doesn't look quite right...
358 SYSTEMTIME SystemTime;
359 GetSystemTime(&SystemTime);
360 WCHAR dateFormat[] = L"ddd, MMM dd yyyy";
361 WCHAR timeFormat[] = L"HH:mm:ss GMT\r\n";
362 WCHAR inBuf[200];
363 DWORD locale = LOCALE_NEUTRAL;
364
365 int ret = GetDateFormat(locale, 0, &SystemTime,
366 (LPTSTR)dateFormat, (LPTSTR)inBuf, sizeof inBuf);
367 inBuf[ret - 1] = ' ';
368 ret = GetTimeFormat(locale, 0, &SystemTime,
369 (LPTSTR)timeFormat,
370 (LPTSTR)inBuf + ret, (sizeof inBuf) - ret);
371 wcstombs(buf, inBuf, wcslen(inBuf));
372#endif
373 return buf;
374}
375