php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #46479 virtual() prints output to browser
Submitted: 2008-11-04 13:39 UTC Modified: 2009-03-29 01:00 UTC
Votes:11
Avg. Score:4.7 ± 0.4
Reproduced:11 of 11 (100.0%)
Same Version:7 (63.6%)
Same OS:7 (63.6%)
From: rosenfield dot albert at gmail dot com Assigned:
Status: No Feedback Package: Apache2 related
PHP Version: 5.2CVS-2008-11-01 OS: win32
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: rosenfield dot albert at gmail dot com
New email:
PHP Version: OS:

 

 [2008-11-04 13:39 UTC] rosenfield dot albert at gmail dot com
Description:
------------
When execution is redirected with virtual(), before the Apache sub-request is carried out, PHP prints headers and document contents.

The document contents are never a practical problem, because you never have any - you're in the act of redirecting the request.

The headers however ARE a problem.  PHP generates some headers itself, and these are then sent to the browser.

Whoops, there we go - now the new/real destination of the request is unable to, for example, set it's own Content-Type header.  Or anything else, because PHP has already printed something completely unrelated to the contents the actual document is going to contain.

It could seem that the idea was to implement something akin to <!--#include--> with a "virtual" keyword on it (often used in IIS for ASP), but currently php's virtual() does not work like that.  Nobody uses it for this purpose, rather developers use include() and require() (often with __FILE__ or similar to hit a relative directory) because these do not flush headers.

Where virtual() is really useful is in it's ability to perform Apache sub-requests.  With this, and a simple Apache RewriteRule, any request can be re-routed to a PHP script (effectively a filter), and then routed back to the original destination after processing with virtual().

(This is an extremely convenient way to implement modular functionality in web applications, for example a security filter written in PHP.)

As is currently, a significant number of hacks are required to work around the fact that virtual() prints headers before redirection.  I personally have about a hundred lines of code that does nothing but try and predict which Content-Type the destination generates and try to coerce PHP into printing that instead, when it would have been much more practical if PHP just had discarded it's buffers in the first place.

Hereby a request to remove this automation.

With regards to backwards compatibility, I haven't been able to find anyone via Google that used virtual() in a different fashion than above.  Just in case someone really does, though, there should be a way for them to continue to have headers flushed.

With the low-to-non-existent amount of scripts depending on this flushing, I suggest that it is enough to modify the documentation.  For example, it could read "if you want PHP's auto-generated headers to be printed before the sub-request is carried out, execute flush()".


Reproduce code:
---------------
Doing virtual(whatever) reproduces the problem.

Here's a piece of code from a filter I'm using, which is perhaps more explanatory.

if ($filter_mode) {
	// Pass request on via Apache to intended destination script or static file.
	// Nesting via virtual() is broken, PHP flush()es and sends a bunch of HTTP headers.
	// Crappy.
	if ($info->content_type == "application/x-httpd-php") {
		// Try to work around broken virtual() by using include() if target happens to
		// be PHP script.
		chdir(dirname($info->filename));
		require($info->filename);
		die();
	} else if ($info->content_type == "text/x-perl") {
		// Dynamic content other than PHP must be fed through virtual(), so we have
		// no way of working around the broken behaviour of header flushing.  Sadly,
		// we'll both have to guess a content type and cross our fingers that the
		// target script does not need to modify headers...
		header("Content-Type: text/html");
		// For some reason, contrary to documentation, virtual() requires an URI
		// including query parameters, and not a filename, here.
		virtual($fulluri);
		die();
	} else if ($info->content_type == "httpd/unix-directory") {
		// See comments above and below.
		if ($block_dir_listing) {
			fail_denied(
				"Listing directory contents is not allowed on this server.\n" .
				"Please browse the web pages to find what you need.\n"
			);
		} else {
			// Switch from httpd/unix-directory (see code below if/else branch) to
			// text/html content type. Apache generates the directory html 
			// automatically.
			header("Content-Type: text/html");
			virtual($uri);
			die();
		}
	}
	// Again, since PHP insists on flushing headers when virtual() is invoked,
	// we have to apply the correct Content-Type.  For static files this can
	// be deduced by looking at the filename, which is done here via lookup_uri.
	// For other types of files, you will need to manually change the script in
	// question and/or modify httpd.conf and modify the if/else block above.
	header("Content-Type: ".$info->content_type);
	virtual($info->uri);
}


Expected result:
----------------
virtual() does not cause anything to print to the browser.


Actual result:
--------------
virtual() causes auto-generated headers to dump to the browser.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2008-11-04 22:30 UTC] jani@php.net
Since you failed to give the actual Apache version (and I doubt 
you're using 5.3 either) I'm assuming you have apache 2 something. 
Please update that information. Also, give the exact PHP version.
 [2008-11-05 11:37 UTC] rosenfield dot albert at gmail dot com
Did not realize the Apache version was relevant, sorry.

I've experienced the problem on more than one server, some of them that I administrate (some not).  Given that virtual() is one of the more 'professional tools' in the PHP 'toolbox', it is probably not used much overall by developers, so I doubt that the code behind had changed and just marked it as being in the "newest version".  Sorry about that!

A server with the newest Apache/PHP combo installed, which also has a filter installed where this is a problem is www.heidisql.com - a GET on port 80 will retrieve the version numbers (via the Server: header):

  Apache/2.2.9 (win32)
  PHP/5.2.7RC3-dev (win32)
 [2009-03-21 23:18 UTC] jani@php.net
Please try using this CVS snapshot:

  http://snaps.php.net/php5.2-latest.tar.gz
 
For Windows:

  http://windows.php.net/snapshots/


 [2009-03-26 21:53 UTC] macke at meteli dot net
Newest 5.2 snapshot has the same problem. Lines 334-335 in /sapi/php_apache.c are the same as before:

php_end_ob_buffers(1 TSRMLS_CC);
php_header(TSRMLS_C);

Is the solution as simple as removing those two lines or are there other implications? If so, could we at least add a new parameter to virtual() that disables those two? It would be backward compatible also.
 [2009-03-27 10:58 UTC] macke at meteli dot net
Since I really need this functionality (to create a intelligent proxy solution using PHP and Apache) I made a desperate try to work around the problem by modifying php source directly. I don't know if this is the right place for the ramblings of clueless php coders but here are my findings:

- latest snapshots of 5.2/5.3/6.0 all have the same issue (by looking at the code). 
- sorry, the relevant file for apache2 is not /sapi/php_apache.c but 
  sapi/apache2handler/php_functions.c

I tried commenting out these rows (98-104):

   php_end_ob_buffers(1 TSRMLS_CC);
   php_header(TSRMLS_C);
   ap_rflush(rr->main);

and something changes but the headers are still sent from php somewhere else and now the Content-type: header will always become
application/x-httpd-php.

Here are some additional information:
http://issues.apache.org/bugzilla/show_bug.cgi?id=17629
 [2009-03-29 01:00 UTC] php-bugs at lists dot php dot net
No feedback was provided for this bug for over a week, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Wed May 15 08:01:34 2024 UTC