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& out);
void Log(ostream& 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& 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& 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(&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;
}