Author: brane Date: Wed Jun 25 20:20:05 2025 New Revision: 1926725 URL: http://svn.apache.org/viewvc?rev=1926725&view=rev Log: On the user-defined-authn branch: Parse authentication parameters so that users don't have to bother with that (and most likely get it wrong, too).
* CMakeLists.txt (SOURCES): Add src/syntax.c. * serf.h: Include apr_hash.h (serf_authn_get_realm_func_t, serf_authn_handle_func_t): Change authn_parameters to a dictionary. Update the docstrings. * serf_private.h (serf__parse_authn_parameters, serf__tolower_inplace, serf__tolower): Declare prototypes. * auth/auth.c (store_header_in_dict): Use serf__tolower_inplace() to fold the scheme. (serf_authn_register_scheme, serf_authn_unregister_scheme): Use serf__tolower() to make the scheme key. * auth/auth_user_defined.c (serf__authn_user__handle): Parse the incoming authentication parameters. * src/genctype.py: New; stolen from Subversion. * src/syntax.c: New; implements serf__parse_authn_parameters(), serf__tolower_inplace() and serf__tolower(). * test/test_auth.c (user_authn_get_realm, user_authn_handle, user_authn_credentials): Update test callbacks. (user_authentication): Accept a 'scope' instead of a 'tweak'. * test/test_internal.c (struct expected_attrs, parse_parameters): New; common test implementation. (test_parse_parameters, test_parse_bad_parameters, test_parse_repeated_parameters, test_parameter_case_folding): New test cases. (test_internal): Register test cases. Added: serf/branches/user-defined-authn/src/genctype.py (with props) serf/branches/user-defined-authn/src/syntax.c (with props) Modified: serf/branches/user-defined-authn/CMakeLists.txt serf/branches/user-defined-authn/auth/auth.c serf/branches/user-defined-authn/auth/auth_user_defined.c serf/branches/user-defined-authn/serf.h serf/branches/user-defined-authn/serf_private.h serf/branches/user-defined-authn/test/test_auth.c serf/branches/user-defined-authn/test/test_internal.c serf/branches/user-defined-authn/test/test_serf.h Modified: serf/branches/user-defined-authn/CMakeLists.txt URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/CMakeLists.txt?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/CMakeLists.txt (original) +++ serf/branches/user-defined-authn/CMakeLists.txt Wed Jun 25 20:20:05 2025 @@ -137,6 +137,7 @@ list(APPEND SOURCES "src/outgoing_request.c" "src/pump.c" "src/ssltunnel.c" + "src/syntax.c" "auth/auth.c" "auth/auth_basic.c" "auth/auth_digest.c" Modified: serf/branches/user-defined-authn/auth/auth.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth.c?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth.c (original) +++ serf/branches/user-defined-authn/auth/auth.c Wed Jun 25 20:20:05 2025 @@ -264,9 +264,7 @@ static int store_header_in_dict(void *ba auth_name = apr_pstrmemdup(ab->pool, header, strlen(header)); /* Convert scheme name to lower case to enable case insensitive matching. */ - for (c = auth_name; *c != '\0'; c++) - *c = (char)apr_tolower(*c); - + serf__tolower_inplace(auth_name); apr_hash_set(ab->hdrs, auth_name, APR_HASH_KEY_STRING, apr_pstrdup(ab->pool, header)); @@ -673,7 +671,6 @@ apr_status_t serf_authn_register_scheme( apr_status_t status; unsigned int scheme_type; const char *key; - char *cp; int index; serf__log(LOGLVL_INFO, LOGCOMP_AUTHN, __FILE__, config, @@ -683,11 +680,8 @@ apr_status_t serf_authn_register_scheme( authn_scheme = apr_palloc(result_pool, sizeof(*authn_scheme)); /* Generate a lower-case key for the scheme. */ - key = cp = apr_pstrdup(result_pool, name); - while (*cp) { - *cp = apr_tolower(*cp); - ++cp; - } + key = serf__tolower(name, result_pool); + authn_scheme->name = apr_pstrdup(result_pool, name); authn_scheme->key = key; /* user_scheme->type = ?; Will be updated later, under lock. */ @@ -780,18 +774,13 @@ apr_status_t serf_authn_unregister_schem apr_status_t lock_status; apr_status_t status; const char *key; - char *cp; int index; serf__log(LOGLVL_INFO, LOGCOMP_AUTHN, __FILE__, config, "Unregistering user-defined scheme %s", name); /* Generate a lower-case key for the scheme. */ - key = cp = apr_pstrdup(scratch_pool, name); - while (*cp) { - *cp = apr_tolower(*cp); - ++cp; - } + key = serf__tolower(name, scratch_pool); lock_status = lock_authn_schemes(config); if (lock_status) { Modified: serf/branches/user-defined-authn/auth/auth_user_defined.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/auth/auth_user_defined.c?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/auth/auth_user_defined.c (original) +++ serf/branches/user-defined-authn/auth/auth_user_defined.c Wed Jun 25 20:20:05 2025 @@ -158,6 +158,7 @@ serf__authn_user__handle(const serf__aut struct authn_baton_wrapper *authn_baton; char *username, *password; apr_pool_t *scratch_pool; + apr_hash_t *auth_param; apr_status_t status; status = validate_handler(conn->config, scheme, code, "handle-auth", @@ -198,12 +199,13 @@ serf__authn_user__handle(const serf__aut apr_pool_create(&scratch_pool, pool); status = APR_SUCCESS; + auth_param = serf__parse_authn_parameters(auth_attr, scratch_pool); if (scheme->user_flags & SERF_AUTHN_FLAG_CREDS) { const char *realm_name; status = scheme->user_get_realm_func(&realm_name, scheme->user_baton, authn_baton->user_authn_baton, - auth_hdr, auth_attr, + auth_hdr, auth_param, scratch_pool, scratch_pool); if (!status) { const char *const realm = serf__construct_realm( @@ -222,7 +224,7 @@ serf__authn_user__handle(const serf__aut status = scheme->user_handle_func(scheme->user_baton, authn_baton->user_authn_baton, code, - auth_hdr, auth_attr, + auth_hdr, auth_param, SERF__HEADER_FROM_CODE(code), username, password, request, response, Modified: serf/branches/user-defined-authn/serf.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/serf.h?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/serf.h (original) +++ serf/branches/user-defined-authn/serf.h Wed Jun 25 20:20:05 2025 @@ -30,6 +30,7 @@ #include <apr_errno.h> #include <apr_allocator.h> #include <apr_pools.h> +#include <apr_hash.h> #include <apr_network_io.h> #include <apr_time.h> #include <apr_poll.h> @@ -1029,11 +1030,12 @@ typedef apr_status_t * * @a authn_baton is the pointer returned from the init-connection callback. * - * @a authn_header and @a authn_attributes contain the value of the - * authentication header (WWW-Authenticate or Proxy-Authenticate) recevied in - * a server response. @a authn_header contains the scheme name, i.e., "scheme - * <atributes>", whereas @a authn_attributes contains only the attributes - * without the scheme name. + * @a authn_header and @a authn_parameters come from the authentication header + * (WWW-Authenticate or Proxy-Authenticate) recevied in a server response. + * @a authn_header is the header value, i.e., "scheme <parameters>"; + * @a authn_parameters is a dictionary of the authentication parameters + * and their values, e.g., `realm="Wonderland"`. The keys are always folded + * to lower case. * * If the scheme flag @a SERF_AUTHN_FLAG_PIPE is *not* set, pipelining will be * disabled on the connection after this callback succeeds. @@ -1047,7 +1049,7 @@ typedef apr_status_t void *baton, void *authn_baton, const char *authn_header, - const char *authn_attributes, + apr_hash_t *authn_parameters, apr_pool_t *result_pool, apr_pool_t *scratch_pool); @@ -1057,7 +1059,7 @@ typedef apr_status_t * Called after the init-conn function has succeeded to prepare (cache) the * credentials for this connection, usually in @a auth_baton. * - * @a baton, @a authn_baton, @a authn_header and @a authn_attributers have the + * @a baton, @a authn_baton, @a authn_header and @a authn_parameters have the * same meaning as in the get-realm function; @a code is the same as in the * init-conn function. * @@ -1080,7 +1082,7 @@ typedef apr_status_t void *authn_baton, int code, const char *authn_header, - const char *authn_attributes, + apr_hash_t *authn_parameters, const char *response_header, const char *username, const char *password, Modified: serf/branches/user-defined-authn/serf_private.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/serf_private.h?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/serf_private.h (original) +++ serf/branches/user-defined-authn/serf_private.h Wed Jun 25 20:20:05 2025 @@ -747,6 +747,23 @@ apr_status_t serf__handle_auth_response( able to cleanup stale objects from time to time. */ serf__authn_info_t *serf__get_authn_info_for_server(serf_connection_t *conn); + +/* Parse authentication scheme parameters from a WWW-Authenticate or + Proxy-Authenticate header. Splits the comma-separated token=value + or token="quoted \" value" pairs into a dictionary. + + The keys in the dictionary will be folded to lowercase. + + See: https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6 */ +apr_hash_t *serf__parse_authn_parameters(const char *attrs, apr_pool_t *pool); + +/* Fold ASCII uppercase letters to lowercase, in place, using the same + case-folding table as serf__parse_authn_attributes() does for keys.*/ +void serf__tolower_inplace(char *dst); + +/* Like serf__tolower_inplace, but allocates a new string from the pool. */ +const char *serf__tolower(const char *src, apr_pool_t *pool); + /* from context.c */ void serf__context_progress_delta(void *progress_baton, apr_off_t read, apr_off_t written); Added: serf/branches/user-defined-authn/src/genctype.py URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/src/genctype.py?rev=1926725&view=auto ============================================================================== --- serf/branches/user-defined-authn/src/genctype.py (added) +++ serf/branches/user-defined-authn/src/genctype.py Wed Jun 25 20:20:05 2025 @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# ====================================================================== +# This file was stolen and slightly modified from Subversion @ r882257. + +"""getctype.py - Generate the character classification table. +""" + +# Table of ASCII character names +names = ('nul', 'soh', 'stx', 'etx', 'eot', 'enq', 'ack', 'bel', + 'bs', 'ht', 'nl', 'vt', 'np', 'cr', 'so', 'si', + 'dle', 'dc1', 'dc2', 'dc3', 'dc4', 'nak', 'syn', 'etb', + 'can', 'em', 'sub', 'esc', 'fs', 'gs', 'rs', 'us', + 'sp', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', 'del') + +# All whitespace characters: +# horizontal tab, vertical tab, new line, form feed, carriage return, space +whitespace = frozenset((9, 10, 11, 12, 13, 32)) + +# Bytes not valid in UTF-8 sequences +utf8_invalid = frozenset((0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF)) + +# HTTP classes: whitespace and token punctuation +http_space = (9, 32) +http_token_punct = frozenset(( + 33, 35, 36, 37, 38, 39, 42, 43, 45, 46, 94, 95, 96, 124, 126)) +# ! # $ % & ' * + - . ^ _ ` | ~ + +print(' /* **** DO NOT EDIT! ****') +print(' This table was generated by genctype.py, make changes there. */') + +for c in range(256): + bits = [] + + # Ascii subrange + if c < 128: + bits.append('CT_ASCII') + + if len(names[c]) == 1: + name = names[c].center(3) + else: + name = names[c].ljust(3) + + # Control characters + if c < 32 or c == 127: + bits.append('CT_CNTRL') + + # Whitespace characters + if c in whitespace: + bits.append('CT_SPACE') + if c in http_space: + bits.append('CT_HTTP_SPACE') + + # Punctuation marks + if c >= 33 and c < 48 \ + or c >= 58 and c < 65 \ + or c >= 91 and c < 97 \ + or c >= 123 and c < 127: + bits.append('CT_PUNCT') + if c in http_token_punct: + bits.append('CT_HTTP_TOKEN') + + # Decimal digits + elif c >= 48 and c < 58: + bits.append('CT_DIGIT') + bits.append('CT_HTTP_TOKEN') + + # Uppercase letters + elif c >= 65 and c < 91: + bits.append('CT_UPPER') + bits.append('CT_HTTP_TOKEN') + # Hexadecimal digits + if c <= 70: + bits.append('CT_XALPHA') + + # Lowercase letters + elif c >= 97 and c < 123: + bits.append('CT_LOWER') + bits.append('CT_HTTP_TOKEN') + # Hexadecimal digits + if c <= 102: + bits.append('CT_XALPHA') + + # UTF-8 multibyte sequences + else: + name = hex(c)[1:] + + # Lead bytes (start of sequence) + if c > 0xC0 and c < 0xFE and c not in utf8_invalid: + bits.append('CT_UTF8_LEAD') + + # Continuation bytes + elif (c & 0xC0) == 0x80: + bits.append('CT_UTF8_CONT') + + if len(bits) == 0: + flags = '0' + else: + flags = ' | '.join(bits) + print(' /* %s */ %s,' % (name, flags)) Propchange: serf/branches/user-defined-authn/src/genctype.py ------------------------------------------------------------------------------ svn:eol-style = native Propchange: serf/branches/user-defined-authn/src/genctype.py ------------------------------------------------------------------------------ svn:executable = * Added: serf/branches/user-defined-authn/src/syntax.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/src/syntax.c?rev=1926725&view=auto ============================================================================== --- serf/branches/user-defined-authn/src/syntax.c (added) +++ serf/branches/user-defined-authn/src/syntax.c Wed Jun 25 20:20:05 2025 @@ -0,0 +1,506 @@ +/* ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "serf.h" +#include "serf_private.h" + + +/* Character classes */ +#define CT_ASCII 0x0001 +#define CT_CNTRL 0x0002 +#define CT_SPACE 0x0004 +#define CT_PUNCT 0x0008 +#define CT_DIGIT 0x0010 +#define CT_UPPER 0x0020 +#define CT_LOWER 0x0040 +#define CT_XALPHA 0x0080 +#define CT_UTF8_CONT 0x0100 +#define CT_UTF8_LEAD 0x0200 + +/* Only space and the horizontal tab are treated as whitespace. */ +#define CT_HTTP_SPACE 0x1000 + +/* Characters that are allowed in tokens, accorting to RFC 9110, are the + ASCII digits, lowercase and uppercase letters, and the following + punctiation marks: + + ! # $ % & ' * + - . ^ _ ` | ~ + + Whitespace and all other printable ASCII characters are delimiters. */ +#define CT_HTTP_TOKEN 0x2000 + + +/* ASCII + UTF-8 character table, stolen wholesale from Subversion. */ +static const unsigned short char_table[256] = +{ + /* **** DO NOT EDIT! **** + This table was generated by genctype.py, make changes there. */ + /* nul */ CT_ASCII | CT_CNTRL, + /* soh */ CT_ASCII | CT_CNTRL, + /* stx */ CT_ASCII | CT_CNTRL, + /* etx */ CT_ASCII | CT_CNTRL, + /* eot */ CT_ASCII | CT_CNTRL, + /* enq */ CT_ASCII | CT_CNTRL, + /* ack */ CT_ASCII | CT_CNTRL, + /* bel */ CT_ASCII | CT_CNTRL, + /* bs */ CT_ASCII | CT_CNTRL, + /* ht */ CT_ASCII | CT_CNTRL | CT_SPACE | CT_HTTP_SPACE, + /* nl */ CT_ASCII | CT_CNTRL | CT_SPACE, + /* vt */ CT_ASCII | CT_CNTRL | CT_SPACE, + /* np */ CT_ASCII | CT_CNTRL | CT_SPACE, + /* cr */ CT_ASCII | CT_CNTRL | CT_SPACE, + /* so */ CT_ASCII | CT_CNTRL, + /* si */ CT_ASCII | CT_CNTRL, + /* dle */ CT_ASCII | CT_CNTRL, + /* dc1 */ CT_ASCII | CT_CNTRL, + /* dc2 */ CT_ASCII | CT_CNTRL, + /* dc3 */ CT_ASCII | CT_CNTRL, + /* dc4 */ CT_ASCII | CT_CNTRL, + /* nak */ CT_ASCII | CT_CNTRL, + /* syn */ CT_ASCII | CT_CNTRL, + /* etb */ CT_ASCII | CT_CNTRL, + /* can */ CT_ASCII | CT_CNTRL, + /* em */ CT_ASCII | CT_CNTRL, + /* sub */ CT_ASCII | CT_CNTRL, + /* esc */ CT_ASCII | CT_CNTRL, + /* fs */ CT_ASCII | CT_CNTRL, + /* gs */ CT_ASCII | CT_CNTRL, + /* rs */ CT_ASCII | CT_CNTRL, + /* us */ CT_ASCII | CT_CNTRL, + /* sp */ CT_ASCII | CT_SPACE | CT_HTTP_SPACE, + /* ! */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* " */ CT_ASCII | CT_PUNCT, + /* # */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* $ */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* % */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* & */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* ' */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* ( */ CT_ASCII | CT_PUNCT, + /* ) */ CT_ASCII | CT_PUNCT, + /* * */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* + */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* , */ CT_ASCII | CT_PUNCT, + /* - */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* . */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* / */ CT_ASCII | CT_PUNCT, + /* 0 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 1 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 2 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 3 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 4 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 5 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 6 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 7 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 8 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* 9 */ CT_ASCII | CT_DIGIT | CT_HTTP_TOKEN, + /* : */ CT_ASCII | CT_PUNCT, + /* ; */ CT_ASCII | CT_PUNCT, + /* < */ CT_ASCII | CT_PUNCT, + /* = */ CT_ASCII | CT_PUNCT, + /* > */ CT_ASCII | CT_PUNCT, + /* ? */ CT_ASCII | CT_PUNCT, + /* @ */ CT_ASCII | CT_PUNCT, + /* A */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN | CT_XALPHA, + /* B */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN | CT_XALPHA, + /* C */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN | CT_XALPHA, + /* D */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN | CT_XALPHA, + /* E */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN | CT_XALPHA, + /* F */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN | CT_XALPHA, + /* G */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* H */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* I */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* J */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* K */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* L */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* M */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* N */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* O */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* P */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* Q */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* R */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* S */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* T */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* U */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* V */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* W */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* X */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* Y */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* Z */ CT_ASCII | CT_UPPER | CT_HTTP_TOKEN, + /* [ */ CT_ASCII | CT_PUNCT, + /* \ */ CT_ASCII | CT_PUNCT, + /* ] */ CT_ASCII | CT_PUNCT, + /* ^ */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* _ */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* ` */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* a */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN | CT_XALPHA, + /* b */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN | CT_XALPHA, + /* c */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN | CT_XALPHA, + /* d */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN | CT_XALPHA, + /* e */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN | CT_XALPHA, + /* f */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN | CT_XALPHA, + /* g */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* h */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* i */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* j */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* k */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* l */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* m */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* n */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* o */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* p */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* q */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* r */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* s */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* t */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* u */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* v */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* w */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* x */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* y */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* z */ CT_ASCII | CT_LOWER | CT_HTTP_TOKEN, + /* { */ CT_ASCII | CT_PUNCT, + /* | */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* } */ CT_ASCII | CT_PUNCT, + /* ~ */ CT_ASCII | CT_PUNCT | CT_HTTP_TOKEN, + /* del */ CT_ASCII | CT_CNTRL, + /* x80 */ CT_UTF8_CONT, + /* x81 */ CT_UTF8_CONT, + /* x82 */ CT_UTF8_CONT, + /* x83 */ CT_UTF8_CONT, + /* x84 */ CT_UTF8_CONT, + /* x85 */ CT_UTF8_CONT, + /* x86 */ CT_UTF8_CONT, + /* x87 */ CT_UTF8_CONT, + /* x88 */ CT_UTF8_CONT, + /* x89 */ CT_UTF8_CONT, + /* x8a */ CT_UTF8_CONT, + /* x8b */ CT_UTF8_CONT, + /* x8c */ CT_UTF8_CONT, + /* x8d */ CT_UTF8_CONT, + /* x8e */ CT_UTF8_CONT, + /* x8f */ CT_UTF8_CONT, + /* x90 */ CT_UTF8_CONT, + /* x91 */ CT_UTF8_CONT, + /* x92 */ CT_UTF8_CONT, + /* x93 */ CT_UTF8_CONT, + /* x94 */ CT_UTF8_CONT, + /* x95 */ CT_UTF8_CONT, + /* x96 */ CT_UTF8_CONT, + /* x97 */ CT_UTF8_CONT, + /* x98 */ CT_UTF8_CONT, + /* x99 */ CT_UTF8_CONT, + /* x9a */ CT_UTF8_CONT, + /* x9b */ CT_UTF8_CONT, + /* x9c */ CT_UTF8_CONT, + /* x9d */ CT_UTF8_CONT, + /* x9e */ CT_UTF8_CONT, + /* x9f */ CT_UTF8_CONT, + /* xa0 */ CT_UTF8_CONT, + /* xa1 */ CT_UTF8_CONT, + /* xa2 */ CT_UTF8_CONT, + /* xa3 */ CT_UTF8_CONT, + /* xa4 */ CT_UTF8_CONT, + /* xa5 */ CT_UTF8_CONT, + /* xa6 */ CT_UTF8_CONT, + /* xa7 */ CT_UTF8_CONT, + /* xa8 */ CT_UTF8_CONT, + /* xa9 */ CT_UTF8_CONT, + /* xaa */ CT_UTF8_CONT, + /* xab */ CT_UTF8_CONT, + /* xac */ CT_UTF8_CONT, + /* xad */ CT_UTF8_CONT, + /* xae */ CT_UTF8_CONT, + /* xaf */ CT_UTF8_CONT, + /* xb0 */ CT_UTF8_CONT, + /* xb1 */ CT_UTF8_CONT, + /* xb2 */ CT_UTF8_CONT, + /* xb3 */ CT_UTF8_CONT, + /* xb4 */ CT_UTF8_CONT, + /* xb5 */ CT_UTF8_CONT, + /* xb6 */ CT_UTF8_CONT, + /* xb7 */ CT_UTF8_CONT, + /* xb8 */ CT_UTF8_CONT, + /* xb9 */ CT_UTF8_CONT, + /* xba */ CT_UTF8_CONT, + /* xbb */ CT_UTF8_CONT, + /* xbc */ CT_UTF8_CONT, + /* xbd */ CT_UTF8_CONT, + /* xbe */ CT_UTF8_CONT, + /* xbf */ CT_UTF8_CONT, + /* xc0 */ 0, + /* xc1 */ CT_UTF8_LEAD, + /* xc2 */ CT_UTF8_LEAD, + /* xc3 */ CT_UTF8_LEAD, + /* xc4 */ CT_UTF8_LEAD, + /* xc5 */ CT_UTF8_LEAD, + /* xc6 */ CT_UTF8_LEAD, + /* xc7 */ CT_UTF8_LEAD, + /* xc8 */ CT_UTF8_LEAD, + /* xc9 */ CT_UTF8_LEAD, + /* xca */ CT_UTF8_LEAD, + /* xcb */ CT_UTF8_LEAD, + /* xcc */ CT_UTF8_LEAD, + /* xcd */ CT_UTF8_LEAD, + /* xce */ CT_UTF8_LEAD, + /* xcf */ CT_UTF8_LEAD, + /* xd0 */ CT_UTF8_LEAD, + /* xd1 */ CT_UTF8_LEAD, + /* xd2 */ CT_UTF8_LEAD, + /* xd3 */ CT_UTF8_LEAD, + /* xd4 */ CT_UTF8_LEAD, + /* xd5 */ CT_UTF8_LEAD, + /* xd6 */ CT_UTF8_LEAD, + /* xd7 */ CT_UTF8_LEAD, + /* xd8 */ CT_UTF8_LEAD, + /* xd9 */ CT_UTF8_LEAD, + /* xda */ CT_UTF8_LEAD, + /* xdb */ CT_UTF8_LEAD, + /* xdc */ CT_UTF8_LEAD, + /* xdd */ CT_UTF8_LEAD, + /* xde */ CT_UTF8_LEAD, + /* xdf */ CT_UTF8_LEAD, + /* xe0 */ 0, + /* xe1 */ CT_UTF8_LEAD, + /* xe2 */ CT_UTF8_LEAD, + /* xe3 */ CT_UTF8_LEAD, + /* xe4 */ CT_UTF8_LEAD, + /* xe5 */ CT_UTF8_LEAD, + /* xe6 */ CT_UTF8_LEAD, + /* xe7 */ CT_UTF8_LEAD, + /* xe8 */ CT_UTF8_LEAD, + /* xe9 */ CT_UTF8_LEAD, + /* xea */ CT_UTF8_LEAD, + /* xeb */ CT_UTF8_LEAD, + /* xec */ CT_UTF8_LEAD, + /* xed */ CT_UTF8_LEAD, + /* xee */ CT_UTF8_LEAD, + /* xef */ CT_UTF8_LEAD, + /* xf0 */ 0, + /* xf1 */ CT_UTF8_LEAD, + /* xf2 */ CT_UTF8_LEAD, + /* xf3 */ CT_UTF8_LEAD, + /* xf4 */ CT_UTF8_LEAD, + /* xf5 */ CT_UTF8_LEAD, + /* xf6 */ CT_UTF8_LEAD, + /* xf7 */ CT_UTF8_LEAD, + /* xf8 */ 0, + /* xf9 */ CT_UTF8_LEAD, + /* xfa */ CT_UTF8_LEAD, + /* xfb */ CT_UTF8_LEAD, + /* xfc */ 0, + /* xfd */ CT_UTF8_LEAD, + /* xfe */ 0, + /* xff */ 0 +}; + +/* Character class lookup */ +static APR_INLINE int ct_isspace(char c) +{ + return char_table[(unsigned char)c] & CT_HTTP_SPACE; +} + +static APR_INLINE int ct_istoken(char c) +{ + return char_table[(unsigned char)c] & CT_HTTP_TOKEN; +} + + +/* Uppercase-lowercase table, also stolen wholesale from Subversion. + Folds uppercase ASCII letters into lowercase and ignores the rest. */ +static const unsigned char casefold_table[256] = + { + /* Identity, except {65:90} => {97:122} */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122, 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 + }; + +/* Fold ASCII to lowercase. */ +static APR_INLINE char ct_tolower(char c) +{ + return (char) casefold_table[(unsigned char)c]; +} + + +static const char *skip_space(const char *src) +{ + while (ct_isspace(*src)) + ++src; + return src; +} + +#if 0 +static const char *skip_comment(const char *src) +{ + if (*src != '(') + return NULL; + + ++src; + while (*src && *src != ')') + { + if (*src == '(') { + src = skip_comment(src); + if (!src) + return NULL; + } + else { + if (*src == '\\') + ++src; /* Skip escape from quoted-pair */ + if (*src) + ++src; + } + } + + if (!*src) + return NULL; + return src + 1; +} +#endif + +static const char *copy_quoted_string(char **dst, const char *src) +{ + if (*src != '"') + return NULL; + + ++src; + while (*src && *src != '"') + { + if (*src == '\\') + ++src; /* Skip escape from quoted-pair */ + if (*src) + *((*dst)++) = *src++; + } + + if (!*src) + return NULL; + return src + 1; +} + +static const char *copy_token_key(char **dst, const char *src) +{ + if (!ct_istoken(*src)) + return NULL; + + do { *((*dst)++) = ct_tolower(*src++); } while (ct_istoken(*src)); + return src; +} + +static const char *copy_token(char **dst, const char *src) +{ + if (!ct_istoken(*src)) + return NULL; + + do { *((*dst)++) = *src++; } while (ct_istoken(*src)); + return src; +} + + +apr_hash_t *serf__parse_authn_parameters(const char *attrs, apr_pool_t *pool) +{ + apr_hash_t *dict = apr_hash_make(pool); + char *dst = apr_palloc(pool, strlen(attrs)); + const char *src = attrs; + bool want_comma = false; + + while (src && *src) + { + const char *key; + apr_ssize_t keylen; + const char *value; + + /* Skip spaces and the separator. */ + src = skip_space(src); + if (want_comma) { + if (*src != ',') + break; + ++src; + if (*src) + src = skip_space(src); + } + if (!*src) + break; + + /* Pars the key, a lower-cased token. */ + key = dst; + src = copy_token_key(&dst, src); + if (!src || key == dst || *src != '=') + break; + keylen = dst - key; + *dst++ = '\0'; + + /* Parse the value, either a token or a quoted string. */ + ++src; + value = dst; + if (*src == '"') + src = copy_quoted_string(&dst, src); + else if (ct_istoken(*src)) + src = copy_token(&dst, src); + if (!src || value == dst) + break; + *dst++ = '\0'; + + /* Must be at the end of the string or at a valid separator. */ + if (*src && *src != ',' && !ct_isspace(*src)) + break; + + /* Put the new key/value pair into the dict. */ + apr_hash_set(dict, key, keylen, value); + want_comma = true; + } + + return dict; +} + + +void serf__tolower_inplace(char *dst) +{ + while(*dst) { + *dst = ct_tolower(*dst); + ++dst; + } +} + + +const char *serf__tolower(const char *src, apr_pool_t *pool) +{ + const size_t len = strlen(src) + 1; + size_t i; + + char *dst = apr_palloc(pool, len); + for (i = 0; i < len; ++i) + dst[i] = ct_tolower(src[i]); /* The NUL byte is copied, too. */ + return dst; +} Propchange: serf/branches/user-defined-authn/src/syntax.c ------------------------------------------------------------------------------ svn:eol-style = native Modified: serf/branches/user-defined-authn/test/test_auth.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/test/test_auth.c?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/test/test_auth.c (original) +++ serf/branches/user-defined-authn/test/test_auth.c Wed Jun 25 20:20:05 2025 @@ -707,33 +707,26 @@ static apr_status_t user_authn_get_realm void *baton, void *authn_baton, const char *authn_header, - const char *authn_attributes, + apr_hash_t *authn_parameters, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - const char *end; - apr_size_t length; + const char *name; USER_AUTHN_COUNT(baton, get_realm); test__log(TEST_VERBOSE, __FILE__, - "user_authn_get_realm, header %s, attrs %s\n", - authn_header, authn_attributes); + "user_authn_get_realm, header %s\n", authn_header); if (strncasecmp(authn_header, "TweedleDee", 10)) { *realm_name = "Wonderland"; return APR_SUCCESS; } - if (strncasecmp(authn_attributes, "scope=", 6)) + name = apr_hash_get(authn_parameters, "scope", 5); + if (!name) return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE; - authn_attributes += 6; - if ((end = strchr(authn_attributes, ' '))) { - length = end - authn_attributes; - } else { - length = strlen(authn_attributes); - } - *realm_name = apr_pstrndup(result_pool, authn_attributes, length); + *realm_name = apr_pstrdup(result_pool, name); return APR_SUCCESS; } @@ -741,7 +734,7 @@ static apr_status_t user_authn_handle(vo void *authn_baton, int code, const char *authn_header, - const char *authn_attributes, + apr_hash_t *authn_parameters, const char *response_header, const char *username, const char *password, @@ -821,15 +814,17 @@ user_authn_credentials(char **username, if (strncmp(user_authn_prefix, authn_type, strlen(user_authn_prefix)) != 0) return REPORT_TEST_SUITE_ERROR(); - realm_name = realm + strlen(realm) - strlen("Alice"); - if (strcmp("Alice", realm_name) != 0) + realm_name = strrchr(realm, ' '); + if (!realm_name + || (strcmp(" Alice", realm_name) + && strcmp(" Cheshire", realm_name))) return REPORT_TEST_SUITE_ERROR(); *username = NULL; *password = apr_pstrdup(pool, authn_type); test__log(TEST_VERBOSE, __FILE__, "user credentials, realm %s, password %s\n", - realm, *password); + realm_name + 1, *password); return APR_SUCCESS; } @@ -837,7 +832,7 @@ user_authn_credentials(char **username, static void user_authentication(CuTest *tc, int close_conn, int use_pipelining, - const char *tweak) + const char *scope) { test_baton_t *tb = tc->testBaton; handler_baton_t handler_ctx; @@ -889,9 +884,9 @@ static void user_authentication(CuTest * serf_config_credentials_callback(tb->context, user_authn_credentials); /* Adjust the authentication header. */ - if (tweak) + if (scope) hdr_value = apr_pstrcat(tb->pool, hdr_value, - " tweak=", tweak, NULL); + ", scope=", scope, NULL); /* Use non-standard case WWW-Authenticate header and scheme name to test for case insensitive comparisons. */ @@ -966,7 +961,7 @@ static void test_user_authentication(CuT user_authentication(tc, 0 /* don't close connection */, 1 /* allow pipelining during authn */, - 0 /* no authn header tweaks */); + 0 /* no custom scope */); } static void test_user_authentication_tweaked(CuTest *tc) @@ -982,7 +977,7 @@ static void test_user_authentication_kee user_authentication(tc, 1 /* close connection */, 1 /* allow pipelining during authn */, - 0 /* no authn header tweaks */); + 0 /* no custom scope */); } static void test_user_authentication_pipelining_off(CuTest *tc) @@ -990,7 +985,7 @@ static void test_user_authentication_pip user_authentication(tc, 0 /* don't close connection */, 0 /* don't allow pipelining during authn */, - 0 /* no authn header tweaks */); + 0 /* no custom scope */); } Modified: serf/branches/user-defined-authn/test/test_internal.c URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/test/test_internal.c?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/test/test_internal.c (original) +++ serf/branches/user-defined-authn/test/test_internal.c Wed Jun 25 20:20:05 2025 @@ -465,6 +465,88 @@ static void test_narrowing_conversions(C CuAssertIntEquals(tc, -1, val); } + +struct expected_attrs +{ + const char *token; + const char *value; +}; + +static void parse_parameters(CuTest *tc, const char *attrs, + const struct expected_attrs expected[]) +{ + test_baton_t *const tb = tc->testBaton; + apr_hash_t *const dict = serf__parse_authn_parameters(attrs, tb->pool); + const short attr_count = apr_hash_count(dict); + int i; + + for (i = 0; expected[i].token; ++i) + { + const char *token = expected[i].token; + const char *value = apr_hash_get(dict, token, APR_HASH_KEY_STRING); + CuAssertStrEquals(tc, expected[i].value, value); + } + CuAssertIntEquals(tc, i, attr_count); +} + +static void test_parse_parameters(CuTest *tc) +{ + static const struct expected_attrs expected[] = { + { "realm", "Wonderland" }, + { "scope", "Alice" }, + { "!#$%&'*+-.^_`|~", "(\"\\)"}, + { NULL, NULL } + }; + + parse_parameters(tc, + "Realm=\"Wonderland\"," + "ScOpE=Alice , " + "!#$%&'*+-.^_`|~=\"(\\\"\\\\)\"", + expected); +} + +static void test_parse_bad_parameters(CuTest *tc) +{ + static const struct expected_attrs expected[] = { + { NULL, NULL } + }; + static const struct expected_attrs unexpected[] = { + { "key", "value1" }, + { NULL, NULL } + }; + + parse_parameters(tc, "", expected); + parse_parameters(tc, "\t", expected); + parse_parameters(tc, "(comm", expected); + parse_parameters(tc, "key", expected); + parse_parameters(tc, "key=\"value", expected); + parse_parameters(tc, "key = value", expected); + parse_parameters(tc, "key=\"value1\"key=value2", expected); + parse_parameters(tc, "key=value1 key=value2", unexpected); +} + +static void test_parse_repeated_parameters(CuTest *tc) +{ + static const struct expected_attrs expected[] = { + { "key", "value2" }, + { NULL, NULL } + }; + + parse_parameters(tc, "key=value1, key=value2", expected); +} + +static void test_parameter_case_folding(CuTest *tc) +{ + static const struct expected_attrs expected[] = { + { "01234abcdefghijklmnopqrstuvwxyz56789", "Val" }, + { NULL, NULL } + }; + + parse_parameters(tc, "01234abcdefghijklmnopqrstuvwxyz56789=Val", expected); + parse_parameters(tc, "01234ABCDEFGHIJKLMNOPQRSTUVWXYZ56789=Val", expected); +} + + CuSuite *test_internal(void) { CuSuite *suite = CuSuiteNew(); @@ -479,6 +561,10 @@ CuSuite *test_internal(void) SUITE_ADD_TEST(suite, test_header_buckets_remove); SUITE_ADD_TEST(suite, test_runtime_versions); SUITE_ADD_TEST(suite, test_narrowing_conversions); + SUITE_ADD_TEST(suite, test_parse_parameters); + SUITE_ADD_TEST(suite, test_parse_bad_parameters); + SUITE_ADD_TEST(suite, test_parse_repeated_parameters); + SUITE_ADD_TEST(suite, test_parameter_case_folding); return suite; } Modified: serf/branches/user-defined-authn/test/test_serf.h URL: http://svn.apache.org/viewvc/serf/branches/user-defined-authn/test/test_serf.h?rev=1926725&r1=1926724&r2=1926725&view=diff ============================================================================== --- serf/branches/user-defined-authn/test/test_serf.h (original) +++ serf/branches/user-defined-authn/test/test_serf.h Wed Jun 25 20:20:05 2025 @@ -33,7 +33,7 @@ /* Test logging facilities, set flag to 1 to enable console logging for the test suite. */ -#define TEST_VERBOSE 0 +#define TEST_VERBOSE 01 /* Preferred proxy port */ #define PROXY_PORT 23456