/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** 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"

/* ----------------- Platform-specific includes and defines ----------------- */
#ifdef XP_MAC
#   define FILESEPARATOR         ':'
#   define FILESEPARATOR2        '\0'
#   define CURRENT_DIR          "HARD DISK:Desktop Folder"
/*  TODO: #include <???> */
#elif 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");     \
    }else   \
    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");     \
    }else   \
    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)
*/
#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;
    JSString    *linebuffer;    /* temp buffer used by readln. */
    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... */
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)
{
#ifdef XP_MAC
    /* pipes are not supported on the MAC */
    return JS_FALSE;
#else
    if(!filename) return JS_FALSE;
    return  filename[0]==PIPE_SYMBOL ||
            filename[strlen(filename)-1]==PIPE_SYMBOL;
#endif
}

static JSBool
js_isAbsolute(const char *name)
{
#if defined(XP_WIN) || defined(XP_OS2)
    return (strlen(name)>1)?((name[1]==':')?JS_TRUE:JS_FALSE):JS_FALSE;
#else
    return (name[0]
#   if defined(XP_UNIX) || defined(XP_BEOS)
            ==
#   else
            !=
#   endif
            FILESEPARATOR)?JS_TRUE:JS_FALSE;
#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 = (char*)JS_malloc(cx, len+strlen(name)+2);

    if (!result)  return NULL;

    strcpy(result, base);

    if (base[len-1]!=FILESEPARATOR
#if defined(XP_WIN) || defined(XP_OS2)
            && base[len-1]!=FILESEPARATOR2
#endif
            ) {
      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;

#if defined(XP_WIN) || defined(XP_OS2)
    /* First, get rid of the drive selector */
    if ((strlen(pathname)>=2)&&(pathname[1]==':')) {
        pathname = &pathname[2];
    }
#endif
    index = strlen(pathname)-1;
    /*
        remove trailing separators -- don't necessarily need to check for
        FILESEPARATOR2, but that's fine
    */
    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 = (char*)JS_malloc(cx, aux-index+1);
    if (!result)  return NULL;
    strncpy(result, &pathname[index+1], aux-index);
    result[aux-index] = '\0';
    return result;
}

/*
    Returns everytynig but the last component from a path name.
    Returned string must be freed. Returned string must be freed.
*/
static char *
js_fileDirectoryName(JSContext *cx, const char *pathname)
{
    jsint index;
    char  *result;

#if defined(XP_WIN) || defined(XP_OS2)
    char  drive = '\0';
    const char *oldpathname = pathname;

    /* First, get rid of the drive selector */
    if ((strlen(pathname)>=2)&&(pathname[1]==':')) {
        drive = pathname[0];
        pathname = &pathname[2];
    }
#endif
    index = strlen(pathname)-1;
    while ((index>0)&&((pathname[index]==FILESEPARATOR)||
                       (pathname[index]==FILESEPARATOR2))) index--;
    while ((index>0)&&(pathname[index]!=FILESEPARATOR)&&
                      (pathname[index]!=FILESEPARATOR2)) index--;

    if (index>=0){
        result = (char*)JS_malloc(cx, index+4);
        if (!result)  return NULL;
#if defined(XP_WIN) || defined(XP_OS2)
        if (drive!='\0') {
            result[0] = toupper(drive);
            result[1] = ':';
            strncpy(&result[2], pathname, index);
			result[index+3] = '\0';
        }else
#endif
        {
            strncpy(result, pathname, index);
			result[index] = '\0';
        }

        /* add terminating separator */
        index = strlen(result)-1;
        result[index] = FILESEPARATOR;
        result[index+1] = '\0';
    } else{
#if defined(XP_WIN) || defined(XP_OS2)
        result = JS_strdup(cx, oldpathname); /* may include drive selector */
#else
        result = JS_strdup(cx, pathname);
#endif
    }

    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<strlen(path)-1 && path[i]==' ') i++;
	while(j>=0 && path[j]==' ') j--;

	tmp = JS_malloc(cx, j-i+2);
	strncpy(tmp, &path[i], j-i+1);
    tmp[j-i+1] = '\0';

    path = tmp;

    /* pipe support */
    if(js_filenameHasAPipe(path))
        return JS_strdup(cx, path);
    /* file:// support */
    if(!strncmp(path, URL_PREFIX, strlen(URL_PREFIX)))
        return js_canonicalPath(cx, &path[strlen(URL_PREFIX)-1]);

    if (!js_isAbsolute(path))
        path = js_absolutePath(cx, path);
    else
        path = JS_strdup(cx, path);

    result = JS_strdup(cx, "");

    current = path;

    base = js_fileBaseName(cx, current);
    dir = js_fileDirectoryName(cx, current);

    /* TODO: MAC -- not going to work??? */
    while (strcmp(dir, current)) {
        if (!strcmp(base, "..")) {
            back++;
        } else
        if(!strcmp(base, ".")){
            /* ??? */
        } else {
            if (back>0)
                back--;
            else {
                tmp = result;
                result = JS_malloc(cx, strlen(base)+1+strlen(tmp)+1);
                if (!result) {
                    JS_free(cx, dir);
                    JS_free(cx, base);
                    JS_free(cx, current);
                    return NULL;
                }
                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) {
        JS_free(cx, dir);
        JS_free(cx, base);
        JS_free(cx, current);
        return NULL;
    }
    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);
    }
    JS_free(cx, tmp);
    JS_free(cx, dir);
    JS_free(cx, base);
    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 if (onechar <= MAX_UCS2) {   numUTF8bytes = 3;
        } else { numUTF8bytes = 2;
                 onechar = DEFAULT_CHAR;
        }

        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;
    file->linebuffer = NULL;    /* TODO: check for mem. leak? */
}

