Nacho37 has asked for the wisdom of the Perl Monks concerning the following question:

May grace favor the sanctuary-

As a new initiate into the monestary, I feel somewhat timid in posting so soon, expecting knowledge so rapidly after giving little in return yet.. I hope that I may be favored with a reply...

I am writing a CGI mileage calculator for my employer between several fixed locations. Each time thru, the trip is given four variables to remember ($date, $source, $destination, $miles). These are stored in an array (@table), and output in an html table for the user to see. @table is stored in a cookie (trip_history), then recalled when the script is called again as the user re-submits and adds four new $date, $source, $destination, $miles. These four values are placed at the beginning of @table with unshift(). When the html table is regenerated again, the first value in the array $table(0) is the list stored in the cookie! I have the cookie formatted as a comma-delimited list, but the first value reads like "1, HHS, GHS, 6.7, 2, GHS, MHS, 4.2" instead of $table(0) being 1, $table(1)=HHS, $table(2)=GHS, and so on.

How do I put an array into a cookie, then retrieve it still as an array and modify it yet again and store it as a cookie and retrieve it again...?

Replies are listed 'Best First'.
(Ovid) Re: Storing an array in a cookie...
by Ovid (Cardinal) on Feb 27, 2002 at 17:45 UTC

    What you're trying to do is maintain state in an inherently stateless protocl (HTTP). To get around this, typically the user is given a a sessionID and this is tied to state information that is stored in a database. That's a more robust solution. For something quick 'n dirty, you could use Storable to serialize your data structure, store it in a cookie, and later retrieve it and deserialize it. Here's a small code sample to show you how this is done (minus using a cookie):

    use strict; use warnings; use Storable qw/ freeze thaw /; use Data::Dumper; my %foo = ( one => [ qw/ a b c / ], two => [ qw/ 1 2 3 / ] ); my $serialized = freeze \%foo; my %clone = %{ thaw $serialized }; print Dumper \%clone;

    For the snippet above, you would store $serialized in the cookie. I've never actually tried this, but it should work. However, remember that this is not a good long-term solution. Amongst other things, cookies accept a max of 4K of data and will truncate anything over that. If you rely on this technique too much, you'll get bit. Further, this increases bandwidth (slightly) and setting too many cookies (by overusing this technique) will also cause problems.

    Good luck!

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

•Re: Storing an array in a cookie...
by merlyn (Sage) on Feb 27, 2002 at 17:49 UTC
Re: Storing an array in a cookie...
by Speedy (Monk) on Feb 27, 2002 at 19:53 UTC
    From your problem description, it sounds as though you may plan to base mileage reimbursements (actual money payments) on the data entered and retrieved over some time for the application users. If so, the advice given above needs to be further emphasized – do not use a cookie to store unvarnished information on which money depends.

    Firstly, depending on how you encode things, a user could read the cookie and actually change the results, adding more miles or more trips than were originally entered. If you don't have a record somewhere else, whom is to reliably say them nay?

    Secondly, as pointed out above, cookies are browser- and client-dependent. What do you plan if cookies are turned off, or the user enters the data at work one time and at home the second? Your script will not have access to both computers at once, so both for an audit trail and for data preservation, you must store the results in some structure at your Web server.

    Cookies are useful for quick identification and the display of more-or-less public information. But if money is riding on the data entry, you want to be more sure that the proper person is entering the data. If so, you may have to go to a password scheme or some other method of authentication before you actually allow a user to enter or view personal data.

    Consider how amazon.com works. You can see recommendations for purchases based on your cookie setting alone, but the minute you try to see information that should be private, like status of orders, entering an order, or even a history of past orders, you have to have a password to at least at some level verify you are who you say you are.

    If in fact cash payments (affecting business expenses and taxes) are involved, you will want a more robust security and storage solution.

    Live in the moment
