/*
 * This module is called by the webbook main routine sees a path_info
 * string ending in slash, implying that the the atarget is a shelf.
 * The main routine deletes the trailing slash and passes the path as an
 * argument to webbook_shelf(). This string is assumed to be the name of
 * the bookshelf file to read (default decw$book:library.decw$bookshelf).  
 * Succeeding elements are used to hold the history of which shelves were 
 * travered to get to the current shelf.
 *
 * Conditional compilation symbols:
 *   NOCGILIB
 *
 * Revised: 29-JUN-1996		Incorporate bug fixes supplied by Jose Baars,
 *				baarsj@abnamro.nl.
 * Revsied: 12-JUL-1999		Update for ODL/BKB.
 *  
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "cgilib.h"

#define SHELF_ICON "<IMG SRC=\"%s/shelf.xbm\">"
#define BOOK_ICON "<IMG SRC=\"%s/book.xbm\">"
#define HTML_ICON "<IMG SRC=\"%s/text.gif\">"
#define DEFAULT_ICON_DIR "/demo"
static char *icon_dir;

struct shelf_rec {
    struct shelf_rec *next;
    char *type;		/* first element. */
    char *fname;
    char *desc;
    int is_odl;		/* true if newer ODL format */
};
typedef struct shelf_rec *shelf_recp;

static shelf_recp get_shelf_rec ( FILE *sf, shelf_recp prev );
static char *escape_string ( char * );
static char *entify_string ( char * );
static char *apply_default ( char *fname, char *defname, char *out );
static int send_http_header ( char *stsline, char *content );

