Example Pizza order handling CGI program

// NB
//  This program must run as a set user id program
// (Either use set-uid mechanism or SU-Exec options for Apache
// server - it has to run under user-id of student because
// will need to write to a datafile created by student).
// setuid or SU-Exec?  It depends on your systems administrators
//  who will chose how they want things set up.  If the sys admin
//  choses setuid style, the directories must honor setuid tags and
//  students must use chmod to set the setuid bit.  If sys admins
//  chose SU-Exec style, you can bascially forget the issue; SU-Exec handles
//  everything.
//  Place in (subdirectory) of cgi-bin along with 
//      DataLog   a file for recording all pizza orders

#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <signal.h>

#include "CGI.h"

Class defining holder for submitted data

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
    Pizza
       data structure for holding order details etc

	Note potential for maintenance problems ---
		Javascript on HTML page has one set of data
			like option choices and costs
		Here we have hopefully the same data repeated

	But easy to make inconsistent changes!  Part of the fun of Internet computing.
*/

class FredsPizza {
public:
	FredsPizza();
	~FredsPizza();
	void	SetSize(const char* sizeinfo);
	void	AddTopping(const char* toptype);
	void	AddExtra(const char* extra);
	void	SetDelivery(const char* deliver);
	void	SetCustomer(const char* customer);
	void	SetAddress(const char* address);
	void	SetEmail(const char* email);
	void	SetPhone(const char* phone);
	void	Report(ostream&amp; out);
	void	Log(ostream&amp; out);
	const char* Email() { return fEmail; }
	const char* Delivery() { return fDelivery; }
private:
	double 	Cost();
	char*	catenate(const char* old, const char* more);
	int	fSize;
	int	fNumToppings;
	double  fPizzaCost;
	double	fExtrasCost;
	double	fDeliveryCost;
	char*	fTops;
	char*	fXtras;
	char*	fDelivery;
	char*	fCustomer;
	char*	fAddress;
	char*	fEmail;
	char* 	fPhone;
};

FredsPizza::FredsPizza() 
{
	fSize = 1; fNumToppings = 0; fExtrasCost = 0.0;
	fTops = fXtras = fDelivery = fCustomer = fAddress = 
		fEmail = fPhone = NULL;
}

FredsPizza::~FredsPizza()
{
	delete fTops;
	delete fXtras;
	delete fDelivery;
	delete fCustomer;
	delete fAddress;
	delete fEmail;
	delete fPhone;
}
void FredsPizza::SetSize(const char* sizeinfo)
{
	fSize = 1;
	if(0 == strcmp(sizeinfo, "fam")) fSize = 2;
	if(0 == strcmp(sizeinfo, "pop")) fSize = 3;
}
void FredsPizza::AddTopping(const char* toptype)
{
	fNumToppings++;
	char *temp = catenate(fTops, toptype);
	delete [] fTops;
	fTops = temp;
}
void FredsPizza::AddExtra(const char* extra)
{
	char *temp = catenate(fXtras, extra);
	delete [] fXtras;
	fXtras = temp;
	if(0 == strcmp("Coke",extra)) fExtrasCost += 1.70;
	if(0 == strcmp("Lemonade",extra)) fExtrasCost += 1.70;
	if(0 == strcmp("Ice cream",extra)) fExtrasCost += 3.5;
	if(0 == strcmp("Salad",extra)) fExtrasCost += 4.50;
}
void FredsPizza::SetDelivery(const char* delivery)
{
	fDelivery  = new char[strlen(delivery) + 1];
	strcpy(fDelivery, delivery);
	if(0 == strcmp(delivery,"Express")) fDeliveryCost = 1.50;
	if(0 == strcmp(delivery,"Gorrila")) fDeliveryCost = 15.0;
	if(0 == strcmp(delivery,"Fat lady who sings")) fDeliveryCost = 20.0;
	if(0 == strcmp(delivery,"Female stripper")) fDeliveryCost = 50.0;
	if(0 == strcmp(delivery,"Male stripper")) fDeliveryCost = 40.0;
	if(0 == strcmp(delivery,"Coco the clown")) fDeliveryCost = 25.0;
	if(0 == strcmp(delivery,"Bozo the programmer")) fDeliveryCost = 1.65;
	if(0 == strcmp(delivery,"Fred the cook")) fDeliveryCost = 101.50;
}
void FredsPizza::SetCustomer(const char* customer)
{
	fCustomer = new char[strlen(customer) + 1];
	strcpy(fCustomer, customer);
}
void FredsPizza::SetAddress(const char* address)
{
	fAddress = new char[strlen(address) + 1];
	strcpy(fAddress, address);
}
void FredsPizza::SetPhone(const char* phone)
{
	fPhone = new char[strlen(phone) + 1];
	strcpy(fPhone, phone);
}
void FredsPizza::SetEmail(const char* email)
{
	fEmail = new char[strlen(email) + 1];
	strcpy(fEmail, email);
}

