/* 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 is a macro: name, arguments, contents, location.
*/ typedefstruct
{ char *name; /* lower case name of the macro */
apr_array_header_t *arguments; /* of char*, macro parameter names */
apr_array_header_t *contents; /* of char*, macro body */ char *location; /* of macro definition, for error messages */
} ap_macro_t;
/* Macros are kept globally... They are not per-server or per-directory entities.
note: they are in a temp_pool, and there is a lazy initialization. ap_macros is reset to NULL in pre_config hook to not depend on static vs dynamic configuration.
/* return configuration-parsed arguments from line as an array. the line is expected not to contain any '\n'?
*/ static apr_array_header_t *get_arguments(apr_pool_t * pool, constchar *line)
{
apr_array_header_t *args = apr_array_make(pool, 1, sizeof(char *));
/* warn if anything non blank appears, but ignore comments...
*/ staticvoid warn_if_non_blank(constchar * what, char * ptr,
ap_configfile_t * cfg)
{ char * p; for (p=ptr; *p; p++) { if (*p == '#') break; if (*p != ' ' && *p != '\t') {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02989) "%s on line %d of %s: %s",
what, cfg->line_number, cfg->name, ptr); break;
}
}
}
/* get read lines as an array till end_token. counts nesting for begin_token/end_token. it assumes a line-per-line configuration (thru getline). this function could be exported. begin_token may be NULL.
*/ staticchar *get_lines_till_end_token(apr_pool_t * pool,
ap_configfile_t * config_file, constchar *end_token, constchar *begin_token, constchar *where,
apr_array_header_t ** plines)
{
apr_array_header_t *lines = apr_array_make(pool, 1, sizeof(char *)); char line[MAX_STRING_LEN]; /* sorry, but this is expected by getline:-( */ int macro_nesting = 1, any_nesting = 1; int line_number_start = config_file->line_number;
while (!ap_cfg_getline(line, MAX_STRING_LEN, config_file)) { char *ptr = line; char *first, **new; /* skip comments */ if (*line == '#') continue;
first = ap_getword_conf_nc(pool, &ptr); if (first) { /* detect nesting... */ if (!strncmp(first, "", 2)) {
any_nesting--; if (any_nesting < 0) {
ap_log_error(APLOG_MARK, APLOG_WARNING,
0, NULL, APLOGNO(02793) "bad (negative) nesting on line %d of %s",
config_file->line_number - line_number_start,
where);
}
} elseif (!strncmp(first, "<", 1)) {
any_nesting++;
}
if (!strcasecmp(first, end_token)) { /* check for proper closing */ char * endp = (char *) ap_strrchr_c(line, '>');
/* this cannot happen if end_token contains '>' */ if (endp == NULL) { return"end directive missing closing '>'";
}
warn_if_non_blank(
APLOGNO(02794) "non blank chars found after directive closing",
endp+1, config_file);
return apr_psprintf(pool, "expected token not found: %s", end_token);
}
/* the @* arguments are double-quote escaped when substituted */ #define ESCAPE_ARG '@'
/* other $* and %* arguments are simply replaced without escaping */ #define ARG_PREFIX "$%@"
/* characters allowed in an argument? not used yet, because that would trigger some backward compatibility.
*/ #define ARG_CONTENT \ "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "0123456789_" ARG_PREFIX
/* returns whether it looks like an argument, i.e. prefixed by ARG_PREFIX.
*/ staticint looks_like_an_argument(constchar *word)
{ return ap_strchr(ARG_PREFIX, *word) != 0;
}
/* generates an error on macro with two arguments of the same name. generates an error if a macro argument name is empty. generates a warning if arguments name prefixes conflict. generates a warning if the first char of an argument is not in ARG_PREFIX
*/ staticconstchar *check_macro_arguments(apr_pool_t * pool, const ap_macro_t * macro)
{ char **tab = (char **) macro->arguments->elts; int nelts = macro->arguments->nelts; int i;
for (i = 0; i < nelts; i++) {
size_t ltabi = strlen(tab[i]); int j;
if (ltabi == 0) { return apr_psprintf(pool, "macro '%s' (%s): empty argument #%d name",
macro->name, macro->location, i + 1);
} elseif (!looks_like_an_argument(tab[i])) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02796) "macro '%s' (%s) " "argument name '%s' (#%d) without expected prefix, " "better prefix argument names with one of '%s'.",
macro->name, macro->location,
tab[i], i + 1, ARG_PREFIX);
}
for (j = i + 1; j < nelts; j++) {
size_t ltabj = strlen(tab[j]);
/* must not use the same argument name twice */ if (!strcmp(tab[i], tab[j])) { return apr_psprintf(pool, "argument name conflict in macro '%s' (%s): " "argument '%s': #%d and #%d, " "change argument names!",
macro->name, macro->location,
tab[i], i + 1, j + 1);
}
/* warn about common prefix, but only if non empty names */ if (ltabi && ltabj &&
!strncmp(tab[i], tab[j], ltabi < ltabj ? ltabi : ltabj)) {
ap_log_error(APLOG_MARK, APLOG_WARNING,
0, NULL, APLOGNO(02797) "macro '%s' (%s): " "argument name prefix conflict (%s #%d and %s #%d), " "be careful about your macro definition!",
macro->name, macro->location,
tab[i], i + 1, tab[j], j + 1);
}
}
}
return NULL;
}
/* warn about empty strings in array. could be legitimate.
*/ staticvoid check_macro_use_arguments(constchar *where, const apr_array_header_t * array)
{ char **tab = (char **) array->elts; int i; for (i = 0; i < array->nelts; i++) { if (empty_string_p(tab[i])) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02798) "%s: empty argument #%d", where, i + 1);
}
}
}
/* could be switched to '\'' */ #define DELIM '"' #define ESCAPE '\\'
/* returns the number of needed escapes for the string
*/ staticint number_of_escapes(constchar delim, constchar *str)
{ int nesc = 0; constchar *s = str; while (*s) { if (*s == ESCAPE || *s == delim)
nesc++;
s++;
}
debug(fprintf(stderr, "escapes: %d ---%s---\n", nesc, str)); return nesc;
}
/* replace name by replacement at the beginning of buf of bufsize. returns an error message or NULL. C is not really a nice language for processing strings.
*/ staticchar *substitute(char *buf, constint bufsize, constchar *name, constchar *replacement, constint do_esc)
{ int lbuf = strlen(buf),
lname = strlen(name),
lrepl = strlen(replacement),
lsubs = lrepl +
(do_esc ? (2 + number_of_escapes(DELIM, replacement)) : 0),
shift = lsubs - lname, size = lbuf + shift, i, j;
/* buf must starts with name */
ap_assert(!strncmp(buf, name, lname));
/* hmmm??? */ if (!strcmp(name, replacement)) return NULL;
if (size >= bufsize) { /* could/should I reallocate? */ return"cannot substitute, buffer size too small";
}
/* cannot use strcpy as strings may overlap */ if (shift != 0) {
memmove(buf + lname + shift, buf + lname, lbuf - lname + 1);
}
/* insert the replacement with escapes */
j = 0; if (do_esc)
buf[j++] = DELIM; for (i = 0; i < lrepl; i++, j++) { if (do_esc && (replacement[i] == DELIM || replacement[i] == ESCAPE))
buf[j++] = ESCAPE;
buf[j] = replacement[i];
} if (do_esc)
buf[j++] = DELIM;
return NULL;
}
/* find first occurrence of args in buf. in case of conflict, the LONGEST argument is kept. (could be the FIRST?). returns the pointer and the whichone found, or NULL.
*/ staticchar *next_substitution(constchar *buf, const apr_array_header_t * args, int *whichone)
{ char *chosen = NULL, **tab = (char **) args->elts;
size_t lchosen = 0; int i;
for (i = 0; i < args->nelts; i++) { char *found = ap_strstr((char *) buf, tab[i]);
size_t lfound = strlen(tab[i]); if (found && (!chosen || found < chosen ||
(found == chosen && lchosen < lfound))) {
chosen = found;
lchosen = lfound;
*whichone = i;
}
}
return chosen;
}
/* substitute macro arguments by replacements in buf of bufsize. returns an error message or NULL. if used is defined, returns the used macro arguments.
*/ staticconstchar *substitute_macro_args( char *buf, int bufsize, const ap_macro_t * macro, const apr_array_header_t * replacements,
apr_array_header_t * used)
{ char *ptr = buf,
**atab = (char **) macro->arguments->elts,
**rtab = (char **) replacements->elts; int whichone = -1;
/* perform substitutions in a macro contents and return the result as a newly allocated array, if result is defined. may also return an error message. passes used down to substitute_macro_args.
*/ staticconstchar *process_content(apr_pool_t * pool, const ap_macro_t * macro, const apr_array_header_t * replacements,
apr_array_header_t * used,
apr_array_header_t ** result)
{
apr_array_header_t *contents = macro->contents; char line[MAX_STRING_LEN]; int i;
if (result) {
*result = apr_array_make(pool, contents->nelts, sizeof(char *));
}
/* for each line of the macro body */ for (i = 0; i < contents->nelts; i++) { constchar *errmsg; /* copy the line and substitute macro parameters */
apr_cpystrn(line, ((char **) contents->elts)[i], MAX_STRING_LEN);
errmsg = substitute_macro_args(line, MAX_STRING_LEN,
macro, replacements, used); if (errmsg) { return apr_psprintf(pool, "while processing line %d of macro '%s' (%s) %s",
i + 1, macro->name, macro->location, errmsg);
} /* append substituted line to result array */ if (result) { char **new = apr_array_push(*result);
*new = apr_pstrdup(pool, line);
}
}
return NULL;
}
/* warn if some macro arguments are not used.
*/ staticconstchar *check_macro_contents(apr_pool_t * pool, const ap_macro_t * macro)
{ int nelts = macro->arguments->nelts; char **names = (char **) macro->arguments->elts;
apr_array_header_t *used; int i; constchar *errmsg;
if (macro->contents->nelts == 0) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02799) "macro '%s' (%s): empty contents!",
macro->name, macro->location); return NULL; /* no need to further warnings... */
}
used = apr_array_make(pool, nelts, sizeof(char));
for (i = 0; i < nelts; i++) {
used->elts[i] = 0;
}
/* The expanded content of the macro is to be parsed as a ap_configfile_t. This is used to have some kind of old fashionned C object oriented inherited data structure for configs.
The following struct stores the contents.
This structure holds pointers (next, upper) to the current "file" which was being processed and is interrupted by the macro expansion. At the end of processing the macro, the initial data structure will be put back in place (see function next_one) and the reading will go on from there.
If macros are used within macros, there may be a cascade of such temporary arrays used to insert the expanded macro contents before resuming the real file processing.
There is some hopus-pocus to deal with line_number when transiting from one config to the other.
*/ typedefstruct
{ int index; /* current element */ int char_index; /* current char in element */ int length; /* cached length of the current line */
apr_array_header_t *contents; /* array of char * */
ap_configfile_t *next; /* next config once this one is processed */
ap_configfile_t **upper; /* hack: where to update it if needed */
} array_contents_t;
/* Get next config if any. this may be called several times if there are continuations.
*/ staticint next_one(array_contents_t * ml)
{ if (ml->next) {
ap_assert(ml->upper);
*(ml->upper) = ml->next; return 1;
} return 0;
}
/* returns next char if possible this may involve switching to enclosing config.
*/ static apr_status_t array_getch(char *ch, void *param)
{
array_contents_t *ml = (array_contents_t *) param; char **tab = (char **) ml->contents->elts;
/* returns a buf a la fgets. no more than a line at a time, otherwise the parsing is too much ahead... NULL at EOF.
*/ static apr_status_t array_getstr(void *buf, size_t bufsize, void *param)
{
array_contents_t *ml = (array_contents_t *) param; char *buffer = (char *) buf; char next = '\0';
size_t i = 0;
apr_status_t rc = APR_SUCCESS;
/* read chars from stream, stop on newline */ while (i < bufsize - 1 && next != LF &&
((rc = array_getch(&next, param)) == APR_SUCCESS)) {
buffer[i++] = next;
}
if (rc == APR_EOF) { /* maybe update to next, possibly a recursion */ if (next_one(ml)) {
ap_assert(ml->next->getstr); /* keep next line count in sync! the caller will update
the current line_number, we need to forward to the next */
ml->next->line_number++; return ml->next->getstr(buf, bufsize, ml->next->param);
} /* else that is really all we can do */ return APR_EOF;
}
buffer[i] = '\0';
return APR_SUCCESS;
}
/* close the array stream?
*/ static apr_status_t array_close(void *param)
{
array_contents_t *ml = (array_contents_t *) param; /* move index at end of stream... */
ml->index = ml->contents->nelts;
ml->char_index = ml->length; return APR_SUCCESS;
}
/* create an array config stream insertion "object". could be exported.
*/ static ap_configfile_t *make_array_config(apr_pool_t * pool,
apr_array_header_t * contents, constchar *where,
ap_configfile_t * cfg,
ap_configfile_t ** upper)
{
array_contents_t *ls =
(array_contents_t *) apr_palloc(pool, sizeof(array_contents_t));
ap_assert(ls!=NULL);
warn_if_non_blank(APLOGNO(02801) "non blank chars found after "
BEGIN_MACRO " closing '>'",
endp+1, cmd->config_file);
/* coldly drop '>[^>]*$' out */
*endp = '\0';
/* get lowercase macro name */
name = ap_getword_conf(pool, &arg); if (empty_string_p(name)) { return BEGIN_MACRO " macro definition: name not found";
}
/* get macro arguments */
macro->location = apr_psprintf(pool, "defined on line %d of \"%s\"",
cmd->config_file->line_number,
cmd->config_file->name);
debug(fprintf(stderr, "macro_section: location=%s\n", macro->location));
where =
apr_psprintf(pool, "macro '%s' (%s)", macro->name, macro->location);
if (looks_like_an_argument(name)) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02803) "%s better prefix a macro name with any of '%s'",
where, ARG_PREFIX);
}
/* get macro parameters */
macro->arguments = get_arguments(pool, arg);
/* must be initialized, or no macros has been defined */ if (ap_macros == NULL) { return"no macro defined before " USE_MACRO;
}
/* get lowercase macro name */
name = ap_getword_conf(cmd->temp_pool, &arg);
ap_str_tolower(name);
if (empty_string_p(name)) { return"no macro name specified with " USE_MACRO;
}
/* get macro definition */
macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING);
if (!macro) { return apr_psprintf(cmd->temp_pool, "macro '%s' undefined", name);
}
/* recursion is detected here by looking at the config file name, * which may already contains "macro 'foo'". Ok, it looks like a hack, * but otherwise it is uneasy to keep this data available somewhere... * the name has just the needed visibility and liveness.
*/
recursion =
apr_pstrcat(cmd->temp_pool, "macro '", macro->name, "'", NULL);
if (ap_strstr((char *) cmd->config_file->name, recursion)) { return apr_psprintf(cmd->temp_pool, "recursive use of macro '%s' is invalid",
macro->name);
}
/* get macro arguments */
replacements = get_arguments(cmd->temp_pool, arg);
if (macro->arguments->nelts != replacements->nelts) { return apr_psprintf(cmd->temp_pool, "macro '%s' (%s) used " "with %d arguments instead of %d",
macro->name, macro->location,
replacements->nelts, macro->arguments->nelts);
}
where = apr_psprintf(cmd->temp_pool, "macro '%s' (%s) used on line %d of \"%s\"",
macro->name, macro->location,
cmd->config_file->line_number,
cmd->config_file->name);
if (errmsg) { return apr_psprintf(cmd->temp_pool, "%s error while substituting: %s",
where, errmsg);
}
/* the current "config file" is replaced by a string array... at the end of processing the array, the initial config file
will be returned there (see next_one) so as to go on. */
cmd->config_file = make_array_config(cmd->temp_pool, contents, where,
cmd->config_file, &cmd->config_file);
/* must be initialized, or no macros has been defined */ if (ap_macros == NULL) { return"no macro defined before " UNDEF_MACRO;
}
if (empty_string_p(arg)) { return"no macro name specified with " UNDEF_MACRO;
}
/* check that the macro is defined */
name = apr_pstrdup(cmd->temp_pool, arg);
ap_str_tolower(name);
macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING); if (macro == NULL) { /* could be a warning? */ return apr_psprintf(cmd->temp_pool, "cannot remove undefined macro '%s'", name);
}
/* free macro: cannot do that */ /* remove macro from hash table */
apr_hash_set(ap_macros, name, APR_HASH_KEY_STRING, NULL);
/* macro module commands. configuration file macro stuff they are processed immediately when found, hence the EXEC_ON_READ.
*/ staticconst command_rec macro_cmds[] = {
AP_INIT_RAW_ARGS(BEGIN_MACRO, macro_section, NULL, EXEC_ON_READ | OR_ALL, "Beginning of a macro definition section."),
AP_INIT_RAW_ARGS(USE_MACRO, use_macro, NULL, EXEC_ON_READ | OR_ALL, "Use of a macro."),
AP_INIT_TAKE1(UNDEF_MACRO, undef_macro, NULL, EXEC_ON_READ | OR_ALL, "Remove a macro definition."),
{NULL}
};
/* Module hooks are request-oriented thus it does not suit configuration file utils a lot. I haven't found any clean hook to apply something before then after configuration file processing. Also what about .htaccess files?
Thus I think that server/util.c or server/config.c would be a better place for this stuff.
*/
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.