Merge commit '7a2fddb4480121712df560cf619c1c3566cae3ff'
authorMichael Niedermayer <michaelni@gmx.at>
Wed, 12 Mar 2014 01:45:34 +0000 (02:45 +0100)
committerMichael Niedermayer <michaelni@gmx.at>
Wed, 12 Mar 2014 01:45:34 +0000 (02:45 +0100)
* commit '7a2fddb4480121712df560cf619c1c3566cae3ff':
  http: K&R formatting cosmetics

Conflicts:
libavformat/http.c

Merged-by: Michael Niedermayer <michaelni@gmx.at>
1  2 
libavformat/http.c

diff --combined libavformat/http.c
@@@ -1,21 -1,21 +1,21 @@@
  /*
 - * HTTP protocol for avconv client
 + * HTTP protocol for ffmpeg client
   * Copyright (c) 2000, 2001 Fabrice Bellard
   *
 - * This file is part of Libav.
 + * This file is part of FFmpeg.
   *
 - * Libav is free software; you can redistribute it and/or
 + * FFmpeg is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
 - * Libav is distributed in the hope that it will be useful,
 + * FFmpeg is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
 - * License along with Libav; if not, write to the Free Software
 + * License along with FFmpeg; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
   */
  
@@@ -33,7 -33,7 +33,7 @@@
  #include <zlib.h>
  #endif
  
 -/* XXX: POST protocol is not completely implemented because avconv uses
 +/* XXX: POST protocol is not completely implemented because ffmpeg uses
     only a subset of it. */
  
  /* The IO buffer size is unrelated to the max URL size in itself, but needs
@@@ -51,11 -51,7 +51,11 @@@ typedef struct 
      int http_code;
      /* Used if "Transfer-Encoding: chunked" otherwise -1. */
      int64_t chunksize;
 -    int64_t off, filesize;
 +    char *content_type;
 +    char *user_agent;
 +    int64_t off, filesize, req_end_offset;
 +    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;
