I'm in the process of moving a working apache/mod-php website to nginx/php-fpm.
Under Apache/mod-php, I could use header("Location: $url"); to redirect the browser to a different page, such as after a login attempt. After switching to nginx/php-fpm, the browser would no longer follow that redirect on certain pages. I confirmed with Firebug and Httpfox that the header "Location: [url]" was actually being received in the response. This behavior also appears in Chrome (not tested in IE).
So I did a few experiments, read a few things about http, and made it work, but I'm not sure why it works (or why it didn't).
The solution I came up with was to send a "Status: 303" header before the "Location: [url]" header. This works in Chrome and Firefox, which both ignored the Location header when I sent "Status: 200" or omitted the Status header, but did the redirect when I changed it to "Status 303". It worked with Status 200 under Apache.
Is the Status header required to use the Location header? Or was Apache doing something else to make it work? I've not changed any of the php code involved other than the header("Status: 303"); line that made it work. There has to be something else at work here, but I have no clue what it could be.
Any ideas?
The Location header does not, by itself, trigger the browser to redirect. The redirect is actually triggered by an HTTP response code that is in the 3xx series. w3c has explanations for all http response codes.
Apache automatically sees the Location header in the response, and forces the response code to be 300-series if you haven't previously set a response code of your own. Nginx does not do this -- it expects you set the proper response code yourself.
You can force php to send a modified HTTP response code like this:
<?php
header("HTTP/1.0 301 Moved Permanently");
?>
...Then, of course, you'll need to be sure you still sent your Location header after sending the HTTP/1.0... line shown above.
Related
I know this is a really simple question, but after searching a lot I can't find how to set headers.
Here's what I want to do:
Whenever the user goes to https://www.google.com/, then an extension should log that into a logfile. I currently am developing the extension on my own device, and using http://localhost/log.php to get POST requests and log information to files based on the information POSTed. My manifest is working and I have tested it. Below is my track.js:
function log(info){
var xhttp=new XMLHttpRequest();
xhttp.onreadystatechange=function(){
if(this.readyState===4&&this.status===200){
console.log(this.responseText);
}
}
xhttp.open("POST","http://localhost/log.php",true);
// xhttp.setRequestHeader("Access-Control-Allow-Origin","*");
// above: my first attempt to set a header (did not work)
xhttp.send("log_info="+info);
}
if(location.href==="https://www.google.com/")log("We're at google.com!");
This is my log.php:
<?php
header("Access-Control-Allow-Origin: *");
if(isset($_POST["log_item"]))
file_put_contents("log.txt", $_POST["log_item"]."\n",FILE_APPEND);
And log.txt is an empty file. I know CORS is the problem because when I open the console on https://www.google.com/, I see this:
Access to XMLHttpRequest at 'http://localhost/log.php' from origin 'https://www.google.com' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.
POST http://localhost/log.php net::ERR_FAILED
This seems really simple to me but I can't find how to set headers. Thanks for any help.
You should add "Access-Control-Allow-Origin: *" on your webserver configuration (Nginx or Apache or ...) that you are currently using on your localhost.
You can not bypass CORS from your code or your request at all.
For nginx (/etc/nginx/nginx.conf):
https://enable-cors.org/server_nginx.html
Apache:
https://enable-cors.org/server_apache.html
I've got a problem with Safari I haven't been able to solve:
<?php
header("Location: ftp://username:password#somedomain.org/somefile.zip");
?>
This code-snippet works in every browser (Fx, Chrome, IE7-9), but not in the latest Safari, which tells me that I don't have permission to view the page (that is, it redirects to the correct page [somedomain.org] with the correct protocol, but does not process the authentication data).
Interestingly it works when I copy it directly to the address bar or when I put in in an <a>-tag an click on it. Is this a Safari bug, or am I missing something here, which the other browsers ignore? And if it's a Safari bug, is there some kind of workaround?
Try:
header('HTTP/1.1 301 Moved Permanently');
header('Location: ftp://username:password#somedomain.org/somefile.zip');
if it doesn't work try :
echo <<< EOF
<META HTTP-EQUIV="Refresh" CONTENT="0;URL=ftp://username:password#somedomain.org/somefile.zip">
EOF;
Or:
header ('Location: ftp://username:password#somedomain.org/somefile.zip');
header ('Content-Length: 0');
The last solution I got from: http://www.ultrashock.com/forum/viewthread/90424/
echo "window.location='ftp://username:password#somedomain.org/somefile.zip';";
Try that JS redirect and see if it's PHP or the request it has a problem with.
You are probably missing some header() info it needs like - header("Status: 200");... A while back header redirect did not work w/o this line in chrome.
You could make the PHP script fetch the file in the background, and serve it to the browser. As in, the PHP script acts as a proxy.
Assuming your download is not larger than your webserver's PHP mem limit, this will be OK.
Benefits:
-There is no redirect.
-You would not be exposing the user/pass in the URL.
If redirecting to the pw protected FTP is your decision, consider the alternatives. There are lots of ways to secure access to a file via http. These days, HTTP performance is about equal to that of FTP (long ago this was not true, and fast sites needed FTP for downloading).
do you receive any error or just not working?
make sure the the output buffer is set correctly in the phpconfig
I had similar problems, and it turned out to be the keychain! If you ever allowed safari, omniweb or opera (or the ftp process in you case) to store the username and password in the keychain for that site, this will be sent to the site instead of the one in the URL. FireFox doesn't do this, so things work as expected there.
When I set a custom header in Apache + mod_php5, this works fine:
header('Foo: Bar');
But when I try this while also sending a 304 Not Modified response, the header appears to be removed by apache (along with X-Powered-By and other standard headers).
header('HTTP/1.1 304 No Content');
header('Foo: Bar');
Does anyone know how to solve this issue?
Does this not answer the question?
If the conditional GET used a strong cache validator (see section 13.3.3), the response SHOULD NOT include other entity-headers. Otherwise (i.e., the conditional GET used a weak validator), the response MUST NOT include other entity-headers; this prevents inconsistencies between cached entity-bodies and updated headers.
from http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
As of Apache 2.4.23 (the latest release as of today, as far as I know), you're not going to be able to get around that problem when you send a 304 "Not Modified" response because, indeed, Apache does explicitly remove all non-whitelisted headers:
http://svn.apache.org/viewvc/httpd/httpd/tags/2.4.23/modules/http/http_filters.c?view=markup#l1331
So, whether we like it or not (because I'm on the same boat of having my CORS headers removed by Apache from the response when I send a 304), it does seem like Apache is following the RFC recommendation and it's indeed treating everything that falls outside of that list as entity headers.
One solution is to patch-up the Apache source to extend that list and turn to deploying your home-grown package to your server(s), but that's definitely not without a long list of implications of its own. On the flip side, I hear that nginx doesn't suffer from this problem.
The content that I'm delivering will be consumed, among others, by WebGL runtimes in standard browsers, so if they do complain about the lack of CORS in my 304 responses I'm going to have to turn everything to 200 OK and forego the bandwidth savings.
I do a trick to solve this issue by :
1. put all header before 304 header
2. flush these header before send 304
header('Foo: Bar');
flush();
header('HTTP/1.1 304 No Content');
Apache will not remove any header until it found 304.
We force other header send out by flush() before send 304.
That apache can not hurt me.
Try:
header('Foo: bar', true, 304);
i just want to ask if do i need to set something to enable the cookies from my hosting?
i have this
<?php
setcookie("TestCookie","Hello",time()+3600);
print_r($_COOKIE);
?>
it will function perfectly at my server which is xampp. but when i upload it to my hosting,
it will not function.. what should i do? or what will i add to the code?
It is also possible that the cookie is actually sent but the client doesn't sent the value back to the webserver in subsequent requests.
Possible causes:
your server's clock is misconfigured and therefore time()+3600 is in the past from the client's perspective => the client will delete the cookie immediately.
the cookie failed the general tail match check, search for "tail match" in http://curl.haxx.se/rfc/cookie_spec.html
the client is configured not to accept those cookies
There are many addons for different browsers that let you see the http headers the client actually received. E.g. Firebug for Firefox. Use them to check if there is a Set-cookie header in the response. If there is the client for some reasons rejected it. If there is no such header you have to check why the server didn't sent it.
Cookies are sent as http response headers. Those headers can only be sent before anything from the response body has been sent.
Make sure no output happens before setcookie():
no echo, printf, readfile
nothing outside the <?php ?> tags, not a single white-space or BOM in any of the scripts that have been included and executed for the same request before setcookie()
increase the error_reporting level and check for warnings/notices. Those messages are usually logged in a file (e.g. error.log). You can set display_errors to true to see them in the output.
PHP can be configured to use output buffers. In this case output is not sent directly to the client but held in a buffer until either it's full, the buffer is flushed or the script ends (implicit flush). So until the content of the buffer is actually sent to the client you can set/modify/delete http headers like cookies. see http://docs.php.net/outcontrol.configuration
Use web developer toolbar to view cookie.
My problem days in practicing PHP reminds me:
there may be new line ahead of <?php [this occurred to me when I use some proxies to upload]
free hosting providers include("top_ads.php") that is put on top of your php file [this occurred to me when I used free hosting]
If I'm not mistaken... Cookies are not accessible on the same page - has to be on the next page. Once cookies are set, you need to forward to another page via a header(); function, and THEN the $_COOKIE vars become accessible. It's not meant to work on the same page.
I know in PHP, it sends the X-Powered-By header to have the PHP version.
I also know by appending some checksums, you can get access to PHP's credits, and some random images (more info here).
I also know in php.ini you can turn expose_php = off.
But here is something I have done on a few sites, and that is use
header('X-Powered-By: Alex');
When I view the headers, I can see that it is now 'Alex' instead of the PHP version. My question is, will this send the previous PHP header first (before it reaches my header(), and is it detectable by any sniffer program? Or are headers 'collected' by PHP, before being sent back to the browser?
By the way, this is not for security by obscurity, just curious how headers work in PHP.
You can set expose_php = Off in your php.ini if you don't want it to send X-Powered-By header.
PHP first compiles everything (including which headers have which values ) and then start the output, not vice-versa.
PHP is also detectable with its own easter eggs, you can read about this topic here : PHP Easter Eggs
See Apache Tips & Tricks: Hide PHP version (X-Powered-By)
Ups… As we can see PHP adds its own
banner:
X-Powered-By: PHP/5.1.2-1+b1…
Let’s see how we can disable it. In
order to prevent PHP from exposing the
fact that it is installed on the
server, by adding its signature to the
web server header we need to locate in
php.ini the variable expose_php and turn it off.
By default expose_php is set to On.
In your php.ini (based on your Linux
distribution this can be found in
various places, like /etc/php.ini,
/etc/php5/apache2/php.ini, etc.)
locate the line containing expose_php
On and set it to Off:
expose_php = Off
After making this change PHP will no
longer add it’s signature to the web
server header. Doing this, will
not make your server more secure… it will just prevent remote hosts to
easily see that you have PHP installed
on the system and what version you are
running.
In PHP, headers aren't sent until PHP encounters its first output statement.
This includes anything before the first <?php.
This is also why setcookie sends throws a warning if you try to use it after something has been output:
Warning: Cannot modify header
information - headers already sent by
(output started at
/path/to/php/file.php:100) in
/path/to/php/file.php on line 150
Note that none of this applies if output buffering is in use, as the output will not be sent until the appropriate output buffering command is run.
To get rid of the X-Powered-By header without having access to php.ini, simply add an empty header.
<?php header('X-Powered-By:'); ?>
This overwrites the default X-Powered-By header with an empty value an though most clients and applications act like this header was not sent at all.
As noticed before, this must be inserted into the code before any output is sent.
And to answer your question:
Only your X-Powered-By header will be sent because it gets replaced by your header with the same name. So it can't be detected by a 'sniffer'.
Headers are "collected" by PHP before being sent back to the browser, so that you can override things like the status header. The way to test it is go to a command prompt, and type:
telnet www.yoursite.com 80
GET /index.php HTTP/1.1
[ENTER]
[ENTER]
And you'll see the headers that are sent in the response (replace /index.php with the URL of your PHP page after the domain.)
To hide X-Powered-By: PHP/7.x.x , if you are using Share Hosting then add the following code in .htaccess file
Header always unset X-Powered-By
Header unset X-Powered-By
Then reload the browser or clear the cache using the LiteSpeed Cache plugin: https://en.wordpress.org/plugins/litespeed-cache/
My question is, will this send the previous PHP header first (before it reaches my header(), and is it detectable by any sniffer program? Or are headers 'collected' by PHP, before being sent back to the browser?
No, it does not send the previous PHP header first. Headers are either sent or not sent (in complete, as one batch) in PHP. By default your headerDocs call replaces a previous header with the same name (unless you specify something different with the second parameter).
Note: If PHP would not collect the headers, it would not be able to replace one.
As it does not sent it earlier, it is not detectable with a sniffer program.
So yes, headers are collected by PHP and are send the moment "the real" output starts (HTTP response body).
See as well headers_sentDocs.
PHP has a built-in function to remove headers: header_remove().
To remove the X-Powered-By header, you can use:
<?php
header_remove(
name: 'X-Powered-By'
);
As you can see, you only have to pass the header name as a string as parameter, and you are done.
Note that name parameter is parsed not case-sensitive, so you are fine calling it with x-powered-by as well.
Since PHP 8.0.0 when calling the function without the name parameter, all previously set headers will be unset.