Commit de6fb646f7 for ffmpeg

commit de6fb646f7c4c35973526066081232327c5d08a4
Author: Michael Niedermayer <michael@niedermayer.cc>
Date:   Mon Oct 12 04:12:37 2026 +0200

    avformat/http: send cookies given without a Domain only to their host

    Fixes: cookie disclosure to other hosts
    Fixes: u2ys08MO8oEF
    Cookie disclosure Replicated through UnModified FFmpeg

    Found-by: Yazan Balawneh, Cystack.ps

diff --git a/doc/protocols.texi b/doc/protocols.texi
index aa3b88fd58..287d972f68 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -524,7 +524,8 @@ Exports the HTTP response version number. Usually "1.0" or "1.1".
 @item cookies
 Set the cookies to be sent in future requests. The format of each cookie is the
 same as the value of a Set-Cookie HTTP response field. Multiple cookies can be
-delimited by a newline character.
+delimited by a newline character. A cookie without a Domain attribute is only
+sent to the host of the URL it is set for.

 @item icy
 If set to 1 request ICY (SHOUTcast) metadata from the server. If the server
@@ -675,7 +676,7 @@ is used for reading large video packets with small audio packets in between.

 Some HTTP requests will be denied unless cookie values are passed in with the
 request. The @option{cookies} option allows these cookies to be specified. At
-the very least, each cookie must specify a value along with a path and domain.
+the very least, each cookie must specify a value.
 HTTP requests that match both the domain and path will automatically include the
 cookie value in the HTTP Cookie header field. Multiple cookies can be delimited
 by a newline.
diff --git a/libavformat/http.c b/libavformat/http.c
index 488a8eb73d..2f4f522363 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -239,6 +239,7 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
                         const char *hoststr, const char *auth,
                         const char *proxyauth);
 static int http_read_header(URLContext *h);
+static int bind_cookies_to_host(HTTPContext *s);
 static int http_shutdown(URLContext *h, int flags);

 void ff_http_init_auth_state(URLContext *dest, const URLContext *src)
@@ -837,6 +838,9 @@ static int http_open(URLContext *h, const char *uri, int flags,
     if (!s->uri)
         return AVERROR(ENOMEM);

+    if (s->cookies && (ret = bind_cookies_to_host(s)) < 0)
+        goto bail_out;
+
     if (options)
         av_dict_copy(&s->chained_options, *options, 0);

@@ -1259,6 +1263,49 @@ static int parse_cookie(HTTPContext *s, const char *p, const char *host,
     return 0;
 }

+/* Cookies without a Domain apply only to the host they are given for. */
+static int bind_cookies_to_host(HTTPContext *s)
+{
+    char host[1024], *cookies, *cookie, *next, *saveptr = NULL, *bound;
+    AVBPrint bp;
+    int ret = 0;
+
+    av_url_split(NULL, 0, NULL, 0, host, sizeof(host), NULL, NULL, 0, s->location);
+
+    next = cookies = av_strdup(s->cookies);
+    if (!cookies)
+        return AVERROR(ENOMEM);
+
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+    while ((cookie = av_strtok(next, "\n", &saveptr))) {
+        AVDictionary *params = NULL;
+        int len = strlen(cookie), host_only;
+
+        next = NULL;
+        while (len && strchr(WHITESPACES, cookie[len - 1]))
+            len--;
+        if ((ret = parse_set_cookie(cookie, &params)) < 0) {
+            av_dict_free(&params);
+            break;
+        }
+        host_only = !cookie_domain(params) && !av_dict_get(params, "@hostonly", NULL, 0);
+        av_dict_free(&params);
+        av_bprintf(&bp, "%.*s%s%s\n", len, cookie, host_only ? "; @hostonly=" : "", host_only ? host : "");
+    }
+    av_free(cookies);
+    if (ret >= 0 && !av_bprint_is_complete(&bp))
+        ret = AVERROR(ENOMEM);
+    if (ret < 0) {
+        av_bprint_finalize(&bp, NULL);
+        return ret;
+    }
+    if ((ret = av_bprint_finalize(&bp, &bound)) < 0)
+        return ret;
+    av_free(s->cookies);
+    s->cookies = bound;
+    return 0;
+}
+
 static int cookie_string(AVDictionary *dict, char **cookies)
 {
     const AVDictionaryEntry *e = NULL;