@@@ -63,7 -59,6 +63,7 @@@
      /* Set if the server correctly handles Connection: close and will close
       * the connection after feeding us the content. */
      int willclose;
 +    int seekable;           /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */
      int chunked_post;
      /* A flag which indicates if the end of chunked encoding has been sent. */
      int end_chunked_post;
      int multiple_requests;
      uint8_t *post_data;
      int post_datalen;
 +    int is_akamai;
 +    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;
  #define OFFSET(x) offsetof(HTTPContext, x)
  #define D AV_OPT_FLAG_DECODING_PARAM
  #define E AV_OPT_FLAG_ENCODING_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", "custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|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 = 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", "custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D|E },
 +{"post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = 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, 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 },
 +{"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(req_end_offset), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D },
  {NULL}
  };
  #define HTTP_CLASS(flavor)\
@@@ -204,7 -181,8 +204,7 @@@ static int http_open_cnx(URLContext *h
      if (s->http_code == 401) {
          if ((cur_auth_type == HTTP_AUTH_NONE || s->auth_state.stale) &&
              s->auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) {
 -            ffurl_close(s->hd);
 -            s->hd = NULL;
 +            ffurl_closep(&s->hd);
              goto redo;
          } else
              goto fail;
      if (s->http_code == 407) {
          if ((cur_proxy_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) &&
              s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) {
 -            ffurl_close(s->hd);
 -            s->hd = NULL;
 +            ffurl_closep(&s->hd);
              goto redo;
          } else
              goto fail;
      if ((s->http_code == 301 || s->http_code == 302 || s->http_code == 303 || s->http_code == 307)
          && location_changed == 1) {
          /* url moved, get next */
 -        ffurl_close(s->hd);
 -        s->hd = NULL;
 +        ffurl_closep(&s->hd);
          if (redirects++ >= MAX_REDIRECTS)
              return AVERROR(EIO);
          /* Restart the authentication process with the new target, which
      return 0;
   fail:
      if (s->hd)
 -        ffurl_close(s->hd);
 -    s->hd = NULL;
 +        ffurl_closep(&s->hd);
      return AVERROR(EIO);
  }
  
@@@ -244,7 -225,6 +244,7 @@@ int ff_http_do_new_request(URLContext *
      int ret;
  
      s->off = 0;
 +    s->icy_data_read = 0;
      av_free(s->location);
      s->location = av_strdup(uri);
      if (!s->location)
@@@ -262,10 -242,7 +262,10 @@@ static int http_open(URLContext *h, con
      HTTPContext *s = h->priv_data;
      int ret;
  
 -    h->is_streamed = 1;
 +    if( s->seekable == 1 )
 +        h->is_streamed = 0;
 +    else
 +        h->is_streamed = 1;
  
      s->filesize = -1;
      s->location = av_strdup(uri);
@@@ -346,16 -323,15 +346,15 @@@ static int process_line(URLContext *h, 
              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. */
-         if (s->http_code >= 400 && s->http_code < 600 && (s->http_code != 401
-             || s->auth_state.auth_type != HTTP_AUTH_NONE) &&
+         if (s->http_code >= 400 && s->http_code < 600 &&
+             (s->http_code != 401 || s->auth_state.auth_type != HTTP_AUTH_NONE) &&
              (s->http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE)) {
              end += strspn(end, SPACE_CHARS);
-             av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n",
-                    s->http_code, end);
+             av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", s->http_code, end);
              return AVERROR(EIO);
          }
      } else {
              av_free(s->location);
              s->location = new_loc;
              *new_location = 1;
-         } else if (!av_strcasecmp (tag, "Content-Length") && s->filesize == -1) {
+         } else if (!av_strcasecmp(tag, "Content-Length") && s->filesize == -1) {
              s->filesize = strtoll(p, NULL, 10);
-         } else if (!av_strcasecmp (tag, "Content-Range")) {
+         } else if (!av_strcasecmp(tag, "Content-Range")) {
              /* "bytes $from-$to/$document_size" */
              const char *slash;
-             if (!strncmp (p, "bytes ", 6)) {
+             if (!strncmp(p, "bytes ", 6)) {
                  p += 6;
                  s->off = strtoll(p, NULL, 10);
                  if ((slash = strchr(p, '/')) && strlen(slash) > 0)
                      s->filesize = strtoll(slash+1, NULL, 10);
              }
 -            h->is_streamed = 0; /* we _can_ in fact seek */
 +            if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647))
 +                h->is_streamed = 0; /* we _can_ in fact seek */
-         } else if (!av_strcasecmp(tag, "Accept-Ranges") && !strncmp(p, "bytes", 5) && s->seekable == -1) {
+         } else if (!av_strcasecmp(tag, "Accept-Ranges") &&
 -                   !strncmp(p, "bytes", 5)) {
++                   !strncmp(p, "bytes", 5) &&
++                   s->seekable == -1) {
              h->is_streamed = 0;
-         } else if (!av_strcasecmp (tag, "Transfer-Encoding") && !av_strncasecmp(p, "chunked", 7)) {
+         } else if (!av_strcasecmp(tag, "Transfer-Encoding") &&
+                    !av_strncasecmp(p, "chunked", 7)) {
              s->filesize = -1;
              s->chunksize = 0;
-         } else if (!av_strcasecmp (tag, "WWW-Authenticate")) {
+         } else if (!av_strcasecmp(tag, "WWW-Authenticate")) {
              ff_http_auth_handle_header(&s->auth_state, tag, p);
-         } else if (!av_strcasecmp (tag, "Authentication-Info")) {
+         } else if (!av_strcasecmp(tag, "Authentication-Info")) {
              ff_http_auth_handle_header(&s->auth_state, tag, p);
-         } else if (!av_strcasecmp (tag, "Proxy-Authenticate")) {
+         } else if (!av_strcasecmp(tag, "Proxy-Authenticate")) {
              ff_http_auth_handle_header(&s->proxy_auth_state, tag, p);
-         } else if (!av_strcasecmp (tag, "Connection")) {
+         } else if (!av_strcasecmp(tag, "Connection")) {
              if (!strcmp(p, "close"))
                  s->willclose = 1;
-         } else if (!av_strcasecmp (tag, "Content-Encoding")) {
-             if (!av_strncasecmp(p, "gzip", 4) || !av_strncasecmp(p, "deflate", 7)) {
 +        } 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")) {
 +            if (!s->cookies) {
 +                if (!(s->cookies = av_strdup(p)))
 +                    return AVERROR(ENOMEM);
 +            } else {
 +                char *tmp = s->cookies;
 +                size_t str_size = strlen(tmp) + strlen(p) + 2;
 +                if (!(s->cookies = av_malloc(str_size))) {
 +                    s->cookies = tmp;
 +                    return AVERROR(ENOMEM);
 +                }
 +                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);
                  // the header at all if this is the case).
              } else {
                  av_log(h, AV_LOG_WARNING, "Unknown content coding: %s\n", p);
 -                return AVERROR(ENOSYS);
              }
          }
      }
      return 1;
  }
  
 +/**
 + * Create a string containing cookie values for use as a HTTP cookie header
 + * field value for a particular path and domain from the cookie values stored in
 + * the HTTP protocol context. The cookie string is stored in *cookies.
 + *
 + * @return a negative value if an error condition occurred, 0 otherwise
 + */
 +static int get_cookies(HTTPContext *s, char **cookies, const char *path,
 +                       const char *domain)
 +{
 +    // cookie strings will look like Set-Cookie header field values.  Multiple
 +    // Set-Cookie fields will result in multiple values delimited by a newline
 +    int ret = 0;
 +    char *next, *cookie, *set_cookies = av_strdup(s->cookies), *cset_cookies = set_cookies;
 +
 +    if (!set_cookies) return AVERROR(EINVAL);
 +
 +    *cookies = NULL;
 +    while ((cookie = av_strtok(set_cookies, "\n", &next))) {
 +        int domain_offset = 0;
 +        char *param, *next_param, *cdomain = NULL, *cpath = NULL, *cvalue = NULL;
 +        set_cookies = NULL;
 +
 +        while ((param = av_strtok(cookie, "; ", &next_param))) {
 +            cookie = NULL;
 +            if        (!av_strncasecmp("path=",   param, 5)) {
 +                av_free(cpath);
 +                cpath = av_strdup(&param[5]);
 +            } else if (!av_strncasecmp("domain=", param, 7)) {
 +                // if the cookie specifies a sub-domain, skip the leading dot thereby
 +                // supporting URLs that point to sub-domains and the master domain
 +                int leading_dot = (param[7] == '.');
 +                av_free(cdomain);
 +                cdomain = av_strdup(&param[7+leading_dot]);
 +            } else if (!av_strncasecmp("secure",  param, 6) ||
 +                       !av_strncasecmp("comment", param, 7) ||
 +                       !av_strncasecmp("max-age", param, 7) ||
 +                       !av_strncasecmp("version", param, 7)) {
 +                // ignore Comment, Max-Age, Secure and Version
 +            } else {
 +                av_free(cvalue);
 +                cvalue = av_strdup(param);
 +            }
 +        }
 +        if (!cdomain)
 +            cdomain = av_strdup(domain);
 +
 +        // ensure all of the necessary values are valid
 +        if (!cdomain || !cpath || !cvalue) {
 +            av_log(s, AV_LOG_WARNING,
 +                   "Invalid cookie found, no value, path or domain specified\n");
 +            goto done_cookie;
 +        }
 +
 +        // check if the request path matches the cookie path
 +        if (av_strncasecmp(path, cpath, strlen(cpath)))
 +            goto done_cookie;
 +
 +        // the domain should be at least the size of our cookie domain
 +        domain_offset = strlen(domain) - strlen(cdomain);
 +        if (domain_offset < 0)
 +            goto done_cookie;
 +
 +        // match the cookie domain
 +        if (av_strcasecmp(&domain[domain_offset], cdomain))
 +            goto done_cookie;
 +
 +        // cookie parameters match, so copy the value
 +        if (!*cookies) {
 +            if (!(*cookies = av_strdup(cvalue))) {
 +                ret = AVERROR(ENOMEM);
 +                goto done_cookie;
 +            }
 +        } else {
 +            char *tmp = *cookies;
 +            size_t str_size = strlen(cvalue) + strlen(*cookies) + 3;
 +            if (!(*cookies = av_malloc(str_size))) {
 +                ret = AVERROR(ENOMEM);
 +                goto done_cookie;
 +            }
 +            snprintf(*cookies, str_size, "%s; %s", tmp, cvalue);
 +            av_free(tmp);
 +        }
 +
 +        done_cookie:
 +        av_free(cdomain);
 +        av_free(cpath);
 +        av_free(cvalue);
 +        if (ret < 0) {
 +            if (*cookies) av_freep(cookies);
 +            av_free(cset_cookies);
 +            return ret;
 +        }
 +    }
 +
 +    av_free(cset_cookies);
 +
 +    return 0;
 +}
 +
  static inline int has_header(const char *str, const char *header)
  {
      /* header + 2 to skip over CRLF prefix. (make sure you have one!) */
@@@ -587,7 -434,7 +590,7 @@@ static int http_read_header(URLContext 
          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)
          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;
  }
  
