| /* 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. | |
| */ | |
| /* | |
| * mod_version.c | |
| * Allow conditional configuration depending on the httpd version | |
| * | |
| * André Malo (nd/perlig.de), January 2004 | |
| * | |
| * Some stuff coded here is heavily based on the core <IfModule> | |
| * containers. | |
| * | |
| * The module makes the following confgurations possible: | |
| * | |
| * <IfVersion op major.minor.patch> | |
| * # conditional config here ... | |
| *</IfVersion> | |
| * | |
| * where "op" is one of: | |
| * = / == equal | |
| * > greater than | |
| * >= greater or equal | |
| * < less than | |
| * <= less or equal | |
| * | |
| * If minor version and patch level are omitted they are assumed to be 0. | |
| * | |
| * Alternatively you can match the whole version (including some vendor-added | |
| * string of the CORE version, see ap_release.h) against a regular expression: | |
| * | |
| * <IfVersion op regex> | |
| * # conditional config here ... | |
| *</IfVersion> | |
| * | |
| * where "op" is one of: | |
| * = / == match; regex must be surrounded by slashes | |
| * ~ match; regex MAY NOT be surrounded by slashes | |
| * | |
| * Note that all operators may be preceded by an exclamation mark | |
| * (without spaces) in order to reverse their meaning. | |
| * | |
| */ | |
| #include "apr.h" | |
| #include "apr_strings.h" | |
| #include "apr_lib.h" | |
| #include "httpd.h" | |
| #include "http_config.h" | |
| #include "http_log.h" | |
| /* module structure */ | |
| module AP_MODULE_DECLARE_DATA version_module; | |
| /* queried httpd version */ | |
| static ap_version_t httpd_version; | |
| /* | |
| * compare the supplied version with the core one | |
| */ | |
| static int compare_version(char *version_string, const char **error) | |
| { | |
| char *p = version_string, *ep; | |
| int version[3] = {0, 0, 0}; | |
| int c = 0; | |
| *error = "Version appears to be invalid. It must have the format " | |
| "major[.minor[.patch]] where major, minor and patch are " | |
| "numbers."; | |
| if (!apr_isdigit(*p)) { | |
| return 0; | |
| } | |
| /* parse supplied version */ | |
| ep = version_string + strlen(version_string); | |
| while (p <= ep && c < 3) { | |
| if (*p == '.') { | |
| *p = '\0'; | |
| } | |
| if (!*p) { | |
| version[c++] = atoi(version_string); | |
| version_string = ++p; | |
| continue; | |
| } | |
| if (!apr_isdigit(*p)) { | |
| break; | |
| } | |
| ++p; | |
| } | |
| if (p < ep) { /* syntax error */ | |
| return 0; | |
| } | |
| *error = NULL; | |
| if (httpd_version.major > version[0]) { | |
| return 1; | |
| } | |
| else if (httpd_version.major < version[0]) { | |
| return -1; | |
| } | |
| else if (httpd_version.minor > version[1]) { | |
| return 1; | |
| } | |
| else if (httpd_version.minor < version[1]) { | |
| return -1; | |
| } | |
| else if (httpd_version.patch > version[2]) { | |
| return 1; | |
| } | |
| else if (httpd_version.patch < version[2]) { | |
| return -1; | |
| } | |
| /* seems to be the same */ | |
| return 0; | |
| } | |
| /* | |
| * match version against a regular expression | |
| */ | |
| static int match_version(apr_pool_t *pool, char *version_string, | |
| const char **error) | |
| { | |
| ap_regex_t *compiled; | |
| const char *to_match; | |
| int rc; | |
| compiled = ap_pregcomp(pool, version_string, AP_REG_EXTENDED); | |
| if (!compiled) { | |
| *error = "Unable to compile regular expression"; | |
| return 0; | |
| } | |
| *error = NULL; | |
| to_match = apr_psprintf(pool, "%d.%d.%d%s", | |
| httpd_version.major, | |
| httpd_version.minor, | |
| httpd_version.patch, | |
| httpd_version.add_string); | |
| rc = !ap_regexec(compiled, to_match, 0, NULL, 0); | |
| ap_pregfree(pool, compiled); | |
| return rc; | |
| } | |
| /* | |
| * Implements the <IfVersion> container | |
| */ | |
| static const char *start_ifversion(cmd_parms *cmd, void *mconfig, | |
| const char *arg1, const char *arg2, | |
| const char *arg3) | |
| { | |
| const char *endp; | |
| int reverse = 0, done = 0, match = 0, compare; | |
| const char *p, *error; | |
| char c; | |
| /* supplying one argument is possible, we assume an equality check then */ | |
| if (!arg2) { | |
| arg2 = arg1; | |
| arg1 = "="; | |
| } | |
| /* surrounding quotes without operator */ | |
| if (!arg3 && *arg2 == '>' && !arg2[1]) { | |
| arg3 = ">"; | |
| arg2 = arg1; | |
| arg1 = "="; | |
| } | |
| /* the third argument makes version surrounding quotes plus operator | |
| * possible. | |
| */ | |
| endp = arg2 + strlen(arg2); | |
| if ( endp == arg2 | |
| || (!(arg3 && *arg3 == '>' && !arg3[1]) && *--endp != '>')) { | |
| return apr_pstrcat(cmd->pool, cmd->cmd->name, | |
| "> directive missing closing '>'", NULL); | |
| } | |
| p = arg1; | |
| if (*p == '!') { | |
| reverse = 1; | |
| if (p[1]) { | |
| ++p; | |
| } | |
| } | |
| c = *p++; | |
| if (!*p || (*p == '=' && !p[1] && c != '~')) { | |
| if (!httpd_version.major) { | |
| ap_get_server_revision(&httpd_version); | |
| } | |
| done = 1; | |
| switch (c) { | |
| case '=': | |
| /* normal comparison */ | |
| if (*arg2 != '/') { | |
| compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2, | |
| endp-arg2), | |
| &error); | |
| if (error) { | |
| return error; | |
| } | |
| match = !compare; | |
| break; | |
| } | |
| /* regexp otherwise */ | |
| if (endp == ++arg2 || *--endp != '/') { | |
| return "Missing delimiting / of regular expression."; | |
| } | |
| case '~': | |
| /* regular expression */ | |
| match = match_version(cmd->temp_pool, | |
| apr_pstrmemdup(cmd->temp_pool, arg2, | |
| endp-arg2), | |
| &error); | |
| if (error) { | |
| return error; | |
| } | |
| break; | |
| case '<': | |
| compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2, | |
| endp-arg2), | |
| &error); | |
| if (error) { | |
| return error; | |
| } | |
| match = ((-1 == compare) || (*p && !compare)); | |
| break; | |
| case '>': | |
| compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2, | |
| endp-arg2), | |
| &error); | |
| if (error) { | |
| return error; | |
| } | |
| match = ((1 == compare) || (*p && !compare)); | |
| break; | |
| default: | |
| done = 0; | |
| break; | |
| } | |
| } | |
| if (!done) { | |
| return apr_pstrcat(cmd->pool, "unrecognized operator '", arg1, "'", | |
| NULL); | |
| } | |
| if ((!reverse && match) || (reverse && !match)) { | |
| ap_directive_t *parent = NULL; | |
| ap_directive_t *current = NULL; | |
| const char *retval; | |
| retval = ap_build_cont_config(cmd->pool, cmd->temp_pool, cmd, | |
| ¤t, &parent, "<IfVersion"); | |
| *(ap_directive_t **)mconfig = current; | |
| return retval; | |
| } | |
| *(ap_directive_t **)mconfig = NULL; | |
| return ap_soak_end_container(cmd, "<IfVersion"); | |
| } | |
| static const command_rec version_cmds[] = { | |
| AP_INIT_TAKE123("<IfVersion", start_ifversion, NULL, EXEC_ON_READ | OR_ALL, | |
| "a comparison operator, a version (and a delimiter)"), | |
| { NULL } | |
| }; | |
| AP_DECLARE_MODULE(version) = | |
| { | |
| STANDARD20_MODULE_STUFF, | |
| NULL, /* dir config creater */ | |
| NULL, /* dir merger --- default is to override */ | |
| NULL, /* server config */ | |
| NULL, /* merge server configs */ | |
| version_cmds, /* command apr_table_t */ | |
| NULL, /* register hooks */ | |
| }; |