1 /* HTTP-RTMP Stream Gateway
2 * Copyright (C) 2009 Andrej Stepanchuk
3 * Copyright (C) 2009-2010 Howard Chu
4 *
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with RTMPDump; see the file COPYING. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 */
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26
27 #include <signal.h>
28 #include <getopt.h>
29
30 #include <assert.h>
31
32 #include "librtmp/rtmp_sys.h"
33 #include "librtmp/log.h"
34
35 #include "thread.h"
36
37 #define RD_SUCCESS 0
38 #define RD_FAILED 1
39 #define RD_INCOMPLETE 2
40
41 #define PACKET_SIZE 1024*1024
42
43 #ifdef WIN32
44 #define InitSockets() {\
45 WORD version; \
46 WSADATA wsaData; \
47 \
48 version = MAKEWORD(1,1); \
49 WSAStartup(version, &wsaData); }
50
51 #define CleanupSockets() WSACleanup()
52 #else
53 #define InitSockets()
54 #define CleanupSockets()
55 #endif
56
57 enum
58 {
59 STREAMING_ACCEPTING,
60 STREAMING_IN_PROGRESS,
61 STREAMING_STOPPING,
62 STREAMING_STOPPED
63 };
64
65 typedef struct
66 {
67 int socket;
68 int state;
69
70 } STREAMING_SERVER;
71
72 STREAMING_SERVER *httpServer = 0; // server structure pointer
73
74 STREAMING_SERVER *startStreaming(const char *address, int port);
75 void stopStreaming(STREAMING_SERVER * server);
76
77 typedef struct
78 {
79 AVal hostname;
80 int rtmpport;
81 int protocol;
82 int bLiveStream; // is it a live stream? then we can't seek/resume
83
84 long int timeout; // timeout connection after 120 seconds
85 uint32_t bufferTime;
86
87 char *rtmpurl;
88 AVal fullUrl;
89 AVal playpath;
90 AVal swfUrl;
91 AVal tcUrl;
92 AVal pageUrl;
93 AVal app;
94 AVal auth;
95 AVal swfHash;
96 AVal flashVer;
97 AVal token;
98 AVal subscribepath;
99 AVal usherToken; //Justin.tv auth token
100 AVal sockshost;
101 AMFObject extras;
102 int edepth;
103 uint32_t swfSize;
104 int swfAge;
105 int swfVfy;
106
107 uint32_t dStartOffset;
108 uint32_t dStopOffset;
109
110 #ifdef CRYPTO
111 unsigned char hash[RTMP_SWF_HASHLEN];
112 #endif
113 } RTMP_REQUEST;
114
115 #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val)
116
117 int
parseAMF(AMFObject * obj,const char * arg,int * depth)118 parseAMF(AMFObject *obj, const char *arg, int *depth)
119 {
120 AMFObjectProperty prop = {{0,0}};
121 int i;
122 char *p;
123
124 if (arg[1] == ':')
125 {
126 p = (char *)arg+2;
127 switch(arg[0])
128 {
129 case 'B':
130 prop.p_type = AMF_BOOLEAN;
131 prop.p_vu.p_number = atoi(p);
132 break;
133 case 'S':
134 prop.p_type = AMF_STRING;
135 STR2AVAL(prop.p_vu.p_aval,p);
136 break;
137 case 'N':
138 prop.p_type = AMF_NUMBER;
139 prop.p_vu.p_number = strtod(p, NULL);
140 break;
141 case 'Z':
142 prop.p_type = AMF_NULL;
143 break;
144 case 'O':
145 i = atoi(p);
146 if (i)
147 {
148 prop.p_type = AMF_OBJECT;
149 }
150 else
151 {
152 (*depth)--;
153 return 0;
154 }
155 break;
156 default:
157 return -1;
158 }
159 }
160 else if (arg[2] == ':' && arg[0] == 'N')
161 {
162 p = strchr(arg+3, ':');
163 if (!p || !*depth)
164 return -1;
165 prop.p_name.av_val = (char *)arg+3;
166 prop.p_name.av_len = p - (arg+3);
167
168 p++;
169 switch(arg[1])
170 {
171 case 'B':
172 prop.p_type = AMF_BOOLEAN;
173 prop.p_vu.p_number = atoi(p);
174 break;
175 case 'S':
176 prop.p_type = AMF_STRING;
177 STR2AVAL(prop.p_vu.p_aval,p);
178 break;
179 case 'N':
180 prop.p_type = AMF_NUMBER;
181 prop.p_vu.p_number = strtod(p, NULL);
182 break;
183 case 'O':
184 prop.p_type = AMF_OBJECT;
185 break;
186 default:
187 return -1;
188 }
189 }
190 else
191 return -1;
192
193 if (*depth)
194 {
195 AMFObject *o2;
196 for (i=0; i<*depth; i++)
197 {
198 o2 = &obj->o_props[obj->o_num-1].p_vu.p_object;
199 obj = o2;
200 }
201 }
202 AMF_AddProp(obj, &prop);
203 if (prop.p_type == AMF_OBJECT)
204 (*depth)++;
205 return 0;
206 }
207
208 /* this request is formed from the parameters and used to initialize a new request,
209 * thus it is a default settings list. All settings can be overriden by specifying the
210 * parameters in the GET request. */
211 RTMP_REQUEST defaultRTMPRequest;
212
213 int ParseOption(char opt, char *arg, RTMP_REQUEST * req);
214
215 #ifdef _DEBUG
216 uint32_t debugTS = 0;
217
218 int pnum = 0;
219
220 FILE *netstackdump = NULL;
221 FILE *netstackdump_read = NULL;
222 #endif
223
224 /* inplace http unescape. This is possible .. strlen(unescaped_string) <= strlen(esacped_string) */
225 void
http_unescape(char * data)226 http_unescape(char *data)
227 {
228 char hex[3];
229 char *stp;
230 int src_x = 0;
231 int dst_x = 0;
232
233 int length = (int) strlen(data);
234 hex[2] = 0;
235
236 while (src_x < length)
237 {
238 if (strncmp(data + src_x, "%", 1) == 0 && src_x + 2 < length)
239 {
240 //
241 // Since we encountered a '%' we know this is an escaped character
242 //
243 hex[0] = data[src_x + 1];
244 hex[1] = data[src_x + 2];
245 data[dst_x] = (char) strtol(hex, &stp, 16);
246 dst_x += 1;
247 src_x += 3;
248 }
249 else if (src_x != dst_x)
250 {
251 //
252 // This doesn't need to be unescaped. If we didn't unescape anything previously
253 // there is no need to copy the string either
254 //
255 data[dst_x] = data[src_x];
256 src_x += 1;
257 dst_x += 1;
258 }
259 else
260 {
261 //
262 // This doesn't need to be unescaped, however we need to copy the string
263 //
264 src_x += 1;
265 dst_x += 1;
266 }
267 }
268 data[dst_x] = '\0';
269 }
270
271 TFTYPE
controlServerThread(void * unused)272 controlServerThread(void *unused)
273 {
274 char ich;
275 while (1)
276 {
277 ich = getchar();
278 switch (ich)
279 {
280 case 'q':
281 RTMP_LogPrintf("Exiting\n");
282 stopStreaming(httpServer);
283 exit(0);
284 break;
285 default:
286 RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich);
287 }
288 }
289 TFRET();
290 }
291
292 /*
293 ssize_t readHTTPLine(int sockfd, char *buffer, size_t length)
294 {
295 size_t i=0;
296
297 while(i < length-1) {
298 char c;
299 int n = read(sockfd, &c, 1);
300
301 if(n == 0)
302 break;
303
304 buffer[i] = c;
305 i++;
306
307 if(c == '\n')
308 break;
309 }
310 buffer[i]='\0';
311 i++;
312
313 return i;
314 }
315
316 int isHTTPRequestEOF(char *line, size_t length)
317 {
318 if(length < 2)
319 return TRUE;
320
321 if(line[0]=='\r' && line[1]=='\n')
322 return TRUE;
323
324 return FALSE;
325 }
326 */
327
processTCPrequest(STREAMING_SERVER * server,int sockfd)328 void processTCPrequest(STREAMING_SERVER * server, // server socket and state (our listening socket)
329 int sockfd // client connection socket
330 )
331 {
332 char buf[512] = { 0 }; // answer buffer
333 char header[2048] = { 0 }; // request header
334 char *filename = NULL; // GET request: file name //512 not enuf
335 char *buffer = NULL; // stream buffer
336 char *ptr = NULL; // header pointer
337 int len;
338
339 size_t nRead = 0;
340
341 char srvhead[] = "\r\nServer: HTTP-RTMP Stream Server " RTMPDUMP_VERSION "\r\n";
342
343 char *status = "404 Not Found";
344
345 server->state = STREAMING_IN_PROGRESS;
346
347 RTMP rtmp = { 0 };
348 uint32_t dSeek = 0; // can be used to start from a later point in the stream
349
350 // reset RTMP options to defaults specified upon invokation of streams
351 RTMP_REQUEST req;
352 memcpy(&req, &defaultRTMPRequest, sizeof(RTMP_REQUEST));
353
354 // timeout for http requests
355 fd_set fds;
356 struct timeval tv;
357
358 memset(&tv, 0, sizeof(struct timeval));
359 tv.tv_sec = 5;
360
361 // go through request lines
362 //do {
363 FD_ZERO(&fds);
364 FD_SET(sockfd, &fds);
365
366 if (select(sockfd + 1, &fds, NULL, NULL, &tv) <= 0)
367 {
368 RTMP_Log(RTMP_LOGERROR, "Request timeout/select failed, ignoring request");
369 goto quit;
370 }
371 else
372 {
373 nRead = recv(sockfd, header, 2047, 0);
374 header[2047] = '\0';
375
376 RTMP_Log(RTMP_LOGDEBUG, "%s: header: %s", __FUNCTION__, header);
377
378 if (strstr(header, "Range: bytes=") != 0)
379 {
380 // TODO check range starts from 0 and asking till the end.
381 RTMP_LogPrintf("%s, Range request not supported\n", __FUNCTION__);
382 len = sprintf(buf, "HTTP/1.0 416 Requested Range Not Satisfiable%s\r\n",
383 srvhead);
384 send(sockfd, buf, len, 0);
385 goto quit;
386 }
387
388 if (strncmp(header, "GET", 3) == 0 && nRead > 4)
389 {
390 filename = header + 4;
391
392 // filter " HTTP/..." from end of request
393 char *p = filename;
394 while (*p != '\0')
395 {
396 if (*p == ' ')
397 {
398 *p = '\0';
399 break;
400 }
401 p++;
402 }
403 }
404 }
405 //} while(!isHTTPRequestEOF(header, nRead));
406
407 // if we got a filename from the GET method
408 if (filename != NULL)
409 {
410 RTMP_Log(RTMP_LOGDEBUG, "%s: Request header: %s", __FUNCTION__, filename);
411 if (filename[0] == '/')
412 { // if its not empty, is it /?
413 ptr = filename + 1;
414
415 // parse parameters
416 if (*ptr == '?')
417 {
418 ptr++;
419 int len = strlen(ptr);
420
421 while (len >= 2)
422 {
423 char ich = *ptr;
424 ptr++;
425 if (*ptr != '=')
426 goto filenotfound; // long parameters not (yet) supported
427
428 ptr++;
429 len -= 2;
430
431 // get position of the next '&'
432 char *temp;
433
434 unsigned int nArgLen = len;
435 if ((temp = strstr(ptr, "&")) != 0)
436 {
437 nArgLen = temp - ptr;
438 }
439
440 char *arg = (char *) malloc((nArgLen + 1) * sizeof(char));
441 memcpy(arg, ptr, nArgLen * sizeof(char));
442 arg[nArgLen] = '\0';
443
444 //RTMP_Log(RTMP_LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg);
445 http_unescape(arg);
446
447 RTMP_Log(RTMP_LOGDEBUG, "%s: parameter: %c, arg: %s", __FUNCTION__,
448 ich, arg);
449
450 ptr += nArgLen + 1;
451 len -= nArgLen + 1;
452
453 if (!ParseOption(ich, arg, &req))
454 {
455 status = "400 unknown option";
456 goto filenotfound;
457 }
458 }
459 }
460 }
461 else
462 {
463 goto filenotfound;
464 }
465 }
466 else
467 {
468 RTMP_LogPrintf("%s: No request header received/unsupported method\n",
469 __FUNCTION__);
470 }
471
472 // do necessary checks right here to make sure the combined request of default values and GET parameters is correct
473 if (!req.hostname.av_len && !req.fullUrl.av_len)
474 {
475 RTMP_Log(RTMP_LOGERROR,
476 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
477 status = "400 Missing Hostname";
478 goto filenotfound;
479 }
480 if (req.playpath.av_len == 0 && !req.fullUrl.av_len)
481 {
482 RTMP_Log(RTMP_LOGERROR,
483 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
484 status = "400 Missing Playpath";
485 goto filenotfound;;
486 }
487
488 if (req.protocol == RTMP_PROTOCOL_UNDEFINED && !req.fullUrl.av_len)
489 {
490 RTMP_Log(RTMP_LOGWARNING,
491 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
492 req.protocol = RTMP_PROTOCOL_RTMP;
493 }
494 if (req.rtmpport == -1 && !req.fullUrl.av_len)
495 {
496 RTMP_Log(RTMP_LOGWARNING,
497 "You haven't specified a port (--port) or rtmp url (-r), using default port");
498 req.rtmpport = 0;
499 }
500 if (req.rtmpport == 0 && !req.fullUrl.av_len)
501 {
502 if (req.protocol & RTMP_FEATURE_SSL)
503 req.rtmpport = 443;
504 else if (req.protocol & RTMP_FEATURE_HTTP)
505 req.rtmpport = 80;
506 else
507 req.rtmpport = 1935;
508 }
509
510 if (req.tcUrl.av_len == 0)
511 {
512 char str[512] = { 0 };
513 req.tcUrl.av_len = snprintf(str, 511, "%s://%.*s:%d/%.*s",
514 RTMPProtocolStringsLower[req.protocol], req.hostname.av_len,
515 req.hostname.av_val, req.rtmpport, req.app.av_len, req.app.av_val);
516 req.tcUrl.av_val = (char *) malloc(req.tcUrl.av_len + 1);
517 strcpy(req.tcUrl.av_val, str);
518 }
519
520 if (req.swfVfy)
521 {
522 #ifdef CRYPTO
523 if (RTMP_HashSWF(req.swfUrl.av_val, &req.swfSize, req.hash, req.swfAge) == 0)
524 {
525 req.swfHash.av_val = (char *)req.hash;
526 req.swfHash.av_len = RTMP_SWF_HASHLEN;
527 }
528 #endif
529 }
530
531 // after validation of the http request send response header
532 len = sprintf(buf, "HTTP/1.0 200 OK%sContent-Type: video/flv\r\n\r\n", srvhead);
533 send(sockfd, buf, len, 0);
534
535 // send the packets
536 buffer = (char *) calloc(PACKET_SIZE, 1);
537
538 // User defined seek offset
539 if (req.dStartOffset > 0)
540 {
541 if (req.bLiveStream)
542 RTMP_Log(RTMP_LOGWARNING,
543 "Can't seek in a live stream, ignoring --seek option");
544 else
545 dSeek += req.dStartOffset;
546 }
547
548 if (dSeek != 0)
549 {
550 RTMP_LogPrintf("Starting at TS: %d ms\n", dSeek);
551 }
552
553 RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", req.bufferTime);
554 RTMP_Init(&rtmp);
555 RTMP_SetBufferMS(&rtmp, req.bufferTime);
556 if (!req.fullUrl.av_len)
557 {
558 RTMP_SetupStream(&rtmp, req.protocol, &req.hostname, req.rtmpport, &req.sockshost,
559 &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, &req.usherToken, dSeek, req.dStopOffset,
560 req.bLiveStream, req.timeout);
561 }
562 else
563 {
564 if (RTMP_SetupURL(&rtmp, req.fullUrl.av_val) == FALSE)
565 {
566 RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", req.fullUrl.av_val);
567 return;
568 }
569 }
570 /* backward compatibility, we always sent this as true before */
571 if (req.auth.av_len)
572 rtmp.Link.lFlags |= RTMP_LF_AUTH;
573
574 rtmp.Link.extras = req.extras;
575 rtmp.Link.token = req.token;
576 rtmp.m_read.timestamp = dSeek;
577
578 RTMP_LogPrintf("Connecting ... port: %d, app: %s\n", req.rtmpport, req.app.av_val);
579 if (!RTMP_Connect(&rtmp, NULL))
580 {
581 RTMP_LogPrintf("%s, failed to connect!\n", __FUNCTION__);
582 }
583 else
584 {
585 unsigned long size = 0;
586 double percent = 0;
587 double duration = 0.0;
588
589 int nWritten = 0;
590 int nRead = 0;
591
592 do
593 {
594 nRead = RTMP_Read(&rtmp, buffer, PACKET_SIZE);
595
596 if (nRead > 0)
597 {
598 if ((nWritten = send(sockfd, buffer, nRead, 0)) < 0)
599 {
600 RTMP_Log(RTMP_LOGERROR, "%s, sending failed, error: %d", __FUNCTION__,
601 GetSockError());
602 goto cleanup; // we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING
603 }
604
605 size += nRead;
606
607 //RTMP_LogPrintf("write %dbytes (%.1f KB)\n", nRead, nRead/1024.0);
608 if (duration <= 0) // if duration unknown try to get it from the stream (onMetaData)
609 duration = RTMP_GetDuration(&rtmp);
610
611 if (duration > 0)
612 {
613 percent =
614 ((double) (dSeek + rtmp.m_read.timestamp)) / (duration *
615 1000.0) * 100.0;
616 percent = ((double) (int) (percent * 10.0)) / 10.0;
617 RTMP_LogStatus("\r%.3f KB / %.2f sec (%.1f%%)",
618 (double) size / 1024.0,
619 (double) (rtmp.m_read.timestamp) / 1000.0, percent);
620 }
621 else
622 {
623 RTMP_LogStatus("\r%.3f KB / %.2f sec", (double) size / 1024.0,
624 (double) (rtmp.m_read.timestamp) / 1000.0);
625 }
626 }
627 #ifdef _DEBUG
628 else
629 {
630 RTMP_Log(RTMP_LOGDEBUG, "zero read!");
631 }
632 #endif
633 }
634 while (server->state == STREAMING_IN_PROGRESS && nRead > -1
635 && RTMP_IsConnected(&rtmp) && nWritten >= 0);
636 }
637 cleanup:
638 RTMP_LogPrintf("Closing connection... ");
639 RTMP_Close(&rtmp);
640 RTMP_LogPrintf("done!\n\n");
641
642 quit:
643 if (buffer)
644 {
645 free(buffer);
646 buffer = NULL;
647 }
648
649 if (sockfd)
650 closesocket(sockfd);
651
652 if (server->state == STREAMING_IN_PROGRESS)
653 server->state = STREAMING_ACCEPTING;
654
655 return;
656
657 filenotfound:
658 RTMP_LogPrintf("%s, %s, %s\n", __FUNCTION__, status, filename);
659 len = sprintf(buf, "HTTP/1.0 %s%s\r\n", status, srvhead);
660 send(sockfd, buf, len, 0);
661 goto quit;
662 }
663
664 TFTYPE
serverThread(void * arg)665 serverThread(void *arg)
666 {
667 STREAMING_SERVER *server = arg;
668 server->state = STREAMING_ACCEPTING;
669
670 while (server->state == STREAMING_ACCEPTING)
671 {
672 struct sockaddr_in addr;
673 socklen_t addrlen = sizeof(struct sockaddr_in);
674 int sockfd =
675 accept(server->socket, (struct sockaddr *) &addr, &addrlen);
676
677 if (sockfd > 0)
678 {
679 // Create a new process and transfer the control to that
680 RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__,
681 inet_ntoa(addr.sin_addr));
682 processTCPrequest(server, sockfd);
683 RTMP_Log(RTMP_LOGDEBUG, "%s: processed request\n", __FUNCTION__);
684 }
685 else
686 {
687 RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__);
688 }
689 }
690 server->state = STREAMING_STOPPED;
691 TFRET();
692 }
693
694 STREAMING_SERVER *
startStreaming(const char * address,int port)695 startStreaming(const char *address, int port)
696 {
697 struct sockaddr_in addr;
698 int sockfd;
699 STREAMING_SERVER *server;
700
701 sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
702 if (sockfd == -1)
703 {
704 RTMP_Log(RTMP_LOGERROR, "%s, couldn't create socket", __FUNCTION__);
705 return 0;
706 }
707
708 addr.sin_family = AF_INET;
709 addr.sin_addr.s_addr = inet_addr(address); //htonl(INADDR_ANY);
710 addr.sin_port = htons(port);
711
712 if (bind(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) ==
713 -1)
714 {
715 RTMP_Log(RTMP_LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__,
716 port);
717 return 0;
718 }
719
720 if (listen(sockfd, 10) == -1)
721 {
722 RTMP_Log(RTMP_LOGERROR, "%s, listen failed", __FUNCTION__);
723 closesocket(sockfd);
724 return 0;
725 }
726
727 server = (STREAMING_SERVER *) calloc(1, sizeof(STREAMING_SERVER));
728 server->socket = sockfd;
729
730 ThreadCreate(serverThread, server);
731
732 return server;
733 }
734
735 void
stopStreaming(STREAMING_SERVER * server)736 stopStreaming(STREAMING_SERVER * server)
737 {
738 assert(server);
739
740 if (server->state != STREAMING_STOPPED)
741 {
742 if (server->state == STREAMING_IN_PROGRESS)
743 {
744 server->state = STREAMING_STOPPING;
745
746 // wait for streaming threads to exit
747 while (server->state != STREAMING_STOPPED)
748 msleep(1);
749 }
750
751 if (closesocket(server->socket))
752 RTMP_Log(RTMP_LOGERROR, "%s: Failed to close listening socket, error %d",
753 __FUNCTION__, GetSockError());
754
755 server->state = STREAMING_STOPPED;
756 }
757 }
758
759
760 void
sigIntHandler(int sig)761 sigIntHandler(int sig)
762 {
763 RTMP_ctrlC = TRUE;
764 RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
765 if (httpServer)
766 stopStreaming(httpServer);
767 signal(SIGINT, SIG_DFL);
768 }
769
770 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
hex2bin(char * str,char ** hex)771 int hex2bin(char *str, char **hex)
772 {
773 char *ptr;
774 int i, l = strlen(str);
775
776 if (l & 1)
777 return 0;
778
779 *hex = malloc(l/2);
780 ptr = *hex;
781 if (!ptr)
782 return 0;
783
784 for (i=0; i<l; i+=2)
785 *ptr++ = (HEX2BIN(str[i]) << 4) | HEX2BIN(str[i+1]);
786 return l/2;
787 }
788
789 // this will parse RTMP related options as needed
790 // excludes the following options: h, d, g
791
792 // Return values: true (option parsing ok)
793 // false (option not parsed/invalid)
794 int
ParseOption(char opt,char * arg,RTMP_REQUEST * req)795 ParseOption(char opt, char *arg, RTMP_REQUEST * req)
796 {
797 switch (opt)
798 {
799 #ifdef CRYPTO
800 case 'w':
801 {
802 int res = hex2bin(arg, &req->swfHash.av_val);
803 if (!res || res != RTMP_SWF_HASHLEN)
804 {
805 req->swfHash.av_val = NULL;
806 RTMP_Log(RTMP_LOGWARNING,
807 "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN);
808 }
809 req->swfHash.av_len = RTMP_SWF_HASHLEN;
810 break;
811 }
812 case 'x':
813 {
814 int size = atoi(arg);
815 if (size <= 0)
816 {
817 RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
818 }
819 else
820 {
821 req->swfSize = size;
822 }
823 break;
824 }
825 case 'W':
826 {
827 STR2AVAL(req->swfUrl, arg);
828 req->swfVfy = 1;
829 }
830 break;
831 case 'X':
832 {
833 int num = atoi(arg);
834 if (num < 0)
835 {
836 RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
837 }
838 else
839 {
840 req->swfAge = num;
841 }
842 break;
843 }
844 #endif
845 case 'b':
846 {
847 int32_t bt = atol(arg);
848 if (bt < 0)
849 {
850 RTMP_Log(RTMP_LOGERROR,
851 "Buffer time must be greater than zero, ignoring the specified value %d!",
852 bt);
853 }
854 else
855 {
856 req->bufferTime = bt;
857 }
858 break;
859 }
860 case 'v':
861 req->bLiveStream = TRUE; // no seeking or resuming possible!
862 break;
863 case 'd':
864 STR2AVAL(req->subscribepath, arg);
865 break;
866 case 'n':
867 STR2AVAL(req->hostname, arg);
868 break;
869 case 'c':
870 req->rtmpport = atoi(arg);
871 break;
872 case 'l':
873 {
874 int protocol = atoi(arg);
875 if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS)
876 {
877 RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d, using default",
878 protocol);
879 return FALSE;
880 }
881 else
882 {
883 req->protocol = protocol;
884 }
885 break;
886 }
887 case 'y':
888 STR2AVAL(req->playpath, arg);
889 break;
890 case 'r':
891 {
892 req->rtmpurl = arg;
893
894 AVal parsedHost, parsedPlaypath, parsedApp;
895 unsigned int parsedPort = 0;
896 int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
897
898 if (!RTMP_ParseURL
899 (req->rtmpurl, &parsedProtocol, &parsedHost, &parsedPort,
900 &parsedPlaypath, &parsedApp))
901 {
902 RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!", arg);
903 }
904 else
905 {
906 if (!req->hostname.av_len)
907 req->hostname = parsedHost;
908 if (req->rtmpport == -1)
909 req->rtmpport = parsedPort;
910 if (req->playpath.av_len == 0 && parsedPlaypath.av_len)
911 {
912 req->playpath = parsedPlaypath;
913 }
914 if (req->protocol == RTMP_PROTOCOL_UNDEFINED)
915 req->protocol = parsedProtocol;
916 if (req->app.av_len == 0 && parsedApp.av_len)
917 {
918 req->app = parsedApp;
919 }
920 }
921 break;
922 }
923 case 'i':
924 STR2AVAL(req->fullUrl, arg);
925 break;
926 case 's':
927 STR2AVAL(req->swfUrl, arg);
928 break;
929 case 't':
930 STR2AVAL(req->tcUrl, arg);
931 break;
932 case 'p':
933 STR2AVAL(req->pageUrl, arg);
934 break;
935 case 'a':
936 STR2AVAL(req->app, arg);
937 break;
938 case 'f':
939 STR2AVAL(req->flashVer, arg);
940 break;
941 case 'u':
942 STR2AVAL(req->auth, arg);
943 break;
944 case 'C':
945 parseAMF(&req->extras, arg, &req->edepth);
946 break;
947 case 'm':
948 req->timeout = atoi(arg);
949 break;
950 case 'A':
951 req->dStartOffset = (int)(atof(arg) * 1000.0);
952 //printf("dStartOffset = %d\n", dStartOffset);
953 break;
954 case 'B':
955 req->dStopOffset = (int)(atof(arg) * 1000.0);
956 //printf("dStartOffset = %d\n", dStartOffset);
957 break;
958 case 'T':
959 STR2AVAL(req->token, arg);
960 break;
961 case 'S':
962 STR2AVAL(req->sockshost, arg);
963 case 'q':
964 RTMP_debuglevel = RTMP_LOGCRIT;
965 break;
966 case 'V':
967 RTMP_debuglevel = RTMP_LOGDEBUG;
968 break;
969 case 'z':
970 RTMP_debuglevel = RTMP_LOGALL;
971 break;
972 case 'j':
973 STR2AVAL(req->usherToken, arg);
974 break;
975 default:
976 RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt, arg);
977 return FALSE;
978 }
979 return TRUE;
980 }
981
982 int
main(int argc,char ** argv)983 main(int argc, char **argv)
984 {
985 int nStatus = RD_SUCCESS;
986
987 // http streaming server
988 char DEFAULT_HTTP_STREAMING_DEVICE[] = "0.0.0.0"; // 0.0.0.0 is any device
989
990 char *httpStreamingDevice = DEFAULT_HTTP_STREAMING_DEVICE; // streaming device, default 0.0.0.0
991 int nHttpStreamingPort = 80; // port
992
993 RTMP_LogPrintf("HTTP-RTMP Stream Gateway %s\n", RTMPDUMP_VERSION);
994 RTMP_LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n");
995
996 // init request
997 memset(&defaultRTMPRequest, 0, sizeof(RTMP_REQUEST));
998
999 defaultRTMPRequest.rtmpport = -1;
1000 defaultRTMPRequest.protocol = RTMP_PROTOCOL_UNDEFINED;
1001 defaultRTMPRequest.bLiveStream = FALSE; // is it a live stream? then we can't seek/resume
1002
1003 defaultRTMPRequest.timeout = 120; // timeout connection after 120 seconds
1004 defaultRTMPRequest.bufferTime = 20 * 1000;
1005
1006 defaultRTMPRequest.swfAge = 30;
1007
1008 int opt;
1009 struct option longopts[] = {
1010 {"help", 0, NULL, 'h'},
1011 {"url", 1, NULL, 'i'},
1012 {"host", 1, NULL, 'n'},
1013 {"port", 1, NULL, 'c'},
1014 {"socks", 1, NULL, 'S'},
1015 {"protocol", 1, NULL, 'l'},
1016 {"playpath", 1, NULL, 'y'},
1017 {"rtmp", 1, NULL, 'r'},
1018 {"swfUrl", 1, NULL, 's'},
1019 {"tcUrl", 1, NULL, 't'},
1020 {"pageUrl", 1, NULL, 'p'},
1021 {"app", 1, NULL, 'a'},
1022 #ifdef CRYPTO
1023 {"swfhash", 1, NULL, 'w'},
1024 {"swfsize", 1, NULL, 'x'},
1025 {"swfVfy", 1, NULL, 'W'},
1026 {"swfAge", 1, NULL, 'X'},
1027 #endif
1028 {"auth", 1, NULL, 'u'},
1029 {"conn", 1, NULL, 'C'},
1030 {"flashVer", 1, NULL, 'f'},
1031 {"live", 0, NULL, 'v'},
1032 //{"flv", 1, NULL, 'o'},
1033 //{"resume", 0, NULL, 'e'},
1034 {"timeout", 1, NULL, 'm'},
1035 {"buffer", 1, NULL, 'b'},
1036 //{"skip", 1, NULL, 'k'},
1037 {"device", 1, NULL, 'D'},
1038 {"sport", 1, NULL, 'g'},
1039 {"subscribe", 1, NULL, 'd'},
1040 {"start", 1, NULL, 'A'},
1041 {"stop", 1, NULL, 'B'},
1042 {"token", 1, NULL, 'T'},
1043 {"debug", 0, NULL, 'z'},
1044 {"quiet", 0, NULL, 'q'},
1045 {"verbose", 0, NULL, 'V'},
1046 {"jtv", 1, NULL, 'j'},
1047 {0, 0, 0, 0}
1048 };
1049
1050 signal(SIGINT, sigIntHandler);
1051 #ifndef WIN32
1052 signal(SIGPIPE, SIG_IGN);
1053 #endif
1054
1055 InitSockets();
1056
1057 while ((opt =
1058 getopt_long(argc, argv,
1059 "hvqVzr:s:t:i:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:", longopts,
1060 NULL)) != -1)
1061 {
1062 switch (opt)
1063 {
1064 case 'h':
1065 RTMP_LogPrintf
1066 ("\nThis program serves media content streamed from RTMP onto HTTP.\n\n");
1067 RTMP_LogPrintf("--help|-h Prints this help screen.\n");
1068 RTMP_LogPrintf
1069 ("--url|-i url URL with options included (e.g. rtmp://host[:port]/path swfUrl=url tcUrl=url)\n");
1070 RTMP_LogPrintf
1071 ("--rtmp|-r url URL (e.g. rtmp://host[:port]/path)\n");
1072 RTMP_LogPrintf
1073 ("--host|-n hostname Overrides the hostname in the rtmp url\n");
1074 RTMP_LogPrintf
1075 ("--port|-c port Overrides the port in the rtmp url\n");
1076 RTMP_LogPrintf
1077 ("--socks|-S host:port Use the specified SOCKS proxy\n");
1078 RTMP_LogPrintf
1079 ("--protocol|-l Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
1080 RTMP_LogPrintf
1081 ("--playpath|-y Overrides the playpath parsed from rtmp url\n");
1082 RTMP_LogPrintf("--swfUrl|-s url URL to player swf file\n");
1083 RTMP_LogPrintf
1084 ("--tcUrl|-t url URL to played stream (default: \"rtmp://host[:port]/app\")\n");
1085 RTMP_LogPrintf("--pageUrl|-p url Web URL of played programme\n");
1086 RTMP_LogPrintf("--app|-a app Name of target app in server\n");
1087 #ifdef CRYPTO
1088 RTMP_LogPrintf
1089 ("--swfhash|-w hexstring SHA256 hash of the decompressed SWF file (32 bytes)\n");
1090 RTMP_LogPrintf
1091 ("--swfsize|-x num Size of the decompressed SWF file, required for SWFVerification\n");
1092 RTMP_LogPrintf
1093 ("--swfVfy|-W url URL to player swf file, compute hash/size automatically\n");
1094 RTMP_LogPrintf
1095 ("--swfAge|-X days Number of days to use cached SWF hash before refreshing\n");
1096 #endif
1097 RTMP_LogPrintf
1098 ("--auth|-u string Authentication string to be appended to the connect string\n");
1099 RTMP_LogPrintf
1100 ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n");
1101 RTMP_LogPrintf
1102 (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
1103 RTMP_LogPrintf
1104 (" Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
1105 RTMP_LogPrintf
1106 ("--flashVer|-f string Flash version string (default: \"%s\")\n",
1107 RTMP_DefaultFlashVer.av_val);
1108 RTMP_LogPrintf
1109 ("--live|-v Get a live stream, no --resume (seeking) of live streams possible\n");
1110 RTMP_LogPrintf
1111 ("--subscribe|-d string Stream name to subscribe to (otherwise defaults to playpath if live is specified)\n");
1112 RTMP_LogPrintf
1113 ("--timeout|-m num Timeout connection num seconds (default: %lu)\n",
1114 defaultRTMPRequest.timeout);
1115 RTMP_LogPrintf
1116 ("--start|-A num Start at num seconds into stream (not valid when using --live)\n");
1117 RTMP_LogPrintf
1118 ("--stop|-B num Stop at num seconds into stream\n");
1119 RTMP_LogPrintf
1120 ("--token|-T key Key for SecureToken response\n");
1121 RTMP_LogPrintf
1122 ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
1123 RTMP_LogPrintf
1124 ("--buffer|-b Buffer time in milliseconds (default: %u)\n\n",
1125 defaultRTMPRequest.bufferTime);
1126
1127 RTMP_LogPrintf
1128 ("--device|-D Streaming device ip address (default: %s)\n",
1129 DEFAULT_HTTP_STREAMING_DEVICE);
1130 RTMP_LogPrintf
1131 ("--sport|-g Streaming port (default: %d)\n\n",
1132 nHttpStreamingPort);
1133 RTMP_LogPrintf
1134 ("--quiet|-q Suppresses all command output.\n");
1135 RTMP_LogPrintf("--verbose|-V Verbose command output.\n");
1136 RTMP_LogPrintf("--debug|-z Debug level command output.\n");
1137 RTMP_LogPrintf
1138 ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
1139 RTMP_LogPrintf("packet.\n\n");
1140 return RD_SUCCESS;
1141 break;
1142 // streaming server specific options
1143 case 'D':
1144 if (inet_addr(optarg) == INADDR_NONE)
1145 {
1146 RTMP_Log(RTMP_LOGERROR,
1147 "Invalid binding address (requested address %s), ignoring",
1148 optarg);
1149 }
1150 else
1151 {
1152 httpStreamingDevice = optarg;
1153 }
1154 break;
1155 case 'g':
1156 {
1157 int port = atoi(optarg);
1158 if (port < 0 || port > 65535)
1159 {
1160 RTMP_Log(RTMP_LOGERROR,
1161 "Streaming port out of range (requested port %d), ignoring\n",
1162 port);
1163 }
1164 else
1165 {
1166 nHttpStreamingPort = port;
1167 }
1168 break;
1169 }
1170 default:
1171 //RTMP_LogPrintf("unknown option: %c\n", opt);
1172 if (!ParseOption(opt, optarg, &defaultRTMPRequest))
1173 return RD_FAILED;
1174 break;
1175 }
1176 }
1177
1178 #ifdef _DEBUG
1179 netstackdump = fopen("netstackdump", "wb");
1180 netstackdump_read = fopen("netstackdump_read", "wb");
1181 #endif
1182
1183 // start text UI
1184 ThreadCreate(controlServerThread, 0);
1185
1186 // start http streaming
1187 if ((httpServer =
1188 startStreaming(httpStreamingDevice, nHttpStreamingPort)) == 0)
1189 {
1190 RTMP_Log(RTMP_LOGERROR, "Failed to start HTTP server, exiting!");
1191 return RD_FAILED;
1192 }
1193 RTMP_LogPrintf("Streaming on http://%s:%d\n", httpStreamingDevice,
1194 nHttpStreamingPort);
1195
1196 while (httpServer->state != STREAMING_STOPPED)
1197 {
1198 sleep(1);
1199 }
1200 RTMP_Log(RTMP_LOGDEBUG, "Done, exiting...");
1201
1202 CleanupSockets();
1203
1204 #ifdef _DEBUG
1205 if (netstackdump != 0)
1206 fclose(netstackdump);
1207 if (netstackdump_read != 0)
1208 fclose(netstackdump_read);
1209 #endif
1210 return nStatus;
1211 }
1212