int webbook_shelf ( char *path, char *software_version )
{
   char *title, *script_name, *elem[20];
   int status, i, j, lvl;
   FILE *sf;
   struct shelf_rec *rec, first_rec;
   char *default_library;
   char title_spec[256];
   char shelf_spec[256];
   char dna_spec[260];
   char shelf_history[2048];
   char icon_image[300];
   /*
    * Parse the path elements.
    */
   strncpy ( shelf_history, path, sizeof(shelf_history)-1 );
   path = shelf_history;
   path[sizeof(shelf_history)-1] = '\0';
   if ( *path == '/' ) path++;
   elem[0] = path;
   for ( j = i = 0; path[i]; i++ ) if ( path[i] == '/' ) {
	path[i] = '\0';
	j++;
	elem[j] = &path[i+1];
	if ( j >= 19 ) break;
   }
   /*
    * Determine approiate default name for open based upon existence
    * of odlibrary logical.
    */
   default_library = getenv("odlibrary");
   if (!default_library) default_library = "decw$book:library.decw$bookshelf";
   else if (strlen(default_library) > sizeof(dna_spec)-5 )
	default_library = "library.odl";
   sprintf ( dna_spec, "dna=%s", default_library );
   /*
    * Attempt to open the shelf file and parse.
    */
   first_rec.next = (shelf_recp) 0;
   first_rec.type = "";
   rec = &first_rec;
   title = path;
#ifdef VERBOSE
printf("<!-- path: '%s', shelf file (elem[%d]): '%s'-->\n", path, j, elem[j] );
#endif
   sf = fopen ( elem[j], "r", dna_spec, "mbc=64" );
   if ( sf ) {
	while ( rec = get_shelf_rec ( sf, rec ) ) {
	    if ( 0 == strcmp ( rec->type, "TITLE" ) ) {
		title = rec->desc;
		if ( rec->is_odl ) {
		    if ( (strlen(rec->fname)+strlen(rec->desc)+2) < 256) {
			sprintf(title_spec, "%s %s", rec->fname, rec->desc );
			title = title_spec;
		    }
		}
	    }
	}
	fgetname(sf, shelf_spec, 1);	/* full file name of bookshelf */
	fclose ( sf );
   }
   /*
    * Generate HTML header.
    */
   send_http_header ( "200 Sending generated HTML", "Text/html" );
   cgi_printf("<HTML>\n<HEAD><TITLE>%s</TITLE>\n", entify_string(title) );
   cgi_printf("<!-- Software: %s -->\n</HEAD>", software_version );
   cgi_printf("<BODY>\n<H2>%s</H2><P>\n", path);
   script_name = cgi_info ( "SCRIPT_NAME" );
   /*
    *  Generate history path.
    */
   lvl = 2;
   for ( i = 1; i < j; i++ ) {
	lvl++;
	if ( lvl > 6 ) lvl = 6;
	cgi_printf ( "<H%d>%s</H%d>\n", lvl, entify_string(elem[i]), lvl );
   }
   cgi_printf("<BR><HR>\n");
   /*
    * determine icon directory from environment variable.
    */
   icon_dir = getenv ( "WEBBOOK_ICON_PATH" );
   if ( !icon_dir ) icon_dir = DEFAULT_ICON_DIR;
   else if ( (strlen(icon_dir)+50) > sizeof(icon_image) ) 
	icon_dir = DEFAULT_ICON_DIR;
   /*
    * Generate directory with links to each.
    */
   if ( ! first_rec.next ) {
	cgi_printf ( "Error opening bookshelf file %s\n", path);
   }
   for ( rec = first_rec.next; rec; rec = rec->next ) {
	char *escaped_target, *escaped_desc, *entified;
	char target[256];

	if ( *rec->type == '#' ) continue;	/* comment */
	apply_default ( rec->fname, shelf_spec, target );
	entified = entify_string(rec->desc);
	escaped_target = escape_string(target);
	if ( 0 == strcmp ( rec->type, "SHELF" ) ) {
	    escaped_desc = escape_string(rec->desc);
	    sprintf ( icon_image, SHELF_ICON, icon_dir );
	    cgi_printf("%s <A HREF=\"../%s/%s/\">%s</A><BR>\n", icon_image, 
		escaped_desc, escaped_target, entified );
	    if ( escaped_desc != rec->desc ) free ( escaped_desc );

	} else if ( 0 == strcmp ( rec->type, "BOOK" ) ) {
	    sprintf ( icon_image, BOOK_ICON, icon_dir );
	    cgi_printf("%s <A HREF=\"%s/%s\">%s</A><BR>\n", icon_image, 
			script_name, escaped_target, entified );

	} else if ( 0 == strcmp ( rec->type, "TITLE" ) ) {
	} else {
	    /*
	     * convert the target back to unix format, and make direct
	     * HREF to it.
	     */
	    int state, k;
	    for ( state = k = 0; target[k]; k++ ) switch ( state ) {
		case 0:
		    if ( target[k] == ':' ) {
			state = 1;
			target[k] = '/';
		    } else if ( target[k] == '[' || target[k] == '<' ) {
			state = 4;
			target[k] = '/';
		    }
		    break;
		case 1:
		    if ( target[k] == '[' ) { state = 2; }
		    if ( target[k] == '<' ) { state = 2; }
		    break;
		case 2:
		    if ( target[k] == '.' ) target[k] = '/';
		    if ( target[k] == ']' || target[k] == '>' ) {
			target[k] = '/';
			state = 3;
		    }
		    target[k-1] = target[k];
		    break;
		case 3:
		    target[k-1] = target[k];
		    break;
		case 4:
		    if ( target[k] == '.' || target[k] == ']' ||
			target[k] == '>' ) target[k] = '/';
		    break;
	    }
	    if ( state == 2 || state == 3 ) target[k-1] = '\0';
	    /* printf("rewritten target: '%s'\n", target ); */
	    /*
	     * Insert proper icon for filetype.
	     */
	    if ( 0 == strcmp ( rec->type, "HTML" ) ) {
	        sprintf ( icon_image, HTML_ICON, icon_dir );
	    } else if ( strlen(rec->type) < (sizeof(icon_image)-3) ) {
		sprintf ( icon_image, "[%s]", rec->type );
	    } else {
		strcpy ( icon_image, "???" );
	    }
	    cgi_printf("%s <A HREF=\"/%s\">%s</A><BR>\n", icon_image, 
			target, entified );
	}
	/*
	 * Only free string if malloc'ed 
	 */
	if ( escaped_target != target ) free ( escaped_target );
	if ( entified != rec->desc )  free ( entified );
   }
   cgi_printf ( "</BODY></HTML>\n" );
   return 1;
}
/****************************************************************************/
/* Convert strings containing punctuation characters into escaped strings.
 */
