1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* 18 * mod_version.c 19 * Allow conditional configuration depending on the httpd version 20 * 21 * Andr� Malo (nd/perlig.de), January 2004 22 * 23 * Some stuff coded here is heavily based on the core <IfModule> 24 * containers. 25 * 26 * The module makes the following confgurations possible: 27 * 28 * <IfVersion op major.minor.patch> 29 * # conditional config here ... 30 *</IfVersion> 31 * 32 * where "op" is one of: 33 * = / == equal 34 * > greater than 35 * >= greater or equal 36 * < less than 37 * <= less or equal 38 * 39 * If minor version and patch level are omitted they are assumed to be 0. 40 * 41 * Alternatively you can match the whole version (including some vendor-added 42 * string of the CORE version, see ap_release.h) against a regular expression: 43 * 44 * <IfVersion op regex> 45 * # conditional config here ... 46 *</IfVersion> 47 * 48 * where "op" is one of: 49 * = / == match; regex must be surrounded by slashes 50 * ~ match; regex MAY NOT be surrounded by slashes 51 * 52 * Note that all operators may be preceeded by an exclamation mark 53 * (without spaces) in order to reverse their meaning. 54 * 55 */ 56 57#include "apr.h" 58#include "apr_strings.h" 59#include "apr_lib.h" 60 61#include "httpd.h" 62#include "http_config.h" 63#include "http_log.h" 64 65 66/* module structure */ 67module AP_MODULE_DECLARE_DATA version_module; 68 69/* queried httpd version */ 70static ap_version_t httpd_version; 71 72 73/* 74 * compare the supplied version with the core one 75 */ 76static int compare_version(char *version_string, const char **error) 77{ 78 char *p = version_string, *ep; 79 int version[3] = {0, 0, 0}; 80 int c = 0; 81 82 *error = "Version appears to be invalid. It must have the format " 83 "major[.minor[.patch]] where major, minor and patch are " 84 "numbers."; 85 86 if (!apr_isdigit(*p)) { 87 return 0; 88 } 89 90 /* parse supplied version */ 91 ep = version_string + strlen(version_string); 92 while (p <= ep && c < 3) { 93 if (*p == '.') { 94 *p = '\0'; 95 } 96 97 if (!*p) { 98 version[c++] = atoi(version_string); 99 version_string = ++p; 100 continue; 101 } 102 103 if (!apr_isdigit(*p)) { 104 break; 105 } 106 107 ++p; 108 } 109 110 if (p < ep) { /* syntax error */ 111 return 0; 112 } 113 114 *error = NULL; 115 116 if (httpd_version.major > version[0]) { 117 return 1; 118 } 119 else if (httpd_version.major < version[0]) { 120 return -1; 121 } 122 else if (httpd_version.minor > version[1]) { 123 return 1; 124 } 125 else if (httpd_version.minor < version[1]) { 126 return -1; 127 } 128 else if (httpd_version.patch > version[2]) { 129 return 1; 130 } 131 else if (httpd_version.patch < version[2]) { 132 return -1; 133 } 134 135 /* seems to be the same */ 136 return 0; 137} 138 139/* 140 * match version against a regular expression 141 */ 142static int match_version(apr_pool_t *pool, char *version_string, 143 const char **error) 144{ 145 ap_regex_t *compiled; 146 const char *to_match; 147 int rc; 148 149 compiled = ap_pregcomp(pool, version_string, AP_REG_EXTENDED); 150 if (!compiled) { 151 *error = "Unable to compile regular expression"; 152 return 0; 153 } 154 155 *error = NULL; 156 157 to_match = apr_psprintf(pool, "%d.%d.%d%s", 158 httpd_version.major, 159 httpd_version.minor, 160 httpd_version.patch, 161 httpd_version.add_string); 162 163 rc = !ap_regexec(compiled, to_match, 0, NULL, 0); 164 165 ap_pregfree(pool, compiled); 166 return rc; 167} 168 169/* 170 * Implements the <IfVersion> container 171 */ 172static const char *start_ifversion(cmd_parms *cmd, void *mconfig, 173 const char *arg1, const char *arg2, 174 const char *arg3) 175{ 176 const char *endp; 177 int reverse = 0, done = 0, match = 0, compare; 178 const char *p, *error; 179 char c; 180 181 /* supplying one argument is possible, we assume an equality check then */ 182 if (!arg2) { 183 arg2 = arg1; 184 arg1 = "="; 185 } 186 187 /* surrounding quotes without operator */ 188 if (!arg3 && *arg2 == '>' && !arg2[1]) { 189 arg3 = ">"; 190 arg2 = arg1; 191 arg1 = "="; 192 } 193 194 /* the third argument makes version surrounding quotes plus operator 195 * possible. 196 */ 197 endp = arg2 + strlen(arg2); 198 if ( endp == arg2 199 || (!(arg3 && *arg3 == '>' && !arg3[1]) && *--endp != '>')) { 200 return apr_pstrcat(cmd->pool, cmd->cmd->name, 201 "> directive missing closing '>'", NULL); 202 } 203 204 p = arg1; 205 if (*p == '!') { 206 reverse = 1; 207 if (p[1]) { 208 ++p; 209 } 210 } 211 212 c = *p++; 213 if (!*p || (*p == '=' && !p[1] && c != '~')) { 214 if (!httpd_version.major) { 215 ap_get_server_revision(&httpd_version); 216 } 217 218 done = 1; 219 switch (c) { 220 case '=': 221 /* normal comparison */ 222 if (*arg2 != '/') { 223 compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2, 224 endp-arg2), 225 &error); 226 if (error) { 227 return error; 228 } 229 230 match = !compare; 231 break; 232 } 233 234 /* regexp otherwise */ 235 if (endp == ++arg2 || *--endp != '/') { 236 return "Missing delimiting / of regular expression."; 237 } 238 239 case '~': 240 /* regular expression */ 241 match = match_version(cmd->temp_pool, 242 apr_pstrmemdup(cmd->temp_pool, arg2, 243 endp-arg2), 244 &error); 245 if (error) { 246 return error; 247 } 248 break; 249 250 case '<': 251 compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2, 252 endp-arg2), 253 &error); 254 if (error) { 255 return error; 256 } 257 258 match = ((-1 == compare) || (*p && !compare)); 259 break; 260 261 case '>': 262 compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2, 263 endp-arg2), 264 &error); 265 if (error) { 266 return error; 267 } 268 269 match = ((1 == compare) || (*p && !compare)); 270 break; 271 272 default: 273 done = 0; 274 break; 275 } 276 } 277 278 if (!done) { 279 return apr_pstrcat(cmd->pool, "unrecognized operator '", arg1, "'", 280 NULL); 281 } 282 283 if ((!reverse && match) || (reverse && !match)) { 284 ap_directive_t *parent = NULL; 285 ap_directive_t *current = NULL; 286 const char *retval; 287 288 retval = ap_build_cont_config(cmd->pool, cmd->temp_pool, cmd, 289 ¤t, &parent, "<IfVersion"); 290 *(ap_directive_t **)mconfig = current; 291 return retval; 292 } 293 294 *(ap_directive_t **)mconfig = NULL; 295 return ap_soak_end_container(cmd, "<IfVersion"); 296} 297 298static const command_rec version_cmds[] = { 299 AP_INIT_TAKE123("<IfVersion", start_ifversion, NULL, EXEC_ON_READ | OR_ALL, 300 "a comparison operator, a version (and a delimiter)"), 301 { NULL } 302}; 303 304AP_DECLARE_MODULE(version) = 305{ 306 STANDARD20_MODULE_STUFF, 307 NULL, /* dir config creater */ 308 NULL, /* dir merger --- default is to override */ 309 NULL, /* server config */ 310 NULL, /* merge server configs */ 311 version_cmds, /* command apr_table_t */ 312 NULL, /* register hooks */ 313}; 314