/*****************************************************************************/
/*
tMAILer.c
This is a drop-in replacement for the OSU TMAIL script (original authors Dick
Monroe and David Jones) for the WASD environment. Written from scratch, it is
intended to be backwardly compatible. Being directly CGI process based it will
avoid that initial latency when a DECnet script is activated.
The concept is roughly this. A form's ACTION= element specifies this script
and another file, a template file, as the path. The script opens the template
file and uses it's contents to parse the form's field(s), generating and
sending a mail message.
If there is a large number of existing OSU-based TMAIL forms/templates in use
the following HTTPD$MAP rule will simply result in them being processed by
TMAILER instead.
map /htbin/tmail/* /cgi-bin/tmailer/*
FORM EXAMPLE
------------
|
An example template file for processing this form is shown below.
TEMPLATE FILE FORMAT
--------------------
The template file is plain text, and is commonly named with .TMAIL as the
extension (although it could be made anything). It comprises at least two and
more often three sections. The first, separated from the next by a blank line,
is termed the header section and contains a version, mail message and response
directives. The first line must always be the "tmail:" version, as follows:
tmail: 1.1
This ensures the file is intended as a template. The next line should be the
destination of the mail message. WASD-specific allows a comma-separated list
of addresses. WASD-specific also allows a "To:" address to begin with a '?'.
In this case a facsimile of the received message is returned to the client.
Another variant allows the address to begin with a '!', suppressing actual
mailing but continuing on to deliver a response to the client. Both useful for
template "debugging" or demonstration purposes.
to: address[,address2,address3]
to: ?address
to: !address
An optional subject line should be provided.
subject: This is the subject of the "subject:" directive
Two more optional directives control the response to the request.
success: URL to redirect the client to after successful mailing
success-status: HTTP status code to return instead of 200
Another optional directive sets the "personal name" part of the source email
address of the message which TMAILer sends:
personal: personal name string
If this directive is not supplied, a default name will be used based on the
name of the template file.
The second section of the template file (remember this is separated from the
header section by a blank, or empty, line) is the body section. The text in
this section (plus any expanded tag information, see below) is what is mailed
to the destination address specified with the "to:" above.
The third, and completely optional section, allows a specific response to be
returned to the client. The command tag "[%%end]" delimits the second section
from the third section. The third section should be in the format of a CGI
script response. It should therefore begin with a "Content-Type: text/html",
an empty line, and then a response body.
TAGS
----
Tags mark points in the template where the contents of a form field or CGI
variable is inserted into the text. Tags may be used anywhere within the
template file. If a field or CGI variable does not exist the string "%unknown"
is returned.
The following characters are reserved and can be escaped by preceding them with
a '[' character, as listed here:
'[' escape using "[["
']' escape using "[]"
':' escape using "[:"
'?' escape using "[?"
Tag information is introduced using the '[' and concluded with ']' characters.
Three tag formats are supported:
[field-name] the data associated with the form field name
[%cgi-var-name] the contents of a CGI variable
[%%command] a directive to the template processor
Form field names inside tags are case-sensitive. CGI variable names are not.
COMMAND TAGS
------------
Tags beginning with "%%" provide directives to the template processor. Three
OSU and one WASD-specifc command tags are defined.
[%%end]
Indicates the end of the mail message contents. Any lines following are
considered to be a CGI-compatible response to be returned to request client.
It should therefore begin with a "Content-Type: text/html", an empty line, and
then a response body.
[%%entify]
Enables conversion of HTML forbidden characters (i.e. '<', '>', '&') to the
corresponding HTML entity (i.e. "<", "gt;", "&"). By default this
conversion is disabled during mail message composition (i.e. (before a [%%end])
and enabled during any CGI response composition (i.e. after a [%%end]).
[%%noentify]
Disables conversion to HTML entities, as described above.
[%%reveal]
This is WASD-specific. It displays the CGI variables and the form fields and
then exits, obviously for template "debugging" purposes. The CGI variables are
shown with the prefixing "WWW_" which is optional when specifying tag field
names. Place this immediately after the "tmail:" version directive.
CONDITIONAL EXPANSION
---------------------
Extensions to the tag format allows information other than the exact contents
of a tag field or CGI variable to be inserted based on the contents of that
field or variable! Two OSU and two WASD-specific variants are available.
[field-name:string]
[%cgi-var-name:string]
If the form field or CGI variable contents are not null (i.e. the tag-name
exists and contains a non-empty string) the string following the ':' (which may
contain white-space, escaped characters, etc., but no nested tags) is inserted
into the text. If the field or variable does not exist or is empty the string
is not inserted.
[field-name:string1::string2]
[%cgi-var-name:string1::string2]
This is WASD-specific. Like the simple variant this one inserts the text of
string1 if the field name is not empty. However if it is empty and a "::"
delimits a second string then that is inserted.
[field-name?string]
[%cgi-var-name?string]
If the form field or CGI variable contents contain the literal "on" (for
evaluating checkboxes) then the string following the '?' is inserted into the
text, otherwise it is not.
[field-name?string1??string2]
[%cgi-var-name?string1??string2]
This is WASD-specific. Like the simple variant this one inserts the text of
string1 if the field name contains "on". However if it is does not and a "??"
delimits a second string then that is inserted.
TEMPLATE FILE EXAMPLE
---------------------
The following example template file provides all three sections, header, body
and response (the '|' just indicates the limits of the file): This template
could be used to process the form in the example above.
|tmail: 1.1
|to: WEBADMIN
|subject: This is just an example (from [%SERVER_HOST])!
|
|This is the body of the message mailed to the above address.
|The message was sent from [%HOST_ADDR]
|by a client using "[%USER_AGENT]".
|
|The form contained a field named "comments", it's contents are:
|---------------------------------------------------------------
|[comments]
|---------------------------------------------------------------
|
|The field "email" contains "[email]"
|and [email:was::was not] completed by the client.
|
|This is the end of the mail message body.
|What follows after the "[[%%end[]" is a CGI response.
|[%%end]
|Content-type: text/html
|
|
|
|The message was successfully mailed. Thankyou for your comments.
|
OSU NOTE!
---------
Although this code has build capabilities for the OSU environment it is not
intended to encroach on functionality already present in the OSU package. I
have this in here merely for testing the CGILIB POSTed body processing
functionality.
QUALIFIERS
----------
/CHARSET= "Content-Type: text/html; charset=..."
/DBUG turns on all "if (Debug)" statements
/SOFTWAREID (and /VERSION) display TMAILER and CGILIB versions
LOGICAL NAMES
-------------
TMAILER$DBUG turns on all "if (Debug)" statements
TMAILER$PARAM equivalent to (overrides) the command line
parameters/qualifiers (define as a system-wide logical)
BUILD DETAILS
-------------
See BUILD_ONE.COM procedure.
COPYRIGHT
---------
Copyright (C) 1999-2022 Mark G.Daniel
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
VERSION HISTORY (update SOFTWAREVN as well!)
---------------
03-APR-2022 MGD v1.3.5, bugfix; test for NULL after CgiLibReadRequestBody()
10-SEP-2009 MGD v1.3.4, break rather than truncate body lines at 255
23-DEC-2003 MGD v1.3.3, minor conditional mods to support IA64
28-NOV-2001 JMB v1.3.2, add 'personal' directive
01-JUL-2001 MGD v1.3.1, add 'SkipParameters' for direct OSU support
28-OCT-2000 MGD v1.3.0, use CGILIB object module,
support RELAXED_ANSI compilation
12-APR-2000 MGD v1.2.1, minor changes for CGILIB 1.4
07-AUG-1999 MGD v1.2.0, use more of the CGILIB functionality
24-APR-1999 MGD v1.1.0, use CGILIB.C
31-MAR-1999 MGD v1.0.1, a couple of wrinkles
27-MAR-1999 MGD v1.0.0, initial development
*/
/*****************************************************************************/
#define SOFTWAREVN "1.3.5"
#define SOFTWARENM "TMAILER"
#ifdef __ALPHA
# define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
# define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
# define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
# define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif
/* standard C header files */
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* VMS-related header files */
#include
#include
#include
#include
#include
#define boolean int
#define true 1
#define false 0
/* application related header file */
#include "cgilib.h"
char CopyrightInfo [] =
"Copyright (C) 2005-2009 Mark G.Daniel.\n\
This program, comes with ABSOLUTELY NO WARRANTY.\n\
This is free software, and you are welcome to redistribute it under the\n\
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.\n\
http://www.gnu.org/licenses/gpl.txt\n";
#ifndef __VAX
# pragma nomember_alignment
#endif
#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) (!((x) & STS$M_SUCCESS))
#define FI_LI __FILE__, __LINE__
#define TMAIL_VERSION "1.1"
#define FORM_URLENCODED "application/x-www-form-urlencoded"
#define UNKNOWN_TAG "%unknown"
#define ISLWS(c) (c == ' ' || c == '\t')
char Utility [] = "TMAILER";
boolean Debug,
CommandTagEnd,
CommandTagEntify;
int BufferCount,
CgiResponseLength,
CgiResponseOffset,
HeaderTmailOffset,
HeaderToOffset,
HeaderSubjectOffset,
HeaderSuccessOffset,
HeaderSuccessStatusOffset,
HeaderPersonalOffset,
ParsedTemplateLength,
RequestDataLength,
TemplateBodyLength,
TemplateBodyOffset,
ThereHasBeenOutput;
char *BufferPtr,
*CharsetPtr,
*CgiContentTypePtr,
*CgiEnvironmentPtr,
*CgiPathInfoPtr,
*CgiPathTranslatedPtr,
*CgiRequestMethodPtr,
*CgiResponsePtr,
*CgiServerSoftwarePtr,
*CliCharsetPtr,
*HeaderToPtr,
*HeaderSubjectPtr,
*HeaderSuccessPtr,
*HeaderSuccessStatusPtr,
*HeaderPersonalPtr,
*ParsedTemplatePtr,
*RequestDataPtr,
*TemplateBodyPtr;
char CharsetString [64],
ContentTypeCharset [64],
SoftwareID [48];
/* required function prototypes */
char* ConditionalExpansion (char*, char*);
char* TagCgiVar (char*, char*);
char* TagFormCgiVar (char*, char*);
/*****************************************************************************/
/*
'argc/argv' are only required to support OSU, in particular CgiLibOsuInit().
*/
main
(
int argc,
char *argv[]
)
{
/*********/
/* begin */
/*********/
sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion());
if (getenv ("TMAILER$DBUG") != NULL) Debug = true;
if (Debug) fprintf (stdout, "Content-Type: text/plain\n\n");
CgiLibEnvironmentSetDebug (Debug);
GetParameters ();
CgiLibEnvironmentInit (argc, argv, false);
CgiLibResponseSetCharset (CliCharsetPtr);
CgiLibResponseSetSoftwareID (SoftwareID);
CgiLibResponseSetErrorMessage ("Reported by TMailer");
CgiEnvironmentPtr = CgiLibEnvironmentName ();
ProcessRequest ();
exit (SS$_NORMAL);
}
/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent. OSU scripts have
the 'method', 'url' and 'protocol' supplied as P1, P2, P3 (these being detected
and used by CGILIB), and are of no interest to this function.
*/
GetParameters ()
{
static char CommandLine [256];
static unsigned long Flags = 0;
int status,
SkipParameters;
unsigned short Length;
char ch;
char *aptr, *cptr, *clptr, *sptr;
$DESCRIPTOR (CommandLineDsc, CommandLine);
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "GetParameters()\n");
if ((clptr = getenv ("TMAILER$PARAM")) == NULL)
{
/* get the entire command line following the verb */
if (VMSnok (status =
lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
exit (status);
(clptr = CommandLine)[Length] = '\0';
}
/* if OSU environment then skip P1, P2, P3 */
if (getenv ("WWWEXEC_RUNDOWN_STRING") != NULL)
SkipParameters = 3;
else
SkipParameters = 0;
aptr = NULL;
ch = *clptr;
for (;;)
{
if (aptr != NULL && *aptr == '/') *aptr = '\0';
if (!ch) break;
*clptr = ch;
if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
while (*clptr && isspace(*clptr)) *clptr++ = '\0';
aptr = clptr;
if (*clptr == '/') clptr++;
while (*clptr && !isspace (*clptr) && *clptr != '/')
{
if (*clptr != '\"')
{
clptr++;
continue;
}
cptr = clptr;
clptr++;
while (*clptr)
{
if (*clptr == '\"')
if (*(clptr+1) == '\"')
clptr++;
else
break;
*cptr++ = *clptr++;
}
*cptr = '\0';
if (*clptr) clptr++;
}
ch = *clptr;
if (*clptr) *clptr = '\0';
if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
if (!*aptr) continue;
if (SkipParameters)
{
SkipParameters--;
continue;
}
if (strsame (aptr, "/CHARSET=", 4))
{
for (cptr = aptr; *cptr && *cptr != '='; cptr++);
if (*cptr) cptr++;
CliCharsetPtr = cptr;
continue;
}
if (strsame (aptr, "/DBUG", -1))
{
Debug = true;
continue;
}
if (strsame (aptr, "/SOFTWAREID", 4) ||
strsame (aptr, "/VERSION", 4))
{
fprintf (stdout, "%%%s-I-SOFTWAREID, %s\n%s\n",
Utility, SoftwareID, CopyrightInfo);
exit (SS$_NORMAL);
}
if (*aptr == '/')
{
fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
Utility, aptr+1);
exit (STS$K_ERROR | STS$M_INHIB_MSG);
}
fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
Utility, aptr);
exit (STS$K_ERROR | STS$M_INHIB_MSG);
}
}
/*****************************************************************************/
/*
*/
ProcessRequest ()
{
int status,
StatusCode,
TemplateLength;
char *cptr,
*TemplatePtr;
char PersonalName [256];
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "ProcessRequest()\n");
CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE");
CgiPathInfoPtr = CgiLibVar ("WWW_PATH_INFO");
CgiPathTranslatedPtr = CgiLibVar ("WWW_PATH_TRANSLATED");
status = ReadFileIntoMemory (CgiPathTranslatedPtr,
&TemplatePtr, &TemplateLength);
if (VMSnok (status))
{
CgiLibResponseError (FI_LI, status, CgiPathInfoPtr);
exit (SS$_NORMAL);
}
CheckTemplateVersion (TemplatePtr);
if (RequestDataPtr == NULL)
CgiLibReadRequestBody (&RequestDataPtr, &RequestDataLength);
if (RequestDataPtr == NULL)
{
CgiLibResponseError (FI_LI, 0, "Must be URL-encoded form data!");
exit (SS$_NORMAL);
}
if (Debug) fprintf (stdout, "RequestDataPtr ...\n|%s|\n", RequestDataPtr);
ParseTemplate (TemplatePtr);
ResolvePointers ();
if (HeaderPersonalPtr)
strcpy (PersonalName, HeaderPersonalPtr);
else
sprintf (PersonalName, "tmailer: %s", CgiPathInfoPtr);
if (HeaderToPtr[0] == '?')
{
/* a question mark allows viewing the message without actual mailing! */
CgiLibResponseHeader (200, "text/plain");
RevealMailMessage (PersonalName, HeaderToPtr,
HeaderSubjectPtr, TemplateBodyPtr);
exit (SS$_NORMAL);
}
else
if (HeaderToPtr[0] != '!')
{
/* an exclamation point allows testing without actual mailing! */
MailMessage (PersonalName, HeaderToPtr,
HeaderSubjectPtr, TemplateBodyPtr);
}
if (CgiResponsePtr == NULL)
{
if (HeaderSuccessStatusPtr[0])
{
for (cptr = HeaderSuccessStatusPtr; *cptr && !isdigit(*cptr); cptr++);
StatusCode = atoi(cptr);
}
if (StatusCode < 200 || StatusCode > 299) StatusCode = 200;
if (HeaderSuccessPtr[0])
{
/* redirection */
CgiLibResponseRedirect (HeaderSuccessPtr);
}
else
{
CgiLibResponseHeader (200, "text/html");
fprintf (stdout,
"\n\
\n\
\n\
Message Mailed!\n\
\n\
\n\
Sending form data as MAIL to: %s\n\
\n\
\n",
SoftwareID, HeaderToPtr);
}
}
else
fprintf (stdout, "%s", CgiResponsePtr);
}
/*****************************************************************************/
/*
Read the request body (if POST) or the request's query string (if GET) into a
single array of char (doesn't matter whether it's text or binary) pointed to by
'BufferPtr' with a length of 'BufferCount'. If the NET$LINK environment
variable exists (DECnet CGI input/output) then read from that in record mode
(it's DECnet after all). For DECnet an empty record (newline character only)
terminates the request body, for subprocess CGI an EOF is read.
*/
ReadRequestDataIntoMemory
(
char **BufferPtrPtr,
int *DataSizePtr
)
{
static int BufferChunk = 1024;
static int BufferCount;
static char *BufferPtr;
int BufferSize,
ReadCount;
char *cptr;
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "ReadRequestDataIntoMemory()\n");
/* if the body has already been read then just return */
if (BufferPtr != NULL)
{
if (BufferPtrPtr != NULL) *BufferPtrPtr = BufferPtr;
if (DataSizePtr != NULL) *DataSizePtr = BufferCount;
return;
}
CgiRequestMethodPtr = CgiLibVar("WWW_REQUEST_METHOD");
if (strsame (CgiRequestMethodPtr, "GET", -1))
{
BufferPtr = CgiLibVar("WWW_QUERY_STRING");
BufferCount = strlen (BufferPtr);
if (BufferPtrPtr != NULL) *BufferPtrPtr = BufferPtr;
if (DataSizePtr != NULL) *DataSizePtr = BufferCount;
return;
}
else
if (strsame (CgiRequestMethodPtr, "POST", -1))
{
CgiContentTypePtr = CgiLibVar("WWW_CONTENT_TYPE");
if (!strsame (CgiContentTypePtr, FORM_URLENCODED, -1))
{
CgiLibResponseError (FI_LI, 0, "Must be URL-encoded form data!");
exit (SS$_NORMAL);
}
}
else
{
CgiLibResponseError (FI_LI, 0,
"HTTP method must be \"GET\" or \"POST\"!");
exit (SS$_NORMAL);
}
CgiLibReadRequestBody (&BufferPtr, &BufferCount);
if (BufferPtrPtr != NULL) *BufferPtrPtr = BufferPtr;
if (DataSizePtr != NULL) *DataSizePtr = BufferCount;
}
/****************************************************************************/
/*
Read the file contents specified by 'FileName' into memory, set the pointer
at 'FileTextPtr' to the contents and the file size at 'FileSizePtr'. Returns a
VMS status value that should be checked.
*/
int ReadFileIntoMemory
(
char *Source,
char **FileTextPtr,
int *FileSizePtr
)
{
static int BytesRemaining;
int status,
Bytes,
BufferCount,
Length;
char *BufferPtr,
*LinePtr;
FILE *FilePtr;
stat_t FstatBuffer;
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "ReadFileIntoMemory() |%s|\n", Source);
if ((FilePtr = fopen (Source, "r", "shr=get", "shr=put")) == NULL)
{
status = vaxc$errno;
if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
return (status);
}
if (fstat (fileno(FilePtr), &FstatBuffer) < 0)
{
status = vaxc$errno;
if (Debug) fprintf (stdout, "fstat() %%X%08.08X\n", status);
fclose (FilePtr);
return (status);
}
Bytes = FstatBuffer.st_size;
if (Debug) fprintf (stdout, "%d bytes\n", Bytes);
/* a little margin for error ;^) */
Bytes += 32;
BufferPtr = calloc (Bytes, 1);
if (BufferPtr == NULL)
{
CgiLibResponseError (FI_LI, vaxc$errno, "calloc()");
exit (SS$_NORMAL);
}
BytesRemaining = Bytes;
LinePtr = BufferPtr;
BufferCount = 0;
while (fgets (LinePtr, BytesRemaining, FilePtr) != NULL)
{
/** if (Debug) fprintf (stdout, "|%s|\n", LinePtr); **/
Length = strlen(LinePtr);
LinePtr += Length;
BufferCount += Length;
BytesRemaining -= Length;
}
fclose (FilePtr);
if (Debug) fprintf (stdout, "%d |%s|\n", BufferCount, BufferPtr);
if (FileTextPtr != NULL) *FileTextPtr = BufferPtr;
if (FileSizePtr != NULL) *FileSizePtr = BufferCount;
return (SS$_NORMAL);
}
/*****************************************************************************/
/*
Simply check the template file version.
*/
CheckTemplateVersion (char *TextPtr)
{
char *cptr;
/*********/
/* begin */
/*********/
if (Debug)
fprintf (stdout, "CheckTemplateVersion() |%*.*s|\n", 10, 10, TextPtr);
for (cptr = TextPtr; *cptr && (*cptr == ' ' || *cptr == '\t'); cptr++);
if (!strsame (cptr, "tmail:", 6))
{
CgiLibResponseError (FI_LI, 0, "\"tmail:\"? Not a template file?");
exit (SS$_NORMAL);
}
cptr += 6;
while (*cptr && (*cptr == ' ' || *cptr == '\t')) cptr++;
if (!strsame (cptr, TMAIL_VERSION, 3))
{
CgiLibResponseError (FI_LI, 0, "Template file version mismatch.");
exit (SS$_NORMAL);
}
}
/*****************************************************************************/
/*
Creates a large, single, dynamically allocated, null-terminated text string
based on the contents of the template file. The template is parsed from
beginning to end expanding tags, etc. As the parse progresses the various
components from the template header, body (mail message) and any CGI response
are identified and the offsets placed in global storage. These will later be
used to generate pointers and otherwise reprocess the parsed contents (by
ResolvePointers()).
*/
ParseTemplate (char* String)
{
#define EXPAND_PARSE_MEMORY \
{ \
Count = sptr - ParsedTemplatePtr; \
if ((ParsedTemplatePtr = \
realloc (ParsedTemplatePtr, BufferSize+BufferChunk+1)) == NULL) \
{ \
CgiLibResponseError (FI_LI, vaxc$errno, "realloc()"); \
exit (SS$_NORMAL); \
} \
BufferSize += BufferChunk; \
zptr = ParsedTemplatePtr + BufferSize; \
sptr = ParsedTemplatePtr + Count; \
}
/***********/
/* storage */
/***********/
static int BufferChunk = 1024;
boolean InTemplateHeader;
int BufferSize,
Count;
char *cptr, *sptr, *tptr, *zptr,
*TagNameEndCharPtr;
char ErrorString [256],
TagName [256];
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "ParseTemplate() |%s|\n", String);
InTemplateHeader = true;
CgiResponseOffset =
CgiResponseLength =
HeaderToOffset =
HeaderSubjectOffset =
HeaderSuccessOffset =
HeaderSuccessStatusOffset =
HeaderPersonalOffset =
ParsedTemplateLength =
TemplateBodyOffset = 0;
if ((sptr = ParsedTemplatePtr =
calloc (BufferSize = BufferChunk, 1)) == NULL)
{
CgiLibResponseError (FI_LI, vaxc$errno, "calloc()");
exit (SS$_NORMAL);
}
zptr = ParsedTemplatePtr + BufferSize;
cptr = String;
while (*cptr)
{
if (InTemplateHeader)
{
if (*(unsigned short*)cptr == '\n\n')
{
TemplateBodyOffset = sptr - ParsedTemplatePtr + 2;
InTemplateHeader = false;
}
else
if (strsame (cptr, "Tmail:", 6))
HeaderTmailOffset = sptr - ParsedTemplatePtr + 6;
else
if (strsame (cptr, "To:", 3))
HeaderToOffset = sptr - ParsedTemplatePtr + 3;
else
if (strsame (cptr, "Subject:", 8))
HeaderSubjectOffset = sptr - ParsedTemplatePtr + 8;
else
/* must come before "Success:" for the obvious reason */
if (strsame (cptr, "Success-status:", 15))
HeaderSuccessStatusOffset = sptr - ParsedTemplatePtr + 15;
else
if (strsame (cptr, "Success:", 8))
HeaderSuccessOffset = sptr - ParsedTemplatePtr + 8;
else
if (strsame (cptr, "Personal:", 9))
HeaderPersonalOffset = sptr - ParsedTemplatePtr + 9;
}
switch (*cptr)
{
case '[' :
/*******/
/* tag */
/*******/
if (*(unsigned short*)cptr == '[[')
{
/* escaped "[" */
if (sptr >= zptr) EXPAND_PARSE_MEMORY;
cptr++;
*sptr++ = *cptr++;
continue;
}
if (*(unsigned short*)cptr == '[]')
{
/* escaped "]" */
if (sptr >= zptr) EXPAND_PARSE_MEMORY;
cptr++;
*sptr++ = *cptr++;
continue;
}
cptr++;
if (*(unsigned short*)cptr == '%%')
{
/***************/
/* command tag */
/***************/
if (strsame (cptr, "%%end]", 6))
{
cptr += 6;
CommandTagEnd = CommandTagEntify = true;
while (*cptr && *cptr != '\n') cptr++;
if (*cptr) cptr++;
CgiResponseOffset = sptr - ParsedTemplatePtr;
continue;
}
if (strsame (cptr, "%%entify]", 9))
{
cptr += 9;
CommandTagEntify = true;
continue;
}
if (strsame (cptr, "%%noentify]", 11))
{
cptr += 11;
CommandTagEntify = false;
continue;
}
if (strsame (cptr, "%%reveal]", 9))
{
/* special case, reveal all available data */
fprintf (stdout, "%s\n\nCGI variables:\n\n", SoftwareID);
fflush (stdout);
system ("show symbol www_*");
fprintf (stdout, "\nForm fields:\n\n");
RevealFormFields ();
exit (SS$_NORMAL);
}
for (sptr = cptr; *sptr && *sptr != '\n'; sptr++);
*sptr = '\0';
sprintf (ErrorString, "Unknown command tag: \"[%s\"",
(char*)CgiLibHtmlEscape(cptr, -1, NULL, -1));
CgiLibResponseError (FI_LI, 0, String);
exit (SS$_NORMAL);
}
if (*cptr == '%')
{
/****************/
/* CGI variable */
/****************/
cptr++;
cptr += GetTagName (cptr, TagName, sizeof(TagName),
&TagNameEndCharPtr);
tptr = TagCgiVar (TagName, TagNameEndCharPtr);
while (*tptr)
{
if (sptr >= zptr) EXPAND_PARSE_MEMORY;
*sptr++ = *tptr++;
}
continue;
}
/**************/
/* field name */
/**************/
cptr += GetTagName (cptr, TagName, sizeof(TagName),
&TagNameEndCharPtr);
tptr = TagFormCgiVar (TagName, TagNameEndCharPtr);
while (*tptr)
{
if (sptr >= zptr) EXPAND_PARSE_MEMORY;
*sptr++ = *tptr++;
}
continue;
/********************/
/* just a character */
/********************/
default :
if (sptr >= zptr) EXPAND_PARSE_MEMORY;
*sptr++ = *cptr++;
continue;
}
}
*sptr = '\0';
ParsedTemplateLength = sptr - ParsedTemplatePtr;
CgiResponseLength = ParsedTemplateLength - CgiResponseOffset;
TemplateBodyLength = ParsedTemplateLength - TemplateBodyOffset -
CgiResponseLength - 1;
}
/*****************************************************************************/
/*
During the parse of the template a number of byte-count offsets into the
dynamically allocated parsed text are set representing the starting locations
of strings of various interests. Now the parsing and realloc()ing are over
turn these into pointers to null-terminated strings.
*/
ResolvePointers ()
{
char *cptr;
/*********/
/* begin */
/*********/
if (Debug)
fprintf (stdout, "ResolvePointers() %d %d %d %d %d %d %d %d %d\n",
HeaderToOffset, HeaderSubjectOffset,
HeaderSuccessOffset, HeaderSuccessStatusOffset,
HeaderPersonalOffset,
TemplateBodyOffset, TemplateBodyLength,
CgiResponseOffset, CgiResponseLength);
if (HeaderToOffset)
{
cptr = ParsedTemplatePtr + HeaderToOffset;
while (*cptr && ISLWS(*cptr)) cptr++;
HeaderToPtr = cptr;
while (*cptr && *cptr != '\n') cptr++;
*cptr = '\0';
}
else
HeaderToPtr = "";
if (HeaderSubjectOffset)
{
cptr = ParsedTemplatePtr + HeaderSubjectOffset;
while (*cptr && ISLWS(*cptr)) cptr++;
HeaderSubjectPtr = cptr;
while (*cptr && *cptr != '\n') cptr++;
*cptr = '\0';
}
else
HeaderSubjectPtr = "";
if (HeaderSuccessOffset)
{
cptr = ParsedTemplatePtr + HeaderSuccessOffset;
while (*cptr && ISLWS(*cptr)) cptr++;
HeaderSuccessPtr = cptr;
while (*cptr && *cptr != '\n') cptr++;
*cptr = '\0';
}
else
HeaderSuccessPtr = "";
if (HeaderSuccessStatusOffset)
{
cptr = ParsedTemplatePtr + HeaderSuccessStatusOffset;
while (*cptr && ISLWS(*cptr)) cptr++;
HeaderSuccessStatusPtr = cptr;
while (*cptr && *cptr != '\n') cptr++;
*cptr = '\0';
}
else
HeaderSuccessStatusPtr = "";
if (HeaderPersonalOffset)
{
cptr = ParsedTemplatePtr + HeaderPersonalOffset;
while (*cptr && ISLWS(*cptr)) cptr++;
HeaderPersonalPtr = cptr;
while (*cptr && *cptr != '\n') cptr++;
*cptr = '\0';
}
else
HeaderPersonalPtr = 0;
if (TemplateBodyOffset)
{
TemplateBodyPtr = ParsedTemplatePtr + TemplateBodyOffset;
TemplateBodyPtr[TemplateBodyLength] = '\0';
}
else
{
CgiLibResponseError (FI_LI, 0, "No template body found.");
exit (SS$_NORMAL);
}
if (CgiResponseOffset)
CgiResponsePtr = ParsedTemplatePtr + CgiResponseOffset;
else
CgiResponsePtr = NULL;
if (Debug)
fprintf (stdout, "|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n",
HeaderToPtr, HeaderSubjectPtr,
HeaderSuccessPtr, HeaderSuccessStatusPtr,
TemplateBodyPtr, CgiResponsePtr);
}
/*****************************************************************************/
/*
Parse a [...] delimited tag name from the string. Any of ':', '?' or ']' will
terminate the tag name. Set the 'TagNameEndCharPtrPtr' to the address of this
terminator.
*/
int GetTagName
(
char *String,
char *BufferPtr,
int SizeOfBuffer,
char **TagNameEndCharPtrPtr
)
{
char *cptr, *sptr, *zptr;
char ErrorString [256];
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "GetTagName()\n");
zptr = (sptr = BufferPtr) + SizeOfBuffer;
for (cptr = String;
*cptr && *cptr != ']' && *cptr != ':' && *cptr != '?' && sptr < zptr;
*sptr++ = *cptr++);
{
/* allow for escaped characters */
if (*(unsigned short*)cptr == '[]')
cptr++;
else
if (*(unsigned short*)cptr == '[:')
cptr++;
else
if (*(unsigned short*)cptr == '[?')
cptr++;
}
if (sptr < zptr && (*cptr == ']' || *cptr == ':' || *cptr == '?'))
{
*sptr = '\0';
if (Debug) fprintf (stdout, "|%s|\n", BufferPtr);
*TagNameEndCharPtrPtr = cptr;
if (*cptr != ']')
{
while (*cptr && *cptr != ']')
{
if (*(unsigned short*)cptr == '[]') cptr++;
cptr++;
}
}
if (*cptr) cptr++;
return (cptr - String);
}
sprintf (ErrorString, "Tag problem: \"%s\"",
(char*)CgiLibHtmlEscape(String, -1, NULL, -1));
CgiLibResponseError (FI_LI, 0, ErrorString);
exit (SS$_NORMAL);
}
/*****************************************************************************/
/*
Get the CGI variable value represented by the tag name. Requires prefixing the
variable name with "WWW_". Do a conditional expansion on the variable value.
*/
char* TagCgiVar
(
char *VarName,
char *TagNameEndCharPtr
)
{
static char WwwVarName [256] = "WWW_";
int Length;
char *cptr, *sptr, *zptr;
/*********/
/* begin */
/*********/
if (Debug)
fprintf (stdout, "TagCgiVar() |%s| %c\n", VarName, *TagNameEndCharPtr);
cptr = CgiLibVar (VarName);
cptr = ConditionalExpansion (cptr, TagNameEndCharPtr);
if (CommandTagEntify)
{
sptr = (char*)CgiLibHtmlEscape (cptr, -1, NULL, -1);
free (cptr);
cptr = sptr;
}
if (Debug) fprintf (stdout, "%s |%s|\n", WwwVarName, cptr);
return (cptr);
}
/*****************************************************************************/
/*
Get the form-URL-encoded form field value. This will have come from the query
string if a "GET" request or the request body if a "POST" request. Both will
the same format, a URL-encoded, null-terminated string. Do a conditional
expansion on the variable value.
*/
char* TagFormCgiVar
(
char *VarName,
char *TagNameEndCharPtr
)
{
int Length;
char *cptr, *sptr, *zptr;
/*********/
/* begin */
/*********/
if (Debug)
fprintf (stdout, "TagFormCgiVar() |%s| %c\n",
VarName, *TagNameEndCharPtr);
cptr = RequestDataPtr;
for (;;)
{
if (!*cptr) break;
for (sptr = cptr; *sptr && *sptr != '='; sptr++);
if (!*sptr)
{
cptr = "";
break;
}
*sptr = '\0';
if (Debug) fprintf (stdout, "|%s|\n", cptr);
/* case-sensitive compare for field names */
if (strcmp (cptr, VarName))
{
*sptr++ = '=';
for (cptr = sptr; *cptr && *cptr != '&'; cptr++);
if (*cptr) cptr++;
continue;
}
*sptr++ = '=';
cptr = sptr;
while (*sptr && *sptr != '&') sptr++;
Length = sptr - cptr;
if ((sptr = calloc (Length+1, 1)) == NULL)
{
CgiLibResponseError (FI_LI, vaxc$errno, "calloc()");
exit (SS$_NORMAL);
}
memcpy (sptr, cptr, Length);
sptr[Length] = '\0';
/* now URL-decode in-situ */
CgiLibUrlDecode (cptr = sptr);
break;
}
cptr = ConditionalExpansion (cptr, TagNameEndCharPtr);
if (CommandTagEntify)
{
sptr = (char*)CgiLibHtmlEscape (cptr, -1, NULL, -1);
free (cptr);
cptr = sptr;
}
if (Debug) fprintf (stdout, "|%s|\n", cptr);
return (cptr);
}
/*****************************************************************************/
/*
Just display each form field as 'name == "value"' (for template debugging).
*/
RevealFormFields ()
{
char *cptr, *sptr;
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "RevealFormFields()\n");
cptr = RequestDataPtr;
while (*cptr)
{
for (sptr = cptr; *sptr && *sptr != '='; sptr++)
if (!*sptr) break;
*sptr++ = '\0';
fprintf (stdout, " %s == \"", cptr);
for (cptr = sptr; *cptr && *cptr != '&'; cptr++);
if (*cptr) *cptr++ = '\0';
/* now URL-decode in-situ */
CgiLibUrlDecode (sptr);
fprintf (stdout, "%s\"\n", sptr);
}
}
/*****************************************************************************/
/*
If the tag name ended with a ':' and the tag value was empty (null) then the
and empty string is returned. If the tag value was non-empty the text
following the ':' up to the next ']' is returned. If the tag name ended with a
'?' and the tag value was "on" (case-insensitive) then the text following the
'?' and up to the next ']' is returned. If not "on" an empty string is
returned.
*/
char* ConditionalExpansion
(
char *TagValue,
char *TagNameEndCharPtr
)
{
int Length;
char *cptr, *sptr, *vptr;
/*********/
/* begin */
/*********/
if (Debug)
fprintf (stdout, "ConditionalExpansion() |%s| %c\n",
TagValue, *TagNameEndCharPtr);
if (*TagNameEndCharPtr == ':')
{
TagNameEndCharPtr++;
if (TagValue != NULL && TagValue[0])
{
for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++)
{
if (*(unsigned short*)sptr == '[]') sptr++;
if (*(unsigned short*)sptr == '::') break;
}
Length = sptr - TagNameEndCharPtr;
if (*(unsigned short*)sptr == '::')
for (/* continue */; *sptr && *sptr != ']'; sptr++)
if (*(unsigned short*)sptr == '[]') sptr++;
}
else
{
for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++)
{
if (*(unsigned short*)sptr == '[]') sptr++;
if (*(unsigned short*)sptr == '::') { sptr += 2; break; }
}
for (TagNameEndCharPtr = sptr; *sptr && *sptr != ']'; sptr++)
if (*(unsigned short*)sptr == '[]') sptr++;
Length = sptr - TagNameEndCharPtr;
}
}
else
if (*TagNameEndCharPtr == '?')
{
TagNameEndCharPtr++;
if (TagValue != NULL && strsame (TagValue, "ON", -1))
{
for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++)
{
if (*(unsigned short*)sptr == '[]') sptr++;
if (*(unsigned short*)sptr == '\?\?') break;
}
Length = sptr - TagNameEndCharPtr;
if (*(unsigned short*)sptr == '\?\?')
for (/* continue */; *sptr && *sptr != ']'; sptr++)
if (*(unsigned short*)sptr == '[]') sptr++;
}
else
{
for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++)
{
if (*(unsigned short*)sptr == '[]') sptr++;
if (*(unsigned short*)sptr == '\?\?') { sptr += 2; break; }
}
for (TagNameEndCharPtr = sptr; *sptr && *sptr != ']'; sptr++)
if (*(unsigned short*)sptr == '[]') sptr++;
Length = sptr - TagNameEndCharPtr;
}
}
else
return (TagValue);
if (Debug) fprintf (stdout, "Length: %d\n", Length);
if (!Length) return ("");
if ((cptr = vptr = calloc (Length+1, 1)) == NULL)
{
CgiLibResponseError (FI_LI, vaxc$errno, "calloc()");
exit (SS$_NORMAL);
}
for (sptr = TagNameEndCharPtr; *sptr && Length; *cptr++ = *sptr++)
{
if (*(unsigned short*)sptr == '[[' ||
*(unsigned short*)sptr == '[:' ||
*(unsigned short*)sptr == '[?' ||
*(unsigned short*)sptr == '[]')
{
if (!Length) break;
Length--;
sptr++;
}
if (!Length) break;
Length--;
}
*cptr = '\0';
return (vptr);
}
/*****************************************************************************/
/*
Use the VMS callable mail interface to create and send a VMS mail message.
'To' can be a list of comma-separated addresses. 'Subject' is a
null-terminated string. 'Body' is a null-terminated string of '\n'-separated
lines of plain text. Just truncates anything longer than 255 characters (body
excluded, body records included)!
*/
int MailMessage
(
char *PersonalName,
char *To,
char *Subject,
char *Body
)
{
int status;
unsigned long SendContext = 0;
char *cptr, *sptr;
struct {
short int buf_len;
short int item;
void *buf_addr;
unsigned short *ret_len;
}
BodyPartItem [] =
{
{ 0, MAIL$_SEND_RECORD, 0, 0 },
{ 0, MAIL$_NOSIGNAL, 0, 0 },
{0,0,0,0}
},
PersonalNameItem [] =
{
{ 0, MAIL$_SEND_PERS_NAME, PersonalName, 0 },
{ 0, MAIL$_NOSIGNAL, 0, 0 },
{0,0,0,0}
},
SendUserNameItem [] =
{
{ 0, MAIL$_SEND_USERNAME, 0, 0 },
{ 0, MAIL$_NOSIGNAL, 0, 0 },
{0,0,0,0}
},
SubjectItem [] =
{
{ 0, MAIL$_SEND_SUBJECT, Subject, 0 },
{ 0, MAIL$_NOSIGNAL, 0, 0 },
{0,0,0,0}
},
NoSignalItem [] =
{
{ 0, MAIL$_NOSIGNAL, 0, 0 },
{0,0,0,0}
},
NullItem = {0,0,0,0};
/*********/
/* begin */
/*********/
if (Debug)
fprintf (stdout, "MailMessage() |%s|\n|%s|\n|%s|\n",
PersonalName, To, Subject);
if (PersonalName != NULL && PersonalName[0])
{
PersonalNameItem[0].buf_len = strlen(PersonalName);
if (PersonalNameItem[0].buf_len > 255) PersonalNameItem[0].buf_len = 255;
status = mail$send_begin (&SendContext, &PersonalNameItem, &NullItem);
}
else
status = mail$send_begin (&SendContext, &NoSignalItem, &NullItem);
if (VMSnok (status))
{
CgiLibResponseError (FI_LI, status, "beginning message");
exit (SS$_NORMAL);
}
/* a single, or multiple comma-separated addresses */
cptr = To;
while (*cptr)
{
sptr = cptr;
while (*cptr && *cptr != ',') cptr++;
if (*cptr) *cptr++ = '\0';
SendUserNameItem[0].buf_addr = sptr;
SendUserNameItem[0].buf_len = strlen(sptr);
if (SendUserNameItem[0].buf_len > 255) SendUserNameItem[0].buf_len = 255;
if (Debug)
fprintf (stdout, "address |%s|\n",
(char*)SendUserNameItem[0].buf_addr);
status = mail$send_add_address (&SendContext, &SendUserNameItem,
&NullItem);
if (VMSnok (status))
{
CgiLibResponseError (FI_LI, status, sptr);
exit (SS$_NORMAL);
}
}
SubjectItem[0].buf_len = strlen(Subject);
if (SubjectItem[0].buf_len > 255) SubjectItem[0].buf_len = 255;
status = mail$send_add_attribute (&SendContext, &SubjectItem, &NullItem);
if (VMSnok (status))
{
CgiLibResponseError (FI_LI, status, "adding subject");
exit (SS$_NORMAL);
}
cptr = Body;
while (*cptr)
{
BodyPartItem[0].buf_addr = sptr = cptr;
/* break at 255 characters regardless */
while (*cptr && cptr-sptr < 255 && *cptr != '\n') cptr++;
BodyPartItem[0].buf_len = cptr - sptr;
if (*cptr && *cptr == '\n') cptr++;
status = mail$send_add_bodypart (&SendContext, &BodyPartItem, &NullItem);
if (VMSnok (status))
{
CgiLibResponseError (FI_LI, status, "adding body");
exit (SS$_NORMAL);
}
}
status = mail$send_message (&SendContext, &NoSignalItem, &NoSignalItem);
if (VMSnok (status))
{
CgiLibResponseError (FI_LI, status, "sending message");
exit (SS$_NORMAL);
}
mail$send_end (&SendContext, &NullItem, &NullItem);
return (SS$_NORMAL);
}
/*****************************************************************************/
/*
Just reveal the contents of the mail message (for template debugging).
*/
RevealMailMessage
(
char *PersonalName,
char *To,
char *Subject,
char *Body
)
{
/*********/
/* begin */
/*********/
if (Debug) fprintf (stdout, "RevealMailMessage()\n");
fprintf (stdout,
"From: HTTP$SERVER \"%s\"\n\
To: %s\n\
CC:\n\
Subj: %s\n\
\n\
%s",
PersonalName, To+1, Subject, Body);
}
/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns
true if two strings are the same, or false if not.
*/
boolean strsame
(
char *sptr1,
char *sptr2,
int count
)
{
/*********/
/* begin */
/*********/
if (count > 0)
return (strncasecmp (sptr1, sptr2, count) == 0);
else
return (strcasecmp (sptr1, sptr2) == 0);
}
/*****************************************************************************/