FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/httpauth.c
Date: 2026-10-19 01:23:10
Exec Total Coverage
Lines: 0 171 0.0%
Functions: 0 8 0.0%
Branches: 0 102 0.0%

Line Branch Exec Source
1 /*
2 * HTTP authentication
3 * Copyright (c) 2010 Martin Storsjo
4 *
5 * This file is part of FFmpeg.
6 *
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.
11 *
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.
16 *
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
20 */
21
22 #include <stdbool.h>
23
24 #include "httpauth.h"
25 #include "libavutil/base64.h"
26 #include "libavutil/avstring.h"
27 #include "libavutil/mem.h"
28 #include "internal.h"
29 #include "libavutil/random_seed.h"
30 #include "libavutil/hash.h"
31 #include "urldecode.h"
32
33 /**
34 * Maximum length of a hash string in hexadecimal, with a string terminator
35 */
36 #define HASH_STRING_LEN (AV_HASH_MAX_SIZE * 2 + 1)
37
38 ✗ static void handle_basic_params(void *context, const char *key,
39 int key_len, char **dest, int *dest_len)
40 {
41 ✗ HTTPAuthState *state = context;
42 ✗ if (!strncmp(key, "realm=", key_len)) {
43 ✗ *dest = state->realm;
44 ✗ *dest_len = sizeof(state->realm);
45 }
46 ✗ }
47
48 ✗ static void handle_digest_params(void *context, const char *key,
49 int key_len, char **dest, int *dest_len)
50 {
51 ✗ HTTPAuthState *state = context;
52 ✗ DigestParams *digest = &state->digest_params;
53
54 ✗ if (!strncmp(key, "realm=", key_len)) {
55 ✗ *dest = state->realm;
56 ✗ *dest_len = sizeof(state->realm);
57 ✗ } else if (!strncmp(key, "nonce=", key_len)) {
58 ✗ *dest = digest->nonce;
59 ✗ *dest_len = sizeof(digest->nonce);
60 ✗ } else if (!strncmp(key, "opaque=", key_len)) {
61 ✗ *dest = digest->opaque;
62 ✗ *dest_len = sizeof(digest->opaque);
63 ✗ } else if (!strncmp(key, "algorithm=", key_len)) {
64 ✗ *dest = digest->algorithm;
65 ✗ *dest_len = sizeof(digest->algorithm);
66 ✗ } else if (!strncmp(key, "qop=", key_len)) {
67 ✗ *dest = digest->qop;
68 ✗ *dest_len = sizeof(digest->qop);
69 ✗ } else if (!strncmp(key, "stale=", key_len)) {
70 ✗ *dest = digest->stale;
71 ✗ *dest_len = sizeof(digest->stale);
72 }
73 ✗ }
74
75 ✗ static void handle_digest_update(void *context, const char *key,
76 int key_len, char **dest, int *dest_len)
77 {
78 ✗ HTTPAuthState *state = context;
79 ✗ DigestParams *digest = &state->digest_params;
80
81 ✗ if (!strncmp(key, "nextnonce=", key_len)) {
82 ✗ *dest = digest->nonce;
83 ✗ *dest_len = sizeof(digest->nonce);
84 }
85 ✗ }
86
87 ✗ static void choose_qop(char *qop, int size)
88 {
89 ✗ char *ptr = strstr(qop, "auth");
90 ✗ char *end = ptr + strlen("auth");
91
92 ✗ if (ptr && (!*end || av_isspace(*end) || *end == ',') &&
93 ✗ (ptr == qop || av_isspace(ptr[-1]) || ptr[-1] == ',')) {
94 ✗ av_strlcpy(qop, "auth", size);
95 } else {
96 ✗ qop[0] = 0;
97 }
98 ✗ }
99
100 ✗ void ff_http_auth_handle_header(HTTPAuthState *state, const char *key,
101 const char *value)
102 {
103 ✗ if (!av_strcasecmp(key, "WWW-Authenticate") || !av_strcasecmp(key, "Proxy-Authenticate")) {
104 const char *p;
105 ✗ if (av_stristart(value, "Basic ", &p) &&
106 ✗ state->auth_type <= HTTP_AUTH_BASIC) {
107 ✗ state->auth_type = HTTP_AUTH_BASIC;
108 ✗ state->realm[0] = 0;
109 ✗ state->stale = 0;
110 ✗ ff_parse_key_value(p, handle_basic_params, state);
111 ✗ } else if (av_stristart(value, "Digest ", &p) &&
112 ✗ state->auth_type <= HTTP_AUTH_DIGEST) {
113 ✗ state->auth_type = HTTP_AUTH_DIGEST;
114 ✗ memset(&state->digest_params, 0, sizeof(DigestParams));
115 ✗ state->realm[0] = 0;
116 ✗ state->stale = 0;
117 ✗ ff_parse_key_value(p, handle_digest_params, state);
118 ✗ choose_qop(state->digest_params.qop,
119 sizeof(state->digest_params.qop));
120 ✗ if (!av_strcasecmp(state->digest_params.stale, "true"))
121 ✗ state->stale = 1;
122 }
123 ✗ } else if (!av_strcasecmp(key, "Authentication-Info")) {
124 ✗ ff_parse_key_value(value, handle_digest_update, state);
125 }
126 ✗ }
127
128 ✗ static void update_hash_strings(struct AVHashContext *hashctx, ...)
129 {
130 va_list vl;
131
132 ✗ va_start(vl, hashctx);
133 ✗ while (1) {
134 ✗ const char* str = va_arg(vl, const char*);
135 ✗ if (!str)
136 ✗ break;
137 ✗ av_hash_update(hashctx, str, strlen(str));
138 }
139 ✗ va_end(vl);
140 ✗ }
141
142 /* Generate a digest reply, according to RFC 2617 and RFC 7616. */
143 ✗ static char *make_digest_auth(HTTPAuthState *state, const char *username,
144 const char *password, const char *uri,
145 const char *method)
146 {
147 ✗ DigestParams *digest = &state->digest_params;
148 int len;
149 uint32_t cnonce_buf[2];
150 char cnonce[17];
151 char nc[9];
152 int i;
153 char A1hash[HASH_STRING_LEN], A2hash[HASH_STRING_LEN], response[HASH_STRING_LEN];
154 ✗ struct AVHashContext *hashctx = NULL;
155 uint8_t hash[AV_HASH_MAX_SIZE];
156 ✗ const char* algorithm = NULL;
157 ✗ bool is_session = false;
158 int hash_size;
159 char *authstr;
160
161 ✗ digest->nc++;
162 ✗ snprintf(nc, sizeof(nc), "%08x", digest->nc);
163
164 ✗ if (!strcmp(digest->algorithm, "")) { algorithm = "MD5"; }
165 ✗ else if (!strcmp(digest->algorithm, "MD5")) { algorithm = "MD5"; }
166 ✗ else if (!strcmp(digest->algorithm, "MD5-sess")) { algorithm = "MD5"; is_session = true; }
167 ✗ else if (!strcmp(digest->algorithm, "SHA-256")) { algorithm = "SHA256"; }
168 ✗ else if (!strcmp(digest->algorithm, "SHA-256-sess")) { algorithm = "SHA256"; is_session = true; }
169 ✗ else if (!strcmp(digest->algorithm, "SHA-512-256")) { algorithm = "SHA512/256"; }
170 ✗ else if (!strcmp(digest->algorithm, "SHA-512-256-sess")){ algorithm = "SHA512/256"; is_session = true; }
171 else {
172 /* Unsupported algorithm */
173 ✗ return NULL;
174 }
175
176 /* Generate a client nonce. */
177 ✗ for (i = 0; i < 2; i++)
178 ✗ cnonce_buf[i] = av_get_random_seed();
179 ✗ ff_data_to_hex(cnonce, (const uint8_t*) cnonce_buf, sizeof(cnonce_buf), 1);
180
181 ✗ if (av_hash_alloc(&hashctx, algorithm) < 0)
182 ✗ return NULL;
183
184 ✗ hash_size = av_hash_get_size(hashctx);
185
186 ✗ av_hash_init (hashctx);
187 ✗ update_hash_strings(hashctx, username, ":", state->realm, ":", password, NULL);
188 ✗ av_hash_final(hashctx, hash);
189 ✗ ff_data_to_hex(A1hash, hash, hash_size, 1);
190
191 ✗ if (is_session) {
192 ✗ av_hash_init(hashctx);
193 ✗ update_hash_strings(hashctx, A1hash, ":", digest->nonce, ":", cnonce, NULL);
194 ✗ av_hash_final(hashctx, hash);
195 ✗ ff_data_to_hex(A1hash, hash, hash_size, 1);
196 }
197
198 ✗ av_hash_init(hashctx);
199 ✗ update_hash_strings(hashctx, method, ":", uri, NULL);
200 ✗ av_hash_final(hashctx, hash);
201 ✗ ff_data_to_hex(A2hash, hash, hash_size, 1);
202
203 ✗ av_hash_init(hashctx);
204 ✗ update_hash_strings(hashctx, A1hash, ":", digest->nonce, NULL);
205 ✗ if (!strcmp(digest->qop, "auth") || !strcmp(digest->qop, "auth-int")) {
206 ✗ update_hash_strings(hashctx, ":", nc, ":", cnonce, ":", digest->qop, NULL);
207 }
208 ✗ update_hash_strings(hashctx, ":", A2hash, NULL);
209 ✗ av_hash_final(hashctx, hash);
210 ✗ ff_data_to_hex(response, hash, hash_size, 1);
211
212 ✗ av_hash_freep(&hashctx);
213
214 ✗ if (!strcmp(digest->qop, "") || !strcmp(digest->qop, "auth")) {
215 ✗ } else if (!strcmp(digest->qop, "auth-int")) {
216 /* qop=auth-int not supported */
217 ✗ return NULL;
218 } else {
219 /* Unsupported qop value. */
220 ✗ return NULL;
221 }
222
223 ✗ len = strlen(username) + strlen(state->realm) + strlen(digest->nonce) +
224 ✗ strlen(uri) + strlen(response) + strlen(digest->algorithm) +
225 ✗ strlen(digest->opaque) + strlen(digest->qop) + strlen(cnonce) +
226 ✗ strlen(nc) + 150;
227
228 ✗ authstr = av_malloc(len);
229 ✗ if (!authstr)
230 ✗ return NULL;
231 ✗ snprintf(authstr, len, "Authorization: Digest ");
232
233 /* TODO: Escape the quoted strings properly. */
234 ✗ av_strlcatf(authstr, len, "username=\"%s\"", username);
235 ✗ av_strlcatf(authstr, len, ", realm=\"%s\"", state->realm);
236 ✗ av_strlcatf(authstr, len, ", nonce=\"%s\"", digest->nonce);
237 ✗ av_strlcatf(authstr, len, ", uri=\"%s\"", uri);
238 ✗ av_strlcatf(authstr, len, ", response=\"%s\"", response);
239
240 // we are violating the RFC and use "" because all others seem to do that too.
241 ✗ if (digest->algorithm[0])
242 ✗ av_strlcatf(authstr, len, ", algorithm=\"%s\"", digest->algorithm);
243
244 ✗ if (digest->opaque[0])
245 ✗ av_strlcatf(authstr, len, ", opaque=\"%s\"", digest->opaque);
246 ✗ if (digest->qop[0]) {
247 ✗ av_strlcatf(authstr, len, ", qop=\"%s\"", digest->qop);
248 ✗ av_strlcatf(authstr, len, ", cnonce=\"%s\"", cnonce);
249 ✗ av_strlcatf(authstr, len, ", nc=%s", nc);
250 }
251
252 ✗ av_strlcatf(authstr, len, "\r\n");
253
254 ✗ return authstr;
255 }
256
257 ✗ char *ff_http_auth_create_response(HTTPAuthState *state, const char *auth,
258 const char *path, const char *method)
259 {
260 ✗ char *authstr = NULL;
261
262 /* Clear the stale flag, we assume the auth is ok now. It is reset
263 * by the server headers if there's a new issue. */
264 ✗ state->stale = 0;
265 ✗ if (!auth || !strchr(auth, ':'))
266 ✗ return NULL;
267
268 ✗ if (state->auth_type == HTTP_AUTH_BASIC) {
269 int auth_b64_len, len;
270 ✗ char *ptr, *decoded_auth = ff_urldecode(auth, 0);
271
272 ✗ if (!decoded_auth)
273 ✗ return NULL;
274
275 ✗ auth_b64_len = AV_BASE64_SIZE(strlen(decoded_auth));
276 ✗ len = auth_b64_len + 30;
277
278 ✗ authstr = av_malloc(len);
279 ✗ if (!authstr) {
280 ✗ av_free(decoded_auth);
281 ✗ return NULL;
282 }
283
284 ✗ snprintf(authstr, len, "Authorization: Basic ");
285 ✗ ptr = authstr + strlen(authstr);
286 ✗ av_base64_encode(ptr, auth_b64_len, decoded_auth, strlen(decoded_auth));
287 ✗ av_strlcat(ptr, "\r\n", len - (ptr - authstr));
288 ✗ av_free(decoded_auth);
289 ✗ } else if (state->auth_type == HTTP_AUTH_DIGEST) {
290 ✗ char *username = ff_urldecode(auth, 0), *password;
291
292 ✗ if (!username)
293 ✗ return NULL;
294
295 ✗ if ((password = strchr(username, ':'))) {
296 ✗ *password++ = 0;
297 ✗ authstr = make_digest_auth(state, username, password, path, method);
298 }
299 ✗ av_free(username);
300 }
301 ✗ return authstr;
302 }
303