]> git.sesse.net Git - ffmpeg/blobdiff - libavformat/http.c
avformat/http: Remove deprecated "user-agent" option
[ffmpeg] / libavformat / http.c
index 072a6ce0b92f408a1f0991a611847db0cac03162..1fc95c768cdea739b3090e40da1a438b693d1ef8 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "libavutil/avassert.h"
 #include "libavutil/avstring.h"
+#include "libavutil/bprint.h"
 #include "libavutil/opt.h"
 #include "libavutil/time.h"
 #include "libavutil/parseutils.h"
@@ -45,7 +46,7 @@
 /* The IO buffer size is unrelated to the max URL size in itself, but needs
  * to be large enough to fit the full request headers (including long
  * path names). */
-#define BUFFER_SIZE   MAX_URL_SIZE
+#define BUFFER_SIZE   (MAX_URL_SIZE + HTTP_HEADERS_SIZE)
 #define MAX_REDIRECTS 8
 #define HTTP_SINGLE   1
 #define HTTP_MUTLI    2
@@ -77,9 +78,6 @@ typedef struct HTTPContext {
     char *http_version;
     char *user_agent;
     char *referer;
-#if FF_API_HTTP_USER_AGENT
-    char *user_agent_deprecated;
-#endif
     char *content_type;
     /* Set if the server correctly handles Connection: close and will close
      * the connection after feeding us the content. */
@@ -113,12 +111,15 @@ typedef struct HTTPContext {
     uint8_t *inflate_buffer;
 #endif /* CONFIG_ZLIB */
     AVDictionary *chained_options;
+    /* -1 = try to send if applicable, 0 = always disabled, 1 = always enabled */
     int send_expect_100;
     char *method;
     int reconnect;
     int reconnect_at_eof;
+    int reconnect_on_network_error;
     int reconnect_streamed;
     int reconnect_delay_max;
+    char *reconnect_on_http_error;
     int listen;
     char *resource;
     int reply_code;
@@ -140,9 +141,6 @@ static const AVOption options[] = {
     { "content_type", "set a specific content type for the POST messages", OFFSET(content_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
     { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D },
     { "referer", "override referer header", OFFSET(referer), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
-#if FF_API_HTTP_USER_AGENT
-    { "user-agent", "use the \"user_agent\" option instead", OFFSET(user_agent_deprecated), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D|AV_OPT_FLAG_DEPRECATED },
-#endif
     { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D | E },
     { "post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D | E },
     { "mime_type", "export the MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY },
@@ -155,13 +153,15 @@ static const AVOption options[] = {
     { "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_BOOL, { .i64 = 0 }, 0, 1, E },
+    { "send_expect_100", "Force sending an Expect: 100-continue header for POST", OFFSET(send_expect_100), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, E },
     { "location", "The actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
     { "offset", "initial byte offset", OFFSET(off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
     { "end_offset", "try to limit the request to bytes preceding this offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
     { "method", "Override the HTTP method or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
     { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
     { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
+    { "reconnect_on_network_error", "auto reconnect in case of tcp/tls error during connect", OFFSET(reconnect_on_network_error), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
+    { "reconnect_on_http_error", "list of http status codes to reconnect on", OFFSET(reconnect_on_http_error), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
     { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
     { "reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, UINT_MAX/1000/1000, D },
     { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D | E },
@@ -189,9 +189,10 @@ void ff_http_init_auth_state(URLContext *dest, const URLContext *src)
 static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
 {
     const char *path, *proxy_path, *lower_proto = "tcp", *local_path;
+    char *hashmark;
     char hostname[1024], hoststr[1024], proto[10];
     char auth[1024], proxyauth[1024] = "";
-    char path1[MAX_URL_SIZE];
+    char path1[MAX_URL_SIZE], sanitized_path[MAX_URL_SIZE];
     char buf[1024], urlbuf[MAX_URL_SIZE];
     int port, use_proxy, err, location_changed = 0;
     HTTPContext *s = h->priv_data;
@@ -210,14 +211,28 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
         use_proxy   = 0;
         if (port < 0)
             port = 443;
+        /* pass http_proxy to underlying protocol */
+        if (s->http_proxy) {
+            err = av_dict_set(options, "http_proxy", s->http_proxy, 0);
+            if (err < 0)
+                return err;
+        }
     }
     if (port < 0)
         port = 80;
 
-    if (path1[0] == '\0')
+    hashmark = strchr(path1, '#');
+    if (hashmark)
+        *hashmark = '\0';
+
+    if (path1[0] == '\0') {
         path = "/";
-    else
+    } else if (path1[0] == '?') {
+        snprintf(sanitized_path, sizeof(sanitized_path), "/%s", path1);
+        path = sanitized_path;
+    } else {
         path = path1;
+    }
     local_path = path;
     if (use_proxy) {
         /* Reassemble the request URL without auth string - we don't
@@ -247,21 +262,73 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
     return location_changed;
 }
 
+static int http_should_reconnect(HTTPContext *s, int err)
+{
+    const char *status_group;
+    char http_code[4];
+
+    switch (err) {
+    case AVERROR_HTTP_BAD_REQUEST:
+    case AVERROR_HTTP_UNAUTHORIZED:
+    case AVERROR_HTTP_FORBIDDEN:
+    case AVERROR_HTTP_NOT_FOUND:
+    case AVERROR_HTTP_OTHER_4XX:
+        status_group = "4xx";
+        break;
+
+    case AVERROR_HTTP_SERVER_ERROR:
+        status_group = "5xx";
+        break;
+
+    default:
+        return s->reconnect_on_network_error;
+    }
+
+    if (!s->reconnect_on_http_error)
+        return 0;
+
+    if (av_match_list(status_group, s->reconnect_on_http_error, ',') > 0)
+        return 1;
+
+    snprintf(http_code, sizeof(http_code), "%d", s->http_code);
+
+    return av_match_list(http_code, s->reconnect_on_http_error, ',') > 0;
+}
+
 /* return non zero if error */
 static int http_open_cnx(URLContext *h, AVDictionary **options)
 {
     HTTPAuthType cur_auth_type, cur_proxy_auth_type;
     HTTPContext *s = h->priv_data;
     int location_changed, attempts = 0, redirects = 0;
+    int reconnect_delay = 0;
+    uint64_t off;
+
 redo:
     av_dict_copy(options, s->chained_options, 0);
 
     cur_auth_type       = s->auth_state.auth_type;
     cur_proxy_auth_type = s->auth_state.auth_type;
 
+    off = s->off;
     location_changed = http_open_cnx_internal(h, options);
-    if (location_changed < 0)
-        goto fail;
+    if (location_changed < 0) {
+        if (!http_should_reconnect(s, location_changed) ||
+            reconnect_delay > s->reconnect_delay_max)
+            goto fail;
+
+        av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s).\n", off, reconnect_delay);
+        location_changed = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, &h->interrupt_callback);
+        if (location_changed != AVERROR(ETIMEDOUT))
+            goto fail;
+        reconnect_delay = 1 + 2 * reconnect_delay;
+
+        /* restore the offset (http_connect resets it) */
+        s->off = off;
+
+        ffurl_closep(&s->hd);
+        goto redo;
+    }
 
     attempts++;
     if (s->http_code == 401) {
@@ -281,7 +348,7 @@ redo:
             goto fail;
     }
     if ((s->http_code == 301 || s->http_code == 302 ||
-         s->http_code == 303 || s->http_code == 307) &&
+         s->http_code == 303 || s->http_code == 307 || s->http_code == 308) &&
         location_changed == 1) {
         /* url moved, get next */
         ffurl_closep(&s->hd);
@@ -303,8 +370,27 @@ fail:
         return location_changed;
     return ff_http_averror(s->http_code, AVERROR(EIO));
 }
+int ff_http_get_shutdown_status(URLContext *h)
+{
+    int ret = 0;
+    HTTPContext *s = h->priv_data;
+
+    /* flush the receive buffer when it is write only mode */
+    char buf[1024];
+    int read_ret;
+    read_ret = ffurl_read(s->hd, buf, sizeof(buf));
+    if (read_ret < 0) {
+        ret = read_ret;
+    }
+
+    return ret;
+}
+
+int ff_http_do_new_request(URLContext *h, const char *uri) {
+    return ff_http_do_new_request2(h, uri, NULL);
+}
 
-int ff_http_do_new_request(URLContext *h, const char *uri)
+int ff_http_do_new_request2(URLContext *h, const char *uri, AVDictionary **opts)
 {
     HTTPContext *s = h->priv_data;
     AVDictionary *options = NULL;
@@ -349,6 +435,9 @@ int ff_http_do_new_request(URLContext *h, const char *uri)
     if (!s->location)
         return AVERROR(ENOMEM);
 
+    if ((ret = av_opt_set_dict(s, opts)) < 0)
+        return ret;
+
     av_log(s, AV_LOG_INFO, "Opening \'%s\' for %s\n", uri, h->flags & AVIO_FLAG_WRITE ? "writing" : "reading");
     ret = http_open_cnx(h, &options);
     av_dict_free(&options);
@@ -544,7 +633,7 @@ static int http_open(URLContext *h, const char *uri, int flags,
                    "No trailing CRLF found in HTTP header. Adding it.\n");
             ret = av_reallocp(&s->headers, len + 3);
             if (ret < 0)
-                return ret;
+                goto bail_out;
             s->headers[len]     = '\r';
             s->headers[len + 1] = '\n';
             s->headers[len + 2] = '\0';
@@ -555,6 +644,7 @@ static int http_open(URLContext *h, const char *uri, int flags,
         return http_listen(h, uri, flags, options);
     }
     ret = http_open_cnx(h, options);
+bail_out:
     if (ret < 0)
         av_dict_free(&s->chained_options);
     return ret;
@@ -753,6 +843,7 @@ static int parse_set_cookie_expiry_time(const char *exp_str, struct tm *buf)
 static int parse_set_cookie(const char *set_cookie, AVDictionary **dict)
 {
     char *param, *next_param, *cstr, *back;
+    char *saveptr = NULL;
 
     if (!set_cookie[0])
         return 0;
@@ -770,8 +861,9 @@ static int parse_set_cookie(const char *set_cookie, AVDictionary **dict)
     }
 
     next_param = cstr;
-    while ((param = av_strtok(next_param, ";", &next_param))) {
+    while ((param = av_strtok(next_param, ";", &saveptr))) {
         char *name, *value;
+        next_param = NULL;
         param += strspn(param, WHITESPACES);
         if ((name = av_strtok(param, "=", &value))) {
             if (av_dict_set(dict, name, value, 0) < 0) {
@@ -1031,6 +1123,7 @@ static int get_cookies(HTTPContext *s, char **cookies, const char *path,
     // Set-Cookie fields will result in multiple values delimited by a newline
     int ret = 0;
     char *cookie, *set_cookies, *next;
+    char *saveptr = NULL;
 
     // destroy any cookies in the dictionary.
     av_dict_free(&s->cookie_dict);
@@ -1043,10 +1136,11 @@ static int get_cookies(HTTPContext *s, char **cookies, const char *path,
         return AVERROR(ENOMEM);
 
     *cookies = NULL;
-    while ((cookie = av_strtok(next, "\n", &next)) && !ret) {
+    while ((cookie = av_strtok(next, "\n", &saveptr)) && !ret) {
         AVDictionary *cookie_params = NULL;
         AVDictionaryEntry *cookie_entry, *e;
 
+        next = NULL;
         // store the cookie in a dict in case it is updated in the response
         if (parse_cookie(s, cookie, &s->cookie_dict))
             av_log(s, AV_LOG_WARNING, "Unable to parse '%s'\n", cookie);
@@ -1146,19 +1240,50 @@ static int http_read_header(URLContext *h, int *new_location)
     return err;
 }
 
+/**
+ * Escape unsafe characters in path in order to pass them safely to the HTTP
+ * request. Insipred by the algorithm in GNU wget:
+ * - escape "%" characters not followed by two hex digits
+ * - escape all "unsafe" characters except which are also "reserved"
+ * - pass through everything else
+ */
+static void bprint_escaped_path(AVBPrint *bp, const char *path)
+{
+#define NEEDS_ESCAPE(ch) \
+    ((ch) <= ' ' || (ch) >= '\x7f' || \
+     (ch) == '"' || (ch) == '%' || (ch) == '<' || (ch) == '>' || (ch) == '\\' || \
+     (ch) == '^' || (ch) == '`' || (ch) == '{' || (ch) == '}' || (ch) == '|')
+    while (*path) {
+        char buf[1024];
+        char *q = buf;
+        while (*path && q - buf < sizeof(buf) - 4) {
+            if (path[0] == '%' && av_isxdigit(path[1]) && av_isxdigit(path[2])) {
+                *q++ = *path++;
+                *q++ = *path++;
+                *q++ = *path++;
+            } else if (NEEDS_ESCAPE(*path)) {
+                q += snprintf(q, 4, "%%%02X", (uint8_t)*path++);
+            } else {
+                *q++ = *path++;
+            }
+        }
+        av_bprint_append_data(bp, buf, q - buf);
+    }
+}
+
 static int http_connect(URLContext *h, const char *path, const char *local_path,
                         const char *hoststr, const char *auth,
                         const char *proxyauth, int *new_location)
 {
     HTTPContext *s = h->priv_data;
     int post, err;
-    char headers[HTTP_HEADERS_SIZE] = "";
+    AVBPrint request;
     char *authstr = NULL, *proxyauthstr = NULL;
     uint64_t off = s->off;
-    int len = 0;
     const char *method;
     int send_expect_100 = 0;
-    int ret;
+
+    av_bprint_init_for_buffer(&request, s->buffer, sizeof(s->buffer));
 
     /* send http header */
     post = h->flags & AVIO_FLAG_WRITE;
@@ -1179,112 +1304,90 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
                                                 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;
-    }
 
-#if FF_API_HTTP_USER_AGENT
-    if (strcmp(s->user_agent_deprecated, DEFAULT_USER_AGENT)) {
-        s->user_agent = av_strdup(s->user_agent_deprecated);
+     if (post && !s->post_data) {
+        if (s->send_expect_100 != -1) {
+            send_expect_100 = s->send_expect_100;
+        } else {
+            send_expect_100 = 0;
+            /* 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;
+        }
     }
-#endif
+
+    av_bprintf(&request, "%s ", method);
+    bprint_escaped_path(&request, path);
+    av_bprintf(&request, " HTTP/1.1\r\n");
+
+    if (post && s->chunked_post)
+        av_bprintf(&request, "Transfer-Encoding: chunked\r\n");
     /* 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);
+        av_bprintf(&request, "User-Agent: %s\r\n", s->user_agent);
     if (s->referer) {
         /* set default headers if needed */
         if (!has_header(s->headers, "\r\nReferer: "))
-            len += av_strlcatf(headers + len, sizeof(headers) - len,
-                               "Referer: %s\r\n", s->referer);
+            av_bprintf(&request, "Referer: %s\r\n", s->referer);
     }
     if (!has_header(s->headers, "\r\nAccept: "))
-        len += av_strlcpy(headers + len, "Accept: */*\r\n",
-                          sizeof(headers) - len);
+        av_bprintf(&request, "Accept: */*\r\n");
     // Note: we send this on purpose even when s->off is 0 when we're probing,
     // since it allows us to detect more reliably if a (non-conforming)
     // server supports seeking by analysing the reply headers.
     if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable == -1)) {
-        len += av_strlcatf(headers + len, sizeof(headers) - len,
-                           "Range: bytes=%"PRIu64"-", s->off);
+        av_bprintf(&request, "Range: bytes=%"PRIu64"-", s->off);
         if (s->end_off)
-            len += av_strlcatf(headers + len, sizeof(headers) - len,
-                               "%"PRId64, s->end_off - 1);
-        len += av_strlcpy(headers + len, "\r\n",
-                          sizeof(headers) - len);
+            av_bprintf(&request, "%"PRId64, s->end_off - 1);
+        av_bprintf(&request, "\r\n");
     }
     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)
-            len += av_strlcpy(headers + len, "Connection: keep-alive\r\n",
-                              sizeof(headers) - len);
-        else
-            len += av_strlcpy(headers + len, "Connection: close\r\n",
-                              sizeof(headers) - len);
-    }
+        av_bprintf(&request, "Expect: 100-continue\r\n");
+
+    if (!has_header(s->headers, "\r\nConnection: "))
+        av_bprintf(&request, "Connection: %s\r\n", s->multiple_requests ? "keep-alive" : "close");
 
     if (!has_header(s->headers, "\r\nHost: "))
-        len += av_strlcatf(headers + len, sizeof(headers) - len,
-                           "Host: %s\r\n", hoststr);
+        av_bprintf(&request, "Host: %s\r\n", hoststr);
     if (!has_header(s->headers, "\r\nContent-Length: ") && s->post_data)
-        len += av_strlcatf(headers + len, sizeof(headers) - len,
-                           "Content-Length: %d\r\n", s->post_datalen);
+        av_bprintf(&request, "Content-Length: %d\r\n", s->post_datalen);
 
     if (!has_header(s->headers, "\r\nContent-Type: ") && s->content_type)
-        len += av_strlcatf(headers + len, sizeof(headers) - len,
-                           "Content-Type: %s\r\n", s->content_type);
+        av_bprintf(&request, "Content-Type: %s\r\n", s->content_type);
     if (!has_header(s->headers, "\r\nCookie: ") && s->cookies) {
         char *cookies = NULL;
         if (!get_cookies(s, &cookies, path, hoststr) && cookies) {
-            len += av_strlcatf(headers + len, sizeof(headers) - len,
-                               "Cookie: %s\r\n", cookies);
+            av_bprintf(&request, "Cookie: %s\r\n", cookies);
             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);
+        av_bprintf(&request, "Icy-MetaData: 1\r\n");
 
     /* now add in custom headers */
     if (s->headers)
-        av_strlcpy(headers + len, s->headers, sizeof(headers) - len);
+        av_bprintf(&request, "%s", s->headers);
 
-    ret = snprintf(s->buffer, sizeof(s->buffer),
-             "%s %s HTTP/1.1\r\n"
-             "%s"
-             "%s"
-             "%s"
-             "%s%s"
-             "\r\n",
-             method,
-             path,
-             post && s->chunked_post ? "Transfer-Encoding: chunked\r\n" : "",
-             headers,
-             authstr ? authstr : "",
-             proxyauthstr ? "Proxy-" : "", proxyauthstr ? proxyauthstr : "");
+    if (authstr)
+        av_bprintf(&request, "%s", authstr);
+    if (proxyauthstr)
+        av_bprintf(&request, "Proxy-%s", proxyauthstr);
+    av_bprintf(&request, "\r\n");
 
-    av_log(h, AV_LOG_DEBUG, "request: %s\n", s->buffer);
+    av_log(h, AV_LOG_DEBUG, "request: %s\n", request.str);
 
-    if (strlen(headers) + 1 == sizeof(headers) ||
-        ret >= sizeof(s->buffer)) {
+    if (!av_bprint_is_complete(&request)) {
         av_log(h, AV_LOG_ERROR, "overlong headers\n");
         err = AVERROR(EINVAL);
         goto done;
     }
 
-
-    if ((err = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0)
+    if ((err = ffurl_write(s->hd, request.str, request.len)) < 0)
         goto done;
 
     if (s->post_data)
@@ -1383,7 +1486,8 @@ static int http_buf_read(URLContext *h, uint8_t *buf, int size)
         if ((!s->willclose || s->chunksize == UINT64_MAX) && s->off >= target_end)
             return AVERROR_EOF;
         len = ffurl_read(s->hd, buf, size);
-        if (!len && (!s->willclose || s->chunksize == UINT64_MAX) && s->off < target_end) {
+        if ((!len || len == AVERROR_EOF) &&
+            (!s->willclose || s->chunksize == UINT64_MAX) && s->off < target_end) {
             av_log(h, AV_LOG_ERROR,
                    "Stream ends prematurely at %"PRIu64", should be %"PRIu64"\n",
                    s->off, target_end
@@ -1504,12 +1608,13 @@ static int http_read_stream_all(URLContext *h, uint8_t *buf, int size)
     return pos;
 }
 
-static void update_metadata(HTTPContext *s, char *data)
+static void update_metadata(URLContext *h, char *data)
 {
     char *key;
     char *val;
     char *end;
     char *next = data;
+    HTTPContext *s = h->priv_data;
 
     while (*next) {
         key = next;
@@ -1525,6 +1630,7 @@ static void update_metadata(HTTPContext *s, char *data)
         val += 2;
 
         av_dict_set(&s->metadata, key, val, 0);
+        av_log(h, AV_LOG_VERBOSE, "Metadata update for %s: %s\n", key, val);
 
         next = end + 2;
     }
@@ -1559,7 +1665,7 @@ static int store_icy(URLContext *h, int size)
             data[len + 1] = 0;
             if ((ret = av_opt_set(s, "icy_metadata_packet", data, 0)) < 0)
                 return ret;
-            update_metadata(s, data);
+            update_metadata(h, data);
         }
         s->icy_data_read = 0;
         remaining        = s->icy_metaint;
@@ -1630,7 +1736,7 @@ static int http_shutdown(URLContext *h, int flags)
             read_ret = ffurl_read(s->hd, buf, sizeof(buf));
             s->hd->flags &= ~AVIO_FLAG_NONBLOCK;
             if (read_ret < 0 && read_ret != AVERROR(EAGAIN)) {
-                av_log(h, AV_LOG_ERROR, "URL read error:  %d\n", read_ret);
+                av_log(h, AV_LOG_ERROR, "URL read error: %s\n", av_err2str(read_ret));
                 ret = read_ret;
             }
         }
@@ -1691,6 +1797,13 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo
     if (s->off && h->is_streamed)
         return AVERROR(ENOSYS);
 
+    /* do not try to make a new connection if seeking past the end of the file */
+    if (s->end_off || s->filesize != UINT64_MAX) {
+        uint64_t end_pos = s->end_off ? s->end_off : s->filesize;
+        if (s->off >= end_pos)
+            return s->off;
+    }
+
     /* we save the old context in case the seek fails */
     old_buf_size = s->buf_end - s->buf_ptr;
     memcpy(old_buf, s->buf_ptr, old_buf_size);
@@ -1754,7 +1867,7 @@ const URLProtocol ff_http_protocol = {
     .priv_data_size      = sizeof(HTTPContext),
     .priv_data_class     = &http_context_class,
     .flags               = URL_PROTOCOL_FLAG_NETWORK,
-    .default_whitelist   = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"
+    .default_whitelist   = "http,https,tls,rtp,tcp,udp,crypto,httpproxy,data"
 };
 #endif /* CONFIG_HTTP_PROTOCOL */