(******************************************************************************************** ** PROGRAM : cgicook ** VERSION : 1.0.0 ** DESCRIPTION : Demonstrates how to use cookies in CGI programs. ** AUTHOR : Stuart King ** COPYRIGHT : Copyright (c) Irie Tools, 2002. All Rights Reserved. ** NOTES : ** This sample program is distributed with Irie Pascal, and was written to illustrate ** how to use cookies (i.e. how to read, write, and delete cookies). To make best use of ** this sample you should have a basic understanding of Pascal as well as a basic ** understanding of the Common Gateway Interface (CGI). ** ** Before describing how to use cookies, here is a very brief description of what ** cookies are and what they are used for. Cookies are named pieces of information that a ** website can ask a client (usually a browser) to store on its behalf. This information ** can be sent back to the website (and any other website in the cookies domain) whenever ** the browser sends a request to the website. Cookies are usually stored on the client's ** hard drive and can persist for weeks, months, or even years. This makes cookies very ** useful for 'remembering' information about website visitors. ** IMPORTANT: The cookies sent between the client and the website are sent in text format ** and are visible to any snooper, so sensitive information should either be encrypted ** or sent only over a secure connection. ** ** Cookies are written by sending a "Set-Cookie" response header back to the client. The ** syntax for the "Set-Cookie" header is given below: ** ** Set-Cookie: name=value; domain=.domain.com; path=/path; ** expires=Day, dd-Mon-yyyy hh:mm:ss GMT; secure ** Where ** "name" is the name of the cookie. ** "value" is the information stored by the cookie and must be specified. If you want ** to delete a cookie then specify an expiry date that has already passed. ** "domain" restricts the domains that will receive the cookie. The client should only send ** the cookie to matching domains. Domains are matched from right to left. So ** "domain=.irietools.com" matches "www.irietools.com" and "info.irietools.com". ** If "domain" is specified it must match the domain of the website setting the ** cookie. "domain" can't specify a top-level domain like ".com" or ".edu.jm". ** If "domain" is not specified then the domain name of the website setting ** the cookie is used. ** "path" restricts the URLs within a domain that will receive the cookie. Paths are ** matched from left to right and trailing /'s are ignored. If "path" is not ** specified then the full path of the request is used. ** "expires" specifies when the cookie should expire. The format of the expiry info ** must be exactly as shown above: ** "Day" is the three letter day of the week (Mon, Tue, Wed, Thu, Fri, Sat, or Sun). ** "dd" is the two digit day of the month (01-31). ** "Mon" is the three letter abbreviated month name ** (Jan, Feb, Mar, Apr, Jun, Jul, Aug, Sep, Oct, Nov, Dec). ** For example: "Mon, 16-Sep-2002 17:03:48 GMT". ** If "expire" is not specified then the cookie is stored memory until the client ** exits. ** "secure" indicates that the cookie should only be sent over a secure connection. ** ** The browser sends cookies back to the website in the form of Cookie headers. The ** format for Cookie headers is given below: ** ** Cookie: Cookie1; ...; CookieN ** Where ** each Cookiue is given as: name=value ** ** Only the "name" and the "value" of the cookies is returned, the other information ** such as the "domain" is not returned. CGI programs can't read the Cookie headers ** directly, so the webserver will make the Cookie header information available to the ** CGI program in the HTTP_COOKIE environment variable. **********************************************************************************************) program cookies; const MAX_BUFFER = 4096; MAX_COOKIE_DATA = 200; //SCRIPT_NAME = '/irietools/cgibin/cookies.exe'; type positive = 0..maxint; BufferType = string[MAX_BUFFER]; CookieDataType = string[MAX_COOKIE_DATA]; DayOfWeek = 1..7; Days = 1..31; Months = 1..12; date = record day : Days; month : Months; year : integer; dow : DayOfWeek end; Cookie = record name : string; value : CookieDataType end; CookieList = list of Cookie; var buffer : BufferType; strSetCookie : BufferType; DaysInMonth : array[Months] of Days; DaysElapsed : array[Months] of integer; DayOfWeekShortNames : array[DayOfWeek] of string[3]; MonthShortNames : array[Months] of string[3]; Cookies : CookieList; procedure DateToInt(dt : date; var iDays : integer); forward; procedure IntToDate(iDays : integer; var dt : date); forward; procedure CalculateDayOfWeek(var dt : date); forward; function urlencode(strIn : string) : string; forward; function EscapeCharacters(s : string) : string; forward; //PURPOSE: Performs program initialization procedure Initialize; var m : months; begin (* Initialize *) DaysInMonth[1] := 31; DaysInMonth[2] := 28; DaysInMonth[3] := 31; DaysInMonth[4] := 30; DaysInMonth[5] := 31; DaysInMonth[6] := 30; DaysInMonth[7] := 31; DaysInMonth[8] := 31; DaysInMonth[9] := 30; DaysInMonth[10] := 31; DaysInMonth[11] := 30; DaysInMonth[12] := 31; DaysElapsed[1] := 0; for m := 2 to 12 do DaysElapsed[m] := DaysElapsed[m-1]+DaysInMonth[m-1]; DayOfWeekShortNames[1] := 'Sun'; DayOfWeekShortNames[2] := 'Mon'; DayOfWeekShortNames[3] := 'Tue'; DayOfWeekShortNames[4] := 'Wed'; DayOfWeekShortNames[5] := 'Thu'; DayOfWeekShortNames[6] := 'Fri'; DayOfWeekShortNames[7] := 'Sat'; MonthShortNames[1] := 'Jan'; MonthShortNames[2] := 'Feb'; MonthShortNames[3] := 'Mar'; MonthShortNames[4] := 'Apr'; MonthShortNames[5] := 'May'; MonthShortNames[6] := 'Jun'; MonthShortNames[7] := 'Jul'; MonthShortNames[8] := 'Aug'; MonthShortNames[9] := 'Sep'; MonthShortNames[10] := 'Oct'; MonthShortNames[11] := 'Nov'; MonthShortNames[12] := 'Dec'; new(Cookies); //initialize the list of cookies end; (* Initialize *) //PURPOSE: Retrieves the information passed to the CGI applications. //PARAMETER(s): cl - Used to store all the cookies that were passed to the CGI program //GLOBAL(s) - buffer - Used to store the GET or POST information passed to the CGI program procedure GetCGIData(var cl : CookieList); var RequestMethod : string; //PURPOSE: Retrieves the cookies passed to the CGI program and puts then in the list. //PARAMETER(s): cl - Used to store all the cookies that were passed to the CGI program procedure GetCookieInfo(var cl : CookieList); var data : BufferType; iNumCookies, iCurrCookie : positive; iPos, iLen : positive; strCurrCookie : CookieDataType; c : Cookie; begin (* GetCookieInfo *) //The cookies are stored in the environment variable HTTP_COOKIE as space // deliminated words with trailing semi-colons after each word (except the last word). //Each word being a single cookie. //Retrieve the cookies data := getenv('HTTP_COOKIE'); //Retrieve the number words/cookies. iNumCookies := countwords(data); //Retrieve each cookie and use the "=" to seperate the name from the value //then insert the cookie into the list. for iCurrCookie := 1 to iNumCookies do begin strCurrCookie := copyword(data, iCurrCookie); iPos := pos('=', strCurrCookie); if iPos <> 0 then begin c.name := trim(copy(strCurrCookie, 1, iPos-1)); c.value := trim(copy(strCurrCookie, iPos+1)); iLen := length(c.value); if (iLen<>0) and (c.value[iLen]=';') then c.value := copy(c.value, 1, iLen-1); insert(c, cl); end; end; end; (* GetCookieInfo *) //PURPOSE: Retrieves information sent to by a GET request (i.e. in the QUERY_STRING // environment variable). procedure GetRequest; begin (* GetRequest *) buffer := getenv('QUERY_STRING') end; (* GetRequest *) //PURPOSE: Retrieves information sent to by a POST request (i.e. through the standard // input stream, with the length of the data in the environment variable // CONTENT_LENGTH). procedure PostRequest; var len, i : positive; err : integer; ContentLength : string; c : char; begin (* PostRequest *) buffer := ''; ContentLength := getenv('CONTENT_LENGTH'); if ContentLength <> '' then val(ContentLength, len, err) else len := 0; if len <= MAX_BUFFER then for i := 1 to len do begin read(c); buffer := buffer + c end end; (* PostRequest *) begin (* GetCGIData *) GetCookieInfo(cl); RequestMethod := getenv('REQUEST_METHOD'); if RequestMethod = 'GET' then GetRequest else PostRequest end; (* GetCGIData *) //PURPOSE: Process the data passed to the program. //NOTES: This is the main part of the program. After retreiving // the information passed to the program this procedure is // called to perform the required processing. This program receives to kinds // of information: // 1. Cookies sent back by the brower. These have already been retrieved by the // the procedure "GetCGIData" and do not require further processing here. // The procedure 'GenerateResponse' will display the retrieved cookies. // 2. Information entered by the user about a cookie to add or delete. This // information needs to formated and put in the global 'strSetCookie', so that // it can be included in the response by the procedure 'GenerateResponse'. procedure ProcessCGIData; var i, num, p : integer; EncodedVariable, DecodedVariable, name, value : string; strName : string; strValue : string; strExpiry : string; blnDelete : boolean; //PURPOSE: Converts a expiry date into a string in the format required // by the Set-Cookie header (i.e. Day, dd-Mon-yyyy hh:mm:ss GMT //ARGUMENT(s): dt - Expire date to convert. //RETURNS: The expiry date/time in Set-Cookie format //NOTES: // This function just hard-codes the last second in the day "23:59:59" // instead of using the current time or allowing the expiry time to be specified. //This is done because // 1. It is easier and this is just a sample program // 2. Usually cookie will be set to expire either when the browser closes // or after many days have passed, so the exact time is not important. function FormatCookieExpiryString(dt : date) : string; var s : string; //PURPOSE: Converts an integer into a zero-padded string //ARGUMENT(s): // 1. i - The integer to convert // 2. len - The length of the string to return //RETURNS: // The zero padded string function Int2String(i : integer; len : integer) : string; var S : string; begin (* Int2String *) str(i:1, s); while length(s) < len do s := '0' + s; Int2String := s end; (* Int2String *) begin (* FormatCookieExpiryString *) CalculateDayOfWeek(dt); s := DayOfWeekShortNames[dt.dow]+', '; s := s + Int2String(dt.day, 2)+'-'+MonthShortNames[dt.month]+'-'+Int2String(dt.year, 4)+' '; s := s + '23:59:59 GMT'; FormatCookieExpiryString := s; end; (* FormatCookieExpiryString *) //PURPOSE: Processes the named value pairs sent with the GET or POST request. // Which in this case is the information entered by the user about the // cookie to add or delete. //ARGUMENT(s): name - name part of the name/value pair // value - value part of name/value pair //NOTES: // The information entered by the user is sent as name/value pairs (i.e. name-value) //with the name part being the name of the form element holding the information and //the value part being the actual information held by the form element. procedure ProcessNameValuePair(var name, value : string); var dt : date; dow : integer; iDays, iErr : integer; iDate : integer; begin //If the name is 'txtname' then this is the name of the cookie. //The name is urlencoded in case it contains special characters if name='txtname' then strName := urlencode(value) //If the name is 'txtvalue' then this is the value of the cookie. //The value is urlencoded in case it contains special characters else if name='txtvalue' then strValue := urlencode(value) //If the name is 'txtdays' then this is the number of days before the cookie //expires. The value is converted to an integer and if the conversion is //successful the value is added to the current date to get the expiry date //and this date is formatted for use in a Set-Cookie header. else if name='txtdays' then begin val(value, iDays, iErr); if iErr=0 then begin getdate(dt.year, dt.month, dt.day, dow);//Get current date DateToInt(dt, iDate); //Convert current date to number of days since Jan 1, 0001 AD iDate := iDate + iDays; //Add in the number of days to expire IntToDate(iDate, dt); //Convert the result back into a date strExpiry := FormatCookieExpiryString(dt); //Format date end; end //If the name is 'cmddelete' then the cookie should be deleted not added. //The cookie is deleted by setting an expiry date that has already passed. else if name='cmddelete' then begin getdate(dt.year, dt.month, dt.day, dow); DateToInt(dt, iDate); iDate := iDate - 2; IntToDate(iDate, dt); strExpiry := FormatCookieExpiryString(dt); blnDelete := true end else ; end; begin (* ProcessCGIData *) strSetCookie := ''; strName := ''; strValue := ''; strExpiry := ''; blnDelete := false; //Retrieve each name/value pair from the form and processes them. num := CountWords(buffer, '&'); for i := 1 to num do begin EncodedVariable := CopyWord(buffer, i, '&'); DecodedVariable := URLDecode(EncodedVariable); p := pos('=', DecodedVariable); if p > 0 then begin name := lowercase(trim(copy(DecodedVariable, 1, p-1))); value := trim(copy(DecodedVariable, p+1)); ProcessNameValuePair(name, value); end end; //If after processing each name/value pair we have enough data for a //"Set-Cookie" header then generate a "Set-Cookie" header in the global //"strSetCookie". //if (strName<>'') and ((strValue<>'') or blnDelete) and (strExpiry<>'') then if (strName<>'') and ((strValue='') or (strExpiry<>'')) then begin //Set-Cookie: name=value; domain=.irietools.com; path=/cgibin; // expires=Tue, 03-Sep-2002 20:42:19 GMT; secure strSetCookie := 'Set-Cookie: ' + strName + '=' + strValue + ';'; if strExpiry<>'' then strSetCookie := strSetCookie + ' expires=' + strExpiry; end; end; (* ProcessCGIData *) //PURPOSE: Generates the response to send back to the browser. //NOTES: // Most of the HTML generated by this procedure actually comes from a template file. //The HTML from the template file is common to almost all the pages on this website. //The HTML specific to this program (i.e. the form that accepts the cookie information //from the user), is generated by the local procedure 'GenerateBodyData'. So this procedure //basically reads the template file and sends most of it to the webserver except for //a small section which it repalces with the output of the procedure 'GenerateBodyData'. //The template file is divided into sections using HTML comments. The start of a section //is marked by the following // // // //Where "SectionName" is the name of the section. // //The end of a section is marked by // // // //What this procedure actually does is read the template file and write the contents //of all the sections except one to the webserver. The section not written to the webserver //is called "bodydata". Instead of writing the contents of the section "bodydata" to the //webserver the ouput of the procedure "GenerateBodyData" is sent to the webserver. //This is done because it is much easier to work with HTML using an HTML editor rather //than using embedded writeln's inside a CGI program. So an HTML editor is used to //create and update the template file and the portion of the generated output that //is specific to this program is generated by "GenerateBodyData". You will notice //when you run this program that the page generated looks alot like the other pages //on the website because most of it comes from the template file. procedure GenerateResponse; const TEMPLATE_FILE = 'template.html'; START_SECTION = ''; SECTION_NAME = 'bodydata'; var f : text; line : BufferType; blnSkipping : boolean; //PURPOSE: Generates the portion of the response page that is specific to this // program (the rest of the response page comes from the template file). //NOTES: // Tables are used to organise the layout of the generated pages on this website. //The portion of the response page generated by this procedure is a single row of //a table. Unless you are an HTML expert the easiest way to understand the HTML //generated by this procedure is probably to run this program and view the //response pages generated. procedure GenerateBodyData; var iNumCookies, iCurrCookie : positive; c : Cookie; begin (* GenerateBodyData *) writeln('
None
') else //For each cookie returned write it's name and value //NOTE: The name and value are escaped in case they contain //special characters. for iCurrCookie := 1 to iNumCookies do begin c := Cookies[iCurrCookie]; writeln('', EscapeCharacters(urldecode(c.name)), '=', EscapeCharacters(urldecode(c.value)),'
'); end; writeln('', getenv('SCRIPT_NAME'), '
'); writeln('