http.c 40.5 KB
Newer Older
1 2
/*
 * HTTP protocol for ffmpeg client
3
 * Copyright (c) 2000, 2001 Fabrice Bellard
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
21 22

#include "libavutil/avstring.h"
23
#include "avformat.h"
24
#include "internal.h"
25
#include "network.h"
26
#include "http.h"
27
#include "os_support.h"
28
#include "httpauth.h"
29
#include "url.h"
30
#include "libavutil/opt.h"
31

32 33 34 35
#if CONFIG_ZLIB
#include <zlib.h>
#endif

Diego Biurrun's avatar
Diego Biurrun committed
36 37
/* XXX: POST protocol is not completely implemented because ffmpeg uses
   only a subset of it. */
38

39 40 41 42 43
/* 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
44 45 46
#define MAX_REDIRECTS 8

typedef struct {
47
    const AVClass *class;
48 49 50 51
    URLContext *hd;
    unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;
    int line_count;
    int http_code;
Luca Barbato's avatar
Luca Barbato committed
52 53
    /* Used if "Transfer-Encoding: chunked" otherwise -1. */
    int64_t chunksize;
54
    int64_t off, end_off, filesize;
55
    char *location;
56
    HTTPAuthState auth_state;
57
    HTTPAuthState proxy_auth_state;
58
    char *headers;
59
    char *mime_type;
60
    char *user_agent;
61
    char *content_type;
Luca Barbato's avatar
Luca Barbato committed
62 63 64
    /* Set if the server correctly handles Connection: close and will close
     * the connection after feeding us the content. */
    int willclose;
65
    int seekable;           /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */
66
    int chunked_post;
Luca Barbato's avatar
Luca Barbato committed
67 68 69 70 71 72
    /* A flag which indicates if the end of chunked encoding has been sent. */
    int end_chunked_post;
    /* A flag which indicates we have finished to read POST reply. */
    int end_header;
    /* A flag which indicates if we use persistent connections. */
    int multiple_requests;
73 74
    uint8_t *post_data;
    int post_datalen;
75
    int is_akamai;
76
    int is_mediagateway;
77
    char *cookies;          ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)
78
    int icy;
79 80 81 82
    /* how much data was read since the last ICY metadata packet */
    int icy_data_read;
    /* after how many bytes of read data a new metadata packet will be found */
    int icy_metaint;
83 84
    char *icy_metadata_headers;
    char *icy_metadata_packet;
85 86 87 88 89
#if CONFIG_ZLIB
    int compressed;
    z_stream inflate_stream;
    uint8_t *inflate_buffer;
#endif
90
    AVDictionary *chained_options;
91
    int send_expect_100;
92 93
} HTTPContext;

