#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
+#include "libavutil/bprint.h"
#include "libavutil/opt.h"
#include "libavutil/time.h"
#include "libavutil/parseutils.h"
/* 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
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. */
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;
{ "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 },
{ "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 },
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;
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
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) {
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);
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)
+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_request2(URLContext *h, const char *uri, AVDictionary **opts)
{
HTTPContext *s = h->priv_data;
AVDictionary *options = NULL;
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);
int len = strlen(s->headers);
if (len < 2 || strcmp("\r\n", s->headers + len - 2)) {
av_log(h, AV_LOG_WARNING,
- "No trailing CRLF found in HTTP header.\n");
+ "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';
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;
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;
}
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) {
while (av_isspace(*p))
p++;
resource = p;
- while (!av_isspace(*p))
+ while (*p && !av_isspace(*p))
p++;
*(p++) = '\0';
av_log(h, AV_LOG_TRACE, "Requested resource: %s\n", resource);
// 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);
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);
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;
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)
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
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;
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;
}
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;
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;
}
}
av_freep(&s->inflate_buffer);
#endif /* CONFIG_ZLIB */
- if (!s->end_chunked_post)
+ if (s->hd && !s->end_chunked_post)
/* Close the write direction by sending the end of chunked encoding. */
ret = http_shutdown(h, h->flags);
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);
.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 */