The new CGI module

It's been some time now since I made my first CGI module. For the time being, it sufficed, but after some time of not using it, it was kind of hard to get using it again. There were some design flaws. So it was time to start a new CGI module and now with better and more functions. The goal is to create a simple yet efficient and ultrafast webshop, based on the functions inside the new CGI module.

The old modulename was lower case only: cgi.def and cgi.mod. This new version is called CGI: CGI.def and CGI.mod do all the work.

CGI.def

At the moment, I am working on version 0.4; the previous versions were rather small and too similar to cgi.def and hence were not enough reason to publish about it.

DEFINITION MODULE CGI;

(*	CopyLeft 2009 jan@verhoeven272.nl

	This CGI library supersedes the former library 'cgi'.

  ver	does						   date
 -----	----------------------------------------------	-----------
  0.1	SetServer & EnvVar				08 Jun 2009
  0.2	Added TYPEs for SetServer			10 Jun 2009
  0.3	Add variable table as a linked list		11 Jun 2009
  0.4	Skip 'Identifier' in favor of 'Strings.String'
  	Added 'ShowList' procedure for debugging	
	Renamed 'SetServer' into 'SetMimeType'		13 Jun 2009
									*)

IMPORT	Strings;

TYPE	MimeType	= (text, html, gif, jpeg, ps, mpeg);
	ReqType		= (get, post);

	VarPtr		= POINTER TO VarNode;

	VarNode		= RECORD
			    varName,
			    value	: Strings.String;
			    next	: VarPtr
			  END;

VAR	thisVar		: VarPtr;


PROCEDURE  StoreVar (CGIvar   : Strings.String) : BOOLEAN;

PROCEDURE  FindVar (str  : Strings.String) : BOOLEAN;

PROCEDURE  ShowList;

PROCEDURE  ConvertHex (str : Strings.String) : CHAR;

PROCEDURE  SetMimeType (mime  : MimeType);

PROCEDURE  EnvVar (name  : Strings.String; VAR value  : Strings.String) : BOOLEAN;

END CGI.
   
MimeType This is slightly different from the previous version. Same types only now fully written in lower case.
ReqType This is new. It keeps track of the method used in the FORM data: GET or POST. Not used at the moment. Perhaps later.
VarNode I created a linked list to store all the data which is used or produced by the FORM and the CGI executable. This way I don't need to set up lots and lots of static variables; one linked list will do.
The linked list is automatically sorting. New entries are inserted such that the list is in alphabetical order all the times. 'VarPtr' and 'thisVar' are pointers that relate to this kind of 'symbol table'.
StoreVar StoreVar traverses the linked list and tries to find an empty slot where to insert the new variable. If the variable already exists, the procedure returns FALSE. Otherwise the CGI variable 'thisVar' is filled with the address of the new slot.
FindVar With this function, you traverse the linked list, looking for a particular name. If the last element is reached, FALSE is returned. If there is a match, then 'thisVar' is loaded with the address of the corresponding memory slot.
ShowList Lists the name/value pairs that occupy the linked list; for debugging purposes.
ConvertHex Handles the conversion from '%xx' values back into readable characters.
SetMimeType Sets the MIME type for the upcoming page or output
EnvVar Tries to find a CGI environment variable with the specifid name and if found, puts it in the value specified in the function call. In all other cases, FALSE is returned and the value contains nonsense data.

CGI.mod

And here is the implementation module of CGI. The code is simple and straightforward, as Modula-2 (and any other language) should be.

IMPLEMENTATION MODULE CGI;

(*	CopyLeft jan@verhoeven272.nl
	For version information, consult the DEF file.	*)


IMPORT	Arguments, InOut, MemPools, NumConv, Strings, SYSTEM;

(*
TYPE	VarPtr		= POINTER TO VarNode;

	NameNode	= RECORD
			    varName,
			    value	: Strings.String;
			    next	: VarPtr
			  END;
*)


VAR	envTable		: Arguments.ArgTable;
	VarPool			: MemPools.MemPool;
(*	thisVar,	*)
	firstVar		: VarPtr;


PROCEDURE StoreVar (CGIvar   : Strings.String) : BOOLEAN;

