gecko-dev/security/nss/cmd/signtool/javascript.c
2000-04-03 19:08:51 +00:00

1788 lines
43 KiB
C

/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is the Netscape security libraries.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1994-2000 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU General Public License Version 2 or later (the
* "GPL"), in which case the provisions of the GPL are applicable
* instead of those above. If you wish to allow use of your
* version of this file only under the terms of the GPL and not to
* allow others to use your version of this file under the MPL,
* indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by
* the GPL. If you do not delete the provisions above, a recipient
* may use your version of this file under either the MPL or the
* GPL.
*/
#include "signtool.h"
#include <prmem.h>
#include <prio.h>
#include <prenv.h>
static int javascript_fn(char *relpath, char *basedir, char *reldir,
char *filename, void *arg);
static int extract_js (char *filename);
static int copyinto (char *from, char *to);
static PRStatus ensureExists (char *base, char *path);
static int make_dirs(char *path, PRInt32 file_perms);
static char *jartree = NULL;
static int idOrdinal;
static PRBool dumpParse=PR_FALSE;
static char *event_handlers[] = {
"onAbort",
"onBlur",
"onChange",
"onClick",
"onDblClick",
"onDragDrop",
"onError",
"onFocus",
"onKeyDown",
"onKeyPress",
"onKeyUp",
"onLoad",
"onMouseDown",
"onMouseMove",
"onMouseOut",
"onMouseOver",
"onMouseUp",
"onMove",
"onReset",
"onResize",
"onSelect",
"onSubmit",
"onUnload"
};
static int num_handlers = 23;
/*
* I n l i n e J a v a S c r i p t
*
* Javascript signing. Instead of passing an archive to signtool,
* a directory containing html files is given. Archives are created
* from the archive= and src= tag attributes inside the html,
* as appropriate. Then the archives are signed.
*
*/
int
InlineJavaScript(char *dir, PRBool recurse)
{
jartree = dir;
if(verbosity >= 0) {
PR_fprintf(outputFD, "\nGenerating inline signatures from HTML files in: %s\n", dir);
}
if(PR_GetEnv("SIGNTOOL_DUMP_PARSE")) {
dumpParse = PR_TRUE;
}
return foreach(dir, "", javascript_fn, recurse, PR_FALSE /*include dirs*/,
(void*)NULL);
}
/************************************************************************
*
* j a v a s c r i p t _ f n
*/
static int javascript_fn
(char *relpath, char *basedir, char *reldir, char *filename, void *arg)
{
char fullname [FNSIZE];
/* only process inline scripts from .htm, .html, and .shtml*/
if(! (PL_strcaserstr(filename, ".htm") == filename + strlen(filename) -4) &&
! (PL_strcaserstr(filename, ".html") == filename + strlen(filename) -5)&&
! (PL_strcaserstr(filename, ".shtml") == filename + strlen(filename)-6)){
return 0;
}
/* don't process scripts that signtool has already
extracted (those that are inside .arc directories) */
if(PL_strcaserstr(filename, ".arc") == filename + strlen(filename) - 4)
return 0;
if(verbosity >= 0) {
PR_fprintf(outputFD, "Processing HTML file: %s\n", relpath);
}
/* reset firstArchive at top of each HTML file */
/* skip directories that contain extracted scripts */
if(PL_strcaserstr(reldir, ".arc") == reldir + strlen(reldir) - 4)
return 0;
sprintf (fullname, "%s/%s", basedir, relpath);
return extract_js (fullname);
}
/*===========================================================================
=
= D A T A S T R U C T U R E S
=
*/
typedef enum {
TEXT_HTML_STATE=0,
SCRIPT_HTML_STATE
} HTML_STATE ;
typedef enum {
/* we start in the start state */
START_STATE,
/* We are looking for or reading in an attribute */
GET_ATT_STATE,
/* We're burning ws before finding an attribute */
PRE_ATT_WS_STATE,
/* We're burning ws after an attribute. Looking for an '='. */
POST_ATT_WS_STATE,
/* We're burning ws after an '=', waiting for a value */
PRE_VAL_WS_STATE,
/* We're reading in a value */
GET_VALUE_STATE,
/* We're reading in a value that's inside quotes */
GET_QUOTED_VAL_STATE,
/* We've encountered the closing '>' */
DONE_STATE,
/* Error state */
ERR_STATE
} TAG_STATE ;
typedef struct AVPair_Str {
char *attribute;
char *value;
unsigned int valueLine; /* the line that the value ends on */
struct AVPair_Str *next;
} AVPair;
typedef enum {
APPLET_TAG,
SCRIPT_TAG,
LINK_TAG,
STYLE_TAG,
COMMENT_TAG,
OTHER_TAG
} TAG_TYPE ;
typedef struct {
TAG_TYPE type;
AVPair *attList;
AVPair *attListTail;
char *text;
} TagItem;
typedef enum {
TAG_ITEM,
TEXT_ITEM
} ITEM_TYPE ;
typedef struct HTMLItem_Str{
unsigned int startLine;
unsigned int endLine;
ITEM_TYPE type;
union {
TagItem *tag;
char *text;
} item;
struct HTMLItem_Str *next;
} HTMLItem;
typedef struct {
PRFileDesc *fd;
PRInt32 curIndex;
PRBool IsEOF;
#define FILE_BUFFER_BUFSIZE 512
char buf[FILE_BUFFER_BUFSIZE];
PRInt32 startOffset;
PRInt32 maxIndex;
unsigned int lineNum;
} FileBuffer;
/*===========================================================================
=
= F U N C T I O N S
=
*/
static HTMLItem* CreateTextItem(char *text, unsigned int startline,
unsigned int endline);
static HTMLItem* CreateTagItem(TagItem* ti, unsigned int startline,
unsigned int endline);
static TagItem* ProcessTag(FileBuffer* fb, char **errStr);
static void DestroyHTMLItem(HTMLItem *item);
static void DestroyTagItem(TagItem* ti);
static TAG_TYPE GetTagType(char *att);
static FileBuffer* FB_Create(PRFileDesc* fd);
static int FB_GetChar(FileBuffer *fb);
static PRInt32 FB_GetPointer(FileBuffer *fb);
static PRInt32 FB_GetRange(FileBuffer *fb, PRInt32 start, PRInt32 end,
char **buf);
static unsigned int FB_GetLineNum(FileBuffer *fb);
static void FB_Destroy(FileBuffer *fb);
static void PrintTagItem(PRFileDesc *fd, TagItem *ti);
static void PrintHTMLStream(PRFileDesc *fd, HTMLItem *head);
/************************************************************************
*
* C r e a t e T e x t I t e m
*/
static HTMLItem*
CreateTextItem(char *text, unsigned int startline, unsigned int endline)
{
HTMLItem *item;
item = PR_Malloc(sizeof(HTMLItem));
if(!item) {
return NULL;
}
item->type = TEXT_ITEM;
item->item.text = text;
item->next = NULL;
item->startLine = startline;
item->endLine = endline;
return item;
}
/************************************************************************
*
* C r e a t e T a g I t e m
*/
static HTMLItem*
CreateTagItem(TagItem* ti, unsigned int startline, unsigned int endline)
{
HTMLItem *item;
item = PR_Malloc(sizeof(HTMLItem));
if(!item) {
return NULL;
}
item->type = TAG_ITEM;
item->item.tag = ti;
item->next = NULL;
item->startLine = startline;
item->endLine = endline;
return item;
}
static PRBool
isAttChar(char c)
{
return (isalnum(c) || c=='/' || c=='-');
}
/************************************************************************
*
* P r o c e s s T a g
*/
static TagItem*
ProcessTag(FileBuffer* fb, char **errStr)
{
TAG_STATE state;
PRInt32 startText, startID, curPos;
PRBool firstAtt;
int curchar;
TagItem *ti=NULL;
AVPair *curPair=NULL;
char quotechar;
unsigned int linenum;
unsigned int startline;
state = START_STATE;
startID = FB_GetPointer(fb);
startText = startID;
firstAtt = PR_TRUE;
ti = (TagItem*) PR_Malloc(sizeof(TagItem));
if(!ti) out_of_memory();
ti->type = OTHER_TAG;
ti->attList = NULL;
ti->attListTail = NULL;
ti->text = NULL;
startline = FB_GetLineNum(fb);
while(state != DONE_STATE && state != ERR_STATE) {
linenum = FB_GetLineNum(fb);
curchar = FB_GetChar(fb);
if(curchar == EOF) {
*errStr = PR_smprintf(
"line %d: Unexpected end-of-file while parsing tag starting at line %d.\n", linenum, startline);
state = ERR_STATE;
continue;
}
switch(state) {
case START_STATE:
if(curchar=='!') {
/*
* SGML tag or comment
* Here's the general rule for SGML tags. Everything from
* <! to > is the tag. Inside the tag, comments are
* delimited with --. So we are looking for the first '>'
* that is not commented out, that is, not inside a pair
* of --: <!DOCTYPE --this is a comment >(psyche!) -->
*/
PRBool inComment = PR_FALSE;
short hyphenCount = 0; /* number of consecutive hyphens */
while(1) {
linenum = FB_GetLineNum(fb);
curchar = FB_GetChar(fb);
if(curchar == EOF) {
/* Uh oh, EOF inside comment */
*errStr = PR_smprintf(
"line %d: Unexpected end-of-file inside comment starting at line %d.\n",
linenum, startline);
state = ERR_STATE;
break;
}
if(curchar=='-') {
if(hyphenCount==1) {
/* This is a comment delimiter */
inComment = !inComment;
hyphenCount=0;
} else {
/* beginning of a comment delimiter? */
hyphenCount=1;
}
} else if(curchar=='>') {
if(!inComment) {
/* This is the end of the tag */
state = DONE_STATE;
break;
} else {
/* The > is inside a comment, so it's not
* really the end of the tag */
hyphenCount=0;
}
} else {
hyphenCount = 0;
}
}
ti->type = COMMENT_TAG;
break;
}
/* fall through */
case GET_ATT_STATE:
if(isspace(curchar) || curchar=='=' || curchar=='>') {
/* end of the current attribute */
curPos = FB_GetPointer(fb)-2;
if(curPos >= startID) {
/* We have an attribute */
curPair = (AVPair*)PR_Malloc(sizeof(AVPair));
if(!curPair) out_of_memory();
curPair->value = NULL;
curPair->next = NULL;
FB_GetRange(fb, startID, curPos, &curPair->attribute);
/* Stick this attribute on the list */
if(ti->attListTail) {
ti->attListTail->next = curPair;
ti->attListTail = curPair;
} else {
ti->attList = ti->attListTail = curPair;
}
/* If this is the first attribute, find the type of tag
* based on it. Also, start saving the text of the tag. */
if(firstAtt) {
ti->type = GetTagType(curPair->attribute);
startText = FB_GetPointer(fb)-1;
firstAtt = PR_FALSE;
}
} else {
if(curchar=='=') {
/* If we don't have any attribute but we do have an
* equal sign, that's an error */
*errStr = PR_smprintf("line %d: Malformed tag starting at line %d.\n", linenum, startline);
state = ERR_STATE;
break;
}
}
/* Compute next state */
if(curchar=='=') {
startID = FB_GetPointer(fb);
state = PRE_VAL_WS_STATE;
} else if(curchar=='>') {
state = DONE_STATE;
} else if(curPair) {
state = POST_ATT_WS_STATE;
} else {
state = PRE_ATT_WS_STATE;
}
} else if(isAttChar(curchar)) {
/* Just another char in the attribute. Do nothing */
state = GET_ATT_STATE;
} else {
/* bogus char */
*errStr= PR_smprintf("line %d: Bogus chararacter '%c' in tag.\n",
linenum, curchar);
state = ERR_STATE;
break;
}
break;
case PRE_ATT_WS_STATE:
if(curchar=='>') {
state = DONE_STATE;
} else if(isspace(curchar)) {
/* more whitespace, do nothing */
} else if(isAttChar(curchar)) {
/* starting another attribute */
startID = FB_GetPointer(fb)-1;
state = GET_ATT_STATE;
} else {
/* bogus char */
*errStr = PR_smprintf("line %d: Bogus character '%c' in tag.\n",
linenum, curchar);
state = ERR_STATE;
break;
}
break;
case POST_ATT_WS_STATE:
if(curchar=='>') {
state = DONE_STATE;
} else if(isspace(curchar)) {
/* more whitespace, do nothing */
} else if(isAttChar(curchar)) {
/* starting another attribute */
startID = FB_GetPointer(fb)-1;
state = GET_ATT_STATE;
} else if(curchar=='=') {
/* there was whitespace between the attribute and its equal
* sign, which means there's a value coming up */
state = PRE_VAL_WS_STATE;
} else {
/* bogus char */
*errStr = PR_smprintf("line %d: Bogus character '%c' in tag.\n",
linenum, curchar);
state = ERR_STATE;
break;
}
break;
case PRE_VAL_WS_STATE:
if(curchar=='>') {
/* premature end-of-tag (sounds like a personal problem). */
*errStr = PR_smprintf(
"line %d: End of tag while waiting for value.\n", linenum);
state = ERR_STATE;
break;
} else if(isspace(curchar)) {
/* more whitespace, do nothing */
break;
} else {
/* this must be some sort of value. Fall through
* to GET_VALUE_STATE */
startID=FB_GetPointer(fb)-1;
state = GET_VALUE_STATE;
}
/* Fall through if we didn't break on '>' or whitespace */
case GET_VALUE_STATE:
if(isspace(curchar) || curchar=='>') {
/* end of value */
curPos = FB_GetPointer(fb)-2;
if(curPos >= startID) {
/* Grab the value */
FB_GetRange(fb, startID, curPos, &curPair->value);
curPair->valueLine = linenum;
} else {
/* empty value, leave as NULL */
}
if(isspace(curchar)) {
state = PRE_ATT_WS_STATE;
} else {
state = DONE_STATE;
}
} else if(curchar=='\"' || curchar=='\'') {
/* quoted value. Start recording the value inside the quote*/
startID = FB_GetPointer(fb);
state = GET_QUOTED_VAL_STATE;
quotechar = curchar; /* look for matching quote type */
} else {
/* just more value */
}
break;
case GET_QUOTED_VAL_STATE:
if(curchar == quotechar) {
/* end of quoted value */
curPos = FB_GetPointer(fb)-2;
if(curPos >= startID) {
/* Grab the value */
FB_GetRange(fb, startID, curPos, &curPair->value);
curPair->valueLine = linenum;
} else {
/* empty value, leave it as NULL */
}
state = GET_ATT_STATE;
startID = FB_GetPointer(fb);
} else {
/* more quoted value, continue */
}
break;
case DONE_STATE:
case ERR_STATE:
default:
; /* should never get here */
}
}
if(state == DONE_STATE) {
/* Get the text of the tag */
curPos = FB_GetPointer(fb)-1;
FB_GetRange(fb, startText, curPos, &ti->text);
/* Return the tag */
return ti;
}
/* Uh oh, an error. Kill the tag item*/
DestroyTagItem(ti);
return NULL;
}
/************************************************************************
*
* D e s t r o y H T M L I t e m
*/
static void
DestroyHTMLItem(HTMLItem *item)
{
if(item->type == TAG_ITEM) {
DestroyTagItem(item->item.tag);
} else {
if(item->item.text) {
PR_Free(item->item.text);
}
}
}
/************************************************************************
*
* D e s t r o y T a g I t e m
*/
static void
DestroyTagItem(TagItem* ti)
{
AVPair *temp;
if(ti->text) {
PR_Free(ti->text); ti->text = NULL;
}
while(ti->attList) {
temp = ti->attList;
ti->attList = ti->attList->next;
if(temp->attribute) {
PR_Free(temp->attribute); temp->attribute = NULL;
}
if(temp->value) {
PR_Free(temp->value); temp->value = NULL;
}
PR_Free(temp);
}
PR_Free(ti);
}
/************************************************************************
*
* G e t T a g T y p e
*/
static TAG_TYPE
GetTagType(char *att)
{
if(!PORT_Strcasecmp(att, "APPLET")) {
return APPLET_TAG;
}
if(!PORT_Strcasecmp(att, "SCRIPT")) {
return SCRIPT_TAG;
}
if(!PORT_Strcasecmp(att, "LINK")) {
return LINK_TAG;
}
if(!PORT_Strcasecmp(att, "STYLE")) {
return STYLE_TAG;
}
return OTHER_TAG;
}
/************************************************************************
*
* F B _ C r e a t e
*/
static FileBuffer*
FB_Create(PRFileDesc* fd)
{
FileBuffer *fb;
PRInt32 amountRead;
PRInt32 storedOffset;
fb = (FileBuffer*) PR_Malloc(sizeof(FileBuffer));
fb->fd = fd;
storedOffset = PR_Seek(fd, 0, PR_SEEK_CUR);
PR_Seek(fd, 0, PR_SEEK_SET);
fb->startOffset = 0;
amountRead = PR_Read(fd, fb->buf, FILE_BUFFER_BUFSIZE);
if(amountRead == -1) goto loser;
fb->maxIndex = amountRead-1;
fb->curIndex = 0;
fb->IsEOF = (fb->curIndex>fb->maxIndex) ? PR_TRUE : PR_FALSE;
fb->lineNum = 1;
PR_Seek(fd, storedOffset, PR_SEEK_SET);
return fb;
loser:
PR_Seek(fd, storedOffset, PR_SEEK_SET);
PR_Free(fb);
return NULL;
}
/************************************************************************
*
* F B _ G e t C h a r
*/
static int
FB_GetChar(FileBuffer *fb)
{
PRInt32 storedOffset;
PRInt32 amountRead;
int retval=-1;
if(fb->IsEOF) {
return EOF;
}
storedOffset = PR_Seek(fb->fd, 0, PR_SEEK_CUR);
retval = fb->buf[fb->curIndex++];
if(retval=='\n') fb->lineNum++;
if(fb->curIndex > fb->maxIndex) {
/* We're at the end of the buffer. Try to get some new data from the
* file */
fb->startOffset += fb->maxIndex+1;
PR_Seek(fb->fd, fb->startOffset, PR_SEEK_SET);
amountRead = PR_Read(fb->fd, fb->buf, FILE_BUFFER_BUFSIZE);
if(amountRead==-1) goto loser;
fb->maxIndex = amountRead-1;
fb->curIndex = 0;
}
fb->IsEOF = (fb->curIndex > fb->maxIndex) ? PR_TRUE : PR_FALSE;
loser:
PR_Seek(fb->fd, storedOffset, PR_SEEK_SET);
return retval;
}
/************************************************************************
*
* F B _ G e t L i n e N u m
*
*/
static unsigned int
FB_GetLineNum(FileBuffer *fb)
{
return fb->lineNum;
}
/************************************************************************
*
* F B _ G e t P o i n t e r
*
*/
static PRInt32
FB_GetPointer(FileBuffer *fb)
{
return fb->startOffset + fb->curIndex;
}
/************************************************************************
*
* F B _ G e t R a n g e
*
*/
static PRInt32
FB_GetRange(FileBuffer *fb, PRInt32 start, PRInt32 end, char **buf)
{
PRInt32 amountRead;
PRInt32 storedOffset;
*buf = PR_Malloc(end-start+2);
if(*buf == NULL) {
return 0;
}
storedOffset = PR_Seek(fb->fd, 0, PR_SEEK_CUR);
PR_Seek(fb->fd, start, PR_SEEK_SET);
amountRead = PR_Read(fb->fd, *buf, end-start+1);
PR_Seek(fb->fd, storedOffset, PR_SEEK_SET);
if(amountRead == -1) {
PR_Free(*buf);
*buf = NULL;
return 0;
}
(*buf)[end-start+1] = '\0';
return amountRead;
}
/************************************************************************
*
* F B _ D e s t r o y
*
*/
static void
FB_Destroy(FileBuffer *fb)
{
if(fb) {
PR_Free(fb);
}
}
/************************************************************************
*
* P r i n t T a g I t e m
*
*/
static void
PrintTagItem(PRFileDesc *fd, TagItem *ti)
{
AVPair *pair;
PR_fprintf(fd, "TAG:\n----\nType: ");
switch(ti->type) {
case APPLET_TAG:
PR_fprintf(fd, "applet\n");
break;
case SCRIPT_TAG:
PR_fprintf(fd, "script\n");
break;
case LINK_TAG:
PR_fprintf(fd, "link\n");
break;
case STYLE_TAG:
PR_fprintf(fd, "style\n");
break;
case COMMENT_TAG:
PR_fprintf(fd, "comment\n");
break;
case OTHER_TAG:
default:
PR_fprintf(fd, "other\n");
break;
}
PR_fprintf(fd, "Attributes:\n");
for(pair = ti->attList; pair; pair=pair->next) {
PR_fprintf(fd, "\t%s=%s\n", pair->attribute,
pair->value ? pair->value : "");
}
PR_fprintf(fd, "Text:%s\n", ti->text ? ti->text : "");
PR_fprintf(fd, "---End of tag---\n");
}
/************************************************************************
*
* P r i n t H T M L S t r e a m
*
*/
static void
PrintHTMLStream(PRFileDesc *fd, HTMLItem *head)
{
while(head) {
if(head->type==TAG_ITEM) {
PrintTagItem(fd, head->item.tag);
} else {
PR_fprintf(fd, "\nTEXT:\n-----\n%s\n-----\n\n", head->item.text);
}
head = head->next;
}
}
/************************************************************************
*
* S a v e I n l i n e S c r i p t
*
*/
static int
SaveInlineScript(char *text, char *id, char *basedir, char *archiveDir)
{
char *filename=NULL;
PRFileDesc *fd=NULL;
int retval = -1;
PRInt32 writeLen;
char *ilDir=NULL;
if(!text || !id || !archiveDir) {
return -1;
}
if(dumpParse) {
PR_fprintf(outputFD, "SaveInlineScript: text=%s, id=%s, \n"
"basedir=%s, archiveDir=%s\n",
text, id, basedir, archiveDir);
}
/* Make sure the archive directory is around */
if(ensureExists(basedir, archiveDir) != PR_SUCCESS) {
PR_fprintf(errorFD,
"ERROR: Unable to create archive directory %s.\n", archiveDir);
errorCount++;
return -1;
}
/* Make sure the inline script directory is around */
ilDir = PR_smprintf("%s/inlineScripts", archiveDir);
scriptdir = "inlineScripts";
if(ensureExists(basedir, ilDir) != PR_SUCCESS) {
PR_fprintf(errorFD,
"ERROR: Unable to create directory %s.\n", ilDir);
errorCount++;
return -1;
}
filename = PR_smprintf("%s/%s/%s", basedir, ilDir, id);
/* If the file already exists, give a warning, then blow it away */
if(PR_Access(filename, PR_ACCESS_EXISTS) == PR_SUCCESS) {
PR_fprintf(errorFD,
"warning: file \"%s\" already exists--will overwrite.\n",
filename);
warningCount++;
if(rm_dash_r(filename)) {
PR_fprintf(errorFD,
"ERROR: Unable to delete %s.\n", filename);
errorCount++;
goto finish;
}
}
/* Write text into file with name id */
fd = PR_Open(filename, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0777);
if(!fd) {
PR_fprintf(errorFD, "ERROR: Unable to create file \"%s\".\n",
filename);
errorCount++;
goto finish;
}
writeLen = strlen(text);
if( PR_Write(fd, text, writeLen) != writeLen) {
PR_fprintf(errorFD, "ERROR: Unable to write to file \"%s\".\n",
filename);
errorCount++;
goto finish;
}
retval = 0;
finish:
if(filename) {
PR_smprintf_free(filename);
}
if(ilDir) {
PR_smprintf_free(ilDir);
}
if(fd) {
PR_Close(fd);
}
return retval;
}
/************************************************************************
*
* S a v e U n n a m a b l e S c r i p t
*
*/
static int
SaveUnnamableScript(char *text, char *basedir, char *archiveDir,
char *HTMLfilename)
{
char *id=NULL;
char *ext=NULL;
char *start=NULL;
int retval = -1;
if(!text || !archiveDir || !HTMLfilename) {
return -1;
}
if(dumpParse) {
PR_fprintf(outputFD, "SaveUnnamableScript: text=%s, basedir=%s,\n"
"archiveDir=%s, filename=%s\n", text, basedir, archiveDir,
HTMLfilename);
}
/* Construct the filename */
ext = PL_strrchr(HTMLfilename, '.');
if(ext) {
*ext = '\0';
}
for(start=HTMLfilename; strpbrk(start, "/\\");
start=strpbrk(start, "/\\")+1);
if(*start=='\0') start = HTMLfilename;
id = PR_smprintf("_%s%d", start, idOrdinal++);
if(ext) {
*ext = '.';
}
/* Now call SaveInlineScript to do the work */
retval = SaveInlineScript(text, id, basedir, archiveDir);
PR_Free(id);
return retval;
}
/************************************************************************
*
* S a v e S o u r c e
*
*/
static int
SaveSource(char *src, char *codebase, char *basedir, char *archiveDir)
{
char *from=NULL, *to=NULL;
int retval = -1;
char *arcDir=NULL;
if(!src || !archiveDir) {
return -1;
}
if(dumpParse) {
PR_fprintf(outputFD, "SaveSource: src=%s, codebase=%s, basedir=%s,\n"
"archiveDir=%s\n", src, codebase, basedir, archiveDir);
}
if(codebase) {
arcDir = PR_smprintf("%s/%s/%s/", basedir, codebase, archiveDir);
} else {
arcDir = PR_smprintf("%s/%s/", basedir, archiveDir);
}
if(codebase) {
from = PR_smprintf("%s/%s/%s", basedir, codebase, src);
to = PR_smprintf("%s%s", arcDir, src);
} else {
from = PR_smprintf("%s/%s", basedir, src);
to = PR_smprintf("%s%s", arcDir, src);
}
if(make_dirs(to, 0777)) {
PR_fprintf(errorFD,
"ERROR: Unable to create archive directory %s.\n", archiveDir);
errorCount++;
goto finish;
}
retval = copyinto(from, to);
finish:
if(from) PR_Free(from);
if(to) PR_Free(to);
if(arcDir) PR_Free(arcDir);
return retval;
}
/************************************************************************
*
* T a g T y p e T o S t r i n g
*
*/
char *
TagTypeToString(TAG_TYPE type)
{
switch(type) {
case APPLET_TAG:
return "APPLET";
case SCRIPT_TAG:
return "SCRIPT";
case LINK_TAG:
return "LINK";
case STYLE_TAG:
return "STYLE";
default:
return "unknown";
}
return "unknown";
}
/************************************************************************
*
* e x t r a c t _ j s
*
*/
static int
extract_js(char *filename)
{
PRFileDesc *fd=NULL;
FileBuffer *fb=NULL;
HTML_STATE state;
int curchar;
HTMLItem *head = NULL;
HTMLItem *tail = NULL;
PRInt32 textStart;
PRInt32 curOffset;
TagItem *tagp=NULL;
char *text=NULL;
HTMLItem *curitem=NULL;
int retval = -1;
char *tagerr=NULL;
unsigned int linenum, startLine;
char *archiveDir=NULL, *firstArchiveDir=NULL;
HTMLItem *styleList, *styleListTail;
HTMLItem *entityList, *entityListTail;
char *basedir=NULL;
styleList = entityList = styleListTail = entityListTail = NULL;
/* Initialize the implicit ID counter for each file */
idOrdinal = 0;
/*
* First, parse the HTML into a stream of tags and text.
*/
fd = PR_Open(filename, PR_RDONLY, 0);
if(!fd) {
PR_fprintf(errorFD, "Unable to open %s for reading.\n", filename);
errorCount++;
return -1;
}
/* Construct base directory of filename. */
{
char *cp;
basedir = PL_strdup(filename);
/* Remove trailing slashes */
while( (cp = PL_strprbrk(basedir, "/\\")) ==
(basedir + strlen(basedir) - 1)) {
*cp = '\0';
}
/* Now remove everything from the last slash (which will be followed
* by a filename) to the end */
cp = PL_strprbrk(basedir, "/\\");
if(cp) {
*cp = '\0';
}
}
state = TEXT_HTML_STATE;
fb = FB_Create(fd);
textStart=0;
startLine = 0;
while(linenum=FB_GetLineNum(fb), (curchar = FB_GetChar(fb)) != EOF) {
switch(state) {
case TEXT_HTML_STATE:
if(curchar == '<') {
/*
* Found a tag
*/
/* Save the text so far to a new text item */
curOffset = FB_GetPointer(fb)-2;
if(curOffset >= textStart) {
if(FB_GetRange(fb, textStart, curOffset, &text) !=
curOffset-textStart+1) {
PR_fprintf(errorFD,
"Unable to read from %s.\n", filename);
errorCount++;
goto loser;
}
/* little fudge here. If the first character on a line
* is '<', meaning a new tag, the preceding text item
* actually ends on the previous line. In this case
* we will be saying that the text segment ends on the
* next line. I don't think this matters for text items. */
curitem = CreateTextItem(text, startLine, linenum);
text = NULL;
if(tail == NULL) {
head = tail = curitem;
} else {
tail->next = curitem;
tail = curitem;
}
}
/* Process the tag */
tagp = ProcessTag(fb, &tagerr);
if(!tagp) {
if(tagerr) {
PR_fprintf(errorFD, "Error in file %s: %s\n",
filename, tagerr);
errorCount++;
} else {
PR_fprintf(errorFD,
"Error in file %s, in tag starting at line %d\n",
filename, linenum);
errorCount++;
}
goto loser;
}
/* Add the tag to the list */
curitem = CreateTagItem(tagp, linenum, FB_GetLineNum(fb));
if(tail == NULL) {
head = tail = curitem;
} else {
tail->next = curitem;
tail = curitem;
}
/* What's the next state */
if(tagp->type == SCRIPT_TAG) {
state = SCRIPT_HTML_STATE;
}
/* Start recording text from the new offset */
textStart = FB_GetPointer(fb);
startLine = FB_GetLineNum(fb);
} else {
/* regular character. Next! */
}
break;
case SCRIPT_HTML_STATE:
if(curchar == '<') {
char *cp;
/*
* If this is a </script> tag, then we're at the end of the
* script. Otherwise, ignore
*/
curOffset = FB_GetPointer(fb)-1;
cp = NULL;
if(FB_GetRange(fb, curOffset, curOffset+8, &cp) != 9) {
if(cp) { PR_Free(cp); cp = NULL; }
} else {
/* compare the strings */
if( !PORT_Strncasecmp(cp, "</script>", 9) ) {
/* This is the end of the script. Record the text. */
curOffset--;
if(curOffset >= textStart) {
if(FB_GetRange(fb, textStart, curOffset, &text) !=
curOffset-textStart+1) {
PR_fprintf(errorFD,
"Unable to read from %s.\n", filename);
errorCount++;
goto loser;
}
curitem = CreateTextItem(text, startLine, linenum);
text = NULL;
if(tail == NULL) {
head = tail = curitem;
} else {
tail->next = curitem;
tail = curitem;
}
}
/* Now parse the /script tag and put it on the list */
tagp = ProcessTag(fb, &tagerr);
if(!tagp) {
if(tagerr) {
PR_fprintf(errorFD,
"Error in file %s: %s\n", filename, tagerr);
} else {
PR_fprintf(errorFD,
"Error in file %s, in tag starting at"
" line %d\n", filename, linenum);
}
errorCount++;
goto loser;
}
curitem = CreateTagItem(tagp, linenum,
FB_GetLineNum(fb));
if(tail == NULL) {
head = tail = curitem;
} else {
tail->next = curitem;
tail = curitem;
}
/* go back to text state */
state = TEXT_HTML_STATE;
textStart = FB_GetPointer(fb);
startLine = FB_GetLineNum(fb);
}
}
}
break;
}
}
/* End of the file. Wrap up any remaining text */
if(state == SCRIPT_HTML_STATE) {
if(tail && tail->type==TAG_ITEM) {
PR_fprintf(errorFD, "ERROR: <SCRIPT> tag at %s:%d is not followed "
"by a </SCRIPT> tag.\n", filename, tail->startLine);
} else {
PR_fprintf(errorFD, "ERROR: <SCRIPT> tag in file %s is not followed"
" by a </SCRIPT tag.\n", filename);
}
errorCount++;
goto loser;
}
curOffset = FB_GetPointer(fb)-1;
if(curOffset >= textStart) {
text = NULL;
if( FB_GetRange(fb, textStart, curOffset, &text) !=
curOffset-textStart+1) {
PR_fprintf(errorFD, "Unable to read from %s.\n", filename);
errorCount++;
goto loser;
}
curitem = CreateTextItem(text, startLine, linenum);
text = NULL;
if(tail == NULL) {
head = tail = curitem;
} else {
tail->next = curitem;
tail = curitem;
}
}
if(dumpParse) {
PrintHTMLStream(outputFD, head);
}
/*
* Now we have a stream of tags and text. Go through and deal with each.
*/
for(curitem = head; curitem; curitem = curitem->next) {
TagItem *tagp=NULL;
AVPair *pairp=NULL;
char *src=NULL, *id=NULL, *codebase=NULL;
PRBool hasEventHandler=PR_FALSE;
int i;
/* Reset archive directory for each tag */
if(archiveDir) {
PR_Free(archiveDir); archiveDir = NULL;
}
/* We only analyze tags */
if(curitem->type != TAG_ITEM) {
continue;
}
tagp = curitem->item.tag;
/* go through the attributes to get information */
for(pairp=tagp->attList; pairp; pairp=pairp->next) {
/* ARCHIVE= */
if( !PL_strcasecmp(pairp->attribute, "archive")) {
if(archiveDir) {
/* Duplicate attribute. Print warning */
PR_fprintf(errorFD,
"warning: \"%s\" attribute overwrites previous attribute"
" in tag starting at %s:%d.\n",
pairp->attribute, filename, curitem->startLine);
warningCount++;
PR_Free(archiveDir);
}
archiveDir = PL_strdup(pairp->value);
/* Substiture ".arc" for ".jar" */
if( (PL_strlen(archiveDir)<4) ||
PL_strcasecmp((archiveDir+strlen(archiveDir)-4), ".jar")){
PR_fprintf(errorFD,
"warning: ARCHIVE attribute should end in \".jar\" in tag"
" starting on %s:%d.\n", filename, curitem->startLine);
warningCount++;
PR_Free(archiveDir);
archiveDir = PR_smprintf("%s.arc", archiveDir);
} else {
PL_strcpy(archiveDir+strlen(archiveDir)-4, ".arc");
}
/* Record the first archive. This will be used later if
* the archive is not specified */
if(firstArchiveDir == NULL) {
firstArchiveDir = PL_strdup(archiveDir);
}
}
/* CODEBASE= */
else if( !PL_strcasecmp(pairp->attribute, "codebase")) {
if(codebase) {
/* Duplicate attribute. Print warning */
PR_fprintf(errorFD,
"warning: \"%s\" attribute overwrites previous attribute"
" in tag staring at %s:%d.\n",
pairp->attribute, filename, curitem->startLine);
warningCount++;
}
codebase = pairp->value;
}
/* SRC= and HREF= */
else if( !PORT_Strcasecmp(pairp->attribute, "src") ||
!PORT_Strcasecmp(pairp->attribute, "href") ) {
if(src) {
/* Duplicate attribute. Print warning */
PR_fprintf(errorFD,
"warning: \"%s\" attribute overwrites previous attribute"
" in tag staring at %s:%d.\n",
pairp->attribute, filename, curitem->startLine);
warningCount++;
}
src = pairp->value;
}
/* CODE= */
else if(!PORT_Strcasecmp(pairp->attribute, "code") ) {
/*!!!XXX Change PORT to PL all over this code !!! */
if(src) {
/* Duplicate attribute. Print warning */
PR_fprintf(errorFD,
"warning: \"%s\" attribute overwrites previous attribute"
" ,in tag staring at %s:%d.\n",
pairp->attribute, filename, curitem->startLine);
warningCount++;
}
src = pairp->value;
/* Append a .class if one is not already present */
if( (PL_strlen(src)<6) ||
PL_strcasecmp( (src + PL_strlen(src) - 6), ".class") ) {
src = PR_smprintf("%s.class", src);
/* Put this string back into the data structure so it
* will be deallocated properly */
PR_Free(pairp->value);
pairp->value = src;
}
}
/* ID= */
else if (!PL_strcasecmp(pairp->attribute, "id") ) {
if(id) {
/* Duplicate attribute. Print warning */
PR_fprintf(errorFD,
"warning: \"%s\" attribute overwrites previous attribute"
" in tag staring at %s:%d.\n",
pairp->attribute, filename, curitem->startLine);
warningCount++;
}
id = pairp->value;
}
/* STYLE= */
/* style= attributes, along with JS entities, are stored into
* files with dynamically generated names. The filenames are
* based on the order in which the text is found in the file.
* All JS entities on all lines up to and including the line
* containing the end of the tag that has this style= attribute
* will be processed before this style=attribute. So we need
* to record the line that this _tag_ (not the attribute) ends on.
*/
else if(!PL_strcasecmp(pairp->attribute, "style") && pairp->value) {
HTMLItem *styleItem;
/* Put this item on the style list */
styleItem = CreateTextItem(PL_strdup(pairp->value),
curitem->startLine, curitem->endLine);
if(styleListTail == NULL) {
styleList = styleListTail = styleItem;
} else {
styleListTail->next = styleItem;
styleListTail = styleItem;
}
}
/* Event handlers */
else {
for(i=0; i < num_handlers; i++) {
if(!PL_strcasecmp(event_handlers[i], pairp->attribute)) {
hasEventHandler = PR_TRUE;
break;
}
}
}
/* JS Entity */
{
char *entityStart, *entityEnd;
HTMLItem *entityItem;
/* go through each JavaScript entity ( &{...}; ) and store it
* in the entityList. The important thing is to record what
* line number it's on, so we can get it in the right order
* in relation to style= attributes.
* Apparently, these can't flow across lines, so the start and
* end line will be the same. That helps matters.
*/
entityEnd = pairp->value;
while( entityEnd &&
(entityStart = PL_strstr(entityEnd, "&{")) != NULL) {
entityStart +=2; /* point at beginning of actual entity */
entityEnd = PL_strstr(entityStart, "}");
if(entityEnd) {
/* Put this item on the entity list */
*entityEnd = '\0';
entityItem = CreateTextItem(PL_strdup(entityStart),
pairp->valueLine, pairp->valueLine);
*entityEnd = '}';
if(entityListTail) {
entityListTail->next = entityItem;
entityListTail = entityItem;
} else {
entityList = entityListTail = entityItem;
}
}
}
}
}
/* If no archive was supplied, we use the first one of the file */
if(!archiveDir && firstArchiveDir) {
archiveDir = PL_strdup(firstArchiveDir);
}
/* If we have an event handler, we need to archive this tag */
if(hasEventHandler) {
if(!id) {
PR_fprintf(errorFD,
"warning: tag starting at %s:%d has event handler but"
" no ID attribute. The tag will not be signed.\n",
filename, curitem->startLine);
warningCount++;
} else if(!archiveDir) {
PR_fprintf(errorFD,
"warning: tag starting at %s:%d has event handler but"
" no ARCHIVE attribute. The tag will not be signed.\n",
filename, curitem->startLine);
warningCount++;
} else {
if(SaveInlineScript(tagp->text, id, basedir, archiveDir)) {
goto loser;
}
}
}
switch(tagp->type) {
case APPLET_TAG:
if(!src) {
PR_fprintf(errorFD,
"error: APPLET tag starting on %s:%d has no CODE "
"attribute.\n", filename, curitem->startLine);
errorCount++;
goto loser;
} else if(!archiveDir) {
PR_fprintf(errorFD,
"error: APPLET tag starting on %s:%d has no ARCHIVE "
"attribute.\n", filename, curitem->startLine);
errorCount++;
goto loser;
} else {
if(SaveSource(src, codebase, basedir, archiveDir)) {
goto loser;
}
}
break;
case SCRIPT_TAG:
case LINK_TAG:
case STYLE_TAG:
if(!archiveDir) {
PR_fprintf(errorFD,
"error: %s tag starting on %s:%d has no ARCHIVE "
"attribute.\n", TagTypeToString(tagp->type),
filename, curitem->startLine);
errorCount++;
goto loser;
} else if(src) {
if(SaveSource(src, codebase, basedir, archiveDir)) {
goto loser;
}
} else if(id) {
/* Save the next text item */
if(!curitem->next || (curitem->next->type != TEXT_ITEM)) {
PR_fprintf(errorFD,
"warning: %s tag starting on %s:%d is not followed"
" by script text.\n", TagTypeToString(tagp->type),
filename, curitem->startLine);
warningCount++;
/* just create empty file */
if(SaveInlineScript("", id, basedir, archiveDir)) {
goto loser;
}
} else {
curitem = curitem->next;
if(SaveInlineScript(curitem->item.text, id, basedir,
archiveDir)){
goto loser;
}
}
} else {
/* No src or id tag--warning */
PR_fprintf(errorFD,
"warning: %s tag starting on %s:%d has no SRC or"
" ID attributes. Will not sign.\n",
TagTypeToString(tagp->type), filename, curitem->startLine);
warningCount++;
}
break;
default:
/* do nothing for other tags */
break;
}
}
/* Now deal with all the unnamable scripts */
if(firstArchiveDir) {
HTMLItem *style, *entity;
/* Go through the lists of JS entities and style attributes. Do them
* in chronological order within a list. Pick the list with the lower
* endLine. In case of a tie, entities come first.
*/
style = styleList; entity = entityList;
while(style || entity) {
if(!entity || (style && (style->endLine < entity->endLine))) {
/* Process style */
SaveUnnamableScript(style->item.text, basedir, firstArchiveDir,
filename);
style=style->next;
} else {
/* Process entity */
SaveUnnamableScript(entity->item.text, basedir, firstArchiveDir,
filename);
entity=entity->next;
}
}
}
retval = 0;
loser:
/* Blow away the stream */
while(head) {
curitem = head;
head = head->next;
DestroyHTMLItem(curitem);
}
while(styleList) {
curitem = styleList;
styleList = styleList->next;
DestroyHTMLItem(curitem);
}
while(entityList) {
curitem = entityList;
entityList = entityList->next;
DestroyHTMLItem(curitem);
}
if(text) {
PR_Free(text); text=NULL;
}
if(fb) {
FB_Destroy(fb); fb=NULL;
}
if(fd) {
PR_Close(fd);
}
if(tagerr) {
PR_smprintf_free(tagerr); tagerr=NULL;
}
if(archiveDir) {
PR_Free(archiveDir); archiveDir=NULL;
}
if(firstArchiveDir) {
PR_Free(firstArchiveDir); firstArchiveDir=NULL;
}
return retval;
}
/**********************************************************************
*
* e n s u r e E x i s t s
*
* Check for existence of indicated directory. If it doesn't exist,
* it will be created.
* Returns PR_SUCCESS if the directory is present, PR_FAILURE otherwise.
*/
static PRStatus
ensureExists (char *base, char *path)
{
char fn [FNSIZE];
PRDir *dir;
sprintf (fn, "%s/%s", base, path);
/*PR_fprintf(outputFD, "Trying to open directory %s.\n", fn);*/
if( (dir=PR_OpenDir(fn)) ) {
PR_CloseDir(dir);
return PR_SUCCESS;
}
return PR_MkDir(fn, 0777);
}
/***************************************************************************
*
* m a k e _ d i r s
*
* Ensure that the directory portion of the path exists. This may require
* making the directory, and its parent, and its parent's parent, etc.
*/
static int
make_dirs(char *path, int file_perms)
{
char *Path;
char *start;
char *sep;
int ret = 0;
PRFileInfo info;
if(!path) {
return 0;
}
Path = PL_strdup(path);
start = strpbrk(Path, "/\\");
if(!start) {
return 0;
}
start++; /* start right after first slash */
/* Each time through the loop add one more directory. */
while( (sep=strpbrk(start, "/\\")) ) {
*sep = '\0';
if( PR_GetFileInfo(Path, &info) != PR_SUCCESS) {
/* No such dir, we have to create it */
if( PR_MkDir(Path, file_perms) != PR_SUCCESS) {
PR_fprintf(errorFD, "ERROR: Unable to create directory %s.\n",
Path);
errorCount++;
ret = -1;
goto loser;
}
} else {
/* something exists by this name, make sure it's a directory */
if( info.type != PR_FILE_DIRECTORY ) {
PR_fprintf(errorFD, "ERROR: Unable to create directory %s.\n",
Path);
errorCount++;
ret = -1;
goto loser;
}
}
start = sep+1; /* start after the next slash */
*sep = '/';
}
loser:
PR_Free(Path);
return ret;
}
/*
* c o p y i n t o
*
* Function to copy file "from" to path "to".
*
*/
static int
copyinto (char *from, char *to)
{
PRInt32 num;
char buf [BUFSIZ];
PRFileDesc *infp=NULL, *outfp=NULL;
int retval = -1;
if ((infp = PR_Open(from, PR_RDONLY, 0777)) == NULL) {
PR_fprintf(errorFD, "ERROR: Unable to open \"%s\" for reading.\n",
from);
errorCount++;
goto finish;
}
/* If to already exists, print a warning before deleting it */
if(PR_Access(to, PR_ACCESS_EXISTS) == PR_SUCCESS) {
PR_fprintf(errorFD, "warning: %s already exists--will overwrite\n",
to);
warningCount++;
if(rm_dash_r(to)) {
PR_fprintf(errorFD,
"ERROR: Unable to remove %s.\n", to);
errorCount++;
goto finish;
}
}
if ((outfp = PR_Open(to, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0777))
== NULL) {
char *errBuf=NULL;
errBuf = PR_Malloc(PR_GetErrorTextLength());
PR_fprintf(errorFD, "ERROR: Unable to open \"%s\" for writing.\n",
to);
if(PR_GetErrorText(errBuf)) {
PR_fprintf(errorFD, "Cause: %s\n", errBuf);
}
if(errBuf) {
PR_Free(errBuf);
}
errorCount++;
goto finish;
}
while( (num = PR_Read(infp, buf, BUFSIZ)) >0) {
if(PR_Write(outfp, buf, num) != num) {
PR_fprintf(errorFD, "ERROR: Error writing to %s.\n", to);
errorCount++;
goto finish;
}
}
retval = 0;
finish:
if(infp) PR_Close(infp);
if(outfp) PR_Close(outfp);
return retval;
}