pjs/js/src/jsfile.c

2736 строки
80 KiB
C

/* -*- 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
#include "jsstddef.h"
#include "jsfile.h"
/* ----------------- Platform-specific includes and defines ----------------- */
#if defined(XP_WIN) || defined(XP_OS2)
# include <direct.h>
# include <io.h>
# include <sys/types.h>
# include <sys/stat.h>
# define FILESEPARATOR '\\'
# define FILESEPARATOR2 '/'
# define CURRENT_DIR "c:\\"
# define POPEN _popen
# define PCLOSE _pclose
#elif defined(XP_UNIX) || defined(XP_BEOS)
# include <strings.h>
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# 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 <string.h>
/* NSPR dependencies */
#include "prio.h"
#include "prerror.h"
#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 */
PRFileDesc *handle; /* the handle for the file, if open. */
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... */
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, unsigned 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) {
count += (!f->isNative)
? PR_Read(f->handle, buf, len)
: fread(buf, 1, len, f->nativehandle);
}
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;count<len;count++) {
i = js_BufferedRead(file, utfbuf+remainder, 3-remainder);
if (i<=0) {
return count;
}
i = utf8_to_ucs2_char(utfbuf, (int16)i, &buf[count] );
if (i<0) {
return count;
} else {
if (i==1) {
utfbuf[0] = utfbuf[1];
utfbuf[1] = utfbuf[2];
remainder = 2;
} else if (i==2) {
utfbuf[0] = utfbuf[2];
remainder = 1;
} else if (i==3) {
remainder = 0;
}
}
}
while (remainder>0) {
file->byteBuffer[file->nbBytesInBuf] = utfbuf[0];
file->nbBytesInBuf++;
utfbuf[0] = utfbuf[1];
utfbuf[1] = utfbuf[2];
remainder--;
}
break;
case UCS2:
count = js_BufferedRead(file, (unsigned 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:
count = PR_Seek(file->handle, len, PR_SEEK_CUR);
break;
case UTF8:
remainder = 0;
for (count = 0;count<len;count++) {
i = js_BufferedRead(file, utfbuf+remainder, 3-remainder);
if (i<=0) {
return 0;
}
i = utf8_to_ucs2_char(utfbuf, (int16)i, &tmp );
if (i<0) {
return 0;
} else {
if (i==1) {
utfbuf[0] = utfbuf[1];
utfbuf[1] = utfbuf[2];
remainder = 2;
} else if (i==2) {
utfbuf[0] = utfbuf[2];
remainder = 1;
} else if (i==3) {
remainder = 0;
}
}
}
while (remainder>0) {
file->byteBuffer[file->nbBytesInBuf] = utfbuf[0];
file->nbBytesInBuf++;
utfbuf[0] = utfbuf[1];
utfbuf[1] = utfbuf[2];
remainder--;
}
break;
case UCS2:
count = PR_Seek(file->handle, len*2, PR_SEEK_CUR)/2;
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; i<len; i++)
aux[i] = buf[i] % 256;
count = (!file->isNative)
? PR_Write(file->handle, aux, len)
: fwrite(aux, 1, len, file->nativehandle);
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;count<len;count++) {
j = one_ucs2_to_utf8_char(utfbuf+i, utfbuf+len*3, buf[count]);
if (j==-1) {
JS_free(cx, utfbuf);
return 0;
}
i+=j;
}
j = (!file->isNative)
? PR_Write(file->handle, utfbuf, i)
: fwrite(utfbuf, 1, i, file->nativehandle);
if (j<i) {
JS_free(cx, utfbuf);
return 0;
}
JS_free(cx, utfbuf);
break;
case UCS2:
count = (!file->isNative)
? PR_Write(file->handle, buf, len*2) >> 1
: fwrite(buf, 1, len*2, file->nativehandle) >> 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, "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;
}
return PR_Access(file->path, PR_ACCESS_EXISTS) == PR_SUCCESS;
}
static JSBool
js_canRead(JSContext *cx, JSFile *file)
{
if (!file->isNative) {
if (file->isOpen && !(file->mode & PR_RDONLY))
return JS_FALSE;
return PR_Access(file->path, PR_ACCESS_READ_OK) == PR_SUCCESS;
}
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;
return PR_Access(file->path, PR_ACCESS_WRITE_OK) == PR_SUCCESS;
}
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) {
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;
}
/* This doesn't make sense for a pipe of stdstream. */
return JS_FALSE;
}
static JSBool
js_isDirectory(JSContext *cx, JSFile *file)
{
if(!file->isNative){
PRFileInfo info;
/* Hack needed to get get_property to work. */
if (!js_exists(cx, file))
return JS_FALSE;
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;
}
/* This doesn't make sense for a pipe of stdstream. */
return JS_FALSE;
}
static jsval
js_size(JSContext *cx, JSFile *file)
{
PRFileInfo info;
JSFILE_CHECK_NATIVE("size");
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 JSVAL_VOID;
}
return INT_TO_JSVAL(info.size);
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, &js_FileClass, 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... */
file->handle = PR_Open(file->path, mask, 0644);
}
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, &js_FileClass, 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{
if(file->handle && PR_Close(file->handle)){
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, &js_FileClass, NULL);
SECURITY_CHECK(cx, NULL, "remove", file);
JSFILE_CHECK_NATIVE("remove");
JSFILE_CHECK_CLOSED("remove");
if ((js_isDirectory(cx, file) ?
PR_RmDir(file->path) : PR_Delete(file->path))==PR_SUCCESS) {
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, &js_FileClass, NULL);
char *dest = NULL;
PRFileDesc *handle = NULL;
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;
}
handle = PR_Open(dest, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0644);
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);
count = INT_TO_JSVAL(PR_Read(file->handle, buffer, size));
/* reading panic */
if (count!=size) {
JS_free(cx, buffer);
JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL,
JSFILEMSG_COPY_READ_ERROR, file->path);
goto out;
}
count = INT_TO_JSVAL(PR_Write(handle, buffer, JSVAL_TO_INT(size)));
/* 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;
}
if(PR_Close(handle)!=PR_SUCCESS){
JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL,
JSFILEMSG_OP_FAILED, "close", dest);
goto out;
}
*rval = JSVAL_TRUE;
return JS_TRUE;
out:
if(file->isOpen && !fileInitiallyOpen){
if(PR_Close(file->handle)!=PR_SUCCESS){
JS_ReportWarning(cx, "Can't close %s, proceeding", file->path);
}
}
if(handle && PR_Close(handle)!=PR_SUCCESS){
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, &js_FileClass, 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])));
if (PR_Rename(file->path, dest)==PR_SUCCESS){
/* 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, &js_FileClass, NULL);
SECURITY_CHECK(cx, NULL, "flush", file);
JSFILE_CHECK_NATIVE("flush");
JSFILE_CHECK_OPEN("flush");
if (PR_Sync(file->handle)==PR_SUCCESS){
*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, &js_FileClass, NULL);
JSString *str;
int32 count;
uintN i;
SECURITY_CHECK(cx, NULL, "write", file);
JSFILE_CHECK_WRITE;
for (i = 0; i<argc; i++) {
str = JS_ValueToString(cx, argv[i]);
count = js_FileWrite(cx, file, JS_GetStringChars(str),
JS_GetStringLength(str), file->type);
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, &js_FileClass, 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, &js_FileClass, 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; i<limit; i++) {
if (!JS_GetElement(cx, array, i, &elemval)) return JS_FALSE;
elem = JSVAL_TO_OBJECT(elemval);
file_writeln(cx, obj, 1, &elemval, rval);
}
*rval = JSVAL_TRUE;
return JS_TRUE;
out:
*rval = JSVAL_FALSE;
return JS_FALSE;
}
static JSBool
file_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
JSFile *file = JS_GetInstancePrivate(cx, obj, &js_FileClass, NULL);
JSString *str;
int32 want, count;
jschar *buf;
SECURITY_CHECK(cx, NULL, "read", file);
JSFILE_CHECK_ONE_ARG("read");
JSFILE_CHECK_READ;
if (!JS_ValueToInt32(cx, argv[0], &want)){
JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL,
JSFILEMSG_FIRST_ARGUMENT_MUST_BE_A_NUMBER, "read", argv[0]);
goto out;
}
/* want = (want>262144)?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, &js_FileClass, 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, &js_FileClass, 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, &js_FileClass, 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)
{
PRDir *dir;
PRDirEntry *entry;
JSFile *file = JS_GetInstancePrivate(cx, obj, &js_FileClass, 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 (VALUE_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;
}
dir = PR_OpenDir(file->path);
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;
while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH))!=NULL) {
/* first, check if we have a regexp */
if (re!=NULL) {
size_t index = 0;
str = JS_NewStringCopyZ(cx, entry->name);
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) {
str = JS_NewStringCopyZ(cx, entry->name);
args[0] = STRING_TO_JSVAL(str);
if(!JS_CallFunction(cx, obj, func, 1, args, &v)){
goto out;
}
if (v==JSVAL_FALSE) {
continue;
}
}
filePath = js_combinePath(cx, file->path, (char*)entry->name);
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);
JS_SetProperty(cx, array, entry->name, &v);
len++;
}
if(PR_CloseDir(dir)!=PR_SUCCESS){
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, &js_FileClass, 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);
if (PR_MkDir(fullName, 0755)==PR_SUCCESS){
*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, &js_FileClass, NULL);
JSString *str;
str = JS_NewStringCopyZ(cx, file->path);
if (!str)
return JS_FALSE;
*rval = STRING_TO_JSVAL(str);
return JS_TRUE;
}
static JSBool
file_toURL(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
JSFile *file = JS_GetInstancePrivate(cx, obj, &js_FileClass, NULL);
char url[MAX_PATH_LENGTH];
jschar *urlChars;
size_t len;
JSString *str;
JSFILE_CHECK_NATIVE("toURL");
sprintf(url, "file://%s", file->path);
len = strlen(url);
urlChars = js_InflateString(cx, url, &len);
if (!urlChars)
return JS_FALSE;
str = js_NewString(cx, urlChars, len, 0);
if (!str) {
JS_free(cx, urlChars);
return JS_FALSE;
}
*rval = STRING_TO_JSVAL(str);
/* TODO: js_escape in jsstr.h may go away at some point */
return js_str_escape(cx, obj, 0, rval, rval);
out:
*rval = JSVAL_VOID;
return JS_FALSE;
}
static void
file_finalize(JSContext *cx, JSObject *obj)
{
JSFile *file = JS_GetInstancePrivate(cx, obj, &js_FileClass, 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, &js_FileClass, 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, &js_FileClass, 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, &js_FileClass, 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, &js_FileClass, NULL);
char *bytes;
JSString *str;
jsint tiny;
PRFileInfo info;
JSBool flag;
PRExplodedTime expandedTime;
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");
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));
break;
case FILE_MODIFIED:
SECURITY_CHECK(cx, NULL, "lastModified", file);
JSFILE_CHECK_NATIVE("lastModified");
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));
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 */
PRDir *dir;
PRDirEntry *entry;
jsint count = 0;
if(!(dir = PR_OpenDir(file->path))){
JS_ReportErrorNumber(cx, JSFile_GetErrorMessage, NULL,
JSFILEMSG_CANNOT_OPEN_DIR, file->path);
goto out;
}
while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH))) {
count++;
}
if(!PR_CloseDir(dir)){
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)) {
int pos = PR_Seek(file->handle, 0, PR_SEEK_CUR);
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)) {
PRDir *dir = NULL;
PRDirEntry *entry = NULL;
char *prop_name;
str = JS_ValueToString(cx, id);
if (!str)
return JS_FALSE;
prop_name = JS_GetStringBytes(str);
/* no native files past this point */
dir = PR_OpenDir(file->path);
if(!dir) {
/* This is probably not a directory */
JS_ReportWarning(cx, "Can't open directory %s", file->path);
return JS_FALSE;
}
while ((entry = PR_ReadDir(dir, PR_SKIP_NONE)) != NULL) {
if (!strcmp(entry->name, prop_name)){
bytes = js_combinePath(cx, file->path, prop_name);
*vp = OBJECT_TO_JSVAL(js_NewFileObject(cx, bytes));
PR_CloseDir(dir);
JS_free(cx, bytes);
return !JSVAL_IS_NULL(*vp);
}
}
PR_CloseDir(dir);
}
}
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, &js_FileClass, 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;
}
pos = PR_Seek(file->handle, offset, PR_SEEK_SET);
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, &js_FileClass, NULL);
/* Look at the rhs and extract a file object from it */
if (JSVAL_IS_OBJECT(*vp)) {
if (JS_InstanceOf(cx, obj, &js_FileClass, 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 */
JSClass js_FileClass = {
"File", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_File),
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, &js_FileClass, 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 */