/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*/
/*
* Resource allocation code... the code here is responsible for making
* sure that nothing leaks.
*
* rst --- 4/95 --- 6/95
*/
#include "apr_private.h"
#include "apr_general.h"
#include "apr_pools.h"
#include "apr_tables.h"
#include "apr_strings.h"
#include "apr_lib.h"
#if APR_HAVE_STDLIB_H
#include
#endif
#if APR_HAVE_STRING_H
#include
#endif
#if APR_HAVE_STRINGS_H
#include
#endif
/*****************************************************************
* This file contains array and apr_table_t functions only.
*/
/*****************************************************************
*
* The 'array' functions...
*/
static void make_array_core(apr_array_header_t *res, apr_pool_t *p,
int nelts, int elt_size, int clear)
{
/*
* Assure sanity if someone asks for
* array of zero elts.
*/
if (nelts < 1) {
nelts = 1;
}
if (clear) {
res->elts = apr_pcalloc(p, nelts * elt_size);
}
else {
res->elts = apr_palloc(p, nelts * elt_size);
}
res->pool = p;
res->elt_size = elt_size;
res->nelts = 0; /* No active elements yet... */
res->nalloc = nelts; /* ...but this many allocated */
}
APR_DECLARE(int) apr_is_empty_array(const apr_array_header_t *a)
{
return ((a == NULL) || (a->nelts == 0));
}
APR_DECLARE(apr_array_header_t *) apr_array_make(apr_pool_t *p,
int nelts, int elt_size)
{
apr_array_header_t *res;
res = (apr_array_header_t *) apr_palloc(p, sizeof(apr_array_header_t));
make_array_core(res, p, nelts, elt_size, 1);
return res;
}
APR_DECLARE(void *) apr_array_pop(apr_array_header_t *arr)
{
if (apr_is_empty_array(arr)) {
return NULL;
}
return arr->elts + (arr->elt_size * (--arr->nelts));
}
APR_DECLARE(void *) apr_array_push(apr_array_header_t *arr)
{
if (arr->nelts == arr->nalloc) {
int new_size = (arr->nalloc <= 0) ? 1 : arr->nalloc * 2;
char *new_data;
new_data = apr_palloc(arr->pool, arr->elt_size * new_size);
memcpy(new_data, arr->elts, arr->nalloc * arr->elt_size);
memset(new_data + arr->nalloc * arr->elt_size, 0,
arr->elt_size * (new_size - arr->nalloc));
arr->elts = new_data;
arr->nalloc = new_size;
}
++arr->nelts;
return arr->elts + (arr->elt_size * (arr->nelts - 1));
}
static void *apr_array_push_noclear(apr_array_header_t *arr)
{
if (arr->nelts == arr->nalloc) {
int new_size = (arr->nalloc <= 0) ? 1 : arr->nalloc * 2;
char *new_data;
new_data = apr_palloc(arr->pool, arr->elt_size * new_size);
memcpy(new_data, arr->elts, arr->nalloc * arr->elt_size);
arr->elts = new_data;
arr->nalloc = new_size;
}
++arr->nelts;
return arr->elts + (arr->elt_size * (arr->nelts - 1));
}
APR_DECLARE(void) apr_array_cat(apr_array_header_t *dst,
const apr_array_header_t *src)
{
int elt_size = dst->elt_size;
if (dst->nelts + src->nelts > dst->nalloc) {
int new_size = (dst->nalloc <= 0) ? 1 : dst->nalloc * 2;
char *new_data;
while (dst->nelts + src->nelts > new_size) {
new_size *= 2;
}
new_data = apr_pcalloc(dst->pool, elt_size * new_size);
memcpy(new_data, dst->elts, dst->nalloc * elt_size);
dst->elts = new_data;
dst->nalloc = new_size;
}
memcpy(dst->elts + dst->nelts * elt_size, src->elts,
elt_size * src->nelts);
dst->nelts += src->nelts;
}
APR_DECLARE(apr_array_header_t *) apr_array_copy(apr_pool_t *p,
const apr_array_header_t *arr)
{
apr_array_header_t *res =
(apr_array_header_t *) apr_palloc(p, sizeof(apr_array_header_t));
make_array_core(res, p, arr->nalloc, arr->elt_size, 0);
memcpy(res->elts, arr->elts, arr->elt_size * arr->nelts);
res->nelts = arr->nelts;
memset(res->elts + res->elt_size * res->nelts, 0,
res->elt_size * (res->nalloc - res->nelts));
return res;
}
/* This cute function copies the array header *only*, but arranges
* for the data section to be copied on the first push or arraycat.
* It's useful when the elements of the array being copied are
* read only, but new stuff *might* get added on the end; we have the
* overhead of the full copy only where it is really needed.
*/
static APR_INLINE void copy_array_hdr_core(apr_array_header_t *res,
const apr_array_header_t *arr)
{
res->elts = arr->elts;
res->elt_size = arr->elt_size;
res->nelts = arr->nelts;
res->nalloc = arr->nelts; /* Force overflow on push */
}
APR_DECLARE(apr_array_header_t *)
apr_array_copy_hdr(apr_pool_t *p,
const apr_array_header_t *arr)
{
apr_array_header_t *res;
res = (apr_array_header_t *) apr_palloc(p, sizeof(apr_array_header_t));
res->pool = p;
copy_array_hdr_core(res, arr);
return res;
}
/* The above is used here to avoid consing multiple new array bodies... */
APR_DECLARE(apr_array_header_t *)
apr_array_append(apr_pool_t *p,
const apr_array_header_t *first,
const apr_array_header_t *second)
{
apr_array_header_t *res = apr_array_copy_hdr(p, first);
apr_array_cat(res, second);
return res;
}
/* apr_array_pstrcat generates a new string from the apr_pool_t containing
* the concatenated sequence of substrings referenced as elements within
* the array. The string will be empty if all substrings are empty or null,
* or if there are no elements in the array.
* If sep is non-NUL, it will be inserted between elements as a separator.
*/
APR_DECLARE(char *) apr_array_pstrcat(apr_pool_t *p,
const apr_array_header_t *arr,
const char sep)
{
char *cp, *res, **strpp;
apr_size_t len;
int i;
if (arr->nelts <= 0 || arr->elts == NULL) { /* Empty table? */
return (char *) apr_pcalloc(p, 1);
}
/* Pass one --- find length of required string */
len = 0;
for (i = 0, strpp = (char **) arr->elts; ; ++strpp) {
if (strpp && *strpp != NULL) {
len += strlen(*strpp);
}
if (++i >= arr->nelts) {
break;
}
if (sep) {
++len;
}
}
/* Allocate the required string */
res = (char *) apr_palloc(p, len + 1);
cp = res;
/* Pass two --- copy the argument strings into the result space */
for (i = 0, strpp = (char **) arr->elts; ; ++strpp) {
if (strpp && *strpp != NULL) {
len = strlen(*strpp);
memcpy(cp, *strpp, len);
cp += len;
}
if (++i >= arr->nelts) {
break;
}
if (sep) {
*cp++ = sep;
}
}
*cp = '\0';
/* Return the result string */
return res;
}
/*****************************************************************
*
* The "table" functions.
*/
#if APR_CHARSET_EBCDIC
#define CASE_MASK 0xbfbfbfbf
#else
#define CASE_MASK 0xdfdfdfdf
#endif
#define TABLE_HASH_SIZE 32
#define TABLE_INDEX_MASK 0x1f
#define TABLE_HASH(key) (TABLE_INDEX_MASK & *(unsigned char *)(key))
#define TABLE_INDEX_IS_INITIALIZED(t, i) ((t)->index_initialized & (1 << (i)))
#define TABLE_SET_INDEX_INITIALIZED(t, i) ((t)->index_initialized |= (1 << (i)))
/* Compute the "checksum" for a key, consisting of the first
* 4 bytes, normalized for case-insensitivity and packed into
* an int...this checksum allows us to do a single integer
* comparison as a fast check to determine whether we can
* skip a strcasecmp
*/
#define COMPUTE_KEY_CHECKSUM(key, checksum) \
{ \
const char *k = (key); \
apr_uint32_t c = (apr_uint32_t)*k; \
(checksum) = c; \
(checksum) <<= 8; \
if (c) { \
c = (apr_uint32_t)*++k; \
checksum |= c; \
} \
(checksum) <<= 8; \
if (c) { \
c = (apr_uint32_t)*++k; \
checksum |= c; \
} \
(checksum) <<= 8; \
if (c) { \
c = (apr_uint32_t)*++k; \
checksum |= c; \
} \
checksum &= CASE_MASK; \
}
/** The opaque string-content table type */
struct apr_table_t {
/* This has to be first to promote backwards compatibility with
* older modules which cast a apr_table_t * to an apr_array_header_t *...
* they should use the table_elts() function for most of the
* cases they do this for.
*/
/** The underlying array for the table */
apr_array_header_t a;
#ifdef MAKE_TABLE_PROFILE
/** Who created the array. */
void *creator;
#endif
/* An index to speed up table lookups. The way this works is:
* - Take the requested key and compute its checksum
* - Hash the checksum into the index:
* - index_first[TABLE_HASH(checksum)] is the offset within
* the table of the first entry with that key checksum
* - index_last[TABLE_HASH(checksum)] is the offset within
* the table of the first entry with that key checksum
* - If (and only if) there is no entry in the table whose
* checksum hashes to index element i, then the i'th bit
* of index_initialized will be zero. (Check this before
* trying to use index_first[i] or index_last[i]!)
*/
apr_uint32_t index_initialized;
int index_first[TABLE_HASH_SIZE];
int index_last[TABLE_HASH_SIZE];
};
/*
* NOTICE: if you tweak this you should look at is_empty_table()
* and table_elts() in alloc.h
*/
#ifdef MAKE_TABLE_PROFILE
static apr_table_entry_t *table_push(apr_table_t *t)
{
if (t->a.nelts == t->a.nalloc) {
return NULL;
}
return (apr_table_entry_t *) apr_array_push_noclear(&t->a);
}
#else /* MAKE_TABLE_PROFILE */
#define table_push(t) ((apr_table_entry_t *) apr_array_push_noclear(&(t)->a))
#endif /* MAKE_TABLE_PROFILE */
APR_DECLARE(const apr_array_header_t *) apr_table_elts(const apr_table_t *t)
{
return (const apr_array_header_t *)t;
}
APR_DECLARE(int) apr_is_empty_table(const apr_table_t *t)
{
return ((t == NULL) || (t->a.nelts == 0));
}
APR_DECLARE(apr_table_t *) apr_table_make(apr_pool_t *p, int nelts)
{
apr_table_t *t = apr_palloc(p, sizeof(apr_table_t));
make_array_core(&t->a, p, nelts, sizeof(apr_table_entry_t), 0);
#ifdef MAKE_TABLE_PROFILE
t->creator = __builtin_return_address(0);
#endif
t->index_initialized = 0;
return t;
}
APR_DECLARE(apr_table_t *) apr_table_copy(apr_pool_t *p, const apr_table_t *t)
{
apr_table_t *new = apr_palloc(p, sizeof(apr_table_t));
#ifdef POOL_DEBUG
/* we don't copy keys and values, so it's necessary that t->a.pool
* have a life span at least as long as p
*/
if (!apr_pool_is_ancestor(t->a.pool, p)) {
fprintf(stderr, "copy_table: t's pool is not an ancestor of p\n");
abort();
}
#endif
make_array_core(&new->a, p, t->a.nalloc, sizeof(apr_table_entry_t), 0);
memcpy(new->a.elts, t->a.elts, t->a.nelts * sizeof(apr_table_entry_t));
new->a.nelts = t->a.nelts;
memcpy(new->index_first, t->index_first, sizeof(int) * TABLE_HASH_SIZE);
memcpy(new->index_last, t->index_last, sizeof(int) * TABLE_HASH_SIZE);
new->index_initialized = t->index_initialized;
return new;
}
static void table_reindex(apr_table_t *t)
{
int i;
int hash;
apr_table_entry_t *next_elt = (apr_table_entry_t *) t->a.elts;
t->index_initialized = 0;
for (i = 0; i < t->a.nelts; i++, next_elt++) {
hash = TABLE_HASH(next_elt->key);
t->index_last[hash] = i;
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
t->index_first[hash] = i;
TABLE_SET_INDEX_INITIALIZED(t, hash);
}
}
}
APR_DECLARE(void) apr_table_clear(apr_table_t *t)
{
t->a.nelts = 0;
t->index_initialized = 0;
}
APR_DECLARE(const char *) apr_table_get(const apr_table_t *t, const char *key)
{
apr_table_entry_t *next_elt;
apr_table_entry_t *end_elt;
apr_uint32_t checksum;
int hash;
if (key == NULL) {
return NULL;
}
hash = TABLE_HASH(key);
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
return NULL;
}
COMPUTE_KEY_CHECKSUM(key, checksum);
next_elt = ((apr_table_entry_t *) t->a.elts) + t->index_first[hash];;
end_elt = ((apr_table_entry_t *) t->a.elts) + t->index_last[hash];
for (; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
return next_elt->val;
}
}
return NULL;
}
APR_DECLARE(void) apr_table_set(apr_table_t *t, const char *key,
const char *val)
{
apr_table_entry_t *next_elt;
apr_table_entry_t *end_elt;
apr_table_entry_t *table_end;
apr_uint32_t checksum;
int hash;
COMPUTE_KEY_CHECKSUM(key, checksum);
hash = TABLE_HASH(key);
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
t->index_first[hash] = t->a.nelts;
TABLE_SET_INDEX_INITIALIZED(t, hash);
goto add_new_elt;
}
next_elt = ((apr_table_entry_t *) t->a.elts) + t->index_first[hash];;
end_elt = ((apr_table_entry_t *) t->a.elts) + t->index_last[hash];
table_end =((apr_table_entry_t *) t->a.elts) + t->a.nelts;
for (; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
/* Found an existing entry with the same key, so overwrite it */
int must_reindex = 0;
apr_table_entry_t *dst_elt = NULL;
next_elt->val = apr_pstrdup(t->a.pool, val);
/* Remove any other instances of this key */
for (next_elt++; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
t->a.nelts--;
if (!dst_elt) {
dst_elt = next_elt;
}
}
else if (dst_elt) {
*dst_elt++ = *next_elt;
must_reindex = 1;
}
}
/* If we've removed anything, shift over the remainder
* of the table (note that the previous loop didn't
* run to the end of the table, just to the last match
* for the index)
*/
if (dst_elt) {
for (; next_elt < table_end; next_elt++) {
*dst_elt++ = *next_elt;
}
must_reindex = 1;
}
if (must_reindex) {
table_reindex(t);
}
return;
}
}
add_new_elt:
t->index_last[hash] = t->a.nelts;
next_elt = (apr_table_entry_t *) table_push(t);
next_elt->key = apr_pstrdup(t->a.pool, key);
next_elt->val = apr_pstrdup(t->a.pool, val);
next_elt->key_checksum = checksum;
}
APR_DECLARE(void) apr_table_setn(apr_table_t *t, const char *key,
const char *val)
{
apr_table_entry_t *next_elt;
apr_table_entry_t *end_elt;
apr_table_entry_t *table_end;
apr_uint32_t checksum;
int hash;
COMPUTE_KEY_CHECKSUM(key, checksum);
hash = TABLE_HASH(key);
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
t->index_first[hash] = t->a.nelts;
TABLE_SET_INDEX_INITIALIZED(t, hash);
goto add_new_elt;
}
next_elt = ((apr_table_entry_t *) t->a.elts) + t->index_first[hash];;
end_elt = ((apr_table_entry_t *) t->a.elts) + t->index_last[hash];
table_end =((apr_table_entry_t *) t->a.elts) + t->a.nelts;
for (; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
/* Found an existing entry with the same key, so overwrite it */
int must_reindex = 0;
apr_table_entry_t *dst_elt = NULL;
next_elt->val = (char *)val;
/* Remove any other instances of this key */
for (next_elt++; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
t->a.nelts--;
if (!dst_elt) {
dst_elt = next_elt;
}
}
else if (dst_elt) {
*dst_elt++ = *next_elt;
must_reindex = 1;
}
}
/* If we've removed anything, shift over the remainder
* of the table (note that the previous loop didn't
* run to the end of the table, just to the last match
* for the index)
*/
if (dst_elt) {
for (; next_elt < table_end; next_elt++) {
*dst_elt++ = *next_elt;
}
must_reindex = 1;
}
if (must_reindex) {
table_reindex(t);
}
return;
}
}
add_new_elt:
t->index_last[hash] = t->a.nelts;
next_elt = (apr_table_entry_t *) table_push(t);
next_elt->key = (char *)key;
next_elt->val = (char *)val;
next_elt->key_checksum = checksum;
}
APR_DECLARE(void) apr_table_unset(apr_table_t *t, const char *key)
{
apr_table_entry_t *next_elt;
apr_table_entry_t *end_elt;
apr_table_entry_t *dst_elt;
apr_uint32_t checksum;
int hash;
int must_reindex;
hash = TABLE_HASH(key);
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
return;
}
COMPUTE_KEY_CHECKSUM(key, checksum);
next_elt = ((apr_table_entry_t *) t->a.elts) + t->index_first[hash];
end_elt = ((apr_table_entry_t *) t->a.elts) + t->index_last[hash];
must_reindex = 0;
for (; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
/* Found a match: remove this entry, plus any additional
* matches for the same key that might follow
*/
apr_table_entry_t *table_end = ((apr_table_entry_t *) t->a.elts) +
t->a.nelts;
t->a.nelts--;
dst_elt = next_elt;
for (next_elt++; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
t->a.nelts--;
}
else {
*dst_elt++ = *next_elt;
}
}
/* Shift over the remainder of the table (note that
* the previous loop didn't run to the end of the table,
* just to the last match for the index)
*/
for (; next_elt < table_end; next_elt++) {
*dst_elt++ = *next_elt;
}
must_reindex = 1;
break;
}
}
if (must_reindex) {
table_reindex(t);
}
}
APR_DECLARE(void) apr_table_merge(apr_table_t *t, const char *key,
const char *val)
{
apr_table_entry_t *next_elt;
apr_table_entry_t *end_elt;
apr_uint32_t checksum;
int hash;
COMPUTE_KEY_CHECKSUM(key, checksum);
hash = TABLE_HASH(key);
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
t->index_first[hash] = t->a.nelts;
TABLE_SET_INDEX_INITIALIZED(t, hash);
goto add_new_elt;
}
next_elt = ((apr_table_entry_t *) t->a.elts) + t->index_first[hash];
end_elt = ((apr_table_entry_t *) t->a.elts) + t->index_last[hash];
for (; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
/* Found an existing entry with the same key, so merge with it */
next_elt->val = apr_pstrcat(t->a.pool, next_elt->val, ", ",
val, NULL);
return;
}
}
add_new_elt:
t->index_last[hash] = t->a.nelts;
next_elt = (apr_table_entry_t *) table_push(t);
next_elt->key = apr_pstrdup(t->a.pool, key);
next_elt->val = apr_pstrdup(t->a.pool, val);
next_elt->key_checksum = checksum;
}
APR_DECLARE(void) apr_table_mergen(apr_table_t *t, const char *key,
const char *val)
{
apr_table_entry_t *next_elt;
apr_table_entry_t *end_elt;
apr_uint32_t checksum;
int hash;
#ifdef POOL_DEBUG
{
if (!apr_pool_is_ancestor(apr_pool_find(key), t->a.pool)) {
fprintf(stderr, "table_set: key not in ancestor pool of t\n");
abort();
}
if (!apr_pool_is_ancestor(apr_pool_find(val), t->a.pool)) {
fprintf(stderr, "table_set: key not in ancestor pool of t\n");
abort();
}
}
#endif
COMPUTE_KEY_CHECKSUM(key, checksum);
hash = TABLE_HASH(key);
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
t->index_first[hash] = t->a.nelts;
TABLE_SET_INDEX_INITIALIZED(t, hash);
goto add_new_elt;
}
next_elt = ((apr_table_entry_t *) t->a.elts) + t->index_first[hash];;
end_elt = ((apr_table_entry_t *) t->a.elts) + t->index_last[hash];
for (; next_elt <= end_elt; next_elt++) {
if ((checksum == next_elt->key_checksum) &&
!strcasecmp(next_elt->key, key)) {
/* Found an existing entry with the same key, so merge with it */
next_elt->val = apr_pstrcat(t->a.pool, next_elt->val, ", ",
val, NULL);
return;
}
}
add_new_elt:
t->index_last[hash] = t->a.nelts;
next_elt = (apr_table_entry_t *) table_push(t);
next_elt->key = (char *)key;
next_elt->val = (char *)val;
next_elt->key_checksum = checksum;
}
APR_DECLARE(void) apr_table_add(apr_table_t *t, const char *key,
const char *val)
{
apr_table_entry_t *elts;
apr_uint32_t checksum;
int hash;
hash = TABLE_HASH(key);
t->index_last[hash] = t->a.nelts;
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
t->index_first[hash] = t->a.nelts;
TABLE_SET_INDEX_INITIALIZED(t, hash);
}
COMPUTE_KEY_CHECKSUM(key, checksum);
elts = (apr_table_entry_t *) table_push(t);
elts->key = apr_pstrdup(t->a.pool, key);
elts->val = apr_pstrdup(t->a.pool, val);
elts->key_checksum = checksum;
}
APR_DECLARE(void) apr_table_addn(apr_table_t *t, const char *key,
const char *val)
{
apr_table_entry_t *elts;
apr_uint32_t checksum;
int hash;
#ifdef POOL_DEBUG
{
if (!apr_pool_is_ancestor(apr_pool_find(key), t->a.pool)) {
fprintf(stderr, "table_set: key not in ancestor pool of t\n");
abort();
}
if (!apr_pool_is_ancestor(apr_pool_find(val), t->a.pool)) {
fprintf(stderr, "table_set: key not in ancestor pool of t\n");
abort();
}
}
#endif
hash = TABLE_HASH(key);
t->index_last[hash] = t->a.nelts;
if (!TABLE_INDEX_IS_INITIALIZED(t, hash)) {
t->index_first[hash] = t->a.nelts;
TABLE_SET_INDEX_INITIALIZED(t, hash);
}
COMPUTE_KEY_CHECKSUM(key, checksum);
elts = (apr_table_entry_t *) table_push(t);
elts->key = (char *)key;
elts->val = (char *)val;
elts->key_checksum = checksum;
}
APR_DECLARE(apr_table_t *) apr_table_overlay(apr_pool_t *p,
const apr_table_t *overlay,
const apr_table_t *base)
{
apr_table_t *res;
#ifdef POOL_DEBUG
/* we don't copy keys and values, so it's necessary that
* overlay->a.pool and base->a.pool have a life span at least
* as long as p
*/
if (!apr_pool_is_ancestor(overlay->a.pool, p)) {
fprintf(stderr,
"overlay_tables: overlay's pool is not an ancestor of p\n");
abort();
}
if (!apr_pool_is_ancestor(base->a.pool, p)) {
fprintf(stderr,
"overlay_tables: base's pool is not an ancestor of p\n");
abort();
}
#endif
res = apr_palloc(p, sizeof(apr_table_t));
/* behave like append_arrays */
res->a.pool = p;
copy_array_hdr_core(&res->a, &overlay->a);
apr_array_cat(&res->a, &base->a);
table_reindex(res);
return res;
}
/* And now for something completely abstract ...
* For each key value given as a vararg:
* run the function pointed to as
* int comp(void *r, char *key, char *value);
* on each valid key-value pair in the apr_table_t t that matches the vararg key,
* or once for every valid key-value pair if the vararg list is empty,
* until the function returns false (0) or we finish the table.
*
* Note that we restart the traversal for each vararg, which means that
* duplicate varargs will result in multiple executions of the function
* for each matching key. Note also that if the vararg list is empty,
* only one traversal will be made and will cut short if comp returns 0.
*
* Note that the table_get and table_merge functions assume that each key in
* the apr_table_t is unique (i.e., no multiple entries with the same key). This
* function does not make that assumption, since it (unfortunately) isn't
* true for some of Apache's tables.
*
* Note that rec is simply passed-on to the comp function, so that the
* caller can pass additional info for the task.
*
* ADDENDUM for apr_table_vdo():
*
* The caching api will allow a user to walk the header values:
*
* apr_status_t apr_cache_el_header_walk(apr_cache_el *el,
* int (*comp)(void *, const char *, const char *), void *rec, ...);
*
* So it can be ..., however from there I use a callback that use a va_list:
*
* apr_status_t (*cache_el_header_walk)(apr_cache_el *el,
* int (*comp)(void *, const char *, const char *), void *rec, va_list);
*
* To pass those ...'s on down to the actual module that will handle walking
* their headers, in the file case this is actually just an apr_table - and
* rather than reimplementing apr_table_do (which IMHO would be bad) I just
* called it with the va_list. For mod_shmem_cache I don't need it since I
* can't use apr_table's, but mod_file_cache should (though a good hash would
* be better, but that's a different issue :).
*
* So to make mod_file_cache easier to maintain, it's a good thing
*/
APR_DECLARE_NONSTD(int) apr_table_do(apr_table_do_callback_fn_t *comp,
void *rec, const apr_table_t *t, ...)
{
int rv;
va_list vp;
va_start(vp, t);
rv = apr_table_vdo(comp, rec, t, vp);
va_end(vp);
return rv;
}
/* XXX: do the semantics of this routine make any sense? Right now,
* if the caller passed in a non-empty va_list of keys to search for,
* the "early termination" facility only terminates on *that* key; other
* keys will continue to process. Note that this only has any effect
* at all if there are multiple entries in the table with the same key,
* otherwise the called function can never effectively early-terminate
* this function, as the zero return value is effectively ignored.
*
* Note also that this behavior is at odds with the behavior seen if an
* empty va_list is passed in -- in that case, a zero return value terminates
* the entire apr_table_vdo (which is what I think should happen in
* both cases).
*
* If nobody objects soon, I'm going to change the order of the nested
* loops in this function so that any zero return value from the (*comp)
* function will cause a full termination of apr_table_vdo. I'm hesitant
* at the moment because these (funky) semantics have been around for a
* very long time, and although Apache doesn't seem to use them at all,
* some third-party vendor might. I can only think of one possible reason
* the existing semantics would make any sense, and it's very Apache-centric,
* which is this: if (*comp) is looking for matches of a particular
* substring in request headers (let's say it's looking for a particular
* cookie name in the Set-Cookie headers), then maybe it wants to be
* able to stop searching early as soon as it finds that one and move
* on to the next key. That's only an optimization of course, but changing
* the behavior of this function would mean that any code that tried
* to do that would stop working right.
*
* Sigh. --JCW, 06/28/02
*/
APR_DECLARE(int) apr_table_vdo(apr_table_do_callback_fn_t *comp,
void *rec, const apr_table_t *t, va_list vp)
{
char *argp;
apr_table_entry_t *elts = (apr_table_entry_t *) t->a.elts;
int vdorv = 1;
argp = va_arg(vp, char *);
do {
int rv = 1, i;
if (argp) {
/* Scan for entries that match the next key */
int hash = TABLE_HASH(argp);
if (TABLE_INDEX_IS_INITIALIZED(t, hash)) {
apr_uint32_t checksum;
COMPUTE_KEY_CHECKSUM(argp, checksum);
for (i = t->index_first[hash];
rv && (i <= t->index_last[hash]); ++i) {
if (elts[i].key && (checksum == elts[i].key_checksum) &&
!strcasecmp(elts[i].key, argp)) {
rv = (*comp) (rec, elts[i].key, elts[i].val);
}
}
}
}
else {
/* Scan the entire table */
for (i = 0; rv && (i < t->a.nelts); ++i) {
if (elts[i].key) {
rv = (*comp) (rec, elts[i].key, elts[i].val);
}
}
}
if (rv == 0) {
vdorv = 0;
}
} while (argp && ((argp = va_arg(vp, char *)) != NULL));
return vdorv;
}
/* During apr_table_overlap(), we build an overlap key for
* each element in the two tables.
*/
#define RED 0
#define BLACK 1
typedef struct overlap_key {
/* The table element */
apr_table_entry_t *elt;
/* 0 if from table 'a', 1 if from table 'b' */
int level;
/* Whether to omit this element when building the result table */
int skip;
/* overlap_keys can be assembled into a red-black tree */
int black;
struct overlap_key *tree_parent;
struct overlap_key *tree_left;
struct overlap_key *tree_right;
int color;
/* List of multiple values for this key */
struct overlap_key *merge_next;
struct overlap_key *merge_last;
} overlap_key;
/* Rotate a subtree of a red-black tree */
static void rotate_counterclockwise(overlap_key **root,
overlap_key *rotate_node)
{
overlap_key *child = rotate_node->tree_right;
rotate_node->tree_right = child->tree_left;
if (rotate_node->tree_right) {
rotate_node->tree_right->tree_parent = rotate_node;
}
child->tree_parent = rotate_node->tree_parent;
if (child->tree_parent == NULL) {
*root = child;
}
else {
if (rotate_node == rotate_node->tree_parent->tree_left) {
rotate_node->tree_parent->tree_left = child;
}
else {
rotate_node->tree_parent->tree_right = child;
}
}
child->tree_left = rotate_node;
rotate_node->tree_parent = child;
}
static void rotate_clockwise(overlap_key **root, overlap_key *rotate_node)
{
overlap_key *child = rotate_node->tree_left;
rotate_node->tree_left = child->tree_right;
if (rotate_node->tree_left) {
rotate_node->tree_left->tree_parent = rotate_node;
}
child->tree_parent = rotate_node->tree_parent;
if (child->tree_parent == NULL) {
*root = child;
}
else {
if (rotate_node == rotate_node->tree_parent->tree_left) {
rotate_node->tree_parent->tree_left = child;
}
else {
rotate_node->tree_parent->tree_right = child;
}
}
child->tree_right = rotate_node;
rotate_node->tree_parent = child;
}
static void overlap_hash(overlap_key *elt,
overlap_key **hash_table, int nhash,
unsigned flags)
{
/* Each bucket in the hash table holds a red-black tree
* containing the overlap_keys that hash into that bucket
*/
overlap_key **child = &(hash_table[elt->elt->key_checksum & (nhash - 1)]);
overlap_key **root = child;
overlap_key *parent = NULL;
overlap_key *next;
/* Look for the element in the tree */
while ((next = *child) != NULL) {
int direction = strcasecmp(elt->elt->key, next->elt->key);
if (direction < 0) {
parent = next;
child = &(next->tree_left);
}
else if (direction > 0) {
parent = next;
child = &(next->tree_right);
}
else {
/* This is the key we're looking for */
if (flags == APR_OVERLAP_TABLES_MERGE) {
/* Just link this node at the end of the list
* of values for the key. It doesn't need to
* be linked into the tree, because the node at
* the head of this key's value list is in the
* tree already.
*/
elt->skip = 1;
elt->merge_next = NULL;
if (next->merge_last) {
next->merge_last->merge_next = elt;
}
else {
next->merge_next = elt;
}
next->merge_last = elt;
}
else {
/* In the "set" case, don't bother storing
* this value in the tree if it's already
* there, except if the previous value was
* from table 'a' (level==0) and this value
* is from table 'b' (level==1)
*/
if (elt->level > next->level) {
elt->tree_left = next->tree_left;
if (next->tree_left) {
next->tree_left->tree_parent = elt;
}
elt->tree_right = next->tree_right;
if (next->tree_right) {
next->tree_right->tree_parent = elt;
}
elt->tree_parent = next->tree_parent;
elt->color = next->color;
(*child) = elt;
elt->merge_next = NULL;
elt->merge_last = NULL;
elt->skip = 0;
next->skip = 1;
}
else {
elt->skip = 1;
}
}
return;
}
}
/* The element wasn't in the tree, so add it */
elt->tree_left = NULL;
elt->tree_right = NULL;
elt->tree_parent = parent;
(*child) = elt;
elt->merge_next = NULL;
elt->merge_last = NULL;
elt->skip = 0;
elt->color = RED;
/* Shuffle the nodes to maintain the red-black tree's balanced
* shape property. (This is what guarantees O(n*log(n)) worst-case
* performance for apr_table_overlap().)
*/
next = elt;
while ((next->tree_parent) && (next->tree_parent->color == RED)) {
/* Note: Root is always black, and red and black nodes
* alternate on any path down the tree. So if we're inside
* this block, the grandparent node is non-NULL.
*/
overlap_key *grandparent = next->tree_parent->tree_parent;
if (next->tree_parent == grandparent->tree_left) {
overlap_key *parent_sibling = grandparent->tree_right;
if (parent_sibling && (parent_sibling->color == RED)) {
next->tree_parent->color = BLACK;
parent_sibling->color = BLACK;
grandparent->color = RED;
next = grandparent;
}
else {
if (next == next->tree_parent->tree_right) {
next = next->tree_parent;
rotate_counterclockwise(root, next);
}
next->tree_parent->color = BLACK;
next->tree_parent->tree_parent->color = RED;
rotate_clockwise(root, next->tree_parent->tree_parent);
}
}
else {
overlap_key *parent_sibling = grandparent->tree_left;
if (parent_sibling && (parent_sibling->color == RED)) {
next->tree_parent->color = BLACK;
parent_sibling->color = BLACK;
grandparent->color = RED;
next = grandparent;
}
else {
if (next == next->tree_parent->tree_left) {
next = next->tree_parent;
rotate_clockwise(root, next);
}
next->tree_parent->color = BLACK;
next->tree_parent->tree_parent->color = RED;
rotate_counterclockwise(root, next->tree_parent->tree_parent);
}
}
}
(*root)->color = BLACK;
}
/* Must be a power of 2 */
#define DEFAULT_HASH_SIZE 16
APR_DECLARE(void) apr_table_overlap(apr_table_t *a, const apr_table_t *b,
unsigned flags)
{
int max_keys;
int nkeys;
overlap_key *cat_keys; /* concatenation of the keys of a and b */
overlap_key **hash_table;
int nhash;
int i;
apr_table_entry_t *elts;
apr_table_entry_t *dst_elt;
max_keys = a->a.nelts + b->a.nelts;
if (!max_keys) {
/* The following logic won't do anything harmful if we keep
* going in this situation, but
*
* 1) certain memory debuggers don't like an alloc size of 0
* so we'd like to avoid that...
* 2) this isn't all that rare a call anyway, so it is useful
* to skip the storage allocation and other checks in the
* following logic
*/
return;
}
cat_keys = apr_palloc(b->a.pool, sizeof(overlap_key) * max_keys);
nhash = DEFAULT_HASH_SIZE;
while (nhash < max_keys) {
nhash <<= 1;
}
hash_table = (overlap_key **)apr_pcalloc(b->a.pool,
sizeof(overlap_key *) * nhash);
/* The cat_keys array contains an element for each entry in a,
* followed by one for each in b. While populating this array,
* we also use it as:
* 1) a hash table, to detect matching keys, and
* 2) a linked list of multiple values for a given key (in the
* APR_OVERLAP_TABLES_MERGE case)
*/
/* First, the elements of a */
nkeys = 0;
elts = (apr_table_entry_t *)a->a.elts;
for (i = 0; i < a->a.nelts; i++, nkeys++) {
cat_keys[nkeys].elt = &(elts[i]);
cat_keys[nkeys].level = 0;
overlap_hash(&(cat_keys[nkeys]), hash_table, nhash, flags);
}
/* Then the elements of b */
elts = (apr_table_entry_t *)b->a.elts;
for (i = 0; i < b->a.nelts; i++, nkeys++) {
cat_keys[nkeys].elt = &(elts[i]);
cat_keys[nkeys].level = 1;
overlap_hash(&(cat_keys[nkeys]), hash_table, nhash, flags);
}
/* Copy concatenated list of elements into table a to
* form the new table contents, but:
* 1) omit the ones marked "skip," and
* 2) merge values for the same key if needed
*/
make_array_core(&a->a, b->a.pool, max_keys, sizeof(apr_table_entry_t), 0);
nkeys = 0;
dst_elt = (apr_table_entry_t *)a->a.elts;
for (i = 0; i < max_keys; i++) {
if (cat_keys[i].skip) {
continue;
}
if (cat_keys[i].merge_next) {
char *new_val;
char *val_next;
overlap_key *next = cat_keys[i].merge_next;
int len = (cat_keys[i].elt->val ?
strlen(cat_keys[i].elt->val) : 0);
do {
len += 2;
if (next->elt->val) {
len += strlen(next->elt->val);
}
next = next->merge_next;
} while (next);
len++;
new_val = (char *)apr_palloc(b->a.pool, len);
val_next = new_val;
if (cat_keys[i].elt->val) {
strcpy(val_next, cat_keys[i].elt->val);
val_next += strlen(cat_keys[i].elt->val);
}
next = cat_keys[i].merge_next;
do {
*val_next++ = ',';
*val_next++ = ' ';
if (next->elt->val) {
strcpy(val_next, next->elt->val);
val_next += strlen(next->elt->val);
}
next = next->merge_next;
} while (next);
*val_next = 0;
dst_elt->key = cat_keys[i].elt->key;
dst_elt->val = new_val;
dst_elt->key_checksum = cat_keys[i].elt->key_checksum;
dst_elt++;
}
else {
dst_elt->key = cat_keys[i].elt->key;
dst_elt->val = cat_keys[i].elt->val;
dst_elt->key_checksum = cat_keys[i].elt->key_checksum;
dst_elt++;
}
}
a->a.nelts = dst_elt - (apr_table_entry_t *)a->a.elts;
table_reindex(a);
}