?
Solved

What sends headers?

Posted on 2011-10-09
21
Medium Priority
?
338 Views
Last Modified: 2012-05-12
I'm trying to send headers from PHP in order to force a download of a file but I'm finding that the send is blocked:


Cannot modify header information - headers already sent by (output started at /home/cp123456/public_html/pay_direct.php:117)

But the only thing at pay_direct.php 117 is a CSS link:

117      <link rel="stylesheet" type="text/css" href="css/main.css" />

I know that's the offending line because if I put a blank line on top of it the same error is reported but at line 118.

It's hard to believe this generates a header to the browser.  Does anyone know what might be going on?

Thanks
Steve
0
Comment
Question by:steva
  • 8
  • 8
  • 5
21 Comments
 
LVL 7

Expert Comment

by:ziceva
ID: 36939755
The fact that you are sending some text (even a blank space) is interpreted as start of content (and implicitly end of headers). If you try to add more headers past the line where ANY text was output-ed, you get the error ...

So, the solution is simple: move the output AFTER all the header operation, or move all the header operations BEFORE the output ...
0
 

Author Comment

by:steva
ID: 36939814
Yes, I understand.  But how is the line

<link rel="stylesheet" type="text/css" href="css/main.css" />

putting any text in the output stream?  This line  is just something the page uses to find its CSS sheet.  It has nothing to do with sending output to the browser.
0
 
LVL 7

Expert Comment

by:ziceva
ID: 36939830
It's not important what the line does or what it means ... in the end it's text... it could be "asdasdasdas" ... the result is the same ... So, the line itself is text in the output stream ...

Let me put it somewhat different: the output stream is NOT what get's drawn on the screen by the browser ... it's the source you see when viewing the source of a web page (of course, including the headers that you only see if you use a debug tool like firebug). So the fact that the line itself does not get rendered into a graphic symbol, it doesn't mean it's not classified as "output"
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 84

Expert Comment

by:Dave Baldwin
ID: 36939905
@ziceva is telling you correctly.  You need to understand that any HTML content no matter where it is on the page is "output" that is not part of the PHP code.  Anything you can see in the "View Source" of your browser is "output".  <link rel="stylesheet" type="text/css" href="css/main.css" /> IS output that is sent to the browser.  It is not PHP code.
0
 

Author Comment

by:steva
ID: 36939914
0
 

Author Comment

by:steva
ID: 36939942
Sorry.  I hit a CR after including the image above and EE sent the whole thing off before I made my comments.

My comment was that if "anything that shows up in the source view" is considered output, then I would expect that the  first line, 108 above, would be reported as already having sent headers. Also, I can see now that it's not particularly the <link line, since when I move that line up,  the line reported as starting the headers becomes 116.

I'm not sure how I can send the headers that I want if I have to do it before the DOCTYPE line the local style rules, etc.   The code I have now  is :