94
#define OFFSET(x) offsetof(HTTPContext, x)
95 96
#define D AV_OPT_FLAG_DECODING_PARAM
#define E AV_OPT_FLAG_ENCODING_PARAM
97
#define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION)
98
static const AVOption options[] = {
99
{"seekable", "control seekability of connection", OFFSET(seekable), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, D },
100
{"chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E },
101
{"headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
102
{"content_type", "set a specific content type for the POST messages", OFFSET(content_type), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
103
{"user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = DEFAULT_USER_AGENT}, 0, 0, D },
104
{"user-agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = DEFAULT_USER_AGENT}, 0, 0, D },
105
{"multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D|E },
106
{"post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D|E },
107
{"mime_type", "export the MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, {0}, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY  },
108
{"cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, {0}, 0, 0, D },
109
{"icy", "request ICY metadata", OFFSET(icy), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D },
110 111
{"icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers), AV_OPT_TYPE_STRING, {0}, 0, 0, AV_OPT_FLAG_EXPORT },
{"icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, {0}, 0, 0, AV_OPT_FLAG_EXPORT },
112 113 114
{"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" },
115
{"send_expect_100", "Force sending an Expect: 100-continue header for POST", OFFSET(send_expect_100), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
116
{"location", "The actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
117
{"offset", "initial byte offset", OFFSET(off), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D },
118
{"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 },
119 120
{NULL}
};
121 122 123 124 125 126
#define HTTP_CLASS(flavor)\
static const AVClass flavor ## _context_class = {\
    .class_name     = #flavor,\
    .item_name      = av_default_item_name,\
    .option         = options,\
    .version        = LIBAVUTIL_VERSION_INT,\
127
}
128

129 130 131
HTTP_CLASS(http);
HTTP_CLASS(https);

132 133 134
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);
135

136 137 138 139
void ff_http_init_auth_state(URLContext *dest, const URLContext *src)
{
    memcpy(&((HTTPContext*)dest->priv_data)->auth_state,
           &((HTTPContext*)src->priv_data)->auth_state, sizeof(HTTPAuthState));
140 141 142
    memcpy(&((HTTPContext*)dest->priv_data)->proxy_auth_state,
           &((HTTPContext*)src->priv_data)->proxy_auth_state,
           sizeof(HTTPAuthState));
143 144
}

145
/* return non zero if error */
146
static int http_open_cnx(URLContext *h, AVDictionary **options)
147
{
148
    const char *path, *proxy_path, *lower_proto = "tcp", *local_path;
149
    char hostname[1024], hoststr[1024], proto[10];
150
    char auth[1024], proxyauth[1024] = "";
151 152
    char path1[MAX_URL_SIZE];
    char buf[1024], urlbuf[MAX_URL_SIZE];
153
    int port, use_proxy, err, location_changed = 0, redirects = 0, attempts = 0;
154
    HTTPAuthType cur_auth_type, cur_proxy_auth_type;
155 156 157 158 159
    HTTPContext *s = h->priv_data;

    /* fill the dest addr */
 redo:
    /* needed in any case to build the host string */
160 161
    av_url_split(proto, sizeof(proto), auth, sizeof(auth),
                 hostname, sizeof(hostname), &port,
Martin Storsjö's avatar
Martin Storsjö committed
162
                 path1, sizeof(path1), s->location);
163
    ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
164

165 166 167 168
    proxy_path = getenv("http_proxy");
    use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), hostname) &&
                proxy_path != NULL && av_strstart(proxy_path, "http://", NULL);

169 170
    if (!strcmp(proto, "https")) {
        lower_proto = "tls";
171
        use_proxy = 0;
172 173 174 175 176 177
        if (port < 0)
            port = 443;
    }
    if (port < 0)
        port = 80;

178 179 180 181 182
    if (path1[0] == '\0')
        path = "/";
    else
        path = path1;
    local_path = path;
183
    if (use_proxy) {
184 185 186 187 188 189 190
        /* Reassemble the request URL without auth string - we don't
         * want to leak the auth to the proxy. */
        ff_url_join(urlbuf, sizeof(urlbuf), proto, NULL, hostname, port, "%s",
                    path1);
        path = urlbuf;
        av_url_split(NULL, 0, proxyauth, sizeof(proxyauth),
                     hostname, sizeof(hostname), &port, NULL, 0, proxy_path);
191 192
    }

193
    ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL);
194

195
    if (!s->hd) {
196
        err = ffurl_open(&s->hd, buf, AVIO_FLAG_READ_WRITE,
197
                         &h->interrupt_callback, options);
198 199 200 201
        if (err < 0)
            goto fail;
    }

202
    cur_auth_type = s->auth_state.auth_type;
203 204
    cur_proxy_auth_type = s->auth_state.auth_type;
    if (http_connect(h, path, local_path, hoststr, auth, proxyauth, &location_changed) < 0)
205
        goto fail;
206
    attempts++;
207
    if (s->http_code == 401) {
208 209
        if ((cur_auth_type == HTTP_AUTH_NONE || s->auth_state.stale) &&
            s->auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) {
210
            ffurl_closep(&s->hd);
211 212 213 214
            goto redo;
        } else
            goto fail;
    }
215
    if (s->http_code == 407) {
216 217
        if ((cur_proxy_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) &&
            s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) {
218
            ffurl_closep(&s->hd);
219 220 221 222
            goto redo;
        } else
            goto fail;
    }
223 224
    if ((s->http_code == 301 || s->http_code == 302 || s->http_code == 303 || s->http_code == 307)
        && location_changed == 1) {
225
        /* url moved, get next */
226
        ffurl_closep(&s->hd);
227
        if (redirects++ >= MAX_REDIRECTS)
228
            return AVERROR(EIO);
229 230 231
        /* Restart the authentication process with the new target, which
         * might use a different auth mechanism. */
        memset(&s->auth_state, 0, sizeof(s->auth_state));
232
        attempts = 0;
233 234 235 236 237
        location_changed = 0;
        goto redo;
    }
    return 0;
 fail:
238
    if (s->hd)
239
        ffurl_closep(&s->hd);
240
    return AVERROR(EIO);
241 242
}

243 244 245
int ff_http_do_new_request(URLContext *h, const char *uri)
{
    HTTPContext *s = h->priv_data;
246 247
    AVDictionary *options = NULL;
    int ret;
248 249

    s->off = 0;
250
    s->icy_data_read = 0;
251 252 253 254
    av_free(s->location);
    s->location = av_strdup(uri);
    if (!s->location)
        return AVERROR(ENOMEM);
255

256 257 258 259
    av_dict_copy(&options, s->chained_options, 0);
    ret = http_open_cnx(h, &options);
    av_dict_free(&options);
    return ret;
260 261
}

262 263
static int http_open(URLContext *h, const char *uri, int flags,
                     AVDictionary **options)
264
{
265
    HTTPContext *s = h->priv_data;
266
    int ret;
267

268 269 270 271
    if( s->seekable == 1 )
        h->is_streamed = 0;
    else
        h->is_streamed = 1;
272 273

    s->filesize = -1;
274 275 276
    s->location = av_strdup(uri);
    if (!s->location)
        return AVERROR(ENOMEM);
277 278
    if (options)
        av_dict_copy(&s->chained_options, *options, 0);
279

280 281 282
    if (s->headers) {
        int len = strlen(s->headers);
        if (len < 2 || strcmp("\r\n", s->headers + len - 2))
283
            av_log(h, AV_LOG_WARNING, "No trailing CRLF found in HTTP header.\n");
284 285
    }

286 287 288 289
    ret = http_open_cnx(h, options);
    if (ret < 0)
        av_dict_free(&s->chained_options);
    return ret;
290 291 292 293 294
}
static int http_getc(HTTPContext *s)
{
    int len;
    if (s->buf_ptr >= s->buf_end) {
295
        len = ffurl_read(s->hd, s->buffer, BUFFER_SIZE);
296
        if (len < 0) {
297
            return len;
298
        } else if (len == 0) {
299
            return AVERROR_EOF;
300 301 302 303 304 305 306 307
        } else {
            s->buf_ptr = s->buffer;
            s->buf_end = s->buffer + len;
        }
    }
    return *s->buf_ptr++;
}

308 309 310 311 312 313 314 315 316
static int http_get_line(HTTPContext *s, char *line, int line_size)
{
    int ch;
    char *q;

    q = line;
    for(;;) {
        ch = http_getc(s);
        if (ch < 0)
317
            return ch;
318 319 320 321 322 323 324 325 326 327 328 329 330 331
        if (ch == '\n') {
            /* process line */
            if (q > line && q[-1] == '\r')
                q--;
            *q = '\0';

            return 0;
        } else {
            if ((q - line) < line_size - 1)
                *q++ = ch;
        }
    }
}

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
static int check_http_code(URLContext *h, int http_code, const char *end)
{
    HTTPContext *s = h->priv_data;
    /* error codes are 4xx and 5xx, but regard 401 as a success, so we
     * don't abort until all headers have been parsed. */
    if (http_code >= 400 && http_code < 600 &&
        (http_code != 401 || s->auth_state.auth_type != HTTP_AUTH_NONE) &&
        (http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE)) {
        end += strspn(end, SPACE_CHARS);
        av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", http_code, end);
        return AVERROR(EIO);
    }
    return 0;
}

static int parse_location(HTTPContext *s, const char *p)
{
    char redirected_location[MAX_URL_SIZE], *new_loc;
    ff_make_absolute_url(redirected_location, sizeof(redirected_location),
                         s->location, p);
    new_loc = av_strdup(redirected_location);
    if (!new_loc)
        return AVERROR(ENOMEM);
    av_free(s->location);
    s->location = new_loc;
    return 0;
}

/* "bytes $from-$to/$document_size" */
361
static void parse_content_range(URLContext *h, const char *p)
362 363 364 365 366 367 368 369 370 371
{
    HTTPContext *s = h->priv_data;
    const char *slash;

    if (!strncmp(p, "bytes ", 6)) {
        p += 6;
        s->off = strtoll(p, NULL, 10);
        if ((slash = strchr(p, '/')) && strlen(slash) > 0)
            s->filesize = strtoll(slash+1, NULL, 10);
    }
372 373
    if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647))
        h->is_streamed = 0; /* we _can_ in fact seek */
374 375
}

376
static int parse_content_encoding(URLContext *h, const char *p)
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
{
    HTTPContext *s = h->priv_data;

    if (!av_strncasecmp(p, "gzip", 4) ||
        !av_strncasecmp(p, "deflate", 7)) {
#if CONFIG_ZLIB
        s->compressed = 1;
        inflateEnd(&s->inflate_stream);
        if (inflateInit2(&s->inflate_stream, 32 + 15) != Z_OK) {
            av_log(h, AV_LOG_WARNING, "Error during zlib initialisation: %s\n",
                   s->inflate_stream.msg);
            return AVERROR(ENOSYS);
        }
        if (zlibCompileFlags() & (1 << 17)) {
            av_log(h, AV_LOG_WARNING,
                   "Your zlib was compiled without gzip support.\n");
            return AVERROR(ENOSYS);
        }
#else
        av_log(h, AV_LOG_WARNING,
               "Compressed (%s) content, need zlib with gzip support\n", p);
        return AVERROR(ENOSYS);
#endif
    } else if (!av_strncasecmp(p, "identity", 8)) {
        // The normal, no-encoding case (although servers shouldn't include
        // the header at all if this is the case).
    } else {
        av_log(h, AV_LOG_WARNING, "Unknown content coding: %s\n", p);
    }
    return 0;
}

409 410 411 412
// Concat all Icy- header lines
static int parse_icy(HTTPContext *s, const char *tag, const char *p)
{
    int len = 4 + strlen(p) + strlen(tag);
413
    int is_first = !s->icy_metadata_headers;
414 415 416 417 418 419 420 421
    int ret;

    if (s->icy_metadata_headers)
        len += strlen(s->icy_metadata_headers);

    if ((ret = av_reallocp(&s->icy_metadata_headers, len)) < 0)
        return ret;

422 423 424
    if (is_first)
        *s->icy_metadata_headers = '\0';

425 426 427 428 429
    av_strlcatf(s->icy_metadata_headers, len, "%s: %s\n", tag, p);

    return 0;
}

430 431 432 433
static int process_line(URLContext *h, char *line, int line_count,
                        int *new_location)
{
    HTTPContext *s = h->priv_data;
434
    char *tag, *p, *end;
435
    int ret;
436 437

    /* end of header */
438 439
    if (line[0] == '\0') {
        s->end_header = 1;
440
        return 0;
441
    }
442 443 444

    p = line;
    if (line_count == 0) {
445
        while (!av_isspace(*p) && *p != '\0')
446
            p++;
447
        while (av_isspace(*p))
448
            p++;
449
        s->http_code = strtol(p, &end, 10);
450

451
        av_log(h, AV_LOG_DEBUG, "http_code=%d\n", s->http_code);
452

453 454
        if ((ret = check_http_code(h, s->http_code, end)) < 0)
            return ret;
455 456 457 458 459 460 461 462 463
    } else {
        while (*p != '\0' && *p != ':')
            p++;
        if (*p != ':')
            return 1;

        *p = '\0';
        tag = line;
        p++;
464
        while (av_isspace(*p))
465
            p++;
466
        if (!av_strcasecmp(tag, "Location")) {
467 468
            if ((ret = parse_location(s, p)) < 0)
                return ret;
469
            *new_location = 1;
470
        } else if (!av_strcasecmp(tag, "Content-Length") && s->filesize == -1) {
471
            s->filesize = strtoll(p, NULL, 10);
472
        } else if (!av_strcasecmp(tag, "Content-Range")) {
473
            parse_content_range(h, p);
474
        } else if (!av_strcasecmp(tag, "Accept-Ranges") &&
475 476
                   !strncmp(p, "bytes", 5) &&
                   s->seekable == -1) {
477
            h->is_streamed = 0;
478 479
        } else if (!av_strcasecmp(tag, "Transfer-Encoding") &&
                   !av_strncasecmp(p, "chunked", 7)) {
480 481
            s->filesize = -1;
            s->chunksize = 0;
482
        } else if (!av_strcasecmp(tag, "WWW-Authenticate")) {
483
            ff_http_auth_handle_header(&s->auth_state, tag, p);
484
        } else if (!av_strcasecmp(tag, "Authentication-Info")) {
485
            ff_http_auth_handle_header(&s->auth_state, tag, p);
486
        } else if (!av_strcasecmp(tag, "Proxy-Authenticate")) {
487
            ff_http_auth_handle_header(&s->proxy_auth_state, tag, p);
488
        } else if (!av_strcasecmp(tag, "Connection")) {
489 490
            if (!strcmp(p, "close"))
                s->willclose = 1;
491 492 493 494 495 496
        } else if (!av_strcasecmp (tag, "Server")) {
            if (!av_strcasecmp (p, "AkamaiGHost")) {
                s->is_akamai = 1;
            } else if (!av_strncasecmp (p, "MediaGateway", 12)) {
                s->is_mediagateway = 1;
            }
497
        } else if (!av_strcasecmp (tag, "Content-Type")) {
498 499
            av_free(s->mime_type);
            s->mime_type = av_strdup(p);
500 501 502 503 504 505 506 507 508 509 510 511 512 513
        } else if (!av_strcasecmp (tag, "Set-Cookie")) {
            if (!s->cookies) {
                if (!(s->cookies = av_strdup(p)))
                    return AVERROR(ENOMEM);
            } else {
                char *tmp = s->cookies;
                size_t str_size = strlen(tmp) + strlen(p) + 2;
                if (!(s->cookies = av_malloc(str_size))) {
                    s->cookies = tmp;
                    return AVERROR(ENOMEM);
                }
                snprintf(s->cookies, str_size, "%s\n%s", tmp, p);
                av_free(tmp);
            }
514 515 516
        } else if (!av_strcasecmp (tag, "Icy-MetaInt")) {
            s->icy_metaint = strtoll(p, NULL, 10);
        } else if (!av_strncasecmp(tag, "Icy-", 4)) {
517 518
            if ((ret = parse_icy(s, tag, p)) < 0)
                return ret;
519
        } else if (!av_strcasecmp(tag, "Content-Encoding")) {
520 521
            if ((ret = parse_content_encoding(h, p)) < 0)
                return ret;
522 523 524 525 526
        }
    }
    return 1;
}

527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
/**
 * Create a string containing cookie values for use as a HTTP cookie header
 * field value for a particular path and domain from the cookie values stored in
 * the HTTP protocol context. The cookie string is stored in *cookies.
 *
 * @return a negative value if an error condition occurred, 0 otherwise
 */
static int get_cookies(HTTPContext *s, char **cookies, const char *path,
                       const char *domain)
{
    // cookie strings will look like Set-Cookie header field values.  Multiple
    // Set-Cookie fields will result in multiple values delimited by a newline
    int ret = 0;
    char *next, *cookie, *set_cookies = av_strdup(s->cookies), *cset_cookies = set_cookies;

    if (!set_cookies) return AVERROR(EINVAL);

    *cookies = NULL;
    while ((cookie = av_strtok(set_cookies, "\n", &next))) {
        int domain_offset = 0;
        char *param, *next_param, *cdomain = NULL, *cpath = NULL, *cvalue = NULL;
        set_cookies = NULL;

        while ((param = av_strtok(cookie, "; ", &next_param))) {
            cookie = NULL;
            if        (!av_strncasecmp("path=",   param, 5)) {
553
                av_free(cpath);
554 555
                cpath = av_strdup(&param[5]);
            } else if (!av_strncasecmp("domain=", param, 7)) {
556 557 558
                // if the cookie specifies a sub-domain, skip the leading dot thereby
                // supporting URLs that point to sub-domains and the master domain
                int leading_dot = (param[7] == '.');
559
                av_free(cdomain);
560
                cdomain = av_strdup(&param[7+leading_dot]);
561 562 563 564 565 566
            } else if (!av_strncasecmp("secure",  param, 6) ||
                       !av_strncasecmp("comment", param, 7) ||
                       !av_strncasecmp("max-age", param, 7) ||
                       !av_strncasecmp("version", param, 7)) {
                // ignore Comment, Max-Age, Secure and Version
            } else {
567
                av_free(cvalue);
568 569 570
                cvalue = av_strdup(param);
            }
        }
571 572
        if (!cdomain)
            cdomain = av_strdup(domain);
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626

        // ensure all of the necessary values are valid
        if (!cdomain || !cpath || !cvalue) {
            av_log(s, AV_LOG_WARNING,
                   "Invalid cookie found, no value, path or domain specified\n");
            goto done_cookie;
        }

        // check if the request path matches the cookie path
        if (av_strncasecmp(path, cpath, strlen(cpath)))
            goto done_cookie;

        // the domain should be at least the size of our cookie domain
        domain_offset = strlen(domain) - strlen(cdomain);
        if (domain_offset < 0)
            goto done_cookie;

        // match the cookie domain
        if (av_strcasecmp(&domain[domain_offset], cdomain))
            goto done_cookie;

        // cookie parameters match, so copy the value
        if (!*cookies) {
            if (!(*cookies = av_strdup(cvalue))) {
                ret = AVERROR(ENOMEM);
                goto done_cookie;
            }
        } else {
            char *tmp = *cookies;
            size_t str_size = strlen(cvalue) + strlen(*cookies) + 3;
            if (!(*cookies = av_malloc(str_size))) {
                ret = AVERROR(ENOMEM);
                goto done_cookie;
            }
            snprintf(*cookies, str_size, "%s; %s", tmp, cvalue);
            av_free(tmp);
        }

        done_cookie:
        av_free(cdomain);
        av_free(cpath);
        av_free(cvalue);
        if (ret < 0) {
            if (*cookies) av_freep(cookies);
            av_free(cset_cookies);
            return ret;
        }
    }

    av_free(cset_cookies);

    return 0;
}

627 628 629
static inline int has_header(const char *str, const char *header)
{
    /* header + 2 to skip over CRLF prefix. (make sure you have one!) */
630 631
    if (!str)
        return 0;
632 633 634
    return av_stristart(str, header + 2, NULL) || av_stristr(str, header);
}

635 636 637
static int http_read_header(URLContext *h, int *new_location)
{
    HTTPContext *s = h->priv_data;
638
    char line[MAX_URL_SIZE];
639 640
    int err = 0;

641 642
    s->chunksize = -1;

643
    for (;;) {
644 645
        if ((err = http_get_line(s, line, sizeof(line))) < 0)
            return err;
646

647
        av_log(h, AV_LOG_DEBUG, "header='%s'\n", line);
648 649 650 651 652 653 654 655 656

        err = process_line(h, line, s->line_count, new_location);
        if (err < 0)
            return err;
        if (err == 0)
            break;
        s->line_count++;
    }

657 658 659
    if (s->seekable == -1 && s->is_mediagateway && s->filesize == 2000000000)
        h->is_streamed = 1; /* we can in fact _not_ seek */

660 661 662
    return err;
}

663 664 665
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)
666 667
{
    HTTPContext *s = h->priv_data;
668
    int post, err;
669
    char headers[4096] = "";
670
    char *authstr = NULL, *proxyauthstr = NULL;
671
    int64_t off = s->off;
672
    int len = 0;
673
    const char *method;
674
    int send_expect_100 = 0;
675 676 677


    /* send http header */
678
    post = h->flags & AVIO_FLAG_WRITE;
679 680 681 682 683 684 685 686

    if (s->post_data) {
        /* force POST method and disable chunked encoding when
         * custom HTTP post data is set */
        post = 1;
        s->chunked_post = 0;
    }

687 688 689 690 691
    method = post ? "POST" : "GET";
    authstr = ff_http_auth_create_response(&s->auth_state, auth, local_path,
                                           method);
    proxyauthstr = ff_http_auth_create_response(&s->proxy_auth_state, proxyauth,
                                                local_path, method);
692 693 694 695 696 697
    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. */
698 699
        if (auth && *auth &&
            s->auth_state.auth_type == HTTP_AUTH_NONE &&
700 701 702
            s->http_code != 401)
            send_expect_100 = 1;
    }
703 704 705

    /* set default headers if needed */
    if (!has_header(s->headers, "\r\nUser-Agent: "))
706
        len += av_strlcatf(headers + len, sizeof(headers) - len,
707
                           "User-Agent: %s\r\n", s->user_agent);
708 709 710
    if (!has_header(s->headers, "\r\nAccept: "))
        len += av_strlcpy(headers + len, "Accept: */*\r\n",
                          sizeof(headers) - len);
711
    // Note: we send this on purpose even when s->off is 0 when we're probing,
712 713
    // since it allows us to detect more reliably if a (non-conforming)
    // server supports seeking by analysing the reply headers.
714
    if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable == -1)) {
715
        len += av_strlcatf(headers + len, sizeof(headers) - len,
716
                           "Range: bytes=%"PRId64"-", s->off);
717
        if (s->end_off)
718
            len += av_strlcatf(headers + len, sizeof(headers) - len,
719
                               "%"PRId64, s->end_off - 1);
720 721 722
        len += av_strlcpy(headers + len, "\r\n",
                          sizeof(headers) - len);
    }
723 724 725
    if (send_expect_100 && !has_header(s->headers, "\r\nExpect: "))
        len += av_strlcatf(headers + len, sizeof(headers) - len,
                           "Expect: 100-continue\r\n");
726 727 728 729 730 731 732 733 734 735 736

    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);
        }
    }

737 738 739
    if (!has_header(s->headers, "\r\nHost: "))
        len += av_strlcatf(headers + len, sizeof(headers) - len,
                           "Host: %s\r\n", hoststr);
740 741 742
    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);