/* 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) {
        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, 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, (char*)buf, len*2)>>1;
        if (count==-1) {
            return 0;
        }
        break;
    }

    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, 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;
    }

    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, 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;
    }
    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){
        return (PR_Access(file->path, PR_ACCESS_EXISTS)==PR_SUCCESS);
    }else{
        /* doesn't make sense for a pipe of stdstream */
        return JS_FALSE;
    }
}

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);
    }else{
        if(file->isPipe){
            /* pipe open for reading */
            return file->path[0]==PIPE_SYMBOL;
        }else{
            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);
    }else{
        if(file->isPipe){
            /* pipe open for writing */
            return file->path[strlen(file->path)-1]==PIPE_SYMBOL;
        }else{
            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;
        }else
            return (info.type==PR_FILE_FILE);
    }else{
        /* 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;
        }else
            return (info.type==PR_FILE_DIRECTORY);
    }else{
        /* 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);
        goto out;
    }else
        return INT_TO_JSVAL(info.size);
out:
    return JSVAL_VOID;
}

/* Return the parent object */
static jsval
js_parent(JSContext *cx, JSFile *file)
{
    char *str;

    /* since we only care about pipes and native files, return NULL */
    if(file->isNative)  return JSVAL_VOID;

    str = js_fileDirectoryName(cx, file->path);
    /* root.parent = null ??? */
    if(!strcmp(file->path, str) ||
            (!strncmp(str, file->path, strlen(str)-1)&&
            file->path[strlen(file->path)]-1)==FILESEPARATOR){
        return JSVAL_NULL;
    }else{
        return OBJECT_TO_JSVAL(js_NewFileObject(cx, str));
        JS_free(cx, str);
    }
}

static jsval
js_name(JSContext *cx, JSFile *file){
    return  file->isPipe?
                JSVAL_VOID:
                STRING_TO_JSVAL(JS_NewStringCopyZ(cx, js_fileBaseName(cx, file->path)));
}

/* ------------------------------ 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;

    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, BTW, 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{
            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[0] = 'r';
                pipemode[1] = file->type==UTF8?'b':'t';
                pipemode[2] = '\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[0] = 'w';
                pipemode[1] = file->type==UTF8?'b':'t';
                pipemode[2] = '\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;
    }else
        goto good;
good:
    file->isOpen = JS_TRUE;
    *rval = JSVAL_TRUE;
    return JS_TRUE;
out:
    if(mode) JS_free(cx, mode);
    *rval = JSVAL_VOID;
    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{
            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:
    *rval = JSVAL_FALSE;
    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");

    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, &file_class, 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, &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])));

    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, &file_class, 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, &file_class, 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, &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; 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, &file_class, 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, &file_class, NULL);
    JSString    *str;
    jschar      *buf;
    int32       offset;
    intN        room;
    jschar      data, data2;
    JSBool      endofline;

    SECURITY_CHECK(cx, NULL, "readln", file);
    JSFILE_CHECK_READ;

    if (!file->linebuffer) {
        buf = JS_malloc(cx, MAX_LINE_LENGTH*(sizeof data));
        if (!buf) goto out;
        file->linebuffer = JS_NewUCString(cx, buf, MAX_LINE_LENGTH);
    }
    room = JS_GetStringLength(file->linebuffer);
    offset = 0;

    /* XXX TEST ME!! TODO: yes, please do */
    for(;;) {
        if (!js_FileRead(cx, file, &data, 1, file->type)) {
            endofline = JS_FALSE;
            goto loop;
        }
        switch (data) {
        case '\n' :
            endofline = JS_TRUE;
            goto loop;
        case '\r' :
            if (!js_FileRead(cx, file, &data2, 1, file->type)) {
                endofline = JS_TRUE;
                goto loop;
            }
            if (data2!='\n') { /* We read one char too far.  Buffer it. */
                file->charBuffer = data2;
                file->charBufferUsed = JS_TRUE;
            }
            endofline = JS_TRUE;
            goto loop;
        default:
            if (--room < 0) {
                buf = JS_malloc(cx, (offset+MAX_LINE_LENGTH)*sizeof data);
                if (!buf) return JS_FALSE;
                room = MAX_LINE_LENGTH-1;
                memcpy(buf, JS_GetStringChars(file->linebuffer),
                    JS_GetStringLength(file->linebuffer));
                /* what follows may not be the cleanest way. */
                file->linebuffer->chars = buf;
                file->linebuffer->length =  offset+MAX_LINE_LENGTH;
            }
            file->linebuffer->chars[offset++] = data;
            break;
        }
    }
loop:
    file->linebuffer->chars[offset] = 0;
    if ((endofline==JS_TRUE)) {
        str = JS_NewUCStringCopyN(cx, JS_GetStringChars(file->linebuffer),
                                    offset);
        *rval = STRING_TO_JSVAL(str);
        return JS_TRUE;
    }else{
        goto out;
    }
out:
    *rval = JSVAL_NULL;
    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;

    SECURITY_CHECK(cx, NULL, "readAll", file);
    JSFILE_CHECK_READ;

    array = JS_NewArrayObject(cx, 0, NULL);
    len = 0;

    while(file_readln(cx, obj, 0, NULL, &line)){
        JS_SetElement(cx, array, len, &line);
        len++;
    }

    *rval = OBJECT_TO_JSVAL(array);
    return JS_TRUE;
out:
    *rval = JSVAL_FALSE;
    return JS_FALSE;
}

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)
{
    PRDir       *dir;
    PRDirEntry  *entry;
    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;
    }

    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, &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);
        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, &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;
    }else
        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;
