/* Maintain server configuration files though HTML forms.
* The script coordinates the configuration (.conf) file with an edit form via
* use of a form template (.template) file along with special directives
* in the configuration file (.DEFINE, .EXPAND, etc).
*
* Author: David Jones
* Date: 2-JUL-1996
* Revised: 7-AUG-1996 fix bugs in collect_symbols() and .iterate
* expansion for .authorize list.
* Revised: 14-AUG-1996 More bugs in .iterate expansion
* Revised: 16-AUG-1996 Inhibit ":80" port, some browsers treat
* host and host:80 and independant realms.
* Revised: 17-AUG-1996 Allow .authorize rule to specify remote
* user of '*'.
* Revised: 13-JAN-1997 Attempt to fix problem with extra blank lines
* inserted when no .next's accompany .iterate.
* Support continuation lines in rule file
* (last character on line is backslash).
* Revised: 19-JUL-1997 Allow double-quotes in form values, replace
* with "''" in displayed forms to get around
* bugs in browser support for "e;
* Revised: 22-FEB-1998 Add reflex hack support.
*/
#include
#include
#include
#include
#include
#include
#include "cgilib.h"
struct field_def { struct field_def *next; char *key, *value; int klen; };
typedef struct field_def *field_def_p;
static char *entify_string ( char *source );
static int output_started;
static void error_abort ( char *stsline, char *reason, void *arg )
{
char fmt[256];
if ( !output_started ) output_started = cgi_begin_output(1);
printf("error abort: '%s' '%s'\n", stsline, reason );
sprintf ( fmt, "content-type: text/plain\nstatus: %s\n\nFailure: %s",
stsline, reason );
cgi_printf ( fmt, arg, arg );
exit ( 1 );
}
static char *url_host ( )
{
/*
* Return port number string and delimiter for building redirects.
*/
static char buffer[300];
char *port, *host;
int length, total;
/*
* If port number is 80, just return host name.
*/
port = cgi_info ( "SERVER_PORT" );
host = cgi_info ( "SERVER_NAME" );
if ( !port ) port = "80";
if ( strcmp ( port, "80" ) == 0 ) return host;
/*
* construct host:port in static storage.
*/
length = strlen ( host );
total = length + 2 + strlen(port);
if ( total > sizeof(buffer) ) return host;
strcpy ( buffer, host );
buffer[length++] = ':';
strcpy ( &buffer[length], port );
return buffer;
}
struct file_rec {
struct file_rec *next;
struct file_rec *related;
int length, type;
char s[1]; /* string */
};
typedef struct file_rec *file_recp;
struct symdef {
file_recp source;
char *symbol;
char *value;
};
struct rec_group {
char *tag;
file_recp first;
file_recp last;
int count;
};
static struct file_rec rule_file, form_file;
static time_t rule_file_mdate;
static struct rec_group file_dir[] = {
{ "ALL", &rule_file, &rule_file, 0 }, /* 0 */
{ ".DEFINE", (file_recp) 0, (file_recp) 0, 0 }, /* 1 */
{ ".EXPAND", (file_recp) 0, (file_recp) 0, 0 }, /* 2 */
{ ".IGNORE", (file_recp) 0, (file_recp) 0, 0 }, /* 3 */
{ ".ITERATE", (file_recp) 0, (file_recp) 0, 0 }, /* 4 */
{ ".NEXT", (file_recp) 0, (file_recp) 0, 0 }, /* 5 */
{ ".AUTHORIZE", (file_recp) 0, (file_recp) 0, 0 }, /* 6 */
{ ".FORM", (file_recp) 0, (file_recp) 0, 0 }, /* 7 */
{ (char *) 0, (file_recp) 0, (file_recp) 0, 0 } };
static void authenticate_user ( struct file_rec *auth_recs, char *fname );
static char *load_configuration ( char *method, char *path_info, char **mancmd );
static char *load_template ( char *fname );
static void generate_form(char *, char*);
static void update_configuration(char *);
extern void server_query ( char * );
static int expand_form_tag ( char *tag, char *, int tag_length, struct symdef * );
/**************************************************************************/
int main ( int argc, char **argv )
{
int i, status, manage_flag, SYS$SETEF();
char *path_info, *fname, *method, *query, *manage_cmd;
/*
* Use low-level cgi_init to allow additional scriptserver transactions.
* (will call cgi_begin_output(1) when ready to generate CGI output.
*/
status = cgi_init_env ( argc, argv );
if ( (status&1) == 0 ) return status;
output_started = 0;
/*
* Break out configfile name and mtime from path_info.
*/
method = cgi_info ( "REQUEST_METHOD" );
path_info = cgi_info ( "PATH_INFO" );
if ( !path_info ) error_abort ( "500 no path", "No path", 0 );
fname = load_configuration ( method, path_info, &manage_cmd );
/*
* authorize access.
*/
if ( file_dir[4].count > 0 ) {
/*
* Test .iterate records for .authorize expansion
*/
char iter_line[32];
file_recp rec, tmp;
for ( rec = file_dir[4].first; rec; rec = rec->related ) {
if ( rec->length < 20 ) continue;
for ( i = 0; i < 20; i++ ) iter_line[i] = toupper(rec->s[i]);
if ( strncmp ( iter_line, ".ITERATE .AUTHORIZE ", 20 ) == 0 ) {
/* Append .next records for iterate to .authorize list */
for ( rec = rec->next; rec; rec = rec->next ) {
if ( rec->type == 4 ) break; /* next .iterate */
if ( rec->type == 5 ) {
/*
* Record is of type .next, reformat as new .authorize
* record.
*/
tmp = malloc ( sizeof(struct file_rec)+rec->length+6 );
tmp->next = tmp->related = (file_recp) 0;
tmp->type = 4;
tmp->length = rec->length + 5;
strcpy ( tmp->s, ".AUTHORIZE" );
strncpy ( &tmp->s[10], &rec->s[5], rec->length-5 );
tmp->s[tmp->length] = '\0';
if ( file_dir[6].count == 0 ) {
file_dir[6].first = file_dir[6].last = tmp;
} else {
file_dir[6].last->related = tmp;
file_dir[6].last = tmp;
}
file_dir[6].count++;
}
}
break;
}
}
}
if ( file_dir[6].count == 0 ) error_abort ( "403 noauth",
"Rule file %s contains no .AUTHORIZE directives so cannot be accessed",
fname );
authenticate_user ( file_dir[6].first, fname );
/*
* read template file.
*/
if ( file_dir[7].count == 0 ) error_abort ( "500 noform",
"No .FORM directive in %s", fname );
else if ( file_dir[7].count > 1 ) error_abort ( "500 duplicate form",
"More that one .FORM directive in %s", fname );
load_template ( &file_dir[7].first->s[6] );
/*
* Action depends upon method.
*/
if ( strncmp ( method, "POST", 5 ) == 0 ) {
output_started = cgi_begin_output(1);
update_configuration(fname);
} else if ( strncmp ( method, "GET", 4 ) == 0 ) {
/*
* Generate form or perform action.
*/
if ( manage_cmd ) {
server_query ( manage_cmd );
} else {
int cgi_begin_preprocessed(), cgi_end_preprocessed();
char full_path[256];
sprintf ( full_path, "/www_system/%s.conf", fname );
output_started = /* cgi_begin_output(1); */
cgi_begin_preprocessed ( argv[1], full_path );
generate_form(path_info, fname);
cgi_end_preprocessed();
}
} else if ( strncmp ( method, "HEAD", 5 ) == 0 ) {
output_started = cgi_begin_output(1);
cgi_printf ( "Content-type: text/plain\n\nHTML form goes here\n" );
} else {
error_abort ( "401 unsupported", "Method %s unsupported", method );
}
return 1;
}
/************************************************************************/
/* Read specified file into global structures.
*/
static char *load_configuration ( char *method, char *path_info, char **mancmd )
{
int i;
unsigned long mtime;
char *cp, *fname, *mod_date, line[4096];
FILE *rfile;
stat_t rstat;
struct file_rec *rec;
fname = (char *) malloc ( strlen(path_info)+1 );
strcpy ( fname, (path_info[0] == '/') ? &path_info[1] : path_info );
mod_date = strchr ( fname, '/' );
if ( mod_date ) *mod_date++ = '\0'; else mod_date = "0";
*mancmd = (char *) 0;
/*
* open file and validate mtime in path, redirecting if needed.
*/
if ( strlen(fname) > 250 ) error_abort("500 invfile", "fname invalid", 0 );
sprintf ( line, "/www_system/%s.conf", fname );
rfile = fopen ( line, "r" );
if ( !rfile ) error_abort ( "500 open", "open failure on %s\n", line );
if ( fstat ( fileno(rfile), &rstat ) < 0 ) error_abort (
"500 stat", "stat() failure on %s\n", fname );
if ( 0 == strncmp ( mod_date, "manage/", 7 ) ) {
*mancmd = &mod_date[7];
mtime = rstat.st_mtime;
} else {
mtime = atoi ( mod_date );
}
if ( mtime != rstat.st_mtime ) {
if ( strcmp(method,"POST") == 0 ) error_abort ( "500 invfile",
"Mismatch on file times, can't update %s", fname );
/* Redirect request */
output_started = cgi_begin_output(1);
#ifdef NOREFLEX_HACK
cgi_printf ( "location: http://%s%s/%s/%d\n\n", url_host(),
cgi_info("SCRIPT_NAME"), fname, rstat.st_mtime );
#else
/* Server will fill in correct information for '*://*:*' */
cgi_printf ( "location: *://*:*%s/%s/%d\n\n",
cgi_info("SCRIPT_NAME"), fname, rstat.st_mtime );
#endif
exit(1);
}
rule_file_mdate = rstat.st_mtime;
/*
* Load file into memory.
*/
rule_file.length = rule_file.type = 0;
rule_file.next = rule_file.related = (struct file_rec *) 0;
while ( fgets ( line, sizeof(line), rfile ) ) {
char *nl; int length;
nl = strchr ( line, '\n' ); if ( nl ) *nl = '\0';
length = strlen(line);
if ( length > 0 ) while ( line[length-1] == '\\' ) {
/*
* Continuation line, concatentate additional lines but
* preserve linefeeds.
*/
line[length++] = '\n';
line[length] = '\0';
if (!fgets(&line[length], sizeof(line)-length, rfile)) break;
nl = strchr ( &line[length], '\n' ); if (nl) *nl = '\0';
length += strlen(&line[length]);
}
rec = (struct file_rec *) malloc
( sizeof(struct file_rec) + length );
if ( !rec ) error_abort ( "500 mem", "malloc failed", 0 );
file_dir[0].last->next = rec;
file_dir[0].last = rec;
rec->next = (struct file_rec *) 0;
rec->related = (struct file_rec *) 0;
rec->length = length;
strcpy ( rec->s, line );
/*
* If line begins with period, group into related list.
*/
rec->type = 0;
if ( rec->length < 1 ) continue;
if ( rec->s[0] != '.' ) continue;
for (cp = &rec->s[1]; *cp && !isspace(*cp); cp++) *cp = toupper(*cp);
*cp = '\0';
for ( i = 1; file_dir[i].tag; i++ ) {
if ( 0 == strcmp ( rec->s, file_dir[i].tag ) ) {
if ( file_dir[i].last ) file_dir[i].last->related = rec;
else file_dir[i].first = rec;
file_dir[i].last = rec;
break;
}
}
file_dir[i].count++;
rec->type = i;
*cp = ' ';
}
fclose ( rfile );
return fname;
}
/*************************************************************************/
/* Read template file used to generate HTML form. Template is parsed into
* a linked list of tagged segments for subsequent processing. The head
* of this list is the global variable form_file.
*
* Segment types:
* 0 Text.
* 1 Text with newline at end.
* 2 Input marker, delimited in template by square bracket.
* 3 Control marker, special input marker of form [$command].
*/
static char *load_template ( char *fname ) {
int j, i;
char line[4096];
FILE *tfile;
struct file_rec *rec;
/*
* open file
*/
while ( *fname && isspace(*fname) ) fname++;
if ( strlen(fname) > 250 ) error_abort("500 invfile", "fname invalid", 0 );
tfile = fopen ( fname, "r" );
if ( !tfile ) error_abort ( "500 open", "Open failure on '%s'", fname );
/*
* Load file into memory.
*/
form_file.length = form_file.type = 0;
form_file.next = form_file.related = (struct file_rec *) 0;
for ( rec=(&form_file); fgets ( line, sizeof(line), tfile ); ) {
int length, level, seg_type;
/*
* Parse file into text segments(types 0,1) separated by marker/control
* segments, type 2/3.
*/
level = 0;
for ( j = i = 0; i < sizeof(line); i++ ) {
if ( line[i] == '[' ) {
level++;
if ( level > 1 ) continue;
seg_type = 0;
} else if ( line[i] == ']' ) {
--level;
if ( level > 0 ) continue;
seg_type = 2;
if ( line[j] == '$' ) seg_type = 3;
} else if ( (line[i] == '\n') || (line[i] == '\0') ) {
seg_type = 1;
} else continue;
/*
* Copy portion of read record to new segment if j has been
* updated.
*/
if ( j < i ) {
length = i - j;
rec->next = (struct file_rec *) malloc (
sizeof(struct file_rec) + length );
if ( !rec ) error_abort ( "500 mem", "malloc failed", 0 );
rec = rec->next;
rec->next = rec->related = (struct file_rec *) 0;
strncpy ( rec->s, &line[j], length );
rec->s[length] = '\0';
rec->length = length;
rec->type = seg_type;
}
j = i + 1;
if ( seg_type == 1 ) break;
}
}
fclose ( tfile );
return fname;
}
/****************************************************************************/
/* Convert pluses and escaped characters in string, also convert
* '' to " (special hack since browser support for "e; sucks) */
static int htmlStrcpy (
char *out,
char *in)
{
int i, j;
/* printf("htmlstr raw string: %s\n", in ); */
for ( j=0; *in; in++)
{
if (*in == '%') {
int xxx ;
if ( (in[1]=='\0') || (in[2]=='\0') ) break; /* too short */
sscanf (&in[1], "%02x", &xxx) ;
if ( xxx == '\'' ) {
if ( strncmp ( &in[3], "%27", 3 ) == 0 ) {
xxx = '"'; in += 3; /* convert '' to " */
}
}
out[j++] = (char) xxx ;
in += 2 ;
} else if ( *in == '+' ) {
out[j++] = ' ';
} else if ( (*in == '\'') && (in[1] == '\'') ) {
in++; /* skip */
out[j++] = '"';
} else {
out[j++] = *in;
}
}
out[j] = '\0';
return j;
}
/****************************************************************************/
/* Read form data and parse into list of key/value pairs. Plus-to-space
* conversion is performed and escaped chars are converted.
* Full CGI responses are generated on error.
*/
static field_def_p form_data()
{
char *method, *content_data, *cp;
field_def_p field_list, fld;
int i, content_length, status;
/*
* Determine what form data is being delivered by client.
*/
method = cgi_info ( "REQUEST_METHOD" );
if ( 0 == strcmp ( method, "POST" ) ) {
/*
* Data was posted.
*/
content_length = atoi(cgi_info ("CONTENT_LENGTH"));
if (content_length == 0) error_abort ( "500 missing content",
"No data in CONTENT_LENGTH (%s)", cgi_info("CONTENT_LENGTH") );
content_data = (char *) malloc(sizeof(char)*(content_length+1));
status = cgi_read(content_data, content_length);
if (status == 0) error_abort ( "500 missing content",
"No data from cgi_read", 0 );
content_data[content_length] = '\0';
} else {
error_abort("501 unsupported method","Method %s is unsupported",
method );
}
/*
* Parse the content into key/value pairs.
*/
field_list = (field_def_p) 0;
if ( content_data[0] != '&' ) {
fld = (field_def_p) malloc ( sizeof(struct field_def) );
fld->next = field_list;
fld->key = content_data;
fld->klen = 0;
field_list = fld;
}
for ( i = 0; i < content_length; i++ ) if ( content_data[i] == '&' ) {
fld = (field_def_p) malloc ( sizeof(struct field_def) );
fld->next = field_list;
fld->key = &content_data[i+1];
fld->klen = 0;
content_data[i] = '\0';
field_list = fld;
}
for ( fld = field_list; fld; fld = fld->next ) {
fld->value = "";
for ( cp = fld->key, i = 0; cp[i]; i++ ) {
if ( cp[i] == '=' ) {
cp[i] = '\0';
fld->klen = i;
fld->value = &cp[i+1];
}
}
htmlStrcpy ( fld->value, fld->value );
/* printf("Parsed key '%s' = '%s'\n", fld->key, fld->value ); */
}
return field_list;
}
/*****************************************************************************/
/* Return table of symbol definitions of a given line type (specified by
* index). Each line is parsed into a symbol name and value.
*
* If copy is true, new memory is allocated to hold the string data, otherwise
* the exsiing strings are modified.
*/
struct symdef *collect_symbols ( int ndx, int copy )
{
struct symdef *table;
file_recp rec;
int i, start_offset;
char *line, *tok;
/*
* Allocate table, the load procedure tracked how many lines of each
* type there were.
*/
start_offset = strlen ( file_dir[ndx].tag );
table = (struct symdef *) malloc (
(file_dir[ndx].count+1)*sizeof(struct symdef));
if ( !table ) error_abort ( "500 malloc", "malloc failed", 0 );
/*
* Scan all records of type ndx.
*/
for ( i = 0, rec = file_dir[ndx].first; rec; rec = rec->related ) {
table[i].source = rec;
if ( copy ) {
/*
* Make copy of line so the parsing doesnt destroy the original.
*/
line = malloc ( rec->length+1 );
if ( !line ) printf("error in malloc, length: %d\n", rec->length);
strncpy ( line, rec->s, rec->length );
line[rec->length] = '\0';
} else line = rec->s;
/*
* Parse of first token after .type, which is symbol name.
*/
tok = &line[start_offset];
while ( *tok && isspace(*tok) ) tok++;
table[i].symbol = tok;
if ( *tok ) {
/*
* Find symbol name's end and terminate it.
*/
while ( *tok && !isspace(*tok) ) tok++;
if ( *tok ) {
*tok++ = '\0';
/* Skip to start of value */
while ( *tok && isspace(*tok) ) tok++;
}
}
table[i].value = tok;
i++;
}
table[i].symbol = table[i].value = (char *) 0;
return table;
}
/***************************************************************************/
/*
* Decode block of 4 characters to 1,2 or 3 characters. Return 0 if
* block is invalid.
*/
static int decode_char ( char **inp, int *context, unsigned char *out )
{
char *enc_table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
int context_bits, context_count, i, j;
char *p, c;
context_bits = *context;
context_count = (context_bits>>12);
context_bits = context_bits&4095;
p = *inp;
while ( context_count < 8 ) {
c = *p++;
if ( c == '=' ) return 0;
if ( c == '\0' ) return 0;
for ( j = 0; (enc_table[j] != c); j++ ) if ( !enc_table[j] ) return 0;
context_bits = (context_bits&63)<<6 | j;
context_count += 6;
}
if ( context_count == 8 ) *out = (context_bits&255);
else *out = (context_bits>>(context_count-8))&255;
*context = (context_bits&4095) | ((context_count-8)<<12);
*inp = p;
return 1;
}
/**************************************************************************/
/* Validate the user has access rights to file. Program aborts on failure.
*
* .authorize formats:
* user[@host-addr] User must match REMOTE_USER variable
* (Script must be protected)
* *[@host-addr] User must have been authenticated via protect
* rule on script.
* user[@host-addr] pwd User and password must match that decoded
* from .authorize statement AND server port must
* be non-privileged (>1024).
*/
static void authenticate_user ( struct file_rec *auth_recs, char *fname )
{
struct symdef *user_list;
char *auth_header, *decode_buf, *user, *password, *remote_user;
char *host_addr, *remote_addr, *remote_host;
char *authenticate = "WWW-authenticate: Basic realm=\"ServerMaint\"";
int i, j, ctx;
unsigned char c;
/*
* Get authorization header and decode user/password;
*/
remote_user = cgi_info ( "REMOTE_USER" );
remote_addr = cgi_info ( "REMOTE_ADDR" );
remote_host = cgi_info ( "REMOTE_HOST" );
auth_header = cgi_info ( "HTTP_AUTHORIZATION" );
if ( !auth_header ) error_abort (
"401 authorization failed, none provided\n%s", "no auth header",
authenticate );
decode_buf = malloc ( strlen ( auth_header ) );
for ( i = 0; auth_header[i] && !isspace(auth_header[i]); i++ ) {
decode_buf[i] = tolower(auth_header[i]);
if ( i > 5 ) break;
}
decode_buf[i] = '\0';
if ( strcmp ( decode_buf, "basic" ) ) error_abort (
"401 authorization failed, wrong type\n%s", "Wrong auth. type",
authenticate );
while ( auth_header[i] && isspace(auth_header[i]) ) i++;
user = &auth_header[i];
password = (char *) 0;
for ( i = ctx = 0; decode_char(&user, &ctx, &c); i++ ) {
decode_buf[i] = toupper(c);
if ( !password ) if ( c == ':' ) {
decode_buf[i] = '\0';
password = &decode_buf[i+1];
}
}
decode_buf[i] = '\0';
if ( !password ) error_abort (
"401 authorization failed, syntax\n%s", "Syntax error",
authenticate );
user = decode_buf;
/*
* Scan file for .authorize directive that matches.
*/
user_list = collect_symbols ( 6, 1 ); /* parse .authorize list */
/* printf("%s records: %d, cand: '%s' pwd: '%s'\n", file_dir[6].tag,
file_dir[6].count, user, password ); */
for ( i = 0; user_list[i].symbol; i++ ) {
/*
* Upcase the username and trim off hostname
*/
host_addr = (char *) 0;
for ( j=0; user_list[i].symbol[j]; j++ ) {
user_list[i].symbol[j] = toupper(user_list[i].symbol[j]);
if ( user_list[i].symbol[j] == '@' ) {
host_addr = &user_list[i].symbol[j+1];
user_list[i].symbol[j] = '\0';
break;
}
}
if ( host_addr ) {
/* Skip this record if host doesn't match */
if ( strcmp ( host_addr, remote_host ) &&
strcmp ( host_addr, remote_addr ) ) continue;
}
if ( user_list[i].value[0] ) {
/* Verify password and succeed if matches */
if ( atoi ( cgi_info("SERVER_PORT") ) < 1024 ) continue;
for ( j=0; user_list[i].value[j]; j++ )
user_list[i].value[j] = toupper(user_list[i].value[j]);
if ( strcmp ( user_list[i].value, password ) == 0 ) return;
} else if ( remote_user ) {
/* No password, user must match remote user or '*' */
if ( *remote_user &&
( 0 == strcmp ("*", user_list[i].symbol ) ) ) return;
if ( 0 == strcmp ( remote_user, user_list[i].symbol ) ) {
/* User verified */
return;
}
}
}
error_abort ( "401 authorization failed\n%s", "authorization failed",
authenticate );
}
/**************************************************************************/
static int update_definition ( char *tag, int tag_length,
field_def_p field_list, struct symdef *symbols, char *suffix )
{
int i, j, k, state, nlen;
char *tmp, *t_value, *value, name[128];
field_def_p fld;
/*
* tag fomats:
* [name;size;max]
* [name|opt1|opt2|...] \n", tmp );
}
break;
default:
cgi_printf ( "-Syntax error in template-" );
}
return 1;
}
/**************************************************************************/
/* Output HTML form from template, loading input fields with current
* values for configuration file.
*/
static void generate_form ( char *path_info, char *fname )
{
int i, j, k, status;
file_recp rec;
char suffix[32];
struct symdef *symbols;
symbols = collect_symbols ( 1, 0 );
suffix[0] = '\0'; /* Appended to all input field names */
cgi_printf("\n");
cgi_printf("Server Configuration File %s\n\n",
fname );
cgi_printf("ServerMaint 1.2, %s last modified %s\n",
fname, ctime ( &rule_file_mdate ) );
return;
}
/*
* Convert special characters to 'entity' forms: <>&". If no expansions,
* function value returned is pointer to source string, otherwise return value is
* pointer to malloc'ed string which caller must deallocate.
* pointer to ori
*/
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] == '&') || (source[i] == '"') ) brack_count++;
}
if ( brack_count > 0 ) {
dest = malloc ( i + brack_count*6 + 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 if ( source[i] == '"' ) {
/*
* NOTE: We should really be producing ""e;" here, but
* browser's are broken so use our own convention of "''".
*/
dest[j++] = '\'';
dest[j++] = '\'';
} else
dest[j++] = source[i];
}
dest[j] = '\0';
}
return dest;
}