743

744 745 746
    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);
747 748
    if (!has_header(s->headers, "\r\nCookie: ") && s->cookies) {
        char *cookies = NULL;
749
        if (!get_cookies(s, &cookies, path, hoststr) && cookies) {
750 751 752 753 754
            len += av_strlcatf(headers + len, sizeof(headers) - len,
                               "Cookie: %s\r\n", cookies);
            av_free(cookies);
        }
    }
755 756 757 758
    if (!has_header(s->headers, "\r\nIcy-MetaData: ") && s->icy) {
        len += av_strlcatf(headers + len, sizeof(headers) - len,
                           "Icy-MetaData: %d\r\n", 1);
    }
759 760

    /* now add in custom headers */
761 762
    if (s->headers)
        av_strlcpy(headers + len, s->headers, sizeof(headers) - len);
763

764 765
    snprintf(s->buffer, sizeof(s->buffer),
             "%s %s HTTP/1.1\r\n"
766
             "%s"
767
             "%s"
768
             "%s"
769
             "%s%s"
770
             "\r\n",
771
             method,
772
             path,
773
             post && s->chunked_post ? "Transfer-Encoding: chunked\r\n" : "",
774
             headers,
775 776
             authstr ? authstr : "",
             proxyauthstr ? "Proxy-" : "", proxyauthstr ? proxyauthstr : "");
