diff options
| author | Tim Redfern <tim@eclectronics.org> | 2013-12-29 12:19:38 +0000 |
|---|---|---|
| committer | Tim Redfern <tim@eclectronics.org> | 2013-12-29 12:19:38 +0000 |
| commit | f7813a5324be39d13ab536c245d15dfc602a7849 (patch) | |
| tree | fad99148b88823d34a5df2f0a25881a002eb291b /ffmpeg/libavformat/http.c | |
| parent | b7a5a477b8ff4d4e3028b9dfb9a9df0a41463f92 (diff) | |
basic type mechanism working
Diffstat (limited to 'ffmpeg/libavformat/http.c')
| -rw-r--r-- | ffmpeg/libavformat/http.c | 252 |
1 files changed, 212 insertions, 40 deletions
diff --git a/ffmpeg/libavformat/http.c b/ffmpeg/libavformat/http.c index 1e3cff7..0619e2a 100644 --- a/ffmpeg/libavformat/http.c +++ b/ffmpeg/libavformat/http.c @@ -29,6 +29,10 @@ #include "url.h" #include "libavutil/opt.h" +#if CONFIG_ZLIB +#include <zlib.h> +#endif + /* XXX: POST protocol is not completely implemented because ffmpeg uses only a subset of it. */ @@ -49,7 +53,9 @@ typedef struct { char *content_type; char *user_agent; int64_t off, filesize; - char location[MAX_URL_SIZE]; + int icy_data_read; ///< how much data was read since last ICY metadata packet + int icy_metaint; ///< after how many bytes of read data a new metadata packet will be found + char *location; HTTPAuthState auth_state; HTTPAuthState proxy_auth_state; char *headers; @@ -62,26 +68,43 @@ typedef struct { uint8_t *post_data; int post_datalen; int is_akamai; - int rw_timeout; + int is_mediagateway; char *mime_type; char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name) + int icy; + char *icy_metadata_headers; + char *icy_metadata_packet; +#if CONFIG_ZLIB + int compressed; + z_stream inflate_stream; + uint8_t *inflate_buffer; +#endif + AVDictionary *chained_options; + int send_expect_100; } HTTPContext; #define OFFSET(x) offsetof(HTTPContext, x) #define D AV_OPT_FLAG_DECODING_PARAM #define E AV_OPT_FLAG_ENCODING_PARAM -#define DEC AV_OPT_FLAG_DECODING_PARAM +#define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION) static const AVOption options[] = { {"seekable", "control seekability of connection", OFFSET(seekable), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, D }, {"chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, {"headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, {"content_type", "force a content type", OFFSET(content_type), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, -{"user-agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC}, +{"user-agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = DEFAULT_USER_AGENT}, 0, 0, D }, {"multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D|E }, {"post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D|E }, -{"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, {"mime_type", "set MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 }, -{"cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 }, +{"cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, {0}, 0, 0, D }, +{"icy", "request ICY metadata", OFFSET(icy), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D }, +{"icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 }, +{"icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 }, +{"auth_type", "HTTP authentication type", OFFSET(auth_state.auth_type), AV_OPT_TYPE_INT, {.i64 = HTTP_AUTH_NONE}, HTTP_AUTH_NONE, HTTP_AUTH_BASIC, D|E, "auth_type" }, +{"none", "No auth method set, autodetect", 0, AV_OPT_TYPE_CONST, {.i64 = HTTP_AUTH_NONE}, 0, 0, D|E, "auth_type" }, +{"basic", "HTTP basic authentication", 0, AV_OPT_TYPE_CONST, {.i64 = HTTP_AUTH_BASIC}, 0, 0, D|E, "auth_type" }, +{"send_expect_100", "Force sending an Expect: 100-continue header for POST", OFFSET(send_expect_100), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, +{"location", "The actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, {NULL} }; #define HTTP_CLASS(flavor)\ @@ -109,7 +132,7 @@ void ff_http_init_auth_state(URLContext *dest, const URLContext *src) } /* return non zero if error */ -static int http_open_cnx(URLContext *h) +static int http_open_cnx(URLContext *h, AVDictionary **options) { const char *path, *proxy_path, *lower_proto = "tcp", *local_path; char hostname[1024], hoststr[1024], proto[10]; @@ -159,15 +182,8 @@ static int http_open_cnx(URLContext *h) ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL); if (!s->hd) { - AVDictionary *opts = NULL; - char opts_format[20]; - if (s->rw_timeout != -1) { - snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout); - av_dict_set(&opts, "timeout", opts_format, 0); - } /* if option is not given, don't pass it and let tcp use its own default */ err = ffurl_open(&s->hd, buf, AVIO_FLAG_READ_WRITE, - &h->interrupt_callback, &opts); - av_dict_free(&opts); + &h->interrupt_callback, options); if (err < 0) goto fail; } @@ -216,16 +232,27 @@ static int http_open_cnx(URLContext *h) int ff_http_do_new_request(URLContext *h, const char *uri) { HTTPContext *s = h->priv_data; + AVDictionary *options = NULL; + int ret; s->off = 0; - av_strlcpy(s->location, uri, sizeof(s->location)); - - return http_open_cnx(h); + s->icy_data_read = 0; + av_free(s->location); + s->location = av_strdup(uri); + if (!s->location) + return AVERROR(ENOMEM); + + av_dict_copy(&options, s->chained_options, 0); + ret = http_open_cnx(h, &options); + av_dict_free(&options); + return ret; } -static int http_open(URLContext *h, const char *uri, int flags) +static int http_open(URLContext *h, const char *uri, int flags, + AVDictionary **options) { HTTPContext *s = h->priv_data; + int ret; if( s->seekable == 1 ) h->is_streamed = 0; @@ -233,7 +260,11 @@ static int http_open(URLContext *h, const char *uri, int flags) h->is_streamed = 1; s->filesize = -1; - av_strlcpy(s->location, uri, sizeof(s->location)); + s->location = av_strdup(uri); + if (!s->location) + return AVERROR(ENOMEM); + if (options) + av_dict_copy(&s->chained_options, *options, 0); if (s->headers) { int len = strlen(s->headers); @@ -241,7 +272,10 @@ static int http_open(URLContext *h, const char *uri, int flags) av_log(h, AV_LOG_WARNING, "No trailing CRLF found in HTTP header.\n"); } - return http_open_cnx(h); + ret = http_open_cnx(h, options); + if (ret < 0) + av_dict_free(&s->chained_options); + return ret; } static int http_getc(HTTPContext *s) { @@ -304,7 +338,7 @@ static int process_line(URLContext *h, char *line, int line_count, p++; s->http_code = strtol(p, &end, 10); - av_dlog(NULL, "http_code=%d\n", s->http_code); + av_log(h, AV_LOG_DEBUG, "http_code=%d\n", s->http_code); /* error codes are 4xx and 5xx, but regard 401 as a success, so we * don't abort until all headers have been parsed. */ @@ -328,7 +362,14 @@ static int process_line(URLContext *h, char *line, int line_count, while (av_isspace(*p)) p++; if (!av_strcasecmp(tag, "Location")) { - av_strlcpy(s->location, p, sizeof(s->location)); + char redirected_location[MAX_URL_SIZE], *new_loc; + ff_make_absolute_url(redirected_location, sizeof(redirected_location), + s->location, p); + new_loc = av_strdup(redirected_location); + if (!new_loc) + return AVERROR(ENOMEM); + av_free(s->location); + s->location = new_loc; *new_location = 1; } else if (!av_strcasecmp (tag, "Content-Length") && s->filesize == -1) { s->filesize = strtoll(p, NULL, 10); @@ -357,8 +398,12 @@ static int process_line(URLContext *h, char *line, int line_count, } else if (!av_strcasecmp (tag, "Connection")) { if (!strcmp(p, "close")) s->willclose = 1; - } else if (!av_strcasecmp (tag, "Server") && !av_strcasecmp (p, "AkamaiGHost")) { - s->is_akamai = 1; + } else if (!av_strcasecmp (tag, "Server")) { + if (!av_strcasecmp (p, "AkamaiGHost")) { + s->is_akamai = 1; + } else if (!av_strncasecmp (p, "MediaGateway", 12)) { + s->is_mediagateway = 1; + } } else if (!av_strcasecmp (tag, "Content-Type")) { av_free(s->mime_type); s->mime_type = av_strdup(p); } else if (!av_strcasecmp (tag, "Set-Cookie")) { @@ -375,6 +420,40 @@ static int process_line(URLContext *h, char *line, int line_count, snprintf(s->cookies, str_size, "%s\n%s", tmp, p); av_free(tmp); } + } else if (!av_strcasecmp (tag, "Icy-MetaInt")) { + s->icy_metaint = strtoll(p, NULL, 10); + } else if (!av_strncasecmp(tag, "Icy-", 4)) { + // Concat all Icy- header lines + char *buf = av_asprintf("%s%s: %s\n", + s->icy_metadata_headers ? s->icy_metadata_headers : "", tag, p); + if (!buf) + return AVERROR(ENOMEM); + av_freep(&s->icy_metadata_headers); + s->icy_metadata_headers = buf; + } else if (!av_strcasecmp (tag, "Content-Encoding")) { + if (!av_strncasecmp(p, "gzip", 4) || !av_strncasecmp(p, "deflate", 7)) { +#if CONFIG_ZLIB + s->compressed = 1; + inflateEnd(&s->inflate_stream); + if (inflateInit2(&s->inflate_stream, 32 + 15) != Z_OK) { + av_log(h, AV_LOG_WARNING, "Error during zlib initialisation: %s\n", + s->inflate_stream.msg); + return AVERROR(ENOSYS); + } + if (zlibCompileFlags() & (1 << 17)) { + av_log(h, AV_LOG_WARNING, "Your zlib was compiled without gzip support.\n"); + return AVERROR(ENOSYS); + } +#else + av_log(h, AV_LOG_WARNING, "Compressed (%s) content, need zlib with gzip support\n", p); + return AVERROR(ENOSYS); +#endif + } else if (!av_strncasecmp(p, "identity", 8)) { + // The normal, no-encoding case (although servers shouldn't include + // the header at all if this is the case). + } else { + av_log(h, AV_LOG_WARNING, "Unknown content coding: %s\n", p); + } } } return 1; @@ -421,6 +500,8 @@ static int get_cookies(HTTPContext *s, char **cookies, const char *path, cvalue = av_strdup(param); } } + if (!cdomain) + cdomain = av_strdup(domain); // ensure all of the necessary values are valid if (!cdomain || !cpath || !cvalue) { @@ -495,7 +576,7 @@ static int http_read_header(URLContext *h, int *new_location) if ((err = http_get_line(s, line, sizeof(line))) < 0) return err; - av_dlog(NULL, "header='%s'\n", line); + av_log(h, AV_LOG_DEBUG, "header='%s'\n", line); err = process_line(h, line, s->line_count, new_location); if (err < 0) @@ -505,6 +586,9 @@ static int http_read_header(URLContext *h, int *new_location) s->line_count++; } + if (s->seekable == -1 && s->is_mediagateway && s->filesize == 2000000000) + h->is_streamed = 1; /* we can in fact _not_ seek */ + return err; } @@ -519,6 +603,7 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, int64_t off = s->off; int len = 0; const char *method; + int send_expect_100 = 0; /* send http header */ @@ -536,12 +621,22 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, method); proxyauthstr = ff_http_auth_create_response(&s->proxy_auth_state, proxyauth, local_path, method); + if (post && !s->post_data) { + send_expect_100 = s->send_expect_100; + /* The user has supplied authentication but we don't know the auth type, + * send Expect: 100-continue to get the 401 response including the + * WWW-Authenticate header, or an 100 continue if no auth actually + * is needed. */ + if (auth && *auth && + s->auth_state.auth_type == HTTP_AUTH_NONE && + s->http_code != 401) + send_expect_100 = 1; + } /* set default headers if needed */ if (!has_header(s->headers, "\r\nUser-Agent: ")) len += av_strlcatf(headers + len, sizeof(headers) - len, - "User-Agent: %s\r\n", - s->user_agent ? s->user_agent : LIBAVFORMAT_IDENT); + "User-Agent: %s\r\n", s->user_agent); if (!has_header(s->headers, "\r\nAccept: ")) len += av_strlcpy(headers + len, "Accept: */*\r\n", sizeof(headers) - len); @@ -551,6 +646,9 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->seekable == -1)) len += av_strlcatf(headers + len, sizeof(headers) - len, "Range: bytes=%"PRId64"-\r\n", s->off); + if (send_expect_100 && !has_header(s->headers, "\r\nExpect: ")) + len += av_strlcatf(headers + len, sizeof(headers) - len, + "Expect: 100-continue\r\n"); if (!has_header(s->headers, "\r\nConnection: ")) { if (s->multiple_requests) { @@ -579,6 +677,10 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, av_free(cookies); } } + if (!has_header(s->headers, "\r\nIcy-MetaData: ") && s->icy) { + len += av_strlcatf(headers + len, sizeof(headers) - len, + "Icy-MetaData: %d\r\n", 1); + } /* now add in custom headers */ if (s->headers) @@ -600,6 +702,9 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, av_freep(&authstr); av_freep(&proxyauthstr); + + av_log(h, AV_LOG_DEBUG, "request: %s\n", s->buffer); + if ((err = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0) return err; @@ -612,11 +717,12 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, s->buf_end = s->buffer; s->line_count = 0; s->off = 0; + s->icy_data_read = 0; s->filesize = -1; s->willclose = 0; s->end_chunked_post = 0; s->end_header = 0; - if (post && !s->post_data) { + if (post && !s->post_data && !send_expect_100) { /* Pretend that it did work. We didn't read any header yet, since * we've still to send the POST data, but the code calling this * function will check http_code after we return. */ @@ -651,12 +757,45 @@ static int http_buf_read(URLContext *h, uint8_t *buf, int size) } if (len > 0) { s->off += len; + s->icy_data_read += len; if (s->chunksize > 0) s->chunksize -= len; } return len; } +#if CONFIG_ZLIB +#define DECOMPRESS_BUF_SIZE (256 * 1024) +static int http_buf_read_compressed(URLContext *h, uint8_t *buf, int size) +{ + HTTPContext *s = h->priv_data; + int ret; + + if (!s->inflate_buffer) { + s->inflate_buffer = av_malloc(DECOMPRESS_BUF_SIZE); + if (!s->inflate_buffer) + return AVERROR(ENOMEM); + } + + if (s->inflate_stream.avail_in == 0) { + int read = http_buf_read(h, s->inflate_buffer, DECOMPRESS_BUF_SIZE); + if (read <= 0) + return read; + s->inflate_stream.next_in = s->inflate_buffer; + s->inflate_stream.avail_in = read; + } + + s->inflate_stream.avail_out = size; + s->inflate_stream.next_out = buf; + + ret = inflate(&s->inflate_stream, Z_SYNC_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + av_log(h, AV_LOG_WARNING, "inflate return value: %d, %s\n", ret, s->inflate_stream.msg); + + return size - s->inflate_stream.avail_out; +} +#endif + static int http_read(URLContext *h, uint8_t *buf, int size) { HTTPContext *s = h->priv_data; @@ -692,6 +831,36 @@ static int http_read(URLContext *h, uint8_t *buf, int size) } size = FFMIN(size, s->chunksize); } + if (s->icy_metaint > 0) { + int remaining = s->icy_metaint - s->icy_data_read; /* until next metadata packet */ + if (!remaining) { + // The metadata packet is variable sized. It has a 1 byte header + // which sets the length of the packet (divided by 16). If it's 0, + // the metadata doesn't change. After the packet, icy_metaint bytes + // of normal data follow. + int ch = http_getc(s); + if (ch < 0) + return ch; + if (ch > 0) { + char data[255 * 16 + 1]; + int n; + int ret; + ch *= 16; + for (n = 0; n < ch; n++) + data[n] = http_getc(s); + data[ch + 1] = 0; + if ((ret = av_opt_set(s, "icy_metadata_packet", data, 0)) < 0) + return ret; + } + s->icy_data_read = 0; + remaining = s->icy_metaint; + } + size = FFMIN(size, remaining); + } +#if CONFIG_ZLIB + if (s->compressed) + return http_buf_read_compressed(h, buf, size); +#endif return http_buf_read(h, buf, size); } @@ -743,6 +912,11 @@ static int http_close(URLContext *h) int ret = 0; HTTPContext *s = h->priv_data; +#if CONFIG_ZLIB + inflateEnd(&s->inflate_stream); + av_freep(&s->inflate_buffer); +#endif + if (!s->end_chunked_post) { /* Close the write direction by sending the end of chunked encoding. */ ret = http_shutdown(h, h->flags); @@ -750,6 +924,7 @@ static int http_close(URLContext *h) if (s->hd) ffurl_closep(&s->hd); + av_dict_free(&s->chained_options); return ret; } @@ -760,6 +935,7 @@ static int64_t http_seek(URLContext *h, int64_t off, int whence) int64_t old_off = s->off; uint8_t old_buf[BUFFER_SIZE]; int old_buf_size; + AVDictionary *options = NULL; if (whence == AVSEEK_SIZE) return s->filesize; @@ -777,7 +953,9 @@ static int64_t http_seek(URLContext *h, int64_t off, int whence) s->off = off; /* if it fails, continue on old connection */ - if (http_open_cnx(h) < 0) { + av_dict_copy(&options, s->chained_options, 0); + if (http_open_cnx(h, &options) < 0) { + av_dict_free(&options); memcpy(s->buffer, old_buf, old_buf_size); s->buf_ptr = s->buffer; s->buf_end = s->buffer + old_buf_size; @@ -785,6 +963,7 @@ static int64_t http_seek(URLContext *h, int64_t off, int whence) s->off = old_off; return -1; } + av_dict_free(&options); ffurl_close(old_hd); return off; } @@ -799,7 +978,7 @@ http_get_file_handle(URLContext *h) #if CONFIG_HTTP_PROTOCOL URLProtocol ff_http_protocol = { .name = "http", - .url_open = http_open, + .url_open2 = http_open, .url_read = http_read, .url_write = http_write, .url_seek = http_seek, @@ -814,7 +993,7 @@ URLProtocol ff_http_protocol = { #if CONFIG_HTTPS_PROTOCOL URLProtocol ff_https_protocol = { .name = "https", - .url_open = http_open, + .url_open2 = http_open, .url_read = http_read, .url_write = http_write, .url_seek = http_seek, @@ -846,8 +1025,6 @@ static int http_proxy_open(URLContext *h, const char *uri, int flags) HTTPAuthType cur_auth_type; char *authstr; int new_loc; - AVDictionary *opts = NULL; - char opts_format[20]; if( s->seekable == 1 ) h->is_streamed = 0; @@ -864,13 +1041,8 @@ static int http_proxy_open(URLContext *h, const char *uri, int flags) ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port, NULL); redo: - if (s->rw_timeout != -1) { - snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout); - av_dict_set(&opts, "timeout", opts_format, 0); - } /* if option is not given, don't pass it and let tcp use its own default */ ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE, - &h->interrupt_callback, &opts); - av_dict_free(&opts); + &h->interrupt_callback, NULL); if (ret < 0) return ret; |
