diff options
author | Daniel Stenberg <daniel@haxx.se> | 2010-01-21 13:58:30 +0000 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2010-01-21 13:58:30 +0000 |
commit | bc4582b68a673d3b0f5a2e7d971605de2c8b3730 (patch) | |
tree | 09897ee9c051870d0be56108d41f6595d41e932d | |
parent | e09718d457f5ba512920c9ed0a0db5c8ca6dcc53 (diff) | |
download | curl-bc4582b68a673d3b0f5a2e7d971605de2c8b3730.tar.gz |
Chris Conroy brought support for RTSP transfers, and with it comes 8(!) new
libcurl options for controlling what to get and how to receive posssibly
interleaved RTP data. Initial commit.
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | RELEASE-NOTES | 7 | ||||
-rw-r--r-- | configure.ac | 27 | ||||
-rw-r--r-- | docs/libcurl/curl_easy_getinfo.3 | 22 | ||||
-rw-r--r-- | docs/libcurl/curl_easy_setopt.3 | 145 | ||||
-rw-r--r-- | docs/libcurl/libcurl.m4 | 3 | ||||
-rw-r--r-- | include/curl/curl.h | 60 | ||||
-rw-r--r-- | lib/Makefile.inc | 4 | ||||
-rw-r--r-- | lib/getinfo.c | 15 | ||||
-rw-r--r-- | lib/http.c | 429 | ||||
-rw-r--r-- | lib/http.h | 32 | ||||
-rw-r--r-- | lib/rtsp.c | 753 | ||||
-rw-r--r-- | lib/rtsp.h | 77 | ||||
-rw-r--r-- | lib/sendf.c | 3 | ||||
-rw-r--r-- | lib/sendf.h | 8 | ||||
-rw-r--r-- | lib/setup.h | 1 | ||||
-rw-r--r-- | lib/strerror.c | 6 | ||||
-rw-r--r-- | lib/transfer.c | 33 | ||||
-rw-r--r-- | lib/url.c | 161 | ||||
-rw-r--r-- | lib/urldata.h | 47 | ||||
-rw-r--r-- | lib/version.c | 5 |
21 files changed, 1628 insertions, 215 deletions
@@ -6,6 +6,11 @@ Changelog +Daniel Stenberg (21 Jan 2010) +- Chris Conroy brought support for RTSP transfers, and with it comes 8(!) new + libcurl options for controlling what to get and how to receive posssibly + interleaved RTP data. + Daniel Stenberg (20 Jan 2010) - As was pointed out on the http-state mailing list, the order of cookies in a HTTP Cookie: header _needs_ to be sorted on the path length in the cases diff --git a/RELEASE-NOTES b/RELEASE-NOTES index e4abf5fa4..8eb4e6643 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -2,7 +2,7 @@ Curl and libcurl 7.20.0 Public curl releases: 114 Command line options: 135 - curl_easy_setopt() options: 166 + curl_easy_setopt() options: 174 Public functions in libcurl: 58 Known libcurl bindings: 39 Contributors: 761 @@ -13,7 +13,8 @@ This release includes the following changes: o curl-config can now show the arguments used when building curl o non-blocking TFTP o send Expect: 100-continue for POSTs with unknown sizes - o added support for IMAP, POP3 and SMTP transfers + o added support for IMAP(S), POP3(S), SMTP(S) and RTSP + o added new curl_easy_setopt() options for SMTP and RTSP o added --mail-from and --mail-rcpt for SMTP o VMS build system enhancements o added support for the PRET ftp command @@ -60,6 +61,6 @@ advice from friends like these: Marc Kleine-Budde, Jad Chamcham, Bjorn Augustsson, David Byron, Markus Koetter, Chad Monroe, Martin Storsjo, Siegfried Gyuricsko, Jon Nelson, Julien Chaffraix, Renato Botelho, Peter Pentchev, Ingmar Runge, - Johan van Selst, Charles Kerr, Gil Weber, David McCreedy + Johan van Selst, Charles Kerr, Gil Weber, David McCreedy, Chris Conroy Thanks! (and sorry if I forgot to mention someone) diff --git a/configure.ac b/configure.ac index 1366eee5e..d240ddb55 100644 --- a/configure.ac +++ b/configure.ac @@ -135,6 +135,7 @@ curl_verbose_msg="enabled (--disable-verbose)" curl_sspi_msg="no (--enable-sspi)" curl_ldap_msg="no (--enable-ldap / --with-ldap-lib / --with-lber-lib)" curl_ldaps_msg="no (--enable-ldaps)" + curl_rtsp_msg="no (--enable-rtsp)" dnl dnl Save anything in $LIBS for later @@ -416,6 +417,28 @@ AC_HELP_STRING([--disable-ldaps],[Disable LDAPS support]), AC_SUBST(CURL_DISABLE_LDAPS, [1]) ) +AC_MSG_CHECKING([whether to support rtsp]) +AC_ARG_ENABLE(rtsp, +AC_HELP_STRING([--enable-rtsp],[Enable RTSP support]) +AC_HELP_STRING([--disable-rtsp],[Disable RTSP support]), +[ case "$enableval" in + no) + AC_MSG_RESULT(no) + AC_DEFINE(CURL_DISABLE_RTSP, 1, [to disable RTSP]) + AC_SUBST(CURL_DISABLE_RTSP, [1]) + ;; + *) if test x$CURL_DISABLE_HTTP = x1 ; then + AC_MSG_ERROR(HTTP support needs to be enabled in order to enable RTSP support!) + else + AC_MSG_RESULT(yes) + curl_rtsp_msg="enabled" + fi + ;; + esac ], + AC_MSG_RESULT(no) + curl_rtsp_msg="disabled" +) + AC_MSG_CHECKING([whether to support proxies]) AC_ARG_ENABLE(proxy, AC_HELP_STRING([--enable-proxy],[Enable proxy support]) @@ -2579,6 +2602,9 @@ if test "x$USE_LIBSSH2" = "x1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SCP" SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SFTP" fi +if test "x$CURL_DISABLE_RTSP" != "x1"; then + SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS RTSP" +fi AC_SUBST(SUPPORT_PROTOCOLS) @@ -2653,6 +2679,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: ca cert path: ${capath} LDAP support: ${curl_ldap_msg} LDAPS support: ${curl_ldaps_msg} + RTSP support: ${curl_rtsp_msg} Protocols: ${SUPPORT_PROTOCOLS} ]) diff --git a/docs/libcurl/curl_easy_getinfo.3 b/docs/libcurl/curl_easy_getinfo.3 index e227f0790..91fb9a760 100644 --- a/docs/libcurl/curl_easy_getinfo.3 +++ b/docs/libcurl/curl_easy_getinfo.3 @@ -209,6 +209,28 @@ the previous request didn't match (see \fICURLOPT_TIMECONDITION\fP). Alas, if this returns a 1 you know that the reason you didn't get data in return is because it didn't fulfill the condition. The long ths argument points to will get a zero stored if the condition instead was met. (Added in 7.19.4) +.IP CURLINFO_RTSP_SESSION_ID +Pass a pointer to a char pointer to receive a pointer to a string holding the +most recent RTSP Session ID. + +Applications wishing to resume an RTSP session on another connection should +retreive this info before closing the active connection. +.IP CURLINFO_RTSP_CLIENT_CSEQ +Pass a pointer to a long to receive the next CSeq that will be used by the +application. +.IP CURLINFO_RTSP_SERVER_CSEQ +Pass a pointer to a long to receive the next server CSeq that will be expected +by the application. + +\fI(NOTE: listening for server initiated requests is currently +unimplemented).\fP + +Applications wishing to resume an RTSP session on another connection should +retreive this info before closing the active connection. +.IP CURLINFO_RTSP_CSEQ_RECV +Pass a pointer to a long to receive the most recently received CSeq from the +server. If your application encounters a \fICURLE_RTSP_CSEQ_ERROR\fP then you +may wish to troubleshoot and/or fix the CSeq mismatch by peeking at this value. .SH TIMES .nf An overview of the six time values available from curl_easy_getinfo() diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index dd1a38ae4..f577f5cd0 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -401,6 +401,34 @@ follows: You will need to override these definitions if they are different on your system. +.IP CURLOPT_RTPFUNCTION +Function pointer that should match the following prototype: \fIsize_t +function( void *ptr, size_t size, size_t nmemb, void *stream)\fP. This +function gets called by libcurl as soon as it has received interleaved RTP +data. This function gets called for each $ block and therefore contains +exactly one upper-layer protocol unit (e.g. one RTP packet). Curl writes the +interleaved header as well as the included data for each call. The first byte +is always an ASCII dollar sign. The dollar sign is followed by a one byte +channel identifier and then a 2 byte integer length in network byte order. See +\fIRFC 2326 Section 10.12\fP for more information on how RTP interleaving +behaves. + +Interleaved RTP poses some challeneges for the client application. Since the +stream data is sharing the RTSP control connection, it is critical to service +the RTP in a timely fashion. If the RTP data is not handled quickly, +subsequent response processing may become unreasonably delayed and the +connection may close. The application may use \fICURL_RTSPREQ_RECEIVE\fP to +service RTP data when no requests are desired. If the application makes a +request, (e.g. \fICURL_RTSPREQ_PAUSE\fP) then the response handler will +process any pending RTP data before marking the request as finished. (Added +in 7.20.0) +.IP CURLOPT_RTPDATA +This is the stream that will be passed to \fICURLOPT_RTPFUNCTION\fP when +interleaved RTP data is received. Since the application is required to provide +a custom function for RTP data, there is no requirement that the stream +pointer be a valid FILE pointer. An application may wish to pass a cookie +containing information about many streams to assist in demultiplexing the +different RTP channels. (Added in 7.20.0) .SH ERROR OPTIONS .IP CURLOPT_ERRORBUFFER Pass a char * to a buffer that the libcurl may store human readable error @@ -1275,6 +1303,107 @@ file \&"normally" (like in the multicwd case). This is somewhat more standards compliant than 'nocwd' but without the full penalty of 'multicwd'. .RE (Added in 7.15.1) +.SH RTSP OPTIONS +.IP CURLOPT_RTSP_REQUEST +Tell libcurl what kind of RTSP request to make. Pass one of the following RTSP +enum values. Unless noted otherwise, commands require the Session ID to be +initialized. (Added in 7.20.0) +.RS +.IP CURL_RTSPREQ_OPTIONS +Used to retrieve the available methods of the server. The application is +responsbile for parsing and obeying the response. \fB(The session ID is not +needed for this method.)\fP (Added in 7.20.0) +.IP CURL_RTSPREQ_DESCRIBE +Used to get the low level description of a stream. The application should note +what formats it understands in the \fI'Accept:'\fP header. Unless set +manually, libcurl will automatically fill in \fI'Accept: +application/sdp'\fP. Time-condition headers will be added to Describe requests +if the \fICURLOPT_TIMECONDITION\fP option is active. \fB(The session ID is not +needed for this method)\fP (Added in 7.20.0) +.IP CURL_RTSPREQ_ANNOUNCE +When sent by a client, this method changes the description of the session. For +example, if a client is using the server to record a meeting, the client can +use Announce to inform the server of all the meta-information about the +session. ANNOUNCE acts like an HTTP PUT or POST just like +\fICURL_RTSPREQ_SET_PARAMETER\fP (Added in 7.20.0) +.IP CURL_RTSPREQ_SETUP +Setup is used to initialize the transport layer for the session. The +application must set the desired Transport options for a session by using the +\fICURLOPT_RTSP_TRANSPORT\fP option prior to calling setup. If no session ID +is currently set with \fICURLOPT_RTSP_SESSION_ID\fP, libcurl will extract and +use the session ID in the response to this request. \fB(The session ID is not +needed for this method).\fP (Added in 7.20.0) +.IP CURL_RTSPREQ_PLAY +Send a Play command to the server. Use the \fICURLOPT_RANGE\fP option to +modify the playback time (e.g. 'npt=10-15'). (Added in 7.20.0) +.IP CURL_RTSPREQ_PAUSE +Send a Pause command to the server. Use the \fICURLOPT_RANGE\fP option with a +single value to indicate when the stream should be halted. (e.g. npt='25') +(Added in 7.20.0) +.IP CURL_RTSPREQ_TEARDOWN +This command terminates an RTSP session. Note that simply closing a connection +does not terminate the RTSP session since it is valid to control an RTSP +session over different connections. (Added in 7.20.0) +.IP CURL_RTSPREQ_GET_PARAMETER +Retrieve a parameter from the server. By default, libcurl will automatically +include an \fIAccept: text/parameters\fP header unless a custom one is set. +Applications wishing to send a heartbeat message (e.g. in the presence of a +server-specified timeout) should send use an empty GET_PARAMETER request. +(Added in 7.20.0) +.IP CURL_RTSPREQ_SET_PARAMETER +Set a parameter on the server. By default, libcurl will automatically include +a \fIContent-Type: text/parameters\fP header unless a custom one is set. The +interaction with SET_PARAMTER is much like an HTTP PUT or POST. An application +may either use \fICURLOPT_UPLOAD\fP with \fICURLOPT_READDATA\fP like an HTTP +PUT, or it may use \fICURLOPT_POSTFIELDS\fP like an HTTP POST. Note that no +chunked transfers are allowed, so the application must set the +\fICURLOPT_INFILESIZE\fP in the former and \fICURLOPT_POSTFIELDSIZE\fP in the +latter. Also, there is no use of multi-part POSTs within RTSP. (Added in +7.20.0) +.IP CURL_RTSPREQ_RECORD +Used to tell the server to record a session. Use the \fICURLOPT_RANGE\fP +option to modify the record time. (Added in 7.20.0) +.IP CURL_RTSPREQ_RECEIVE +This is a special request because it does not send any data to the server. The +application may call this function in order to receive interleaved RTP +data. It will return after processing one read buffer of data in order to give +the application a chance to run. (Added in 7.20.0) +.RE +.IP CURLOPT_RTSP_SESSION_ID +Pass a char * as a parameter to set the value of the current RTSP Session ID +for the handle. Useful for resuming an in-progress session. Once this value is +set to any non-NULL value, libcurl will return \fICURLE_RTSP_SESSION_ERROR\fP +if ID received from the server does not match. If unset (or set to NULL), +libcurl will automatically set the ID the first time the server sets it in a +response. (Added in 7.20.0) +.IP CURLOPT_RTSP_STREAM_URI +Set the stream URI to operate on by passing a char * . For example, a single +session may be controlling \fIrtsp://foo/twister/audio\fP and +\fIrtsp://foo/twister/video\fP and the application can switch to the +appropriate stream using this option. If unset, libcurl will default to +operating on generic server options by passing '*' in the place of the RTSP +Stream URI. Note that this option is distinct from \fICURLOPT_URL\fP. When +working with RTSP, the \fICURLOPT_STREAM_URI\fP indicates what URL to send to +the server in the request header while the \fICURLOPT_URL\fP indicates where +to make the connection to. (e.g. the \fICURLOPT_URL\fP for the above examples +might be set to \fIrtsp://foo/twister\fP (Added in 7.20.0) +.IP CURLOPT_RTSP_TRANSPORT +Pass a char * to tell libcurl what to pass for the Transport: header for this +RTSP session. This is mainly a convenience method to avoid needing to set a +custom Transport: header for every SETUP request. The application must set a +Transport: header before issuing a SETUP request. (Added in 7.20.0) +.IP CURLOPT_RTSP_HEADER +This option is simply an alias for \fICURLOPT_HTTP_HEADER\fP. Use this to +replace the standard headers that RTSP and HTTP share. It is also valid to use +the shortcuts such as \fICURLOPT_USERAGENT\fP. (Added in 7.20.0) +.IP CURLOPT_RTSP_CLIENT_CSEQ +Manually set the the CSEQ number to issue for the next RTSP request. Useful if +the application is resuming a previously broken connection. The CSEQ will +increment from this new number henceforth. (Added in 7.20.0) +.IP CURLOPT_RTSP_SERVER_CSEQ +Manually set the CSEQ number to expect for the next RTSP Server->Client +request. At the moment, this feature (listening for Server requests) is +unimplemented. (Added in 7.20.0) .SH PROTOCOL OPTIONS .IP CURLOPT_TRANSFERTEXT A parameter set to 1 tells the library to use ASCII mode for FTP transfers, @@ -1301,9 +1430,14 @@ want. It should be in the format "X-Y", where X or Y may be left out. HTTP transfers also support several intervals, separated with commas as in \fI"X-Y,N-M"\fP. Using this kind of multiple intervals will cause the HTTP server to send the response document in pieces (using standard MIME separation -techniques). Pass a NULL to this option to disable the use of ranges. +techniques). For RTSP, the formatting of a range should follow RFC 2326 Section +12.29. Note that for RTSP, byte ranges are \fBnot\fP permitted. Instead, ranges +should be given in npt, utc, or smpte formats. + +Pass a NULL to this option to disable the use of ranges. -Ranges work on HTTP, FTP and FILE (since 7.18.0) transfers only. +Ranges work on HTTP, FTP, FILE (since 7.18.0), and RTSP (since 7.20.0) +transfers only. .IP CURLOPT_RESUME_FROM Pass a long as parameter. It contains the offset in number of bytes that you want the transfer to start from. Set this option to 0 to make the transfer @@ -1408,7 +1542,8 @@ given limit. This concerns both FTP and HTTP transfers. .IP CURLOPT_TIMECONDITION Pass a long as parameter. This defines how the \fICURLOPT_TIMEVALUE\fP time value is treated. You can set this parameter to \fICURL_TIMECOND_IFMODSINCE\fP -or \fICURL_TIMECOND_IFUNMODSINCE\fP. This feature applies to HTTP and FTP. +or \fICURL_TIMECOND_IFUNMODSINCE\fP. This feature applies to HTTP, FTP, and +RTSP. The last modification time of a file is not always known and in such instances this feature will have no effect even if the given time condition would not @@ -1629,8 +1764,8 @@ Pass a char * to a zero terminated string naming a file holding a CA certificate in PEM format. If the option is set, an additional check against the peer certificate is performed to verify the issuer is indeed the one associated with the certificate provided by the option. This additional check -is useful in multi-level PKI where one needs to enforce that the peer certificate is -from a specific branch of the tree. +is useful in multi-level PKI where one needs to enforce that the peer +certificate is from a specific branch of the tree. This option makes sense only when used in combination with the \fICURLOPT_SSL_VERIFYPEER\fP option. Otherwise, the result of the check is not diff --git a/docs/libcurl/libcurl.m4 b/docs/libcurl/libcurl.m4 index f54a5f2d1..74299b8fa 100644 --- a/docs/libcurl/libcurl.m4 +++ b/docs/libcurl/libcurl.m4 @@ -55,6 +55,7 @@ AC_DEFUN([LIBCURL_CHECK_CONFIG], AH_TEMPLATE([LIBCURL_PROTOCOL_LDAP],[Defined if libcurl supports LDAP]) AH_TEMPLATE([LIBCURL_PROTOCOL_DICT],[Defined if libcurl supports DICT]) AH_TEMPLATE([LIBCURL_PROTOCOL_TFTP],[Defined if libcurl supports TFTP]) + AH_TEMPLATE([LIBCURL_PROTOCOL_RTSP],[Defined if libcurl supports RTSP]) AC_ARG_WITH(libcurl, AC_HELP_STRING([--with-libcurl=DIR],[look for the curl library in DIR]), @@ -194,7 +195,7 @@ x=CURLOPT_VERBOSE; # We don't have --protocols, so just assume that all # protocols are available - _libcurl_protocols="HTTP FTP FILE TELNET LDAP DICT" + _libcurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP RTSP" if test x$libcurl_feature_SSL = xyes ; then _libcurl_protocols="$_libcurl_protocols HTTPS" diff --git a/include/curl/curl.h b/include/curl/curl.h index 3d31086c3..5fb53e32a 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -197,6 +197,12 @@ typedef size_t (*curl_write_callback)(char *buffer, size_t nitems, void *outstream); +typedef size_t (*curl_rtp_write_callback)(char *buffer, + size_t size, + size_t nitems, + void *outstream, + int channel); + /* These are the return codes for the seek callbacks */ #define CURL_SEEKFUNC_OK 0 #define CURL_SEEKFUNC_FAIL 1 /* fail the entire transfer */ @@ -406,6 +412,9 @@ typedef enum { CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in 7.19.0) */ CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */ + CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */ + CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Identifiers */ + CURL_LAST /* never use! */ } CURLcode; @@ -618,6 +627,7 @@ typedef enum { #define CURLPROTO_POP3S (1<<15) #define CURLPROTO_SMTP (1<<16) #define CURLPROTO_SMTPS (1<<17) +#define CURLPROTO_RTSP (1<<18) #define CURLPROTO_ALL (~0) /* enable everything */ /* long may be 32 or 64 bits, but we should never depend on anything else @@ -1287,6 +1297,30 @@ typedef enum { /* FTP: send PRET before PASV */ CINIT(FTP_USE_PRET, LONG, 188), + /* RTSP request method (OPTIONS, SETUP, PLAY, etc...) */ + CINIT(RTSP_REQUEST, LONG, 189), + + /* The RTSP session identifier */ + CINIT(RTSP_SESSION_ID, OBJECTPOINT, 190), + + /* The RTSP stream URI */ + CINIT(RTSP_STREAM_URI, OBJECTPOINT, 191), + + /* The Transport: header to use in RTSP requests */ + CINIT(RTSP_TRANSPORT, OBJECTPOINT, 192), + + /* Manually initialize the client RTSP CSeq for this handle */ + CINIT(RTSP_CLIENT_CSEQ, LONG, 193), + + /* Manually initialize the server RTSP CSeq for this handle */ + CINIT(RTSP_SERVER_CSEQ, LONG, 194), + + /* The stream to pass to RTPFUNCTION. */ + CINIT(RTPDATA, OBJECTPOINT, 195), + + /* Let the application define a custom write method for RTP data */ + CINIT(RTPFUNCTION, FUNCTIONPOINT, 196), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -1330,6 +1364,7 @@ typedef enum { #define CURLOPT_WRITEDATA CURLOPT_FILE #define CURLOPT_READDATA CURLOPT_INFILE #define CURLOPT_HEADERDATA CURLOPT_WRITEHEADER +#define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ enum { @@ -1342,6 +1377,25 @@ enum { CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ }; +/* + * Public API enums for RTSP requests + */ +enum { + CURL_RTSPREQ_NONE, /* first in list */ + CURL_RTSPREQ_OPTIONS, + CURL_RTSPREQ_DESCRIBE, + CURL_RTSPREQ_ANNOUNCE, + CURL_RTSPREQ_SETUP, + CURL_RTSPREQ_PLAY, + CURL_RTSPREQ_PAUSE, + CURL_RTSPREQ_TEARDOWN, + CURL_RTSPREQ_GET_PARAMETER, + CURL_RTSPREQ_SET_PARAMETER, + CURL_RTSPREQ_RECORD, + CURL_RTSPREQ_RECEIVE, + CURL_RTSPREQ_LAST /* last in list */ +}; + /* These enums are for use with the CURLOPT_NETRC option. */ enum CURL_NETRC_OPTION { CURL_NETRC_IGNORED, /* The .netrc will never be read. @@ -1711,9 +1765,13 @@ typedef enum { CURLINFO_APPCONNECT_TIME = CURLINFO_DOUBLE + 33, CURLINFO_CERTINFO = CURLINFO_SLIST + 34, CURLINFO_CONDITION_UNMET = CURLINFO_LONG + 35, + CURLINFO_RTSP_SESSION_ID = CURLINFO_STRING + 36, + CURLINFO_RTSP_CLIENT_CSEQ = CURLINFO_LONG + 37, + CURLINFO_RTSP_SERVER_CSEQ = CURLINFO_LONG + 38, + CURLINFO_RTSP_CSEQ_RECV = CURLINFO_LONG + 39, /* Fill in new entries below here! */ - CURLINFO_LASTONE = 35 + CURLINFO_LASTONE = 39 } CURLINFO; /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as diff --git a/lib/Makefile.inc b/lib/Makefile.inc index ff9269e63..b7b10d3e1 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -11,7 +11,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ inet_ntop.c parsedate.c select.c gtls.c sslgen.c tftp.c splay.c \ strdup.c socks.c ssh.c nss.c qssl.c rawstr.c curl_addrinfo.c \ socks_gssapi.c socks_sspi.c curl_sspi.c slist.c nonblock.c \ - curl_memrchr.c imap.c pop3.c smtp.c pingpong.c + curl_memrchr.c imap.c pop3.c smtp.c pingpong.c rtsp.c HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ @@ -23,4 +23,4 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \ transfer.h select.h easyif.h multiif.h parsedate.h sslgen.h gtls.h \ tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h ssh.h nssg.h \ curl_base64.h rawstr.h curl_addrinfo.h curl_sspi.h slist.h nonblock.h \ - curl_memrchr.h imap.h pop3.h smtp.h pingpong.h + curl_memrchr.h imap.h pop3.h smtp.h pingpong.h rtsp.h diff --git a/lib/getinfo.c b/lib/getinfo.c index be88f5091..f295a503e 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -235,6 +235,19 @@ CURLcode Curl_getinfo(struct SessionHandle *data, CURLINFO info, ...) /* return if the condition prevented the document to get transfered */ *param_longp = data->info.timecond; break; + case CURLINFO_RTSP_SESSION_ID: + *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; + break; + case CURLINFO_RTSP_CLIENT_CSEQ: + *param_longp = data->state.rtsp_next_client_CSeq; + break; + case CURLINFO_RTSP_SERVER_CSEQ: + *param_longp = data->state.rtsp_next_server_CSeq; + break; + case CURLINFO_RTSP_CSEQ_RECV: + *param_longp = data->state.rtsp_CSeq_recv; + break; + default: return CURLE_BAD_FUNCTION_ARGUMENT; } diff --git a/lib/http.c b/lib/http.c index 2cc1154fc..deade9183 100644 --- a/lib/http.c +++ b/lib/http.c @@ -98,6 +98,7 @@ #include "multiif.h" #include "rawstr.h" #include "content_encoding.h" +#include "rtsp.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -173,7 +174,7 @@ const struct Curl_handler Curl_handler_https = { * * Returns a pointer to the first matching header or NULL if none matched. */ -static char *checkheaders(struct SessionHandle *data, const char *thisheader) +char *Curl_checkheaders(struct SessionHandle *data, const char *thisheader) { struct curl_slist *head; size_t thislen = strlen(thisheader); @@ -565,9 +566,9 @@ output_auth_headers(struct connectdata *conn, if(authstatus->picked == CURLAUTH_BASIC) { /* Basic */ if((proxy && conn->bits.proxy_user_passwd && - !checkheaders(data, "Proxy-authorization:")) || + !Curl_checkheaders(data, "Proxy-authorization:")) || (!proxy && conn->bits.user_passwd && - !checkheaders(data, "Authorization:"))) { + !Curl_checkheaders(data, "Authorization:"))) { auth="Basic"; result = http_output_basic(conn, proxy); if(result) @@ -960,41 +961,23 @@ static size_t readmoredata(char *buffer, } /* ------------------------------------------------------------------------- */ -/* - * The add_buffer series of functions are used to build one large memory chunk - * from repeated function invokes. Used so that the entire HTTP request can - * be sent in one go. - */ - -struct send_buffer { - char *buffer; - size_t size_max; - size_t size_used; -}; -typedef struct send_buffer send_buffer; - -static CURLcode add_custom_headers(struct connectdata *conn, - send_buffer *req_buffer); -static CURLcode - add_buffer(send_buffer *in, const void *inptr, size_t size); +/* add_buffer functions */ /* - * add_buffer_init() sets up and returns a fine buffer struct + * Curl_add_buffer_init() sets up and returns a fine buffer struct */ -static -send_buffer *add_buffer_init(void) +Curl_send_buffer *Curl_add_buffer_init(void) { - return calloc(1, sizeof(send_buffer)); + return calloc(1, sizeof(Curl_send_buffer)); } /* - * add_buffer_send() sends a header buffer and frees all associated memory. + * Curl_add_buffer_send() sends a header buffer and frees all associated memory. * Body data may be appended to the header data if desired. * * Returns CURLcode */ -static -CURLcode add_buffer_send(send_buffer *in, +CURLcode Curl_add_buffer_send(Curl_send_buffer *in, struct connectdata *conn, long *bytes_written, /* add the number of sent bytes to this counter */ @@ -1144,8 +1127,7 @@ CURLcode add_buffer_send(send_buffer *in, /* * add_bufferf() add the formatted input to the buffer. */ -static -CURLcode add_bufferf(send_buffer *in, const char *fmt, ...) +CURLcode Curl_add_bufferf(Curl_send_buffer *in, const char *fmt, ...) { char *s; va_list ap; @@ -1154,7 +1136,7 @@ CURLcode add_bufferf(send_buffer *in, const char *fmt, ...) va_end(ap); if(s) { - CURLcode result = add_buffer(in, s, strlen(s)); + CURLcode result = Curl_add_buffer(in, s, strlen(s)); free(s); return result; } @@ -1168,8 +1150,7 @@ CURLcode add_bufferf(send_buffer *in, const char *fmt, ...) /* * add_buffer() appends a memory chunk to the existing buffer */ -static -CURLcode add_buffer(send_buffer *in, const void *inptr, size_t size) +CURLcode Curl_add_buffer(Curl_send_buffer *in, const void *inptr, size_t size) { char *new_rb; size_t new_size; @@ -1223,6 +1204,8 @@ CURLcode add_buffer(send_buffer *in, const void *inptr, size_t size) /* end of the add_buffer functions */ /* ------------------------------------------------------------------------- */ + + /* * Curl_compareheader() * @@ -1320,7 +1303,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn, do { if(!conn->bits.tunnel_connecting) { /* BEGIN CONNECT PHASE */ char *host_port; - send_buffer *req_buffer; + Curl_send_buffer *req_buffer; infof(data, "Establish HTTP proxy tunnel to %s:%d\n", hostname, remote_port); @@ -1334,7 +1317,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn, } /* initialize a dynamic send-buffer */ - req_buffer = add_buffer_init(); + req_buffer = Curl_add_buffer_init(); if(!req_buffer) return CURLE_OUT_OF_MEMORY; @@ -1355,7 +1338,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn, const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1"; - if(!checkheaders(data, "Host:")) { + if(!Curl_checkheaders(data, "Host:")) { host = aprintf("Host: %s\r\n", host_port); if(!host) { free(req_buffer); @@ -1363,17 +1346,17 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn, return CURLE_OUT_OF_MEMORY; } } - if(!checkheaders(data, "Proxy-Connection:")) + if(!Curl_checkheaders(data, "Proxy-Connection:")) proxyconn = "Proxy-Connection: Keep-Alive\r\n"; - if(!checkheaders(data, "User-Agent:") && + if(!Curl_checkheaders(data, "User-Agent:") && data->set.str[STRING_USERAGENT]) useragent = conn->allocptr.uagent; /* Send the connect request to the proxy */ /* BLOCKING */ result = - add_bufferf(req_buffer, + Curl_add_bufferf(req_buffer, "CONNECT %s:%d HTTP/%s\r\n" "%s" /* Host: */ "%s" /* Proxy-Authorization */ @@ -1390,15 +1373,15 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn, free(host); if(CURLE_OK == result) - result = add_custom_headers(conn, req_buffer); + result = Curl_add_custom_headers(conn, req_buffer); if(CURLE_OK == result) /* CRLF terminate the request */ - result = add_bufferf(req_buffer, "\r\n"); + result = Curl_add_bufferf(req_buffer, "\r\n"); if(CURLE_OK == result) { /* Now send off the request */ - result = add_buffer_send(req_buffer, conn, + result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, sockindex); } req_buffer = NULL; @@ -1921,7 +1904,7 @@ CURLcode Curl_http_done(struct connectdata *conn, return CURLE_OK; if(http->send_buffer) { - send_buffer *buff = http->send_buffer; + Curl_send_buffer *buff = http->send_buffer; free(buff->buffer); free(buff); @@ -1962,9 +1945,9 @@ CURLcode Curl_http_done(struct connectdata *conn, /* Determine if we should use HTTP 1.1 for this request. Reasons to avoid it -are if the user specifically requested HTTP 1.0, if the server we are -connected to only supports 1.0, or if any server previously contacted to -handle this request only supports 1.0. */ + are if the user specifically requested HTTP 1.0, if the server we are + connected to only supports 1.0, or if any server previously contacted to + handle this request only supports 1.0. */ static bool use_http_1_1(const struct SessionHandle *data, const struct connectdata *conn) { @@ -1978,7 +1961,7 @@ static bool use_http_1_1(const struct SessionHandle *data, /* check and possibly add an Expect: header */ static CURLcode expect100(struct SessionHandle *data, struct connectdata *conn, - send_buffer *req_buffer) + Curl_send_buffer *req_buffer) { CURLcode result = CURLE_OK; const char *ptr; @@ -1988,14 +1971,14 @@ static CURLcode expect100(struct SessionHandle *data, /* if not doing HTTP 1.0 or disabled explicitly, we add a Expect: 100-continue to the headers which actually speeds up post operations (as there is one packet coming back from the web server) */ - ptr = checkheaders(data, "Expect:"); + ptr = Curl_checkheaders(data, "Expect:"); if (ptr) { data->state.expect100header = Curl_compareheader(ptr, "Expect:", "100-continue"); } else { - result = add_bufferf(req_buffer, - "Expect: 100-continue\r\n"); + result = Curl_add_bufferf(req_buffer, + "Expect: 100-continue\r\n"); if(result == CURLE_OK) data->state.expect100header = TRUE; } @@ -2003,8 +1986,8 @@ static CURLcode expect100(struct SessionHandle *data, return result; } -static CURLcode add_custom_headers(struct connectdata *conn, - send_buffer *req_buffer) +CURLcode Curl_add_custom_headers(struct connectdata *conn, + Curl_send_buffer *req_buffer) { char *ptr; struct curl_slist *headers=conn->data->set.headers; @@ -2036,7 +2019,8 @@ static CURLcode add_custom_headers(struct connectdata *conn, checkprefix("Content-Length", headers->data)) ; else { - CURLcode result = add_bufferf(req_buffer, "%s\r\n", headers->data); + CURLcode result = Curl_add_bufferf(req_buffer, "%s\r\n", + headers->data); if(result) return result; } @@ -2047,6 +2031,58 @@ static CURLcode add_custom_headers(struct connectdata *conn, return CURLE_OK; } +CURLcode Curl_add_timecondition(struct SessionHandle *data, + Curl_send_buffer *req_buffer) +{ + struct tm *tm; + char *buf = data->state.buffer; + CURLcode result = CURLE_OK; + + /* The If-Modified-Since header family should have their times set in + * GMT as RFC2616 defines: "All HTTP date/time stamps MUST be + * represented in Greenwich Mean Time (GMT), without exception. For the + * purposes of HTTP, GMT is exactly equal to UTC (Coordinated Universal + * Time)." (see page 20 of RFC2616). + */ + +#ifdef HAVE_GMTIME_R + /* thread-safe version */ + struct tm keeptime; + tm = (struct tm *)gmtime_r(&data->set.timevalue, &keeptime); +#else + tm = gmtime(&data->set.timevalue); +#endif + + /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ + snprintf(buf, BUFSIZE-1, + "%s, %02d %s %4d %02d:%02d:%02d GMT", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + + switch(data->set.timecondition) { + case CURL_TIMECOND_IFMODSINCE: + default: + result = Curl_add_bufferf(req_buffer, + "If-Modified-Since: %s\r\n", buf); + break; + case CURL_TIMECOND_IFUNMODSINCE: + result = Curl_add_bufferf(req_buffer, + "If-Unmodified-Since: %s\r\n", buf); + break; + case CURL_TIMECOND_LASTMOD: + result = Curl_add_bufferf(req_buffer, + "Last-Modified: %s\r\n", buf); + break; + } + + return result; +} + /* * Curl_http() gets called from the generic Curl_do() function when a HTTP * request is to be performed. This creates and sends a properly constructed @@ -2055,7 +2091,6 @@ static CURLcode add_custom_headers(struct connectdata *conn, CURLcode Curl_http(struct connectdata *conn, bool *done) { struct SessionHandle *data=conn->data; - char *buf = data->state.buffer; /* this is a short cut to the buffer */ CURLcode result=CURLE_OK; struct HTTP *http; const char *ppath = data->state.path; @@ -2069,7 +2104,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) char *addcookies = NULL; curl_off_t included_body = 0; const char *httpstring; - send_buffer *req_buffer; + Curl_send_buffer *req_buffer; curl_off_t postsize; /* off_t type to be able to hold a large file size */ int seekerr = CURL_SEEKFUNC_OK; @@ -2140,7 +2175,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) it might have been used in the proxy connect, but if we have got a header with the user-agent string specified, we erase the previously made string here. */ - if(checkheaders(data, "User-Agent:") && conn->allocptr.uagent) { + if(Curl_checkheaders(data, "User-Agent:") && conn->allocptr.uagent) { free(conn->allocptr.uagent); conn->allocptr.uagent=NULL; } @@ -2161,15 +2196,15 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) conn->bits.authneg = FALSE; Curl_safefree(conn->allocptr.ref); - if(data->change.referer && !checkheaders(data, "Referer:")) + if(data->change.referer && !Curl_checkheaders(data, "Referer:")) conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); else conn->allocptr.ref = NULL; - if(data->set.str[STRING_COOKIE] && !checkheaders(data, "Cookie:")) + if(data->set.str[STRING_COOKIE] && !Curl_checkheaders(data, "Cookie:")) addcookies = data->set.str[STRING_COOKIE]; - if(!checkheaders(data, "Accept-Encoding:") && + if(!Curl_checkheaders(data, "Accept-Encoding:") && data->set.str[STRING_ENCODING]) { Curl_safefree(conn->allocptr.accept_encoding); conn->allocptr.accept_encoding = @@ -2178,7 +2213,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) return CURLE_OUT_OF_MEMORY; } - ptr = checkheaders(data, "Transfer-Encoding:"); + ptr = Curl_checkheaders(data, "Transfer-Encoding:"); if(ptr) { /* Some kind of TE is requested, check if 'chunked' is chosen */ data->req.upload_chunky = @@ -2191,7 +2226,8 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) if (use_http_1_1(data, conn)) { /* HTTP, upload, unknown file size and not HTTP 1.0 */ data->req.upload_chunky = TRUE; - } else { + } + else { failf(data, "Chunky upload is not supported by HTTP 1.0"); return CURLE_UPLOAD_FAILED; } @@ -2207,7 +2243,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) Curl_safefree(conn->allocptr.host); - ptr = checkheaders(data, "Host:"); + ptr = Curl_checkheaders(data, "Host:"); if(ptr && (!data->state.this_is_a_follow || Curl_raw_equal(data->state.first_host, conn->host.name))) { #if !defined(CURL_DISABLE_COOKIES) @@ -2334,7 +2370,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* we must build the whole darned post sequence first, so that we have a size of the whole shebang before we start to send it */ result = Curl_getFormData(&http->sendit, data->set.httppost, - checkheaders(data, "Content-Type:"), + Curl_checkheaders(data, "Content-Type:"), &http->postsize); if(CURLE_OK != result) { /* Curl_getFormData() doesn't use failf() */ @@ -2344,7 +2380,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } - http->p_accept = checkheaders(data, "Accept:")?NULL:"Accept: */*\r\n"; + http->p_accept = Curl_checkheaders(data, "Accept:")?NULL:"Accept: */*\r\n"; if(( (HTTPREQ_POST == httpreq) || (HTTPREQ_POST_FORM == httpreq) || @@ -2427,7 +2463,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) * ones if any such are specified. */ if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && - !checkheaders(data, "Range:")) { + !Curl_checkheaders(data, "Range:")) { /* if a line like this was already allocated, free the previous one */ if(conn->allocptr.rangeline) free(conn->allocptr.rangeline); @@ -2435,7 +2471,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) data->state.range); } else if((httpreq != HTTPREQ_GET) && - !checkheaders(data, "Content-Range:")) { + !Curl_checkheaders(data, "Content-Range:")) { /* if a line like this was already allocated, free the previous one */ if(conn->allocptr.rangeline) @@ -2478,27 +2514,27 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) httpstring= use_http_1_1(data, conn)?"1.1":"1.0"; /* initialize a dynamic send-buffer */ - req_buffer = add_buffer_init(); + req_buffer = Curl_add_buffer_init(); if(!req_buffer) return CURLE_OUT_OF_MEMORY; /* add the main request stuff */ /* GET/HEAD/POST/PUT */ - result = add_bufferf(req_buffer, "%s ", request); + result = Curl_add_bufferf(req_buffer, "%s ", request); if (result) return result; /* url */ if (paste_ftp_userpwd) - result = add_bufferf(req_buffer, "ftp://%s:%s@%s", + result = Curl_add_bufferf(req_buffer, "ftp://%s:%s@%s", conn->user, conn->passwd, ppath + sizeof("ftp://") - 1); else - result = add_buffer(req_buffer, ppath, strlen(ppath)); + result = Curl_add_buffer(req_buffer, ppath, strlen(ppath)); if (result) return result; - result = add_bufferf(req_buffer, + result = Curl_add_bufferf(req_buffer, "%s" /* ftp typecode (;type=x) */ " HTTP/%s\r\n" /* HTTP version */ "%s" /* proxyuserpwd */ @@ -2532,7 +2568,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) conn->allocptr.ref:"" /* Referer: <data> */, (conn->bits.httpproxy && !conn->bits.tunnel_proxy && - !checkheaders(data, "Proxy-Connection:"))? + !Curl_checkheaders(data, "Proxy-Connection:"))? "Proxy-Connection: Keep-Alive\r\n":"", te ); @@ -2568,11 +2604,11 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) while(co) { if(co->value) { if(0 == count) { - result = add_bufferf(req_buffer, "Cookie: "); + result = Curl_add_bufferf(req_buffer, "Cookie: "); if(result) break; } - result = add_bufferf(req_buffer, + result = Curl_add_bufferf(req_buffer, "%s%s=%s", count?"; ":"", co->name, co->value); if(result) @@ -2585,16 +2621,16 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } if(addcookies && (CURLE_OK == result)) { if(!count) - result = add_bufferf(req_buffer, "Cookie: "); + result = Curl_add_bufferf(req_buffer, "Cookie: "); if(CURLE_OK == result) { - result = add_bufferf(req_buffer, "%s%s", + result = Curl_add_bufferf(req_buffer, "%s%s", count?"; ":"", addcookies); count++; } } if(count && (CURLE_OK == result)) - result = add_buffer(req_buffer, "\r\n", 2); + result = Curl_add_buffer(req_buffer, "\r\n", 2); if(result) return result; @@ -2602,54 +2638,12 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) #endif if(data->set.timecondition) { - struct tm *tm; - - /* The If-Modified-Since header family should have their times set in - * GMT as RFC2616 defines: "All HTTP date/time stamps MUST be - * represented in Greenwich Mean Time (GMT), without exception. For the - * purposes of HTTP, GMT is exactly equal to UTC (Coordinated Universal - * Time)." (see page 20 of RFC2616). - */ - -#ifdef HAVE_GMTIME_R - /* thread-safe version */ - struct tm keeptime; - tm = (struct tm *)gmtime_r(&data->set.timevalue, &keeptime); -#else - tm = gmtime(&data->set.timevalue); -#endif - - /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - snprintf(buf, BUFSIZE-1, - "%s, %02d %s %4d %02d:%02d:%02d GMT", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - - switch(data->set.timecondition) { - case CURL_TIMECOND_IFMODSINCE: - default: - result = add_bufferf(req_buffer, - "If-Modified-Since: %s\r\n", buf); - break; - case CURL_TIMECOND_IFUNMODSINCE: - result = add_bufferf(req_buffer, - "If-Unmodified-Since: %s\r\n", buf); - break; - case CURL_TIMECOND_LASTMOD: - result = add_bufferf(req_buffer, - "Last-Modified: %s\r\n", buf); - break; - } + result = Curl_add_timecondition(data, req_buffer); if(result) return result; } - result = add_custom_headers(conn, req_buffer); + result = Curl_add_custom_headers(conn, req_buffer); if(result) return result; @@ -2665,11 +2659,11 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) case HTTPREQ_POST_FORM: if(!http->sendit || conn->bits.authneg) { /* nothing to post! */ - result = add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); + result = Curl_add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); if(result) return result; - result = add_buffer_send(req_buffer, conn, + result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending POST request"); @@ -2701,7 +2695,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) if(!data->req.upload_chunky) { /* only add Content-Length if not uploading chunked */ - result = add_bufferf(req_buffer, + result = Curl_add_bufferf(req_buffer, "Content-Length: %" FORMAT_OFF_T "\r\n", http->postsize); if(result) @@ -2725,13 +2719,13 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) return CURLE_HTTP_POST_ERROR; } - result = add_buffer(req_buffer, contentType, linelength); + result = Curl_add_buffer(req_buffer, contentType, linelength); if(result) return result; } /* make the request end in a true CRLF */ - result = add_buffer(req_buffer, "\r\n", 2); + result = Curl_add_buffer(req_buffer, "\r\n", 2); if(result) return result; @@ -2739,7 +2733,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) Curl_pgrsSetUploadSize(data, http->postsize); /* fire away the whole request to the server */ - result = add_buffer_send(req_buffer, conn, + result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending POST request"); @@ -2773,7 +2767,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) if((postsize != -1) && !data->req.upload_chunky) { /* only add Content-Length if not uploading chunked */ - result = add_bufferf(req_buffer, + result = Curl_add_bufferf(req_buffer, "Content-Length: %" FORMAT_OFF_T "\r\n", postsize ); if(result) @@ -2784,7 +2778,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) if(result) return result; - result = add_buffer(req_buffer, "\r\n", 2); /* end of headers */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers */ if(result) return result; @@ -2792,7 +2786,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) Curl_pgrsSetUploadSize(data, postsize); /* this sends the buffer and frees all the buffer resources */ - result = add_buffer_send(req_buffer, conn, + result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending PUT request"); @@ -2822,10 +2816,10 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) we don't upload data chunked, as RFC2616 forbids us to set both kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if(conn->bits.authneg || !checkheaders(data, "Content-Length:")) { + if(conn->bits.authneg || !Curl_checkheaders(data, "Content-Length:")) { /* we allow replacing this header if not during auth negotiation, although it isn't very wise to actually set your own */ - result = add_bufferf(req_buffer, + result = Curl_add_bufferf(req_buffer, "Content-Length: %" FORMAT_OFF_T"\r\n", postsize); if(result) @@ -2833,8 +2827,8 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } } - if(!checkheaders(data, "Content-Type:")) { - result = add_bufferf(req_buffer, + if(!Curl_checkheaders(data, "Content-Type:")) { + result = Curl_add_bufferf(req_buffer, "Content-Type: application/x-www-form-urlencoded\r\n"); if(result) return result; @@ -2844,7 +2838,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) the somewhat bigger ones we allow the app to disable it. Just make sure that the expect100header is always set to the preferred value here. */ - ptr = checkheaders(data, "Expect:"); + ptr = Curl_checkheaders(data, "Expect:"); if(ptr) { data->state.expect100header = Curl_compareheader(ptr, "Expect:", "100-continue"); @@ -2868,25 +2862,25 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) is no magic limit but only set to prevent really huge POSTs to get the data duplicated with malloc() and family. */ - result = add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ if(result) return result; if(!data->req.upload_chunky) { /* We're not sending it 'chunked', append it to the request already now to reduce the number if send() calls */ - result = add_buffer(req_buffer, data->set.postfields, + result = Curl_add_buffer(req_buffer, data->set.postfields, (size_t)postsize); included_body = postsize; } else { /* Append the POST data chunky-style */ - result = add_bufferf(req_buffer, "%x\r\n", (int)postsize); + result = Curl_add_bufferf(req_buffer, "%x\r\n", (int)postsize); if(CURLE_OK == result) - result = add_buffer(req_buffer, data->set.postfields, + result = Curl_add_buffer(req_buffer, data->set.postfields, (size_t)postsize); if(CURLE_OK == result) - result = add_buffer(req_buffer, + result = Curl_add_buffer(req_buffer, "\x0d\x0a\x30\x0d\x0a\x0d\x0a", 7); /* CR LF 0 CR LF CR LF */ included_body = postsize + 7; @@ -2907,20 +2901,20 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) /* set the upload size to the progress meter */ Curl_pgrsSetUploadSize(data, http->postsize); - result = add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ if(result) return result; } } else { - result = add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ if(result) return result; if(data->req.upload_chunky && conn->bits.authneg) { /* Chunky upload is selected and we're negotiating auth still, send end-of-data only */ - result = add_buffer(req_buffer, + result = Curl_add_buffer(req_buffer, "\x0d\x0a\x30\x0d\x0a\x0d\x0a", 7); /* CR LF 0 CR LF CR LF */ if(result) @@ -2941,7 +2935,7 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } } /* issue the request */ - result = add_buffer_send(req_buffer, conn, &data->info.request_size, + result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, (size_t)included_body, FIRSTSOCKET); if(result) @@ -2955,12 +2949,12 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) break; default: - result = add_buffer(req_buffer, "\r\n", 2); + result = Curl_add_buffer(req_buffer, "\r\n", 2); if(result) return result; /* issue the request */ - result = add_buffer_send(req_buffer, conn, + result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, FIRSTSOCKET); if(result) @@ -2999,12 +2993,11 @@ checkhttpprefix(struct SessionHandle *data, bool rc = FALSE; #ifdef CURL_DOES_CONVERSIONS /* convert from the network encoding using a scratch area */ - char *scratch = calloc(1, strlen(s)+1); + char *scratch = strdup(s); if(NULL == scratch) { - failf (data, "Failed to calloc memory for conversion!"); + failf (data, "Failed to allocate memory for conversion!"); return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ } - strcpy(scratch, s); if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { /* Curl_convert_from_network calls failf if unsuccessful */ free(scratch); @@ -3031,6 +3024,51 @@ checkhttpprefix(struct SessionHandle *data, return rc; } +#ifndef CURL_DISABLE_RTSP +static bool +checkrtspprefix(struct SessionHandle *data, + const char *s) +{ + +#ifdef CURL_DOES_CONVERSIONS + /* convert from the network encoding using a scratch area */ + char *scratch = strdup(s); + if(NULL == scratch) { + failf (data, "Failed to allocate memory for conversion!"); + return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ + } + if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { + /* Curl_convert_from_network calls failf if unsuccessful */ + free(scratch); + return FALSE; /* can't return CURLE_foobar so return FALSE */ + } + s = scratch; +#else + (void)data; /* unused */ +#endif /* CURL_DOES_CONVERSIONS */ + if(checkprefix("RTSP/", s)) + return TRUE; + else + return FALSE; +} +#endif /* CURL_DISABLE_RTSP */ + +static bool +checkprotoprefix(struct SessionHandle *data, struct connectdata *conn, + const char *s) +{ +#ifndef CURL_DISABLE_RTSP + if(conn->protocol & PROT_RTSP) + return checkrtspprefix(data, s); +#endif /* CURL_DISABLE_RTSP */ + + return checkhttpprefix(data, s); +} + +#endif + + + /* * header_append() copies a chunk of data to the end of the already received * header. We make sure that the full string fit in the allocated header @@ -3079,9 +3117,9 @@ static CURLcode header_append(struct SessionHandle *data, * Read any HTTP header lines from the server and pass them to the client app. */ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading) + struct connectdata *conn, + ssize_t *nread, + bool *stop_reading) { CURLcode result; struct SingleRequest *k = &data->req; @@ -3106,9 +3144,9 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, return result; if(!k->headerline && (k->hbuflen>5)) { - /* make a first check that this looks like a HTTP header */ - if(!checkhttpprefix(data, data->state.headerbuff)) { - /* this is not the beginning of a HTTP first header line */ + /* make a first check that this looks like a protocol header */ + if(!checkprotoprefix(data, conn, data->state.headerbuff)) { + /* this is not the beginning of a protocol first header line */ k->header = FALSE; k->badheader = HEADER_ALLBAD; break; @@ -3140,8 +3178,8 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, if(!k->headerline) { /* the first read header */ if((k->hbuflen>5) && - !checkhttpprefix(data, data->state.headerbuff)) { - /* this is not the beginning of a HTTP first header line */ + !checkprotoprefix(data, conn, data->state.headerbuff)) { + /* this is not the beginning of a protocol first header line */ k->header = FALSE; if(*nread) /* since there's more, this is a partial bad header */ @@ -3198,7 +3236,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, k->header = FALSE; /* no more header to parse! */ if((k->size == -1) && !k->chunk && !conn->bits.close && - (conn->httpversion >= 11) ) { + (conn->httpversion >= 11) && !(conn->protocol & PROT_RTSP)) { /* On HTTP 1.1, when connection is not to get closed, but no Content-Length nor Content-Encoding chunked have been received, according to RFC2616 section 4.4 point 5, we @@ -3310,6 +3348,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, Curl_pgrsSetDownloadSize(data, k->size); k->maxdownload = k->size; } + /* If max download size is *zero* (nothing) we already have nothing and can safely return ok now! */ if(0 == k->maxdownload) @@ -3341,6 +3380,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, /* This is the first header, it MUST be the error code line or else we consider this to be the body right away! */ int httpversion_major; + int rtspversion_major; int nc; #ifdef CURL_DOES_CONVERSIONS #define HEADER1 scratch @@ -3365,35 +3405,53 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, #define HEADER1 k->p /* no conversion needed, just use k->p */ #endif /* CURL_DOES_CONVERSIONS */ - nc = sscanf(HEADER1, - " HTTP/%d.%d %3d", - &httpversion_major, - &conn->httpversion, - &k->httpcode); - if(nc==3) { - conn->httpversion += 10 * httpversion_major; - } - else { - /* this is the real world, not a Nirvana - NCSA 1.5.x returns this crap when asked for HTTP/1.1 - */ - nc=sscanf(HEADER1, " HTTP %3d", &k->httpcode); - conn->httpversion = 10; + if(conn->protocol & PROT_HTTP) { + nc = sscanf(HEADER1, + " HTTP/%d.%d %3d", + &httpversion_major, + &conn->httpversion, + &k->httpcode); + if(nc==3) { + conn->httpversion += 10 * httpversion_major; + } + else { + /* this is the real world, not a Nirvana + NCSA 1.5.x returns this crap when asked for HTTP/1.1 + */ + nc=sscanf(HEADER1, " HTTP %3d", &k->httpcode); + conn->httpversion = 10; - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - if(!nc) { - if(checkhttpprefix(data, k->p)) { - nc = 1; - k->httpcode = 200; - conn->httpversion = 10; + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + if(!nc) { + if(checkhttpprefix(data, k->p)) { + nc = 1; + k->httpcode = 200; + conn->httpversion = 10; + } } } } + else if(conn->protocol & PROT_RTSP) { + nc = sscanf(HEADER1, + " RTSP/%d.%d %3d", + &rtspversion_major, + &conn->rtspversion, + &k->httpcode); + if(nc==3) { + conn->rtspversion += 10 * rtspversion_major; + conn->httpversion = 11; /* For us, RTSP acts like HTTP 1.1 */ + } + else { + /* TODO: do we care about the other cases here? */ + nc = 0; + } + } if(nc) { data->info.httpcode = k->httpcode; + data->info.httpversion = conn->httpversion; if (!data->state.httpversion || data->state.httpversion > conn->httpversion) @@ -3571,8 +3629,8 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, */ conn->bits.close = TRUE; /* close when done */ } - else if(Curl_compareheader(k->p, - "Transfer-Encoding:", "chunked")) { + else if(Curl_compareheader(k->p, "Transfer-Encoding:", "chunked") && + !(conn->protocol & PROT_RTSP)) { /* * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding * means that the server will send a series of "chunks". Each @@ -3708,7 +3766,13 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, } } } - +#ifndef CURL_DISABLE_RTSP + else if(conn->protocol & PROT_RTSP) { + result = Curl_rtsp_parseheader(conn, k->p); + if(result) + return result; + } +#endif /* * End of header-checks. Write them to the client. */ @@ -3741,4 +3805,3 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, return CURLE_OK; } -#endif diff --git a/lib/http.h b/lib/http.h index 155027d99..b2cbdae35 100644 --- a/lib/http.h +++ b/lib/http.h @@ -35,8 +35,40 @@ bool Curl_compareheader(const char *headerline, /* line to check */ const char *header, /* header keyword _with_ colon */ const char *content); /* content string to find */ +char *Curl_checkheaders(struct SessionHandle *data, const char *thisheader); + char *Curl_copy_header_value(const char *h); + +/* ------------------------------------------------------------------------- */ +/* + * The add_buffer series of functions are used to build one large memory chunk + * from repeated function invokes. Used so that the entire HTTP request can + * be sent in one go. + */ +struct Curl_send_buffer { + char *buffer; + size_t size_max; + size_t size_used; +}; +typedef struct Curl_send_buffer Curl_send_buffer; + +Curl_send_buffer *Curl_add_buffer_init(void); +CURLcode Curl_add_bufferf(Curl_send_buffer *in, const char *fmt, ...); +CURLcode Curl_add_buffer(Curl_send_buffer *in, const void *inptr, size_t size); +CURLcode Curl_add_buffer_send(Curl_send_buffer *in, + struct connectdata *conn, + long *bytes_written, + size_t included_body_bytes, + int socketindex); + + +CURLcode Curl_add_timecondition(struct SessionHandle *data, + Curl_send_buffer *buf); +CURLcode Curl_add_custom_headers(struct connectdata *conn, + Curl_send_buffer *req_buffer); + + /* ftp can use this as well */ CURLcode Curl_proxyCONNECT(struct connectdata *conn, int tunnelsocket, diff --git a/lib/rtsp.c b/lib/rtsp.c new file mode 100644 index 000000000..b527fe985 --- /dev/null +++ b/lib/rtsp.c @@ -0,0 +1,753 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +#include "setup.h" + +#ifndef CURL_DISABLE_RTSP + +#include "urldata.h" +#include <curl/curl.h> +#include "transfer.h" +#include "sendf.h" +#include "easyif.h" /* for Curl_convert_... prototypes */ +#include "multiif.h" +#include "http.h" +#include "url.h" +#include "progress.h" +#include "rtsp.h" +#include "rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include <curl/mprintf.h> + +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * TODO (general) + * -incoming server requests + * -server CSeq counter + * -digest authentication + * -connect thru proxy + * -pipelining? + */ + + +static int rtsp_getsock_do(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); + +/* this returns the socket to wait for in the DO and DOING state for the multi + interface and then we're always _sending_ a request and thus we wait for + the single socket to become writable only */ +static int rtsp_getsock_do(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + /* write mode */ + (void)numsocks; /* unused, we trust it to be at least 1 */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); +} + +static +CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len); + + +/* + * RTSP handler interface. + */ +const struct Curl_handler Curl_handler_rtsp = { + "RTSP", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_rtsp, /* do_it */ + Curl_rtsp_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_rtsp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + rtsp_getsock_do, /* doing_getsock */ + ZERO_NULL, /* perform_getsock */ + Curl_rtsp_disconnect, /* disconnect */ + PORT_RTSP, /* defport */ + PROT_RTSP, /* protocol */ +}; + +CURLcode Curl_rtsp_connect(struct connectdata *conn, bool *done) +{ + CURLcode httpStatus; + struct SessionHandle *data = conn->data; + + httpStatus = Curl_http_connect(conn, done); + + /* Initialize the CSeq if not already done */ + if(data->state.rtsp_next_client_CSeq == 0) + data->state.rtsp_next_client_CSeq = 1; + if(data->state.rtsp_next_server_CSeq == 0) + data->state.rtsp_next_server_CSeq = 1; + + conn->proto.rtspc.rtp_channel = -1; + + return httpStatus; +} + +CURLcode Curl_rtsp_disconnect(struct connectdata *conn) { + Curl_safefree(conn->proto.rtspc.rtp_buf); + return CURLE_OK; +} + + +CURLcode Curl_rtsp_done(struct connectdata *conn, + CURLcode status, bool premature) +{ + CURLcode httpStatus; + struct SessionHandle *data = conn->data; + long CSeq_sent; + long CSeq_recv; + + httpStatus = Curl_http_done(conn, status, premature); + + /* Check the sequence numbers */ + CSeq_sent = data->state.proto.rtsp->CSeq_sent; + CSeq_recv = data->state.proto.rtsp->CSeq_recv; + if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) { + failf(data, "The CSeq of this request %ld did not match the response %ld", + CSeq_sent, CSeq_recv); + return CURLE_RTSP_CSEQ_ERROR; + } + else if (data->set.rtspreq == RTSPREQ_RECEIVE && + (conn->proto.rtspc.rtp_channel == -1)) { + infof(data, "Got a non RTP Receive with a CSeq of %ld\n", CSeq_recv); + /* TODO CPC: Server -> Client logic here */ + } + + return httpStatus; +} + +CURLcode Curl_rtsp(struct connectdata *conn, bool *done) +{ + struct SessionHandle *data = conn->data; + CURLcode result=CURLE_OK; + Curl_RtspReq rtspreq = data->set.rtspreq; + struct RTSP *rtsp; + struct HTTP *http; + Curl_send_buffer *req_buffer; + curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + + const char *p_request = NULL; + const char *p_session_id = NULL; + const char *p_accept = NULL; + const char *p_accept_encoding = NULL; + const char *p_range = NULL; + const char *p_referrer = NULL; + const char *p_stream_uri = NULL; + const char *p_transport = NULL; + const char *p_uagent = NULL; + + *done = TRUE; + + Curl_reset_reqproto(conn); + + if(!data->state.proto.rtsp) { + /* Only allocate this struct if we don't already have it! */ + + rtsp = calloc(sizeof(struct RTSP), 1); + if(!rtsp) + return CURLE_OUT_OF_MEMORY; + data->state.proto.rtsp = rtsp; + } + else { + rtsp = data->state.proto.rtsp; + } + + http = &(rtsp->http_wrapper); + /* Assert that no one has changed the RTSP struct in an evil way */ + DEBUGASSERT((void *)http == (void *)rtsp); + + rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; + rtsp->CSeq_recv = 0; + + /* Setup the 'p_request' pointer to the proper p_request string + * Since all RTSP requests are included here, there is no need to + * support custom requests like HTTP. + **/ + DEBUGASSERT((rtspreq > RTSPREQ_NONE && rtspreq < RTSPREQ_LAST)); + data->set.opt_no_body = TRUE; /* most requests don't contain a body */ + switch(rtspreq) { + case RTSPREQ_NONE: + failf(data, "Got invalid RTSP request: RTSPREQ_NONE"); + return CURLE_BAD_FUNCTION_ARGUMENT; + break; + case RTSPREQ_OPTIONS: + p_request = "OPTIONS"; + break; + case RTSPREQ_DESCRIBE: + p_request = "DESCRIBE"; + data->set.opt_no_body = FALSE; + break; + case RTSPREQ_ANNOUNCE: + p_request = "ANNOUNCE"; + break; + case RTSPREQ_SETUP: + p_request = "SETUP"; + break; + case RTSPREQ_PLAY: + p_request = "PLAY"; + break; + case RTSPREQ_PAUSE: + p_request = "PAUSE"; + break; + case RTSPREQ_TEARDOWN: + p_request = "TEARDOWN"; + break; + case RTSPREQ_GET_PARAMETER: + p_request = "GET_PARAMETER"; + data->set.opt_no_body = FALSE; + break; + case RTSPREQ_SET_PARAMETER: + p_request = "SET_PARAMETER"; + break; + case RTSPREQ_RECORD: + p_request = "RECORD"; + break; + case RTSPREQ_RECEIVE: + p_request = ""; + /* Treat interleaved RTP as body*/ + data->set.opt_no_body = FALSE; + break; + case RTSPREQ_LAST: + failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); + return CURLE_BAD_FUNCTION_ARGUMENT; + break; + } + + if(rtspreq == RTSPREQ_RECEIVE) { + result = Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, -1, NULL); + + return result; + } + + p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; + if(!p_session_id && + (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { + failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", + p_request ? p_request : ""); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* TODO: auth? */ + /* TODO: proxy? */ + + /* Stream URI. Default to server '*' if not specified */ + if(data->set.str[STRING_RTSP_STREAM_URI]) { + p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; + } + else { + p_stream_uri = "*"; + } + + /* Transport Header for SETUP requests */ + p_transport = Curl_checkheaders(data, "Transport:"); + if(rtspreq == RTSPREQ_SETUP && !p_transport) { + /* New Transport: setting? */ + if(data->set.str[STRING_RTSP_TRANSPORT]) { + Curl_safefree(conn->allocptr.rtsp_transport); + + conn->allocptr.rtsp_transport = + aprintf("Transport: %s\r\n", + data->set.str[STRING_RTSP_TRANSPORT]); + if(!conn->allocptr.rtsp_transport) + return CURLE_OUT_OF_MEMORY; + } + else { + failf(data, + "Refusing to issue an RTSP SETUP without a Transport: header."); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + p_transport = conn->allocptr.rtsp_transport; + } + + /* Accept Headers for DESCRIBE requests */ + if(rtspreq == RTSPREQ_DESCRIBE) { + /* Accept Header */ + p_accept = Curl_checkheaders(data, "Accept:")? + NULL:"Accept: application/sdp\r\n"; + + /* Accept-Encoding header */ + if(!Curl_checkheaders(data, "Accept-Encoding:") && + data->set.str[STRING_ENCODING]) { + Curl_safefree(conn->allocptr.accept_encoding); + conn->allocptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); + + if(!conn->allocptr.accept_encoding) + return CURLE_OUT_OF_MEMORY; + + p_accept_encoding = conn->allocptr.accept_encoding; + } + } + + /* Default to text/parameters for GET_PARAMETER */ + if(rtspreq == RTSPREQ_GET_PARAMETER) { + p_accept = Curl_checkheaders(data, "Accept:")? + NULL:"Accept: text/parameters\r\n"; + } + + /* The User-Agent string might have been allocated in url.c already, because + it might have been used in the proxy connect, but if we have got a header + with the user-agent string specified, we erase the previously made string + here. */ + if(Curl_checkheaders(data, "User-Agent:") && conn->allocptr.uagent) { + Curl_safefree(conn->allocptr.uagent); + conn->allocptr.uagent = NULL; + } + else if(!Curl_checkheaders(data, "User-Agent:") && + data->set.str[STRING_USERAGENT]) { + p_uagent = conn->allocptr.uagent; + } + + /* Referrer */ + Curl_safefree(conn->allocptr.ref); + if(data->change.referer && !Curl_checkheaders(data, "Referer:")) + conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); + else + conn->allocptr.ref = NULL; + + p_referrer = conn->allocptr.ref; + + /* + * Range Header + * Only applies to PLAY, PAUSE, RECORD + * + * Go ahead and use the Range stuff supplied for HTTP + */ + if(data->state.use_range && + (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { + + /* Check to see if there is a range set in the custom headers */ + if(!Curl_checkheaders(data, "Range:") && data->state.range) { + Curl_safefree(conn->allocptr.rangeline); + conn->allocptr.rangeline = aprintf("Range: %s\r\n", data->state.range); + p_range = conn->allocptr.rangeline; + } + } + + /* + * Sanity check the custom headers + */ + if(Curl_checkheaders(data, "CSeq:")) { + failf(data, "CSeq cannot be set as a custom header."); + return CURLE_RTSP_CSEQ_ERROR; + } + if(Curl_checkheaders(data, "Session:")) { + failf(data, "Session ID cannot be set as a custom header."); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* Initialize a dynamic send buffer */ + req_buffer = Curl_add_buffer_init(); + + if(!req_buffer) + return CURLE_OUT_OF_MEMORY; + + result = + Curl_add_bufferf(req_buffer, + "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ + "CSeq: %d \r\n", /* CSeq */ + (p_request ? p_request : ""), p_stream_uri, + rtsp->CSeq_sent); + if(result) + return result; + + /* + * Rather than do a normal alloc line, keep the session_id unformatted + * to make comparison easier + */ + if(p_session_id) { + result = Curl_add_bufferf(req_buffer, "Session: %s \r\n", p_session_id); + if(result) + return result; + } + + /* + * Shared HTTP-like options + */ + result = Curl_add_bufferf(req_buffer, + "%s" /* transport */ + "%s" /* accept */ + "%s" /* accept-encoding */ + "%s" /* range */ + "%s" /* referrer */ + "%s" /* user-agent */ + , + p_transport ? p_transport : "", + p_accept ? p_accept : "", + p_accept_encoding ? p_accept_encoding : "", + p_range ? p_range : "", + p_referrer ? p_referrer : "", + p_uagent ? p_uagent : ""); + + if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { + result = Curl_add_timecondition(data, req_buffer); + if(result) + return result; + } + + result = Curl_add_custom_headers(conn, req_buffer); + if(result) + return result; + + if(rtspreq == RTSPREQ_ANNOUNCE || rtspreq == RTSPREQ_SET_PARAMETER) { + if(data->set.upload) { + putsize = data->set.infilesize; + data->set.httpreq = HTTPREQ_PUT; + + } + else { + postsize = (data->set.postfieldsize != -1)? + data->set.postfieldsize: + (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); + data->set.httpreq = HTTPREQ_POST; + } + + /* As stated in the http comments, it is probably not wise to + * actually set a custom Content-Length in the headers */ + if(!Curl_checkheaders(data, "Content-Length:")) { + result = Curl_add_bufferf(req_buffer, + "Content-Length: %" FORMAT_OFF_T"\r\n", + (data->set.upload ? putsize : postsize)); + if(result) + return result; + } + + if(rtspreq == RTSPREQ_SET_PARAMETER) { + if(!Curl_checkheaders(data, "Content-Type:")) { + result = Curl_add_bufferf(req_buffer, + "Content-Type: text/parameters\r\n"); + } + if(result) + return result; + } + + if(rtspreq == RTSPREQ_ANNOUNCE) { + if(!Curl_checkheaders(data, "Content-Type:")) { + result = Curl_add_bufferf(req_buffer, + "Content-Type: application/sdp\r\n"); + } + } + + data->state.expect100header = FALSE; /* RTSP posts are simple/small */ + } + + + /* RTSP never allows chunked transfer */ + data->req.forbidchunk = TRUE; + /* Finish the request buffer */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); + if(result) + return result; + + if(postsize > 0) { + result = Curl_add_buffer(req_buffer, data->set.postfields, + (size_t)postsize); + if(result) + return result; + } + + /* issue the request */ + result = Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) { + failf(data, "Failed sending RTSP request"); + return result; + } + + result = Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, + putsize?FIRSTSOCKET:-1, + putsize?&http->writebytecount:NULL); + + if(result) { + failf(data, "Failed RTSP transfer"); + return result; + } + + /* Increment the CSeq on success */ + data->state.rtsp_next_client_CSeq++; + + if(http->writebytecount) { + /* if a request-body has been sent off, we make sure this progress is + noted properly */ + Curl_pgrsSetUploadCounter(data, http->writebytecount); + if(Curl_pgrsUpdate(conn)) + result = CURLE_ABORTED_BY_CALLBACK; + } + + return result; + + return CURLE_OK; + +} + +CURLcode Curl_rtsp_rtp_readwrite(struct SessionHandle *data, + struct connectdata *conn, + ssize_t *nread, + bool *readmore, + bool *done) { + struct SingleRequest *k = &data->req; + struct rtsp_conn *rtspc = &(conn->proto.rtspc); + + char *rtp; /* moving pointer to rtp data */ + ssize_t rtp_dataleft; /* how much data left to parse in this round */ + char *scratch; + CURLcode result; + + if(rtspc->rtp_buf) { + /* There was some leftover data the last time. Merge buffers */ + rtspc->rtp_buf = realloc(rtspc->rtp_buf, rtspc->rtp_bufsize + *nread); + if(!rtspc->rtp_buf) + return CURLE_OUT_OF_MEMORY; + memcpy(rtspc->rtp_buf + rtspc->rtp_bufsize, k->str, *nread); + rtspc->rtp_bufsize += *nread; + rtp = rtspc->rtp_buf; + rtp_dataleft = rtspc->rtp_bufsize; + } + else { + /* Just parse the request buffer directly */ + rtp = k->str; + rtp_dataleft = *nread; + } + + if(rtp_dataleft == 0 || rtp[0] != '$') { + return CURLE_OK; + } + + while((rtp_dataleft > 0) && + (rtp[0] == '$')) { + if(rtp_dataleft > 4) { + char channel; + int rtp_length; + + /* Parse the header */ + /* The channel identifier immediately follows and is 1 byte */ + channel = rtp[1]; + rtspc->rtp_channel = (int) channel; + + /* The length is two bytes */ + rtp_length = ntohs( *((unsigned short *)(&rtp[2])) ); + + if(rtp_dataleft < rtp_length + 4) { + /* Need more - incomplete payload*/ + *readmore = TRUE; + break; + } + else { + /* We have the full RTP interleaved packet + * Write out the header but strip the leading '$' */ + infof(data, "CPCDEBUG: RTP write channel %d rtp_length %d\n", + rtspc->rtp_channel, rtp_length); + result = rtp_client_write(conn, &rtp[1], rtp_length + 3); + if(result) { + failf(data, "Got an error writing an RTP packet"); + *done = TRUE; + *readmore = FALSE; + return result; + } + + /* Update progress */ + k->bytecount += rtp_length + 4; + Curl_pgrsSetDownloadCounter(data, k->bytecount); + if(k->bytecountp) + *k->bytecountp = k->bytecount; + + /* Move forward in the buffer */ + rtp_dataleft -= rtp_length + 4; + rtp += rtp_length + 4; + + if(data->set.rtspreq == RTSPREQ_RECEIVE) { + /* If we are in a passive receive, give control back + * to the app as often as we can. + * + * Otherwise, keep chugging along until we get RTSP data + */ + k->keepon &= ~KEEP_RECV; + *done = TRUE; + } + } + } + else { + /* Need more - incomplete header */ + *readmore = TRUE; + break; + } + } + + if(*done || *readmore) { + if(rtp_dataleft != 0 && rtp[0] == '$') { + infof(data, "RTP Rewinding %zu %s %s\n", rtp_dataleft, + *done ? "DONE " : "", + *readmore ? "READMORE" : ""); + + /* Store the incomplete RTP packet for a "rewind" */ + scratch = malloc(rtp_dataleft); + if(!scratch) + return CURLE_OUT_OF_MEMORY; + memcpy(scratch, rtp, rtp_dataleft); + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = scratch; + rtspc->rtp_bufsize = rtp_dataleft; + return CURLE_OK; + } + } + else { + /* RTP followed by RTSP */ + if(rtp_dataleft == 0) { + /* Need more */ + *readmore = TRUE; + } + else { + /* Fix up k->str to point just after the last RTP packet */ + k->str += *nread - rtp_dataleft; + + /* rtp may point into the leftover buffer, but at this point + * it is somewhere in the merged data from k->str. */ + DEBUGASSERT(k->str[0] == rtp[0]); + + DEBUGASSERT(rtp_dataleft < *nread); /* sanity check */ + + *nread = rtp_dataleft; + } + } + + /* If we get here, we have finished with the leftover/merge buffer */ + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = NULL; + rtspc->rtp_bufsize = 0; + + /* TODO CPC: Could implement parsing logic for Server->Client requests + here */ + + return CURLE_OK; +} + +CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len) +{ + struct SessionHandle *data = conn->data; + size_t wrote; + curl_write_callback writeit; + + if(len == 0) { + failf (data, "Cannot write a 0 size RTP packet."); + return CURLE_WRITE_ERROR; + } + + writeit = data->set.fwrite_rtp?data->set.fwrite_rtp:data->set.fwrite_func; + wrote = writeit(ptr, 1, len, data->set.rtp_out); + + if(CURL_WRITEFUNC_PAUSE == wrote) { + failf (data, "Cannot pause RTP"); + return CURLE_WRITE_ERROR; + } + + if(wrote != len) { + failf (data, "Failed writing RTP data"); + return CURLE_WRITE_ERROR; + } + + return CURLE_OK; +} + +CURLcode Curl_rtsp_parseheader(struct connectdata *conn, + char *header) +{ + struct SessionHandle *data = conn->data; + long CSeq = 0; + + if(checkprefix("CSeq:", header)) { + /* Store the received CSeq. Match is verified in rtsp_done */ + int nc; + char *temp = strdup(header); + if(!temp) + return CURLE_OUT_OF_MEMORY; + Curl_strntoupper(temp, temp, sizeof(temp)); + nc = sscanf(temp, "CSEQ: %ld", &CSeq); + free(temp); + if(nc == 1) { + data->state.proto.rtsp->CSeq_recv = CSeq; /* mark the request */ + data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ + } + else { + failf(data, "Unable to read the CSeq header: [%s]", header); + return CURLE_RTSP_CSEQ_ERROR; + } + } + else if(checkprefix("Session:", header)) { + char *start; + + /* Find the first non-space letter */ + start = header + 9; + while(*start && ISSPACE(*start)) + start++; + + if(!start) { + failf(data, "Got a blank Session ID"); + } + else if(data->set.str[STRING_RTSP_SESSION_ID]) { + /* If the Session ID is set, then compare */ + if(strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], + strlen(data->set.str[STRING_RTSP_SESSION_ID])) != 0) { + failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", + start, data->set.str[STRING_RTSP_SESSION_ID]); + return CURLE_RTSP_SESSION_ERROR; + } + } + else { + /* If the Session ID is not set, and we find it in a response, then + set it */ + + /* The session ID can be an alphanumeric or a 'safe' character + * + * RFC 2326 15.1 Base Syntax: + * safe = "\$" | "-" | "_" | "." | "+" + * */ + char *end = start; + while(*end && + (ISALNUM(*end) || *end == '-' || *end == '_' || *end == '.' || + *end == '+' || + (*end == '\\' && *(end + 1) && *(end + 1) == '$' && (++end, 1)))) + end++; + + /* Copy the id substring into a new buffer */ + data->set.str[STRING_RTSP_SESSION_ID] = malloc(end - start + 1); + memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, end - start); + (data->set.str[STRING_RTSP_SESSION_ID])[end - start] = '\0'; + } + } + return CURLE_OK; +} + +#endif /* CURL_DISABLE_RTSP */ diff --git a/lib/rtsp.h b/lib/rtsp.h new file mode 100644 index 000000000..dcc8d392e --- /dev/null +++ b/lib/rtsp.h @@ -0,0 +1,77 @@ +#ifndef __RTSP_H_ +#define __RTSP_H_ + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ +#ifndef CURL_DISABLE_RTSP + + +extern const struct Curl_handler Curl_handler_rtsp; + +/* + * RTSP Connection data + * + * Currently, only used for tracking incomplete RTP data reads + */ +struct rtsp_conn { + char *rtp_buf; + ssize_t rtp_bufsize; + int rtp_channel; +}; + +/**************************************************************************** + * RTSP unique setup + ***************************************************************************/ +struct RTSP { + /* + * http_wrapper MUST be the first element of this structure for the wrap + * logic to work. In this way, we get a cheap polymorphism because + * &(data->state.proto.rtsp) == &(data->state.proto.http) per the C spec + * + * HTTP functions can safely treat this as an HTTP struct, but RTSP aware + * functions can also index into the later elements. + */ + struct HTTP http_wrapper; /*wrap HTTP to do the heavy lifting */ + + long CSeq_sent; /* CSeq of this request */ + long CSeq_recv; /* CSeq received */ +}; + + +CURLcode Curl_rtsp_rtp_readwrite(struct SessionHandle *data, + struct connectdata *conn, + ssize_t *nread, + bool *readmore, + bool *done); + + +/* protocol-specific functions set up to be called by the main engine */ +CURLcode Curl_rtsp(struct connectdata *conn, bool *done); +CURLcode Curl_rtsp_done(struct connectdata *conn, CURLcode, bool premature); +CURLcode Curl_rtsp_connect(struct connectdata *conn, bool *done); +CURLcode Curl_rtsp_disconnect(struct connectdata *conn); + +CURLcode Curl_rtsp_parseheader(struct connectdata *conn, char *header); + +#endif /* CURL_DISABLE_RTSP */ +#endif /* __RTSP_H_ */ diff --git a/lib/sendf.c b/lib/sendf.c index e80da04c7..9b360be79 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -43,6 +43,7 @@ #include "sslgen.h" #include "ssh.h" #include "multiif.h" +#include "rtsp.h" #define _MPRINTF_REPLACE /* use the internal *printf() functions */ #include <curl/mprintf.h> diff --git a/lib/sendf.h b/lib/sendf.h index dab91433d..2ed975ec7 100644 --- a/lib/sendf.h +++ b/lib/sendf.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -48,13 +48,15 @@ void Curl_failf(struct SessionHandle *, const char *fmt, ...); #define failf Curl_failf -#define CLIENTWRITE_BODY 1 -#define CLIENTWRITE_HEADER 2 +#define CLIENTWRITE_BODY (1<<0) +#define CLIENTWRITE_HEADER (1<<1) #define CLIENTWRITE_BOTH (CLIENTWRITE_BODY|CLIENTWRITE_HEADER) CURLcode Curl_client_write(struct connectdata *conn, int type, char *ptr, size_t len); +CURLcode Curl_rtp_client_write(struct connectdata *conn, char *ptr, size_t len); + /* internal read-function, does plain socket only */ int Curl_read_plain(curl_socket_t sockfd, char *buf, diff --git a/lib/setup.h b/lib/setup.h index 8620f83b9..b8d62d417 100644 --- a/lib/setup.h +++ b/lib/setup.h @@ -169,6 +169,7 @@ # define CURL_DISABLE_TELNET # define CURL_DISABLE_DICT # define CURL_DISABLE_FILE +# define CURL_DISABLE_RTSP #endif /* ================================================================ */ diff --git a/lib/strerror.c b/lib/strerror.c index 540886919..b16e04b82 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -270,6 +270,12 @@ curl_easy_strerror(CURLcode error) case CURLE_AGAIN: return "Socket not ready for send/recv"; + case CURLE_RTSP_CSEQ_ERROR: + return "RTSP CSeq mismatch or invalid CSeq"; + + case CURLE_RTSP_SESSION_ERROR: + return "RTSP session error"; + /* error codes not used by current libcurl */ case CURLE_OBSOLETE4: case CURLE_OBSOLETE10: diff --git a/lib/transfer.c b/lib/transfer.c index 3c5821692..e6b2259f8 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -103,6 +103,7 @@ #include "select.h" #include "multiif.h" #include "easyif.h" /* for Curl_convert_to_network prototype */ +#include "rtsp.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -124,7 +125,7 @@ CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp) #ifdef CURL_DOES_CONVERSIONS bool sending_http_headers = FALSE; - if((conn->protocol&PROT_HTTP) && + if((conn->protocol&(PROT_HTTP|PROT_RTSP)) && (data->state.proto.http->sending == HTTPSEND_REQUEST)) { /* We're sending the HTTP request headers, not the data. Remember that so we don't re-translate them into garbage. */ @@ -368,6 +369,7 @@ static CURLcode readwrite_data(struct SessionHandle *data, CURLcode result = CURLE_OK; ssize_t nread; /* number of bytes read */ bool is_empty_data = FALSE; + bool readmore = FALSE; /* used by RTP to signal for more data */ *done = FALSE; @@ -435,6 +437,19 @@ static CURLcode readwrite_data(struct SessionHandle *data, in the flow below before the actual storing is done. */ k->str = k->buf; +#ifndef CURL_DISABLE_RTSP + if(conn->protocol & PROT_RTSP) { + readmore = FALSE; + result = Curl_rtsp_rtp_readwrite(data, conn, &nread, &readmore, done); + if(result) + return result; + if(readmore) + break; + if(*done) + return CURLE_OK; + } +#endif + #ifndef CURL_DISABLE_HTTP /* Since this is a two-state thing, we check if we are parsing headers at the moment or not. */ @@ -456,11 +471,12 @@ static CURLcode readwrite_data(struct SessionHandle *data, is non-headers. */ if(k->str && !k->header && (nread > 0 || is_empty_data)) { + #ifndef CURL_DISABLE_HTTP if(0 == k->bodywrites && !is_empty_data) { /* These checks are only made the first time we are about to write a piece of the body */ - if(conn->protocol&PROT_HTTP) { + if(conn->protocol&(PROT_HTTP|PROT_RTSP)) { /* HTTP-only checks */ if(data->req.newurl) { @@ -747,7 +763,7 @@ static CURLcode readwrite_upload(struct SessionHandle *data, break; } - if(conn->protocol&PROT_HTTP) { + if(conn->protocol&(PROT_HTTP|PROT_RTSP)) { if(data->state.proto.http->sending == HTTPSEND_REQUEST) /* We're sending the HTTP request headers, not the data. Remember that so we don't change the line endings. */ @@ -846,7 +862,7 @@ static CURLcode readwrite_upload(struct SessionHandle *data, /* write to socket (send away data) */ result = Curl_write(conn, - conn->writesockfd, /* socket to send to */ + conn->writesockfd, /* socket to send to */ data->req.upload_fromhere, /* buffer pointer */ data->req.upload_present, /* buffer size */ &bytes_written); /* actually sent */ @@ -1825,14 +1841,15 @@ CURLcode Curl_retry_request(struct connectdata *conn, /* if we're talking upload, we can't do the checks below, unless the protocol is HTTP as when uploading over HTTP we will still get a response */ - if(data->set.upload && !(conn->protocol&PROT_HTTP)) + if(data->set.upload && !(conn->protocol&(PROT_HTTP|PROT_RTSP))) return CURLE_OK; if(/* workaround for broken TLS servers */ data->state.ssl_connect_retry || ((data->req.bytecount + - data->req.headerbytecount == 0) && - conn->bits.reuse && - !data->set.opt_no_body)) { + data->req.headerbytecount == 0) && + conn->bits.reuse && + !data->set.opt_no_body && + data->set.rtspreq != RTSPREQ_RECEIVE)) { /* We got no data, we attempted to re-use a connection and yet we want a "body". This might happen if the connection was left alive when we were done using it before, but that was closed when we wanted to read from @@ -136,6 +136,7 @@ void idn_free (void *ptr); /* prototype from idn-free.h, not provided by #include "inet_ntop.h" #include "http_ntlm.h" #include "socks.h" +#include "rtsp.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -226,6 +227,10 @@ static const struct Curl_handler * const protocols[] = { #endif #endif +#ifndef CURL_DISABLE_RTSP + &Curl_handler_rtsp, +#endif + (struct Curl_handler *) NULL }; @@ -699,6 +704,7 @@ CURLcode Curl_init_userdefined(struct UserDefined *set) set->maxredirs = -1; /* allow any amount by default */ set->httpreq = HTTPREQ_GET; /* Default HTTP request */ + set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */ set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */ set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */ set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */ @@ -2323,6 +2329,114 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, data->set.mail_rcpt = va_arg(param, struct curl_slist *); break; + case CURLOPT_RTSP_REQUEST: + { + /* + * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) + * Would this be better if the RTSPREQ_* were just moved into here? + */ + long curl_rtspreq = va_arg(param, long); + long rtspreq = RTSPREQ_NONE; + switch(curl_rtspreq) { + case CURL_RTSPREQ_OPTIONS: + rtspreq = RTSPREQ_OPTIONS; + break; + + case CURL_RTSPREQ_DESCRIBE: + rtspreq = RTSPREQ_DESCRIBE; + break; + + case CURL_RTSPREQ_ANNOUNCE: + rtspreq = RTSPREQ_ANNOUNCE; + break; + + case CURL_RTSPREQ_SETUP: + rtspreq = RTSPREQ_SETUP; + break; + + case CURL_RTSPREQ_PLAY: + rtspreq = RTSPREQ_PLAY; + break; + + case CURL_RTSPREQ_PAUSE: + rtspreq = RTSPREQ_PAUSE; + break; + + case CURL_RTSPREQ_TEARDOWN: + rtspreq = RTSPREQ_TEARDOWN; + break; + + case CURL_RTSPREQ_GET_PARAMETER: + rtspreq = RTSPREQ_GET_PARAMETER; + break; + + case CURL_RTSPREQ_SET_PARAMETER: + rtspreq = RTSPREQ_SET_PARAMETER; + break; + + case CURL_RTSPREQ_RECORD: + rtspreq = RTSPREQ_RECORD; + break; + + case CURL_RTSPREQ_RECEIVE: + rtspreq = RTSPREQ_RECEIVE; + break; + default: + rtspreq = RTSPREQ_NONE; + } + + data->set.rtspreq = rtspreq; + break; + } + + + case CURLOPT_RTSP_SESSION_ID: + /* + * Set the RTSP Session ID manually. Useful if the application is + * resuming a previously established RTSP session + */ + result = setstropt(&data->set.str[STRING_RTSP_SESSION_ID], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_STREAM_URI: + /* + * Set the Stream URI for the RTSP request. Unless the request is + * for generic server options, the application will need to set this. + */ + result = setstropt(&data->set.str[STRING_RTSP_STREAM_URI], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_TRANSPORT: + /* + * The content of the Transport: header for the RTSP request + */ + result = setstropt(&data->set.str[STRING_RTSP_TRANSPORT], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_CLIENT_CSEQ: + /* + * Set the CSEQ number to issue for the next RTSP request. Useful if the + * application is resuming a previously broken connection. The CSEQ + * will increment from this new number henceforth. + */ + data->state.rtsp_next_client_CSeq = va_arg(param, long); + break; + + case CURLOPT_RTSP_SERVER_CSEQ: + /* Same as the above, but for server-initiated requests */ + data->state.rtsp_next_client_CSeq = va_arg(param, long); + break; + + case CURLOPT_RTPDATA: + data->set.rtp_out = va_arg(param, void *); + break; + case CURLOPT_RTPFUNCTION: + /* Set the user defined RTP write function */ + data->set.fwrite_rtp = va_arg(param, curl_write_callback); + break; default: /* unknown tag and its companion, just ignore: */ result = CURLE_FAILED_INIT; /* correct this */ @@ -2360,6 +2474,7 @@ static void conn_free(struct connectdata *conn) Curl_safefree(conn->allocptr.ref); Curl_safefree(conn->allocptr.host); Curl_safefree(conn->allocptr.cookiehost); + Curl_safefree(conn->allocptr.rtsp_transport); Curl_safefree(conn->trailer); Curl_safefree(conn->host.rawalloc); /* host name buffer */ Curl_safefree(conn->proxy.rawalloc); /* proxy name buffer */ @@ -2500,6 +2615,42 @@ static bool SocketIsDead(curl_socket_t sock) return ret_val; } +#ifndef CURL_DISABLE_RTSP +/* + * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not + * want to block the application forever while receiving a stream. Therefore, + * we cannot assume that an RTSP socket is dead just because it is readable. + * + * Instead, if it is readable, run Curl_getconnectinfo() to peek at the socket + * and distinguish between closed and data. + */ +static bool RTSPConnIsDead(struct connectdata *check) +{ + int sval; + bool ret_val = TRUE; + + sval = Curl_socket_ready(check->sock[FIRSTSOCKET], CURL_SOCKET_BAD, 0); + if(sval == 0) { + /* timeout */ + ret_val = FALSE; + } + else if (sval & CURL_CSELECT_ERR) { + /* socket is in an error state */ + ret_val = TRUE; + } + else if (sval & CURL_CSELECT_IN) { + /* readable with no error. could be closed or could be alive */ + long connectinfo = 0; + Curl_getconnectinfo(check->data, &connectinfo, &check); + if(connectinfo != -1) { + ret_val = FALSE; + } + } + + return ret_val; +} +#endif /* CURL_DISABLE_RTSP */ + static bool IsPipeliningPossible(const struct SessionHandle *handle) { if(handle->multi && Curl_multi_canPipeline(handle->multi) && @@ -2664,7 +2815,15 @@ ConnectionExists(struct SessionHandle *data, /* The check for a dead socket makes sense only if there are no handles in pipeline and the connection isn't already marked in use */ - bool dead = SocketIsDead(check->sock[FIRSTSOCKET]); + bool dead; +#ifndef CURL_DISABLE_RTSP + if(check->protocol & PROT_RTSP) + /* RTSP is a special case due to RTP interleaving */ + dead = RTSPConnIsDead(check); + else +#endif /*CURL_DISABLE_RTSP*/ + dead = SocketIsDead(check->sock[FIRSTSOCKET]); + if(dead) { check->data = data; infof(data, "Connection #%d seems to be dead!\n", i); diff --git a/lib/urldata.h b/lib/urldata.h index 278fd4667..8e3f612ce 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -43,6 +43,7 @@ #define PORT_POP3S 995 #define PORT_SMTP 25 #define PORT_SMTPS 465 /* sometimes called SSMTP */ +#define PORT_RTSP 554 #define DICT_MATCH "/MATCH:" #define DICT_MATCH2 "/M:" @@ -147,6 +148,7 @@ #include "file.h" #include "ssh.h" #include "http.h" +#include "rtsp.h" #ifdef HAVE_GSSAPI # ifdef HAVE_GSSGNU @@ -510,7 +512,8 @@ struct SingleRequest { bool content_range; /* set TRUE if Content-Range: was found */ curl_off_t offset; /* possible resume offset read from the Content-Range: header */ - int httpcode; /* error code from the 'HTTP/1.? XXX' line */ + int httpcode; /* error code from the 'HTTP/1.? XXX' or + 'RTSP/1.? XXX' line */ struct timeval start100; /* time stamp to wait for the 100 code from */ enum expect100 exp100; /* expect 100 continue state */ @@ -672,8 +675,9 @@ struct connectdata { #define PROT_POP3S CURLPROTO_POP3S #define PROT_SMTP CURLPROTO_SMTP #define PROT_SMTPS CURLPROTO_SMTPS +#define PROT_RTSP CURLPROTO_RTSP -/* (1<<17) is currently the highest used bit in the public bitmask. We make +/* (1<<18) is currently the highest used bit in the public bitmask. We make sure we use "private bits" above the public ones to make things easier. */ #define PROT_EXTMASK 0xfffff @@ -718,7 +722,8 @@ struct connectdata { char *proxypasswd; /* proxy password string, allocated */ curl_proxytype proxytype; /* what kind of proxy that is in use */ - int httpversion; /* the HTTP version*10 reported by the server */ + int httpversion; /* the HTTP version*10 reported by the server */ + int rtspversion; /* the RTSP version*10 reported by the server */ struct timeval now; /* "current" time */ struct timeval created; /* creation time */ @@ -750,6 +755,7 @@ struct connectdata { char *ref; /* free later if not NULL! */ char *host; /* free later if not NULL */ char *cookiehost; /* free later if not NULL */ + char *rtsp_transport; /* free later if not NULL */ } allocptr; int sec_complete; /* if kerberos is enabled for this connection */ @@ -825,6 +831,7 @@ struct connectdata { struct imap_conn imapc; struct pop3_conn pop3c; struct smtp_conn smtpc; + struct rtsp_conn rtspc; } proto; int cselect_bits; /* bitmask of socket events */ @@ -844,7 +851,7 @@ struct connectdata { * Struct to keep statistical and informational data. */ struct PureInfo { - int httpcode; /* Recent HTTP or FTP response code */ + int httpcode; /* Recent HTTP, FTP, or RTSP response code */ int httpproxycode; /* response code from proxy when received separate */ int httpversion; /* the http version number X.Y = X*10+Y */ long filetime; /* If requested, this is might get set. Set to -1 if the time @@ -915,6 +922,22 @@ typedef enum { HTTPREQ_LAST /* last in list */ } Curl_HttpReq; +typedef enum { + RTSPREQ_NONE, /* first in list */ + RTSPREQ_OPTIONS, + RTSPREQ_DESCRIBE, + RTSPREQ_ANNOUNCE, + RTSPREQ_SETUP, + RTSPREQ_PLAY, + RTSPREQ_PAUSE, + RTSPREQ_TEARDOWN, + RTSPREQ_GET_PARAMETER, + RTSPREQ_SET_PARAMETER, + RTSPREQ_RECORD, + RTSPREQ_RECEIVE, + RTSPREQ_LAST /* last in list */ +} Curl_RtspReq; + /* * Values that are generated, temporary or calculated internally for a * "session handle" must be defined within the 'struct UrlState'. This struct @@ -1065,6 +1088,11 @@ struct UrlState { this syntax. */ curl_off_t resume_from; /* continue [ftp] transfer from here */ + /* This RTSP state information survives requests and connections */ + long rtsp_next_client_CSeq; /* the session's next client CSeq */ + long rtsp_next_server_CSeq; /* the session's next server CSeq */ + long rtsp_CSeq_recv; /* most recent CSeq received */ + /* Protocol specific data. * ************************************************************************* @@ -1075,6 +1103,7 @@ struct UrlState { union { struct HTTP *http; struct HTTP *https; /* alias, just for the sake of being more readable */ + struct RTSP *rtsp; struct FTP *ftp; /* void *tftp; not used */ struct FILEPROTO *file; @@ -1125,7 +1154,7 @@ enum dupstring { STRING_CERT_TYPE, /* format for certificate (default: PEM)*/ STRING_COOKIE, /* HTTP cookie string to send */ STRING_COOKIEJAR, /* dump all cookies to this file */ - STRING_CUSTOMREQUEST, /* HTTP/FTP request/method to use */ + STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ STRING_DEVICE, /* local network interface/address to use */ STRING_ENCODING, /* Accept-Encoding string */ STRING_FTP_ACCOUNT, /* ftp account data */ @@ -1156,6 +1185,9 @@ enum dupstring { STRING_PROXYPASSWORD, /* Proxy <password>, if used */ STRING_NOPROXY, /* List of hosts which should not use the proxy, if used */ + STRING_RTSP_SESSION_ID, /* Session ID to use */ + STRING_RTSP_STREAM_URI, /* Stream URI for this request */ + STRING_RTSP_TRANSPORT, /* Transport for this session */ #ifdef USE_LIBSSH2 STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ @@ -1181,6 +1213,7 @@ struct UserDefined { void *out; /* the fetched file goes here */ void *in; /* the uploaded file is read from here */ void *writeheader; /* write the header to this if non-NULL */ + void *rtp_out; /* write RTP to this if non-NULL */ long use_port; /* which port to use (when not using default) */ long httpauth; /* what kind of HTTP authentication to use (bitmask) */ long proxyauth; /* what kind of proxy authentication to use (bitmask) */ @@ -1202,6 +1235,7 @@ struct UserDefined { 'localport' one can't be bind()ed */ curl_write_callback fwrite_func; /* function that stores the output */ curl_write_callback fwrite_header; /* function that stores headers */ + curl_write_callback fwrite_rtp; /* function that stores interleaved RTP */ curl_read_callback fread_func; /* function that reads the input */ curl_progress_callback fprogress; /* function for progress information */ curl_debug_callback fdebug; /* function that write informational data */ @@ -1338,6 +1372,9 @@ struct UserDefined { long socks5_gssapi_nec; /* flag to support nec socks5 server */ #endif struct curl_slist *mail_rcpt; /* linked list of mail recipients */ + /* Common RTSP header options */ + Curl_RtspReq rtspreq; /* RTSP request type */ + long rtspversion; /* like httpversion, for RTSP */ }; struct Names { diff --git a/lib/version.c b/lib/version.c index 19eb3d4aa..2b9ebf893 100644 --- a/lib/version.c +++ b/lib/version.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -143,6 +143,9 @@ static const char * const protocols[] = { #ifndef CURL_DISABLE_FILE "file", #endif +#ifndef CURL_DISABLE_RTSP + "rtsp", +#endif #ifdef USE_SSL #ifndef CURL_DISABLE_HTTP |