diff options
author | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2022-05-30 21:54:09 +0300 |
---|---|---|
committer | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2022-05-31 11:45:36 +0300 |
commit | 9039d65241daf512e7756319cd64d3d54750cb22 (patch) | |
tree | 9c109e217ad1014a89de5495fcebdc1723672e31 /src/microhttpd/gen_auth.c | |
parent | c15077e312ffe934b242c7ecc6559c6eb4eb62cb (diff) | |
download | libmicrohttpd-9039d65241daf512e7756319cd64d3d54750cb22.tar.gz libmicrohttpd-9039d65241daf512e7756319cd64d3d54750cb22.zip |
authentication: reworked header parsing
Added single function to parse all enabled authentication schemes header
strings.
The parsing result is cached and reused thus avoiding repetitive header
parsing.
The new function correctly "unquotes" values (backslashes are removed)
as required by RFC.
Diffstat (limited to 'src/microhttpd/gen_auth.c')
-rw-r--r-- | src/microhttpd/gen_auth.c | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/src/microhttpd/gen_auth.c b/src/microhttpd/gen_auth.c new file mode 100644 index 00000000..e993fb20 --- /dev/null +++ b/src/microhttpd/gen_auth.c | |||
@@ -0,0 +1,511 @@ | |||
1 | /* | ||
2 | This file is part of libmicrohttpd | ||
3 | Copyright (C) 2022 Evgeny Grin (Karlson2k) | ||
4 | |||
5 | This library is free software; you can redistribute it and/or | ||
6 | modify it under the terms of the GNU Lesser General Public | ||
7 | License as published by the Free Software Foundation; either | ||
8 | version 2.1 of the License, or (at your option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, | ||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Lesser General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Lesser General Public | ||
16 | License along with this library. | ||
17 | If not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | /** | ||
21 | * @file microhttpd/gen_auth.c | ||
22 | * @brief HTTP authorisation general functions | ||
23 | * @author Karlson2k (Evgeny Grin) | ||
24 | */ | ||
25 | |||
26 | #include "gen_auth.h" | ||
27 | #include "internal.h" | ||
28 | #include "connection.h" | ||
29 | #include "mhd_str.h" | ||
30 | #include "mhd_assert.h" | ||
31 | |||
32 | #ifdef BAUTH_SUPPORT | ||
33 | #include "basicauth.h" | ||
34 | #endif /* BAUTH_SUPPORT */ | ||
35 | #ifdef DAUTH_SUPPORT | ||
36 | #include "digestauth.h" | ||
37 | #endif /* DAUTH_SUPPORT */ | ||
38 | |||
39 | #if ! defined(BAUTH_SUPPORT) && ! defined(DAUTH_SUPPORT) | ||
40 | #error This file requires Basic or Digest authentication support | ||
41 | #endif | ||
42 | |||
43 | #ifdef BAUTH_SUPPORT | ||
44 | /** | ||
45 | * Parse request Authorization header parameters for Basic Authentication | ||
46 | * @param str the header string, everything after "Basic " substring | ||
47 | * @param str_len the length of @a str in characters | ||
48 | * @param[out] pbauth the pointer to the structure with Basic Authentication | ||
49 | * parameters | ||
50 | * @return true if parameters has been successfully parsed, | ||
51 | * false if format of the @a str is invalid | ||
52 | */ | ||
53 | static bool | ||
54 | parse_bauth_params (const char *str, | ||
55 | size_t str_len, | ||
56 | struct MHD_RqBAuth *pbauth) | ||
57 | { | ||
58 | size_t i; | ||
59 | |||
60 | i = 0; | ||
61 | |||
62 | /* Skip all whitespaces at start */ | ||
63 | while (i < str_len && (' ' == str[i] || '\t' == str[i])) | ||
64 | i++; | ||
65 | |||
66 | if (str_len > i) | ||
67 | { | ||
68 | size_t token68_start; | ||
69 | size_t token68_len; | ||
70 | |||
71 | /* 'i' points to the first non-whitespace char after scheme token */ | ||
72 | token68_start = i; | ||
73 | /* Find end of the token. Token cannot contain whitespace. */ | ||
74 | while (i < str_len && ' ' != str[i] && '\t' != str[i]) | ||
75 | { | ||
76 | if (0 == str[0]) | ||
77 | return false; /* Binary zero is not allowed */ | ||
78 | i++; | ||
79 | } | ||
80 | token68_len = i - token68_start; | ||
81 | mhd_assert (0 != token68_len); | ||
82 | |||
83 | /* Skip all whitespaces */ | ||
84 | while (i < str_len && (' ' == str[i] || '\t' == str[i])) | ||
85 | i++; | ||
86 | /* Check whether any garbage is present at the end of the string */ | ||
87 | if (str_len != i) | ||
88 | return false; | ||
89 | else | ||
90 | { | ||
91 | /* No more data in the string, only single token68. */ | ||
92 | const struct _MHD_cstr_w_len tkn = { str + token68_start, token68_len}; | ||
93 | memcpy (&pbauth->token68, &tkn, sizeof(tkn)); | ||
94 | } | ||
95 | } | ||
96 | return true; | ||
97 | } | ||
98 | |||
99 | |||
100 | #endif /* BAUTH_SUPPORT */ | ||
101 | |||
102 | #ifdef DAUTH_SUPPORT | ||
103 | |||
104 | /** | ||
105 | * Helper structure to map token name to position where to put token's value | ||
106 | */ | ||
107 | struct dauth_token_param | ||
108 | { | ||
109 | const struct _MHD_cstr_w_len *const tk_name; | ||
110 | struct MHD_RqDAuthParam *const param; | ||
111 | }; | ||
112 | |||
113 | /** | ||
114 | * Parse request Authorization header parameters for Digest Authentication | ||
115 | * @param str the header string, everything after "Digest " substring | ||
116 | * @param str_len the length of @a str in characters | ||
117 | * @param[out] pdauth the pointer to the structure with Digest Authentication | ||
118 | * parameters | ||
119 | * @return true if parameters has been successfully parsed, | ||
120 | * false if format of the @a str is invalid | ||
121 | */ | ||
122 | static bool | ||
123 | parse_dauth_params (const char *str, | ||
124 | const size_t str_len, | ||
125 | struct MHD_RqDAuth *pdauth) | ||
126 | { | ||
127 | static const struct _MHD_cstr_w_len nonce_tk = _MHD_S_STR_W_LEN ("nonce"); | ||
128 | static const struct _MHD_cstr_w_len opaque_tk = _MHD_S_STR_W_LEN ("opaque"); | ||
129 | static const struct _MHD_cstr_w_len algorithm_tk = | ||
130 | _MHD_S_STR_W_LEN ("algorithm"); | ||
131 | static const struct _MHD_cstr_w_len response_tk = | ||
132 | _MHD_S_STR_W_LEN ("response"); | ||
133 | static const struct _MHD_cstr_w_len username_tk = | ||
134 | _MHD_S_STR_W_LEN ("username"); | ||
135 | static const struct _MHD_cstr_w_len username_ext_tk = | ||
136 | _MHD_S_STR_W_LEN ("username*"); | ||
137 | static const struct _MHD_cstr_w_len realm_tk = _MHD_S_STR_W_LEN ("realm"); | ||
138 | static const struct _MHD_cstr_w_len uri_tk = _MHD_S_STR_W_LEN ("uri"); | ||
139 | static const struct _MHD_cstr_w_len qop_tk = _MHD_S_STR_W_LEN ("qop"); | ||
140 | static const struct _MHD_cstr_w_len cnonce_tk = _MHD_S_STR_W_LEN ("cnonce"); | ||
141 | static const struct _MHD_cstr_w_len nc_tk = _MHD_S_STR_W_LEN ("nc"); | ||
142 | static const struct _MHD_cstr_w_len userhash_tk = | ||
143 | _MHD_S_STR_W_LEN ("userhash"); | ||
144 | struct MHD_RqDAuthParam userhash; | ||
145 | struct dauth_token_param map[] = { | ||
146 | {&nonce_tk, &(pdauth->nonce)}, | ||
147 | {&opaque_tk, &(pdauth->opaque)}, | ||
148 | {&algorithm_tk, &(pdauth->algorithm)}, | ||
149 | {&response_tk, &(pdauth->response)}, | ||
150 | {&username_tk, &(pdauth->username)}, | ||
151 | {&username_ext_tk, &(pdauth->username_ext)}, | ||
152 | {&realm_tk, &(pdauth->realm)}, | ||
153 | {&uri_tk, &(pdauth->uri)}, | ||
154 | {&qop_tk, &(pdauth->qop)}, | ||
155 | {&cnonce_tk, &(pdauth->cnonce)}, | ||
156 | {&nc_tk, &(pdauth->nc)}, | ||
157 | {&userhash_tk, &userhash} | ||
158 | }; | ||
159 | size_t i; | ||
160 | size_t p; | ||
161 | |||
162 | memset (&userhash, 0, sizeof(userhash)); | ||
163 | i = 0; | ||
164 | |||
165 | /* Skip all whitespaces at start */ | ||
166 | while (i < str_len && (' ' == str[i] || '\t' == str[i])) | ||
167 | i++; | ||
168 | |||
169 | while (str_len > i) | ||
170 | { | ||
171 | size_t left; | ||
172 | mhd_assert (' ' != str[i]); | ||
173 | mhd_assert ('\t' != str[i]); | ||
174 | |||
175 | left = str_len - i; | ||
176 | for (p = 0; p < sizeof(map) / sizeof(map[0]); p++) | ||
177 | { | ||
178 | struct dauth_token_param *const aparam = map + p; | ||
179 | if ( (aparam->tk_name->len < left) && | ||
180 | MHD_str_equal_caseless_bin_n_ (str + i, aparam->tk_name->str, | ||
181 | aparam->tk_name->len) && | ||
182 | (('=' == str[i + aparam->tk_name->len]) || | ||
183 | (' ' == str[i + aparam->tk_name->len]) || | ||
184 | ('\t' == str[i + aparam->tk_name->len])) ) | ||
185 | { | ||
186 | size_t value_start; | ||
187 | size_t value_len; | ||
188 | bool quoted; /* Only mark as "quoted" if backslash-escape used */ | ||
189 | |||
190 | quoted = false; | ||
191 | i += aparam->tk_name->len; | ||
192 | /* Skip all whitespaces before '=' */ | ||
193 | while (str_len > i && (' ' == str[i] || '\t' == str[i])) | ||
194 | i++; | ||
195 | if ((i == str_len) || ('=' != str[i])) | ||
196 | return false; /* No equal sign, broken data */ | ||
197 | i++; | ||
198 | /* Skip all whitespaces after '=' */ | ||
199 | while (str_len > i && (' ' == str[i] || '\t' == str[i])) | ||
200 | i++; | ||
201 | if ((str_len > i) && ('"' == str[i])) | ||
202 | { /* Value is in quotation marks */ | ||
203 | i++; /* Advance after the opening quote */ | ||
204 | value_start = i; | ||
205 | while (str_len > i && '"' != str[i]) | ||
206 | { | ||
207 | if ('\\' == str[i]) | ||
208 | { | ||
209 | i++; | ||
210 | quoted = true; /* Have escaped chars */ | ||
211 | } | ||
212 | if (0 == str[i]) | ||
213 | return false; /* Binary zero in parameter value */ | ||
214 | i++; | ||
215 | } | ||
216 | if (str_len <= i) | ||
217 | return false; /* No closing quote */ | ||
218 | mhd_assert ('"' == str[i]); | ||
219 | value_len = i - value_start; | ||
220 | i++; /* Advance after the closing quote */ | ||
221 | } | ||
222 | else | ||
223 | { | ||
224 | value_start = i; | ||
225 | while (str_len > i && ',' != str[i] && | ||
226 | ' ' != str[i] && '\t' != str[i] && ';' != str[i]) | ||
227 | { | ||
228 | if (0 == str[i]) | ||
229 | return false; /* Binary zero in parameter value */ | ||
230 | i++; | ||
231 | } | ||
232 | value_len = i - value_start; | ||
233 | } | ||
234 | /* Skip all whitespaces after parameter value */ | ||
235 | while (str_len > i && (' ' == str[i] || '\t' == str[i])) | ||
236 | i++; | ||
237 | if ((str_len > i) && (',' != str[i])) | ||
238 | return false; /* Garbage after parameter value */ | ||
239 | |||
240 | /* Have valid parameter name and value */ | ||
241 | mhd_assert (! quoted || 0 != value_len); | ||
242 | if (1) | ||
243 | { | ||
244 | const struct _MHD_cstr_w_len val = {str + value_start, value_len}; | ||
245 | memcpy (&aparam->param->value, &val, sizeof(val)); | ||
246 | } | ||
247 | aparam->param->quoted = quoted; | ||
248 | |||
249 | break; /* Found matching parameter name */ | ||
250 | } | ||
251 | } | ||
252 | if (p == sizeof(map) / sizeof(map[0])) | ||
253 | { | ||
254 | /* No matching parameter name */ | ||
255 | while (str_len > i && ',' != str[i]) | ||
256 | { | ||
257 | if ('"' == str[i]) | ||
258 | { /* Skip quoted part */ | ||
259 | i++; /* Advance after the opening quote */ | ||
260 | while (str_len > i && '"' != str[i]) | ||
261 | { | ||
262 | if ('\\' == str[i]) | ||
263 | i++; /* Skip escaped char */ | ||
264 | i++; | ||
265 | } | ||
266 | if (str_len <= i) | ||
267 | return false; /* No closing quote */ | ||
268 | mhd_assert ('"' == str[i]); | ||
269 | } | ||
270 | i++; | ||
271 | } | ||
272 | } | ||
273 | mhd_assert (str_len == i || ',' == str[i]); | ||
274 | if (str_len > i) | ||
275 | i++; /* Advance after ',' */ | ||
276 | /* Skip all whitespaces before next parameter name */ | ||
277 | while (i < str_len && (' ' == str[i] || '\t' == str[i])) | ||
278 | i++; | ||
279 | } | ||
280 | |||
281 | /* Postprocess values */ | ||
282 | if ((NULL != userhash.value.str) && (0 != userhash.value.len)) | ||
283 | { | ||
284 | const char *param_str; | ||
285 | size_t param_len; | ||
286 | char buf[5 * 2]; /* 5 is the length of "false" (longer then "true") */ | ||
287 | if (! userhash.quoted) | ||
288 | { | ||
289 | param_str = userhash.value.str; | ||
290 | param_len = userhash.value.len; | ||
291 | } | ||
292 | else | ||
293 | { | ||
294 | if (sizeof(buf) / sizeof(buf[0]) >= userhash.value.len) | ||
295 | { | ||
296 | param_len = MHD_str_unquote (userhash.value.str, userhash.value.len, | ||
297 | buf); | ||
298 | param_str = buf; | ||
299 | } | ||
300 | else | ||
301 | param_len = 0; | ||
302 | } | ||
303 | if ((param_len == 4) && MHD_str_equal_caseless_bin_n_ (param_str, "true", | ||
304 | 4)) | ||
305 | pdauth->userhash = true; | ||
306 | else | ||
307 | pdauth->userhash = false; | ||
308 | } | ||
309 | else | ||
310 | pdauth->userhash = false; | ||
311 | |||
312 | return true; | ||
313 | } | ||
314 | |||
315 | |||
316 | #endif /* DAUTH_SUPPORT */ | ||
317 | |||
318 | |||
319 | /** | ||
320 | * Parse request "Authorization" header | ||
321 | * @param c the connection to process | ||
322 | * @return true if any supported Authorisation scheme were found, | ||
323 | * false if no "Authorization" header found, no supported scheme found, | ||
324 | * or an error occurred. | ||
325 | */ | ||
326 | _MHD_static_inline bool | ||
327 | parse_auth_rq_header_ (struct MHD_Connection *c) | ||
328 | { | ||
329 | const char *h; /**< The "Authorization" header */ | ||
330 | size_t h_len; | ||
331 | struct MHD_AuthRqHeader *rq_auth; | ||
332 | size_t i; | ||
333 | |||
334 | mhd_assert (NULL == c->rq_auth); | ||
335 | mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= c->state); | ||
336 | if (MHD_CONNECTION_HEADERS_PROCESSED > c->state) | ||
337 | return false; | ||
338 | |||
339 | if (MHD_NO == | ||
340 | MHD_lookup_connection_value_n (c, MHD_HEADER_KIND, | ||
341 | MHD_HTTP_HEADER_AUTHORIZATION, | ||
342 | MHD_STATICSTR_LEN_ ( \ | ||
343 | MHD_HTTP_HEADER_AUTHORIZATION), &h, | ||
344 | &h_len)) | ||
345 | { | ||
346 | rq_auth = (struct MHD_AuthRqHeader *) | ||
347 | MHD_connection_alloc_memory_ (c, | ||
348 | sizeof (struct MHD_AuthRqHeader)); | ||
349 | c->rq_auth = rq_auth; | ||
350 | if (NULL != rq_auth) | ||
351 | { | ||
352 | memset (rq_auth, 0, sizeof(struct MHD_AuthRqHeader)); | ||
353 | rq_auth->auth_type = MHD_AUTHTYPE_NONE; | ||
354 | } | ||
355 | return false; | ||
356 | } | ||
357 | |||
358 | rq_auth = NULL; | ||
359 | i = 0; | ||
360 | /* Skip the leading whitespace */ | ||
361 | while (i < h_len) | ||
362 | { | ||
363 | const char ch = h[i]; | ||
364 | if ((' ' != ch) && ('\t' != ch)) | ||
365 | break; | ||
366 | i++; | ||
367 | } | ||
368 | h += i; | ||
369 | h_len -= i; | ||
370 | |||
371 | #ifdef DAUTH_SUPPORT | ||
372 | if (1) | ||
373 | { | ||
374 | static const struct _MHD_cstr_w_len scheme_token = | ||
375 | _MHD_S_STR_W_LEN (_MHD_AUTH_DIGEST_BASE); | ||
376 | |||
377 | if ((scheme_token.len <= h_len) && | ||
378 | MHD_str_equal_caseless_bin_n_ (h, scheme_token.str, scheme_token.len)) | ||
379 | { | ||
380 | i = scheme_token.len; | ||
381 | /* RFC 7235 require only space after scheme token */ | ||
382 | if ( (h_len <= i) || | ||
383 | ((' ' == h[i]) || ('\t' == h[i])) ) /* Actually tab should NOT be allowed */ | ||
384 | { /* Matched Digest authorisation scheme */ | ||
385 | i++; /* Advance to the next char (even if it is beyond the end of the string) */ | ||
386 | |||
387 | rq_auth = (struct MHD_AuthRqHeader *) | ||
388 | MHD_connection_alloc_memory_ (c, | ||
389 | sizeof (struct MHD_AuthRqHeader) | ||
390 | + sizeof (struct MHD_RqDAuth)); | ||
391 | c->rq_auth = rq_auth; | ||
392 | if (NULL == rq_auth) | ||
393 | { | ||
394 | #ifdef HAVE_MESSAGES | ||
395 | MHD_DLOG (c->daemon, | ||
396 | _ ("Failed to allocate memory in connection pool to " \ | ||
397 | "process \"" MHD_HTTP_HEADER_AUTHORIZATION "\" " \ | ||
398 | "header.\n")); | ||
399 | #endif /* HAVE_MESSAGES */ | ||
400 | return false; | ||
401 | } | ||
402 | memset (rq_auth, 0, sizeof (struct MHD_AuthRqHeader) | ||
403 | + sizeof (struct MHD_RqDAuth)); | ||
404 | rq_auth->params.dauth = (struct MHD_RqDAuth *) (rq_auth + 1); | ||
405 | |||
406 | if (h_len > i) | ||
407 | { | ||
408 | if (! parse_dauth_params (h + i, h_len - i, rq_auth->params.dauth)) | ||
409 | { | ||
410 | rq_auth->auth_type = MHD_AUTHTYPE_INVALID; | ||
411 | return false; | ||
412 | } | ||
413 | } | ||
414 | |||
415 | rq_auth->auth_type = MHD_AUTHTYPE_DIGEST; | ||
416 | return true; | ||
417 | } | ||
418 | } | ||
419 | } | ||
420 | #endif /* DAUTH_SUPPORT */ | ||
421 | #ifdef BAUTH_SUPPORT | ||
422 | if (1) | ||
423 | { | ||
424 | static const struct _MHD_cstr_w_len scheme_token = | ||
425 | _MHD_S_STR_W_LEN (_MHD_AUTH_BASIC_BASE); | ||
426 | |||
427 | if ((scheme_token.len <= h_len) && | ||
428 | MHD_str_equal_caseless_bin_n_ (h, scheme_token.str, scheme_token.len)) | ||
429 | { | ||
430 | i = scheme_token.len; | ||
431 | /* RFC 7235 require only space after scheme token */ | ||
432 | if ( (h_len <= i) || | ||
433 | ((' ' == h[i]) || ('\t' == h[i])) ) /* Actually tab should NOT be allowed */ | ||
434 | { /* Matched Basic authorisation scheme */ | ||
435 | i++; /* Advance to the next char (even if it is beyond the end of the string) */ | ||
436 | |||
437 | rq_auth = (struct MHD_AuthRqHeader *) | ||
438 | MHD_connection_alloc_memory_ (c, | ||
439 | sizeof (struct MHD_AuthRqHeader) | ||
440 | + sizeof (struct MHD_RqBAuth)); | ||
441 | c->rq_auth = rq_auth; | ||
442 | if (NULL == rq_auth) | ||
443 | { | ||
444 | #ifdef HAVE_MESSAGES | ||
445 | MHD_DLOG (c->daemon, | ||
446 | _ ("Failed to allocate memory in connection pool to " \ | ||
447 | "process \"" MHD_HTTP_HEADER_AUTHORIZATION "\" " \ | ||
448 | "header.\n")); | ||
449 | #endif /* HAVE_MESSAGES */ | ||
450 | return false; | ||
451 | } | ||
452 | memset (rq_auth, 0, sizeof (struct MHD_AuthRqHeader) | ||
453 | + sizeof (struct MHD_RqBAuth)); | ||
454 | rq_auth->params.bauth = (struct MHD_RqBAuth *) (rq_auth + 1); | ||
455 | |||
456 | if (h_len > i) | ||
457 | { | ||
458 | if (! parse_bauth_params (h + i, h_len - i, rq_auth->params.bauth)) | ||
459 | { | ||
460 | rq_auth->auth_type = MHD_AUTHTYPE_INVALID; | ||
461 | return false; | ||
462 | } | ||
463 | } | ||
464 | |||
465 | rq_auth->auth_type = MHD_AUTHTYPE_BASIC; | ||
466 | return true; | ||
467 | } | ||
468 | } | ||
469 | } | ||
470 | #endif /* BAUTH_SUPPORT */ | ||
471 | |||
472 | if (NULL == rq_auth) | ||
473 | rq_auth = (struct MHD_AuthRqHeader *) | ||
474 | MHD_connection_alloc_memory_ (c, | ||
475 | sizeof (struct MHD_AuthRqHeader)); | ||
476 | c->rq_auth = rq_auth; | ||
477 | if (NULL != rq_auth) | ||
478 | { | ||
479 | memset (rq_auth, 0, sizeof(struct MHD_AuthRqHeader)); | ||
480 | rq_auth->auth_type = MHD_AUTHTYPE_INVALID; | ||
481 | } | ||
482 | return false; | ||
483 | } | ||
484 | |||
485 | |||
486 | /** | ||
487 | * Return request's Authentication type and parameters. | ||
488 | * | ||
489 | * Function return result of parsing of the request's "Authorization" header or | ||
490 | * returns cached parsing result if the header was already parsed for | ||
491 | * the current request. | ||
492 | * @param connection the connection to process | ||
493 | * @return the pointer to structure with Authentication type and parameters, | ||
494 | * NULL if no memory in memory pool or if called too early (before | ||
495 | * header has been received). | ||
496 | */ | ||
497 | const struct MHD_AuthRqHeader * | ||
498 | MHD_get_auth_rq_params_ (struct MHD_Connection *connection) | ||
499 | { | ||
500 | mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= connection->state); | ||
501 | |||
502 | if (NULL != connection->rq_auth) | ||
503 | return connection->rq_auth; | ||
504 | |||
505 | if (MHD_CONNECTION_HEADERS_PROCESSED > connection->state) | ||
506 | return NULL; | ||
507 | |||
508 | parse_auth_rq_header_ (connection); | ||
509 | |||
510 | return connection->rq_auth; | ||
511 | } | ||