777

778 779
    av_log(h, AV_LOG_DEBUG, "request: %s\n", s->buffer);

780
    if ((err = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0)
781
        goto done;
782

783 784
    if (s->post_data)
        if ((err = ffurl_write(s->hd, s->post_data, s->post_datalen)) < 0)
785
            goto done;
786 787 788 789 790 791

    /* init input buffer */
    s->buf_ptr = s->buffer;
    s->buf_end = s->buffer;
    s->line_count = 0;
    s->off = 0;
792
    s->icy_data_read = 0;
793
    s->filesize = -1;
794
    s->willclose = 0;
795
    s->end_chunked_post = 0;
796
    s->end_header = 0;
797
    if (post && !s->post_data && !send_expect_100) {
798 799 800 801
        /* Pretend that it did work. We didn't read any header yet, since
         * we've still to send the POST data, but the code calling this
         * function will check http_code after we return. */
        s->http_code = 200;
802 803
        err = 0;
        goto done;
804 805 806
    }

    /* wait for header */
807 808
    err = http_read_header(h, new_location);
    if (err < 0)
809
        goto done;
810

811 812 813 814 815
    err = (off == s->off) ? 0 : -1;
done:
    av_freep(&authstr);
    av_freep(&proxyauthstr);
    return err;
816 817 818
}


