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 /lib | |
parent | e09718d457f5ba512920c9ed0a0db5c8ca6dcc53 (diff) |
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.
Diffstat (limited to 'lib')
-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 |
14 files changed, 1369 insertions, 205 deletions
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 |