#include "libavutil/avstring.h"
#include "avformat.h"
-#include <unistd.h>
#include "internal.h"
#include "network.h"
#include "http.h"
#include "url.h"
#include "libavutil/opt.h"
+#if CONFIG_ZLIB
+#include <zlib.h>
+#endif
+
/* XXX: POST protocol is not completely implemented because avconv uses
only a subset of it. */
-/* used for protocol handling */
-#define BUFFER_SIZE 1024
+/* 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 MAX_REDIRECTS 8
typedef struct {
int multiple_requests; /**< A flag which indicates if we use persistent connections. */
uint8_t *post_data;
int post_datalen;
+#if CONFIG_ZLIB
+ int compressed;
+ z_stream inflate_stream;
+ uint8_t *inflate_buffer;
+#endif
} HTTPContext;
#define OFFSET(x) offsetof(HTTPContext, x)
#define D AV_OPT_FLAG_DECODING_PARAM
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
-{"chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_INT, {.dbl = 1}, 0, 1, E },
+{"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 },
-{"multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_INT, {.dbl = 0}, 0, 1, D|E },
+{"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 },
{NULL}
};
const char *path, *proxy_path, *lower_proto = "tcp", *local_path;
char hostname[1024], hoststr[1024], proto[10];
char auth[1024], proxyauth[1024] = "";
- char path1[1024];
- char buf[1024], urlbuf[1024];
+ char path1[MAX_URL_SIZE];
+ char buf[1024], urlbuf[MAX_URL_SIZE];
int port, use_proxy, err, location_changed = 0, redirects = 0, attempts = 0;
HTTPAuthType cur_auth_type, cur_proxy_auth_type;
HTTPContext *s = h->priv_data;
- URLContext *hd = NULL;
-
- proxy_path = getenv("http_proxy");
- use_proxy = (proxy_path != NULL) && !getenv("no_proxy") &&
- av_strstart(proxy_path, "http://", NULL);
/* fill the dest addr */
redo:
path1, sizeof(path1), s->location);
ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
+ proxy_path = getenv("http_proxy");
+ use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), hostname) &&
+ proxy_path != NULL && av_strstart(proxy_path, "http://", NULL);
+
if (!strcmp(proto, "https")) {
lower_proto = "tls";
use_proxy = 0;
ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL);
if (!s->hd) {
- err = ffurl_open(&hd, buf, AVIO_FLAG_READ_WRITE,
+ err = ffurl_open(&s->hd, buf, AVIO_FLAG_READ_WRITE,
&h->interrupt_callback, NULL);
if (err < 0)
goto fail;
-
- s->hd = hd;
}
cur_auth_type = s->auth_state.auth_type;
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(hd);
+ ffurl_close(s->hd);
+ s->hd = NULL;
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(hd);
+ ffurl_close(s->hd);
+ s->hd = NULL;
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(hd);
+ ffurl_close(s->hd);
+ s->hd = NULL;
if (redirects++ >= MAX_REDIRECTS)
return AVERROR(EIO);
/* Restart the authentication process with the new target, which
}
return 0;
fail:
- if (hd)
- ffurl_close(hd);
+ if (s->hd)
+ ffurl_close(s->hd);
s->hd = NULL;
return AVERROR(EIO);
}
if (s->buf_ptr >= s->buf_end) {
len = ffurl_read(s->hd, s->buffer, BUFFER_SIZE);
if (len < 0) {
- return AVERROR(EIO);
+ return len;
} else if (len == 0) {
return -1;
} else {
for(;;) {
ch = http_getc(s);
if (ch < 0)
- return AVERROR(EIO);
+ return ch;
if (ch == '\n') {
/* process line */
if (q > line && q[-1] == '\r')
p = line;
if (line_count == 0) {
- while (!isspace(*p) && *p != '\0')
+ while (!av_isspace(*p) && *p != '\0')
p++;
- while (isspace(*p))
+ while (av_isspace(*p))
p++;
s->http_code = strtol(p, &end, 10);
*p = '\0';
tag = line;
p++;
- while (isspace(*p))
+ while (av_isspace(*p))
p++;
if (!av_strcasecmp(tag, "Location")) {
- strcpy(s->location, p);
+ av_strlcpy(s->location, p, sizeof(s->location));
*new_location = 1;
} else if (!av_strcasecmp (tag, "Content-Length") && s->filesize == -1) {
- s->filesize = atoll(p);
+ s->filesize = strtoll(p, NULL, 10);
} else if (!av_strcasecmp (tag, "Content-Range")) {
/* "bytes $from-$to/$document_size" */
const char *slash;
if (!strncmp (p, "bytes ", 6)) {
p += 6;
- s->off = atoll(p);
+ s->off = strtoll(p, NULL, 10);
if ((slash = strchr(p, '/')) && strlen(slash) > 0)
- s->filesize = atoll(slash+1);
+ s->filesize = strtoll(slash+1, NULL, 10);
}
h->is_streamed = 0; /* we _can_ in fact seek */
} else if (!av_strcasecmp(tag, "Accept-Ranges") && !strncmp(p, "bytes", 5)) {
} 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)) {
+#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 AVERROR(ENOSYS);
+ }
}
}
return 1;
static int http_read_header(URLContext *h, int *new_location)
{
HTTPContext *s = h->priv_data;
- char line[1024];
+ char line[MAX_URL_SIZE];
int err = 0;
+ s->chunksize = -1;
+
for (;;) {
- if (http_get_line(s, line, sizeof(line)) < 0)
- return AVERROR(EIO);
+ if ((err = http_get_line(s, line, sizeof(line))) < 0)
+ return err;
av_dlog(NULL, "header='%s'\n", line);
av_freep(&authstr);
av_freep(&proxyauthstr);
- if (ffurl_write(s->hd, s->buffer, strlen(s->buffer)) < 0)
- return AVERROR(EIO);
+ if ((err = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0)
+ return err;
if (s->post_data)
if ((err = ffurl_write(s->hd, s->post_data, s->post_datalen)) < 0)
s->http_code = 200;
return 0;
}
- s->chunksize = -1;
/* wait for header */
err = http_read_header(h, new_location);
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;
int err, new_location;
- if (s->end_chunked_post) {
- if (!s->end_header) {
- err = http_read_header(h, &new_location);
- if (err < 0)
- return err;
- }
+ if (!s->hd)
+ return AVERROR_EOF;
- return http_buf_read(h, buf, size);
+ if (s->end_chunked_post && !s->end_header) {
+ err = http_read_header(h, &new_location);
+ if (err < 0)
+ return err;
}
if (s->chunksize >= 0) {
for(;;) {
do {
- if (http_get_line(s, line, sizeof(line)) < 0)
- return AVERROR(EIO);
+ if ((err = http_get_line(s, line, sizeof(line))) < 0)
+ return err;
} while (!*line); /* skip CR LF from last chunk */
s->chunksize = strtoll(line, NULL, 16);
}
size = FFMIN(size, s->chunksize);
}
+#if CONFIG_ZLIB
+ if (s->compressed)
+ return http_buf_read_compressed(h, buf, size);
+#endif
return http_buf_read(h, buf, size);
}
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);
.url_seek = http_seek,
.url_close = http_close,
.url_get_file_handle = http_get_file_handle,
+ .url_shutdown = http_shutdown,
.priv_data_size = sizeof(HTTPContext),
.priv_data_class = &https_context_class,
.flags = URL_PROTOCOL_FLAG_NETWORK,