819
static int http_buf_read(URLContext *h, uint8_t *buf, int size)
820 821 822
{
    HTTPContext *s = h->priv_data;
    int len;
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
    /* read bytes from input buffer first */
    len = s->buf_end - s->buf_ptr;
    if (len > 0) {
        if (len > size)
            len = size;
        memcpy(buf, s->buf_ptr, len);
        s->buf_ptr += len;
    } else {
        if (!s->willclose && s->filesize >= 0 && s->off >= s->filesize)
            return AVERROR_EOF;
        len = ffurl_read(s->hd, buf, size);
    }
    if (len > 0) {
        s->off += len;
        if (s->chunksize > 0)
            s->chunksize -= len;
    }
    return len;
}

843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868
#if CONFIG_ZLIB
#define DECOMPRESS_BUF_SIZE (256 * 1024)
static int http_buf_read_compressed(URLContext *h, uint8_t *buf, int size)
{
    HTTPContext *s = h->priv_data;
    int ret;

    if (!s->inflate_buffer) {
        s->inflate_buffer = av_malloc(DECOMPRESS_BUF_SIZE);
        if (!s->inflate_buffer)
            return AVERROR(ENOMEM);
    }

    if (s->inflate_stream.avail_in == 0) {
        int read = http_buf_read(h, s->inflate_buffer, DECOMPRESS_BUF_SIZE);
        if (read <= 0)
            return read;
        s->inflate_stream.next_in  = s->inflate_buffer;
        s->inflate_stream.avail_in = read;
    }

    s->inflate_stream.avail_out = size;
    s->inflate_stream.next_out  = buf;

    ret = inflate(&s->inflate_stream, Z_SYNC_FLUSH);
    if (ret != Z_OK && ret != Z_STREAM_END)
869
        av_log(h, AV_LOG_WARNING, "inflate return value: %d, %s\n", ret, s->inflate_stream.msg);
870 871 872 873 874

    return size - s->inflate_stream.avail_out;
}
#endif

