diff -Naur htscanner-cvs/ap_fnmatch.c htscanner/ap_fnmatch.c --- htscanner-cvs/ap_fnmatch.c 1970-01-01 01:00:00.000000000 +0100 +++ htscanner/ap_fnmatch.c 2007-07-09 02:11:27.000000000 +0200 @@ -0,0 +1,263 @@ +/* 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. + */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS 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 REGENTS OR 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. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)fnmatch.c 8.2 (Berkeley) 4/16/94"; +#endif /* LIBC_SCCS and not lint */ + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ + +#include "ap_fnmatch.h" +#include +#include + +#define ap_tolower(c) (tolower(((unsigned char)(c)))) +#define EOS '\0' + +static const char *rangematch(const char *, int, int); + +int ap_fnmatch(const char *pattern, const char *string, int flags) +{ + const char *stringstart; + char c, test; + + for (stringstart = string;;) { + switch (c = *pattern++) { + case EOS: + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) { + return (FNM_NOMATCH); + } + if (*string == '/' && (flags & FNM_PATHNAME)) { + return (FNM_NOMATCH); + } + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) { + return (FNM_NOMATCH); + } + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') { + c = *++pattern; + } + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) { + return (FNM_NOMATCH); + } + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) { + if (flags & FNM_PATHNAME) { + return (strchr(string, '/') == NULL ? 0 : FNM_NOMATCH); + } + else { + return (0); + } + } + else if (c == '/' && flags & FNM_PATHNAME) { + if ((string = strchr(string, '/')) == NULL) { + return (FNM_NOMATCH); + } + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!ap_fnmatch(pattern, string, flags & ~FNM_PERIOD)) { + return (0); + } + if (test == '/' && flags & FNM_PATHNAME) { + break; + } + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) { + return (FNM_NOMATCH); + } + if (*string == '/' && flags & FNM_PATHNAME) { + return (FNM_NOMATCH); + } + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) { + return (FNM_NOMATCH); + } + if ((pattern = rangematch(pattern, *string, flags)) == NULL) { + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + if (flags & FNM_CASE_BLIND) { + if (ap_tolower(c) != ap_tolower(*string)) { + return (FNM_NOMATCH); + } + } + else if (c != *string) { + return (FNM_NOMATCH); + } + string++; + break; + } + /* NOTREACHED */ + } +} + +static const char *rangematch(const char *pattern, int test, int flags) +{ + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^'))) { + ++pattern; + } + + for (ok = 0; (c = *pattern++) != ']';) { + if (c == '\\' && !(flags & FNM_NOESCAPE)) { + c = *pattern++; + } + if (c == EOS) { + return (NULL); + } + if (*pattern == '-' && (c2 = *(pattern + 1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) { + c2 = *pattern++; + } + if (c2 == EOS) { + return (NULL); + } + if ((c <= test && test <= c2) + || ((flags & FNM_CASE_BLIND) + && ((ap_tolower(c) <= ap_tolower(test)) + && (ap_tolower(test) <= ap_tolower(c2))))) { + ok = 1; + } + } + else if ((c == test) + || ((flags & FNM_CASE_BLIND) + && (ap_tolower(c) == ap_tolower(test)))) { + ok = 1; + } + } + return (ok == negate ? NULL : pattern); +} + + +/* This function is an Apache addition */ +/* return non-zero if pattern has any glob chars in it */ +int ap_is_fnmatch(const char *pattern) +{ + int nesting; + + nesting = 0; + while (*pattern) { + switch (*pattern) { + case '?': + case '*': + return 1; + + case '\\': + if (*pattern++ == '\0') { + return 0; + } + break; + + case '[': /* '[' is only a glob if it has a matching ']' */ + ++nesting; + break; + + case ']': + if (nesting) { + return 1; + } + break; + } + ++pattern; + } + return 0; +} +/* + * Local variables: + * tab-width: 8 + * c-basic-offset: 8 + * End: + * vim600: noet sw=8 ts=8 fdm=marker + * vim<600: noet sw=8 ts=8 + */ diff -Naur htscanner-cvs/ap_fnmatch.h htscanner/ap_fnmatch.h --- htscanner-cvs/ap_fnmatch.h 1970-01-01 01:00:00.000000000 +0100 +++ htscanner/ap_fnmatch.h 2007-07-09 02:11:48.000000000 +0200 @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS 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 REGENTS OR 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. + * + * @(#)fnmatch.h 8.1 (Berkeley) 6/2/93 + */ + +/* This file has been modified by the Apache Group. */ + +#ifndef _FNMATCH_H_ +#define _FNMATCH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define FNM_NOMATCH 1 /* Match failed. */ + +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +/* This flag is an Apache addition */ +#define FNM_CASE_BLIND 0x08 /* Compare characters case-insensitively. */ + +int ap_fnmatch(const char *, const char *, int); + +/* this function is an Apache addition */ +extern int ap_is_fnmatch(const char *); + +#ifdef __cplusplus +} +#endif + +#endif /* !_FNMATCH_H_ */ +/* + * Local variables: + * tab-width: 8 + * c-basic-offset: 8 + * End: + * vim600: noet sw=8 ts=8 fdm=marker + * vim<600: noet sw=8 ts=8 + */ diff -Naur htscanner-cvs/ap_util.c htscanner/ap_util.c --- htscanner-cvs/ap_util.c 1970-01-01 01:00:00.000000000 +0100 +++ htscanner/ap_util.c 2007-07-09 02:12:19.000000000 +0200 @@ -0,0 +1,111 @@ +/* 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. + */ + +/* + * util.c: string utility things + * + * 3/21/93 Rob McCool + * 1995-96 Many changes by the Apache Group + * + */ + +#include "ap_util.h" + +void ap_no2slash(char *name) +{ + char *d, *s; + + s = d = name; + +#ifdef PHP_WIN32 + /* Check for UNC names. Leave leading two slashes. */ + if (s[0] == '/' && s[1] == '/') + *d++ = *s++; +#endif + + while (*s) { + if ((*d++ = *s) == '/') { + do { + ++s; + } while (*s == '/'); + } + else { + ++s; + } + } + *d = '\0'; +} + + +/* + * copy at most n leading directories of s into d + * d should be at least as large as s plus 1 extra byte + * assumes n > 0 + * the return value is the ever useful pointer to the trailing \0 of d + * + * examples: + * /a/b, 1 ==> / + * /a/b, 2 ==> /a/ + * /a/b, 3 ==> /a/b/ + * /a/b, 4 ==> /a/b/ + * + * MODIFIED FOR HAVE_DRIVE_LETTERS and NETWARE environments, + * so that if n == 0, "/" is returned in d with n == 1 + * and s == "e:/test.html", "e:/" is returned in d + * *** See also directory_walk in src/main/http_request.c + */ +char * ap_make_dirstr_prefix(char *d, const char *s, int n) +{ +#if defined(PHP_WIN32) || defined(NETWARE) + if (!n) { + *d = '/'; + *++d = '\0'; + return (d); + } +#endif /* def HAVE_DRIVE_LETTERS || NETWARE */ + for (;;) { + *d = *s; + if (*d == '\0') { + *d = '/'; + break; + } + if (*d == '/' && (--n) == 0) + break; + ++d; + ++s; + } + *++d = 0; + return (d); +} + + +int ap_count_dirs(const char *path) +{ + register int x, n; + + for (x = 0, n = 0; path[x]; x++) + if (path[x] == '/') + n++; + return n; +} +/* + * Local variables: + * tab-width: 8 + * c-basic-offset: 8 + * End: + * vim600: noet sw=8 ts=8 fdm=marker + * vim<600: noet sw=8 ts=8 + */ diff -Naur htscanner-cvs/ap_util.h htscanner/ap_util.h --- htscanner-cvs/ap_util.h 1970-01-01 01:00:00.000000000 +0100 +++ htscanner/ap_util.h 2007-07-09 02:11:55.000000000 +0200 @@ -0,0 +1,40 @@ +/* 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. + */ + +#ifndef APACHE_UTIL_H +#define APACHE_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +void ap_no2slash(char *name); +char * ap_make_dirstr_prefix(char *d, const char *s, int n); +int ap_count_dirs(const char *path); + +#ifdef __cplusplus +} +#endif + +#endif /* !APACHE_UTIL_H */ +/* + * Local variables: + * tab-width: 8 + * c-basic-offset: 8 + * End: + * vim600: noet sw=8 ts=8 fdm=marker + * vim<600: noet sw=8 ts=8 + */ diff -Naur htscanner-cvs/config.m4 htscanner/config.m4 --- htscanner-cvs/config.m4 2006-11-30 02:54:09.000000000 +0100 +++ htscanner/config.m4 2007-08-27 14:14:11.000000000 +0200 @@ -2,8 +2,76 @@ dnl config.m4 for extension htscanner PHP_ARG_ENABLE(htscanner, whether to enable htscanner support, -[ --enable-htscanner Enable htscanner support]) +[ --enable-htscanner Enable htscanner support]) + +PHP_ARG_ENABLE(htscanner-httpd, whether to enable htscanner httpd emulation support, +[ --enable-htscanner-httpd Enable htscanner support], no, no) + +if test -z "$PHP_LIBXML_DIR"; then + PHP_ARG_WITH(libxml-dir, libxml2 install dir, + [ --with-libxml-dir=DIR htscanner: libxml2 install prefix], no, no) +fi + +dnl needed for php4 support +m4_ifndef([PHP_ADD_EXTENSION_DEP], [ + AC_DEFUN([PHP_ADD_EXTENSION_DEP], []) +]) + +dnl needed for php4 support +m4_ifndef([PHP_SETUP_LIBXML], [ + AC_DEFUN([PHP_SETUP_LIBXML], [ + AC_CACHE_CHECK([for xml2-config path], ac_cv_php_xml2_config_path, + [ + for i in $PHP_LIBXML_DIR /usr/local /usr; do + if test -x "$i/bin/xml2-config"; then + ac_cv_php_xml2_config_path="$i/bin/xml2-config" + break + fi + done + ]) + + if test -x "$ac_cv_php_xml2_config_path"; then + XML2_CONFIG="$ac_cv_php_xml2_config_path" + libxml_full_version=`$XML2_CONFIG --version` + ac_IFS=$IFS + IFS="." + set $libxml_full_version + IFS=$ac_IFS + LIBXML_VERSION=`expr [$]1 \* 1000000 + [$]2 \* 1000 + [$]3` + if test "$LIBXML_VERSION" -ge "2004014"; then + LIBXML_LIBS=`$XML2_CONFIG --libs` + LIBXML_INCS=`$XML2_CONFIG --cflags` + PHP_EVAL_LIBLINE($LIBXML_LIBS, $1) + PHP_EVAL_INCLINE($LIBXML_INCS) + AC_DEFINE(HAVE_LIBXML, 1, [ ]) + $2 + else + AC_MSG_ERROR(libxml version 2.4.14 or greater required.) + fi + else + AC_MSG_RESULT(not found) + AC_MSG_ERROR(Please reinstall the libxml >= 2.4.14 distribution) + fi + ]) +]) if test "$PHP_HTSCANNER" != "no"; then - PHP_NEW_EXTENSION(htscanner, htscanner.c, $ext_shared) + + if test "$PHP_HTSCANNER_HTTPD" != "no" && test "$PHP_LIBXML" = "no"; then + AC_MSG_ERROR([htscanner httpd emulation requires LIBXML extension, add --enable-libxml]) + fi + + if test "$PHP_HTSCANNER_HTTPD" != "no"; then + PHP_NEW_EXTENSION(htscanner, htscanner.c ap_fnmatch.c ap_util.c, $ext_shared) + + PHP_SETUP_LIBXML(HTSCANNER_SHARED_LIBADD, [ + AC_DEFINE(HTSCANNER_HTTPD,1,[ ]) + PHP_ADD_EXTENSION_DEP(htscanner, libxml) + PHP_SUBST(HTSCANNER_SHARED_LIBADD) + ], [ + AC_MSG_ERROR([xml2-config not found. Please check your libxml2 installation.]) + ]) + else + PHP_NEW_EXTENSION(htscanner, htscanner.c, $ext_shared) + fi fi diff -Naur htscanner-cvs/CREDITS htscanner/CREDITS --- htscanner-cvs/CREDITS 2006-11-30 02:54:09.000000000 +0100 +++ htscanner/CREDITS 2007-08-24 16:59:40.000000000 +0200 @@ -1,2 +1,2 @@ htscanner -Bart Vanbrabant, Pierre-Alain Joye +Bart Vanbrabant, Pierre-Alain Joye, Manuel Mausz diff -Naur htscanner-cvs/docs/htscanner.ini htscanner/docs/htscanner.ini --- htscanner-cvs/docs/htscanner.ini 2007-02-17 20:33:42.000000000 +0100 +++ htscanner/docs/htscanner.ini 2007-08-27 20:56:18.000000000 +0200 @@ -1,12 +1,17 @@ -[htscanner] extension="htscanner.so" +[htscanner] ; The configuration file htscanner needs to scan for php_* directives -config_file=".htaccess" +htscanner.config_file=".htaccess" + +; The configuration file for httpd emulation. Must be placed at php's ini-directory +; Converter for apache configuration can be found in scripts-directory +; Note that this feature has to be enabled explicitly at compile time +htscanner.httpd_file="php_httpd.xml" ; The fallback docroot when htscanner can't determine the current docroot -default_docroot="/" -default_ttl=300 +htscanner.default_docroot="/" +htscanner.default_ttl=300 ; Stop when an error occured in RINIT (no document root, cannot get path_translated,...) -stop_on_error = 0 +htscanner.stop_on_error = 0 diff -Naur htscanner-cvs/htscanner.c htscanner/htscanner.c --- htscanner-cvs/htscanner.c 2007-07-25 14:59:42.000000000 +0200 +++ htscanner/htscanner.c 2007-09-04 23:07:25.000000000 +0200 @@ -14,6 +14,7 @@ +----------------------------------------------------------------------+ | Authors: Bart Vanbrabant | | Pierre-Alain Joye | + | Manuel Mausz | +----------------------------------------------------------------------+ */ @@ -31,10 +32,17 @@ #include "ext/standard/file.h" #include "ext/standard/php_string.h" +#ifdef HTSCANNER_HTTPD +#include "ap_fnmatch.h" +#include "ap_util.h" +#endif + ZEND_DECLARE_MODULE_GLOBALS(htscanner) +int (*php_cgi_sapi_activate)(TSRMLS_D); + #define FILE_BUFFER 1000 -#define HTSCANNER_DEBUG 1 +#define HTSCANNER_DEBUG 0 #define HTSCANNER_ENABLE_CACHE 0 #if HTSCANNER_DEBUG @@ -42,21 +50,21 @@ { char output_buf[512]; va_list args; - char *debug; - debug = getenv("PHP_HTSCANNER_DEBUG"); - if (debug == NULL) { + if (!getenv("PHP_HTSCANNER_DEBUG")) { return; } - - va_start (args, format); - vsnprintf (output_buf, sizeof (output_buf), format, args); - va_end (args); - fputs (output_buf, stderr); - fflush (stderr); + va_start(args, format); + vsnprintf(output_buf, sizeof (output_buf), format, args); + va_end(args); + + fputs(output_buf, stderr); + fflush(stderr); } /* }}} */ +#else +#define htscanner_debug(...) #endif #define PHP_HTSCANNER_LTRIM(p) { \ @@ -78,7 +86,7 @@ static int php_htscanner_ini_check_path(char *option_name, int option_len, char *new_option_name, int new_option_len) /* {{{ */ { - if ( option_len != (new_option_len-1) ) { + if (option_len != (new_option_len-1) ) { return 0; } @@ -87,100 +95,146 @@ /* }}} */ /* {{{ value_hnd - * Parse an option and try to set the option + * Parse an option and try to set the option */ -static void value_hnd(char *string, int flag, int status, HashTable *ini_entries TSRMLS_DC) +static int value_hnd(char *name, char *value, int flag, int mode, HashTable *ini_entries TSRMLS_DC) { - char *name = string; - char *value; int name_len; + int value_len; +#ifdef HTSCANNER_HTTPD + int *altered_ini_mode; +#endif - name = string; - /* strip any leading whitespaces or tabs from the name */ - PHP_HTSCANNER_LTRIM(name); - value = strchr(name, ' '); - - if (value) { - int len; - *value = 0; - name_len = strlen(name); - ++value; + name_len = strlen(name); + value_len = strlen(value); - /* strip any leading whitespaces or tabs from the value */ - len = strlen(value); - PHP_HTSCANNER_LTRIM(value); - if (len > 2 && value[len - 2] == '\r') { - value[len - 2] = 0; - len -= 2; + if (flag) { + /* it's a flag */ + if (!strcasecmp(value, "On") || (value[0] == '1' && value[1] == '\0')) { + value = "1"; } else { - value[len - 1] = 0; - len--; + value = "0"; } - - if (flag) { - /* it's a flag */ - if (!strcasecmp(value, "On") || (value[0] == '1' && value[1] == '\0')) { - value = "1"; - } else { - value = "0"; - } - len = 1; - } else { - /* it's a value */ - if (!strncasecmp(value, "none", sizeof("none"))) { - value = ""; - len = 0; - } + value_len = 1; + } else { + /* it's a value */ + if (!strncasecmp(value, "none", sizeof("none"))) { + value = ""; + value_len = 0; } + } #define _CHECK_PATH(var, var_len, ini) php_htscanner_ini_check_path(var, var_len, ini, sizeof(ini)) - /* safe_mode & basedir check */ - if (PG(safe_mode) || PG(open_basedir)) { - if (_CHECK_PATH(name, name_len, "error_log") || - _CHECK_PATH(name, name_len, "java.class.path") || - _CHECK_PATH(name, name_len, "java.home") || - _CHECK_PATH(name, name_len, "java.library.path") || - _CHECK_PATH(name, name_len, "session.save_path") || - _CHECK_PATH(name, name_len, "vpopmail.directory")) { - if (PG(safe_mode) && !php_checkuid(value, NULL, CHECKUID_CHECK_FILE_AND_DIR)) { - return; - } - - if (php_check_open_basedir(value TSRMLS_CC)) { - return; - } + /* safe_mode & basedir check */ + if (mode != PHP_INI_SYSTEM && (PG(safe_mode) || PG(open_basedir))) { + if (_CHECK_PATH(name, name_len, "error_log") || + _CHECK_PATH(name, name_len, "java.class.path") || + _CHECK_PATH(name, name_len, "java.home") || + _CHECK_PATH(name, name_len, "java.library.path") || + _CHECK_PATH(name, name_len, "session.save_path") || + _CHECK_PATH(name, name_len, "vpopmail.directory")) { + if (PG(safe_mode) && !php_checkuid(value, NULL, CHECKUID_CHECK_FILE_AND_DIR)) { + return FAILURE; } - } - /* checks that ensure the user does not overwrite certain ini settings when safe_mode is enabled */ - if (PG(safe_mode)) { - if (!strncmp("max_execution_time", name, sizeof("max_execution_time")) || - !strncmp("memory_limit", name, sizeof("memory_limit")) || - !strncmp("child_terminate", name, sizeof("child_terminate")) || - !strncmp("open_basedir", name, sizeof("open_basedir")) || - !strncmp("safe_mode", name, sizeof("safe_mode")) ) { - return; + if (php_check_open_basedir(value TSRMLS_CC)) { + return FAILURE; } } + } - if (zend_alter_ini_entry(name, name_len + 1, value, len, - status, PHP_INI_STAGE_RUNTIME) == FAILURE) { - zend_error(E_WARNING, "Adding option (Name: %s Value: %s) (%i, %i) failed!\n", name, value, name_len, len); - return; + /* checks that ensure the user does not overwrite certain ini settings when safe_mode is enabled */ + if (mode != PHP_INI_SYSTEM && PG(safe_mode)) { + if (!strncmp("max_execution_time", name, sizeof("max_execution_time")) || + !strncmp("memory_limit", name, sizeof("memory_limit")) || + !strncmp("child_terminate", name, sizeof("child_terminate")) || + !strncmp("open_basedir", name, sizeof("open_basedir")) || + !strncmp("safe_mode", name, sizeof("safe_mode")) ) { + return FAILURE; } + } + +#ifdef HTSCANNER_HTTPD + if (HTG(xml_doc_cache) && mode != PHP_INI_SYSTEM && + zend_hash_find(&HTG(altered_ini_entries), name, name_len + 1, (void **) &altered_ini_mode) == SUCCESS && + *altered_ini_mode == PHP_INI_SYSTEM) { + return FAILURE; + } + + if (HTG(xml_doc_cache) && mode == PHP_INI_SYSTEM && + zend_hash_find(&HTG(altered_ini_entries), name, name_len + 1, (void **) &altered_ini_mode) == FAILURE) { + zend_hash_add(&HTG(altered_ini_entries), name, name_len + 1, &mode, sizeof(int), NULL); + } +#endif + + if (zend_alter_ini_entry(name, name_len + 1, value, value_len, mode, PHP_INI_STAGE_RUNTIME) == FAILURE) { + zend_error(E_WARNING, "Adding option (Name: %s Value: %s) (%i, %i) failed!\n", name, value, name_len, value_len); + return FAILURE; + } + #if HTSCANNER_ENABLE_CACHE - zend_hash_update(ini_entries, name, name_len + 1, value, len + 1, NULL); + if (ini_entries) { + zend_hash_update(ini_entries, name, name_len + 1, value, value_len + 1, NULL); + } #endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ value_hnd + * Parse an option and try to set the option + */ +static int value_hnd_strip(char *string, int flag, int mode, HashTable *ini_entries TSRMLS_DC) +{ + char *name; + char *value; + int value_len; + + name = string; + /* strip any leading whitespaces or tabs from the name */ + PHP_HTSCANNER_LTRIM(name); + value = strchr(name, ' '); + if (!value) { + value = strchr(name, '\t'); } + if (value) { + *value = 0; + ++value; + PHP_HTSCANNER_LTRIM(value); + + /* strip any leading whitespaces or tabs from the value */ + value_len = strlen(value); + if (value_len > 2 && value[value_len - 2] == '\r') { + value[value_len - 2] = 0; + } else { + value[value_len - 1] = 0; + } + + /* strip quoting characters */ + value_len = strlen(value); + if ((value[0] == '\'' && value[value_len - 1] == '\'') || + (value[0] == '\"' && value[value_len - 1] == '\"')) { + value[value_len - 1] = 0; + value++; + } + + return value_hnd(name, value, flag, mode, ini_entries TSRMLS_CC); + } + + return FAILURE; } /* }}} */ -/* {{{ parse_config_file - * Parse the configuration file +/* {{{ parse_htaccess_file + * Parse the htaccess configuration file */ -static void parse_config_file(char *file, HashTable *ini_entries TSRMLS_DC) +static int parse_htaccess_file(char *file, HashTable *ini_entries TSRMLS_DC) { + struct stat sb; + char buf[FILE_BUFFER]; + char *pos; php_stream *stream; /* see main/safemode.c:70 @@ -191,56 +245,621 @@ * pierre@php.net */ if (PG(safe_mode)) { - struct stat sb; if (VCWD_STAT(file, &sb) != 0) { - return; + return FAILURE; } } stream = php_stream_open_wrapper(file, "rb", ENFORCE_SAFE_MODE, NULL); + if (!stream) { + return FAILURE; + } - if (stream != NULL) { - char buf[FILE_BUFFER]; - char *pos; - while ((pos = php_stream_gets(stream, buf, FILE_BUFFER)) != NULL) { - /* strip leading spaces or tabs */ - PHP_HTSCANNER_LTRIM(pos); + while ((pos = php_stream_gets(stream, buf, FILE_BUFFER))) { + /* strip leading spaces or tabs */ + PHP_HTSCANNER_LTRIM(pos); + + if (strncmp(pos, "php_value", sizeof("php_value") - 1) == 0) { + value_hnd_strip(pos + sizeof("php_value"), 0, PHP_INI_PERDIR, ini_entries TSRMLS_CC); + } else if (strncmp(pos, "php_flag", sizeof("php_flag") - 1) == 0) { + value_hnd_strip(pos + sizeof("php_flag"), 1, PHP_INI_PERDIR, ini_entries TSRMLS_CC); + } + } + php_stream_close(stream); - if (strncmp(pos, "php_value", sizeof("php_value") - 1) == 0) { - value_hnd(pos + sizeof("php_value"), 0, PHP_INI_PERDIR, ini_entries TSRMLS_CC); - } else if (strncmp(pos, "php_flag", sizeof("php_flag") - 1) == 0) { - value_hnd(pos + sizeof("php_flag"), 1, PHP_INI_PERDIR, ini_entries TSRMLS_CC); - } + return SUCCESS; +} +/* }}} */ + +#ifdef HTSCANNER_HTTPD +/* {{{ parse_httpd_php_node + * Parse a php node + */ +static int parse_httpd_php_node(xmlNodePtr xml_node TSRMLS_DC) +{ + xmlChar *prop_type, *prop_name, *node_content; + int flag; + int mode = -1; + + if (!xml_node || xml_node->type != XML_ELEMENT_NODE || xmlStrcmp(xml_node->name, (const xmlChar *) "php")) { + return FAILURE; + } + + prop_type = xmlGetProp(xml_node, (const xmlChar *) "type"); + prop_name = xmlGetProp(xml_node, (const xmlChar *) "name"); + node_content = xmlNodeGetContent(xml_node); + if (prop_type && prop_name && node_content) { + if (!xmlStrcmp(prop_type, (const xmlChar *) "php_admin_value")) { + mode = PHP_INI_SYSTEM; + flag = 0; + } else if (!xmlStrcmp(prop_type, (const xmlChar *) "php_admin_flag")) { + mode = PHP_INI_SYSTEM; + flag = 1; + } else if (!xmlStrcmp(prop_type, (const xmlChar *) "php_value")) { + mode = PHP_INI_PERDIR; + flag = 0; + } else if (!xmlStrcmp(prop_type, (const xmlChar *) "php_flag")) { + mode = PHP_INI_PERDIR; + flag = 1; + } + + if (mode > 0) { + /* there's no support for caching httpd settings as zend hash yet, + * thus ini_entries is NULL. But is there really a need for a cache, + * as we already cache the internal structure of libxml? + */ + value_hnd((char *)prop_name, (char *)node_content, flag, mode, NULL TSRMLS_CC); } - php_stream_close(stream); } + + xmlFree(prop_type); + xmlFree(prop_name); + xmlFree(node_content); + + return SUCCESS; } /* }}} */ -/* {{{ get_doc_root - * doc_root only seems to be available in _SERVER["doc_root"] - * so get it there. +/* {{{ check_httpd_vhost_nodes + * Parse the virtualhost nodes */ -static char* get_doc_root(TSRMLS_D) +static xmlNodePtr check_httpd_vhost_nodes(xmlNodePtr xml_orignode, xmlChar *sapi TSRMLS_DC) { - zval **server, **data; - - if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &server) != FAILURE && - (Z_TYPE_PP(server) == IS_ARRAY) - ) { - zend_hash_internal_pointer_reset(Z_ARRVAL_PP(server)); - if (zend_hash_find(Z_ARRVAL_PP(server), "DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT"), (void **) &data) != FAILURE) { - if (Z_TYPE_PP(data) == IS_STRING) { - return Z_STRVAL_PP(data); + xmlNodePtr xml_node, xml_childnode, vhost_node, vhost_safe1, vhost_safe2, vhost_safe3, vhost_default, first_vhost; + xmlChar *vhost_addr, *vhost_port, *vhost_name; + char *server_addr, *server_port, *server_name; + int match_addr1, match_addr2, match_name; + + if (!xml_orignode) { + return NULL; + } + + /* Pseudo code for vhost handling. First matching vhost precedes + * - MATCH(ip) AND (MATCH(port) OR port = *) AND MATCH(servername) + * - MATCH(ip) AND (MATCH(port) OR port = *) + * - ip = * AND (MATCH(port) OR port = *) AND MATCH(servername) + * - ip = * AND (MATCH(port) OR port = *) + * - ip = "_default_" AND (MATCH(port) OR port = *) + * - first vhost + * + * TODO: maybe replace the vhost handling below with + * some equivalent semantic as apache's vhost handling + */ + + server_addr = sapi_module.getenv("SERVER_ADDR", sizeof("SERVER_ADDR")-1 TSRMLS_CC); + server_port = sapi_module.getenv("SERVER_PORT", sizeof("SERVER_PORT")-1 TSRMLS_CC); + server_name = sapi_module.getenv("SERVER_NAME", sizeof("SERVER_NAME")-1 TSRMLS_CC); + if (!server_addr || !server_port) { + return NULL; + } + + vhost_node = vhost_safe1 = vhost_safe2 = vhost_safe3 = vhost_default = first_vhost = NULL; + for (xml_node = xml_orignode->xmlChildrenNode; !vhost_node && xml_node; xml_node = xml_node->next) { + if (xmlStrcmp(xml_node->name, (const xmlChar *) "virtualhost")) { + continue; + } + + /* safe for later use */ + if (!first_vhost) { + first_vhost = xml_node; + } + + match_addr1 = match_addr2 = match_name = 0; + for (xml_childnode = xml_node->xmlChildrenNode; !vhost_node && xml_childnode; xml_childnode = xml_childnode->next) { + if (!xmlStrcmp(xml_childnode->name, (const xmlChar *) "address")) { + vhost_port = xmlGetProp(xml_childnode, (const xmlChar *) "port"); + vhost_addr = xmlNodeGetContent(xml_childnode); + + if (!xmlStrcmp(vhost_addr, (xmlChar *)server_addr) && + (!xmlStrcmp(vhost_port, (xmlChar *)server_port) || !xmlStrcmp(vhost_port, (const xmlChar *) "*"))) { + /* safe first matching node */ + if (!vhost_safe1) { + vhost_safe1 = xml_node; + } + match_addr1 = 1; + } + else if (!vhost_safe2 && !xmlStrcmp(vhost_addr, (const xmlChar *) "*") && + (!xmlStrcmp(vhost_port, (xmlChar *)server_port) || !xmlStrcmp(vhost_port, (const xmlChar *) "*"))) { + /* safe first matching node */ + if (!vhost_safe3) { + vhost_safe3 = xml_node; + } + match_addr2 = 1; + } + /* this may safe some cpu time later */ + else if (!vhost_default && !xmlStrcmp(vhost_addr, (const xmlChar *) "_default_") && + (!xmlStrcmp(vhost_port, (xmlChar *)server_port) || !xmlStrcmp(vhost_port, (const xmlChar *) "*"))) { + vhost_default = xml_node; + } + + xmlFree(vhost_port); + xmlFree(vhost_addr); + } + + if (!xmlStrcmp(xml_childnode->name, (const xmlChar *) "servername")) { + vhost_name = xmlNodeGetContent(xml_childnode); + + if (!xmlStrcmp(vhost_name, (xmlChar *)server_name)) { + match_name = 1; + } + + xmlFree(vhost_name); } + + + if (match_addr1 && match_name) { + vhost_node = xml_node; + } else if (match_addr2 && match_name) { + /* safe matching node */ + vhost_safe2 = xml_node; + } + } + } + + /* restore matching nodes accordingly */ + if (!vhost_node) { + if (vhost_safe1) { + vhost_node = vhost_safe1; + } else if (vhost_safe2) { + vhost_node = vhost_safe2; + } else if (vhost_safe3) { + vhost_node = vhost_safe3; + } else if (vhost_default) { + vhost_node = vhost_default; + } else if (first_vhost) { + vhost_node = first_vhost; + } + } + + return vhost_node; +} +/* }}} */ + +/* {{{ directive_entry_dtor + */ +static void directive_entry_dtor(htscanner_directive_entry *dir_entry TSRMLS_DC) +{ + if (dir_entry->path) { + pefree(dir_entry->path, 1); + } + if (dir_entry->regex) { + regfree(dir_entry->regex); + } +} +/* }}} */ + +/* {{{ directive_entry_cmp + * see apache src/main/http_core.c reorder_sorter() + */ + +#ifndef PHP_WIN32 +# define IS_SPECIAL(entry) \ + ((entry)->regex != NULL || ((entry)->path[0] != '/' && (entry)->path[1] != ':')) +#else +# define IS_SPECIAL(entry) \ + ((entry)->regex != NULL || ((entry)->path[0] != '/') +#endif + +static int directive_entry_cmp(const void *a, const void *b TSRMLS_DC) +{ + Bucket *f = *((Bucket **) a); + Bucket *s = *((Bucket **) b); + htscanner_directive_entry *entry_a; + htscanner_directive_entry *entry_b; + + entry_a = (htscanner_directive_entry *) f->pData; + entry_b = (htscanner_directive_entry *) s->pData; + + if (IS_SPECIAL(entry_a)) { + if (!IS_SPECIAL(entry_b)) { + return 1; } + } else if (IS_SPECIAL(entry_b)) { + return -1; } else { - return HTG(default_docroot); + if (entry_a->components < entry_b->components) { + return -1; + } + else if (entry_a->components > entry_b->components) { + return 1; + } + } + return entry_a->orig_index - entry_b->orig_index; +} +/* }}} */ + +/* {{{ httpd_directory_walk + * Parse the httpd directory nodes. + * see apache src/main/http_request.c directory_walk() + * TODO: merge htaccess check into directory_walk (like apache does) ? + * contra: httpd_xml has to exist! + */ +static int httpd_directory_walk(HashTable *directives TSRMLS_DC) +{ + char *filename, *test_filename, *test_dirname; +#if defined(PHP_WIN32) || defined(NETWARE) + char *s; +#endif + htscanner_directive_entry *entry; + xmlNodePtr *xml_node_ptr, xml_node; + int test_filename_len, match; + uint num_dirs, i; + ulong num_key; + + if (!zend_hash_num_elements(directives)) { + return FAILURE; + } + + if (!(filename = SG(request_info).path_translated) && + !(filename = sapi_module.getenv("SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME")-1 TSRMLS_CC))) { + return FAILURE; + } + + test_filename = estrdup(filename); + +#if defined(PHP_WIN32) || defined(NETWARE) + s = test_filename; + while (*s) { + if (*s == '\\') { + *s = '/'; + } + s++; + } +#endif + + ap_no2slash(test_filename); + num_dirs = ap_count_dirs(test_filename); + test_filename_len = strlen(test_filename); + test_dirname = emalloc(test_filename_len + 2); + + i = 1; +#if defined(PHP_WIN32) || defined(NETWARE) + if (test_filename[0] != '/') { + i = 0; + } +#endif + + zend_hash_internal_pointer_reset(directives); + for (; i <= num_dirs; i++) { + ap_make_dirstr_prefix(test_dirname, test_filename, i); + + /* this deals with not-regex directives only */ + while (zend_hash_get_current_data(directives, (void **)&entry) == SUCCESS) { + match = 0; + + if (entry->regex +#if defined(PHP_WIN32) || defined(NETWARE) + || (entry->components > 1 + && entry->components > i)) +#else + || entry->components > i) +#endif + break; + + if (entry->is_fnmatch) { + if (!ap_fnmatch(entry->path, test_dirname, FNM_PATHNAME)) { + match = 1; + } + } else if (!strcmp(test_dirname, entry->path)) { + match = 1; + } + + if (match) { + zend_hash_get_current_key(directives, (char **)&xml_node_ptr, &num_key, 0); + for (xml_node = (*xml_node_ptr)->xmlChildrenNode; xml_node; xml_node = xml_node->next) { + parse_httpd_php_node(xml_node TSRMLS_CC); + } + } + + zend_hash_move_forward(directives); + } + } + + /* now deal with regex directives */ + while (zend_hash_get_current_data(directives, (void **)&entry) == SUCCESS) { + if (entry->regex) { + if (!regexec(entry->regex, test_dirname, 0, NULL, REG_NOTEOL)) { + zend_hash_get_current_key(directives, (char **)&xml_node_ptr, &num_key, 0); + for (xml_node = (*xml_node_ptr)->xmlChildrenNode; xml_node; xml_node = xml_node->next) { + parse_httpd_php_node(xml_node TSRMLS_CC); + } + } + } + + zend_hash_move_forward(directives); + } + + efree(test_dirname); + efree(test_filename); + + return SUCCESS; +} +/* }}} */ + +/* {{{ httpd_file_walk + * Parse the httpd file nodes. + * see apache src/main/http_request.c file_walk() + */ +static int httpd_file_walk(HashTable *directives TSRMLS_DC) +{ + char *filename, *test_file; + htscanner_directive_entry *entry; + xmlNodePtr *xml_node_ptr, xml_node; + size_t test_file_len; + ulong num_key; + int match; + + if (!zend_hash_num_elements(directives)) { + return FAILURE; + } + + if (!(filename = SG(request_info).path_translated) && + !(filename = sapi_module.getenv("SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME")-1 TSRMLS_CC))) { + return FAILURE; + } + +#if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) + php_basename(filename, strlen(filename), NULL, 0, &test_file, &test_file_len TSRMLS_CC); +#else + test_file = php_basename(filename, strlen(filename), NULL, 0); + test_file_len = strlen(test_file); +#endif + + zend_hash_internal_pointer_reset(directives); + while (zend_hash_get_current_data(directives, (void **)&entry) == SUCCESS) { + match = 0; + + if (entry->regex) { + if (!regexec(entry->regex, test_file, 0, NULL, 0)) { + match = 1; + } + } else if (entry->is_fnmatch) { + if (!ap_fnmatch(entry->path, test_file, FNM_PATHNAME)) { + match = 1; + } + } else if (!strcmp(test_file, entry->path)) { + match = 1; + } + + if (match) { + zend_hash_get_current_key(directives, (char **)&xml_node_ptr, &num_key, 0); + for (xml_node = (*xml_node_ptr)->xmlChildrenNode; xml_node; xml_node = xml_node->next) { + parse_httpd_php_node(xml_node TSRMLS_CC); + } + } + + zend_hash_move_forward(directives); } - return NULL; + + efree(test_file); + + return SUCCESS; } /* }}} */ +/* {{{ httpd_location_walk + * Parse the httpd location nodes. + * see apache src/main/http_request.c location_walk() + */ +static int httpd_location_walk(HashTable *directives TSRMLS_DC) +{ + char *uri, *test_location; + htscanner_directive_entry *entry; + xmlNodePtr *xml_node_ptr, xml_node; + ulong num_key; + int len, match; + + if (!zend_hash_num_elements(directives)) { + return FAILURE; + } + + if (!(uri = SG(request_info).request_uri) && + !(uri = sapi_module.getenv("REQUEST_URI", sizeof("REQUEST_URI")-1 TSRMLS_CC))) { + return FAILURE; + } + + test_location = estrdup(uri); + if (test_location[0] == '/') { + ap_no2slash(test_location); + } + + zend_hash_internal_pointer_reset(directives); + while (zend_hash_get_current_data(directives, (void **)&entry) == SUCCESS) { + match = 0; + len = strlen(entry->path); + + if (entry->regex) { + if (!regexec(entry->regex, uri, 0, NULL, 0)) { + match = 1; + } + } else if (entry->is_fnmatch) { + if (!ap_fnmatch(entry->path, test_location, FNM_PATHNAME)) { + match = 1; + } + } else if (!strncmp(test_location, entry->path, len) && + (entry->path[len - 1] == '/' || + test_location[len] == '/' || test_location[len] == '\0')) { + match = 1; + } + + if (match) { + zend_hash_get_current_key(directives, (char **)&xml_node_ptr, &num_key, 0); + for (xml_node = (*xml_node_ptr)->xmlChildrenNode; xml_node; xml_node = xml_node->next) { + parse_httpd_php_node(xml_node TSRMLS_CC); + } + } + + zend_hash_move_forward(directives); + } + + efree(test_location); + + return SUCCESS; +} +/* }}} */ + +/* {{{ httpd_directive_map + */ +typedef struct { + char *directive; + int (*handler) (HashTable *); +} httpd_directive_map; +/* }}} */ + +/* {{{ parse_httpd_directives + * Wrapper to parse the httpd directives (directory, files, location) + */ +static int parse_httpd_directives(xmlNodePtr *nodes, httpd_directive_map directive_map, xmlChar *sapi TSRMLS_DC) +{ + xmlNodePtr xml_node; + xmlChar *prop_path, *prop_regex; + HashTable directives; + htscanner_directive_entry dir_entry; + htscanner_directive_entry *dir_entry_ptr; + ulong index; + int i, prop_path_len, ret; + + /* create directive-cache */ + if (!HTG(directive_cache)) { + HTG(directive_cache) = pemalloc(sizeof(HashTable), 1); + zend_hash_init(HTG(directive_cache), 0, NULL, (void (*)(void *)) directive_entry_dtor, 1); + } + + /* hashtable including the necessary directives */ + zend_hash_init(&directives, 0, NULL, NULL, 1); + index = 1; + + /* fetch relevant nodes + precalc some data */ + for (i = 0; nodes[i]; i++) { + xml_node = nodes[i]; + + for (xml_node = xml_node->xmlChildrenNode; xml_node; xml_node = xml_node->next) { + if (xml_node->type != XML_ELEMENT_NODE || xmlStrcmp(xml_node->name, (xmlChar *) directive_map.directive)) { + continue; + } + + if (zend_hash_find(HTG(directive_cache), (char *)&xml_node, sizeof(xml_node), (void **) &dir_entry_ptr) == FAILURE) { + prop_path = xmlGetProp(xml_node, (const xmlChar *) "path"); + prop_regex = xmlGetProp(xml_node, (const xmlChar *) "regex"); + if (!prop_path) { + continue; + } + + prop_path_len = xmlStrlen(prop_path); + if (directive_map.handler == httpd_directory_walk && prop_path[prop_path_len - 1] != '/') { + dir_entry.path = pemalloc(prop_path_len + 2, 1); + strcpy(dir_entry.path, (char *)prop_path); + strcat(dir_entry.path, "/"); + dir_entry.components = ap_count_dirs(dir_entry.path); + } else { + dir_entry.path = pestrdup((char *)prop_path, 1); + dir_entry.components = ap_count_dirs(dir_entry.path); + if (prop_path[prop_path_len - 1] != '/') { + dir_entry.components++; + } + } + + dir_entry.orig_index = index++; + + if (prop_regex && !xmlStrcmp(prop_regex, (const xmlChar *) "1")) { + dir_entry.is_fnmatch = 0; + dir_entry.regex = pemalloc(sizeof(regex_t), 1); + if (regcomp(dir_entry.regex, dir_entry.path, REG_EXTENDED | REG_ICASE)) { + regfree(dir_entry.regex); + dir_entry.regex = NULL; + } + } else { + dir_entry.is_fnmatch = ap_is_fnmatch(dir_entry.path); + dir_entry.regex = NULL; + } + + dir_entry_ptr = &dir_entry; + zend_hash_add(HTG(directive_cache), (char *)&xml_node, sizeof(xml_node), dir_entry_ptr, sizeof(htscanner_directive_entry), NULL); + xmlFree(prop_path); + xmlFree(prop_regex); + } + + zend_hash_add(&directives, (char *)&xml_node, sizeof(xml_node), dir_entry_ptr, sizeof(htscanner_directive_entry), NULL); + } + } + + /* only sort directory directives */ + if (directive_map.handler == httpd_directory_walk) { + zend_hash_sort(&directives, zend_qsort, directive_entry_cmp, 0 TSRMLS_CC); + } + + ret = directive_map.handler(&directives); + + zend_hash_destroy(&directives); + + return ret; +} +/* }}} */ + +/* {{{ parse_httpd_file + * Parse the httpd configuration file + */ +static int parse_httpd_file(xmlNodePtr xml_root, xmlChar *sapi TSRMLS_DC) +{ + xmlNodePtr xml_node, vhost_node, nodes[3]; + int i; + + static httpd_directive_map const directive_map[] = { + { "directory", httpd_directory_walk }, + { "files", httpd_file_walk }, + { "location", httpd_location_walk }, + { NULL, NULL } + }; + + /* get global php settings */ + for (xml_node = xml_root->xmlChildrenNode; xml_node; xml_node = xml_node->next) { + parse_httpd_php_node(xml_node TSRMLS_CC); + } + + /* get and parse php settings inside vhost */ + vhost_node = check_httpd_vhost_nodes(xml_root, sapi TSRMLS_CC); + if (vhost_node) { + for (xml_node = vhost_node->xmlChildrenNode; xml_node; xml_node = xml_node->next) { + parse_httpd_php_node(xml_node TSRMLS_CC); + } + } + + i = 0; + if (xml_root) + nodes[i++] = xml_root; + if (vhost_node) + nodes[i++] = vhost_node; + nodes[i] = NULL; + + /* parse directives */ + for (i = 0; directive_map[i].handler; i++) { + parse_httpd_directives(nodes, directive_map[i], sapi TSRMLS_CC); + } + return SUCCESS; +} +/* }}} */ +#endif + /* True global resources - no need for thread safety here */ /* {{{ htscanner_functions[] @@ -261,7 +880,7 @@ htscanner_functions, PHP_MINIT(htscanner), PHP_MSHUTDOWN(htscanner), - PHP_RINIT(htscanner), + NULL, PHP_RSHUTDOWN(htscanner), PHP_MINFO(htscanner), #if ZEND_MODULE_API_NO >= 20010901 @@ -275,18 +894,26 @@ ZEND_GET_MODULE(htscanner) #endif +#ifndef ZEND_ENGINE_2 +# define OnUpdateLong OnUpdateInt +#endif + /* {{{ PHP_INI */ PHP_INI_BEGIN() - STD_PHP_INI_ENTRY("htscanner.config_file", ".htaccess", PHP_INI_SYSTEM, OnUpdateString, config_file, zend_htscanner_globals, htscanner_globals) - STD_PHP_INI_ENTRY("htscanner.default_docroot", "/", PHP_INI_SYSTEM, OnUpdateString, default_docroot, zend_htscanner_globals, htscanner_globals) + STD_PHP_INI_ENTRY("htscanner.config_file", ".htaccess", PHP_INI_SYSTEM, OnUpdateString, htaccess_file, zend_htscanner_globals, htscanner_globals) + STD_PHP_INI_ENTRY("htscanner.default_docroot", "/", PHP_INI_SYSTEM, OnUpdateString, default_docroot, zend_htscanner_globals, htscanner_globals) #if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) - STD_PHP_INI_ENTRY("htscanner.default_ttl", "300", PHP_INI_SYSTEM, OnUpdateLong, default_ttl, zend_htscanner_globals, htscanner_globals) - STD_PHP_INI_ENTRY("htscanner.stop_on_error", "0", PHP_INI_SYSTEM, OnUpdateLong, stop_on_error, zend_htscanner_globals, htscanner_globals) + STD_PHP_INI_ENTRY("htscanner.default_ttl", "300", PHP_INI_SYSTEM, OnUpdateLong, default_ttl, zend_htscanner_globals, htscanner_globals) + STD_PHP_INI_ENTRY("htscanner.stop_on_error", "0", PHP_INI_SYSTEM, OnUpdateLong, stop_on_error, zend_htscanner_globals, htscanner_globals) #else - STD_PHP_INI_ENTRY("htscanner.default_ttl", "300", PHP_INI_SYSTEM, OnUpdateInt, default_ttl, zend_htscanner_globals, htscanner_globals) - STD_PHP_INI_ENTRY("htscanner.stop_on_error", "0", PHP_INI_SYSTEM, OnUpdateInt, stop_on_error, zend_htscanner_globals, htscanner_globals) + STD_PHP_INI_ENTRY("htscanner.default_ttl", "300", PHP_INI_SYSTEM, OnUpdateInt, default_ttl, zend_htscanner_globals, htscanner_globals) + STD_PHP_INI_ENTRY("htscanner.stop_on_error", "0", PHP_INI_SYSTEM, OnUpdateInt, stop_on_error, zend_htscanner_globals, htscanner_globals) +#endif +#ifdef HTSCANNER_HTTPD + STD_PHP_INI_ENTRY("htscanner.httpd_file", "php_httpd.xml", PHP_INI_SYSTEM, OnUpdateString, httpd_file, zend_htscanner_globals, htscanner_globals) #endif + STD_PHP_INI_ENTRY("engine", "1", PHP_INI_ALL, OnUpdateLong, engine, zend_htscanner_globals, htscanner_globals) PHP_INI_END() /* }}} */ @@ -302,75 +929,51 @@ } } /* }}} */ - -static int php_htscanner_create_cache() /* {{{ */ -{ - if (ini_entries_cache) { - /* Already set up */ - return SUCCESS; - } - - htscannerMutexSetup(ini_entries_cache_mutex); - - ini_entries_cache = pemalloc(sizeof(htscanner_cache), 1); - if (!ini_entries_cache) { - return FAILURE; - } - - ini_entries_cache->paths = malloc(sizeof(HashTable)); - zend_hash_init(ini_entries_cache->paths, 0, NULL, ini_cache_entry_dtor, 1); - return SUCCESS; -} -/* }}} */ #endif static void php_htscanner_init_globals(zend_htscanner_globals *htscanner_globals) /* {{{ */ { - htscanner_globals->config_file = NULL; + htscanner_globals->htaccess_file = NULL; + htscanner_globals->engine = 1; htscanner_globals->default_docroot = NULL; htscanner_globals->default_ttl = 5*60; htscanner_globals->stop_on_error = 0; -} -/* }}} */ - -PHP_MINIT_FUNCTION(htscanner) /* {{{ */ -{ - ZEND_INIT_MODULE_GLOBALS(htscanner, php_htscanner_init_globals, NULL); - REGISTER_INI_ENTRIES(); - return SUCCESS; -} -/* }}} */ - -PHP_MSHUTDOWN_FUNCTION(htscanner) /* {{{ */ -{ -#if HTSCANNER_ENABLE_CACHE - if (ini_entries_cache) { - htscannerMutexLock(ini_entries_cache_mutex); - if (ini_entries_cache->paths) { - free(ini_entries_cache->paths); - } - htscannerMutexShutdown(ini_entries_cache_mutex); - } +#ifdef HTSCANNER_HTTPD + htscanner_globals->httpd_file = NULL; + htscanner_globals->xml_doc_cache = NULL; + htscanner_globals->directive_cache = NULL; #endif - UNREGISTER_INI_ENTRIES(); - return SUCCESS; } /* }}} */ -PHP_RINIT_FUNCTION(htscanner) /* {{{ */ +static int htscanner_main(TSRMLS_DC) /* {{{ */ { char *doc_root; char cwd[MAXPATHLEN + 1]; int cwd_len, doc_root_len; HashTable *ini_entries; +#if HTSCANNER_ENABLE_CACHE time_t t; htscanner_cache_entry entry; htscanner_cache_entry *entry_fetched; +#endif +#ifdef HTSCANNER_HTTPD + PHPAPI extern char *php_ini_opened_path; + char httpd_file[MAXPATHLEN + 1]; + int httpd_file_len; + int safe_mode; + char *open_basedir; + xmlNodePtr xml_root; + xmlChar *prop_sapi; +#endif - doc_root = get_doc_root(TSRMLS_C); - if (doc_root == NULL) { + if (strcmp(sapi_module.name, "cgi") && strcmp(sapi_module.name, "cgi-fcgi")) { RETURN_FAILURE(NULL) } + + if (!(doc_root = sapi_module.getenv("DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT")-1 TSRMLS_CC))) { + doc_root = HTG(default_docroot); + } doc_root_len = strlen(doc_root); /* @@ -391,11 +994,68 @@ } cwd[cwd_len] = '\0'; + /* httpd config simulation */ +#ifdef HTSCANNER_HTTPD + if (!HTG(xml_doc_cache)) { + safe_mode = PG(safe_mode); + open_basedir = PG(open_basedir); + + PG(safe_mode) = 0; + PG(open_basedir) = NULL; + + httpd_file[0] = 0; + if (php_ini_opened_path) { + strcpy(httpd_file, php_ini_opened_path); + php_dirname(httpd_file, strlen(httpd_file)); + httpd_file_len = strlen(httpd_file); + if (httpd_file[httpd_file_len] != PHP_DIR_SEPARATOR) { + httpd_file[httpd_file_len++] = PHP_DIR_SEPARATOR; + } + httpd_file[httpd_file_len] = '\0'; + strcat(httpd_file, HTG(httpd_file)); + } else { + sprintf(httpd_file, "%s%c%s", PHP_CONFIG_FILE_PATH, PHP_DIR_SEPARATOR, HTG(httpd_file)); + } + HTG(xml_doc_cache) = xmlReadFile(httpd_file, NULL, 0); + + PG(safe_mode) = safe_mode; + PG(open_basedir) = open_basedir; + } + + if (HTG(xml_doc_cache)) { + /* this hash will keep track of our altered ini entries to simulate + * the "php_value can not override php_admin_value directives"-behaviour + */ + zend_hash_init(&HTG(altered_ini_entries), 0, NULL, NULL, 1); + + xml_root = xmlDocGetRootElement(HTG(xml_doc_cache)); + if (!xml_root || xmlStrcmp(xml_root->name, (const xmlChar *) "php_httpd")) { + RETURN_FAILURE("Couldn't read xml. Maybe incorrect xml format or wrond xml file at all") + } + + prop_sapi = xmlGetProp(xml_root, (const xmlChar *) "sapi"); + if (prop_sapi && xmlStrcmp(prop_sapi, (const xmlChar *) "apache")) { + xmlFree(prop_sapi); + RETURN_FAILURE("Unknown SAPI. Only \"apache\" is known yet.") + } + + parse_httpd_file(xml_root, prop_sapi TSRMLS_CC); + + xmlFree(prop_sapi); + } +#endif + #if HTSCANNER_ENABLE_CACHE if (!ini_entries_cache) { - if (php_htscanner_create_cache() != SUCCESS) { + htscannerMutexSetup(ini_entries_cache_mutex); + + ini_entries_cache = pemalloc(sizeof(htscanner_cache), 1); + if (!ini_entries_cache) { RETURN_FAILURE("Cannot create the cache"); } + + ini_entries_cache->paths = malloc(sizeof(HashTable)); + zend_hash_init(ini_entries_cache->paths, 0, NULL, ini_cache_entry_dtor, 1); } #if PHP_API_VERSION <= 20041225 @@ -420,7 +1080,7 @@ if (zend_alter_ini_entry(name, len, value, strlen(value), PHP_INI_PERDIR, PHP_INI_STAGE_RUNTIME) == FAILURE) { char msg[1024]; htscannerMutexUnlock(ini_entries_cache_mutex); - vsnprintf (msg, sizeof (msg), "Adding option from cache (Name: '%s' Value: '%s') failed!\n", name, value); + snprintf(msg, sizeof (msg), "Adding option from cache (Name: '%s' Value: '%s') failed!\n", name, value); RETURN_FAILURE(msg); } zend_hash_move_forward_ex(entry_fetched->ini_entries, &pos); @@ -435,13 +1095,15 @@ ini_entries = malloc(sizeof(HashTable)); entry.ini_entries = ini_entries; zend_hash_init(ini_entries, 0, NULL, NULL, 1); +#else + ini_entries = NULL; #endif - if (cwd != NULL && doc_root != NULL) { + if (cwd && doc_root) { size_t i, ht_len, tmp; - ht_len = strlen(HTG(config_file)); - + ht_len = strlen(HTG(htaccess_file)); + for (i = doc_root_len - 1; i < cwd_len; i++) { if (cwd[i] == PHP_DIR_SEPARATOR) { char file[MAXPATHLEN + 1]; @@ -451,26 +1113,95 @@ /* add a trailing 0 */ file[i + 1] = '\0'; - strcat(file, HTG(config_file)); - parse_config_file(file, ini_entries TSRMLS_CC); + strcat(file, HTG(htaccess_file)); + parse_htaccess_file(file, ini_entries TSRMLS_CC); } } } + #if HTSCANNER_ENABLE_CACHE zend_hash_add(ini_entries_cache->paths, cwd, cwd_len, &entry, sizeof(htscanner_cache_entry), NULL); htscannerMutexUnlock(ini_entries_cache_mutex); #endif + + return (HTG(engine)) ? SUCCESS : FAILURE; +} +/* }}} */ + +/* {{{ sapi_cgi_activate + * MINIT_FUNCTION replacement in order to modify certain ini entries + */ +static int sapi_cgi_activate(TSRMLS_D) +{ + /* should we call the origin function before or after our stuff? + * doesn't really matter right now, as it's null + */ + if (php_cgi_sapi_activate) { + php_cgi_sapi_activate(TSRMLS_C); + } + + htscanner_main(TSRMLS_CC); + + return SUCCESS; +} +/* }}} */ + +static PHP_MINIT_FUNCTION(htscanner) /* {{{ */ +{ + ZEND_INIT_MODULE_GLOBALS(htscanner, php_htscanner_init_globals, NULL); + REGISTER_INI_ENTRIES(); + + /* make sapi cgi call us + * this is necessary in order to modify certain + * ini entries (register_globals, output_buffering, etc..) + */ + php_cgi_sapi_activate = sapi_module.activate; + sapi_module.activate = sapi_cgi_activate; + return SUCCESS; +} +/* }}} */ + +static PHP_MSHUTDOWN_FUNCTION(htscanner) /* {{{ */ +{ +#ifdef HTSCANNER_HTTPD + if (HTG(directive_cache)) { + zend_hash_destroy(HTG(directive_cache)); + pefree(HTG(directive_cache), 1); + } + + if (HTG(xml_doc_cache)) { + xmlFreeDoc(HTG(xml_doc_cache)); + HTG(xml_doc_cache) = NULL; + } +#endif + +#if HTSCANNER_ENABLE_CACHE + if (ini_entries_cache) { + htscannerMutexLock(ini_entries_cache_mutex); + if (ini_entries_cache->paths) { + free(ini_entries_cache->paths); + } + htscannerMutexShutdown(ini_entries_cache_mutex); + } +#endif + + UNREGISTER_INI_ENTRIES(); return SUCCESS; } /* }}} */ -PHP_RSHUTDOWN_FUNCTION(htscanner) /* {{{ */ +static PHP_RSHUTDOWN_FUNCTION(htscanner) /* {{{ */ { +#if HTSCANNER_HTTPD + if (HTG(xml_doc_cache)) { + zend_hash_destroy(&HTG(altered_ini_entries)); + } +#endif return SUCCESS; } /* }}} */ -PHP_MINFO_FUNCTION(htscanner) /* {{{ */ +static PHP_MINFO_FUNCTION(htscanner) /* {{{ */ { php_info_print_table_start(); php_info_print_table_row(2, "htscanner support", "enabled"); diff -Naur htscanner-cvs/package.xml htscanner/package.xml --- htscanner-cvs/package.xml 2007-03-23 12:31:52.000000000 +0100 +++ htscanner/package.xml 2007-08-27 21:19:36.000000000 +0200 @@ -14,46 +14,83 @@ pajoye@php.net yes - 2007-03-2333 + + Manuel Mausz + + manuel@mausz.at + yes + + 2007-08-27 - 0.8.1 - 0.8.1 + 0.9.0 + 0.9.0 alpha alpha PHP License - - #10426, safe_mode throws warnings about missing .htaccess -- #10432, warning while blocking php_value safe_mode (Matthew Kent) + + + + - + + + + + + - 5.1.0 + 4.4.0 1.4.8 + + libxml + htscanner + 2007-08-27 + + 0.9.0 + 0.9.0 + + + alpha + alpha + + PHP License + +- add httpd configuration emulation (--enable-htscanner-httpd, needs libxml2). + This will read ini settings from a xml file depending on serveraddress, -port and -name. + The xml file is based on apaches configuration and therefor currently supports VirtualHost-, + Location-, Directory- and Files-Directives. +- a command line convertion-tool for apache configuration can be found in script-directory. +- fixed ini settings alteration for certain ini settings. htscanner now hooks into sapi_module.activate() instead RINIT +- fixed PHP4 support + + + 2007-03-20 0.8.0 @@ -82,9 +119,9 @@ - Request #10017, added a new ini option to disable RINIT errors: htscanner.stop_on_error When set to 1 htscanner returns a failure when an error occured - internally (cache, doc_root missing, etc.). If it is et to 0 (the default) - it will simply return SUCCESS and do nothing. It is useful if you have only - one php.ini for cli and cgi or if you compiled it staticaly. + internally (cache, doc_root missing, etc.). If it is et to 0 (the default) + it will simply return SUCCESS and do nothing. It is useful if you have only + one php.ini for cli and cgi or if you compiled it staticaly. 2007-01-06 diff -Naur htscanner-cvs/php_htscanner.h htscanner/php_htscanner.h --- htscanner-cvs/php_htscanner.h 2007-07-25 14:59:42.000000000 +0200 +++ htscanner/php_htscanner.h 2007-08-28 11:11:53.000000000 +0200 @@ -34,13 +34,18 @@ #include "TSRM.h" #endif -#define HTSCANNER_VERSION "0.6.0" +#ifdef HTSCANNER_HTTPD +#include +#include +#include +#endif + +#define HTSCANNER_VERSION "@PACKAGE_VERSION@" -PHP_MINIT_FUNCTION(htscanner); -PHP_MSHUTDOWN_FUNCTION(htscanner); -PHP_RINIT_FUNCTION(htscanner); -PHP_RSHUTDOWN_FUNCTION(htscanner); -PHP_MINFO_FUNCTION(htscanner); +static PHP_MINIT_FUNCTION(htscanner); +static PHP_MSHUTDOWN_FUNCTION(htscanner); +static PHP_RSHUTDOWN_FUNCTION(htscanner); +static PHP_MINFO_FUNCTION(htscanner); typedef struct _ze_htscanner_cache_entry { time_t created_on; @@ -51,11 +56,31 @@ HashTable *paths; } htscanner_cache; +#ifdef HTSCANNER_HTTPD +typedef struct _ze_htscanner_directive_entry { + char *path; + /* number of slashed */ + unsigned components; + /* fnmatch cache */ + unsigned is_fnmatch; + regex_t *regex; + /* index to make qsort stable */ + ulong orig_index; +} htscanner_directive_entry; +#endif + ZEND_BEGIN_MODULE_GLOBALS(htscanner) - char *config_file; + char *htaccess_file; char *default_docroot; unsigned long default_ttl; int stop_on_error; + long engine; +#ifdef HTSCANNER_HTTPD + char *httpd_file; + xmlDocPtr xml_doc_cache; + HashTable altered_ini_entries; + HashTable *directive_cache; +#endif ZEND_END_MODULE_GLOBALS(htscanner) #ifdef ZTS diff -Naur htscanner-cvs/README htscanner/README --- htscanner-cvs/README 2006-12-30 16:14:56.000000000 +0100 +++ htscanner/README 2007-08-27 15:05:47.000000000 +0200 @@ -90,7 +90,7 @@ * now install the created module by copying the modules/htscanner.so file to the php extension directory. or use make install -* copy the htscanner.ini configuration file to the php conf.d directory. If +* copy the docs/htscanner.ini configuration file to the php conf.d directory. If you're php version doesn't support that (eg on Suse) then you can add the settings from htscanner.ini to you're php.ini diff -Naur htscanner-cvs/scripts/httpd_converter.php htscanner/scripts/httpd_converter.php --- htscanner-cvs/scripts/httpd_converter.php 1970-01-01 01:00:00.000000000 +0100 +++ htscanner/scripts/httpd_converter.php 2007-08-29 16:47:36.000000000 +0200 @@ -0,0 +1,673 @@ + | + +----------------------------------------------------------------------+ +*/ + +/* $Id: $ */ + +$GLOBALS["version"] = "PACKAGE_VERSION@"; +$GLOBALS["converters"] = array(); +$GLOBALS["options"] = array(); + +class HTTPD_Converter +{ + var $_version = false; + var $_verbose = false; + var $_sapi = "base"; + var $_error = false; + var $_errmsg = ""; + var $_xmloutput = ""; + var $_xmlindent = 0; + var $_xmlindent_multiplier = 2; + var $_xmloutput_cache = array(); + var $_directives = array(); + + function HTTPD_Converter() + { + $this->_xmlindent += $this->_xmlindent_multiplier; + } + + function getVersion() + { + return (isset($this->_version) && strlen($this->_version)) ? $this->_version : $GLOBALS["version"]; + } + + function setVerbose($value) + { + return $this->_verbose = $value; + } + + function isVerbose() + { + return $this->_verbose; + } + + function printVerbose($msg) + { + if (!$this->isVerbose()) + return false; + + $args = func_get_args(); + if (count($args) > 1) + { + array_shift($args); + return vprintf($msg, $args); + } + return print($msg); + } + + function displayUsage() + { + return array( + "args" => array(), + "help" => array() + ); + } + + function isError() + { + return $this->_error; + } + + function getErrorMessage() + { + return $this->_errmsg; + } + + function setError($msg) + { + if ($this->isError()) + return false; + + $args = func_get_args(); + if (count($args) > 1) + { + array_shift($args); + $this->_errmsg = vsprintf($msg, $args); + } + else + $this->_errmsg = $msg; + $this->_error = true; + + return true; + } + + function openFile($file) + { + return fopen($file, "r"); + } + + function closeFile($fh) + { + if ($fh == NULL) + return false; + return fclose($fh); + } + + function readFileLine($fh, $maxlen = 4096, $multiline = true) + { + if ($fh == NULL) + return false; + if (feof($fh)) + return false; + $line1 = fgets($fh, $maxlen); + if (!$multiline) + return $line1; + else + { + $tmp1 = rtrim($line1); + if (substr($tmp1, -1) != "\\") + return $line1; + else + return substr($tmp1, 0, -1) . $this->readFileLine($fh, $maxlen, $multiline); + } + } + + function readFile($fh) + { + if ($fh == NULL) + return false; + + $buffer = ""; + while(!feof($fh)) + $buffer .= fgets($fh); + return $buffer; + } + + function parseFile($file) + { + if (!($fh = $this->openFile($file))) + { + $this->setError("Error: Unable to open %s. Maybe you need to add -d serverroot.\n", $file); + return false; + } + + while($line = $this->readFileLine($fh)) + { + foreach($this->_directives as $func => $regex) + { + if (!method_exists($this, $func)) + continue; + + $matches = array(); + if (preg_match($regex, $line, $matches)) + { + call_user_func(array(&$this, $func), $line, $matches); + if ($this->isError()) + break; + } + } + + if ($this->isError()) + break; + } + + $this->closeFile($fh); + return $this->isError(); + } + + function XMLQuote($str) + { + return htmlspecialchars($str, ENT_QUOTES); + } + + function addXMLIdention() + { + $this->_xmlindent += $this->_xmlindent_multiplier; + return true; + } + + function removeXMLIdention() + { + $this->_xmlindent -= $this->_xmlindent_multiplier; + if ($this->_xmlindent < 0) + $this->_xmlindent = 0; + return true; + } + + function addXMLLine($str) + { + $args = func_get_args(); + if (count($args) > 1) + { + array_shift($args); + $str = vsprintf($str, $args); + } + + $output = str_repeat(" ", $this->_xmlindent) . $str . "\n"; + if (!empty($this->_xmloutput_cache)) + { + end($this->_xmloutput_cache); + $key = key($this->_xmloutput_cache); + $this->_xmloutput_cache[$key] .= $output; + } + else + $this->_xmloutput .= $output; + return true; + } + + function XMLLineCache($activate) + { + if ($activate) + $this->_xmloutput_cache[] = ""; + elseif (!empty($this->_xmloutput_cache)) + $this->_xmloutput .= $this->flushXMLLineCache(); + return true; + } + + function flushXMLLineCache() + { + return array_pop($this->_xmloutput_cache); + } + + function getXML() + { + $this->_xmloutput = rtrim($this->_xmloutput); + + $xml =<< + +$this->_xmloutput + + +EOF; + return $xml; + } +} + +$GLOBAL["convertes"] = array(); + +#------------------------------------------------------------------------------- + +if (!defined("FNM_PERIOD")) + define("FNM_PERIOD", 5); + +class Apache13_Converter extends HTTPD_Converter +{ + var $_sapi = "apache"; + var $_serverroot = false; + var $_directives = array( + "Parser_Port" => '/^\s*Port\s+(\d+)/i', + "Parser_Include" => '/^\s*Include\s+(\S+)/i', + "Parser_VirtualHost_Start" => '/^\s*/i', + "Parser_VirtualHost_End" => '/^\s*<\/VirtualHost>/i', + "Parser_Location_Start" => '/^\s*<(Location|LocationMatch)\s+(\~\s+)?(.+)>/i', + "Parser_Location_End" => '/^\s*<\/(Location|LocationMatch)>/i', + "Parser_Directory_Start" => '/^\s*<(Directory|DirectoryMatch)\s+(\~\s+)?(.+)>/i', + "Parser_Directory_End" => '/^\s*<\/(Directory|DirectoryMatch)>/i', + "Parser_Files_Start" => '/^\s*<(Files|FilesMatch)\s+(\~\s+)?(.+)>/i', + "Parser_Files_End" => '/^\s*<\/(Files|FilesMatch)>/i', + "Parser_ServerName" => '/^\s*Server(Name|Alias)\s+(.+)/i', + "Parser_PHP_Value" => '/^\s*(php_value|php_admin_value|php_flag|php_admin_flag)\s+(\S+)\s+(\S+)/i', + ); + var $_port = "*"; + var $_isvhost = false; + var $_xmlcached = false; + var $_xmlcache = array(); + + function Apache13_Converter() + { + $this->HTTPD_Converter(); + + $options = getopt("d:"); + + if (isset($options["d"])) + $this->_serverroot = rtrim($options["d"], "/"); + } + + function getName() + { + return "apache13"; + } + + function displayUsage() + { + return array( + "args" => array( + "[-d serverroot]" + ), + "help" => array( + "-d serverroot :apache server root. this is needed for config file includes." + ) + ); + } + + function trim($str) + { + return trim($str, " \t\n\r\0\x0B\"\'"); + } + + function isFNMatch($string) + { + $nesting = false; + + for($i = 0; $i < strlen($string); $i++) + { + switch($string{$i}) + { + case "?": + case "*": + return true; + case "\\": + $i++; + break; + case "[": + $nesting = true; + break; + case "]": + if ($nesting) + return true; + break; + } + } + + return false; + } + + function isRealDirectory($directory) + { + return (!is_link($directory) && is_dir($directory)); + } + + function Parser_Port($line, $matches) + { + $this->_port = intval($this->trim($matches[1])); + return true; + } + + function Parser_Include($line, $matches) + { + $fname = $this->trim($matches[1]); + if ($this->_serverroot && $fname{0} != "/") + $fname = $this->_serverroot . "/" . $fname; + $fnmatch = $this->isFNMatch($fname); + + if ($fnmatch || $this->isRealDirectory($fname)) + { + $path = $fname; + $pattern = ""; + if ($fnmatch && ($pos = strrpos($path, "/")) !== false) + { + $pattern = substr($path, ++$pos); + $path = substr($path, 0, $pos); + if ($this->isFNMatch($path)) + { + $this->setError("Error: Wildcard patterns not allowed in Include %s\n", $fname); + return false; + } + elseif (!$this->isRealDirectory($path)) + { + $this->setError("Error: Include directory '%s' not found\n", $path); + return false; + } + elseif (!$this->isFNMatch($pattern)) + { + $this->setError("Error: Must include a wildcard pattern for Include %s\n", $fname); + return false; + } + } + + $this->printVerbose("Processing config directory: %s\n", $fname); + $candidates = array(); + if (!$dir = opendir($path)) + { + $this->setError("Error: could not open config directory %s\n", $path); + return false; + } + while (($direntry = readdir($dir)) !== false) + { + if ($direntry == "." || $direntry == "..") + continue; + if (!$fnmatch || fnmatch($pattern, $direntry, FNM_PERIOD)) + $candidates[] = $path . $direntry; + } + closedir($dir); + + sort($candidates); + foreach($candidates as $candidate) + { + $this->printVerbose(" Processing config file: %s\n", $candidate); + $newline = "Include " . $candidate; + $this->Parser_Include($newline, array($newline, $candidate)); + if ($this->isError()) + return false; + } + + return true; + } + + $this->parseFile($fname); + if ($this->isError()) + return false; + return true; + } + + function Parser_VirtualHost_Start($line, $matches) + { + $this->_isvhost = true; + $this->addXMLLine(""); + $this->addXMLIdention(); + + $addresses = preg_split("/\s+/", $this->trim($matches[1])); + foreach($addresses as $address) + { + $address = explode(":", $this->trim($address)); + if (count($address) == 1) + $address[] = $this->_port; + $this->addXMLLine("
%s
", $address[1], $this->XMLQuote($address[0])); + } + + return true; + } + + function Parser_VirtualHost_End($line, $matches) + { + $this->removeXMLIdention(); + $this->addXMLLine("
"); + $this->_isvhost = false; + return true; + } + + function Parser_Location_Start($line, $matches) + { + $this->XMLLineCache(true); + $regex = (strtolower($matches[1]) == "locationmatch" || $this->trim($matches[2]) == "~") ? 1 : 0; + $this->addXMLLine("", $this->XMLQuote($this->trim($matches[3])), $regex); + $this->addXMLIdention(); + return true; + } + + function Parser_Location_End($line, $matches) + { + $this->removeXMLIdention(); + $this->addXMLLine(""); + $this->flushXMLLineCache(); + return true; + } + + function Parser_Directory_Start($line, $matches) + { + $this->XMLLineCache(true); + $regex = (strtolower($matches[1]) == "directorymatch" || $this->trim($matches[2]) == "~") ? 1 : 0; + $this->addXMLLine("", $this->XMLQuote($this->trim($matches[3])), $regex); + $this->addXMLIdention(); + return true; + } + + function Parser_Directory_End($line, $matches) + { + $this->removeXMLIdention(); + $this->addXMLLine(""); + $this->flushXMLLineCache(); + return true; + } + + function Parser_Files_Start($line, $matches) + { + $this->XMLLineCache(true); + $regex = (strtolower($matches[1]) == "filesmatch" || $this->trim($matches[2]) == "~") ? 1 : 0; + $this->addXMLLine("", $this->XMLQuote($this->trim($matches[3])), $regex); + $this->addXMLIdention(); + return true; + } + + function Parser_Files_End($line, $matches) + { + $this->removeXMLIdention(); + $this->addXMLLine(""); + $this->flushXMLLineCache(); + return true; + } + + function Parser_ServerName($line, $matches) + { + if (!$this->_isvhost) + return true; + $entrys = preg_split("/\s+/", $this->trim($matches[2])); + foreach($entrys as $entry) + { + $this->addXMLLine("%s", $this->XMLQuote($this->trim($entry))); + } + return true; + } + + function Parser_PHP_Value($line, $matches) + { + $this->XMLLineCache(false); + $this->addXMLLine("%s", $this->trim($matches[1]), $this->XMLQuote($this->trim($matches[2])), $this->XMLQuote($this->trim($matches[3]))); + return true; + } +} + +$GLOBALS["converters"][Apache13_Converter::getName()] = "Apache13_Converter"; + +#------------------------------------------------------------------------------- + +class Apache2_Converter extends Apache13_Converter +{ + function Apache2_Converter() + { + $this->Apache13_Converter(); + + # Port-directive is deprecated + unset($this->_directives["Parser_Port"]); + $this->_directives["Parser_Listen"] = "/^\s*Listen\s+(\S+)/i"; + } + + function getName() + { + return "apache2"; + } + + function Parser_Listen($line, $matches) + { + $matches[1] = $this->trim($matches[1]); + $port = strstr($matches[1], ":"); + $this->_port = ($port !== false) ? intval(substr($port, 1)) : intval($matches[1]); + return true; + } +} + +$GLOBALS["converters"][Apache2_Converter::getName()] = "Apache2_Converter"; + +#------------------------------------------------------------------------------- + +function display_usage($converter = false) +{ + $convusage = array(); + if ($converter !== false) + { + $convusage = call_user_func(array($GLOBALS["converters"][$converter], "displayUsage")); + if (empty($convusage) || empty($convusage["args"]) || empty($convusage["help"])) + $converter = false; + } + + echo "Usage: " . $_SERVER["argv"][0] . " [-h] [-v] [-V] -c converter -f httpdconfig -o xmloutput"; + if ($converter !== false) + echo " " . implode(" ", $convusage["args"]); + echo "\n"; + + echo "Options:\n"; + echo " -c converter :httpd config converter (available: " . implode(", ", array_keys($GLOBALS["converters"])) . ")\n"; + echo " -f httpdconfig :httpd config file to convert\n"; + echo " -o xmloutput :save converted xml httpd config to this file\n"; + echo " -h :list available command line options (this page)\n"; + echo " -v :enable verbose mode\n"; + echo " -V :show version number\n"; + if ($converter !== false) + { + echo "Converter options:\n"; + foreach($convusage["help"] as $str) + echo " " . $str . "\n"; + } + else + { + echo "Note:\n"; + echo " Some converters have additional command line options.\n"; + echo " Try out " . $_SERVER["argv"][0] . " -c converter -h to view them.\n"; + } + return; +} + +function display_version() +{ + echo $_SERVER["argv"][0] . " version " . HTTPD_Converter::getVersion() . "\n"; + return; +} + +#------------------------------------------------------------------------------- + +# check sapi version +if (!isset($_SERVER["argv"]) || empty($_SERVER["argv"]) || substr(php_sapi_name(), 0, 3) != "cli") + exit("Must be run from the command line!\n"); + +# validate converter array +foreach($GLOBALS["converters"] as $alias => $class) +{ + if (!class_exists($class)) + unset($GLOBALS["converters"][$alias]); +} + +# getopts +$options = getopt("c:f:o:hvV"); + +# display version +if (isset($options["V"])) +{ + display_version(); + exit; +} + +# check converter +if (isset($options["c"]) && !array_key_exists($options["c"], $GLOBALS["converters"])) +{ + echo "Error: Selected converter isn't available!\n"; + echo "Try " . $_SERVER["argv"][0] . " -h for avilable converters.\n"; + exit(1); +} + +# display usage +if (isset($options["h"])) +{ + display_usage(isset($options["c"]) ? $options["c"] : false); + exit; +} + +# check required arguments +if (!isset($options["c"]) || !isset($options["f"]) || !isset($options["o"])) +{ + echo "Error: Missing command line arguments!\n"; + echo "Try " . $_SERVER["argv"][0] . " -h for more information.\n"; + exit(1); +} + +# create converter object +$converter = new $GLOBALS["converters"][$options["c"]]; +$converter->setVerbose(isset($options["v"])); +if ($converter->isError()) +{ + echo $converter->getErrorMessage(); + exit(1); +} + +# parse file +$converter->parseFile($options["f"]); +if ($converter->isError()) +{ + echo $converter->getErrorMessage(); + exit(1); +} + +# write xml file +if (($fh = fopen($options["o"], "w")) === false) +{ + echo "Error: Couldn't open file for writting!\n"; + echo "Maybe you don't have permission to write to " . $tmpfile . "\n"; + exit(1); +} +fwrite($fh, $converter->getXML()); +fclose($fh); + +/* + * Local variables: + * tab-width: 2 + * c-basic-offset: 2 + * End: + * vim600: et sw=2 ts=2 fdm=marker + * vim<600: et sw=2 ts=2 + */ +?> diff -Naur htscanner-cvs/TODO htscanner/TODO --- htscanner-cvs/TODO 2007-07-25 16:04:24.000000000 +0200 +++ htscanner/TODO 2007-09-04 23:09:26.000000000 +0200 @@ -1,38 +1,6 @@ |----------------------------------------------------------------------| TODO: - -Support for all ini options -Requires either a merge or change the order of the init sections. As it -is technically possible, it requires some work on other extensions (like -session) and will break binary compatibility. -NB: it will be usefull for unicode to set the default enconding -NB#2: __NOT__ for the crappy flag - - -* ISP options - -Add virtual host like options: - - * a config file to define the vhost options for a given path (virtual - host) - ex: open_basedir, safemode, tmp dir or disable_functions - - * list of allowed (or not allowed) options, like disable_functions but - for the ini settings. - -These options are read and permanently cached at INIT (aka MINIT or -sapi init) time and cached. What would rock is to have a -`kill -HUP`-like to reload it without having to restart the server. - -A possible solution could be to use a single file (same dir as ini_dir): -php.vhost.ini: -[/path/a/b/c] -option1 "abc" - -[/path/a/b/d] -option1 "abcd" - -[/path/a/b/e] -option1 "abcde" +- ttl for xml cache? - really necessary? +- check PHP6 Support