Re: Storing an array in a cookie...
by drinkd (Pilgrim) on Feb 28, 2002 at 00:48 UTC
    Loathe as I am to disagree with such as these, I must do so now. IMHO, Internet calculators are a great application for cookies.

    When I put something in the memory of my calculator on my desk, it is not a system-generated price, or secret, or whatever, but just a number I already typed in once and don't want to type in again (at least for the next few hours/days). I don't expect this memory to last forever, or get put in a database, or follow me to the calculator at home, I just want it to be there when I show up tomorrow morning without having to leave my browser on all night.

    It looks like what you are doing is just storing values already entered by the user so they don't have to type them in again. You don't care if the user edits the cookie, or if the values follow her to a different computer.

    Here is the code snippet I wrote for a type of internet calculator that uses a similar kluge to yours. A 2-D array of atoms with upper and lower limits is stored in a string using a space to delimit in one dimension and a . to delimit in the other dimension. The calculator will determine all molecular formulas containing these elements within these limits that have a given exact mass. The data can be slow to enter and successive forms will only require slight changes to the input data, so it will be kept in a cookie for 3 days so I can leave for the night without having to re-enter the array in the morning.

    if(param()){$cookval=param('entercook');} elsif(cookie('testcook')){$cookval=cookie('testcook');} else{$cookval="O 0 10.N 0 20.C 1 40.H 1 100";} $cookie = cookie(-name=>'testcook', -value=>$cookval, -expires=>'+3h');

    In case you're interested, this will test to see if the form has been submitted with new input data, failing that it will check for the data in a cookie, and failing that will set the cookie to a default value.

    I still haven't answered your question, but perhaps one of our more enlightened bretheren can shed some light on the matter (is storable the best way?).

    drinkd

      I dissagree. IMHO cookies should only really be used for session id's simply becuase of the random volatile nature of them.

      I especially disagree in this case becuase if the data in the cookie is used for cash reimbursement is it particularly poor design IMO. In my experience with end users, sales reps on the road etc are not particularly technical, and can do amazing things to their machines (deleting directories etc), hence losing their data and the cash they would have got from it.

      Having a persistant data store need not be difficult. It can be as easy as a flat file with DBI (or not) or be as complex as a many table, lots of data, normalised RDBMS information system.

      I'm on the wagon of using the cookie for a sess_id then cross referencing it to a user and associated data.

      ++ for being brave enuff to kick the wagon and offer an alternative backed up by argument.

        This application is meant as a "Sit and do it all at one time" application. I don't care about the cookie after the user enters his data 20 or 30 times and prints the final formatted page. The page is submitted to a higher beaurocracy than I for verification anyways. I am just looking to save several hundred people an hour's time each month in authoring mileage reimbursements. I want to get away from flipping thru several pages of "official mileage reimbursement distances" and just have people enter data one trip at a time and have the math done for them. I want it dated for each trip, in such a way that they specify what day of the month they made the trip. Thanks all for the insight into cookies, size limits, etc.

        Maybe someday I will incorporate authentication, stored databases of user mileage, higher security and the rest, but for now all I need to do is get the data to flow properly from query to query.