VAR	this, prev, new		: VarPtr;
	result			: INTEGER;
	  
BEGIN
   this := firstVar;
   prev := NIL;
   LOOP
      result := Strings.compare (this^.varName, CGIvar);
      IF  result = 0  THEN  RETURN FALSE  END;
      IF  result = 1  THEN  EXIT  END;
      prev := this;
      this := this^.next
   END;
   MemPools.PoolAllocate (VarPool, new, SYSTEM.TSIZE (VarNode));
   new^.varName := CGIvar;
   new^.next := this;
   IF  prev = NIL  THEN
      firstVar := new
   ELSE
      prev^.next := new
   END;
   thisVar := new;
   RETURN TRUE
END StoreVar;
										     

PROCEDURE FindVar (str  : Strings.String) : BOOLEAN;

VAR     thisOne, nextOne : VarPtr;

BEGIN
   thisOne := firstVar;
   LOOP
      IF  Strings.StrEq (thisOne^.varName, str)  THEN
	 thisVar := thisOne;
	 RETURN TRUE
      END;
      IF  thisOne^.next = NIL  THEN  EXIT  END;
      thisOne := thisOne^.next
   END;
   RETURN  FALSE
END FindVar;

									      
PROCEDURE  ShowList;

VAR     thisOne		: VarPtr;
	str		: Strings.String;

BEGIN
   thisOne := firstVar;
   REPEAT
      str := thisOne^.varName;
      InOut.WriteString (str);
      InOut.WriteString ("  =  ");
      str := thisOne^.value;
      InOut.WriteString (str);
      InOut.WriteLn;
      thisOne := thisOne^.next
   UNTIL  thisOne^.next = NIL;
   InOut.WriteBf
END  ShowList;


PROCEDURE  SetMimeType (mime  : MimeType);

VAR	type	: Strings.String;

BEGIN
   CASE  mime  OF
     html : type := 'text/html'			|
     gif  : type := 'image/gif'			|
     jpeg : type := 'image/jpeg'		|
     mpeg : type := 'video/mpeg'		|
     ps	  : type := 'application/postscript'
   ELSE
     type := 'text/plain'
   END;
   InOut.WriteString ('Content-type:');
   InOut.WriteString (type);
   InOut.WriteLn;
   InOut.WriteLn
END SetMimeType;


PROCEDURE  EnvVar (name	 : Strings.String; VAR value : Strings.String) : BOOLEAN;

VAR	found		: BOOLEAN;
	chr		: CHAR;
	i, j		: CARDINAL;
	eNvar		: Strings.String;

BEGIN
   found := FALSE;
   i := 0;
   LOOP
      IF  envTable^ [i] = NIL  THEN  RETURN FALSE  END;
      Strings.Assign (eNvar, envTable^ [i]^);
      IF  Strings.pos (name, eNvar) = 0  THEN
	 found := TRUE;
	 EXIT
      END;
      INC (i)
   END;
   i := 0;		j := 0;
   REPEAT
      chr := eNvar [i];
      INC (i)
   UNTIL  chr = '=';
   REPEAT
      chr := eNvar [i];
      value [j] := chr;
      INC (i);
      INC (j);
   UNTIL  (chr = 0C) OR (i > HIGH (eNvar));
   value [j] := 0C;
   RETURN  TRUE
END EnvVar;


PROCEDURE ConvertHex (str : Strings.String) : CHAR;

VAR	num		: CARDINAL;
	ok		: BOOLEAN;

BEGIN
   NumConv.Str2Num (num, 16, str, ok);
   IF  NOT ok  THEN
      InOut.WriteString ("Error in number : ");
      InOut.WriteString (str);
      InOut.WriteLn;
      HALT
   END;
   RETURN  CHR (num)
END ConvertHex;


BEGIN
   Arguments.GetEnv (envTable);
   MemPools.NewPool (VarPool);
   MemPools.PoolAllocate (VarPool, firstVar, SYSTEM.TSIZE (VarNode));
   firstVar^.varName := "|||";
   firstVar^.next := NIL
END CGI.
   
Some points to keep in mind:

Page created on 13 June 2009 and