@@@ -609,7 -453,7 +612,7 @@@ static int http_connect(URLContext *h, 
  {
      HTTPContext *s = h->priv_data;
      int post, err;
 -    char headers[1024] = "";
 +    char headers[4096] = "";
      char *authstr = NULL, *proxyauthstr = NULL;
      int64_t off = s->off;
      int len = 0;
  
      /* 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", LIBAVFORMAT_IDENT);
 +        len += av_strlcatf(headers + len, sizeof(headers) - len,
 +                           "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);
 -    if (!has_header(s->headers, "\r\nRange: ") && !post)
 +    // 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->req_end_offset || s->seekable == -1)) {
          len += av_strlcatf(headers + len, sizeof(headers) - len,
 -                           "Range: bytes=%"PRId64"-\r\n", s->off);
 +                           "Range: bytes=%"PRId64"-", s->off);
 +        if (s->req_end_offset)
 +            len += av_strlcatf(headers + len, sizeof(headers) - len,
 +                               "%"PRId64, s->req_end_offset - 1);
 +        len += av_strlcpy(headers + len, "\r\n",
 +                          sizeof(headers) - len);
 +    }
      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\nContent-Length: ") && s->post_data)
          len += av_strlcatf(headers + len, sizeof(headers) - len,
                             "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);
 +    if (!has_header(s->headers, "\r\nCookie: ") && s->cookies) {
 +        char *cookies = NULL;
 +        if (!get_cookies(s, &cookies, path, hoststr)) {
 +            len += av_strlcatf(headers + len, sizeof(headers) - len,
 +                               "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);
 +    }
  
      /* now add in custom headers */
      if (s->headers)
  
      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;
  
      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;
@@@ -812,7 -628,7 +815,7 @@@ static int http_buf_read_compressed(URL
  }
  #endif
  
 -static int http_read(URLContext *h, uint8_t *buf, int size)
 +static int http_read_stream(URLContext *h, uint8_t *buf, int size)
  {
      HTTPContext *s = h->priv_data;
      int err, new_location;
      return http_buf_read(h, buf, size);
  }
  
 +// Like http_read_stream(), but no short reads.
 +// Assumes partial reads are an error.
 +static int http_read_stream_all(URLContext *h, uint8_t *buf, int size)
 +{
 +    int pos = 0;
 +    while (pos < size) {
 +        int len = http_read_stream(h, buf + pos, size - pos);
 +        if (len < 0)
 +            return len;
 +        pos += len;
 +    }
 +    return pos;
 +}
 +
 +static int http_read(URLContext *h, uint8_t *buf, int size)
 +{
 +    HTTPContext *s = h->priv_data;
 +
 +    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.
 +            uint8_t ch;
 +            int len = http_read_stream_all(h, &ch, 1);
 +            if (len < 1)
 +                return len;
 +            if (ch > 0) {
 +                char data[255 * 16 + 1];
 +                int ret;
 +                len = ch * 16;
 +                ret = http_read_stream_all(h, data, len);
 +                if (ret < len)
 +                    return ret;
 +                data[len + 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);
 +    }
 +    size = http_read_stream(h, buf, size);
 +    if (size > 0)
 +        s->icy_data_read += size;
 +    return size;
 +}
 +
  /* used only when posting data */
  static int http_write(URLContext *h, const uint8_t *buf, int size)
  {
@@@ -964,7 -729,7 +967,7 @@@ static int http_close(URLContext *h
      }
  
      if (s->hd)
 -        ffurl_close(s->hd);
 +        ffurl_closep(&s->hd);
      av_dict_free(&s->chained_options);
      return ret;
  }
@@@ -980,26 -745,19 +983,26 @@@ static int64_t http_seek(URLContext *h
  
      if (whence == AVSEEK_SIZE)
          return s->filesize;
 +    else if ((whence == SEEK_CUR && off == 0) || (whence == SEEK_SET && off == s->off))
 +        return s->off;
      else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed)
          return AVERROR(ENOSYS);
  
 -    /* 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);
 -    s->hd = NULL;
      if (whence == SEEK_CUR)
          off += s->off;
      else if (whence == SEEK_END)
          off += s->filesize;
 +    else if (whence != SEEK_SET)
 +        return AVERROR(EINVAL);
 +    if (off < 0)
 +        return AVERROR(EINVAL);
      s->off = 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);
 +    s->hd = NULL;
 +
      /* if it fails, continue on old connection */
      av_dict_copy(&options, s->chained_options, 0);
      if ((ret = http_open_cnx(h, &options)) < 0) {
@@@ -1059,7 -817,7 +1062,7 @@@ static int http_proxy_close(URLContext 
  {
      HTTPContext *s = h->priv_data;
      if (s->hd)
 -        ffurl_close(s->hd);
 +        ffurl_closep(&s->hd);
      return 0;
  }
  
@@@ -1074,10 -832,7 +1077,10 @@@ static int http_proxy_open(URLContext *
      char *authstr;
      int new_loc;
  
 -    h->is_streamed = 1;
 +    if( s->seekable == 1 )
 +        h->is_streamed = 0;
 +    else
 +        h->is_streamed = 1;
  
      av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
                   pathbuf, sizeof(pathbuf), uri);
@@@ -1133,7 -888,8 +1136,7 @@@ redo
      if (s->http_code == 407 &&
          (cur_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) &&
          s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 2) {
 -        ffurl_close(s->hd);
 -        s->hd = NULL;
 +        ffurl_closep(&s->hd);
          goto redo;
      }