3 * Copyright (c) 2010 Martin Storsjo
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
23 #include "libavutil/base64.h"
24 #include "libavutil/avstring.h"
26 #include "libavutil/random_seed.h"
27 #include "libavutil/md5.h"
28 #include "urldecode.h"
31 static void handle_basic_params(HTTPAuthState
*state
, const char *key
,
32 int key_len
, char **dest
, int *dest_len
)
34 if (!strncmp(key
, "realm=", key_len
)) {
36 *dest_len
= sizeof(state
->realm
);
40 static void handle_digest_params(HTTPAuthState
*state
, const char *key
,
41 int key_len
, char **dest
, int *dest_len
)
43 DigestParams
*digest
= &state
->digest_params
;
45 if (!strncmp(key
, "realm=", key_len
)) {
47 *dest_len
= sizeof(state
->realm
);
48 } else if (!strncmp(key
, "nonce=", key_len
)) {
49 *dest
= digest
->nonce
;
50 *dest_len
= sizeof(digest
->nonce
);
51 } else if (!strncmp(key
, "opaque=", key_len
)) {
52 *dest
= digest
->opaque
;
53 *dest_len
= sizeof(digest
->opaque
);
54 } else if (!strncmp(key
, "algorithm=", key_len
)) {
55 *dest
= digest
->algorithm
;
56 *dest_len
= sizeof(digest
->algorithm
);
57 } else if (!strncmp(key
, "qop=", key_len
)) {
59 *dest_len
= sizeof(digest
->qop
);
60 } else if (!strncmp(key
, "stale=", key_len
)) {
61 *dest
= digest
->stale
;
62 *dest_len
= sizeof(digest
->stale
);
66 static void handle_digest_update(HTTPAuthState
*state
, const char *key
,
67 int key_len
, char **dest
, int *dest_len
)
69 DigestParams
*digest
= &state
->digest_params
;
71 if (!strncmp(key
, "nextnonce=", key_len
)) {
72 *dest
= digest
->nonce
;
73 *dest_len
= sizeof(digest
->nonce
);
77 static void choose_qop(char *qop
, int size
)
79 char *ptr
= strstr(qop
, "auth");
80 char *end
= ptr
+ strlen("auth");
82 if (ptr
&& (!*end
|| av_isspace(*end
) || *end
== ',') &&
83 (ptr
== qop
|| av_isspace(ptr
[-1]) || ptr
[-1] == ',')) {
84 av_strlcpy(qop
, "auth", size
);
90 void ff_http_auth_handle_header(HTTPAuthState
*state
, const char *key
,
93 if (!strcmp(key
, "WWW-Authenticate") || !strcmp(key
, "Proxy-Authenticate")) {
95 if (av_stristart(value
, "Basic ", &p
) &&
96 state
->auth_type
<= HTTP_AUTH_BASIC
) {
97 state
->auth_type
= HTTP_AUTH_BASIC
;
100 ff_parse_key_value(p
, (ff_parse_key_val_cb
) handle_basic_params
,
102 } else if (av_stristart(value
, "Digest ", &p
) &&
103 state
->auth_type
<= HTTP_AUTH_DIGEST
) {
104 state
->auth_type
= HTTP_AUTH_DIGEST
;
105 memset(&state
->digest_params
, 0, sizeof(DigestParams
));
108 ff_parse_key_value(p
, (ff_parse_key_val_cb
) handle_digest_params
,
110 choose_qop(state
->digest_params
.qop
,
111 sizeof(state
->digest_params
.qop
));
112 if (!av_strcasecmp(state
->digest_params
.stale
, "true"))
115 } else if (!strcmp(key
, "Authentication-Info")) {
116 ff_parse_key_value(value
, (ff_parse_key_val_cb
) handle_digest_update
,
122 static void update_md5_strings(struct AVMD5
*md5ctx
, ...)
126 va_start(vl
, md5ctx
);
128 const char* str
= va_arg(vl
, const char*);
131 av_md5_update(md5ctx
, str
, strlen(str
));
136 /* Generate a digest reply, according to RFC 2617. */
137 static char *make_digest_auth(HTTPAuthState
*state
, const char *username
,
138 const char *password
, const char *uri
,
141 DigestParams
*digest
= &state
->digest_params
;
143 uint32_t cnonce_buf
[2];
147 char A1hash
[33], A2hash
[33], response
[33];
148 struct AVMD5
*md5ctx
;
153 snprintf(nc
, sizeof(nc
), "%08x", digest
->nc
);
155 /* Generate a client nonce. */
156 for (i
= 0; i
< 2; i
++)
157 cnonce_buf
[i
] = av_get_random_seed();
158 ff_data_to_hex(cnonce
, (const uint8_t*) cnonce_buf
, sizeof(cnonce_buf
), 1);
159 cnonce
[2*sizeof(cnonce_buf
)] = 0;
161 md5ctx
= av_md5_alloc();
166 update_md5_strings(md5ctx
, username
, ":", state
->realm
, ":", password
, NULL
);
167 av_md5_final(md5ctx
, hash
);
168 ff_data_to_hex(A1hash
, hash
, 16, 1);
171 if (!strcmp(digest
->algorithm
, "") || !strcmp(digest
->algorithm
, "MD5")) {
172 } else if (!strcmp(digest
->algorithm
, "MD5-sess")) {
174 update_md5_strings(md5ctx
, A1hash
, ":", digest
->nonce
, ":", cnonce
, NULL
);
175 av_md5_final(md5ctx
, hash
);
176 ff_data_to_hex(A1hash
, hash
, 16, 1);
179 /* Unsupported algorithm */
185 update_md5_strings(md5ctx
, method
, ":", uri
, NULL
);
186 av_md5_final(md5ctx
, hash
);
187 ff_data_to_hex(A2hash
, hash
, 16, 1);
191 update_md5_strings(md5ctx
, A1hash
, ":", digest
->nonce
, NULL
);
192 if (!strcmp(digest
->qop
, "auth") || !strcmp(digest
->qop
, "auth-int")) {
193 update_md5_strings(md5ctx
, ":", nc
, ":", cnonce
, ":", digest
->qop
, NULL
);
195 update_md5_strings(md5ctx
, ":", A2hash
, NULL
);
196 av_md5_final(md5ctx
, hash
);
197 ff_data_to_hex(response
, hash
, 16, 1);
202 if (!strcmp(digest
->qop
, "") || !strcmp(digest
->qop
, "auth")) {
203 } else if (!strcmp(digest
->qop
, "auth-int")) {
204 /* qop=auth-int not supported */
207 /* Unsupported qop value. */
211 len
= strlen(username
) + strlen(state
->realm
) + strlen(digest
->nonce
) +
212 strlen(uri
) + strlen(response
) + strlen(digest
->algorithm
) +
213 strlen(digest
->opaque
) + strlen(digest
->qop
) + strlen(cnonce
) +
216 authstr
= av_malloc(len
);
219 snprintf(authstr
, len
, "Authorization: Digest ");
221 /* TODO: Escape the quoted strings properly. */
222 av_strlcatf(authstr
, len
, "username=\"%s\"", username
);
223 av_strlcatf(authstr
, len
, ",realm=\"%s\"", state
->realm
);
224 av_strlcatf(authstr
, len
, ",nonce=\"%s\"", digest
->nonce
);
225 av_strlcatf(authstr
, len
, ",uri=\"%s\"", uri
);
226 av_strlcatf(authstr
, len
, ",response=\"%s\"", response
);
228 // we are violating the RFC and use "" because all others seem to do that too.
229 if (digest
->algorithm
[0])
230 av_strlcatf(authstr
, len
, ",algorithm=\"%s\"", digest
->algorithm
);
232 if (digest
->opaque
[0])
233 av_strlcatf(authstr
, len
, ",opaque=\"%s\"", digest
->opaque
);
234 if (digest
->qop
[0]) {
235 av_strlcatf(authstr
, len
, ",qop=\"%s\"", digest
->qop
);
236 av_strlcatf(authstr
, len
, ",cnonce=\"%s\"", cnonce
);
237 av_strlcatf(authstr
, len
, ",nc=%s", nc
);
240 av_strlcatf(authstr
, len
, "\r\n");
245 char *ff_http_auth_create_response(HTTPAuthState
*state
, const char *auth
,
246 const char *path
, const char *method
)
248 char *authstr
= NULL
;
250 /* Clear the stale flag, we assume the auth is ok now. It is reset
251 * by the server headers if there's a new issue. */
253 if (!auth
|| !strchr(auth
, ':'))
256 if (state
->auth_type
== HTTP_AUTH_BASIC
) {
257 int auth_b64_len
, len
;
258 char *ptr
, *decoded_auth
= ff_urldecode(auth
);
263 auth_b64_len
= AV_BASE64_SIZE(strlen(decoded_auth
));
264 len
= auth_b64_len
+ 30;
266 authstr
= av_malloc(len
);
268 av_free(decoded_auth
);
272 snprintf(authstr
, len
, "Authorization: Basic ");
273 ptr
= authstr
+ strlen(authstr
);
274 av_base64_encode(ptr
, auth_b64_len
, decoded_auth
, strlen(decoded_auth
));
275 av_strlcat(ptr
, "\r\n", len
- (ptr
- authstr
));
276 av_free(decoded_auth
);
277 } else if (state
->auth_type
== HTTP_AUTH_DIGEST
) {
278 char *username
= ff_urldecode(auth
), *password
;
283 if ((password
= strchr(username
, ':'))) {
285 authstr
= make_digest_auth(state
, username
, password
, path
, method
);