static char *escape_string ( char *source )
{
   int i, j, punct_count;
   char *dest;
   dest = source;
   punct_count = 0;
   for ( i = 0; source[i]; i++ ) if ( ispunct ( source[i] ) ) {
	if ( (source[i] != '.') && (source[i] != '$') )punct_count++;
   } else if ( isspace ( source[i] ) ) punct_count++;

   if ( punct_count > 0 ) {
	dest = malloc ( i + punct_count*3 + 1 );
	if ( !dest ) return source;

	for ( i = j = 0; source[i]; i++ ) {
	    if ( isspace(source[i]) || (ispunct ( source[i] ) &&
			(source[i] != '.') && (source[i] != '$')) ) {
		sprintf ( &dest[j], "%%%02x", source[i] );
		if ( dest[j+1] == ' ' ) dest[j+1] = '0';
		j += 3;
	    } else
		dest[j++] = source[i];
	}
	dest[j] = '\0';
   }
   return dest;
}
/****************************************************************************/
/* Convert strings containing punctuation characters into escaped strings.
 */
static char *entify_string ( char *source )
{
   int i, j,brack_count;
   char *dest;
   dest = source;
   brack_count = 0;
   for ( i = 0; source[i]; i++ ) {
	if ( (source[i] == '<') || (source[i] == '>') ||
	    (source[i] == '&') ) brack_count++;
   }
   if ( brack_count > 0 ) {
	dest = malloc ( i + brack_count*4 + 1 );
	if ( !dest ) return source;

	for ( i = j = 0; source[i]; i++ ) {
	    if ( source[i] == '<' ) {
		dest[j++] = '&';
		dest[j++] = 'l';
		dest[j++] = 't';
		dest[j++] = ';';
	    } else if ( source[i] == '>' ) {
		dest[j++] = '&';
		dest[j++] = 'g';
		dest[j++] = 't';
		dest[j++] = ';';
	    } else if ( source[i] == '&' ) {
		dest[j++] = '&';
		dest[j++] = 'a';
		dest[j++] = 'm';
		dest[j++] = 'p';
		dest[j++] = ';';
	    } else
		dest[j++] = source[i];
	}
	dest[j] = '\0';
   }
   return dest;
}
/****************************************************************************/
/* Read next record from bookshelf file and copy to structure.
 */
static shelf_recp get_shelf_rec ( FILE *sf, shelf_recp previous )
{
    int i,j, backslash_count;
    shelf_recp cur;
    char *line, tline[4096];
    /*
     * Be optimistic and allocate new structure.
     */
    cur = malloc ( sizeof(struct shelf_rec) );
    if ( !cur ) return cur;
    cur->next = (shelf_recp) 0;
    cur->type = cur->fname = cur->desc = "";
    cur->is_odl = 0;
    backslash_count = 0;

    if ( fgets ( tline, sizeof(tline)-1, sf ) ) {
	/*
	 * Copy and Parse the record on backslashes or tabs.  It will
	 * also parse on the first space.
	 */
	for ( i = 0; i < sizeof(tline) && tline[i] && tline[i] != '\n'; i++ );
	line = malloc ( i + 1 );
	if ( !line ) return (shelf_recp) 0;
	strncpy ( line, tline, i );
	line[i] = '\0';

	cur->type = line;
	for ( j = i = 0; line[i]; i++ ) if ( (line[i]=='\\') ||
		((backslash_count==0) && (line[i] == ' ')) || 
		(line[i] == '\t') ) {
	    if ( line[i] == '\\' ) backslash_count++;
	    line[i] = '\0';
	    if ( backslash_count == 0 ) {
		/*
		 * compress whitespace.
		 */
		while ( line[i+1] && isspace(line[i+1]) ) i++;
	    }
	    if ( j == 0 ) {
		cur->fname = &line[i+1];
		j++;
	    } else {
		cur->desc = &line[i+1];		/* rest of line */
		break;
	    }
	}
	if ( backslash_count == 0 ) cur->is_odl = 1;
	/*
	 * Unescape characters and upcase type.
	 */
	for ( i = 0; line[i]; i++ ) 
		if ( islower(line[i]) ) line[i] = _toupper(line[i]);
	
    } else {
	/*
	 * Read error, return null.
	 */
	free ( cur );
	cur = (shelf_recp) 0;
    }
#ifdef DEBUG
if ( cur ) printf("shelf rec parse: '%s' '%s' '%s'\n", cur->type, cur->fname,
 cur->desc );
#endif
    /*
     * append new record to list.
     */
    previous->next = cur;
    return cur;
}
/**************************************************************************/
/* Prepare to send back response.  Build standard response header.
 */