Re: Storing an array in a cookie...
by Nacho37 (Initiate) on Mar 05, 2002 at 22:31 UTC
    Submitted for the perusal of the monestary:

    Here is the code for the cgi program. Now, at the line separated by asterisks from the rest of the code, I have an error. My httpd error log gives the following explanation:

    "Can't use an undefined value as an ARRAY reference at /var/www/cgi-bin/perl/distance-test.cgi line 39."

    Ovid was kind enough to show me an explanation here once, but when I attempted to implement his suggestion, I am evidently short some info. I guess I need help using "thaw".

    #! /usr/bin/perl -w use strict; use Storable qw/freeze thaw/; use CGI ':standard'; my $source; my $destination; my $oldtotal; my $miles; my $date; my $tripdate; my $tripcounter; my $tablesize; my @table; #master array to carry data from previous iterat +ions #my @newdata; #new array to push array-style data into @table #my @tablereform; #new array to take care of spaces in items in @t +able #my %spaghetti; #to remind myself that my code is horribly convo +luted #my %sauce; #to keep spaghetti company #my $i; #for the "for" loop that creates the table my $e; #for the "for" loop that controls the number of e +lements in @table my $m; #new variable to play with in @table assignments my $n; #ditto as $m my $cookiedata; #outgoing formatted table data comma delimited my $cookiepickup; #incoming cookie data # Now we get our info from the HTML parameters $source = param('source'); $destination = param('destination'); $oldtotal = param('oldtotal'); $tripdate = param('tripdate'); $tripcounter = param('tripcounter'); $cookiepickup = cookie('trip_history'); ********************************************* @table = @{ thaw $cookiepickup }; #this is the problem. ********************************************* # Now we set some controller variables $tablesize = 1; $e = 1; $m = -1; $n = $tripcounter; # Here is the distance database. Can I do this better? if ($source eq 'HHS') { if ($destination eq 'District') { $miles = 6.7} if ($destination eq 'GHS') { $miles = 5} if ($destination eq 'MHS') { $miles = 8.3} } if ($source eq 'GHS') { if ($destination eq 'HHS') { $miles = 5} if ($destination eq 'District') { $miles = 1.7} if ($destination eq 'MHS') { $miles = 3.3} } if ($source eq 'MHS') { if ($destination eq 'District') { $miles = 1.6} if ($destination eq 'GHS') { $miles = 3.3} if ($destination eq 'HHS') { $miles = 8.3} } if ($source eq 'District') { if ($destination eq 'MHS') { $miles = 1.6} if ($destination eq 'GHS') { $miles = 1.7} if ($destination eq 'HHS') { $miles = 6.7} } if ($source ne $destination) { $oldtotal = $miles + $oldtotal; $tripcounter = $tripcounter + 1; push (@table, $tripdate, $source, $destination, $miles); $cookiedata = freeze \@table; # push(@table, @newdata); print qq (Set-cookie:trip_history=$cookiedata \n); print qq (Content-type: text/html\n\n <html><title>Mileage</title>); print qq (<body><form action="http://10.1.1.205/cgi-bin/perl/distanc +e-test.cgi" method="Post">); print qq (Trip Number $tripcounter <input type="hidden" name="tripco +unter" value="$tripcounter">----------); print qq (Trip Date: <select name="tripdate"><option value="1">1</op +tion><option value="2">2</option>); print qq (<option value="3">3</option> <option value="4">4</option> + <option value="5">5</option>); print qq (<option value="6">6</option> <option value="7">7</option> + <option value="8">8</option>); print qq (<option value="9">9</option> <option value="10">10</optio +n> <option value="11">11</option>); print qq (<option value="12">12</option> <option value="13">13</opt +ion> <option value="14">14</option>); print qq (<option value="15">15</option> <option value="16">16</opt +ion> <option value="17">17</option>); print qq (<option value="18">18</option> <option value="19">19</opt +ion> <option value="20">20</option>); print qq (<option value="21">21</option> <option value="22">22</opt +ion> <option value="23">23</option>); print qq (<option value="24">24</option> <option value="25">25</opt +ion> <option value="26">26</option>); print qq (<option value="27">27</option> <option value="28">28</opt +ion> <option value="29">29</option>); print qq (<option value="30">30</option> <option value="31">31</opt +ion></select><hr>); print qq (Starting Point:<select name="source"><option value="HHS"> +Highland High School </option>); print qq (<option value="MHS"> Mesquite High School </option><option + value="GHS"> Gilbert High School </option>); print qq (<option value="District"> District Office</option></select +>); print qq (Ending Point:<select name="destination"><option value="HHS +"> Highland High School </option>); print qq (<option value="MHS"> Mesquite High School </option><option + value="GHS"> Gilbert High School </option>); print qq (<option value="District"> District Office </option></selec +t>); print qq (<br><hr>); print qq (Current mileage is <Input Type=text name=oldtotal value=$o +ldtotal> miles<br>); print qq (<input type="submit" value="Get Distance"><input type="res +et" value="Oops! Reset Please!"></form>); # Retrieve Date &get_date; sub get_date { my @days; my @months; my $sec; my $min; my $hour; my $mday; my $mon; my $year; my $wday; my $time; # Define arrays for the day of the week and month of the year. @days = ('Sunday','Monday','Tuesday','Wednesday', 'Thursday','Friday','Saturday'); @months = ('January','February','March','April','May','June','July +', 'August','September','October','November','December') +; # Get the current time and format the hour, minutes and seconds. +Add # 1900 to the year to get the full 4 digit year. ($sec,$min,$hour,$mday,$mon,$year,$wday) = (localtime(time))[0,1,2 +,3,4,5,6]; $time = sprintf("%02d:%02d:%02d",$hour,$min,$sec); $year += 1900; # Format the date. $date = "$days[$wday], $months[$mon] $mday, $year"; } print qq (<p><hr>$date); print qq (</body></html>); print qq (<br>cookie value is @table); }