Download | Plain Text | Line Numbers


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 <string.h>
+#include <stdio.h>
+
+#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 <bart dot vanbrabant at zoeloelip dot be>   |
   |          Pierre-Alain Joye <pierre@php.net>                          |
+  |          Manuel Mausz <manuel@mausz.at>                              |
   +----------------------------------------------------------------------+
 */
 
@@ -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 @@
     <email>pajoye@php.net</email>
     <active>yes</active>
   </lead>
-  <date>2007-03-2333</date>
+  <developer>
+    <name>Manuel Mausz</name>
+    <user></user>
+    <email>manuel@mausz.at</email>
+    <active>yes</active>
+  </developer>
+  <date>2007-08-27</date>
   <version>
-    <release>0.8.1</release>
-    <api>0.8.1</api>
+    <release>0.9.0</release>
+    <api>0.9.0</api>
   </version>
   <stability>
     <release>alpha</release>
     <api>alpha</api>
   </stability>
   <license uri="http://www.php.net/license">PHP License</license>
-  <notes>- #10426, safe_mode throws warnings about missing .htaccess
-- #10432, warning while blocking php_value safe_mode (Matthew Kent)</notes>
   <contents>
     <dir name="/">
+      <file name="ap_fnmatch.c" role="src"/>
+      <file name="ap_fnmatch.h" role="src"/>
+      <file name="ap_util.c" role="src"/>
+      <file name="ap_util.h" role="src"/>
       <file name="config.m4" role="src"/>
       <file name="htscanner.c" role="src">
         <tasks:replace from="@PACKAGE_VERSION@" to="version" type="package-info"/>
       </file>
-      <file name="php_htscanner.h" role="src"/>
+      <file name="php_htscanner.h" role="src">
+        <tasks:replace from="@PACKAGE_VERSION@" to="version" type="package-info"/>
+      </file>
       <file name="CREDITS" role="doc"/>
       <file name="README" role="doc"/>
       <dir name="docs">
         <file name="htscanner.ini" role="src"/>
       </dir>
+      <dir name="scripts">
+        <file name="httpd_converter.php" role="src"/>
+      </dir>
     </dir>
   </contents>
   <dependencies>
     <required>
       <php>
-        <min>5.1.0</min>
+        <min>4.4.0</min>
       </php>
       <pearinstaller>
         <min>1.4.8</min>
       </pearinstaller>
+      <extension>
+        <min>libxml</min>
+      </extension>
     </required>
   </dependencies>
   <providesextension>htscanner</providesextension>
   <extsrcrelease/>
   <changelog>
     <release>
+      <date>2007-08-27</date>
+      <version>
+        <release>0.9.0</release>
+        <api>0.9.0</api>
+      </version>
+      <stability>
+        <release>alpha</release>
+        <api>alpha</api>
+      </stability>
+      <license uri="http://www.php.net/license">PHP License</license>
+      <notes>
+- 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
+      </notes>
+    </release>
+    <release>
       <date>2007-03-20</date>
       <version>
         <release>0.8.0</release>
@@ -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.</notes>
+  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.</notes>
     </release>
     <release>
       <date>2007-01-06</date>
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 <libxml/tree.h>
+#include <libxml/tree.h>
+#include <regex/regex.h>
+#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 @@
+<?php
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 4 and 5                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2004 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.0 of the PHP license,       |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_0.txt.                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | license@php.net so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Manuel Mausz <manuel@mausz.at>                              |
+  +----------------------------------------------------------------------+
+*/
+
+/* $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 =<<<EOF
+<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
+<php_httpd sapi="$this->_sapi">
+$this->_xmloutput
+</php_httpd>
+
+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*<VirtualHost\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("<virtualhost>");
+    $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("<address port=\"%d\">%s</address>", $address[1], $this->XMLQuote($address[0]));
+    }
+
+    return true;
+  }
+
+  function Parser_VirtualHost_End($line, $matches)
+  {
+    $this->removeXMLIdention();
+    $this->addXMLLine("</virtualhost>");
+    $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("<location path=\"%s\" regex=\"%d\">", $this->XMLQuote($this->trim($matches[3])), $regex);
+    $this->addXMLIdention();
+    return true;
+  }
+
+  function Parser_Location_End($line, $matches)
+  {
+    $this->removeXMLIdention();
+    $this->addXMLLine("</location>");
+    $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("<directory path=\"%s\" regex=\"%d\">", $this->XMLQuote($this->trim($matches[3])), $regex);
+    $this->addXMLIdention();
+    return true;
+  }
+
+  function Parser_Directory_End($line, $matches)
+  {
+    $this->removeXMLIdention();
+    $this->addXMLLine("</directory>");
+    $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("<files path=\"%s\" regex=\"%d\">", $this->XMLQuote($this->trim($matches[3])), $regex);
+    $this->addXMLIdention();
+    return true;
+  }
+
+  function Parser_Files_End($line, $matches)
+  {
+    $this->removeXMLIdention();
+    $this->addXMLLine("</files>");
+    $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("<servername>%s</servername>", $this->XMLQuote($this->trim($entry)));
+    }
+    return true;
+  }
+
+  function Parser_PHP_Value($line, $matches)
+  {
+    $this->XMLLineCache(false);
+    $this->addXMLLine("<php type=\"%s\" name=\"%s\">%s</php>", $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