<body>
<div id="wrapper">
		<div id="content">
             <?php 	include("includes/header.php");   ?>
		    <div id="thankYou">
            	<?php
				
				      /************************************************** BUYER THANK YOU  ****************************************************/
					  
					if ($httpParsedResponseAr["ACK"] == "Success") {
						$file=$downloads['pn'];  // includes path
						$array = explode("/", $file);
						$name = array_pop($array);
    					header("Content-type: application/force-download");
    					header("Content-Transfer-Encoding: Binary");
    					header("Content-length: ".filesize($file));
    					header("Content-Disposition: attachment; filename=$name");
    					readfile("$file");  

Open in new window


The body seemed to be the place to put this, but maybe not.   Maybe it has to go before the head section with the PHP that does the PayPal transaction.

Thanks for your input.
0
 
LVL 84

Accepted Solution

by:
Dave Baldwin earned 1000 total points
ID: 36939970
What you show above doesn't work at all.  A PHP file download routine needs to be in a file that has NO HTML and just returns the headers and the file data.  It would be a link in your page, not the whole routine as you are showing.  Example here: http://php.net/manual/en/function.readfile.php
0
 
LVL 7

Assisted Solution

by:ziceva
ziceva earned 1000 total points
ID: 36939993
Indeed, the line should be 108 ... there might be more than one thing wrong ... but I don't think that is very important ... the fact is that the placement of the header() calls is not correct .. the logic is off: you want to send two files in the same time: one file with the html Thank you and the download ... you cannot do that ...

One thing you could do, is only display the message and add a link to it: the link will point to a php script file that does the download part ...
0
 
LVL 7

Expert Comment

by:ziceva
ID: 36939997
Alternatively, instead of the link, you could add a little javascript that does the redirect to the download page automatically after a short timeout ...
0
 

Author Comment

by:steva
ID: 36940130
Ok, I didn't realize that the readfile() code had to be on a page without any other HTML.  Thanks for that.

But putting a link to that page seems to defeat what I was trying to do.  Once PayPal comes back to me and says that the transaction was successful I know it's ok to send the user the file so I just wanted to do it, at that moment, without any further checking  If I have a link to the page that downloads the file, that link will be visible in the source view and anyone who buys the product  can jot it down and give it to to anyone else.  

What if I put the readfile() code in the PHP at the top of the page, where the PayPal transaction is done, and before any HTML is emitted?  I'll do a flush, etc, and then let the page HTML get emitted after that, where I can say, "Thank you" etc.?

Thanks for the help.
0
 
LVL 7

Expert Comment

by:ziceva
ID: 36940140
You can try that, but it might not work in all the browsers (testing is required).

The orthodox thing to do is to keep the external link and pass it a generated key which might have more benefits: control over the number of allowed downloads, time of expiration of the link (in one word flexibility).
0
 
LVL 84

Expert Comment

by:Dave Baldwin
ID: 36940177
SourceForge and CNET have code that pops up a download window.  Maybe putting the file download PHP in a javascript popup would work.
0
 

Author Comment

by:steva
ID: 36940505
I tried my idea of letting the body HTML emit after I did the readfile() at the top of the page with the other PHP, but nothing gets emitted after the readfile(), so no HTML appears

Using your idea, Dave, of having a link to call a separate download.php page that has nothing but the PHP to do the readfile() works fine, but, as I was afraid, the link is visible to all.  

Perhaps I could require on the download.php page that HTTP_REFERRER be the page that does the PayPal transaction and normally calls download.php if the transaction is successful. Then at least people couldn't just plug the download.php page into a URL in their browser.

Anyway, thanks for the help, guys.  I split the points.

0
 
LVL 84

Expert Comment

by:Dave Baldwin
ID: 36940648
A simple page that you pass the file URL to is going to show but there are ways around that.  Most of us when we use a PHP file download page, send a code and not the actual URL to the page.  In addition, we check to make sure the person is logged in and has permission or rights to do download the file.  
0
 

Author Comment

by:steva
ID: 36940811
I don't pass the actual URL to the file.  I pass a product number.  The download.php script that gets called by the link uses the product number to index an array that holds the file URL for each product.  But still, if someone sees "download.php?pn=1"  they could enter that into a browser, which would be as good as having the file url if the request got to the download.php script.  

Checking that HTTP_REFERER =" https://www.mysite/pay_direct.php" works and seems like it will keep out people who don't come from the right page on my site, which would be anyone who tried to enter something in their browser.  But if someone is able to write HTTP_REFERER to whatever they want, I have no defense.

Regarding login and permissions, the site doesn't log people in.  It's open to anyone.
0
 
LVL 7

Expert Comment

by:ziceva
ID: 36940828
Firstly, the HTTP_REFERER is no good ... if Private browsing is activated, the legit users will not be able to download ...

I will reiterate an idea I gave you before: when creating the link, besides the product id, add a secret code that is used to control the download ... If the link is accessed twice with the same code, you can take whatever measure is necessary ..
0
 

Author Comment

by:steva
ID: 36942682
ziceva:

"Firstly, the HTTP_REFERER is no good ... if Private browsing is activated, the legit users will not be able to download"
 


So you're saying that people can turn on a privacy mode in  their browser  that stops the browser from sending HTTP_REFERER? Doesn't that also break all the mod_rewrite rules out there that are looking at HTTP_REFERER?

Regarding the "secret code" idea, if I understand you you're saying to include a key in the link, maybe the session ID,  and in the download.php script, see if it's already in a database.  If not, store it in the database and proceed with the download. If it's already there block  the download.  Or  better may be to present a link to them with the key, and honor that link (key) for 24 hours.  Then if something goes wrong with the download they can still get their product by putting the link into a browser later.  I could even send the link to them  in a thank you email.

Sorry this question is dragging on with no extra points to offer.  I appreciate the help.
0
 
LVL 7

Expert Comment

by:ziceva
ID: 36942758
I never tested the HTTP_REFERER, but a past project pointed to this behavior ...

Nevertheless, it is a BAD idea to rely on HTTP_REFERER ... this variable can be easily manipulated by an attacker ...

And yes, that was the idea with the secret key ...
0
 
LVL 84

Expert Comment

by:Dave Baldwin
ID: 36943722
I agree about the 'secret key' idea, I was suggesting something similar.  There should be something to identify a 'legitimate' request.
0
 
LVL 7

Expert Comment

by:ziceva
ID: 36943744
The PayPal IPN should come in handy in this situation ... There are mechanisms implemented in the IPN system that allow for the validation of the requests ...
0
 

Author Comment

by:steva
ID: 36953983
I have all this working now. Thanks for your help.

Regarding the PayPal IPN, I've used this before but I've never found it to be "handy."  It's a pain to get working properly.  The ACK=SUCCESS from PayPal  is really the only validation I need for the purchase, and this comes back from the Payments Pro DirectPayment API without setting up IPN.

Thanks again.


0

Featured Post

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

These days socially coordinated efforts have turned into a critical requirement for enterprises.
There are times when I have encountered the need to decompress a response from a PHP request. This is how it's done, but you must have control of the request and you can set the Accept-Encoding header.
Learn how to match and substitute tagged data using PHP regular expressions. Demonstrated on Windows 7, but also applies to other operating systems. Demonstrated technique applies to PHP (all versions) and Firefox, but very similar techniques will w…
The viewer will learn how to look for a specific file type in a local or remote server directory using PHP.
Suggested Courses
Course of the Month17 days, 2 hours left to enroll

862 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question