875
static int http_read_stream(URLContext *h, uint8_t *buf, int size)
876 877
{
    HTTPContext *s = h->priv_data;
878 879
    int err, new_location;

880 881
    if (!s->hd)
        return AVERROR_EOF;
882

883 884 885 886
    if (s->end_chunked_post && !s->end_header) {
        err = http_read_header(h, &new_location);
        if (err < 0)
            return err;
887
    }
888

889 890 891 892 893 894
    if (s->chunksize >= 0) {
        if (!s->chunksize) {
            char line[32];

            for(;;) {
                do {
895 896
                    if ((err = http_get_line(s, line, sizeof(line))) < 0)
                        return err;
897 898 899 900
                } while (!*line);    /* skip CR LF from last chunk */

                s->chunksize = strtoll(line, NULL, 16);

901
                av_dlog(NULL, "Chunked encoding data size: %"PRId64"'\n", s->chunksize);
902 903 904 905 906 907 908 909

                if (!s->chunksize)
                    return 0;
                break;
            }
        }
        size = FFMIN(size, s->chunksize);
    }
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
#if CONFIG_ZLIB
    if (s->compressed)
        return http_buf_read_compressed(h, buf, size);
#endif
    return http_buf_read(h, buf, size);
}

// Like http_read_stream(), but no short reads.
// Assumes partial reads are an error.
static int http_read_stream_all(URLContext *h, uint8_t *buf, int size)
{
    int pos = 0;
    while (pos < size) {
        int len = http_read_stream(h, buf + pos, size - pos);
        if (len < 0)
            return len;
        pos += len;
    }
    return pos;
}

