Index: ossp-pkg/js/TODO RCS File: /v/ossp/cvs/ossp-pkg/js/TODO,v rcsdiff -q -kk '-r1.3' '-r1.4' -u '/v/ossp/cvs/ossp-pkg/js/TODO,v' 2>/dev/null --- TODO 2006/07/23 08:30:45 1.3 +++ TODO 2006/07/23 10:52:12 1.4 @@ -13,5 +13,5 @@ CANDO - - JS_HAS_FILE_OBJECT support without the need for NSPR + - nothing known at this point. Index: ossp-pkg/js/configure.ac RCS File: /v/ossp/cvs/ossp-pkg/js/configure.ac,v rcsdiff -q -kk '-r1.11' '-r1.12' -u '/v/ossp/cvs/ossp-pkg/js/configure.ac,v' 2>/dev/null --- configure.ac 2006/07/23 08:00:43 1.11 +++ configure.ac 2006/07/23 10:52:12 1.12 @@ -119,6 +119,15 @@ CLI_LIBS="$CLI_LIBS `$PERL -MExtUtils::Embed -e ldopts`" fi +dnl # configure option --with-file +AC_ARG_WITH([file], + AS_HELP_STRING([--with-file], [build with File object (grants access to the filesystem)]), + [ac_cv_with_file=$withval], [ac_cv_with_file=no]) +AC_CACHE_CHECK([whether to build with the File object], [ac_cv_with_file], [ac_cv_with_file=no]) +if test ".$ac_cv_with_file" != ".no"; then + CPPFLAGS="$CPPFLAGS -DJS_HAS_FILE_OBJECT" +fi + AC_SUBST(CLI_CFLAGS) AC_SUBST(CLI_CPPFLAGS) AC_SUBST(CLI_LDFLAGS) Index: ossp-pkg/js/devtool.conf RCS File: /v/ossp/cvs/ossp-pkg/js/devtool.conf,v rcsdiff -q -kk '-r1.5' '-r1.6' -u '/v/ossp/cvs/ossp-pkg/js/devtool.conf,v' 2>/dev/null --- devtool.conf 2006/07/22 19:43:48 1.5 +++ devtool.conf 2006/07/23 10:52:12 1.6 @@ -17,6 +17,7 @@ --prefix=/tmp/js \ --with-editline=/usr/opkg/lib \ --with-perl=/usr/opkg/bin/perl \ + --with-file \ --disable-shared \ "$@" Index: ossp-pkg/js/src/jsfile.c RCS File: /v/ossp/cvs/ossp-pkg/js/src/jsfile.c,v co -q -kk -p'1.2' '/v/ossp/cvs/ossp-pkg/js/src/jsfile.c,v' | diff -u /dev/null - -L'ossp-pkg/js/src/jsfile.c' 2>/dev/null --- ossp-pkg/js/src/jsfile.c +++ - 2024-05-12 11:04:26.208840141 +0200 @@ -0,0 +1,3051 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=80: + * + * ***** 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 Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 ***** */ + +/* + * JS File object + */ +#if JS_HAS_FILE_OBJECT + +#ifdef OSSP +#define XP_UNIX +#endif + +#include "jsstddef.h" + +/* ----------------- Platform-specific includes and defines ----------------- */ +#if defined(XP_WIN) || defined(XP_OS2) +# include +# include +# include +# include +# define FILESEPARATOR '\\' +# define FILESEPARATOR2 '/' +# define CURRENT_DIR "c:\\" +# define POPEN _popen +# define PCLOSE _pclose +#elif defined(XP_UNIX) || defined(XP_BEOS) +# include +# include +# include +# include +#ifdef OSSP +# include +# include +# include +# include +# include +#endif +# define FILESEPARATOR '/' +# define FILESEPARATOR2 '\0' +# define CURRENT_DIR "/" +# define POPEN popen +# define PCLOSE pclose +#endif + +/* --------------- Platform-independent includes and defines ---------------- */ +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsdate.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jslock.h" +#include "jsobj.h" +#include "jsparse.h" +#include "jsscan.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" +#include "jsutil.h" /* Added by JSIFY */ +#include + +/* NSPR dependencies */ +#ifdef OSSP +#define PR_RDONLY 0x01 +#define PR_WRONLY 0x02 +#define PR_RDWR 0x04 +#define PR_CREATE_FILE 0x08 +#define PR_APPEND 0x10 +#define PR_TRUNCATE 0x20 +#define PR_SYNC 0x40 +#define PR_EXCL 0x80 +#else +#include "prio.h" +#include "prerror.h" +#endif + +#define SPECIAL_FILE_STRING "Special File" +#define CURRENTDIR_PROPERTY "currentDir" +#define SEPARATOR_PROPERTY "separator" +#define FILE_CONSTRUCTOR "File" +#define PIPE_SYMBOL '|' + +#define ASCII 0 +#define UTF8 1 +#define UCS2 2 + +#define asciistring "text" +#define utfstring "binary" +#define unicodestring "unicode" + +#define MAX_PATH_LENGTH 1024 +#define MODE_SIZE 256 +#define NUMBER_SIZE 32 +#define MAX_LINE_LENGTH 256 +#define URL_PREFIX "file://" + +#define STDINPUT_NAME "Standard input stream" +#define STDOUTPUT_NAME "Standard output stream" +#define STDERROR_NAME "Standard error stream" + +#define RESOLVE_PATH js_canonicalPath /* js_absolutePath */ + +/* Error handling */ +typedef enum JSFileErrNum { +#define MSG_DEF(name, number, count, exception, format) \ + name = number, +#include "jsfile.msg" +#undef MSG_DEF + JSFileErr_Limit +#undef MSGDEF +} JSFileErrNum; + +#define JSFILE_HAS_DFLT_MSG_STRINGS 1 + +JSErrorFormatString JSFile_ErrorFormatString[JSFileErr_Limit] = { +#if JSFILE_HAS_DFLT_MSG_STRINGS +#define MSG_DEF(name, number, count, exception, format) \ + { format, count }, +#else +#define MSG_DEF(name, number, count, exception, format) \ + { NULL, count }, +#endif +#include "jsfile.msg" +#undef MSG_DEF +}; + +const JSErrorFormatString * +JSFile_GetErrorMessage(void *userRef, const char *locale, + const uintN errorNumber) +{ + if ((errorNumber > 0) && (errorNumber < JSFileErr_Limit)) + return &JSFile_ErrorFormatString[errorNumber]; + else + return NULL; +} + +#define JSFILE_CHECK_NATIVE(op) \ + if (file->isNative) { \ + JS_ReportWarning(cx, "Cannot call or access \"%s\" on native file %s",\ + op, file->path); \ + goto out; \ + } + +#define JSFILE_CHECK_WRITE \ + if (!file->isOpen) { \ + JS_ReportWarning(cx, \ + "File %s is closed, will open it for writing, proceeding", \ + file->path); \ + js_FileOpen(cx, obj, file, "write,append,create"); \ + } \ + if (!js_canWrite(cx, file)) { \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_CANNOT_WRITE, file->path); \ + goto out; \ + } + +#define JSFILE_CHECK_READ \ + if (!file->isOpen) { \ + JS_ReportWarning(cx, \ + "File %s is closed, will open it for reading, proceeding", \ + file->path); \ + js_FileOpen(cx, obj, file, "read"); \ + } \ + if (!js_canRead(cx, file)) { \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_CANNOT_READ, file->path); \ + goto out; \ + } + +#define JSFILE_CHECK_OPEN(op) \ + if (!file->isOpen) { \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_FILE_MUST_BE_CLOSED, op); \ + goto out; \ + } + +#define JSFILE_CHECK_CLOSED(op) \ + if (file->isOpen) { \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_FILE_MUST_BE_OPEN, op); \ + goto out; \ + } + +#define JSFILE_CHECK_ONE_ARG(op) \ + if (argc != 1) { \ + char str[NUMBER_SIZE]; \ + sprintf(str, "%d", argc); \ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, \ + JSFILEMSG_EXPECTS_ONE_ARG_ERROR, op, str); \ + goto out; \ + } + + +/* + Security mechanism, should define a callback for this. + The parameters are as follows: + SECURITY_CHECK(JSContext *cx, JSPrincipals *ps, char *op_name, JSFile *file) + XXX Should this be a real function returning a JSBool result (and getting + some typesafety help from the compiler?). +*/ +#define SECURITY_CHECK(cx, ps, op, file) \ + /* Define a callback here... */ + + +/* Structure representing the file internally */ +typedef struct JSFile { + char *path; /* the path to the file. */ + JSBool isOpen; + int32 mode; /* mode used to open the file: read, write, append, create, etc.. */ + int32 type; /* Asciiz, utf, unicode */ + char byteBuffer[3]; /* bytes read in advance by js_FileRead ( UTF8 encoding ) */ + jsint nbBytesInBuf; /* number of bytes stored in the buffer above */ + jschar charBuffer; /* character read in advance by readln ( mac files only ) */ + JSBool charBufferUsed; /* flag indicating if the buffer above is being used */ + JSBool hasRandomAccess;/* can the file be randomly accessed? false for stdin, and + UTF-encoded files. */ + JSBool hasAutoflush; /* should we force a flush for each line break? */ + JSBool isNative; /* if the file is using OS-specific file FILE type */ + /* We can actually put the following two in a union since they should never be used at the same time */ +#ifdef OSSP + FILE *handle; /* the handle for the file, if open. */ +#else + PRFileDesc *handle; /* the handle for the file, if open. */ +#endif + FILE *nativehandle; /* native handle, for stuff NSPR doesn't do. */ + JSBool isPipe; /* if the file is really an OS pipe */ +} JSFile; + +/* a few forward declarations... */ +static JSClass file_class; +JS_PUBLIC_API(JSObject*) js_NewFileObject(JSContext *cx, char *filename); +static JSBool file_open(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); +static JSBool file_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); + +/* New filename manipulation procesures */ +/* assumes we don't have leading/trailing spaces */ +static JSBool +js_filenameHasAPipe(const char *filename) +{ + if (!filename) + return JS_FALSE; + + return filename[0] == PIPE_SYMBOL || + filename[strlen(filename) - 1] == PIPE_SYMBOL; +} + +static JSBool +js_isAbsolute(const char *name) +{ +#if defined(XP_WIN) || defined(XP_OS2) + return *name && name[1] == ':'; +#else + return (name[0] +# if defined(XP_UNIX) || defined(XP_BEOS) + == +# else + != +# endif + FILESEPARATOR); +#endif +} + +/* + * Concatinates base and name to produce a valid filename. + * Returned string must be freed. +*/ +static char* +js_combinePath(JSContext *cx, const char *base, const char *name) +{ + int len = strlen(base); + char* result = JS_malloc(cx, len + strlen(name) + 2); + + if (!result) + return NULL; + + strcpy(result, base); + + if (base[len - 1] != FILESEPARATOR && base[len - 1] != FILESEPARATOR2) { + result[len] = FILESEPARATOR; + result[len + 1] = '\0'; + } + strcat(result, name); + return result; +} + +/* Extract the last component from a path name. Returned string must be freed */ +static char * +js_fileBaseName(JSContext *cx, const char *pathname) +{ + jsint index, aux; + char *result; + + index = strlen(pathname)-1; + + /* Chop off trailing seperators. */ + while (index > 0 && (pathname[index]==FILESEPARATOR || + pathname[index]==FILESEPARATOR2)) { + --index; + } + + aux = index; + + /* Now find the next separator. */ + while (index >= 0 && pathname[index] != FILESEPARATOR && + pathname[index] != FILESEPARATOR2) { + --index; + } + + /* Allocate and copy. */ + result = JS_malloc(cx, aux - index + 1); + if (!result) + return NULL; + strncpy(result, pathname + index + 1, aux - index); + result[aux - index] = '\0'; + return result; +} + +/* + * Returns everything but the last component from a path name. + * Returned string must be freed. + */ +static char * +js_fileDirectoryName(JSContext *cx, const char *pathname) +{ + char *result; + const char *cp, *end; + size_t pathsize; + + end = pathname + strlen(pathname); + cp = end - 1; + + /* If this is already a directory, chop off the trailing /s. */ + while (cp >= pathname) { + if (*cp != FILESEPARATOR && *cp != FILESEPARATOR2) + break; + --cp; + } + + if (cp < pathname && end != pathname) { + /* There were just /s, return the root. */ + result = JS_malloc(cx, 1 + 1); /* The separator + trailing NUL. */ + result[0] = FILESEPARATOR; + result[1] = '\0'; + return result; + } + + /* Now chop off the last portion. */ + while (cp >= pathname) { + if (*cp == FILESEPARATOR || *cp == FILESEPARATOR2) + break; + --cp; + } + + /* Check if this is a leaf. */ + if (cp < pathname) { + /* It is, return "pathname/". */ + if (end[-1] == FILESEPARATOR || end[-1] == FILESEPARATOR2) { + /* Already has its terminating /. */ + return JS_strdup(cx, pathname); + } + + pathsize = end - pathname + 1; + result = JS_malloc(cx, pathsize + 1); + if (!result) + return NULL; + + strcpy(result, pathname); + result[pathsize - 1] = FILESEPARATOR; + result[pathsize] = '\0'; + + return result; + } + + /* Return everything up to and including the seperator. */ + pathsize = cp - pathname + 1; + result = JS_malloc(cx, pathsize + 1); + if (!result) + return NULL; + + strncpy(result, pathname, pathsize); + result[pathsize] = '\0'; + + return result; +} + +static char * +js_absolutePath(JSContext *cx, const char * path) +{ + JSObject *obj; + JSString *str; + jsval prop; + + if (js_isAbsolute(path)) { + return JS_strdup(cx, path); + } else { + obj = JS_GetGlobalObject(cx); + if (!JS_GetProperty(cx, obj, FILE_CONSTRUCTOR, &prop)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FILE_CONSTRUCTOR_UNDEFINED_ERROR); + return JS_strdup(cx, path); + } + + obj = JSVAL_TO_OBJECT(prop); + if (!JS_GetProperty(cx, obj, CURRENTDIR_PROPERTY, &prop)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FILE_CURRENTDIR_UNDEFINED_ERROR); + return JS_strdup(cx, path); + } + + str = JS_ValueToString(cx, prop); + if (!str) + return JS_strdup(cx, path); + + /* should we have an array of curr dirs indexed by drive for windows? */ + return js_combinePath(cx, JS_GetStringBytes(str), path); + } +} + +/* Side effect: will remove spaces in the beginning/end of the filename */ +static char * +js_canonicalPath(JSContext *cx, char *oldpath) +{ + char *tmp; + char *path = oldpath; + char *base, *dir, *current, *result; + jsint c; + jsint back = 0; + unsigned int i = 0, j = strlen(path)-1; + + /* This is probably optional */ + /* Remove possible spaces in the beginning and end */ + while (i < j && path[i] == ' ') + i++; + while (j >= 0 && path[j] == ' ') + j--; + + tmp = JS_malloc(cx, j-i+2); + if (!tmp) + return NULL; + + strncpy(tmp, path + i, j - i + 1); + tmp[j - i + 1] = '\0'; + + path = tmp; + + /* Pipe support. */ + if (js_filenameHasAPipe(path)) + return path; + + /* file:// support. */ + if (!strncmp(path, URL_PREFIX, strlen(URL_PREFIX))) { + tmp = js_canonicalPath(cx, path + strlen(URL_PREFIX)); + JS_free(cx, path); + return tmp; + } + + if (!js_isAbsolute(path)) { + tmp = js_absolutePath(cx, path); + if (!tmp) + return NULL; + path = tmp; + } + + result = JS_strdup(cx, ""); + + current = path; + + base = js_fileBaseName(cx, current); + dir = js_fileDirectoryName(cx, current); + + while (strcmp(dir, current)) { + if (!strcmp(base, "..")) { + back++; + } else { + if (back > 0) { + back--; + } else { + tmp = result; + result = JS_malloc(cx, strlen(base) + 1 + strlen(tmp) + 1); + if (!result) + goto out; + + strcpy(result, base); + c = strlen(result); + if (*tmp) { + result[c] = FILESEPARATOR; + result[c + 1] = '\0'; + strcat(result, tmp); + } + JS_free(cx, tmp); + } + } + JS_free(cx, current); + JS_free(cx, base); + current = dir; + base = js_fileBaseName(cx, current); + dir = js_fileDirectoryName(cx, current); + } + + tmp = result; + result = JS_malloc(cx, strlen(dir)+1+strlen(tmp)+1); + if (!result) + goto out; + + strcpy(result, dir); + c = strlen(result); + if (tmp[0]!='\0') { + if ((result[c-1]!=FILESEPARATOR)&&(result[c-1]!=FILESEPARATOR2)) { + result[c] = FILESEPARATOR; + result[c+1] = '\0'; + } + strcat(result, tmp); + } + +out: + if (tmp) + JS_free(cx, tmp); + if (dir) + JS_free(cx, dir); + if (base) + JS_free(cx, base); + if (current) + JS_free(cx, current); + + return result; +} + +/* -------------------------- Text conversion ------------------------------- */ +/* The following is ripped from libi18n/unicvt.c and include files.. */ + +/* + * UTF8 defines and macros + */ +#define ONE_OCTET_BASE 0x00 /* 0xxxxxxx */ +#define ONE_OCTET_MASK 0x7F /* x1111111 */ +#define CONTINUING_OCTET_BASE 0x80 /* 10xxxxxx */ +#define CONTINUING_OCTET_MASK 0x3F /* 00111111 */ +#define TWO_OCTET_BASE 0xC0 /* 110xxxxx */ +#define TWO_OCTET_MASK 0x1F /* 00011111 */ +#define THREE_OCTET_BASE 0xE0 /* 1110xxxx */ +#define THREE_OCTET_MASK 0x0F /* 00001111 */ +#define FOUR_OCTET_BASE 0xF0 /* 11110xxx */ +#define FOUR_OCTET_MASK 0x07 /* 00000111 */ +#define FIVE_OCTET_BASE 0xF8 /* 111110xx */ +#define FIVE_OCTET_MASK 0x03 /* 00000011 */ +#define SIX_OCTET_BASE 0xFC /* 1111110x */ +#define SIX_OCTET_MASK 0x01 /* 00000001 */ + +#define IS_UTF8_1ST_OF_1(x) (( (x)&~ONE_OCTET_MASK ) == ONE_OCTET_BASE) +#define IS_UTF8_1ST_OF_2(x) (( (x)&~TWO_OCTET_MASK ) == TWO_OCTET_BASE) +#define IS_UTF8_1ST_OF_3(x) (( (x)&~THREE_OCTET_MASK) == THREE_OCTET_BASE) +#define IS_UTF8_1ST_OF_4(x) (( (x)&~FOUR_OCTET_MASK ) == FOUR_OCTET_BASE) +#define IS_UTF8_1ST_OF_5(x) (( (x)&~FIVE_OCTET_MASK ) == FIVE_OCTET_BASE) +#define IS_UTF8_1ST_OF_6(x) (( (x)&~SIX_OCTET_MASK ) == SIX_OCTET_BASE) +#define IS_UTF8_2ND_THRU_6TH(x) \ + (( (x)&~CONTINUING_OCTET_MASK ) == CONTINUING_OCTET_BASE) +#define IS_UTF8_1ST_OF_UCS2(x) \ + IS_UTF8_1ST_OF_1(x) \ + || IS_UTF8_1ST_OF_2(x) \ + || IS_UTF8_1ST_OF_3(x) + + +#define MAX_UCS2 0xFFFF +#define DEFAULT_CHAR 0x003F /* Default char is "?" */ +#define BYTE_MASK 0xBF +#define BYTE_MARK 0x80 + + +/* Function: one_ucs2_to_utf8_char + * + * Function takes one UCS-2 char and writes it to a UTF-8 buffer. + * We need a UTF-8 buffer because we don't know before this + * function how many bytes of utf-8 data will be written. It also + * takes a pointer to the end of the UTF-8 buffer so that we don't + * overwrite data. This function returns the number of UTF-8 bytes + * of data written, or -1 if the buffer would have been overrun. + */ + +#define LINE_SEPARATOR 0x2028 +#define PARAGRAPH_SEPARATOR 0x2029 +static int16 one_ucs2_to_utf8_char(unsigned char *tobufp, + unsigned char *tobufendp, + uint16 onechar) +{ + int16 numUTF8bytes = 0; + + if (onechar == LINE_SEPARATOR || onechar == PARAGRAPH_SEPARATOR) { + strcpy((char*)tobufp, "\n"); + return strlen((char*)tobufp); + } + + if (onechar < 0x80) { + numUTF8bytes = 1; + } else if (onechar < 0x800) { + numUTF8bytes = 2; + } else { + /* 0x800 >= onechar <= MAX_UCS2 */ + numUTF8bytes = 3; + } + + tobufp += numUTF8bytes; + + /* return error if we don't have space for the whole character */ + if (tobufp > tobufendp) { + return(-1); + } + + switch(numUTF8bytes) { + case 3: *--tobufp = (onechar | BYTE_MARK) & BYTE_MASK; onechar >>=6; + *--tobufp = (onechar | BYTE_MARK) & BYTE_MASK; onechar >>=6; + *--tobufp = onechar | THREE_OCTET_BASE; + break; + + case 2: *--tobufp = (onechar | BYTE_MARK) & BYTE_MASK; onechar >>=6; + *--tobufp = onechar | TWO_OCTET_BASE; + break; + + case 1: *--tobufp = (unsigned char)onechar; + break; + } + + return numUTF8bytes; +} + +/* + * utf8_to_ucs2_char + * + * Convert a utf8 multibyte character to ucs2 + * + * inputs: pointer to utf8 character(s) + * length of utf8 buffer ("read" length limit) + * pointer to return ucs2 character + * + * outputs: number of bytes in the utf8 character + * -1 if not a valid utf8 character sequence + * -2 if the buffer is too short + */ +static int16 +utf8_to_ucs2_char(const unsigned char *utf8p, int16 buflen, uint16 *ucs2p) +{ + uint16 lead, cont1, cont2; + + /* + * Check for minimum buffer length + */ + if ((buflen < 1) || (utf8p == NULL)) { + return -2; + } + lead = (uint16) (*utf8p); + + /* + * Check for a one octet sequence + */ + if (IS_UTF8_1ST_OF_1(lead)) { + *ucs2p = lead & ONE_OCTET_MASK; + return 1; + } + + /* + * Check for a two octet sequence + */ + if (IS_UTF8_1ST_OF_2(*utf8p)) { + if (buflen < 2) + return -2; + cont1 = (uint16) *(utf8p+1); + if (!IS_UTF8_2ND_THRU_6TH(cont1)) + return -1; + *ucs2p = (lead & TWO_OCTET_MASK) << 6; + *ucs2p |= cont1 & CONTINUING_OCTET_MASK; + return 2; + } + + /* + * Check for a three octet sequence + */ + else if (IS_UTF8_1ST_OF_3(lead)) { + if (buflen < 3) + return -2; + cont1 = (uint16) *(utf8p+1); + cont2 = (uint16) *(utf8p+2); + if ( (!IS_UTF8_2ND_THRU_6TH(cont1)) + || (!IS_UTF8_2ND_THRU_6TH(cont2))) + return -1; + *ucs2p = (lead & THREE_OCTET_MASK) << 12; + *ucs2p |= (cont1 & CONTINUING_OCTET_MASK) << 6; + *ucs2p |= cont2 & CONTINUING_OCTET_MASK; + return 3; + } + else { /* not a valid utf8/ucs2 character */ + return -1; + } +} + +/* ----------------------------- Helper functions --------------------------- */ +/* Ripped off from lm_win.c .. */ +/* where is strcasecmp?.. for now, it's case sensitive.. + * + * strcasecmp is in strings.h, but on windows it's called _stricmp... + * will need to #ifdef this +*/ + +static int32 +js_FileHasOption(JSContext *cx, const char *oldoptions, const char *name) +{ + char *comma, *equal, *current; + char *options = JS_strdup(cx, oldoptions); + int32 found = 0; + + current = options; + for (;;) { + comma = strchr(current, ','); + if (comma) *comma = '\0'; + equal = strchr(current, '='); + if (equal) *equal = '\0'; + if (strcmp(current, name) == 0) { + if (!equal || strcmp(equal + 1, "yes") == 0) + found = 1; + else + found = atoi(equal + 1); + } + if (equal) *equal = '='; + if (comma) *comma = ','; + if (found || !comma) + break; + current = comma + 1; + } + JS_free(cx, options); + return found; +} + +/* empty the buffer */ +static void +js_ResetBuffers(JSFile * file) +{ + file->charBufferUsed = JS_FALSE; + file->nbBytesInBuf = 0; +} + +/* Reset file attributes */ +static void +js_ResetAttributes(JSFile * file) +{ + file->mode = file->type = 0; + file->isOpen = JS_FALSE; + file->handle = NULL; + file->nativehandle = NULL; + file->hasRandomAccess = JS_TRUE; /* Innocent until proven guilty. */ + file->hasAutoflush = JS_FALSE; + file->isNative = JS_FALSE; + file->isPipe = JS_FALSE; + + js_ResetBuffers(file); +} + +static JSBool +js_FileOpen(JSContext *cx, JSObject *obj, JSFile *file, char *mode){ + JSString *type, *mask; + jsval v[2]; + jsval rval; + + type = JS_InternString(cx, asciistring); + mask = JS_NewStringCopyZ(cx, mode); + v[0] = STRING_TO_JSVAL(mask); + v[1] = STRING_TO_JSVAL(type); + + if (!file_open(cx, obj, 2, v, &rval)) + return JS_FALSE; + return JS_TRUE; +} + +/* Buffered version of PR_Read. Used by js_FileRead */ +static int32 +js_BufferedRead(JSFile * f, char *buf, int32 len) +{ + int32 count = 0; + + while (f->nbBytesInBuf>0&&len>0) { + buf[0] = f->byteBuffer[0]; + f->byteBuffer[0] = f->byteBuffer[1]; + f->byteBuffer[1] = f->byteBuffer[2]; + f->nbBytesInBuf--; + len--; + buf+=1; + count++; + } + + if (len>0) { +#ifdef OSSP + count += (!f->isNative) + ? fread(buf, 1, len, f->handle) + : fread(buf, 1, len, f->nativehandle); +#else + count += (!f->isNative) + ? PR_Read(f->handle, buf, len) + : fread(buf, 1, len, f->nativehandle); +#endif + } + return count; +} + +static int32 +js_FileRead(JSContext *cx, JSFile *file, jschar *buf, int32 len, int32 mode) +{ + unsigned char *aux; + int32 count = 0, i; + jsint remainder; + unsigned char utfbuf[3]; + + if (file->charBufferUsed) { + buf[0] = file->charBuffer; + buf++; + len--; + file->charBufferUsed = JS_FALSE; + } + + switch (mode) { + case ASCII: + aux = (unsigned char*)JS_malloc(cx, len); + if (!aux) + return 0; + + count = js_BufferedRead(file, aux, len); + if (count == -1) { + JS_free(cx, aux); + return 0; + } + + for (i = 0; i < len; i++) + buf[i] = (jschar)aux[i]; + + JS_free(cx, aux); + break; + + case UTF8: + remainder = 0; + for (count = 0;count0) { + file->byteBuffer[file->nbBytesInBuf] = utfbuf[0]; + file->nbBytesInBuf++; + utfbuf[0] = utfbuf[1]; + utfbuf[1] = utfbuf[2]; + remainder--; + } + break; + + case UCS2: + count = js_BufferedRead(file, (char*)buf, len*2) >> 1; + if (count == -1) + return 0; + + break; + + default: + /* Not reached. */ + JS_ASSERT(0); + } + + if(count == -1) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "read", file->path); + } + + return count; +} + +static int32 +js_FileSeek(JSContext *cx, JSFile *file, int32 len, int32 mode) +{ + int32 count = 0, i; + jsint remainder; + unsigned char utfbuf[3]; + jschar tmp; + + switch (mode) { + case ASCII: +#ifdef OSSP + count = fseek(file->handle, len, SEEK_CUR); +#else + count = PR_Seek(file->handle, len, PR_SEEK_CUR); +#endif + break; + + case UTF8: + remainder = 0; + for (count = 0;count0) { + file->byteBuffer[file->nbBytesInBuf] = utfbuf[0]; + file->nbBytesInBuf++; + utfbuf[0] = utfbuf[1]; + utfbuf[1] = utfbuf[2]; + remainder--; + } + break; + + case UCS2: +#ifdef OSSP + count = fseek(file->handle, len*2, SEEK_CUR)/2; +#else + count = PR_Seek(file->handle, len*2, PR_SEEK_CUR)/2; +#endif + break; + + default: + /* Not reached. */ + JS_ASSERT(0); + } + + if(count == -1) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "seek", file->path); + } + + return count; +} + +static int32 +js_FileWrite(JSContext *cx, JSFile *file, jschar *buf, int32 len, int32 mode) +{ + unsigned char *aux; + int32 count = 0, i, j; + unsigned char *utfbuf; + + switch (mode) { + case ASCII: + aux = (unsigned char*)JS_malloc(cx, len); + if (!aux) + return 0; + + for (i = 0; iisNative) + ? fwrite(aux, 1, len, file->handle) + : fwrite(aux, 1, len, file->nativehandle); +#else + count = (!file->isNative) + ? PR_Write(file->handle, aux, len) + : fwrite(aux, 1, len, file->nativehandle); +#endif + + if (count==-1) { + JS_free(cx, aux); + return 0; + } + + JS_free(cx, aux); + break; + + case UTF8: + utfbuf = (unsigned char*)JS_malloc(cx, len*3); + if (!utfbuf) return 0; + i = 0; + for (count = 0;countisNative) + ? fwrite(utfbuf, 1, i, file->handle) + : fwrite(utfbuf, 1, i, file->nativehandle); +#else + j = (!file->isNative) + ? PR_Write(file->handle, utfbuf, i) + : fwrite(utfbuf, 1, i, file->nativehandle); +#endif + + if (jisNative) + ? fwrite(buf, 1, len*2, file->handle) >> 1 + : fwrite(buf, 1, len*2, file->nativehandle) >> 1; +#else + count = (!file->isNative) + ? PR_Write(file->handle, buf, len*2) >> 1 + : fwrite(buf, 1, len*2, file->nativehandle) >> 1; +#endif + + if (count == -1) + return 0; + break; + + default: + /* Not reached. */ + JS_ASSERT(0); + } + + if(count == -1) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "write", file->path); + } + + return count; +} + +/* ----------------------------- Property checkers -------------------------- */ +static JSBool +js_exists(JSContext *cx, JSFile *file) +{ + if (file->isNative) { + /* It doesn't make sense for a pipe of stdstream. */ + return JS_FALSE; + } + +#ifdef OSSP + return access(file->path, F_OK) == 0; +#else + return PR_Access(file->path, PR_ACCESS_EXISTS) == PR_SUCCESS; +#endif +} + +static JSBool +js_canRead(JSContext *cx, JSFile *file) +{ + if (!file->isNative) { + if (file->isOpen && !(file->mode & PR_RDONLY)) + return JS_FALSE; +#ifdef OSSP + return access(file->path, R_OK) == 0; +#else + return PR_Access(file->path, PR_ACCESS_READ_OK) == PR_SUCCESS; +#endif + } + + if (file->isPipe) { + /* Is this pipe open for reading? */ + return file->path[0] == PIPE_SYMBOL; + } + + return !strcmp(file->path, STDINPUT_NAME); +} + +static JSBool +js_canWrite(JSContext *cx, JSFile *file) +{ + if (!file->isNative) { + if (file->isOpen && !(file->mode & PR_WRONLY)) + return JS_FALSE; +#ifdef OSSP + return access(file->path, W_OK) == 0; +#else + return PR_Access(file->path, PR_ACCESS_WRITE_OK) == PR_SUCCESS; +#endif + } + + if(file->isPipe) { + /* Is this pipe open for writing? */ + return file->path[strlen(file->path)-1] == PIPE_SYMBOL; + } + + return !strcmp(file->path, STDOUTPUT_NAME) || + !strcmp(file->path, STDERROR_NAME); +} + +static JSBool +js_isFile(JSContext *cx, JSFile *file) +{ + if (!file->isNative) { +#ifdef OSSP + struct stat info; + + if (file->isOpen + ? fstat(fileno(file->handle), &info) != 0 + : stat(file->path, &info) != 0) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + } + + return S_ISREG(info.st_mode) != 0; +#else + PRFileInfo info; + + if (file->isOpen + ? PR_GetOpenFileInfo(file->handle, &info) + : PR_GetFileInfo(file->path, &info) != PR_SUCCESS) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + } + + return info.type == PR_FILE_FILE; +#endif + } + + /* This doesn't make sense for a pipe of stdstream. */ + return JS_FALSE; +} + +static JSBool +js_isDirectory(JSContext *cx, JSFile *file) +{ + if(!file->isNative){ +#ifdef OSSP + struct stat info; +#else + PRFileInfo info; +#endif + + /* Hack needed to get get_property to work. */ + if (!js_exists(cx, file)) + return JS_FALSE; + +#ifdef OSSP + if (file->isOpen + ? fstat(fileno(file->handle), &info) != 0 + : stat(file->path, &info) != 0) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + } + + return S_ISDIR(info.st_mode) != 0; +#else + if (file->isOpen + ? PR_GetOpenFileInfo(file->handle, &info) + : PR_GetFileInfo(file->path, &info) != PR_SUCCESS) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + } + + return info.type == PR_FILE_DIRECTORY; +#endif + } + + /* This doesn't make sense for a pipe of stdstream. */ + return JS_FALSE; +} + +static jsval +js_size(JSContext *cx, JSFile *file) +{ +#ifdef OSSP + struct stat info; +#else + PRFileInfo info; +#endif + + JSFILE_CHECK_NATIVE("size"); + +#ifdef OSSP + if (file->isOpen + ? fstat(fileno(file->handle), &info) != 0 + : stat(file->path, &info) != 0) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + } + + return INT_TO_JSVAL(info.st_size); +#else + if (file->isOpen + ? PR_GetOpenFileInfo(file->handle, &info) + : PR_GetFileInfo(file->path, &info) != PR_SUCCESS) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + return JS_FALSE; + } + + return INT_TO_JSVAL(info.size); +#endif + +out: + return JSVAL_VOID; +} + +/* + * Return the parent object + */ +static JSBool +js_parent(JSContext *cx, JSFile *file, jsval *resultp) +{ + char *str; + + /* Since we only care about pipes and native files, return NULL. */ + if (file->isNative) { + *resultp = JSVAL_VOID; + return JS_TRUE; + } + + str = js_fileDirectoryName(cx, file->path); + if (!str) + return JS_FALSE; + + /* If the directory is equal to the original path, we're at the root. */ + if (!strcmp(file->path, str)) { + *resultp = JSVAL_NULL; + } else { + JSObject *obj = js_NewFileObject(cx, str); + if (!obj) { + JS_free(cx, str); + return JS_FALSE; + } + *resultp = OBJECT_TO_JSVAL(obj); + } + + JS_free(cx, str); + return JS_TRUE; +} + +static JSBool +js_name(JSContext *cx, JSFile *file, jsval *vp) +{ + char *name; + JSString *str; + + if (file->isPipe) { + *vp = JSVAL_VOID; + return JS_TRUE; + } + + name = js_fileBaseName(cx, file->path); + if (!name) + return JS_FALSE; + + str = JS_NewString(cx, name, strlen(name)); + if (!str) { + JS_free(cx, name); + return JS_FALSE; + } + + *vp = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +/* ------------------------------ File object methods ---------------------------- */ +static JSBool +file_open(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *strmode, *strtype; + char *ctype, *mode; + int32 mask, type; + int len; + + mode = NULL; + + SECURITY_CHECK(cx, NULL, "open", file); + + /* A native file that is already open */ + if(file->isOpen && file->isNative) { + JS_ReportWarning(cx, "Native file %s is already open, proceeding", + file->path); + goto good; + } + + /* Close before proceeding */ + if (file->isOpen) { + JS_ReportWarning(cx, "File %s is already open, we will close it and " + "reopen, proceeding", file->path); + if(!file_close(cx, obj, 0, NULL, rval)) + goto out; + } + + if (js_isDirectory(cx, file)) { + JS_ReportWarning(cx, "%s seems to be a directory, there is no point in " + "trying to open it, proceeding", file->path); + goto good; + } + + /* Path must be defined at this point */ + len = strlen(file->path); + + /* Mode */ + if (argc >= 1) { + strmode = JS_ValueToString(cx, argv[0]); + if (!strmode) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_OPEN_NOT_STRING_ERROR, + argv[0]); + goto out; + } + mode = JS_strdup(cx, JS_GetStringBytes(strmode)); + } else { + if(file->path[0]==PIPE_SYMBOL) { + /* pipe default mode */ + mode = JS_strdup(cx, "read"); + } else if(file->path[len-1]==PIPE_SYMBOL) { + /* pipe default mode */ + mode = JS_strdup(cx, "write"); + } else { + /* non-destructive, permissive defaults. */ + mode = JS_strdup(cx, "readWrite,append,create"); + } + } + + /* Process the mode */ + mask = 0; + /* TODO: this is pretty ugly, we walk thru the string too many times */ + mask |= js_FileHasOption(cx, mode, "read") ? PR_RDONLY : 0; + mask |= js_FileHasOption(cx, mode, "write") ? PR_WRONLY : 0; + mask |= js_FileHasOption(cx, mode, "readWrite")? PR_RDWR : 0; + mask |= js_FileHasOption(cx, mode, "append") ? PR_APPEND : 0; + mask |= js_FileHasOption(cx, mode, "create") ? PR_CREATE_FILE : 0; + mask |= js_FileHasOption(cx, mode, "replace") ? PR_TRUNCATE : 0; + + if (mask & PR_RDWR) + mask |= (PR_RDONLY | PR_WRONLY); + if ((mask & PR_RDONLY) && (mask & PR_WRONLY)) + mask |= PR_RDWR; + + file->hasAutoflush |= js_FileHasOption(cx, mode, "autoflush"); + + /* Type */ + if (argc > 1) { + strtype = JS_ValueToString(cx, argv[1]); + if (!strtype) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_SECOND_ARGUMENT_OPEN_NOT_STRING_ERROR, + argv[1]); + goto out; + } + ctype = JS_GetStringBytes(strtype); + + if(!strcmp(ctype, utfstring)) { + type = UTF8; + } else if (!strcmp(ctype, unicodestring)) { + type = UCS2; + } else { + if (strcmp(ctype, asciistring)) { + JS_ReportWarning(cx, "File type %s is not supported, using " + "'text' instead, proceeding", ctype); + } + type = ASCII; + } + } else { + type = ASCII; + } + + /* Save the relevant fields */ + file->type = type; + file->mode = mask; + file->nativehandle = NULL; + file->hasRandomAccess = (type != UTF8); + + /* + * Deal with pipes here. We can't use NSPR for pipes, so we have to use + * POPEN. + */ + if (file->path[0]==PIPE_SYMBOL || file->path[len-1]==PIPE_SYMBOL) { + if (file->path[0] == PIPE_SYMBOL && file->path[len-1] == PIPE_SYMBOL) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_BIDIRECTIONAL_PIPE_NOT_SUPPORTED); + goto out; + } else { + int i = 0; + char pipemode[3]; + SECURITY_CHECK(cx, NULL, "pipe_open", file); + + if(file->path[0] == PIPE_SYMBOL){ + if(mask & (PR_WRONLY | PR_APPEND | PR_CREATE_FILE | PR_TRUNCATE)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OPEN_MODE_NOT_SUPPORTED_WITH_PIPES, + mode, file->path); + goto out; + } + /* open(SPOOLER, "| cat -v | lpr -h 2>/dev/null") -- pipe for writing */ + pipemode[i++] = 'r'; +#ifndef XP_UNIX + pipemode[i++] = file->type==UTF8 ? 'b' : 't'; +#endif + pipemode[i++] = '\0'; + file->nativehandle = POPEN(&file->path[1], pipemode); + } else if(file->path[len-1] == PIPE_SYMBOL) { + char *command = JS_malloc(cx, len); + + strncpy(command, file->path, len-1); + command[len-1] = '\0'; + /* open(STATUS, "netstat -an 2>&1 |") */ + pipemode[i++] = 'w'; +#ifndef XP_UNIX + pipemode[i++] = file->type==UTF8 ? 'b' : 't'; +#endif + pipemode[i++] = '\0'; + file->nativehandle = POPEN(command, pipemode); + JS_free(cx, command); + } + /* set the flags */ + file->isNative = JS_TRUE; + file->isPipe = JS_TRUE; + file->hasRandomAccess = JS_FALSE; + } + } else { + /* TODO: what about the permissions?? Java ignores the problem... */ +#ifdef OSSP + { + int my_fd; + int my_fd_mode = 0; + char *my_fp_mode = ""; + my_fd_mode |= ((mask & PR_RDONLY) ? O_RDONLY : 0); + my_fd_mode |= ((mask & PR_WRONLY) ? O_WRONLY : 0); + my_fd_mode |= ((mask & PR_RDWR) ? O_RDWR : 0); + my_fd_mode |= ((mask & PR_APPEND) ? O_APPEND : 0); + my_fd_mode |= ((mask & PR_CREATE_FILE) ? O_CREAT : 0); + my_fd_mode |= ((mask & PR_TRUNCATE) ? O_TRUNC : 0); + if ((mask & PR_RDWR)) my_fp_mode = "r+"; + else if ((mask & PR_RDONLY)) my_fp_mode = "r"; + else if ((mask & PR_WRONLY)) my_fp_mode = "w"; + else if ((mask & PR_APPEND) && (mask & PR_RDWR)) my_fp_mode = "a+"; + else if ((mask & PR_APPEND)) my_fp_mode = "a"; + else my_fp_mode = "rw"; + if ((my_fd = open(file->path, my_fd_mode, 0644)) != -1) + file->handle = fdopen(my_fd, my_fp_mode); + } +#else + file->handle = PR_Open(file->path, mask, 0644); +#endif + } + + js_ResetBuffers(file); + JS_free(cx, mode); + mode = NULL; + + /* Set the open flag and return result */ + if (file->handle == NULL && file->nativehandle == NULL) { + file->isOpen = JS_FALSE; + + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", file->path); + goto out; + } + +good: + file->isOpen = JS_TRUE; + *rval = JSVAL_TRUE; + return JS_TRUE; + +out: + if(mode) + JS_free(cx, mode); + return JS_FALSE; +} + +static JSBool +file_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "close", file); + + if(!file->isOpen){ + JS_ReportWarning(cx, "File %s is not open, can't close it, proceeding", + file->path); + goto out; + } + + if(!file->isPipe){ + if(file->isNative){ + JS_ReportWarning(cx, "Unable to close a native file, proceeding", file->path); + goto out; + }else{ +#ifdef OSSP + if (file->handle && fclose(file->handle) != 0) { +#else + if(file->handle && PR_Close(file->handle)){ +#endif + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", file->path); + + goto out; + } + } + }else{ + if(PCLOSE(file->nativehandle)==-1){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "pclose", file->path); + goto out; + } + } + + js_ResetAttributes(file); + *rval = JSVAL_TRUE; + return JS_TRUE; + +out: + return JS_FALSE; +} + + +static JSBool +file_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "remove", file); + JSFILE_CHECK_NATIVE("remove"); + JSFILE_CHECK_CLOSED("remove"); + +#ifdef OSSP + if (remove(file->path) == 0) { +#else + if ((js_isDirectory(cx, file) ? + PR_RmDir(file->path) : PR_Delete(file->path))==PR_SUCCESS) { +#endif + js_ResetAttributes(file); + *rval = JSVAL_TRUE; + return JS_TRUE; + } else { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "remove", file->path); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +/* Raw PR-based function. No text processing. Just raw data copying. */ +static JSBool +file_copyTo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char *dest = NULL; +#ifdef OSSP + FILE *handle = NULL; +#else + PRFileDesc *handle = NULL; +#endif + char *buffer; + jsval count, size; + JSBool fileInitiallyOpen=JS_FALSE; + + SECURITY_CHECK(cx, NULL, "copyTo", file); /* may need a second argument!*/ + JSFILE_CHECK_ONE_ARG("copyTo"); + JSFILE_CHECK_NATIVE("copyTo"); + /* remeber the state */ + fileInitiallyOpen = file->isOpen; + JSFILE_CHECK_READ; + + dest = JS_GetStringBytes(JS_ValueToString(cx, argv[0])); + + /* make sure we are not reading a file open for writing */ + if (file->isOpen && !js_canRead(cx, file)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_COPY_FILE_OPEN_FOR_WRITING_ERROR, file->path); + goto out; + } + + if (file->handle==NULL){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", file->path); + goto out; + } + +#ifdef OSSP + { + int my_fd; + if ((my_fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) != -1) + handle = fdopen(my_fd, "w"); + } +#else + handle = PR_Open(dest, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0644); +#endif + + if(!handle){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", dest); + goto out; + } + + if ((size=js_size(cx, file))==JSVAL_VOID) { + goto out; + } + + buffer = JS_malloc(cx, size); + +#ifdef OSSP + count = INT_TO_JSVAL((int)fread(buffer, 1, (size_t)size, file->handle)); +#else + count = INT_TO_JSVAL(PR_Read(file->handle, buffer, size)); +#endif + + /* reading panic */ + if (count!=size) { + JS_free(cx, buffer); + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_COPY_READ_ERROR, file->path); + goto out; + } + +#ifdef OSSP + count = INT_TO_JSVAL((int)fwrite(buffer, 1, (size_t)JSVAL_TO_INT(size), handle)); +#else + count = INT_TO_JSVAL(PR_Write(handle, buffer, JSVAL_TO_INT(size))); +#endif + + /* writing panic */ + if (count!=size) { + JS_free(cx, buffer); + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_COPY_WRITE_ERROR, file->path); + goto out; + } + + JS_free(cx, buffer); + + if(!fileInitiallyOpen){ + if(!file_close(cx, obj, 0, NULL, rval)) goto out; + } + +#ifdef OSSP + if (fclose(handle) != 0) { +#else + if(PR_Close(handle)!=PR_SUCCESS){ +#endif + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", dest); + goto out; + } + + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + if(file->isOpen && !fileInitiallyOpen){ +#ifdef OSSP + if (fclose(file->handle) != 0) { +#else + if(PR_Close(file->handle)!=PR_SUCCESS){ +#endif + JS_ReportWarning(cx, "Can't close %s, proceeding", file->path); + } + } + +#ifdef OSSP + if (handle && fclose(handle) != 0) { +#else + if(handle && PR_Close(handle)!=PR_SUCCESS){ +#endif + JS_ReportWarning(cx, "Can't close %s, proceeding", dest); + } + + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_renameTo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char *dest; + + SECURITY_CHECK(cx, NULL, "renameTo", file); /* may need a second argument!*/ + JSFILE_CHECK_ONE_ARG("renameTo"); + JSFILE_CHECK_NATIVE("renameTo"); + JSFILE_CHECK_CLOSED("renameTo"); + + dest = RESOLVE_PATH(cx, JS_GetStringBytes(JS_ValueToString(cx, argv[0]))); + +#ifdef OSSP + if (rename(file->path, dest) == 0){ +#else + if (PR_Rename(file->path, dest)==PR_SUCCESS){ +#endif + /* copy the new filename */ + JS_free(cx, file->path); + file->path = dest; + *rval = JSVAL_TRUE; + return JS_TRUE; + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_RENAME_FAILED, file->path, dest); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_flush(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "flush", file); + JSFILE_CHECK_NATIVE("flush"); + JSFILE_CHECK_OPEN("flush"); + +#ifdef OSSP + if (fflush(file->handle) == 0){ +#else + if (PR_Sync(file->handle)==PR_SUCCESS){ +#endif + *rval = JSVAL_TRUE; + return JS_TRUE; + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "flush", file->path); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *str; + int32 count; + uintN i; + + SECURITY_CHECK(cx, NULL, "write", file); + JSFILE_CHECK_WRITE; + + for (i = 0; itype); + if (count==-1){ + *rval = JSVAL_FALSE; + return JS_FALSE; + } + } + + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *str; + + SECURITY_CHECK(cx, NULL, "writeln", file); + JSFILE_CHECK_WRITE; + + /* don't report an error here */ + if(!file_write(cx, obj, argc, argv, rval)) return JS_FALSE; + /* don't do security here -- we passed the check in file_write */ + str = JS_NewStringCopyZ(cx, "\n"); + + if (js_FileWrite(cx, file, JS_GetStringChars(str), JS_GetStringLength(str), + file->type)==-1){ + *rval = JSVAL_FALSE; + return JS_FALSE; + } + + /* eol causes flush if hasAutoflush is turned on */ + if (file->hasAutoflush) + file_flush(cx, obj, 0, NULL, rval); + + *rval = JSVAL_TRUE; + return JS_TRUE; +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_writeAll(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + jsuint i; + jsuint limit; + JSObject *array; + JSObject *elem; + jsval elemval; + + SECURITY_CHECK(cx, NULL, "writeAll", file); + JSFILE_CHECK_ONE_ARG("writeAll"); + JSFILE_CHECK_WRITE; + + if (!JS_IsArrayObject(cx, JSVAL_TO_OBJECT(argv[0]))) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_WRITEALL_NOT_ARRAY_ERROR); + goto out; + } + + array = JSVAL_TO_OBJECT(argv[0]); + + JS_GetArrayLength(cx, array, &limit); + + for (i = 0; i262144)?262144:want; * arbitrary size limitation */ + + buf = JS_malloc(cx, want*sizeof buf[0]); + if (!buf) goto out; + + count = js_FileRead(cx, file, buf, want, file->type); + if (count>0) { + str = JS_NewUCStringCopyN(cx, buf, count); + *rval = STRING_TO_JSVAL(str); + JS_free(cx, buf); + return JS_TRUE; + } else { + JS_free(cx, buf); + goto out; + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSString *str; + jschar *buf = NULL, *tmp; + int32 offset, read; + intN room; + jschar data, data2; + + SECURITY_CHECK(cx, NULL, "readln", file); + JSFILE_CHECK_READ; + + buf = JS_malloc(cx, MAX_LINE_LENGTH * sizeof data); + if (!buf) + return JS_FALSE; + + room = MAX_LINE_LENGTH - 1; + offset = 0; + + for (;;) { + read = js_FileRead(cx, file, &data, 1, file->type); + if (read < 0) + goto out; + if (read == 0) + goto eof; + + switch (data) { + case '\r': + read = js_FileRead(cx, file, &data2, 1, file->type); + if (read < 0) + goto out; + + if (read == 1 && data2 != '\n') { + /* We read one char too far. Buffer it. */ + file->charBuffer = data2; + file->charBufferUsed = JS_TRUE; + } + + /* Fall through. */ + case '\n': + goto done; + + default: + if (--room < 0) { + tmp = JS_realloc(cx, buf, + (offset + MAX_LINE_LENGTH) * sizeof data); + if (!tmp) + goto out; + + room = MAX_LINE_LENGTH - 1; + buf = tmp; + } + + buf[offset++] = data; + break; + } + } + +eof: + if (offset == 0) { + *rval = JSVAL_NULL; + return JS_TRUE; + } + +done: + buf[offset] = 0; + tmp = JS_realloc(cx, buf, (offset + 1) * sizeof data); + if (!tmp) + goto out; + + str = JS_NewUCString(cx, tmp, offset); + if (!str) + goto out; + + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; + +out: + if (buf) + JS_free(cx, buf); + + return JS_FALSE; +} + +static JSBool +file_readAll(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSObject *array; + jsint len; + jsval line; + JSBool lineok = JS_FALSE; + + SECURITY_CHECK(cx, NULL, "readAll", file); + JSFILE_CHECK_READ; + + array = JS_NewArrayObject(cx, 0, NULL); + if (!array) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(array); + + len = 0; + + lineok = file_readln(cx, obj, 0, NULL, &line); + while (lineok && !JSVAL_IS_NULL(line)) { + JS_SetElement(cx, array, len++, &line); + lineok = file_readln(cx, obj, 0, NULL, &line); + } + +out: + return lineok; +} + +static JSBool +file_seek(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + int32 toskip; + int32 pos; + + SECURITY_CHECK(cx, NULL, "seek", file); + JSFILE_CHECK_ONE_ARG("seek"); + JSFILE_CHECK_NATIVE("seek"); + JSFILE_CHECK_READ; + + if (!JS_ValueToInt32(cx, argv[0], &toskip)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_MUST_BE_A_NUMBER, "seek", argv[0]); + goto out; + } + + if(!file->hasRandomAccess){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_NO_RANDOM_ACCESS, file->path); + goto out; + } + + if(js_isDirectory(cx, file)){ + JS_ReportWarning(cx,"Seek on directories is not supported, proceeding"); + goto out; + } + + pos = js_FileSeek(cx, file, toskip, file->type); + + if (pos!=-1) { + *rval = INT_TO_JSVAL(pos); + return JS_TRUE; + } +out: + *rval = JSVAL_VOID; + return JS_FALSE; +} + +static JSBool +file_list(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ +#ifdef OSSP + DIR *dir; + struct dirent *entry; +#else + PRDir *dir; + PRDirEntry *entry; +#endif + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + JSObject *array; + JSObject *eachFile; + jsint len; + jsval v; + JSRegExp *re = NULL; + JSFunction *func = NULL; + JSString *str; + jsval args[1]; + char *filePath; + + SECURITY_CHECK(cx, NULL, "list", file); + JSFILE_CHECK_NATIVE("list"); + + if (argc==1) { + if (JSVAL_IS_REGEXP(cx, argv[0])) { + re = JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[0])); + }else + if (JSVAL_IS_FUNCTION(cx, argv[0])) { + func = JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[0])); + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_MUST_BE_A_FUNCTION_OR_REGEX, argv[0]); + goto out; + } + } + + if (!js_isDirectory(cx, file)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_DO_LIST_ON_A_FILE, file->path); + goto out; + } + +#ifdef OSSP + dir = opendir(file->path); +#else + dir = PR_OpenDir(file->path); +#endif + if(!dir){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "open", file->path); + goto out; + } + + /* create JSArray here... */ + array = JS_NewArrayObject(cx, 0, NULL); + len = 0; + +#ifdef OSSP + while ((entry = readdir(dir))!=NULL) { +#else + while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH))!=NULL) { +#endif +#ifdef OSSP + if ( strcmp(entry->d_name, ".") == 0 + || strcmp(entry->d_name, "..") == 0) + continue; +#endif + /* first, check if we have a regexp */ + if (re!=NULL) { + size_t index = 0; + +#ifdef OSSP + str = JS_NewStringCopyZ(cx, entry->d_name); +#else + str = JS_NewStringCopyZ(cx, entry->name); +#endif + if(!js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, &v)){ + /* don't report anything here */ + goto out; + } + /* not matched! */ + if (JSVAL_IS_NULL(v)) { + continue; + } + }else + if (func!=NULL) { +#ifdef OSSP + str = JS_NewStringCopyZ(cx, entry->d_name); +#else + str = JS_NewStringCopyZ(cx, entry->name); +#endif + args[0] = STRING_TO_JSVAL(str); + if(!JS_CallFunction(cx, obj, func, 1, args, &v)){ + goto out; + } + + if (v==JSVAL_FALSE) { + continue; + } + } + +#ifdef OSSP + filePath = js_combinePath(cx, file->path, (char*)entry->d_name); +#else + filePath = js_combinePath(cx, file->path, (char*)entry->name); +#endif + + eachFile = js_NewFileObject(cx, filePath); + JS_free(cx, filePath); + if (!eachFile){ + JS_ReportWarning(cx, "File %s cannot be retrieved", filePath); + continue; + } + v = OBJECT_TO_JSVAL(eachFile); + JS_SetElement(cx, array, len, &v); +#ifdef OSSP + JS_SetProperty(cx, array, entry->d_name, &v); +#else + JS_SetProperty(cx, array, entry->name, &v); +#endif + len++; + } + +#ifdef OSSP + if (closedir(dir) != 0) { +#else + if(PR_CloseDir(dir)!=PR_SUCCESS){ +#endif + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", file->path); + goto out; + } + *rval = OBJECT_TO_JSVAL(array); + return JS_TRUE; +out: + *rval = JSVAL_NULL; + return JS_FALSE; +} + +static JSBool +file_mkdir(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + SECURITY_CHECK(cx, NULL, "mkdir", file); + JSFILE_CHECK_ONE_ARG("mkdir"); + JSFILE_CHECK_NATIVE("mkdir"); + + /* if the current file is not a directory, find out the directory name */ + if (!js_isDirectory(cx, file)) { + char *dir = js_fileDirectoryName(cx, file->path); + JSObject *dirObj = js_NewFileObject(cx, dir); + + JS_free(cx, dir); + + /* call file_mkdir with the right set of parameters if needed */ + if (file_mkdir(cx, dirObj, argc, argv, rval)) + return JS_TRUE; + else + goto out; + }else{ + char *dirName = JS_GetStringBytes(JS_ValueToString(cx, argv[0])); + char *fullName; + + fullName = js_combinePath(cx, file->path, dirName); +#ifdef OSSP + if (mkdir(fullName, 0755) == 0) { +#else + if (PR_MkDir(fullName, 0755)==PR_SUCCESS){ +#endif + *rval = JSVAL_TRUE; + JS_free(cx, fullName); + return JS_TRUE; + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "mkdir", fullName); + JS_free(cx, fullName); + goto out; + } + } +out: + *rval = JSVAL_FALSE; + return JS_FALSE; +} + +static JSBool +file_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval*rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, file->path)); + return JS_TRUE; +} + +static JSBool +file_toURL(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char url[MAX_PATH_LENGTH]; + jschar *urlChars; + + JSFILE_CHECK_NATIVE("toURL"); + + sprintf(url, "file://%s", file->path); + /* TODO: js_escape in jsstr.h may go away at some point */ + + urlChars = js_InflateString(cx, url, strlen(url)); + if (urlChars == NULL) return JS_FALSE; + *rval = STRING_TO_JSVAL(js_NewString(cx, urlChars, strlen(url), 0)); + if (!js_str_escape(cx, obj, 0, rval, rval)) return JS_FALSE; + + return JS_TRUE; +out: + *rval = JSVAL_VOID; + return JS_FALSE; +} + + +static void +file_finalize(JSContext *cx, JSObject *obj) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + if(file) { + /* Close the file before exiting. */ + if(file->isOpen && !file->isNative) { + jsval vp; + file_close(cx, obj, 0, NULL, &vp); + } + + if (file->path) + JS_free(cx, file->path); + + JS_free(cx, file); + } +} + +/* + Allocates memory for the file object, sets fields to defaults. +*/ +static JSFile* +file_init(JSContext *cx, JSObject *obj, char *bytes) +{ + JSFile *file; + + file = JS_malloc(cx, sizeof *file); + if (!file) + return NULL; + memset(file, 0 , sizeof *file); + + js_ResetAttributes(file); + + file->path = RESOLVE_PATH(cx, bytes); + + if (!JS_SetPrivate(cx, obj, file)) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_SET_PRIVATE_FILE, file->path); + JS_free(cx, file); + return NULL; + } + + return file; +} + +/* Returns a JSObject. This function is globally visible */ +JS_PUBLIC_API(JSObject*) +js_NewFileObject(JSContext *cx, char *filename) +{ + JSObject *obj; + JSFile *file; + + obj = JS_NewObject(cx, &file_class, NULL, NULL); + if (!obj){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OBJECT_CREATION_FAILED, "js_NewFileObject"); + return NULL; + } + file = file_init(cx, obj, filename); + if(!file) return NULL; + return obj; +} + +/* Internal function, used for cases which NSPR file support doesn't cover */ +JSObject* +js_NewFileObjectFromFILE(JSContext *cx, FILE *nativehandle, char *filename, + int32 mode, JSBool open, JSBool randomAccess) +{ + JSObject *obj; + JSFile *file; + + obj = JS_NewObject(cx, &file_class, NULL, NULL); + if (!obj){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OBJECT_CREATION_FAILED, "js_NewFileObjectFromFILE"); + return NULL; + } + file = file_init(cx, obj, filename); + if(!file) return NULL; + + file->nativehandle = nativehandle; + + /* free result of RESOLVE_PATH from file_init. */ + JS_ASSERT(file->path != NULL); + JS_free(cx, file->path); + + file->path = strdup(filename); + file->isOpen = open; + file->mode = mode; + file->hasRandomAccess = randomAccess; + file->isNative = JS_TRUE; + return obj; +} + +/* + Real file constructor that is called from JavaScript. + Basically, does error processing and calls file_init. +*/ +static JSBool +file_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + JSFile *file; + + if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { + /* Replace obj with a new File object. */ + obj = JS_NewObject(cx, &file_class, NULL, NULL); + if (!obj) + return JS_FALSE; + *rval = OBJECT_TO_JSVAL(obj); + } + + str = (argc == 0) + ? JS_InternString(cx, "") + : JS_ValueToString(cx, argv[0]); + + if (!str) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_CONSTRUCTOR_NOT_STRING_ERROR, + argv[0]); + return JS_FALSE; + } + + file = file_init(cx, obj, JS_GetStringBytes(str)); + if (!file) + return JS_FALSE; + + SECURITY_CHECK(cx, NULL, "constructor", file); + + return JS_TRUE; +} + +/* -------------------- File methods and properties ------------------------- */ +static JSFunctionSpec file_functions[] = { + { "open", file_open, 0}, + { "close", file_close, 0}, + { "remove", file_remove, 0}, + { "copyTo", file_copyTo, 0}, + { "renameTo", file_renameTo, 0}, + { "flush", file_flush, 0}, + { "seek", file_seek, 0}, + { "read", file_read, 0}, + { "readln", file_readln, 0}, + { "readAll", file_readAll, 0}, + { "write", file_write, 0}, + { "writeln", file_writeln, 0}, + { "writeAll", file_writeAll, 0}, + { "list", file_list, 0}, + { "mkdir", file_mkdir, 0}, + { "toString", file_toString, 0}, + { "toURL", file_toURL, 0}, + {0} +}; + +enum file_tinyid { + FILE_LENGTH = -2, + FILE_PARENT = -3, + FILE_PATH = -4, + FILE_NAME = -5, + FILE_ISDIR = -6, + FILE_ISFILE = -7, + FILE_EXISTS = -8, + FILE_CANREAD = -9, + FILE_CANWRITE = -10, + FILE_OPEN = -11, + FILE_TYPE = -12, + FILE_MODE = -13, + FILE_CREATED = -14, + FILE_MODIFIED = -15, + FILE_SIZE = -16, + FILE_RANDOMACCESS = -17, + FILE_POSITION = -18, + FILE_APPEND = -19, + FILE_REPLACE = -20, + FILE_AUTOFLUSH = -21, + FILE_ISNATIVE = -22, +}; + +static JSPropertySpec file_props[] = { + {"length", FILE_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"parent", FILE_PARENT, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"path", FILE_PATH, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"name", FILE_NAME, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"isDirectory", FILE_ISDIR, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"isFile", FILE_ISFILE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"exists", FILE_EXISTS, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canRead", FILE_CANREAD, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canWrite", FILE_CANWRITE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canAppend", FILE_APPEND, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"canReplace", FILE_REPLACE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"isOpen", FILE_OPEN, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"type", FILE_TYPE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"mode", FILE_MODE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"creationTime", FILE_CREATED, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"lastModified", FILE_MODIFIED, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"size", FILE_SIZE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"hasRandomAccess", FILE_RANDOMACCESS, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"hasAutoFlush", FILE_AUTOFLUSH, JSPROP_ENUMERATE | JSPROP_READONLY }, + {"position", FILE_POSITION, JSPROP_ENUMERATE }, + {"isNative", FILE_ISNATIVE, JSPROP_ENUMERATE | JSPROP_READONLY }, + {0} +}; + +/* ------------------------- Property getter/setter ------------------------- */ +static JSBool +file_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + char *bytes; + JSString *str; + jsint tiny; +#ifdef OSSP + struct stat info; +#else + PRFileInfo info; +#endif + JSBool flag; +#ifdef OSSP + struct tm *tm; + time_t t; +#else + PRExplodedTime expandedTime; +#endif + + tiny = JSVAL_TO_INT(id); + if (!file) + return JS_TRUE; + + switch (tiny) { + case FILE_PARENT: + SECURITY_CHECK(cx, NULL, "parent", file); + if (!js_parent(cx, file, vp)) + return JS_FALSE; + break; + case FILE_PATH: + str = JS_NewStringCopyZ(cx, file->path); + if (!str) + return JS_FALSE; + *vp = STRING_TO_JSVAL(str); + break; + case FILE_NAME: + if (!js_name(cx, file, vp)) + return JS_FALSE; + break; + case FILE_ISDIR: + SECURITY_CHECK(cx, NULL, "isDirectory", file); + *vp = BOOLEAN_TO_JSVAL(js_isDirectory(cx, file)); + break; + case FILE_ISFILE: + SECURITY_CHECK(cx, NULL, "isFile", file); + *vp = BOOLEAN_TO_JSVAL(js_isFile(cx, file)); + break; + case FILE_EXISTS: + SECURITY_CHECK(cx, NULL, "exists", file); + *vp = BOOLEAN_TO_JSVAL(js_exists(cx, file)); + break; + case FILE_ISNATIVE: + SECURITY_CHECK(cx, NULL, "isNative", file); + *vp = BOOLEAN_TO_JSVAL(file->isNative); + break; + case FILE_CANREAD: + SECURITY_CHECK(cx, NULL, "canRead", file); + *vp = BOOLEAN_TO_JSVAL(js_canRead(cx, file)); + break; + case FILE_CANWRITE: + SECURITY_CHECK(cx, NULL, "canWrite", file); + *vp = BOOLEAN_TO_JSVAL(js_canWrite(cx, file)); + break; + case FILE_OPEN: + SECURITY_CHECK(cx, NULL, "isOpen", file); + *vp = BOOLEAN_TO_JSVAL(file->isOpen); + break; + case FILE_APPEND : + SECURITY_CHECK(cx, NULL, "canAppend", file); + JSFILE_CHECK_OPEN("canAppend"); + *vp = BOOLEAN_TO_JSVAL(!file->isNative && + (file->mode&PR_APPEND)==PR_APPEND); + break; + case FILE_REPLACE : + SECURITY_CHECK(cx, NULL, "canReplace", file); + JSFILE_CHECK_OPEN("canReplace"); + *vp = BOOLEAN_TO_JSVAL(!file->isNative && + (file->mode&PR_TRUNCATE)==PR_TRUNCATE); + break; + case FILE_AUTOFLUSH : + SECURITY_CHECK(cx, NULL, "hasAutoFlush", file); + JSFILE_CHECK_OPEN("hasAutoFlush"); + *vp = BOOLEAN_TO_JSVAL(!file->isNative && file->hasAutoflush); + break; + case FILE_TYPE: + SECURITY_CHECK(cx, NULL, "type", file); + JSFILE_CHECK_OPEN("type"); + if(js_isDirectory(cx, file)){ + *vp = JSVAL_VOID; + break; + } + + switch (file->type) { + case ASCII: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, asciistring)); + break; + case UTF8: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, utfstring)); + break; + case UCS2: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, unicodestring)); + break; + default: + JS_ReportWarning(cx, "Unsupported file type %d, proceeding", + file->type); + } + break; + case FILE_MODE: + SECURITY_CHECK(cx, NULL, "mode", file); + JSFILE_CHECK_OPEN("mode"); + bytes = JS_malloc(cx, MODE_SIZE); + bytes[0] = '\0'; + flag = JS_FALSE; + + if ((file->mode&PR_RDONLY)==PR_RDONLY) { + if (flag) strcat(bytes, ","); + strcat(bytes, "read"); + flag = JS_TRUE; + } + if ((file->mode&PR_WRONLY)==PR_WRONLY) { + if (flag) strcat(bytes, ","); + strcat(bytes, "write"); + flag = JS_TRUE; + } + if ((file->mode&PR_RDWR)==PR_RDWR) { + if (flag) strcat(bytes, ","); + strcat(bytes, "readWrite"); + flag = JS_TRUE; + } + if ((file->mode&PR_APPEND)==PR_APPEND) { + if (flag) strcat(bytes, ","); + strcat(bytes, "append"); + flag = JS_TRUE; + } + if ((file->mode&PR_CREATE_FILE)==PR_CREATE_FILE) { + if (flag) strcat(bytes, ","); + strcat(bytes, "create"); + flag = JS_TRUE; + } + if ((file->mode&PR_TRUNCATE)==PR_TRUNCATE) { + if (flag) strcat(bytes, ","); + strcat(bytes, "replace"); + flag = JS_TRUE; + } + if (file->hasAutoflush) { + if (flag) strcat(bytes, ","); + strcat(bytes, "hasAutoFlush"); + flag = JS_TRUE; + } + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, bytes)); + JS_free(cx, bytes); + break; + case FILE_CREATED: + SECURITY_CHECK(cx, NULL, "creationTime", file); + JSFILE_CHECK_NATIVE("creationTime"); +#ifdef OSSP + if (file->isOpen + ? fstat(fileno(file->handle), &info) != 0 + : stat(file->path, &info) != 0) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + goto out; + } + + t = (time_t)(info.st_birthtime); + tm = localtime(&t); + *vp = OBJECT_TO_JSVAL(js_NewDateObject(cx, tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec)); +#else + if(((file->isOpen)? + PR_GetOpenFileInfo(file->handle, &info): + PR_GetFileInfo(file->path, &info))!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + goto out; + } + + PR_ExplodeTime(info.creationTime, PR_LocalTimeParameters,&expandedTime); + *vp = OBJECT_TO_JSVAL(js_NewDateObject(cx, expandedTime.tm_year, + expandedTime.tm_month, + expandedTime.tm_mday, + expandedTime.tm_hour, + expandedTime.tm_min, + expandedTime.tm_sec)); +#endif + break; + case FILE_MODIFIED: + SECURITY_CHECK(cx, NULL, "lastModified", file); + JSFILE_CHECK_NATIVE("lastModified"); +#ifdef OSSP + if (file->isOpen + ? fstat(fileno(file->handle), &info) != 0 + : stat(file->path, &info) != 0) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + goto out; + } + + t = (time_t)(info.st_mtime); + tm = localtime(&t); + *vp = OBJECT_TO_JSVAL(js_NewDateObject(cx, tm->tm_year + 1900, + tm->tm_mon + 1, + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec)); +#else + if(((file->isOpen)? + PR_GetOpenFileInfo(file->handle, &info): + PR_GetFileInfo(file->path, &info))!=PR_SUCCESS){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_ACCESS_FILE_STATUS, file->path); + goto out; + } + + PR_ExplodeTime(info.modifyTime, PR_LocalTimeParameters, &expandedTime); + *vp = OBJECT_TO_JSVAL(js_NewDateObject(cx, expandedTime.tm_year, + expandedTime.tm_month, + expandedTime.tm_mday, + expandedTime.tm_hour, + expandedTime.tm_min, + expandedTime.tm_sec)); +#endif + break; + case FILE_SIZE: + SECURITY_CHECK(cx, NULL, "size", file); + *vp = js_size(cx, file); + break; + case FILE_LENGTH: + SECURITY_CHECK(cx, NULL, "length", file); + JSFILE_CHECK_NATIVE("length"); + + if (js_isDirectory(cx, file)) { /* XXX debug me */ +#ifdef OSSP + DIR *dir; + struct dirent *entry; +#else + PRDir *dir; + PRDirEntry *entry; +#endif + jsint count = 0; + +#ifdef OSSP + if(!(dir = opendir(file->path))){ +#else + if(!(dir = PR_OpenDir(file->path))){ +#endif + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_OPEN_DIR, file->path); + goto out; + } + +#ifdef OSSP + while ((entry = readdir(dir))) { + if ( strcmp(entry->d_name, ".") == 0 + || strcmp(entry->d_name, "..") == 0) + continue; + +#else + while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH))) { +#endif + count++; + } + +#ifdef OSSP + if(closedir(dir) != 0){ +#else + if(!PR_CloseDir(dir)){ +#endif + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_OP_FAILED, "close", file->path); + + goto out; + } + + *vp = INT_TO_JSVAL(count); + break; + }else{ + /* return file size */ + *vp = js_size(cx, file); + } + break; + case FILE_RANDOMACCESS: + SECURITY_CHECK(cx, NULL, "hasRandomAccess", file); + JSFILE_CHECK_OPEN("hasRandomAccess"); + *vp = BOOLEAN_TO_JSVAL(file->hasRandomAccess); + break; + case FILE_POSITION: + SECURITY_CHECK(cx, NULL, "position", file); + JSFILE_CHECK_NATIVE("position"); + JSFILE_CHECK_OPEN("position"); + + if(!file->hasRandomAccess){ + JS_ReportWarning(cx, "File %s doesn't support random access, can't report the position, proceeding"); + *vp = JSVAL_VOID; + break; + } + + if (file->isOpen && js_isFile(cx, file)) { +#ifdef OSSP + int pos = fseek(file->handle, 0, SEEK_CUR); +#else + int pos = PR_Seek(file->handle, 0, PR_SEEK_CUR); +#endif + if(pos!=-1){ + *vp = INT_TO_JSVAL(pos); + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_REPORT_POSITION, file->path); + goto out; + } + }else { + JS_ReportWarning(cx, "File %s is closed or not a plain file," + " can't report position, proceeding"); + goto out; + } + break; + default: + SECURITY_CHECK(cx, NULL, "file_access", file); + + /* this is some other property -- try to use the dir["file"] syntax */ + if (js_isDirectory(cx, file)) { +#ifdef OSSP + DIR *dir = NULL; + struct dirent *entry = NULL; +#else + PRDir *dir = NULL; + PRDirEntry *entry = NULL; +#endif + char *prop_name; + + str = JS_ValueToString(cx, id); + if (!str) + return JS_FALSE; + + prop_name = JS_GetStringBytes(str); + + /* no native files past this point */ +#ifdef OSSP + dir = opendir(file->path); +#else + dir = PR_OpenDir(file->path); +#endif + if(!dir) { + /* This is probably not a directory */ + JS_ReportWarning(cx, "Can't open directory %s", file->path); + return JS_FALSE; + } + +#ifdef OSSP + while ((entry = readdir(dir)) != NULL) { + if ( strcmp(entry->d_name, ".") == 0 + || strcmp(entry->d_name, "..") == 0) + continue; + if (!strcmp(entry->d_name, prop_name)){ +#else + while ((entry = PR_ReadDir(dir, PR_SKIP_NONE)) != NULL) { + if (!strcmp(entry->name, prop_name)){ +#endif + bytes = js_combinePath(cx, file->path, prop_name); + *vp = OBJECT_TO_JSVAL(js_NewFileObject(cx, bytes)); + JS_free(cx, bytes); +#ifdef OSSP + closedir(dir); +#else + PR_CloseDir(dir); /* XXX: bugfix */ +#endif + return JS_TRUE; + } + } +#ifdef OSSP + closedir(dir); +#else + PR_CloseDir(dir); /* XXX: bugfix */ +#endif + } + } + return JS_TRUE; + +out: + return JS_FALSE; +} + +static JSBool +file_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSFile *file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + jsint slot; + + if (JSVAL_IS_STRING(id)){ + return JS_TRUE; + } + + slot = JSVAL_TO_INT(id); + + switch (slot) { + /* File.position = 10 */ + case FILE_POSITION: + SECURITY_CHECK(cx, NULL, "set_position", file); + JSFILE_CHECK_NATIVE("set_position"); + + if(!file->hasRandomAccess){ + JS_ReportWarning(cx, "File %s doesn't support random access, can't " + "report the position, proceeding"); + goto out; + } + + if (file->isOpen && js_isFile(cx, file)) { + int32 pos; + int32 offset; + + if (!JS_ValueToInt32(cx, *vp, &offset)){ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_FIRST_ARGUMENT_MUST_BE_A_NUMBER, "position", *vp); + goto out; + } + +#ifdef OSSP + pos = fseek(file->handle, offset, SEEK_SET); +#else + pos = PR_Seek(file->handle, offset, PR_SEEK_SET); +#endif + + if(pos!=-1){ + *vp = INT_TO_JSVAL(pos); + }else{ + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_CANNOT_SET_POSITION, file->path); + goto out; + } + } else { + JS_ReportWarning(cx, "File %s is closed or not a file, can't set " + "position, proceeding", file->path); + goto out; + } + } + + return JS_TRUE; +out: + return JS_FALSE; +} + +/* + File.currentDir = new File("D:\") or File.currentDir = "D:\" +*/ +static JSBool +file_currentDirSetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSFile *file; + + file = JS_GetInstancePrivate(cx, obj, &file_class, NULL); + + /* Look at the rhs and extract a file object from it */ + if (JSVAL_IS_OBJECT(*vp)) { + if (JS_InstanceOf(cx, obj, &file_class, NULL)) { + /* Braindamaged rhs -- just return the old value */ + if (file && (!js_exists(cx, file) || !js_isDirectory(cx, file))) { + JS_GetProperty(cx, obj, CURRENTDIR_PROPERTY, vp); + return JS_FALSE; + } else { + chdir(file->path); + return JS_TRUE; + } + } else { + return JS_FALSE; + } + } else { + JSObject *rhsObject; + char *path; + + path = JS_GetStringBytes(JS_ValueToString(cx, *vp)); + rhsObject = js_NewFileObject(cx, path); + if (!rhsObject) + return JS_FALSE; + + if (!file || !js_exists(cx, file) || !js_isDirectory(cx, file)){ + JS_GetProperty(cx, obj, CURRENTDIR_PROPERTY, vp); + } else { + *vp = OBJECT_TO_JSVAL(rhsObject); + chdir(path); + } + } + + return JS_TRUE; +} + +/* Declare class */ +static JSClass file_class = { + FILE_CONSTRUCTOR, JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, file_getProperty, file_setProperty, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, file_finalize +}; + +/* -------------------- Functions exposed to the outside -------------------- */ +JS_PUBLIC_API(JSObject*) +js_InitFileClass(JSContext *cx, JSObject* obj) +{ + JSObject *file, *ctor, *afile; + jsval vp; + char *currentdir; + char separator[2]; + + file = JS_InitClass(cx, obj, NULL, &file_class, file_constructor, 1, + file_props, file_functions, NULL, NULL); + if (!file) { + JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL, + JSFILEMSG_INIT_FAILED); + return NULL; + } + + ctor = JS_GetConstructor(cx, file); + if (!ctor) return NULL; + + /* Define CURRENTDIR property. We are doing this to get a + slash at the end of the current dir */ + afile = js_NewFileObject(cx, CURRENT_DIR); + currentdir = JS_malloc(cx, MAX_PATH_LENGTH); + currentdir = getcwd(currentdir, MAX_PATH_LENGTH); + afile = js_NewFileObject(cx, currentdir); + JS_free(cx, currentdir); + vp = OBJECT_TO_JSVAL(afile); + JS_DefinePropertyWithTinyId(cx, ctor, CURRENTDIR_PROPERTY, 0, vp, + JS_PropertyStub, file_currentDirSetter, + JSPROP_ENUMERATE | JSPROP_READONLY ); + + /* Define input */ + vp = OBJECT_TO_JSVAL(js_NewFileObjectFromFILE(cx, stdin, + STDINPUT_NAME, PR_RDONLY, JS_TRUE, JS_FALSE)); + JS_SetProperty(cx, ctor, "input", &vp); + + /* Define output */ + vp = OBJECT_TO_JSVAL(js_NewFileObjectFromFILE(cx, stdout, + STDOUTPUT_NAME, PR_WRONLY, JS_TRUE, JS_FALSE)); + JS_SetProperty(cx, ctor, "output", &vp); + + /* Define error */ + vp = OBJECT_TO_JSVAL(js_NewFileObjectFromFILE(cx, stderr, + STDERROR_NAME, PR_WRONLY, JS_TRUE, JS_FALSE)); + JS_SetProperty(cx, ctor, "error", &vp); + + separator[0] = FILESEPARATOR; + separator[1] = '\0'; + vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, separator)); + JS_DefinePropertyWithTinyId(cx, ctor, SEPARATOR_PROPERTY, 0, vp, + JS_PropertyStub, JS_PropertyStub, + JSPROP_ENUMERATE | JSPROP_READONLY ); + return file; +} +#endif /* JS_HAS_FILE_OBJECT */