#ifdef XP_MAC
    JS_ReportWarning(cx, "Native files are not fully supported on the MAC");
#endif

    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;

    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]);
        goto out;
    }

    file = file_init(cx, obj, JS_GetStringBytes(str));
    if (!file)  goto out;

    SECURITY_CHECK(cx, NULL, "constructor", file);

    return JS_TRUE;
out:
    *rval = JSVAL_VOID;
    return JS_FALSE;
}

/* -------------------- 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        *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);
        *vp = js_parent(cx, file);
        break;
    case FILE_PATH:
        *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, file->path));
        break;
    case FILE_NAME:
        *vp = js_name(cx, file);
        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");
        str = (char*)JS_malloc(cx, MODE_SIZE);
        str[0] = '\0';
        flag = JS_FALSE;

        if ((file->mode&PR_RDONLY)==PR_RDONLY) {
            if (flag) strcat(str, ",");
            strcat(str, "read");
            flag = JS_TRUE;
        }
        if ((file->mode&PR_WRONLY)==PR_WRONLY) {
            if (flag) strcat(str, ",");
            strcat(str, "write");
            flag = JS_TRUE;
        }
        if ((file->mode&PR_RDWR)==PR_RDWR) {
            if (flag) strcat(str, ",");
            strcat(str, "readWrite");
            flag = JS_TRUE;
        }
        if ((file->mode&PR_APPEND)==PR_APPEND) {
            if (flag) strcat(str, ",");
            strcat(str, "append");
            flag = JS_TRUE;
        }
        if ((file->mode&PR_CREATE_FILE)==PR_CREATE_FILE) {
            if (flag) strcat(str, ",");
            strcat(str, "create");
            flag = JS_TRUE;
        }
        if ((file->mode&PR_TRUNCATE)==PR_TRUNCATE) {
            if (flag) strcat(str, ",");
            strcat(str, "replace");
            flag = JS_TRUE;
        }
        if (file->hasAutoflush) {
            if (flag) strcat(str, ",");
            strcat(str, "hasAutoFlush");
            flag = JS_TRUE;
        }
        *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
        JS_free(cx, str);
        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 = JS_GetStringBytes(JS_ValueToString(cx, id));

            /* 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)){
                    str = js_combinePath(cx, file->path, prop_name);
                    *vp = OBJECT_TO_JSVAL(js_NewFileObject(cx, str));
					JS_free(cx, str);
                    return JS_TRUE;
				}
			}
		}
    }
    return JS_TRUE;
out:
	*vp = JSVAL_VOID;
    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;
			}

			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:
	*vp = JSVAL_VOID;
	return JS_FALSE;
}

/*
    File.currentDir = new File("D:\") or File.currentDir = "D:\"
*/
static JSBool
file_currentDirSetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    JSObject *rhsObject;
    char     *path;
    JSFile   *file = JS_GetInstancePrivate(cx, rhsObject, &file_class, NULL);

    /* Look at the rhs and extract a file object from it */
    if (JSVAL_IS_OBJECT(*vp)){
        if (JS_InstanceOf(cx, rhsObject, &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);
                goto out;
            }else{
                rhsObject = JSVAL_TO_OBJECT(*vp);
                chdir(file->path);
                return JS_TRUE;
            }
        }else
            goto out;
    }else{
        path      = JS_GetStringBytes(JS_ValueToString(cx, *vp));
        rhsObject = js_NewFileObject(cx, path);
        if (!rhsObject)  goto out;

        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;
out:
	*vp = JSVAL_VOID;
	return JS_FALSE;
}

/* 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, JSBool initStandardStreams)
{
    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 );

    if(initStandardStreams){
        /* Code to create stdin, stdout, and stderr. Insert in the appropriate place. */
        /* 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 */