931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966
static int store_icy(URLContext *h, int size)
{
    HTTPContext *s = h->priv_data;
    /* until next metadata packet */
    int remaining = s->icy_metaint - s->icy_data_read;

    if (remaining < 0)
        return AVERROR_INVALIDDATA;

    if (!remaining) {
        // The metadata packet is variable sized. It has a 1 byte header
        // which sets the length of the packet (divided by 16). If it's 0,
        // the metadata doesn't change. After the packet, icy_metaint bytes
        // of normal data follow.
        uint8_t ch;
        int len = http_read_stream_all(h, &ch, 1);
        if (len < 0)
            return len;
        if (ch > 0) {
            char data[255 * 16 + 1];
            int ret;
            len = ch * 16;
            ret = http_read_stream_all(h, data, len);
            if (ret < 0)
                return ret;
            data[len + 1] = 0;
            if ((ret = av_opt_set(s, "icy_metadata_packet", data, 0)) < 0)
                return ret;
        }
        s->icy_data_read = 0;
        remaining = s->icy_metaint;
    }

    return FFMIN(size, remaining);
}

967 968 969 970
static int http_read(URLContext *h, uint8_t *buf, int size)
{
    HTTPContext *s = h->priv_data;

971
    if (s->icy_metaint > 0) {
972 973 974
        size = store_icy(h, size);
        if (size < 0)
            return size;
975
    }
976

977 978 979 980
    size = http_read_stream(h, buf, size);
    if (size > 0)
        s->icy_data_read += size;
    return size;
981 982 983
}

/* used only when posting data */
984
static int http_write(URLContext *h, const uint8_t *buf, int size)
985
{
986
    char temp[11] = "";  /* 32-bit hex + CRLF + nul */
987 988
    int ret;
    char crlf[] = "\r\n";
989
    HTTPContext *s = h->priv_data;
990

991
    if (!s->chunked_post) {
992
        /* non-chunked data is sent without any special encoding */
993
        return ffurl_write(s->hd, buf, size);
994 995 996 997 998 999
    }

    /* silently ignore zero-size data since chunk encoding that would
     * signal EOF */
    if (size > 0) {
        /* upload data using chunked encoding */
Martin Storsjö's avatar
Martin Storsjö committed
1000
        snprintf(temp, sizeof(temp), "%x\r\n", size);
1001

1002 1003 1004
        if ((ret = ffurl_write(s->hd, temp, strlen(temp))) < 0 ||
            (ret = ffurl_write(s->hd, buf, size)) < 0 ||
            (ret = ffurl_write(s->hd, crlf, sizeof(crlf) - 1)) < 0)
1005 1006 1007
            return ret;
    }
    return size;
1008 1009
}

1010
static int http_shutdown(URLContext *h, int flags)
1011
{
1012 1013
    int ret = 0;
    char footer[] = "0\r\n\r\n";
1014
    HTTPContext *s = h->priv_data;
1015 1016

    /* signal end of chunked encoding if used */
1017
    if ((flags & AVIO_FLAG_WRITE) && s->chunked_post) {
1018
        ret = ffurl_write(s->hd, footer, sizeof(footer) - 1);
1019
        ret = ret > 0 ? 0 : ret;
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
        s->end_chunked_post = 1;
    }

    return ret;
}

static int http_close(URLContext *h)
{
    int ret = 0;
    HTTPContext *s = h->priv_data;

1031 1032 1033 1034 1035
#if CONFIG_ZLIB
    inflateEnd(&s->inflate_stream);
    av_freep(&s->inflate_buffer);
#endif

1036 1037 1038
    if (!s->end_chunked_post) {
        /* Close the write direction by sending the end of chunked encoding. */
        ret = http_shutdown(h, h->flags);
1039 1040
    }

1041
    if (s->hd)
1042
        ffurl_closep(&s->hd);
1043
    av_dict_free(&s->chained_options);
1044
    return ret;
1045 1046
}

1047
static int64_t http_seek(URLContext *h, int64_t off, int whence)
1048 1049 1050
{
    HTTPContext *s = h->priv_data;
    URLContext *old_hd = s->hd;
1051
    int64_t old_off = s->off;
1052
    uint8_t old_buf[BUFFER_SIZE];
1053
    int old_buf_size, ret;
1054
    AVDictionary *options = NULL;
1055 1056 1057

    if (whence == AVSEEK_SIZE)
        return s->filesize;
1058 1059
    else if ((whence == SEEK_CUR && off == 0) ||
             (whence == SEEK_SET && off == s->off))
1060
        return s->off;
1061
    else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed)
1062
        return AVERROR(ENOSYS);
1063 1064 1065 1066 1067

    if (whence == SEEK_CUR)
        off += s->off;
    else if (whence == SEEK_END)
        off += s->filesize;
1068 1069
    else if (whence != SEEK_SET)
        return AVERROR(EINVAL);
1070 1071
    if (off < 0)
        return AVERROR(EINVAL);
1072 1073
    s->off = off;

1074 1075 1076 1077 1078
    /* we save the old context in case the seek fails */
    old_buf_size = s->buf_end - s->buf_ptr;
    memcpy(old_buf, s->buf_ptr, old_buf_size);
    s->hd = NULL;

