2 * HTTP protocol for ffmpeg client
3 * Copyright (c) 2000, 2001 Fabrice Bellard
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 #include "config_components.h"
31 #endif /* CONFIG_ZLIB */
33 #include "libavutil/avassert.h"
34 #include "libavutil/avstring.h"
35 #include "libavutil/bprint.h"
36 #include "libavutil/getenv_utf8.h"
37 #include "libavutil/macros.h"
38 #include "libavutil/mem.h"
39 #include "libavutil/opt.h"
40 #include "libavutil/time.h"
41 #include "libavutil/parseutils.h"
48 #include "os_support.h"
52 /* XXX: POST protocol is not completely implemented because ffmpeg uses
53 * only a subset of it. */
55 /* The IO buffer size is unrelated to the max URL size in itself, but needs
56 * to be large enough to fit the full request headers (including long
58 #define BUFFER_SIZE (MAX_URL_SIZE + HTTP_HEADERS_SIZE)
59 #define MAX_REDIRECTS 8
60 #define MAX_CACHED_REDIRECTS 32
63 #define MAX_DATE_LEN 19
64 #define WHITESPACES " \n\t\r"
72 typedef struct HTTPContext
{
75 unsigned char buffer
[BUFFER_SIZE
], *buf_ptr
, *buf_end
;
78 /* Used if "Transfer-Encoding: chunked" otherwise -1. */
81 uint64_t off
, end_off
, filesize
;
84 HTTPAuthState auth_state
;
85 HTTPAuthState proxy_auth_state
;
93 /* Set if the server correctly handles Connection: close and will close
94 * the connection after feeding us the content. */
96 int seekable
; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */
98 /* A flag which indicates if the end of chunked encoding has been sent. */
100 /* A flag which indicates we have finished to read POST reply. */
102 /* A flag which indicates if we use persistent connections. */
103 int multiple_requests
;
108 char *cookies
; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)
109 /* A dictionary containing cookies keyed by cookie name */
110 AVDictionary
*cookie_dict
;
112 /* how much data was read since the last ICY metadata packet */
113 uint64_t icy_data_read
;
114 /* after how many bytes of read data a new metadata packet will be found */
115 uint64_t icy_metaint
;
116 char *icy_metadata_headers
;
117 char *icy_metadata_packet
;
118 AVDictionary
*metadata
;
121 z_stream inflate_stream
;
122 uint8_t *inflate_buffer
;
123 #endif /* CONFIG_ZLIB */
124 AVDictionary
*chained_options
;
125 /* -1 = try to send if applicable, 0 = always disabled, 1 = always enabled */
129 int reconnect_at_eof
;
130 int reconnect_on_network_error
;
131 int reconnect_streamed
;
132 int reconnect_delay_max
;
133 char *reconnect_on_http_error
;
138 HandshakeState handshake_step
;
139 int is_connected_server
;
143 AVDictionary
*redirect_cache
;
144 uint64_t filesize_from_content_range
;
145 int respect_retry_after
;
146 unsigned int retry_after
;
147 int reconnect_max_retries
;
148 int reconnect_delay_total_max
;
151 #define OFFSET(x) offsetof(HTTPContext, x)
152 #define D AV_OPT_FLAG_DECODING_PARAM
153 #define E AV_OPT_FLAG_ENCODING_PARAM
154 #define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION)
156 static const AVOption options
[] = {
157 { "seekable", "control seekability of connection", OFFSET(seekable
), AV_OPT_TYPE_BOOL
, { .i64
= -1 }, -1, 1, D
},
158 { "chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post
), AV_OPT_TYPE_BOOL
, { .i64
= 1 }, 0, 1, E
},
159 { "http_proxy", "set HTTP proxy to tunnel through", OFFSET(http_proxy
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, D
| E
},
160 { "headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, D
| E
},
161 { "content_type", "set a specific content type for the POST messages", OFFSET(content_type
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, D
| E
},
162 { "user_agent", "override User-Agent header", OFFSET(user_agent
), AV_OPT_TYPE_STRING
, { .str
= DEFAULT_USER_AGENT
}, 0, 0, D
},
163 { "referer", "override referer header", OFFSET(referer
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, D
},
164 { "multiple_requests", "use persistent connections", OFFSET(multiple_requests
), AV_OPT_TYPE_BOOL
, { .i64
= 0 }, 0, 1, D
| E
},
165 { "post_data", "set custom HTTP post data", OFFSET(post_data
), AV_OPT_TYPE_BINARY
, .flags
= D
| E
},
166 { "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
},
167 { "http_version", "export the http response version", OFFSET(http_version
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, AV_OPT_FLAG_EXPORT
| AV_OPT_FLAG_READONLY
},
168 { "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
, { .str
= NULL
}, 0, 0, D
},
169 { "icy", "request ICY metadata", OFFSET(icy
), AV_OPT_TYPE_BOOL
, { .i64
= 1 }, 0, 1, D
},
170 { "icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, AV_OPT_FLAG_EXPORT
},
171 { "icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, AV_OPT_FLAG_EXPORT
},
172 { "metadata", "metadata read from the bitstream", OFFSET(metadata
), AV_OPT_TYPE_DICT
, {0}, 0, 0, AV_OPT_FLAG_EXPORT
},
173 { "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
, .unit
= "auth_type"},
174 { "none", "No auth method set, autodetect", 0, AV_OPT_TYPE_CONST
, { .i64
= HTTP_AUTH_NONE
}, 0, 0, D
| E
, .unit
= "auth_type"},
175 { "basic", "HTTP basic authentication", 0, AV_OPT_TYPE_CONST
, { .i64
= HTTP_AUTH_BASIC
}, 0, 0, D
| E
, .unit
= "auth_type"},
176 { "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
},
177 { "location", "The actual location of the data received", OFFSET(location
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, D
| E
},
178 { "offset", "initial byte offset", OFFSET(off
), AV_OPT_TYPE_INT64
, { .i64
= 0 }, 0, INT64_MAX
, D
},
179 { "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
},
180 { "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
},
181 { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect
), AV_OPT_TYPE_BOOL
, { .i64
= 0 }, 0, 1, D
},
182 { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof
), AV_OPT_TYPE_BOOL
, { .i64
= 0 }, 0, 1, D
},
183 { "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
},
184 { "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
},
185 { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed
), AV_OPT_TYPE_BOOL
, { .i64
= 0 }, 0, 1, D
},
186 { "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
},
187 { "reconnect_max_retries", "the max number of times to retry a connection", OFFSET(reconnect_max_retries
), AV_OPT_TYPE_INT
, { .i64
= -1 }, -1, INT_MAX
, D
},
188 { "reconnect_delay_total_max", "max total reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_total_max
), AV_OPT_TYPE_INT
, { .i64
= 256 }, 0, UINT_MAX
/1000/1000, D
},
189 { "respect_retry_after", "respect the Retry-After header when retrying connections", OFFSET(respect_retry_after
), AV_OPT_TYPE_BOOL
, { .i64
= 1 }, 0, 1, D
},
190 { "listen", "listen on HTTP", OFFSET(listen
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, 2, D
| E
},
191 { "resource", "The resource requested by a client", OFFSET(resource
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, E
},
192 { "reply_code", "The http status code to return to a client", OFFSET(reply_code
), AV_OPT_TYPE_INT
, { .i64
= 200}, INT_MIN
, 599, E
},
193 { "short_seek_size", "Threshold to favor readahead over seek.", OFFSET(short_seek_size
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, INT_MAX
, D
},
197 static int http_connect(URLContext
*h
, const char *path
, const char *local_path
,
198 const char *hoststr
, const char *auth
,
199 const char *proxyauth
);
200 static int http_read_header(URLContext
*h
);
201 static int http_shutdown(URLContext
*h
, int flags
);
203 void ff_http_init_auth_state(URLContext
*dest
, const URLContext
*src
)
205 memcpy(&((HTTPContext
*)dest
->priv_data
)->auth_state
,
206 &((HTTPContext
*)src
->priv_data
)->auth_state
,
207 sizeof(HTTPAuthState
));
208 memcpy(&((HTTPContext
*)dest
->priv_data
)->proxy_auth_state
,
209 &((HTTPContext
*)src
->priv_data
)->proxy_auth_state
,
210 sizeof(HTTPAuthState
));
213 static int http_open_cnx_internal(URLContext
*h
, AVDictionary
**options
)
215 const char *path
, *proxy_path
, *lower_proto
= "tcp", *local_path
;
216 char *env_http_proxy
, *env_no_proxy
;
218 char hostname
[1024], hoststr
[1024], proto
[10], tmp_host
[1024];
219 char auth
[1024], proxyauth
[1024] = "";
220 char path1
[MAX_URL_SIZE
], sanitized_path
[MAX_URL_SIZE
+ 1];
221 char buf
[1024], urlbuf
[MAX_URL_SIZE
];
222 int port
, use_proxy
, err
= 0;
223 HTTPContext
*s
= h
->priv_data
;
225 av_url_split(proto
, sizeof(proto
), auth
, sizeof(auth
),
226 hostname
, sizeof(hostname
), &port
,
227 path1
, sizeof(path1
), s
->location
);
229 av_strlcpy(tmp_host
, hostname
, sizeof(tmp_host
));
230 // In case of an IPv6 address, we need to strip the Zone ID,
231 // if any. We do it at the first % sign, as percent encoding
232 // can be used in the Zone ID itself.
233 if (strchr(tmp_host
, ':'))
234 tmp_host
[strcspn(tmp_host
, "%")] = '\0';
235 ff_url_join(hoststr
, sizeof(hoststr
), NULL
, NULL
, tmp_host
, port
, NULL
);
237 env_http_proxy
= getenv_utf8("http_proxy");
238 proxy_path
= s
->http_proxy
? s
->http_proxy
: env_http_proxy
;
240 env_no_proxy
= getenv_utf8("no_proxy");
241 use_proxy
= !ff_http_match_no_proxy(env_no_proxy
, hostname
) &&
242 proxy_path
&& av_strstart(proxy_path
, "http://", NULL
);
243 freeenv_utf8(env_no_proxy
);
245 if (!strcmp(proto
, "https")) {
250 /* pass http_proxy to underlying protocol */
252 err
= av_dict_set(options
, "http_proxy", s
->http_proxy
, 0);
260 hashmark
= strchr(path1
, '#');
264 if (path1
[0] == '\0') {
266 } else if (path1
[0] == '?') {
267 snprintf(sanitized_path
, sizeof(sanitized_path
), "/%s", path1
);
268 path
= sanitized_path
;
274 /* Reassemble the request URL without auth string - we don't
275 * want to leak the auth to the proxy. */
276 ff_url_join(urlbuf
, sizeof(urlbuf
), proto
, NULL
, hostname
, port
, "%s",
279 av_url_split(NULL
, 0, proxyauth
, sizeof(proxyauth
),
280 hostname
, sizeof(hostname
), &port
, NULL
, 0, proxy_path
);
283 ff_url_join(buf
, sizeof(buf
), lower_proto
, NULL
, hostname
, port
, NULL
);
286 err
= ffurl_open_whitelist(&s
->hd
, buf
, AVIO_FLAG_READ_WRITE
,
287 &h
->interrupt_callback
, options
,
288 h
->protocol_whitelist
, h
->protocol_blacklist
, h
);
292 freeenv_utf8(env_http_proxy
);
293 return err
< 0 ? err
: http_connect(
294 h
, path
, local_path
, hoststr
, auth
, proxyauth
);
297 static int http_should_reconnect(HTTPContext
*s
, int err
)
299 const char *status_group
;
303 case AVERROR_HTTP_BAD_REQUEST
:
304 case AVERROR_HTTP_UNAUTHORIZED
:
305 case AVERROR_HTTP_FORBIDDEN
:
306 case AVERROR_HTTP_NOT_FOUND
:
307 case AVERROR_HTTP_TOO_MANY_REQUESTS
:
308 case AVERROR_HTTP_OTHER_4XX
:
309 status_group
= "4xx";
312 case AVERROR_HTTP_SERVER_ERROR
:
313 status_group
= "5xx";
317 return s
->reconnect_on_network_error
;
320 if (!s
->reconnect_on_http_error
)
323 if (av_match_list(status_group
, s
->reconnect_on_http_error
, ',') > 0)
326 snprintf(http_code
, sizeof(http_code
), "%d", s
->http_code
);
328 return av_match_list(http_code
, s
->reconnect_on_http_error
, ',') > 0;
331 static char *redirect_cache_get(HTTPContext
*s
)
333 AVDictionaryEntry
*re
;
337 re
= av_dict_get(s
->redirect_cache
, s
->location
, NULL
, AV_DICT_MATCH_CASE
);
342 delim
= strchr(re
->value
, ';');
347 expiry
= strtoll(re
->value
, NULL
, 10);
348 if (time(NULL
) > expiry
) {
355 static int redirect_cache_set(HTTPContext
*s
, const char *source
, const char *dest
, int64_t expiry
)
360 value
= av_asprintf("%"PRIi64
";%s", expiry
, dest
);
362 return AVERROR(ENOMEM
);
365 ret
= av_dict_set(&s
->redirect_cache
, source
, value
, AV_DICT_MATCH_CASE
| AV_DICT_DONT_STRDUP_VAL
);
372 /* return non zero if error */
373 static int http_open_cnx(URLContext
*h
, AVDictionary
**options
)
375 HTTPAuthType cur_auth_type
, cur_proxy_auth_type
;
376 HTTPContext
*s
= h
->priv_data
;
377 int ret
, conn_attempts
= 1, auth_attempts
= 0, redirects
= 0;
378 int reconnect_delay
= 0;
379 int reconnect_delay_total
= 0;
385 cached
= redirect_cache_get(s
);
387 av_free(s
->location
);
388 s
->location
= av_strdup(cached
);
390 ret
= AVERROR(ENOMEM
);
396 av_dict_copy(options
, s
->chained_options
, 0);
398 cur_auth_type
= s
->auth_state
.auth_type
;
399 cur_proxy_auth_type
= s
->auth_state
.auth_type
;
402 ret
= http_open_cnx_internal(h
, options
);
404 if (!http_should_reconnect(s
, ret
) ||
405 reconnect_delay
> s
->reconnect_delay_max
||
406 (s
->reconnect_max_retries
>= 0 && conn_attempts
> s
->reconnect_max_retries
) ||
407 reconnect_delay_total
> s
->reconnect_delay_total_max
)
410 /* Both fields here are in seconds. */
411 if (s
->respect_retry_after
&& s
->retry_after
> 0) {
412 reconnect_delay
= s
->retry_after
;
413 if (reconnect_delay
> s
->reconnect_delay_max
)
418 av_log(h
, AV_LOG_WARNING
, "Will reconnect at %"PRIu64
" in %d second(s).\n", off
, reconnect_delay
);
419 ret
= ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay
, &h
->interrupt_callback
);
420 if (ret
!= AVERROR(ETIMEDOUT
))
422 reconnect_delay_total
+= reconnect_delay
;
423 reconnect_delay
= 1 + 2 * reconnect_delay
;
426 /* restore the offset (http_connect resets it) */
429 ffurl_closep(&s
->hd
);
434 if (s
->http_code
== 401) {
435 if ((cur_auth_type
== HTTP_AUTH_NONE
|| s
->auth_state
.stale
) &&
436 s
->auth_state
.auth_type
!= HTTP_AUTH_NONE
&& auth_attempts
< 4) {
437 ffurl_closep(&s
->hd
);
442 if (s
->http_code
== 407) {
443 if ((cur_proxy_auth_type
== HTTP_AUTH_NONE
|| s
->proxy_auth_state
.stale
) &&
444 s
->proxy_auth_state
.auth_type
!= HTTP_AUTH_NONE
&& auth_attempts
< 4) {
445 ffurl_closep(&s
->hd
);
450 if ((s
->http_code
== 301 || s
->http_code
== 302 ||
451 s
->http_code
== 303 || s
->http_code
== 307 || s
->http_code
== 308) &&
453 /* url moved, get next */
454 ffurl_closep(&s
->hd
);
455 if (redirects
++ >= MAX_REDIRECTS
)
459 s
->expires
= (s
->http_code
== 301 || s
->http_code
== 308) ? INT64_MAX
: -1;
462 if (s
->expires
> time(NULL
) && av_dict_count(s
->redirect_cache
) < MAX_CACHED_REDIRECTS
) {
463 redirect_cache_set(s
, s
->location
, s
->new_location
, s
->expires
);
466 av_free(s
->location
);
467 s
->location
= s
->new_location
;
468 s
->new_location
= NULL
;
470 /* Restart the authentication process with the new target, which
471 * might use a different auth mechanism. */
472 memset(&s
->auth_state
, 0, sizeof(s
->auth_state
));
480 ffurl_closep(&s
->hd
);
483 return ff_http_averror(s
->http_code
, AVERROR(EIO
));
486 int ff_http_do_new_request(URLContext
*h
, const char *uri
) {
487 return ff_http_do_new_request2(h
, uri
, NULL
);
490 int ff_http_do_new_request2(URLContext
*h
, const char *uri
, AVDictionary
**opts
)
492 HTTPContext
*s
= h
->priv_data
;
493 AVDictionary
*options
= NULL
;
495 char hostname1
[1024], hostname2
[1024], proto1
[10], proto2
[10];
499 !(!strcmp(h
->prot
->name
, "http") ||
500 !strcmp(h
->prot
->name
, "https")))
501 return AVERROR(EINVAL
);
503 av_url_split(proto1
, sizeof(proto1
), NULL
, 0,
504 hostname1
, sizeof(hostname1
), &port1
,
505 NULL
, 0, s
->location
);
506 av_url_split(proto2
, sizeof(proto2
), NULL
, 0,
507 hostname2
, sizeof(hostname2
), &port2
,
509 if (strcmp(proto1
, proto2
) != 0) {
510 av_log(h
, AV_LOG_INFO
, "Cannot reuse HTTP connection for different protocol %s vs %s\n",
512 return AVERROR(EINVAL
);
514 if (port1
!= port2
|| strncmp(hostname1
, hostname2
, sizeof(hostname2
)) != 0) {
515 av_log(h
, AV_LOG_INFO
, "Cannot reuse HTTP connection for different host: %s:%d != %s:%d\n",
519 return AVERROR(EINVAL
);
522 if (!s
->end_chunked_post
) {
523 ret
= http_shutdown(h
, h
->flags
);
531 s
->end_chunked_post
= 0;
534 s
->icy_data_read
= 0;
536 av_free(s
->location
);
537 s
->location
= av_strdup(uri
);
539 return AVERROR(ENOMEM
);
542 s
->uri
= av_strdup(uri
);
544 return AVERROR(ENOMEM
);
546 if ((ret
= av_opt_set_dict(s
, opts
)) < 0)
549 av_log(s
, AV_LOG_INFO
, "Opening \'%s\' for %s\n", uri
, h
->flags
& AVIO_FLAG_WRITE
? "writing" : "reading");
550 ret
= http_open_cnx(h
, &options
);
551 av_dict_free(&options
);
555 int ff_http_averror(int status_code
, int default_averror
)
557 switch (status_code
) {
558 case 400: return AVERROR_HTTP_BAD_REQUEST
;
559 case 401: return AVERROR_HTTP_UNAUTHORIZED
;
560 case 403: return AVERROR_HTTP_FORBIDDEN
;
561 case 404: return AVERROR_HTTP_NOT_FOUND
;
562 case 429: return AVERROR_HTTP_TOO_MANY_REQUESTS
;
565 if (status_code
>= 400 && status_code
<= 499)
566 return AVERROR_HTTP_OTHER_4XX
;
567 else if (status_code
>= 500)
568 return AVERROR_HTTP_SERVER_ERROR
;
570 return default_averror
;
573 const char* ff_http_get_new_location(URLContext
*h
)
575 HTTPContext
*s
= h
->priv_data
;
576 return s
->new_location
;
579 static int http_write_reply(URLContext
* h
, int status_code
)
581 int ret
, body
= 0, reply_code
, message_len
;
582 const char *reply_text
, *content_type
;
583 HTTPContext
*s
= h
->priv_data
;
584 char message
[BUFFER_SIZE
];
585 content_type
= "text/plain";
589 switch (status_code
) {
590 case AVERROR_HTTP_BAD_REQUEST
:
593 reply_text
= "Bad Request";
595 case AVERROR_HTTP_FORBIDDEN
:
598 reply_text
= "Forbidden";
600 case AVERROR_HTTP_NOT_FOUND
:
603 reply_text
= "Not Found";
605 case AVERROR_HTTP_TOO_MANY_REQUESTS
:
608 reply_text
= "Too Many Requests";
613 content_type
= s
->content_type
? s
->content_type
: "application/octet-stream";
615 case AVERROR_HTTP_SERVER_ERROR
:
618 reply_text
= "Internal server error";
621 return AVERROR(EINVAL
);
625 message_len
= snprintf(message
, sizeof(message
),
626 "HTTP/1.1 %03d %s\r\n"
627 "Content-Type: %s\r\n"
628 "Content-Length: %"SIZE_SPECIFIER
"\r\n"
635 strlen(reply_text
) + 6, // 3 digit status code + space + \r\n
636 s
->headers
? s
->headers
: "",
641 message_len
= snprintf(message
, sizeof(message
),
642 "HTTP/1.1 %03d %s\r\n"
643 "Content-Type: %s\r\n"
644 "Transfer-Encoding: chunked\r\n"
650 s
->headers
? s
->headers
: "");
652 av_log(h
, AV_LOG_TRACE
, "HTTP reply header: \n%s----\n", message
);
653 if ((ret
= ffurl_write(s
->hd
, message
, message_len
)) < 0)
658 static void handle_http_errors(URLContext
*h
, int error
)
660 av_assert0(error
< 0);
661 http_write_reply(h
, error
);
664 static int http_handshake(URLContext
*c
)
667 HTTPContext
*ch
= c
->priv_data
;
668 URLContext
*cl
= ch
->hd
;
669 switch (ch
->handshake_step
) {
671 av_log(c
, AV_LOG_TRACE
, "Lower protocol\n");
672 if ((ret
= ffurl_handshake(cl
)) > 0)
676 ch
->handshake_step
= READ_HEADERS
;
677 ch
->is_connected_server
= 1;
680 av_log(c
, AV_LOG_TRACE
, "Read headers\n");
681 if ((err
= http_read_header(c
)) < 0) {
682 handle_http_errors(c
, err
);
685 ch
->handshake_step
= WRITE_REPLY_HEADERS
;
687 case WRITE_REPLY_HEADERS
:
688 av_log(c
, AV_LOG_TRACE
, "Reply code: %d\n", ch
->reply_code
);
689 if ((err
= http_write_reply(c
, ch
->reply_code
)) < 0)
691 ch
->handshake_step
= FINISH
;
696 // this should never be reached.
697 return AVERROR(EINVAL
);
700 static int http_listen(URLContext
*h
, const char *uri
, int flags
,
701 AVDictionary
**options
) {
702 HTTPContext
*s
= h
->priv_data
;
704 char hostname
[1024], proto
[10];
706 const char *lower_proto
= "tcp";
708 av_url_split(proto
, sizeof(proto
), NULL
, 0, hostname
, sizeof(hostname
), &port
,
710 if (!strcmp(proto
, "https"))
712 ff_url_join(lower_url
, sizeof(lower_url
), lower_proto
, NULL
, hostname
, port
,
714 if ((ret
= av_dict_set_int(options
, "listen", s
->listen
, 0)) < 0)
716 if ((ret
= ffurl_open_whitelist(&s
->hd
, lower_url
, AVIO_FLAG_READ_WRITE
,
717 &h
->interrupt_callback
, options
,
718 h
->protocol_whitelist
, h
->protocol_blacklist
, h
721 s
->handshake_step
= LOWER_PROTO
;
722 if (s
->listen
== HTTP_SINGLE
) { /* single client */
724 while ((ret
= http_handshake(h
)) > 0);
727 av_dict_free(&s
->chained_options
);
728 av_dict_free(&s
->cookie_dict
);
732 static int http_open(URLContext
*h
, const char *uri
, int flags
,
733 AVDictionary
**options
)
735 HTTPContext
*s
= h
->priv_data
;
738 if( s
->seekable
== 1 )
743 s
->filesize
= UINT64_MAX
;
745 s
->location
= av_strdup(uri
);
747 return AVERROR(ENOMEM
);
749 s
->uri
= av_strdup(uri
);
751 return AVERROR(ENOMEM
);
754 av_dict_copy(&s
->chained_options
, *options
, 0);
757 int len
= strlen(s
->headers
);
758 if (len
< 2 || strcmp("\r\n", s
->headers
+ len
- 2)) {
759 av_log(h
, AV_LOG_WARNING
,
760 "No trailing CRLF found in HTTP header. Adding it.\n");
761 ret
= av_reallocp(&s
->headers
, len
+ 3);
764 s
->headers
[len
] = '\r';
765 s
->headers
[len
+ 1] = '\n';
766 s
->headers
[len
+ 2] = '\0';
771 return http_listen(h
, uri
, flags
, options
);
773 ret
= http_open_cnx(h
, options
);
776 av_dict_free(&s
->chained_options
);
777 av_dict_free(&s
->cookie_dict
);
778 av_dict_free(&s
->redirect_cache
);
779 av_freep(&s
->new_location
);
785 static int http_accept(URLContext
*s
, URLContext
**c
)
788 HTTPContext
*sc
= s
->priv_data
;
790 URLContext
*sl
= sc
->hd
;
791 URLContext
*cl
= NULL
;
793 av_assert0(sc
->listen
);
794 if ((ret
= ffurl_alloc(c
, s
->filename
, s
->flags
, &sl
->interrupt_callback
)) < 0)
796 cc
= (*c
)->priv_data
;
797 if ((ret
= ffurl_accept(sl
, &cl
)) < 0)
800 cc
->is_multi_client
= 1;
809 static int http_getc(HTTPContext
*s
)
812 if (s
->buf_ptr
>= s
->buf_end
) {
813 len
= ffurl_read(s
->hd
, s
->buffer
, BUFFER_SIZE
);
816 } else if (len
== 0) {
819 s
->buf_ptr
= s
->buffer
;
820 s
->buf_end
= s
->buffer
+ len
;
823 return *s
->buf_ptr
++;
826 static int http_get_line(HTTPContext
*s
, char *line
, int line_size
)
838 if (q
> line
&& q
[-1] == '\r')
844 if ((q
- line
) < line_size
- 1)
850 static int check_http_code(URLContext
*h
, int http_code
, const char *end
)
852 HTTPContext
*s
= h
->priv_data
;
853 /* error codes are 4xx and 5xx, but regard 401 as a success, so we
854 * don't abort until all headers have been parsed. */
855 if (http_code
>= 400 && http_code
< 600 &&
856 (http_code
!= 401 || s
->auth_state
.auth_type
!= HTTP_AUTH_NONE
) &&
857 (http_code
!= 407 || s
->proxy_auth_state
.auth_type
!= HTTP_AUTH_NONE
)) {
858 end
+= strspn(end
, SPACE_CHARS
);
859 av_log(h
, AV_LOG_WARNING
, "HTTP error %d %s\n", http_code
, end
);
860 return ff_http_averror(http_code
, AVERROR(EIO
));
865 static int parse_location(HTTPContext
*s
, const char *p
)
867 char redirected_location
[MAX_URL_SIZE
];
868 ff_make_absolute_url(redirected_location
, sizeof(redirected_location
),
870 av_freep(&s
->new_location
);
871 s
->new_location
= av_strdup(redirected_location
);
872 if (!s
->new_location
)
873 return AVERROR(ENOMEM
);
877 /* "bytes $from-$to/$document_size" */
878 static void parse_content_range(URLContext
*h
, const char *p
)
880 HTTPContext
*s
= h
->priv_data
;
883 if (!strncmp(p
, "bytes ", 6)) {
885 s
->off
= strtoull(p
, NULL
, 10);
886 if ((slash
= strchr(p
, '/')) && strlen(slash
) > 0)
887 s
->filesize_from_content_range
= strtoull(slash
+ 1, NULL
, 10);
889 if (s
->seekable
== -1 && (!s
->is_akamai
|| s
->filesize
!= 2147483647))
890 h
->is_streamed
= 0; /* we _can_ in fact seek */
893 static int parse_content_encoding(URLContext
*h
, const char *p
)
895 if (!av_strncasecmp(p
, "gzip", 4) ||
896 !av_strncasecmp(p
, "deflate", 7)) {
898 HTTPContext
*s
= h
->priv_data
;
901 inflateEnd(&s
->inflate_stream
);
902 if (inflateInit2(&s
->inflate_stream
, 32 + 15) != Z_OK
) {
903 av_log(h
, AV_LOG_WARNING
, "Error during zlib initialisation: %s\n",
904 s
->inflate_stream
.msg
);
905 return AVERROR(ENOSYS
);
907 if (zlibCompileFlags() & (1 << 17)) {
908 av_log(h
, AV_LOG_WARNING
,
909 "Your zlib was compiled without gzip support.\n");
910 return AVERROR(ENOSYS
);
913 av_log(h
, AV_LOG_WARNING
,
914 "Compressed (%s) content, need zlib with gzip support\n", p
);
915 return AVERROR(ENOSYS
);
916 #endif /* CONFIG_ZLIB */
917 } else if (!av_strncasecmp(p
, "identity", 8)) {
918 // The normal, no-encoding case (although servers shouldn't include
919 // the header at all if this is the case).
921 av_log(h
, AV_LOG_WARNING
, "Unknown content coding: %s\n", p
);
926 // Concat all Icy- header lines
927 static int parse_icy(HTTPContext
*s
, const char *tag
, const char *p
)
929 int len
= 4 + strlen(p
) + strlen(tag
);
930 int is_first
= !s
->icy_metadata_headers
;
933 av_dict_set(&s
->metadata
, tag
, p
, 0);
935 if (s
->icy_metadata_headers
)
936 len
+= strlen(s
->icy_metadata_headers
);
938 if ((ret
= av_reallocp(&s
->icy_metadata_headers
, len
)) < 0)
942 *s
->icy_metadata_headers
= '\0';
944 av_strlcatf(s
->icy_metadata_headers
, len
, "%s: %s\n", tag
, p
);
949 static int parse_http_date(const char *date_str
, struct tm
*buf
)
951 char date_buf
[MAX_DATE_LEN
];
952 int i
, j
, date_buf_len
= MAX_DATE_LEN
-1;
955 // strip off any punctuation or whitespace
956 for (i
= 0, j
= 0; date_str
[i
] != '\0' && j
< date_buf_len
; i
++) {
957 if ((date_str
[i
] >= '0' && date_str
[i
] <= '9') ||
958 (date_str
[i
] >= 'A' && date_str
[i
] <= 'Z') ||
959 (date_str
[i
] >= 'a' && date_str
[i
] <= 'z')) {
960 date_buf
[j
] = date_str
[i
];
967 // move the string beyond the day of week
968 while ((*date
< '0' || *date
> '9') && *date
!= '\0')
971 return av_small_strptime(date
, "%d%b%Y%H%M%S", buf
) ? 0 : AVERROR(EINVAL
);
974 static int parse_set_cookie(const char *set_cookie
, AVDictionary
**dict
)
976 char *param
, *next_param
, *cstr
, *back
;
977 char *saveptr
= NULL
;
982 if (!(cstr
= av_strdup(set_cookie
)))
983 return AVERROR(EINVAL
);
985 // strip any trailing whitespace
986 back
= &cstr
[strlen(cstr
)-1];
987 while (strchr(WHITESPACES
, *back
)) {
995 while ((param
= av_strtok(next_param
, ";", &saveptr
))) {
998 param
+= strspn(param
, WHITESPACES
);
999 if ((name
= av_strtok(param
, "=", &value
))) {
1000 if (av_dict_set(dict
, name
, value
, 0) < 0) {
1011 static int parse_cookie(HTTPContext
*s
, const char *p
, AVDictionary
**cookies
)
1013 AVDictionary
*new_params
= NULL
;
1014 const AVDictionaryEntry
*e
, *cookie_entry
;
1017 // ensure the cookie is parsable
1018 if (parse_set_cookie(p
, &new_params
))
1021 // if there is no cookie value there is nothing to parse
1022 cookie_entry
= av_dict_iterate(new_params
, NULL
);
1023 if (!cookie_entry
|| !cookie_entry
->value
) {
1024 av_dict_free(&new_params
);
1028 // ensure the cookie is not expired or older than an existing value
1029 if ((e
= av_dict_get(new_params
, "expires", NULL
, 0)) && e
->value
) {
1030 struct tm new_tm
= {0};
1031 if (!parse_http_date(e
->value
, &new_tm
)) {
1032 AVDictionaryEntry
*e2
;
1034 // if the cookie has already expired ignore it
1035 if (av_timegm(&new_tm
) < av_gettime() / 1000000) {
1036 av_dict_free(&new_params
);
1040 // only replace an older cookie with the same name
1041 e2
= av_dict_get(*cookies
, cookie_entry
->key
, NULL
, 0);
1042 if (e2
&& e2
->value
) {
1043 AVDictionary
*old_params
= NULL
;
1044 if (!parse_set_cookie(p
, &old_params
)) {
1045 e2
= av_dict_get(old_params
, "expires", NULL
, 0);
1046 if (e2
&& e2
->value
) {
1047 struct tm old_tm
= {0};
1048 if (!parse_http_date(e
->value
, &old_tm
)) {
1049 if (av_timegm(&new_tm
) < av_timegm(&old_tm
)) {
1050 av_dict_free(&new_params
);
1051 av_dict_free(&old_params
);
1057 av_dict_free(&old_params
);
1061 av_dict_free(&new_params
);
1063 // duplicate the cookie name (dict will dupe the value)
1064 if (!(eql
= strchr(p
, '='))) return AVERROR(EINVAL
);
1065 if (!(name
= av_strndup(p
, eql
- p
))) return AVERROR(ENOMEM
);
1067 // add the cookie to the dictionary
1068 av_dict_set(cookies
, name
, eql
, AV_DICT_DONT_STRDUP_KEY
);
1073 static int cookie_string(AVDictionary
*dict
, char **cookies
)
1075 const AVDictionaryEntry
*e
= NULL
;
1078 // determine how much memory is needed for the cookies string
1079 while ((e
= av_dict_iterate(dict
, e
)))
1080 len
+= strlen(e
->key
) + strlen(e
->value
) + 1;
1082 // reallocate the cookies
1084 if (*cookies
) av_free(*cookies
);
1085 *cookies
= av_malloc(len
);
1086 if (!*cookies
) return AVERROR(ENOMEM
);
1089 // write out the cookies
1090 while ((e
= av_dict_iterate(dict
, e
)))
1091 av_strlcatf(*cookies
, len
, "%s%s\n", e
->key
, e
->value
);
1096 static void parse_expires(HTTPContext
*s
, const char *p
)
1100 if (!parse_http_date(p
, &tm
)) {
1101 s
->expires
= av_timegm(&tm
);
1105 static void parse_cache_control(HTTPContext
*s
, const char *p
)
1110 /* give 'Expires' higher priority over 'Cache-Control' */
1115 if (av_stristr(p
, "no-cache") || av_stristr(p
, "no-store")) {
1120 age
= av_stristr(p
, "s-maxage=");
1123 age
= av_stristr(p
, "max-age=");
1128 s
->expires
= time(NULL
) + atoi(p
+ offset
);
1132 static int process_line(URLContext
*h
, char *line
, int line_count
, int *parsed_http_code
)
1134 HTTPContext
*s
= h
->priv_data
;
1135 const char *auto_method
= h
->flags
& AVIO_FLAG_READ
? "POST" : "GET";
1136 char *tag
, *p
, *end
, *method
, *resource
, *version
;
1140 if (line
[0] == '\0') {
1146 if (line_count
== 0) {
1147 if (s
->is_connected_server
) {
1150 while (*p
&& !av_isspace(*p
))
1153 av_log(h
, AV_LOG_TRACE
, "Received method: %s\n", method
);
1155 if (av_strcasecmp(s
->method
, method
)) {
1156 av_log(h
, AV_LOG_ERROR
, "Received and expected HTTP method do not match. (%s expected, %s received)\n",
1158 return ff_http_averror(400, AVERROR(EIO
));
1161 // use autodetected HTTP method to expect
1162 av_log(h
, AV_LOG_TRACE
, "Autodetected %s HTTP method\n", auto_method
);
1163 if (av_strcasecmp(auto_method
, method
)) {
1164 av_log(h
, AV_LOG_ERROR
, "Received and autodetected HTTP method did not match "
1165 "(%s autodetected %s received)\n", auto_method
, method
);
1166 return ff_http_averror(400, AVERROR(EIO
));
1168 if (!(s
->method
= av_strdup(method
)))
1169 return AVERROR(ENOMEM
);
1173 while (av_isspace(*p
))
1176 while (*p
&& !av_isspace(*p
))
1179 av_log(h
, AV_LOG_TRACE
, "Requested resource: %s\n", resource
);
1180 if (!(s
->resource
= av_strdup(resource
)))
1181 return AVERROR(ENOMEM
);
1184 while (av_isspace(*p
))
1187 while (*p
&& !av_isspace(*p
))
1190 if (av_strncasecmp(version
, "HTTP/", 5)) {
1191 av_log(h
, AV_LOG_ERROR
, "Malformed HTTP version string.\n");
1192 return ff_http_averror(400, AVERROR(EIO
));
1194 av_log(h
, AV_LOG_TRACE
, "HTTP version string: %s\n", version
);
1196 if (av_strncasecmp(p
, "HTTP/1.0", 8) == 0)
1198 while (*p
!= '/' && *p
!= '\0')
1202 av_freep(&s
->http_version
);
1203 s
->http_version
= av_strndup(p
, 3);
1204 while (!av_isspace(*p
) && *p
!= '\0')
1206 while (av_isspace(*p
))
1208 s
->http_code
= strtol(p
, &end
, 10);
1210 av_log(h
, AV_LOG_TRACE
, "http_code=%d\n", s
->http_code
);
1212 *parsed_http_code
= 1;
1214 if ((ret
= check_http_code(h
, s
->http_code
, end
)) < 0)
1218 while (*p
!= '\0' && *p
!= ':')
1226 while (av_isspace(*p
))
1228 if (!av_strcasecmp(tag
, "Location")) {
1229 if ((ret
= parse_location(s
, p
)) < 0)
1231 } else if (!av_strcasecmp(tag
, "Content-Length") &&
1232 s
->filesize
== UINT64_MAX
) {
1233 s
->filesize
= strtoull(p
, NULL
, 10);
1234 } else if (!av_strcasecmp(tag
, "Content-Range")) {
1235 parse_content_range(h
, p
);
1236 } else if (!av_strcasecmp(tag
, "Accept-Ranges") &&
1237 !strncmp(p
, "bytes", 5) &&
1238 s
->seekable
== -1) {
1240 } else if (!av_strcasecmp(tag
, "Transfer-Encoding") &&
1241 !av_strncasecmp(p
, "chunked", 7)) {
1242 s
->filesize
= UINT64_MAX
;
1244 } else if (!av_strcasecmp(tag
, "WWW-Authenticate")) {
1245 ff_http_auth_handle_header(&s
->auth_state
, tag
, p
);
1246 } else if (!av_strcasecmp(tag
, "Authentication-Info")) {
1247 ff_http_auth_handle_header(&s
->auth_state
, tag
, p
);
1248 } else if (!av_strcasecmp(tag
, "Proxy-Authenticate")) {
1249 ff_http_auth_handle_header(&s
->proxy_auth_state
, tag
, p
);
1250 } else if (!av_strcasecmp(tag
, "Connection")) {
1251 if (!strcmp(p
, "close"))
1253 } else if (!av_strcasecmp(tag
, "Server")) {
1254 if (!av_strcasecmp(p
, "AkamaiGHost")) {
1256 } else if (!av_strncasecmp(p
, "MediaGateway", 12)) {
1257 s
->is_mediagateway
= 1;
1259 } else if (!av_strcasecmp(tag
, "Content-Type")) {
1260 av_free(s
->mime_type
);
1261 s
->mime_type
= av_get_token((const char **)&p
, ";");
1262 } else if (!av_strcasecmp(tag
, "Set-Cookie")) {
1263 if (parse_cookie(s
, p
, &s
->cookie_dict
))
1264 av_log(h
, AV_LOG_WARNING
, "Unable to parse '%s'\n", p
);
1265 } else if (!av_strcasecmp(tag
, "Icy-MetaInt")) {
1266 s
->icy_metaint
= strtoull(p
, NULL
, 10);
1267 } else if (!av_strncasecmp(tag
, "Icy-", 4)) {
1268 if ((ret
= parse_icy(s
, tag
, p
)) < 0)
1270 } else if (!av_strcasecmp(tag
, "Content-Encoding")) {
1271 if ((ret
= parse_content_encoding(h
, p
)) < 0)
1273 } else if (!av_strcasecmp(tag
, "Expires")) {
1274 parse_expires(s
, p
);
1275 } else if (!av_strcasecmp(tag
, "Cache-Control")) {
1276 parse_cache_control(s
, p
);
1277 } else if (!av_strcasecmp(tag
, "Retry-After")) {
1278 /* The header can be either an integer that represents seconds, or a date. */
1280 int date_ret
= parse_http_date(p
, &tm
);
1282 time_t retry
= av_timegm(&tm
);
1283 int64_t now
= av_gettime() / 1000000;
1284 int64_t diff
= ((int64_t) retry
) - now
;
1285 s
->retry_after
= (unsigned int) FFMAX(0, diff
);
1287 s
->retry_after
= strtoul(p
, NULL
, 10);
1295 * Create a string containing cookie values for use as a HTTP cookie header
1296 * field value for a particular path and domain from the cookie values stored in
1297 * the HTTP protocol context. The cookie string is stored in *cookies, and may
1298 * be NULL if there are no valid cookies.
1300 * @return a negative value if an error condition occurred, 0 otherwise
1302 static int get_cookies(HTTPContext
*s
, char **cookies
, const char *path
,
1305 // cookie strings will look like Set-Cookie header field values. Multiple
1306 // Set-Cookie fields will result in multiple values delimited by a newline
1308 char *cookie
, *set_cookies
, *next
;
1309 char *saveptr
= NULL
;
1311 // destroy any cookies in the dictionary.
1312 av_dict_free(&s
->cookie_dict
);
1317 next
= set_cookies
= av_strdup(s
->cookies
);
1319 return AVERROR(ENOMEM
);
1322 while ((cookie
= av_strtok(next
, "\n", &saveptr
)) && !ret
) {
1323 AVDictionary
*cookie_params
= NULL
;
1324 const AVDictionaryEntry
*cookie_entry
, *e
;
1327 // store the cookie in a dict in case it is updated in the response
1328 if (parse_cookie(s
, cookie
, &s
->cookie_dict
))
1329 av_log(s
, AV_LOG_WARNING
, "Unable to parse '%s'\n", cookie
);
1331 // continue on to the next cookie if this one cannot be parsed
1332 if (parse_set_cookie(cookie
, &cookie_params
))
1335 // if the cookie has no value, skip it
1336 cookie_entry
= av_dict_iterate(cookie_params
, NULL
);
1337 if (!cookie_entry
|| !cookie_entry
->value
)
1340 // if the cookie has expired, don't add it
1341 if ((e
= av_dict_get(cookie_params
, "expires", NULL
, 0)) && e
->value
) {
1342 struct tm tm_buf
= {0};
1343 if (!parse_http_date(e
->value
, &tm_buf
)) {
1344 if (av_timegm(&tm_buf
) < av_gettime() / 1000000)
1349 // if no domain in the cookie assume it applied to this request
1350 if ((e
= av_dict_get(cookie_params
, "domain", NULL
, 0)) && e
->value
) {
1351 // find the offset comparison is on the min domain (b.com, not a.b.com)
1352 int domain_offset
= strlen(domain
) - strlen(e
->value
);
1353 if (domain_offset
< 0)
1356 // match the cookie domain
1357 if (av_strcasecmp(&domain
[domain_offset
], e
->value
))
1361 // if a cookie path is provided, ensure the request path is within that path
1362 e
= av_dict_get(cookie_params
, "path", NULL
, 0);
1363 if (e
&& av_strncasecmp(path
, e
->value
, strlen(e
->value
)))
1366 // cookie parameters match, so copy the value
1368 *cookies
= av_asprintf("%s=%s", cookie_entry
->key
, cookie_entry
->value
);
1370 char *tmp
= *cookies
;
1371 *cookies
= av_asprintf("%s; %s=%s", tmp
, cookie_entry
->key
, cookie_entry
->value
);
1375 ret
= AVERROR(ENOMEM
);
1378 av_dict_free(&cookie_params
);
1381 av_free(set_cookies
);
1386 static inline int has_header(const char *str
, const char *header
)
1388 /* header + 2 to skip over CRLF prefix. (make sure you have one!) */
1391 return av_stristart(str
, header
+ 2, NULL
) || av_stristr(str
, header
);
1394 static int http_read_header(URLContext
*h
)
1396 HTTPContext
*s
= h
->priv_data
;
1397 char line
[MAX_URL_SIZE
];
1398 int err
= 0, http_err
= 0;
1400 av_freep(&s
->new_location
);
1402 s
->chunksize
= UINT64_MAX
;
1403 s
->filesize_from_content_range
= UINT64_MAX
;
1406 int parsed_http_code
= 0;
1408 if ((err
= http_get_line(s
, line
, sizeof(line
))) < 0)
1411 av_log(h
, AV_LOG_TRACE
, "header='%s'\n", line
);
1413 err
= process_line(h
, line
, s
->line_count
, &parsed_http_code
);
1415 if (parsed_http_code
) {
1418 /* Prefer to return HTTP code error if we've already seen one. */
1432 // filesize from Content-Range can always be used, even if using chunked Transfer-Encoding
1433 if (s
->filesize_from_content_range
!= UINT64_MAX
)
1434 s
->filesize
= s
->filesize_from_content_range
;
1436 if (s
->seekable
== -1 && s
->is_mediagateway
&& s
->filesize
== 2000000000)
1437 h
->is_streamed
= 1; /* we can in fact _not_ seek */
1439 // add any new cookies into the existing cookie string
1440 cookie_string(s
->cookie_dict
, &s
->cookies
);
1441 av_dict_free(&s
->cookie_dict
);
1447 * Escape unsafe characters in path in order to pass them safely to the HTTP
1448 * request. Insipred by the algorithm in GNU wget:
1449 * - escape "%" characters not followed by two hex digits
1450 * - escape all "unsafe" characters except which are also "reserved"
1451 * - pass through everything else
1453 static void bprint_escaped_path(AVBPrint
*bp
, const char *path
)
1455 #define NEEDS_ESCAPE(ch) \
1456 ((ch) <= ' ' || (ch) >= '\x7f' || \
1457 (ch) == '"' || (ch) == '%' || (ch) == '<' || (ch) == '>' || (ch) == '\\' || \
1458 (ch) == '^' || (ch) == '`' || (ch) == '{' || (ch) == '}' || (ch) == '|')
1462 while (*path
&& q
- buf
< sizeof(buf
) - 4) {
1463 if (path
[0] == '%' && av_isxdigit(path
[1]) && av_isxdigit(path
[2])) {
1467 } else if (NEEDS_ESCAPE(*path
)) {
1468 q
+= snprintf(q
, 4, "%%%02X", (uint8_t)*path
++);
1473 av_bprint_append_data(bp
, buf
, q
- buf
);
1477 static int http_connect(URLContext
*h
, const char *path
, const char *local_path
,
1478 const char *hoststr
, const char *auth
,
1479 const char *proxyauth
)
1481 HTTPContext
*s
= h
->priv_data
;
1484 char *authstr
= NULL
, *proxyauthstr
= NULL
;
1485 uint64_t off
= s
->off
;
1487 int send_expect_100
= 0;
1489 av_bprint_init_for_buffer(&request
, s
->buffer
, sizeof(s
->buffer
));
1491 /* send http header */
1492 post
= h
->flags
& AVIO_FLAG_WRITE
;
1495 /* force POST method and disable chunked encoding when
1496 * custom HTTP post data is set */
1498 s
->chunked_post
= 0;
1504 method
= post
? "POST" : "GET";
1506 authstr
= ff_http_auth_create_response(&s
->auth_state
, auth
,
1507 local_path
, method
);
1508 proxyauthstr
= ff_http_auth_create_response(&s
->proxy_auth_state
, proxyauth
,
1509 local_path
, method
);
1511 if (post
&& !s
->post_data
) {
1512 if (s
->send_expect_100
!= -1) {
1513 send_expect_100
= s
->send_expect_100
;
1515 send_expect_100
= 0;
1516 /* The user has supplied authentication but we don't know the auth type,
1517 * send Expect: 100-continue to get the 401 response including the
1518 * WWW-Authenticate header, or an 100 continue if no auth actually
1520 if (auth
&& *auth
&&
1521 s
->auth_state
.auth_type
== HTTP_AUTH_NONE
&&
1522 s
->http_code
!= 401)
1523 send_expect_100
= 1;
1527 av_bprintf(&request
, "%s ", method
);
1528 bprint_escaped_path(&request
, path
);
1529 av_bprintf(&request
, " HTTP/1.1\r\n");
1531 if (post
&& s
->chunked_post
)
1532 av_bprintf(&request
, "Transfer-Encoding: chunked\r\n");
1533 /* set default headers if needed */
1534 if (!has_header(s
->headers
, "\r\nUser-Agent: "))
1535 av_bprintf(&request
, "User-Agent: %s\r\n", s
->user_agent
);
1537 /* set default headers if needed */
1538 if (!has_header(s
->headers
, "\r\nReferer: "))
1539 av_bprintf(&request
, "Referer: %s\r\n", s
->referer
);
1541 if (!has_header(s
->headers
, "\r\nAccept: "))
1542 av_bprintf(&request
, "Accept: */*\r\n");
1543 // Note: we send the Range header on purpose, even when we're probing,
1544 // since it allows us to detect more reliably if a (non-conforming)
1545 // server supports seeking by analysing the reply headers.
1546 if (!has_header(s
->headers
, "\r\nRange: ") && !post
&& (s
->off
> 0 || s
->end_off
|| s
->seekable
!= 0)) {
1547 av_bprintf(&request
, "Range: bytes=%"PRIu64
"-", s
->off
);
1549 av_bprintf(&request
, "%"PRId64
, s
->end_off
- 1);
1550 av_bprintf(&request
, "\r\n");
1552 if (send_expect_100
&& !has_header(s
->headers
, "\r\nExpect: "))
1553 av_bprintf(&request
, "Expect: 100-continue\r\n");
1555 if (!has_header(s
->headers
, "\r\nConnection: "))
1556 av_bprintf(&request
, "Connection: %s\r\n", s
->multiple_requests
? "keep-alive" : "close");
1558 if (!has_header(s
->headers
, "\r\nHost: "))
1559 av_bprintf(&request
, "Host: %s\r\n", hoststr
);
1560 if (!has_header(s
->headers
, "\r\nContent-Length: ") && s
->post_data
)
1561 av_bprintf(&request
, "Content-Length: %d\r\n", s
->post_datalen
);
1563 if (!has_header(s
->headers
, "\r\nContent-Type: ") && s
->content_type
)
1564 av_bprintf(&request
, "Content-Type: %s\r\n", s
->content_type
);
1565 if (!has_header(s
->headers
, "\r\nCookie: ") && s
->cookies
) {
1566 char *cookies
= NULL
;
1567 if (!get_cookies(s
, &cookies
, path
, hoststr
) && cookies
) {
1568 av_bprintf(&request
, "Cookie: %s\r\n", cookies
);
1572 if (!has_header(s
->headers
, "\r\nIcy-MetaData: ") && s
->icy
)
1573 av_bprintf(&request
, "Icy-MetaData: 1\r\n");
1575 /* now add in custom headers */
1577 av_bprintf(&request
, "%s", s
->headers
);
1580 av_bprintf(&request
, "%s", authstr
);
1582 av_bprintf(&request
, "Proxy-%s", proxyauthstr
);
1583 av_bprintf(&request
, "\r\n");
1585 av_log(h
, AV_LOG_DEBUG
, "request: %s\n", request
.str
);
1587 if (!av_bprint_is_complete(&request
)) {
1588 av_log(h
, AV_LOG_ERROR
, "overlong headers\n");
1589 err
= AVERROR(EINVAL
);
1593 if ((err
= ffurl_write(s
->hd
, request
.str
, request
.len
)) < 0)
1597 if ((err
= ffurl_write(s
->hd
, s
->post_data
, s
->post_datalen
)) < 0)
1600 /* init input buffer */
1601 s
->buf_ptr
= s
->buffer
;
1602 s
->buf_end
= s
->buffer
;
1605 s
->icy_data_read
= 0;
1606 s
->filesize
= UINT64_MAX
;
1608 s
->end_chunked_post
= 0;
1613 if (post
&& !s
->post_data
&& !send_expect_100
) {
1614 /* Pretend that it did work. We didn't read any header yet, since
1615 * we've still to send the POST data, but the code calling this
1616 * function will check http_code after we return. */
1622 /* wait for header */
1623 err
= http_read_header(h
);
1627 if (s
->new_location
)
1630 err
= (off
== s
->off
) ? 0 : -1;
1633 av_freep(&proxyauthstr
);
1637 static int http_buf_read(URLContext
*h
, uint8_t *buf
, int size
)
1639 HTTPContext
*s
= h
->priv_data
;
1642 if (s
->chunksize
!= UINT64_MAX
) {
1646 if (!s
->chunksize
) {
1651 if ((err
= http_get_line(s
, line
, sizeof(line
))) < 0)
1653 } while (!*line
); /* skip CR LF from last chunk */
1655 s
->chunksize
= strtoull(line
, NULL
, 16);
1657 av_log(h
, AV_LOG_TRACE
,
1658 "Chunked encoding data size: %"PRIu64
"\n",
1661 if (!s
->chunksize
&& s
->multiple_requests
) {
1662 http_get_line(s
, line
, sizeof(line
)); // read empty chunk
1666 else if (!s
->chunksize
) {
1667 av_log(h
, AV_LOG_DEBUG
, "Last chunk received, closing conn\n");
1668 ffurl_closep(&s
->hd
);
1671 else if (s
->chunksize
== UINT64_MAX
) {
1672 av_log(h
, AV_LOG_ERROR
, "Invalid chunk size %"PRIu64
"\n",
1674 return AVERROR(EINVAL
);
1677 size
= FFMIN(size
, s
->chunksize
);
1680 /* read bytes from input buffer first */
1681 len
= s
->buf_end
- s
->buf_ptr
;
1685 memcpy(buf
, s
->buf_ptr
, len
);
1688 uint64_t target_end
= s
->end_off
? s
->end_off
: s
->filesize
;
1689 if ((!s
->willclose
|| s
->chunksize
== UINT64_MAX
) && s
->off
>= target_end
)
1691 len
= ffurl_read(s
->hd
, buf
, size
);
1692 if ((!len
|| len
== AVERROR_EOF
) &&
1693 (!s
->willclose
|| s
->chunksize
== UINT64_MAX
) && s
->off
< target_end
) {
1694 av_log(h
, AV_LOG_ERROR
,
1695 "Stream ends prematurely at %"PRIu64
", should be %"PRIu64
"\n",
1698 return AVERROR(EIO
);
1703 if (s
->chunksize
> 0 && s
->chunksize
!= UINT64_MAX
) {
1704 av_assert0(s
->chunksize
>= len
);
1705 s
->chunksize
-= len
;
1712 #define DECOMPRESS_BUF_SIZE (256 * 1024)
1713 static int http_buf_read_compressed(URLContext
*h
, uint8_t *buf
, int size
)
1715 HTTPContext
*s
= h
->priv_data
;
1718 if (!s
->inflate_buffer
) {
1719 s
->inflate_buffer
= av_malloc(DECOMPRESS_BUF_SIZE
);
1720 if (!s
->inflate_buffer
)
1721 return AVERROR(ENOMEM
);
1724 if (s
->inflate_stream
.avail_in
== 0) {
1725 int read
= http_buf_read(h
, s
->inflate_buffer
, DECOMPRESS_BUF_SIZE
);
1728 s
->inflate_stream
.next_in
= s
->inflate_buffer
;
1729 s
->inflate_stream
.avail_in
= read
;
1732 s
->inflate_stream
.avail_out
= size
;
1733 s
->inflate_stream
.next_out
= buf
;
1735 ret
= inflate(&s
->inflate_stream
, Z_SYNC_FLUSH
);
1736 if (ret
!= Z_OK
&& ret
!= Z_STREAM_END
)
1737 av_log(h
, AV_LOG_WARNING
, "inflate return value: %d, %s\n",
1738 ret
, s
->inflate_stream
.msg
);
1740 return size
- s
->inflate_stream
.avail_out
;
1742 #endif /* CONFIG_ZLIB */
1744 static int64_t http_seek_internal(URLContext
*h
, int64_t off
, int whence
, int force_reconnect
);
1746 static int http_read_stream(URLContext
*h
, uint8_t *buf
, int size
)
1748 HTTPContext
*s
= h
->priv_data
;
1751 int reconnect_delay
= 0;
1752 int reconnect_delay_total
= 0;
1753 int conn_attempts
= 1;
1758 if (s
->end_chunked_post
&& !s
->end_header
) {
1759 err
= http_read_header(h
);
1766 return http_buf_read_compressed(h
, buf
, size
);
1767 #endif /* CONFIG_ZLIB */
1768 read_ret
= http_buf_read(h
, buf
, size
);
1769 while (read_ret
< 0) {
1770 uint64_t target
= h
->is_streamed
? 0 : s
->off
;
1771 bool is_premature
= s
->filesize
> 0 && s
->off
< s
->filesize
;
1773 if (read_ret
== AVERROR_EXIT
)
1776 if (h
->is_streamed
&& !s
->reconnect_streamed
)
1779 if (!(s
->reconnect
&& is_premature
) &&
1780 !(s
->reconnect_at_eof
&& read_ret
== AVERROR_EOF
)) {
1782 return AVERROR(EIO
);
1787 if (reconnect_delay
> s
->reconnect_delay_max
|| (s
->reconnect_max_retries
>= 0 && conn_attempts
> s
->reconnect_max_retries
) ||
1788 reconnect_delay_total
> s
->reconnect_delay_total_max
)
1789 return AVERROR(EIO
);
1791 av_log(h
, AV_LOG_WARNING
, "Will reconnect at %"PRIu64
" in %d second(s), error=%s.\n", s
->off
, reconnect_delay
, av_err2str(read_ret
));
1792 err
= ff_network_sleep_interruptible(1000U*1000*reconnect_delay
, &h
->interrupt_callback
);
1793 if (err
!= AVERROR(ETIMEDOUT
))
1795 reconnect_delay_total
+= reconnect_delay
;
1796 reconnect_delay
= 1 + 2*reconnect_delay
;
1798 seek_ret
= http_seek_internal(h
, target
, SEEK_SET
, 1);
1799 if (seek_ret
>= 0 && seek_ret
!= target
) {
1800 av_log(h
, AV_LOG_ERROR
, "Failed to reconnect at %"PRIu64
".\n", target
);
1804 read_ret
= http_buf_read(h
, buf
, size
);
1810 // Like http_read_stream(), but no short reads.
1811 // Assumes partial reads are an error.
1812 static int http_read_stream_all(URLContext
*h
, uint8_t *buf
, int size
)
1815 while (pos
< size
) {
1816 int len
= http_read_stream(h
, buf
+ pos
, size
- pos
);
1824 static void update_metadata(URLContext
*h
, char *data
)
1830 HTTPContext
*s
= h
->priv_data
;
1834 val
= strstr(key
, "='");
1837 end
= strstr(val
, "';");
1845 av_dict_set(&s
->metadata
, key
, val
, 0);
1846 av_log(h
, AV_LOG_VERBOSE
, "Metadata update for %s: %s\n", key
, val
);
1852 static int store_icy(URLContext
*h
, int size
)
1854 HTTPContext
*s
= h
->priv_data
;
1855 /* until next metadata packet */
1858 if (s
->icy_metaint
< s
->icy_data_read
)
1859 return AVERROR_INVALIDDATA
;
1860 remaining
= s
->icy_metaint
- s
->icy_data_read
;
1863 /* The metadata packet is variable sized. It has a 1 byte header
1864 * which sets the length of the packet (divided by 16). If it's 0,
1865 * the metadata doesn't change. After the packet, icy_metaint bytes
1866 * of normal data follows. */
1868 int len
= http_read_stream_all(h
, &ch
, 1);
1872 char data
[255 * 16 + 1];
1875 ret
= http_read_stream_all(h
, data
, len
);
1879 if ((ret
= av_opt_set(s
, "icy_metadata_packet", data
, 0)) < 0)
1881 update_metadata(h
, data
);
1883 s
->icy_data_read
= 0;
1884 remaining
= s
->icy_metaint
;
1887 return FFMIN(size
, remaining
);
1890 static int http_read(URLContext
*h
, uint8_t *buf
, int size
)
1892 HTTPContext
*s
= h
->priv_data
;
1894 if (s
->icy_metaint
> 0) {
1895 size
= store_icy(h
, size
);
1900 size
= http_read_stream(h
, buf
, size
);
1902 s
->icy_data_read
+= size
;
1906 /* used only when posting data */
1907 static int http_write(URLContext
*h
, const uint8_t *buf
, int size
)
1909 char temp
[11] = ""; /* 32-bit hex + CRLF + nul */
1911 char crlf
[] = "\r\n";
1912 HTTPContext
*s
= h
->priv_data
;
1914 if (!s
->chunked_post
) {
1915 /* non-chunked data is sent without any special encoding */
1916 return ffurl_write(s
->hd
, buf
, size
);
1919 /* silently ignore zero-size data since chunk encoding that would
1922 /* upload data using chunked encoding */
1923 snprintf(temp
, sizeof(temp
), "%x\r\n", size
);
1925 if ((ret
= ffurl_write(s
->hd
, temp
, strlen(temp
))) < 0 ||
1926 (ret
= ffurl_write(s
->hd
, buf
, size
)) < 0 ||
1927 (ret
= ffurl_write(s
->hd
, crlf
, sizeof(crlf
) - 1)) < 0)
1933 static int http_shutdown(URLContext
*h
, int flags
)
1936 char footer
[] = "0\r\n\r\n";
1937 HTTPContext
*s
= h
->priv_data
;
1939 /* signal end of chunked encoding if used */
1940 if (((flags
& AVIO_FLAG_WRITE
) && s
->chunked_post
) ||
1941 ((flags
& AVIO_FLAG_READ
) && s
->chunked_post
&& s
->listen
)) {
1942 ret
= ffurl_write(s
->hd
, footer
, sizeof(footer
) - 1);
1943 ret
= ret
> 0 ? 0 : ret
;
1944 /* flush the receive buffer when it is write only mode */
1945 if (!(flags
& AVIO_FLAG_READ
)) {
1948 s
->hd
->flags
|= AVIO_FLAG_NONBLOCK
;
1949 read_ret
= ffurl_read(s
->hd
, buf
, sizeof(buf
));
1950 s
->hd
->flags
&= ~AVIO_FLAG_NONBLOCK
;
1951 if (read_ret
< 0 && read_ret
!= AVERROR(EAGAIN
)) {
1952 av_log(h
, AV_LOG_ERROR
, "URL read error: %s\n", av_err2str(read_ret
));
1956 s
->end_chunked_post
= 1;
1962 static int http_close(URLContext
*h
)
1965 HTTPContext
*s
= h
->priv_data
;
1968 inflateEnd(&s
->inflate_stream
);
1969 av_freep(&s
->inflate_buffer
);
1970 #endif /* CONFIG_ZLIB */
1972 if (s
->hd
&& !s
->end_chunked_post
)
1973 /* Close the write direction by sending the end of chunked encoding. */
1974 ret
= http_shutdown(h
, h
->flags
);
1977 ffurl_closep(&s
->hd
);
1978 av_dict_free(&s
->chained_options
);
1979 av_dict_free(&s
->cookie_dict
);
1980 av_dict_free(&s
->redirect_cache
);
1981 av_freep(&s
->new_location
);
1986 static int64_t http_seek_internal(URLContext
*h
, int64_t off
, int whence
, int force_reconnect
)
1988 HTTPContext
*s
= h
->priv_data
;
1989 URLContext
*old_hd
= s
->hd
;
1990 uint64_t old_off
= s
->off
;
1991 uint8_t old_buf
[BUFFER_SIZE
];
1992 int old_buf_size
, ret
;
1993 AVDictionary
*options
= NULL
;
1995 if (whence
== AVSEEK_SIZE
)
1997 else if (!force_reconnect
&&
1998 ((whence
== SEEK_CUR
&& off
== 0) ||
1999 (whence
== SEEK_SET
&& off
== s
->off
)))
2001 else if ((s
->filesize
== UINT64_MAX
&& whence
== SEEK_END
))
2002 return AVERROR(ENOSYS
);
2004 if (whence
== SEEK_CUR
)
2006 else if (whence
== SEEK_END
)
2008 else if (whence
!= SEEK_SET
)
2009 return AVERROR(EINVAL
);
2011 return AVERROR(EINVAL
);
2014 if (s
->off
&& h
->is_streamed
)
2015 return AVERROR(ENOSYS
);
2017 /* do not try to make a new connection if seeking past the end of the file */
2018 if (s
->end_off
|| s
->filesize
!= UINT64_MAX
) {
2019 uint64_t end_pos
= s
->end_off
? s
->end_off
: s
->filesize
;
2020 if (s
->off
>= end_pos
)
2024 /* if the location changed (redirect), revert to the original uri */
2025 if (strcmp(s
->uri
, s
->location
)) {
2027 new_uri
= av_strdup(s
->uri
);
2029 return AVERROR(ENOMEM
);
2030 av_free(s
->location
);
2031 s
->location
= new_uri
;
2034 /* we save the old context in case the seek fails */
2035 old_buf_size
= s
->buf_end
- s
->buf_ptr
;
2036 memcpy(old_buf
, s
->buf_ptr
, old_buf_size
);
2039 /* if it fails, continue on old connection */
2040 if ((ret
= http_open_cnx(h
, &options
)) < 0) {
2041 av_dict_free(&options
);
2042 memcpy(s
->buffer
, old_buf
, old_buf_size
);
2043 s
->buf_ptr
= s
->buffer
;
2044 s
->buf_end
= s
->buffer
+ old_buf_size
;
2049 av_dict_free(&options
);
2050 ffurl_close(old_hd
);
2054 static int64_t http_seek(URLContext
*h
, int64_t off
, int whence
)
2056 return http_seek_internal(h
, off
, whence
, 0);
2059 static int http_get_file_handle(URLContext
*h
)
2061 HTTPContext
*s
= h
->priv_data
;
2062 return ffurl_get_file_handle(s
->hd
);
2065 static int http_get_short_seek(URLContext
*h
)
2067 HTTPContext
*s
= h
->priv_data
;
2068 if (s
->short_seek_size
>= 1)
2069 return s
->short_seek_size
;
2070 return ffurl_get_short_seek(s
->hd
);
2073 #define HTTP_CLASS(flavor) \
2074 static const AVClass flavor ## _context_class = { \
2075 .class_name = # flavor, \
2076 .item_name = av_default_item_name, \
2077 .option = options, \
2078 .version = LIBAVUTIL_VERSION_INT, \
2081 #if CONFIG_HTTP_PROTOCOL
2084 const URLProtocol ff_http_protocol
= {
2086 .url_open2
= http_open
,
2087 .url_accept
= http_accept
,
2088 .url_handshake
= http_handshake
,
2089 .url_read
= http_read
,
2090 .url_write
= http_write
,
2091 .url_seek
= http_seek
,
2092 .url_close
= http_close
,
2093 .url_get_file_handle
= http_get_file_handle
,
2094 .url_get_short_seek
= http_get_short_seek
,
2095 .url_shutdown
= http_shutdown
,
2096 .priv_data_size
= sizeof(HTTPContext
),
2097 .priv_data_class
= &http_context_class
,
2098 .flags
= URL_PROTOCOL_FLAG_NETWORK
,
2099 .default_whitelist
= "http,https,tls,rtp,tcp,udp,crypto,httpproxy,data"
2101 #endif /* CONFIG_HTTP_PROTOCOL */
2103 #if CONFIG_HTTPS_PROTOCOL
2106 const URLProtocol ff_https_protocol
= {
2108 .url_open2
= http_open
,
2109 .url_read
= http_read
,
2110 .url_write
= http_write
,
2111 .url_seek
= http_seek
,
2112 .url_close
= http_close
,
2113 .url_get_file_handle
= http_get_file_handle
,
2114 .url_get_short_seek
= http_get_short_seek
,
2115 .url_shutdown
= http_shutdown
,
2116 .priv_data_size
= sizeof(HTTPContext
),
2117 .priv_data_class
= &https_context_class
,
2118 .flags
= URL_PROTOCOL_FLAG_NETWORK
,
2119 .default_whitelist
= "http,https,tls,rtp,tcp,udp,crypto,httpproxy"
2121 #endif /* CONFIG_HTTPS_PROTOCOL */
2123 #if CONFIG_HTTPPROXY_PROTOCOL
2124 static int http_proxy_close(URLContext
*h
)
2126 HTTPContext
*s
= h
->priv_data
;
2128 ffurl_closep(&s
->hd
);
2132 static int http_proxy_open(URLContext
*h
, const char *uri
, int flags
)
2134 HTTPContext
*s
= h
->priv_data
;
2135 char hostname
[1024], hoststr
[1024];
2136 char auth
[1024], pathbuf
[1024], *path
;
2137 char lower_url
[100];
2138 int port
, ret
= 0, auth_attempts
= 0;
2139 HTTPAuthType cur_auth_type
;
2142 if( s
->seekable
== 1 )
2147 av_url_split(NULL
, 0, auth
, sizeof(auth
), hostname
, sizeof(hostname
), &port
,
2148 pathbuf
, sizeof(pathbuf
), uri
);
2149 ff_url_join(hoststr
, sizeof(hoststr
), NULL
, NULL
, hostname
, port
, NULL
);
2154 ff_url_join(lower_url
, sizeof(lower_url
), "tcp", NULL
, hostname
, port
,
2157 ret
= ffurl_open_whitelist(&s
->hd
, lower_url
, AVIO_FLAG_READ_WRITE
,
2158 &h
->interrupt_callback
, NULL
,
2159 h
->protocol_whitelist
, h
->protocol_blacklist
, h
);
2163 authstr
= ff_http_auth_create_response(&s
->proxy_auth_state
, auth
,
2165 snprintf(s
->buffer
, sizeof(s
->buffer
),
2166 "CONNECT %s HTTP/1.1\r\n"
2168 "Connection: close\r\n"
2173 authstr
? "Proxy-" : "", authstr
? authstr
: "");
2176 if ((ret
= ffurl_write(s
->hd
, s
->buffer
, strlen(s
->buffer
))) < 0)
2179 s
->buf_ptr
= s
->buffer
;
2180 s
->buf_end
= s
->buffer
;
2182 s
->filesize
= UINT64_MAX
;
2183 cur_auth_type
= s
->proxy_auth_state
.auth_type
;
2185 /* Note: This uses buffering, potentially reading more than the
2186 * HTTP header. If tunneling a protocol where the server starts
2187 * the conversation, we might buffer part of that here, too.
2188 * Reading that requires using the proper ffurl_read() function
2189 * on this URLContext, not using the fd directly (as the tls
2190 * protocol does). This shouldn't be an issue for tls though,
2191 * since the client starts the conversation there, so there
2192 * is no extra data that we might buffer up here.
2194 ret
= http_read_header(h
);
2199 if (s
->http_code
== 407 &&
2200 (cur_auth_type
== HTTP_AUTH_NONE
|| s
->proxy_auth_state
.stale
) &&
2201 s
->proxy_auth_state
.auth_type
!= HTTP_AUTH_NONE
&& auth_attempts
< 2) {
2202 ffurl_closep(&s
->hd
);
2206 if (s
->http_code
< 400)
2208 ret
= ff_http_averror(s
->http_code
, AVERROR(EIO
));
2211 http_proxy_close(h
);
2215 static int http_proxy_write(URLContext
*h
, const uint8_t *buf
, int size
)
2217 HTTPContext
*s
= h
->priv_data
;
2218 return ffurl_write(s
->hd
, buf
, size
);
2221 const URLProtocol ff_httpproxy_protocol
= {
2222 .name
= "httpproxy",
2223 .url_open
= http_proxy_open
,
2224 .url_read
= http_buf_read
,
2225 .url_write
= http_proxy_write
,
2226 .url_close
= http_proxy_close
,
2227 .url_get_file_handle
= http_get_file_handle
,
2228 .priv_data_size
= sizeof(HTTPContext
),
2229 .flags
= URL_PROTOCOL_FLAG_NETWORK
,
2231 #endif /* CONFIG_HTTPPROXY_PROTOCOL */