static int send_http_header ( char *stsline, char *content )
{
    int status;
    /*
     */
    cgi_printf ( "content-type: %s\n", content );
    if ( strncmp(stsline,"200",3) != 0 ) 
	cgi_printf ( "status: %s\n\n", stsline );
    else cgi_printf("\n");
    return 1;
}
/**************************************************************************/
/* Take default directory from defname and apply to fname.  If fname
 * begins with a percent sign, convert ODL shelf syntax to VMS filespec.
 */
static char *apply_default ( char *fname, char *defname, char *out )
{
    int i, j, d_limit, slash_fixup;

    if ( *fname == '%' ) {
	/*
	 * Change "%devname/dir/file" --> "devname:[dir]file"
	 */
	j = 0;			/* output position */
	d_limit = -1;		/* position of last slash */
	for ( i = 1; fname[i]; i++ ) {
	    if ( fname[i] == '/' ) {
		if ( d_limit < 0 ) {	/* first slash */
		    out[j++] = ':';
		    out[j++] = '[';
		 } else {
		    out[j++] = '.';
		}
		d_limit = j-1;
	    } else {
		out[j++] = fname[i];
	    }
	}
	out[j] = '\0';
	if ( d_limit >= 0 ) {
	    if ( out[d_limit] == '.' ) out[d_limit] = ']';
	    else for ( i = d_limit; out[i]; i++ ) out[i] = out[i+1];
	}
	return out;
    }
    /*
     * Find end of directory string, default to "decw$book:".
     */
    for ( d_limit = i = 0; defname[i]; i++ ) {
	if ( defname[i] == ':' || defname[i] == ']' || defname[i] == '>' )
	    d_limit = i+1;	/* end of defname directory */
    }
    if ( d_limit == 0 ) {
	defname = "decw$book:";
	d_limit = 10;
    }
    /*
     * If fname looks like it supplies it's own directory, make defname NULL.
     */
    for ( slash_fixup = i = 0; fname[i]; i++ ) {
	if ( fname[i] == ':' || fname[i] == ']' || fname[i] == '>' ) {
	    d_limit = 0;
	} else if ( fname[i] == '/' ) {
	    slash_fixup = 1;
	}
    }
    /*
     * concatenate defname//fname.
     */
    if ( d_limit ) {
	strncpy ( out, defname, d_limit );
	if ( slash_fixup ) {
	    if ( defname[d_limit-1] == ':' ) {
		strcpy ( &out[d_limit], "[000000." );
		d_limit += 8;
	    } else {
		out[d_limit-1] = '.';
	    }
	}
    }
    if ( i+d_limit > 255 ) i = 255-d_limit;
    strncpy ( &out[d_limit], fname, i );
    out[i+d_limit] = '\0';
    if ( slash_fixup ) {
	for ( i = d_limit+i-1; i > d_limit; --i ) if ( out[i] == '/' ) {
	    out[i] = (slash_fixup) ? ']' : '.';
	    slash_fixup = '\0';
	}
    }
    return out;
}