1079
    /* if it fails, continue on old connection */
1080
    av_dict_copy(&options, s->chained_options, 0);
1081
    if ((ret = http_open_cnx(h, &options)) < 0) {
1082
        av_dict_free(&options);
1083 1084 1085
        memcpy(s->buffer, old_buf, old_buf_size);
        s->buf_ptr = s->buffer;
        s->buf_end = s->buffer + old_buf_size;
1086 1087
        s->hd = old_hd;
        s->off = old_off;
1088
        return ret;
1089
    }
1090
    av_dict_free(&options);
1091
    ffurl_close(old_hd);
1092 1093 1094
    return off;
}

1095 1096 1097 1098
static int
http_get_file_handle(URLContext *h)
{
    HTTPContext *s = h->priv_data;
1099
    return ffurl_get_file_handle(s->hd);
1100 1101
}

1102
#if CONFIG_HTTP_PROTOCOL
1103
URLProtocol ff_http_protocol = {
1104
    .name                = "http",
1105
    .url_open2           = http_open,
1106 1107 1108 1109
    .url_read            = http_read,
    .url_write           = http_write,
    .url_seek            = http_seek,
    .url_close           = http_close,
1110
    .url_get_file_handle = http_get_file_handle,
1111
    .url_shutdown        = http_shutdown,
1112
    .priv_data_size      = sizeof(HTTPContext),
1113
    .priv_data_class     = &http_context_class,
1114
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
1115
};
1116 1117 1118 1119
#endif
#if CONFIG_HTTPS_PROTOCOL
URLProtocol ff_https_protocol = {
    .name                = "https",
1120
    .url_open2           = http_open,
1121 1122 1123 1124 1125
    .url_read            = http_read,
    .url_write           = http_write,
    .url_seek            = http_seek,
    .url_close           = http_close,
    .url_get_file_handle = http_get_file_handle,
1126
    .url_shutdown        = http_shutdown,
1127
    .priv_data_size      = sizeof(HTTPContext),
1128
    .priv_data_class     = &https_context_class,
1129
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
1130 1131
};
#endif
1132 1133 1134 1135 1136 1137

#if CONFIG_HTTPPROXY_PROTOCOL
static int http_proxy_close(URLContext *h)
{
    HTTPContext *s = h->priv_data;
    if (s->hd)
1138
        ffurl_closep(&s->hd);
1139 1140 1141 1142 1143 1144 1145 1146
    return 0;
}

static int http_proxy_open(URLContext *h, const char *uri, int flags)
{
    HTTPContext *s = h->priv_data;
    char hostname[1024], hoststr[1024];
    char auth[1024], pathbuf[1024], *path;
1147
    char lower_url[100];
1148
    int port, ret = 0, attempts = 0;
1149 1150
    HTTPAuthType cur_auth_type;
    char *authstr;
1151
    int new_loc;
1152

1153 1154 1155 1156
    if( s->seekable == 1 )
        h->is_streamed = 0;
    else
        h->is_streamed = 1;
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194

    av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
                 pathbuf, sizeof(pathbuf), uri);
    ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
    path = pathbuf;
    if (*path == '/')
        path++;

    ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port,
                NULL);
redo:
    ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE,
                     &h->interrupt_callback, NULL);
    if (ret < 0)
        return ret;

    authstr = ff_http_auth_create_response(&s->proxy_auth_state, auth,
                                           path, "CONNECT");
    snprintf(s->buffer, sizeof(s->buffer),
             "CONNECT %s HTTP/1.1\r\n"
             "Host: %s\r\n"
             "Connection: close\r\n"
             "%s%s"
             "\r\n",
             path,
             hoststr,
             authstr ? "Proxy-" : "", authstr ? authstr : "");
    av_freep(&authstr);

    if ((ret = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0)
        goto fail;

    s->buf_ptr = s->buffer;
    s->buf_end = s->buffer;
    s->line_count = 0;
    s->filesize = -1;
    cur_auth_type = s->proxy_auth_state.auth_type;

1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206
    /* Note: This uses buffering, potentially reading more than the
     * HTTP header. If tunneling a protocol where the server starts
     * the conversation, we might buffer part of that here, too.
     * Reading that requires using the proper ffurl_read() function
     * on this URLContext, not using the fd directly (as the tls
     * protocol does). This shouldn't be an issue for tls though,
     * since the client starts the conversation there, so there
     * is no extra data that we might buffer up here.
     */
    ret = http_read_header(h, &new_loc);
    if (ret < 0)
        goto fail;
1207

1208 1209 1210 1211
    attempts++;
    if (s->http_code == 407 &&
        (cur_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) &&
        s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 2) {
1212
        ffurl_closep(&s->hd);
1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238
        goto redo;
    }

    if (s->http_code < 400)
        return 0;
    ret = AVERROR(EIO);

fail:
    http_proxy_close(h);
    return ret;
}

static int http_proxy_write(URLContext *h, const uint8_t *buf, int size)
{
    HTTPContext *s = h->priv_data;
    return ffurl_write(s->hd, buf, size);
}

URLProtocol ff_httpproxy_protocol = {
    .name                = "httpproxy",
    .url_open            = http_proxy_open,
    .url_read            = http_buf_read,
    .url_write           = http_proxy_write,
    .url_close           = http_proxy_close,
    .url_get_file_handle = http_get_file_handle,
    .priv_data_size      = sizeof(HTTPContext),
1239
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
1240 1241
};
#endif