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