Generation of response page

void FredsPizza::Report(ostream&amp; out)
{
	out << "Your ";
	if(fSize==1) out << "regular ";
	if(fSize==2) out << "Family ";
	if(fSize==3) out << "POPULAR ";
	out << "sized pizza";
	if(fNumToppings>0) out << ", with " << fTops << "," ;
	out << " is now being prepared." << endl;
	out << "Your pizza ";
	if(fXtras != NULL) out << ", and the following extras : " << fXtras << "," << endl;
	out << "will be delivered soon by our " << fDelivery << " service." << endl;
	out << "The cost will be $" << Cost() << ".  Please have money ready to pay delivery person." << endl;
}

Logging data to file

void FredsPizza::Log(ostream&amp; out)
{
	out << "Customer: " << fCustomer << endl;
	out << "Address:  " << fAddress << endl;
	out << "Phone:    " << fPhone << endl;
	out << "Cost:     $" << Cost() << endl;
	out << "Size:     " << fSize << endl;
        out << "Tops:     " << fTops  << endl;
	out << "Extras:   " << fXtras << endl;
	out << "Delivery: " << fDelivery << endl;
}

double FredsPizza::Cost()
{
	double cost = 0.0;
	if(fSize == 1) cost = 7.0;
	if(fSize == 2) cost = 11.0;
	if(fSize == 3) cost = 15.0;
	cost += fSize*fNumToppings*0.50;
	cost += fExtrasCost;
	cost += fDeliveryCost;
	return cost;
}

char* FredsPizza::catenate(const char* old, const char* more)
{
	char *temp = NULL;
	if(old == NULL) { 
		temp = new char[strlen(more) + 1];
		strcpy(temp, more);
		}
	else {
		int len = strlen(old) + strlen(more) + 3;
		temp = new char[len];
		strcpy(temp, old);
		strcat(temp, ", ");
		strcat(temp, more);
		}
	return temp;
}

Concrete sub-class of CGI_Helper

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
	Here have chosen to do everything in a specialised subclass of CGI_Helper
	An alternative choice would be to have an object of some other class to handle
   	the work and use a CGI_Helper subclass where only real change is ProcessToken()
	function that calls other object to do processing.


	Processing:
		automatically lock the records file 
		read and process tokens to build a pizza with order details
		use this to generate HTML of reply and entry in log file
			(move to end of log file before writing)
		automatically unlock the records file


	The code is missing safety checks --- like did we open the records file successfully?
*/

class FredsCGI : public CGI_Helper {
public:
	FredsCGI();
	~FredsCGI(); 
protected:
	virtual void StartTokens() ;
	virtual void ProcessToken(Token *tok);
	virtual void EndTokens();
private:
	fstream		fRecords;
	time_t		fArrivalTime;
	FredsPizza	fPizza;
	int 		fFiledescriptor;
};

Claiming and releasing a locked record file

FredsCGI::FredsCGI()  
{ 
	fRecords.open("DataLog",ios::in | ios::out);
	fRecords.seekp(0, ios::end);
	fFiledescriptor = fRecords.rdbuf()->fd();
	lockf(fFiledescriptor, F_LOCK, 0);
}

FredsCGI::~FredsCGI() 
{ 
	fRecords.close(); 
	lockf(fFiledescriptor, F_ULOCK, 0);

}

Override functions generating HTML page header and footer

void FredsCGI::StartTokens()
{
	(void) time(&fArrivalTime);
	fRecords << "Order received at " << ctime(&amp;fArrivalTime);
	cout << "<h1>Fred's CyberPizza Parlor</h1>";
	cout << "<em>Thank you for your order.</em><br>";
}

void FredsCGI::EndTokens()
{
	fPizza.Report(cout);
	fPizza.Log(fRecords);
	fRecords << "----" << endl;
}

Process token function, data saved in Pizza struct

void FredsCGI::ProcessToken(Token *tok)
{
	if(0==strcmp(tok->Name(), "phone")) fPizza.SetPhone(tok->Value());
	if(0==strcmp(tok->Name(), "email")) fPizza.SetEmail(tok->Value());
	if(0==strcmp(tok->Name(), "address")) fPizza.SetAddress(tok->Value());
	if(0==strcmp(tok->Name(), "customer")) fPizza.SetCustomer(tok->Value());
	if(0==strcmp(tok->Name(), "deliv")) fPizza.SetDelivery(tok->Value());
	if(0==strcmp(tok->Name(), "xtra")) fPizza.AddExtra(tok->Value());
	if(0==strcmp(tok->Name(), "tops")) fPizza.AddTopping(tok->Value());
	if(0==strcmp(tok->Name(), "p_size")) fPizza.SetSize(tok->Value());
}

main() function

int main()
{
	FredsCGI x;
	x.HTML_Header("Re: Your pizza order");
	x.Handle_Request();
	x.HTML_Trailer();	
	return 0;
}