View Full Version : setcookie before content????
jscheuer1
08-13-2012, 09:55 PM
setcookie() defines a cookie to be sent along with the rest of the HTTP headers. Like other headers, cookies must be sent before any output from your script (this is a protocol restriction). This requires that you place calls to this function prior to any output, including <html> and <head> tags as well as any whitespace.
As some of you may know I recently authored my first real all PHP script on quotes.
Anyways, it was suggested that I use COOKIE instead of SESSION to preserve the history. I had thought about that and knew from javascript that cookies are tricky and that in PHP that they're easier in some respects and trickier in others.
It took me a little while to iron out the issues. However, in the process I discovered that the above is apparently not true. I hadn't really paid it any real attention until after I got things working. It was in the back of my mind though, so after things were working I reread it.
It just seems wrong. I'm using PHP 5.30 on a localhost Apache server via WAMP. I set the PHP cookies in the body of the page, and they work!
They also work on my live demo:
http://jscheuer1.comli.com/quotes/quote_c.php
Any ideas as to why?
keyboard
08-13-2012, 10:04 PM
Can we see the source of the new page?
It shouldn't work if you've already sent the headers.... do you have warnings and errors turned on in you php.ini?
I actually experienced something similar to this; without realising, I put a header('location:url'); on my site after the content was sent. It worked fine on wamp (I have warnings disabled), but as soon as I uploaded it to a server (with warnings enabled), my page stopped working....
jscheuer1
08-13-2012, 10:27 PM
Warnings and errors are on for the localhost server. I don't know about the live server. But clearly I'm setting the two cookies in the body:
<?php
header('Content-type: text/html; charset=utf-8');
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<title>Quote Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="quote.css" type="text/css">
<!--
// Quote Fetching Script (c)2012 John Davenport Scheuer
// as first seen in http://www.dynamicdrive.com/forums/
// username: jscheuer1 - This Notice Must Remain for Legal Use
-->
</head>
<body>
<div class=quotecontent>
<div class="quotecontainer">
<?php
if(isset($_GET['clear'])){
setcookie('quoteIndexes', "", time() - 3600);
setcookie('bigIndexRand', "", time() - 3600);
}
$qpattern = '/[^a-z]+$/i';
$quotefile = 'quotations_num.php';
$quoterights = false;
$lastUpdate = date( "j F Y", filemtime($quotefile));
include $quotefile;
$noAuthor = " ";
$theCount = count($quotations);
$numQuotes = $theCount / 2;
$maxlength = strlen($numQuotes);
$hqnum = isset($_GET['hqnum'])? abs($_GET['hqnum']) : '';
if($hqnum and is_nan($hqnum)){
$hqnum = '';
}
$bigIndexRand = isset($_COOKIE['bigIndexRand'])? explode('.', $_COOKIE['bigIndexRand']) : array();
if(isset($_GET['qnum'])){
$bigIndex = ($_GET['qnum'] + 2) % $theCount & ~1;
} elseif ($hqnum){
$bigIndex = (($hqnum -1) * 2) % $theCount & ~1;
} elseif (isset($_GET['pnum'])){
$bigIndex = ($_GET['pnum'] - 2 + $theCount) % $theCount & ~1;
} else {
$bigIndex = mt_rand(0, $theCount - 1) & ~1;
if(!isset($_GET['clear']) and isset($_COOKIE['bigIndexRand'])){
setcookie('bigIndexRand', $_COOKIE['bigIndexRand'] . ".$bigIndex", time() + 3600 * 24 * 365);
} else {
setcookie('bigIndexRand', $bigIndex, time() + 3600 * 24 * 365);
}
$bigIndexRand[] = $bigIndex;
}
if(!isset($_GET['clear']) and isset($_COOKIE['quoteIndexes'])){
$quoteIndexes = $_COOKIE['quoteIndexes'] . '.' . ($bigIndex / 2 + 1);
$quoteIndexes = explode('.', $quoteIndexes);
} else {
$quoteIndexes = $bigIndex / 2 + 1;
$quoteIndexes = array($quoteIndexes);
}
echo ($bigIndex / 2 + 1) . ' of ' . $numQuotes;
?>
<br /><br /><div class="quotations">
<?php
echo $quotations [$bigIndex];
?>
<br /><br /><div class="byline">
<?php
if ($quotations [$bigIndex + 1] != $noAuthor) {
echo "- {$quotations [$bigIndex + 1]}<br /><br />";
}
?>
</div></div>
<div class="clear">Quote File dated: <?php echo $lastUpdate; ?></div>
<div class="center">
<a href="<?php echo "{$_SERVER['PHP_SELF']}?pnum=$bigIndex" ; ?>" title="Previous in Numeric Order">Prev Quote</a>
<a href="<?php echo $_SERVER['PHP_SELF']; ?>" title="Get a Random Quote">Random Quote</a>
<a href="<?php echo "{$_SERVER['PHP_SELF']}?qnum=$bigIndex" ; ?>" title="Next in Numeric Order">Next Quote</a>
</div>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>">
Go To #: <input type="text" name="hqnum" maxlength=<?php echo $maxlength; ?> size=<?php echo $maxlength + 1; ?> title="Input Number from 1 to <?php echo $numQuotes; ?>" />
</form>
<div class="clear"></div>
<div><b>History</b>:<span class="center">
<a href="<?php echo "{$_SERVER['PHP_SELF']}?clear=clear" ; ?>" title="Clear History, Reload with a Random Quote">
Clear</a></span>
<div id="history"><?php
$quoteIndexes = array_unique(array_reverse($quoteIndexes));
function formatval($v){
$v = trim($v);// . '';
$v = str_pad($v, 3, ' ', STR_PAD_LEFT);
return $v;
}
foreach ($quoteIndexes as $val){
$bval = ($val - 1) * 2;
$asterisk = in_array($bval, $bigIndexRand)? '<span class="asterisk">*</span>' : '<span class="asteriskhidden">*</span>';
echo "<a href='{$_SERVER['PHP_SELF']}?hqnum=$val'><pre>" .
formatval($val) . "</pre></a> - $asterisk" .
preg_replace($qpattern, '', substr(strip_tags(html_entity_decode($quotations [$bval], ENT_QUOTES, 'UTF-8')), 0, 20)) .
" . . .<br/>\n";
}
setcookie('quoteIndexes', implode('.', array_reverse($quoteIndexes)), time() + 3600 * 24 * 365);
?></div><pre> </pre> <span class="asteriskhidden">-</span>
<span class="asterisk">*</span><span class="smaller">a quote chosen at random at some point</span></div>
</div>
</div>
<?php if($quoterights){echo "<div class='quoterights'>$quoterights</div>";}; ?>
<!-- The below HTML and PHP may be used (be careful, backup what you've got) to generate -->
<!-- a renumbered/reformated version of the quotes section of the quotes file -->
<!-- <textarea name="" cols="50" rows="5" wrap="off"> --><?php
/* foreach ($quotations as $key => $val){
echo ($key % 2 == 0)? "\n//#" . ($key / 2 + 1) . "\n" : '';
echo '"' . str_replace('"', '\"', $val) . "\",\n";
} */
?><!-- </textarea> -->
<!-- End Optional Renumbering/Reformatting Routine -->
<?php
//to see what the cookies are doing, uncomment the below line:
//print_r($_COOKIE);
?>
</body>
</html>
Note: The first two are for clearing the cookies if that link is clicked. The other three (even farther down in the code) are for setting the cookies. All five are in the body of the page.
djr33
08-14-2012, 05:46 AM
Sending headers after content is sent shouldn't work. I don't know about WAMP, but I'd assume it's just an exception because it's running locally. Maybe even some servers could work like that, but it's well known that it's not reliable in PHP, and I've never seen it work (but I'm not doubting that it did for you).
By the way, I suggest always using sessions instead of cookies, unless you need to store long-term information, in which case cookies will last longer (with the same limitations as in Javascript) than a session (which lasts... one session). But sessions are more reliable for important information and tracking anything while the user is on the site. So a really good solution would be to use sessions and also cookies, so the cookie acts as a long-term backup for the session, while the session still will work even if cookies are disabled.
jscheuer1
08-14-2012, 09:46 AM
It might depend upon the type of cookie. I know from javascript cookies that they may be set and read at any time. However PHP cookies cannot be read until the page is reloaded. That's partly due to the way PHP works. But it fails the logic test in that I see no reason why it has to be that way for insecure PHP cookies that are also available to javascript. You can make a secure PHP cookie that's not available to javascript. I can see how that type of cookie might not be able to be updated so easily.
In any case, I had originally written the code with $_SESSION. So it should be easy enough to bring that back. In fact, using cookies I found I had to make a parallel array for each cookie anyway for use while the page was being parsed by the server, those could as easily be session arrays as they were in the original code.
Now I'm wondering if the manual has the same restriction(s) on session though. I seem to recall something about that, I'll have to check. It was working though with session being set in the body.
And, without looking at the code too closely right at the moment, I'm pretty sure all of that (cookie and/or session activity) could be done before any content is sent/parsed.
djr33
08-14-2012, 10:33 AM
It might depend upon the type of cookie.No, it shouldn't--
I know from javascript cookies that they may be set and read at any time.This is the fundamental difference between clientside and serverside languages. Javascript is always active; PHP is only active while processing the code, then the HTML is sent as text to the browser-- after all headers are sent. This is simply how PHP works.
However PHP cookies cannot be read until the page is reloaded.That's absolutely true and due to the cookies being sent during the request. There's no way around that at all.
In the case of setting cookies, it's theoretically imaginable that a serverside language could set cookies several times-- that is, it could send headers several times, even as the page is being served. But that's not how it works or how PHP (or the internet?) is designed. Headers are sent first, and others are ignored.
Technically when PHP yells at you and says "Cannot do X. Headers already sent.", it's lying. It can do that. But the browser will ignore it. So it's not configured to do it. In short, it can't.
But it fails the logic test in that I see no reason why it has to be that way for insecure PHP cookies that are also available to javascript. You can make a secure PHP cookie that's not available to javascript. I can see how that type of cookie might not be able to be updated so easily.It's not about cookie security. Cookies, as far as I know, aren't actually secure. They simply have a setting to be sent only to PHP and not readable by JS. Otherwise they're just normal. (I'm not positive about that, so if someone else knows more, jump in to correct me.)
This all ends up being the order of operations problem described above.
In any case, I had originally written the code with $_SESSION. So it should be easy enough to bring that back. In fact, using cookies I found I had to make a parallel array for each cookie anyway for use while the page was being parsed by the server, those could as easily be session arrays as they were in the original code.If I'm imagining it correctly, you could probably store the array keys in CSV format (or look into PHP's serialize() function to store an array as a string) and use that in the cookie. Then combine that with the session method. The session would work as it is now. You'd just also have a cookie to store that data for later in case the user left. So the cookie would only be used if no session info existed and a cookie was detected-- returning user. Otherwise, use the session by default. (I mentioned that in the last post, same idea, just a little more worked out now.)
Now I'm wondering if the manual has the same restriction(s) on session though. I seem to recall something about that, I'll have to check. It was working though with session being set in the body.It's exactly the same restriction. Sessions work with a session ID cookie, so the session must be started before headers are sent. But unlike cookies, sessions (that is: $_SESSION) can be used at any time, regardless of headers or not. So you must simply include <?php session_start(); ?> as the very first item in your script and never worry about it again. All that matters is that the cookie is set to link the user to the session. All session data is stored on the server in a database (automatically configured by PHP). In fact, sessions are completely secure-- only when you decide to show the user some variable from it can they see it-- not like cookies where they are stored on the computer. So sessions are often better for security as well, either to not reveal everything and/or prevent the user from changing a value (for example, setting a progress level on a quiz, without letting the user change that number to move ahead and cheat), or to keep that information on the server instead of leaving it vulnerable on the user's computer for someone else to look at later.
(Actually, so I'm not skipping any details here, there are several other things about sessions that must be placed in the 'headers' part of the PHP, including when you want to end/destroy a session or change the session ID. In those cases, you need to do that at the top, too. But only advanced uses of sessions would ever get you to that point. The first time you'll encounter it is if you want to create a logout option in a login system. But another very easy way to never deal with this, and to avoid the headers problem is simply to use unset($_SESSION), which will destroy all data while keeping the session itself (just a shell then) open. The $_SESSION array will be reset the next time the page loads, or you can recreate an entry manually if you want. Just be sure to include isset() if you want to access any values in it after the data may be destroyed, or you'll find that the array index you're looking for isn't set.)
And, without looking at the code too closely right at the moment, I'm pretty sure all of that (cookie and/or session activity) could be done before any content is sent/parsed. Not required in this case. But while the topic is relevant (because it can come up, often in the case of trying to add code to the <head> section, after you're already processing the <body>), here's some quick info.
Sometimes with some basic logic (separating logic and content from layout), it's easy to avoid the problem. But sometimes it's not. When it's not:
The lazy method is to use an output buffer (see php.net). It's easy, but "bad" and harder on the server. It also means you don't really know how to properly work around it. But sometimes it can be useful. Very very rarely are output buffers necessary-- the only time I've ever found was when I was creating images using the GD library and one of the options didn't allow saving the file instead of outputting it.
The real method is to use some form of templating. It makes your job a lot harder, but it's worth setting up a basic templating system (whatever that means in your specific case) instead of trying to awkwardly rearrange everything to be in the right order, using lots of individually pre-processed HTML chunks, etc.
jscheuer1
08-14-2012, 11:13 AM
The manual says not to serialize for cookies as it can lead to errors. I'm already using PSV (period separated values) as opposed to CSV because the period character doesn't need to be encoded, while the comma does - an old trick I learned from javascript cookies, saving 2 characters per separator in the cookie value (allowing for more data to be stored), and making the cookie more human legible (that part has minor trade offs). It could be mistaken by code as a decimal point, but not the way I'm using it. If that were a problem (if the numbers included the decimal point) and the content was otherwise numbers only, an ordinary letter could probably be used instead.
And it's my understanding session requires cookies unless measures are taken to use a query string backup.
djr33
08-14-2012, 11:21 AM
A session requires the session ID is sent. It can be sent via cookies or get/post methods. I'm not exactly sure how that works, and my impression was that it was automatic. But perhaps not.
As for the PSV method, that's fine. I just meant in general that you can look into something like that. You could use serialize and some filter to make it safe, if you need something more than a simple list. But I've had some trouble with serialize too, so be careful with it if you do.
jscheuer1
08-14-2012, 04:24 PM
Now that I'm thinking of it, it was security:
Cookies names can be set as array names and will be available to your PHP scripts as arrays but separate cookies are stored on the user's system. Consider explode() to set one cookie with multiple names and values. It is not recommended to use serialize() for this purpose, because it can result in security holes.
I had already chosen implode/explode for storing/retrieving arrays in/from cookies. That part is just like javascript's join/split for the same purposes.
And the thing on session id without cookies, I'm not sure. I know there's at least an option to manually define and pass it (somepage.php?myvar=[SID], something like that) as a part of the query string, I think regardless of whether or not cookies are enabled. I'll have to reread that part.
What I didn't know is that the rest of the session data is on the server, that's pretty cool.
Also, from playing with this it looked like I could store an array as an array in a session variable. What's you're take on that?
Also, from playing with this it looked like I could store an array as an array in a session variable. What's you're take on that?
absolutely - global variables (like $_SESSION) function just like any other php variable, with the exception that they are available in all scopes and can have values preset by the php engine. I can't imagine $_SESSION would be very useful at all if you couldn't use multi-dimensional arrays.
Also, I'm betting that your setcookie() is working (even though it should not be) because php does a limited amount of caching on its own - even when you're out side of ?><?php tags, it doesn't output to the browser byte by byte, it waits for a decent amount of data to build up and then flushes it. So, even though it's after the output "started," your cookie is probably being set before the first actual output to the browser. I wouldn't have expected that, however, and I certainly wouldn't rely on it.
djr33
08-14-2012, 10:59 PM
I have no idea about security holes regarding serialize(). I think the reason might be that if you allow the user to have full control of the content of an array, they could manipulate the array and potentially input something dangerous or some value you did not set. But if you validate the data, you'll be safe. In this case, if you are sure to only use the values of that array as which quotes have already been used, there's nothing to worry about at all. Just don't also borrow that array for any security settings or whatever :)
To expand on your last question and traq's answer, there's something very simple and interesting about all of the superglobal variables ($_VAR format). They're just normal variables in every sense, except that they happen to be scopeless and available everywhere you could want them (and you can never make a local variable within a function that has the same name).
So this means that you can manually change the values and PHP will allow that. $_SESSION is an array that you can do whatever you want with. I very frequently have many-dimensional arrays in it. Basically $_SESSION['var1'] is one variable equivalent to $var1 and $_SESSION['var2'] is another, equivalent to $var2. And in fact I often do that-- I have some variable, let's say $var1, and then I set that in $_SESSION['var1'] to preserve the value for later.
Beyond this, you actually have complete control of all of those arrays. In certain circumstances I've actually manually changed the contents of $_GET to fit my purposes (often if I'm pre-processing before another script, especially when I'm manipulating the URL with .htaccess). You can add/remove any variable you'd like, and PHP will be fine with it, just as if it had originally come from the URL. There are no restrictions, but obviously you need to be careful. And I've also done this on occasion with $_SERVER variables such as 'QUERY_STRING' for the same reason.
Traq, that's an interesting theory. It makes sense to me, although I can't specifically verify it. Regardless, I completely agree that it's an exception and shouldn't be relied upon.
[superglobals are] just normal variables in every sense, except that they happen to be scopeless and available everywhere you could want them...
So this means that you can manually change the values and PHP will allow that. $_SESSION is an array that you can do whatever you want with... There are no restrictions, but obviously you need to be careful.
you could even do something like $_SESSION = 5; and it would work as expected. (Not particularly useful, and most likely counterproductive, but it would work just fine.)
Traq, that's an interesting theory. It makes sense to me, although I can't specifically verify it. Regardless, I completely agree that it's an exception and shouldn't be relied upon.
I remember reading about it somewhere, and I've been trying to find it for a few hours now, without luck.
BTW, John, if you ever want to write a script that way (without worrying about the order of headers/body/etc.), look into php's output control functions (http://us.php.net/manual/en/ref.outcontrol.php).
Usually (such as in this case), you can achieve same/better results by simply writing your script a little differently, but there are times when they are very useful.
Powered by vBulletin® Version 4.2.2 Copyright © 2021 vBulletin Solutions, Inc. All rights reserved.