/*
* ratMailcap.c --
*
* This file contains support for reading & parsing mailcap files
* Mailcap files are defined in rfc1343
*
* TkRat software and its included text is Copyright 1996-2002 by
* Martin Forssén
*
* The full text of the legal notices is contained in the file called
* COPYRIGHT, included with this distribution.
*/
#include "ratFolder.h"
/*
* Each entry is represented by one of the following structures
*/
typedef struct {
char *type;
char *subtype;
char *test;
char *view;
char *compose;
char *composetyped;
char *edit;
char *print;
unsigned int needsterminal : 1;
unsigned int copiousoutput : 1;
char *description;
char *bitmap;
} MailcapEntry;
/*
* Id of the current load of the table
*/
static int tableId = 0;
/*
* Pointer to the current table as well as current size, allocated size
* and increment.
*/
static MailcapEntry *tablePtr = NULL;
static int tableSize = 0;
static int tableAllocated = 0;
/*
* Local functions
*/
static void MailcapReload(Tcl_Interp *interp);
static char *ExpandString(Tcl_Interp *interp, BodyInfo *bodyInfoPtr, char *s,
char **filePtr);
/*
*----------------------------------------------------------------------
*
* MailcapReload --
*
* Reloads the mailcaps into memory.
*
* Results:
* None.
*
* Side effects:
* The mailcap files will be loaded.
*
*
*----------------------------------------------------------------------
*/
static void
MailcapReload(Tcl_Interp *interp)
{
static char **textBlock = NULL;
static int numTextBlocks = 0;
static int allocTextBlocks = 0;
Tcl_DString ds, bufDs;
struct stat sbuf;
char buf[1024], *s, *cPtr, **cPtrPtr, *dstPtr, *data, *tPtr, *pPtr;
int i, fd, line;
/*
* Free old data
*/
for (i=0; i<numTextBlocks; i++) {
ckfree(textBlock[i]);
}
numTextBlocks = tableSize = 0;
/*
* Construct path to search
*/
Tcl_DStringInit(&ds);
if ((s = getenv("MAILCAP"))) {
Tcl_DStringAppend(&ds, s, -1);
Tcl_DStringAppend(&ds, ":", 1);
}
Tcl_DStringAppend(&ds,
Tcl_GetVar2(interp, "option", "mailcap_path", TCL_GLOBAL_ONLY),-1);
/*
* Check all files mentioned in the path
*/
Tcl_DStringInit(&bufDs);
for (pPtr = Tcl_DStringValue(&ds); *pPtr; ) {
for (s=pPtr++; *pPtr && ':' != *pPtr; pPtr++);
if (*pPtr) {
*pPtr++ = '\0';
}
Tcl_DStringSetLength(&bufDs, 0);
s = Tcl_TranslateFileName(interp, s, &bufDs);
if (stat(s, &sbuf) || !S_ISREG(sbuf.st_mode)) {
/*
* No such file
*/
continue;
}
/*
* Now we should read the file into a buffer
*/
if (-1 == (fd = open(s, O_RDONLY))) {
continue;
}
if (allocTextBlocks == numTextBlocks) {
allocTextBlocks += 5;
textBlock =
(char**)ckrealloc(textBlock,allocTextBlocks*sizeof(char*));
}
data = textBlock[numTextBlocks++] = (char*)ckalloc(sbuf.st_size+1);
read(fd, data, sbuf.st_size);
close(fd);
data[sbuf.st_size] = '\0';
/*
* Extract the useful data (join lines and discard non-data lines)
*
* This is done in a loop which loops over all the data.
* - In the loop we start by skipping all initial whitespace.
* - Then we check the first character. If it is an '#' then we
* skip until the next newline and restart the loop.
* - Else we start copying characters to the real area until
* we find a newline. When we do we check the preceding character
* and if it was a '\' we skip the last two characters and
* continue to copy the next line etc.
*/
for (i = line = 0, dstPtr = data; i < sbuf.st_size; i++) {
while (data[i] && isspace((unsigned char)data[i])) {
if ('\n' == data[i]) {
line++;
}
i++;
}
if (!data[i]) {
break;
}
if ('#' == data[i]) {
for (; '\n' != data[i] && data[i]; i++);
line++;
continue;
}
cPtr = dstPtr;
do {
while (data[i] && '\n' != data[i]) {
*dstPtr++ = data[i++];
}
if ('\\' != data[i-1]) {
break;
}
dstPtr -= 1;
line++;
} while (data[i++]);
*dstPtr++ = '\0';
line++;
/*
* Parse the line into an entry
*
* - First we make sure there are enough empty entries in the table
* - Then we find the first delimiter in the type specification.
* If this is ';' then we assume that the subtype should be '*'.
* If it is '/' then we read the subtype.
* - The we read the value (we start by discarding whitespace
* and then we copy everything until the ';'.
* - Finally we loop over the other entries and read them
*/
if (tableSize == tableAllocated) {
tableAllocated = tableSize+64;
tablePtr = (MailcapEntry*)ckrealloc(tablePtr,
tableAllocated*sizeof(MailcapEntry));
}
tablePtr[tableSize].type = cPtr;
for (;'/' != *cPtr && ';' != *cPtr && *cPtr; cPtr++);
if (!*cPtr) {
RatLogF(interp, RAT_ERROR, "syntax_error", RATLOG_TIME,s,line);
continue;
}
if (';' == *cPtr) {
*cPtr++ = '\0';
tablePtr[tableSize].subtype = "*";
} else {
*cPtr++ = '\0';
tablePtr[tableSize].subtype = cPtr;
for (;';' != *cPtr && *cPtr; cPtr++);
if (!*cPtr) {
RatLogF(interp, RAT_ERROR, "syntax_error", RATLOG_TIME,
s, line);
continue;
}
for (tPtr = cPtr-1;
*tPtr && isspace((unsigned char)*tPtr); tPtr--) {
*tPtr = '\0';
}
*cPtr++ = '\0';
}
for (;isspace((unsigned char)*cPtr) && *cPtr; cPtr++);
if (!*cPtr) {
RatLogF(interp, RAT_ERROR, "syntax_error", RATLOG_TIME,s,line);
continue;
}
tablePtr[tableSize].view = cPtr;
for (;*cPtr && (';' != *cPtr || '\\' == *(cPtr-1)); cPtr++);
tablePtr[tableSize].test = NULL;
tablePtr[tableSize].compose = NULL;
tablePtr[tableSize].composetyped = NULL;
tablePtr[tableSize].edit = NULL;
tablePtr[tableSize].print = NULL;
tablePtr[tableSize].description = NULL;
tablePtr[tableSize].bitmap = NULL;
tablePtr[tableSize].needsterminal = 0;
tablePtr[tableSize].copiousoutput = 0;
while (*cPtr) {
*cPtr++ = '\0';
cPtrPtr = NULL;
for (;isspace((unsigned char)*cPtr) && *cPtr; cPtr++);
if (!strncasecmp(cPtr, "test", 4)) {
cPtrPtr = &tablePtr[tableSize].test;
} else if (!strncasecmp(cPtr, "composetyped", 12)) {
cPtrPtr = &tablePtr[tableSize].composetyped;
} else if (!strncasecmp(cPtr, "compose", 7)) {
cPtrPtr = &tablePtr[tableSize].compose;
} else if (!strncasecmp(cPtr, "edit", 4)) {
cPtrPtr = &tablePtr[tableSize].edit;
} else if (!strncasecmp(cPtr, "print", 5)) {
cPtrPtr = &tablePtr[tableSize].print;
} else if (!strncasecmp(cPtr, "description", 11)) {
cPtrPtr = &tablePtr[tableSize].description;
} else if (!strncasecmp(cPtr, "bitmap", 6)) {
cPtrPtr = &tablePtr[tableSize].bitmap;
} else if (!strncasecmp(cPtr, "needsterminal", 13)) {
tablePtr[tableSize].needsterminal = 1;
} else if (!strncasecmp(cPtr, "copiousoutput", 13)) {
tablePtr[tableSize].copiousoutput = 1;
}
for (;isalpha((unsigned char)*cPtr) && *cPtr; cPtr++);
for (;isspace((unsigned char)*cPtr) && *cPtr; cPtr++);
if (!cPtrPtr) {
continue;
}
if ('=' != *cPtr) {
snprintf(buf, sizeof(buf),
"Syntax error in %s at line %d", s, line);
for (; *cPtr; cPtr++);
continue;
}
for (cPtr++;isspace((unsigned char)*cPtr) && *cPtr; cPtr++);
if (!*cPtr) {
snprintf(buf, sizeof(buf),
"Syntax error in %s at line %d", s, line);
for (; *cPtr; cPtr++);
continue;
}
*cPtrPtr = cPtr;
for (;*cPtr && (';' != *cPtr || '\\' == *(cPtr-1)); cPtr++);
}
tableSize++;
}
}
Tcl_DStringFree(&bufDs);
tableId++;
}
/*
*----------------------------------------------------------------------
*
* ExpandString --
*
* Expands the given string. If filePtr is null the any '%s' in the
* string will be left as is.
*
* Results:
* Returns a pointer to a static area which contains the expanded
* string. If the string contained any "%s" then a pointer to the
* filename they were replaced with will be placed in *filePtr. If
* not then this value will be NULL.
* If the expansion failed then NULL is returned.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static char*
ExpandString(Tcl_Interp *interp, BodyInfo *bodyInfoPtr, char *s,char **filePtr)
{
static Tcl_DString ds;
static Tcl_DString file;
static int init = 0;
char *cPtr, *srcPtr;
PARAMETER *parmPtr;
int l;
/*
* Initialize the string the first time.
*/
if (!init) {
Tcl_DStringInit(&ds);
Tcl_DStringInit(&file);
init = 1;
}
if (filePtr) {
*filePtr = NULL;
}
Tcl_DStringSetLength(&ds, 0);
Tcl_DStringSetLength(&file, 0);
for (srcPtr = s; *srcPtr; ) {
if ('\\' == *srcPtr) {
Tcl_DStringAppend(&ds, ++srcPtr, 1);
if (*srcPtr) {
srcPtr++;
}
continue;
}
if ('%' != *srcPtr) {
Tcl_DStringAppend(&ds, srcPtr++, 1);
continue;
}
srcPtr++;
if ('s' == *srcPtr) {
if (filePtr) {
if (0 == Tcl_DStringLength(&file)) {
Tcl_DStringAppend(&file, "/tmp/rat.", -1);
RatGenId(NULL, interp, 0, NULL);
Tcl_DStringAppend(&file, Tcl_GetStringResult(interp), -1);
*filePtr = Tcl_DStringValue(&file);
}
Tcl_DStringAppend(&ds, Tcl_DStringValue(&file), -1);
} else {
Tcl_DStringAppend(&ds, "%s", 2);
}
srcPtr++;
continue;
}
if ('t' == *srcPtr) {
for (cPtr = body_types[bodyInfoPtr->bodyPtr->type]; *cPtr; cPtr++){
if (strchr("|<>%*?\"`'", *cPtr)) {
Tcl_DStringAppend(&ds, " ", 1);
} else {
Tcl_DStringAppend(&ds, cPtr, 1);
}
}
Tcl_DStringAppend(&ds, "/", 1);
for (cPtr = bodyInfoPtr->bodyPtr->subtype; *cPtr; cPtr++) {
if (strchr("|<>%*?\"`'", *cPtr)) {
Tcl_DStringAppend(&ds, " ", 1);
} else {
Tcl_DStringAppend(&ds, cPtr, 1);
}
}
srcPtr++;
continue;
}
if ('{' != *srcPtr++) {
if (filePtr) {
*filePtr = NULL;
}
return NULL;
}
for (cPtr = srcPtr, l = 0; *srcPtr && '}' != *srcPtr; srcPtr++, l++);
if (*srcPtr) {
srcPtr++;
}
for (parmPtr = bodyInfoPtr->bodyPtr->parameter; parmPtr;
parmPtr = parmPtr->next) {
if (!strncasecmp(cPtr, parmPtr->attribute, l)) {
break;
}
}
if (!parmPtr) {
if (filePtr) {
*filePtr = NULL;
}
return NULL;
}
/*
* Copy the parameter value and in the process we remove any
* chanacters that might be used to slip a trojan horse in
* through our gates.
*/
for (cPtr = parmPtr->value; *cPtr; cPtr++) {
if (strchr("|<>%*?\"`'", *cPtr)) {
Tcl_DStringAppend(&ds, " ", 1);
} else {
Tcl_DStringAppend(&ds, cPtr, 1);
}
}
}
return Tcl_DStringValue(&ds);
}
/*
*----------------------------------------------------------------------
*
* RatMcapFindCmd --
*
* Find a matching mailcap entry for a bodypart
*
* Results:
* See ../doc/interface
*
* Side effects:
* The mailcap files may be loaded.
*
*
*----------------------------------------------------------------------
*/
int
RatMcapFindCmd(Tcl_Interp *interp, BodyInfo *bodyInfoPtr)
{
char *cmd, *file, *s;
Tcl_Channel channel;
int i, perm;
Tcl_Obj *rPtr, *oPtr;
/*
* We start by making sure that the mailcap files have been loaded
*/
if (0 == tableId) {
MailcapReload(interp);
}
/*
* Loop through all entries and check them.
* - First we check the type/subtype for match.
* - If they matched we check eventual test commands
*/
for (i=0; i<tableSize; i++) {
if (strcasecmp(tablePtr[i].type,body_types[bodyInfoPtr->bodyPtr->type])
|| ('*' != *tablePtr[i].subtype
&& strcasecmp(tablePtr[i].subtype,
bodyInfoPtr->bodyPtr->subtype))) {
continue;
}
if (tablePtr[i].test) {
if (!(cmd = ExpandString(interp, bodyInfoPtr, tablePtr[i].test,
&file))) {
continue;
}
if (file) {
oPtr = Tcl_GetVar2Ex(interp, "option", "permissions",
TCL_GLOBAL_ONLY);
Tcl_GetIntFromObj(interp, oPtr, &perm);
channel = Tcl_OpenFileChannel(interp, file, "w", perm);
RatBodySave(interp, channel, bodyInfoPtr, 0, 1);
Tcl_Close(interp, channel);
}
if (system(cmd)) {
if (file) {
unlink(file);
}
continue;
}
if (file) {
unlink(file);
}
}
rPtr = Tcl_NewObj();
s = ExpandString(interp, bodyInfoPtr, tablePtr[i].view, NULL);
Tcl_ListObjAppendElement(interp, rPtr, Tcl_NewStringObj(s, -1));
Tcl_ListObjAppendElement(interp, rPtr,
Tcl_NewBooleanObj(tablePtr[i].needsterminal));
Tcl_ListObjAppendElement(interp, rPtr,
Tcl_NewBooleanObj(tablePtr[i].copiousoutput));
Tcl_ListObjAppendElement(interp, rPtr,
Tcl_NewStringObj(tablePtr[i].description,-1));
Tcl_ListObjAppendElement(interp, rPtr,
Tcl_NewStringObj(tablePtr[i].bitmap, -1));
Tcl_SetObjResult(interp, rPtr);
return TCL_OK;
}
Tcl_SetResult(interp, "{} 0 0 {} {}", TCL_STATIC);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* RatMailcapReload --
*
* This is just a wrapper which calls MailcapReload
*
* Results:
* A standard tcl result.
*
* Side effects:
* The mailcap files will be loaded.
*
*
*----------------------------------------------------------------------
*/
int
RatMailcapReload(ClientData dummy, Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[])
{
MailcapReload(interp);
return TCL_OK;
}
syntax highlighted by Code2HTML, v. 0.9.1