X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fhttp.c;h=5a0dda400c3e0d92d0da5e477eb56c80bb6515bb;hb=HEAD;hp=74d743850de65d64628a0350a8d82faef64beaa0;hpb=7be245498b1d0a1b11cebad6a3a0a4ab0e9a846a;p=ffmpeg diff --git a/libavformat/http.c b/libavformat/http.c index 74d743850de..5a0dda400c3 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -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; -int ff_http_do_new_request(URLContext *h, const char *uri) + /* 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_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); @@ -411,7 +500,19 @@ static int http_write_reply(URLContext* h, int status_code) default: return AVERROR(EINVAL); } - if (body) { + if (h->flags & AVIO_FLAG_METACUBE) { + s->chunked_post = 0; + message_len = snprintf(message, sizeof(message), + "HTTP/1.1 %03d %s\r\n" + "Content-Type: %s\r\n" + "Content-Encoding: metacube\r\n" + "%s" + "\r\n", + reply_code, + reply_text, + content_type, + s->headers ? s->headers : ""); + } else if (body) { s->chunked_post = 0; message_len = snprintf(message, sizeof(message), "HTTP/1.1 %03d %s\r\n" @@ -544,7 +645,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 +656,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 +855,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 +873,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 +1135,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 +1148,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 +1252,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 +1316,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 +1498,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 @@ -1632,7 +1748,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; } } @@ -1763,7 +1879,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 */