gecko/security/nss/cmd/signtool/javascript.c
2008-06-06 08:40:11 -04:00

1868 lines
45 KiB
C

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* 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 the Initial Developer are Copyright (C) 1994-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#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(int 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 = '\0';
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;
PORT_Assert(quotechar == '\0');
quotechar = curchar; /* look for matching quote type */
} else {
/* just more value */
}
break;
case GET_QUOTED_VAL_STATE:
PORT_Assert(quotechar != '\0');
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;
quotechar = '\0';
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 = (unsigned char) 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)
/* do nothing */;
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:
break;
}
return "unknown";
}
/************************************************************************
*
* e x t r a c t _ j s
*
*/
static int
extract_js(char *filename)
{
PRFileDesc * fd = NULL;
FileBuffer * fb = NULL;
HTMLItem * head = NULL;
HTMLItem * tail = NULL;
HTMLItem * curitem = NULL;
HTMLItem * styleList = NULL;
HTMLItem * styleListTail = NULL;
HTMLItem * entityList = NULL;
HTMLItem * entityListTail = NULL;
TagItem * tagp = NULL;
char *text = NULL;
char *tagerr = NULL;
char *archiveDir = NULL;
char *firstArchiveDir = NULL;
char *basedir = NULL;
PRInt32 textStart;
PRInt32 curOffset;
HTML_STATE state;
int curchar;
int retval = -1;
unsigned int linenum, startLine;
/* 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;
}