Coding With SpiderMonkey

Part VI - Calling Functions From C

Time for another SpiderMonkey write up. This write up is inspired by an email I received where the sender wanted to execute a Javascript function and get the results into his C++ Application. The email asked:

I have a javascript file (hello.js). This file contains a function (sayHello(name)) which accepts an argument. this function will return a string value("Hello, Keith"). So basically I need to run a particular function in the javascript file(which acceps an argument also), and this function will return a string value which should be stored to a variable in my c++ code.

the above example is simple, but my javascript code is complicated. so from cpp code I need to execute a particular function which returns a string back to my cpp variable.

greetings = call_JS_Function(hello.js, sayHello(Keith));

once executed, greetings contains the string "Hello, Keith"

A Reader

As you can see by the email, the example shows a C function which takes two parameters. First, we pass the location of a Javascript file which contains the code we want to executed. Second, we pass the function we want executed. Now, of course we can't just pass function arguments and in the function name as shown. What we will do instead is pass a third parameters, which is an array of arguments to pass to the javascript function when it is executed. We may need to pass the length of this array as well, I am not sure on that. We will find out when I get there.

Like most the rest of my article's on SpiderMonkey I will be writing and updating this as I go along. Just be sure to read through rather than jump and skip around a bunch and this should all make sense in the end :)

Getting started

To get started, I will be starting fresh for this article. Not only will this help me jog my memory about SpiderMonkey but it will help keep the example code size to a minimum rather than inflating it with all the extra code from the previous examples which is not really necessary.

First thing's first, lets create a base application which does not include SpiderMonkey yet, but is ready to include it in order to extend the application. For this example, I will be creating an application which will encrypt the data fed to it from stdin. The encryption type will be specified on the command line as the first argument to the application. Encryption types will be defined in the application's configuration file.

So, a few hours later and I return with my base application, ready to be integrated with SpiderMonkey. As you can see in the code, the application will load up a config file, either in the user's home directory, or globally from /etc/ and then process this config file. The config file includes the encryption definitions with one definition per line. Each definition consists of an identifier, file location, and function name, separated by a tab.

jsencrypt.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jsapi.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
#include <time.h>
#include <utime.h>
#include "jsencrypt.h"
#include "encrypt.h"


static EncryptionTypes *loadConfigFile(const char *path);
static char *getConfigFilePath();

int main(int argc, char **argv){
    EncryptionTypes *encMap=NULL;
    EncryptionTypes *currentType=NULL;
    char *config = getConfigFilePath();
    char *stdinData=NULL;
    char *encryptedData=NULL;
    int stdinLength=0;
    int bufferLength=0;
    char buffer[1024];


    if (config == NULL){
        printf("Configuration file could not be loaded.");
        return 1;
    }


    encMap = loadConfigFile(config);
    if (encMap == NULL){
        printf("Cannot load config file, probably an invalid format.");
        return 2;
    }


    if (argc < 2){
        printf("Must specify encryption type to use as command line argument.");
        return 3;
    }

    currentType = encMap;
    while (currentType){
        if (strcmp(currentType->typeID, argv[1]) == 0){
            break;
        }
        else {
            currentType = currentType->next;
        }
    }

    if (currentType == NULL){
        printf("Specified encryption type is not supported.");
        return 4;
    }



    while (!feof(stdin)){
        memset(buffer, 0, sizeof(buffer));
        bufferLength=fread(buffer, sizeof(char), sizeof(buffer), stdin);
        if (bufferLength > 0){
            char *tmp = realloc(stdinData, stdinLength+bufferLength+1);
            if (tmp != NULL){
                stdinData = tmp;
                memset(&stdinData[stdinLength], 0, bufferLength+1);
                if (stdinLength > 0){
                    strcpy(&stdinData[stdinLength], buffer);
                }
                else {
                    strcpy(stdinData, buffer);
                }
                stdinLength += bufferLength;
            }
        }
    }

    printf("STDIN Data to Encrypt (with %s): \n%s", currentType->typeID, stdinData);
    encryptedData = encryptData(currentType, stdinData);

    printf("Encrypted data: \n\n%s", encryptedData);

    free(stdinData);
    free(encryptedData);

    return 0;
}

/* Loads the configuration file into a singly-linked list of encryption types.
*/

static EncryptionTypes *loadConfigFile(const char *path){
    EncryptionTypes *map=NULL;
    EncryptionTypes *node=NULL;
    EncryptionTypes *lastNode=NULL;

    FILE *fp = NULL;
    char data[1024];


    fp = fopen(path, "r");
    if (fp == NULL){
        return NULL;
    }

    while (!feof(fp)){
        memset(data, 0, sizeof(data));
        if (fgets(data, sizeof(data), fp) != NULL){
            char typeID[1024];
            char filePath[1024];
            char functionName[1024];
            memset(typeID, 0, sizeof(typeID));
            memset(filePath, 0, sizeof(filePath));
            memset(functionName, 0, sizeof(functionName));

            if (sscanf(data, "%s %s %s", typeID, filePath, functionName) == 3){
                node = malloc(sizeof(*node));
                memset(node, 0, sizeof(*node));
                node->typeID = strdup(typeID);
                node->filePath = strdup(filePath);
                node->functionName = strdup(functionName);

                if (map == NULL){
                    map = node;
                    lastNode = map;
                }
                else {
                    while (lastNode->next != NULL){
                        lastNode = lastNode->next;
                    }
                    lastNode->next = node;
                }
            }
        }
    }
    return map;
}

/* Return the path of the configuration file for this applciation.
* First we will check for a user config in ~
* Second we will check for a global config in /etc
* If no config file is found, return NULL
*/
static char *getConfigFilePath(){
    char *filePath=NULL;
    char *expandedPath=NULL;
    int filePathLen = 0;
    struct stat fileDetails;


    filePathLen = snprintf(NULL, 0, "~/.%s", APPCONFIG_NAME)+1;

    if (filePathLen > 1){
        filePath = malloc(sizeof(*filePath)*filePathLen);
        memset(filePath, 0, sizeof(*filePath)*filePathLen);
        snprintf(filePath, sizeof(*filePath)*filePathLen, "~/.%s", APPCONFIG_NAME);

        expandedPath = expandTilda(filePath);
        free(filePath);
        filePath = NULL;

        /* Check to see if this config file exists using stat. */
        if (stat(expandedPath, &fileDetails) == 0){
            return expandedPath;
        }
        else {
            free(expandedPath);
            expandedPath=NULL;
            filePath=NULL;
        }
    }


    filePathLen = snprintf(NULL, 0, "/etc/%s", APPCONFIG_NAME)+1;
    if (filePathLen > 1){
        filePath = malloc(sizeof(*filePath)*filePathLen);
        memset(filePath, 0, sizeof(*filePath)*filePathLen);
        snprintf(filePath, sizeof(*filePath)*filePathLen, "/etc/%s", APPCONFIG_NAME);

        expandedPath = expandTilda(filePath);
        free(filePath);

        /* Check to see if this config file exists using stat. */
        if (stat(expandedPath, &fileDetails) == 0){
            return expandedPath;
        }
        else {
            free(expandedPath);
            filePath = NULL;
        }
    }
    return expandedPath;
}


/* Returns the current user's home directory in place of the shortcut ~
*/
char *expandTilda(const char *str){
    char *homeDir=NULL;
    char *finalOutput = NULL;
    int finalOutputLen = 0;

    homeDir = getenv("HOME");
    if (homeDir!=NULL){
        char *copy = strdup(str);
        char *beforeTilda=copy;
        char *afterTilda=copy;

        if (homeDir[strlen(homeDir)-1] != '/'){
            char *tmp=homeDir;
            homeDir = malloc((strlen(tmp)+1)*sizeof(*homeDir));
            memset(homeDir, 0, (strlen(tmp)+1));
            sprintf(homeDir, "%s/", tmp);
        }
        else {
            homeDir = strdup(homeDir);
        }

        beforeTilda = copy;
        afterTilda = strchr(copy, '~');
        while (afterTilda != NULL){
            *afterTilda = '\0';
            afterTilda++;

            while (*afterTilda == '/'){
                *afterTilda='\0';
                afterTilda++;
            }

            finalOutputLen = strlen(beforeTilda)+strlen(homeDir)+strlen(afterTilda)+1;
            finalOutput = malloc(sizeof(*finalOutput)*finalOutputLen);
            memset(finalOutput, 0, sizeof(*finalOutput)*finalOutputLen);

            snprintf(finalOutput, sizeof(*finalOutput)*finalOutputLen, "%s%s%s", beforeTilda, homeDir, afterTilda);


            free(copy);
            copy = finalOutput;
            beforeTilda = copy;
            afterTilda = strchr(copy, '~');
        }

        return finalOutput;
    }
    else {
        return strdup(str);
    }
}

There is one nice function in there that even non-SpiderMonkey people may find useful, and that is the expandTilda function. It will expand the tilda character (~) that appears in paths to a persons home directory. The reason for this function is to allow me to use ~/ when specifying both the location of the config file (~/.jsencrypt.conf) and also so people can use ~ when specifying the location of the .js files in the config file, as I have done in my config file below:

jsencrypt.conf

rot13   ~/.jsencrypt/encrypt.js encryptRot13

From here on out, all the work we will need to do for SpiderMonkey will be contained in the encryptData function. This function will prepare the data for execution by SpiderMonkey. That is, we will convert the data to a jsval array and then pass it to the executeJSFunction function which will handle the actual execution of our script. This is the function I described above, with the three parameters for the file location, function name, and parameter list. The encryptData and executeJSFunction functions are currently defined in a separate file, which currently looks like so:

encrypt.c

#include <string.h>
#include "encrypt.h"

/* Commented out because we have not include the Spidermonkey API yet which is needed to define jsval
 * static jsval executeJSFunction(const char *file, const char *function, jsval[] argv);
 */


char *encryptData(EncryptionTypes *type, const char *data){
    return strdup(data);
}

/* Commented out because we have not include the Spidermonkey API yet which is needed to define jsval
 * static jsval executeJSFunction(const char *file, const char *function, jsval[] argv){
 * }
 */

Implementing SpiderMonkey

So, the first thing we need to do is include the SpiderMonkey header file jsapi.h. Our make file already includes the necessary library and header file paths and flags, since it was used in previous articles, so no changes need to be made there. Once the file is included, we will be able to uncomment the prototype and definition for executeJSFunction because our program will now know about the jsval data type.

encrypt.c

#include <string.h>
#include <jsapi.h>
#include "encrypt.h"

static jsval executeJSFunction(const char *file, const char *function, jsval *argv);



char *encryptData(EncryptionTypes *type, const char *data){
    return strdup(data);
}


static jsval executeJSFunction(const char *file, const char *function, jsval *argv){
    return JS_FALSE;
}

Upon trying to compile the code, I found I needed to change a few things. I added a return value to the function, and also change the argv parameter from an array definition to a pointer definition. Yea, I mess up sometimes, that's why compilers yell at me when I do :)

Looking at the reference for JS_NewString I see we need to pass the current context to create the string. As such, we will have to setup our runtime and context in the encryptData function, and modify the executeJSFunction to accept the context as an additional parameter. We then need to use JS_NewString to make our stdin data readable by SpiderMonkey, and then convert it to a jsval array.

With the string conversion in place, and the call to executeJSFunction ready, our code now looks like the following:

encrypt.c

#include <stdio.h>
#include <string.h>
#include <jsapi.h>
#include "encrypt.h"

static jsval executeJSFunction(JSContext *cx, const char *file, const char *function, jsval *argv);

char *encryptData(EncryptionTypes *type, const char *data){
    JSRuntime *rt=NULL;
    JSContext *cx=NULL;
    jsval *parameters=NULL;
    JSString *stdinData=NULL;
    char *stdinDataBuffer;


    rt = JS_NewRuntime(1073741824);
    if (rt == NULL){
        printf("Failed to start Spidermonkey, cannot perform encryption.");
        return stdinDataBuffer;
    }

    cx = JS_NewContext(rt, 8192);
    if (cx == NULL){
        JS_DestroyRuntime(rt);
        printf("Failed to start Spidermonkey, cannor perform encryption.");
        return stdinDataBuffer;
    }

    /* Spidermonkey manages the memory space of it's strings, so we need to duplicate the passed string.
     * We must allocate the memory using Spidermonkey's JS_malloc function though, so that it is safe for
     * Spidermonkey to manage it.
     */
    stdinDataBuffer = JS_malloc(cx, strlen(data));
    memcpy(stdinDataBuffer, data, strlen(data));

    stdinData = JS_NewString(cx, stdinDataBuffer, strlen(stdinDataBuffer));

    parameters=JS_malloc(cx, sizeof(*parameters));
    parameters[0] = STRING_TO_JSVAL(stdinData);

    /* Execute the javascript file. */
    executeJSFunction(cx, type->filePath, type->functionName, parameters);

    /* Cleanup Spidermonkey */
    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    return stdinDataBuffer;
}


static jsval executeJSFunction(JSContext *cx, const char *file, const char *function, jsval *argv){
    return JS_FALSE;
}

Now we get to work on executeJSFunction. We need to have this function load up the file specified by file, then execute the function specified by function, and pass in the parameters specified by argv (which is our string). First, we will check to see if the file to load exists using stat. A little error checking never hurt. We also need to expand any tilda's in the filename using the expandTilda function. Doing this stat validation to see if the file exists brings up another concern, and that is how to return and error code. To accomplish that, I am going to alter the executeJSFunction yet again to have a return type of JSBool, and the return value from executing the function will be through another parameter.

To load the JS file into SpiderMonkey for parsing, we will use the function JS_CompileFile. According to the documentation this function requires an Object, so we will need to initialize a global object for the context. We will also want to Initialize the standard classes so that the encoding script has classes to work with and be functional.

encrypt.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <jsapi.h>
#include "encrypt.h"


static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, jsval *argv, jsval *ret);
JSClass globalObjectClass = {
    "global",
    0,

    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    JS_FinalizeStub,

    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};


char *encryptData(EncryptionTypes *type, const char *data){
    JSRuntime *rt=NULL;
    JSContext *cx=NULL;
    jsval *parameters=NULL;
    jsval ret;
    JSString *stdinData=NULL;
    char *stdinDataBuffer;


    rt = JS_NewRuntime(1073741824);
    if (rt == NULL){
        printf("Failed to start Spidermonkey, cannot perform encryption.");
        return stdinDataBuffer;
    }

    cx = JS_NewContext(rt, 8192);
    if (cx == NULL){
        JS_DestroyRuntime(rt);
        printf("Failed to start Spidermonkey, cannor perform encryption.");
        return stdinDataBuffer;
    }

    /* Spidermonkey manages the memory space of it's strings, so we need to duplicate the passed string.
    * We must allocate the memory using Spidermonkey's JS_malloc function though, so that it is safe for
    * Spidermonkey to manage it.
    */
    stdinDataBuffer = JS_malloc(cx, strlen(data));
    memcpy(stdinDataBuffer, data, strlen(data));

    stdinData = JS_NewString(cx, stdinDataBuffer, strlen(stdinDataBuffer));

    parameters=JS_malloc(cx, sizeof(*parameters));
    parameters[0] = STRING_TO_JSVAL(stdinData);

    /* Execute the javascript file. */
    if (executeJSFunction(cx, type->filePath, type->functionName, parameters, &ret) == JS_TRUE){
    }

    /* Cleanup Spidermonkey */
    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    return stdinDataBuffer;
}


static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, jsval *argv, jsval *ret){
    char *expandedFilename = expandTilda(file);
    struct stat fileDetails;
    JSObject *global=NULL;
    JSScript *script=NULL;


    if (stat(expandedFilename, &fileDetails) == -1){
        printf("Encryption code file '%s' does not exist.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }


    /* Create a global object */
    global = JS_NewObject(cx, &globalObjectClass, NULL, NULL);
    if (global == NULL){
        printf("Execution of encryptor failed: Cannot create global object.");
        free(expandedFilename);
        return JS_FALSE;
    }

    JS_InitStandardClasses(cx, global);

    script = JS_CompileFile(cx, global, expandedFilename);
    if (script == NULL){
        printf("Execution of encryptor failed: Cannot compile file '%s'.", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }


    free(expandedFilename);
    return JS_FALSE;
}

Now we are ready to execute the function and get the return value. This will be accomplished using the JS_CallFunctionName API. We need to get a value for argc so we can specify how many arguments the function has. The easiest way to go about that will be to pass the value in as another parameter to executeJSFunction, so change the function prototype again to define that parameter, and when I call the function I will just pass a static value of 1 because that is all we are using (the string to be encrypted).

encrypt.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <jsapi.h>
#include "encrypt.h"


static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret);
JSClass globalObjectClass = {
    "global",
    0,

    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    JS_FinalizeStub,

    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};


char *encryptData(EncryptionTypes *type, const char *data){
    JSRuntime *rt=NULL;
    JSContext *cx=NULL;
    jsval *parameters=NULL;
    jsval ret;
    JSString *stdinData=NULL;
    char *stdinDataBuffer;


    rt = JS_NewRuntime(1073741824);
    if (rt == NULL){
        printf("Failed to start Spidermonkey, cannot perform encryption.");
        return stdinDataBuffer;
    }

    cx = JS_NewContext(rt, 8192);
    if (cx == NULL){
        JS_DestroyRuntime(rt);
        printf("Failed to start Spidermonkey, cannor perform encryption.");
        return stdinDataBuffer;
    }

    /* Spidermonkey manages the memory space of it's strings, so we need to duplicate the passed string.
    * We must allocate the memory using Spidermonkey's JS_malloc function though, so that it is safe for
    * Spidermonkey to manage it.
    */
    stdinDataBuffer = JS_malloc(cx, strlen(data));
    memcpy(stdinDataBuffer, data, strlen(data));

    stdinData = JS_NewString(cx, stdinDataBuffer, strlen(stdinDataBuffer));

    parameters=JS_malloc(cx, sizeof(*parameters));
    parameters[0] = STRING_TO_JSVAL(stdinData);

    /* Execute the javascript file. */
    if (executeJSFunction(cx, type->filePath, type->functionName, 1, parameters, &ret) == JS_TRUE){
    }

    /* Cleanup Spidermonkey */
    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    return stdinDataBuffer;
}


static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret){
    char *expandedFilename = expandTilda(file);
    struct stat fileDetails;
    JSObject *global=NULL;
    JSScript *script=NULL;


    if (stat(expandedFilename, &fileDetails) == -1){
        printf("Encryption code file '%s' does not exist.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }


    /* Create a global object */
    global = JS_NewObject(cx, &globalObjectClass, NULL, NULL);
    if (global == NULL){
        printf("Execution of encryptor failed: Cannot create global object.");
        free(expandedFilename);
        return JS_FALSE;
    }

    JS_InitStandardClasses(cx, global);

    script = JS_CompileFile(cx, global, expandedFilename);
    if (script == NULL){
        printf("Execution of encryptor failed: Cannot compile file '%s'.", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }

    if (JS_CallFunctionName(cx, global, function, argc, argv, ret) == JS_FALSE){
        printf("Execution of encryptor failed: Cannot execute function '%s' with string parameter.", function);
        free(expandedFilename);
        return JS_FALSE;
    }

    free(expandedFilename);
    return JS_TRUE;
}

Now all that is left is to pass the new encrypted string back to the main function so it can be printed out on the screen. If the function runs properly, then the returned string will be in the ret parameter we passed into executeJSFunction. The value should be a string, so we will cast it to a JSString using JS_ValueToString. We will then convert the string from a JSString to a C char * string using the function JS_GetStringBytes.

encrypt.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <jsapi.h>
#include "encrypt.h"

static void js_error_handler(JSContext *ctx, const char *msg, JSErrorReport *er);
static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret);
JSClass globalObjectClass = {
    "global",
    0,

    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    JS_FinalizeStub,

    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};


char *encryptData(EncryptionTypes *type, const char *data){
    JSRuntime *rt=NULL;
    JSContext *cx=NULL;
    jsval *parameters=NULL;
    jsval ret;
    JSString *stdinData=NULL;
    char *stdinDataBuffer;


    rt = JS_NewRuntime(1073741824);
    if (rt == NULL){
        printf("Failed to start Spidermonkey, cannot perform encryption.\n");
        return strdup(data);
    }

    cx = JS_NewContext(rt, 8192);
    if (cx == NULL){
        JS_DestroyRuntime(rt);
        printf("Failed to start Spidermonkey, cannot perform encryption.\n");
        return strdup(data);
    }

    JS_SetErrorReporter(cx, js_error_handler);

    /* Spidermonkey manages the memory space of it's strings, so we need to duplicate the passed string.
    * We must allocate the memory using Spidermonkey's JS_malloc function though, so that it is safe for
    * Spidermonkey to manage it.
    */
    stdinDataBuffer = JS_malloc(cx, strlen(data));
    memcpy(stdinDataBuffer, data, strlen(data));

    stdinData = JS_NewString(cx, stdinDataBuffer, strlen(stdinDataBuffer));

    parameters=JS_malloc(cx, sizeof(*parameters));
    parameters[0] = STRING_TO_JSVAL(stdinData);

    /* Execute the javascript file. */
    if (executeJSFunction(cx, type->filePath, type->functionName, 1, parameters, &ret) == JS_TRUE){
        JSString *retString = JS_ValueToString(cx, ret);
        char *encryptedString = JS_GetStringBytes(retString);
        char *returnedString;


        /* We duplicate the string returned by JS_GetStringBytes because when we clean up the
        * Runtime and Context the string pointed to by encryptedString will be cleaned up and
        * invalid.  Therefore we need our own copy if we are going to use it after the cleanup.
        */
        returnedString = strdup(encryptedString);

        JS_DestroyContext(cx);
        JS_DestroyRuntime(rt);
        return returnedString;
    }

    /* Cleanup Spidermonkey */
    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    return strdup(data);
}


static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret){
    char *expandedFilename = expandTilda(file);
    struct stat fileDetails;
    JSObject *global=NULL;
    JSScript *script=NULL;


    if (stat(expandedFilename, &fileDetails) == -1){
        printf("Encryption code file '%s' does not exist.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }


    /* Create a global object */
    global = JS_NewObject(cx, &globalObjectClass, NULL, NULL);
    if (global == NULL){
        printf("Execution of encryptor failed: Cannot create global object.\n");
        free(expandedFilename);
        return JS_FALSE;
    }

    JS_InitStandardClasses(cx, global);

    script = JS_CompileFile(cx, global, expandedFilename);
    if (script == NULL){
        printf("Execution of encryptor failed: Cannot compile file '%s'.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }

    if (JS_CallFunctionName(cx, global, function, argc, argv, ret) == JS_FALSE){
        printf("Execution of encryptor failed: Cannot execute function '%s' with string parameter.\n", function);
        free(expandedFilename);
        return JS_FALSE;
    }

    free(expandedFilename);
    return JS_TRUE;
}

So there we go, that is a complete application for executing a file. During compiling I found some bugs with a couple free's on data which was not allocated, so I fixed those errors in the above code as well.

Uh oh

So, I try and run my newly created program. For my encryptRot13 function all I did was tell it to return the string as it is, haven't even bothered to program that yet, and I find that it does not even work doing that. I notice that it errors out on calling the function, but because I don't have any error reporting code in here to report compile errors of a JS file, I can't get much more info. As such, I decided I better add my error handler code that I wrote a few articles back into the mix so I can get a better idea of what is going on.

encrypt.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <jsapi.h>
#include "encrypt.h"

static void js_error_handler(JSContext *ctx, const char *msg, JSErrorReport *er);
static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret);
JSClass globalObjectClass = {
    "global",
    0,

    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    JS_FinalizeStub,

    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};


char *encryptData(EncryptionTypes *type, const char *data){
    JSRuntime *rt=NULL;
    JSContext *cx=NULL;
    jsval *parameters=NULL;
    jsval ret;
    JSString *stdinData=NULL;
    char *stdinDataBuffer;


    rt = JS_NewRuntime(1073741824);
    if (rt == NULL){
        printf("Failed to start Spidermonkey, cannot perform encryption.\n");
        return strdup(data);
    }

    cx = JS_NewContext(rt, 8192);
    if (cx == NULL){
        JS_DestroyRuntime(rt);
        printf("Failed to start Spidermonkey, cannot perform encryption.\n");
        return strdup(data);
    }

    JS_SetErrorReporter(cx, js_error_handler);

    /* Spidermonkey manages the memory space of it's strings, so we need to duplicate the passed string.
    * We must allocate the memory using Spidermonkey's JS_malloc function though, so that it is safe for
    * Spidermonkey to manage it.
    */
    stdinDataBuffer = JS_malloc(cx, strlen(data));
    memcpy(stdinDataBuffer, data, strlen(data));

    stdinData = JS_NewString(cx, stdinDataBuffer, strlen(stdinDataBuffer));

    parameters=JS_malloc(cx, sizeof(*parameters));
    parameters[0] = STRING_TO_JSVAL(stdinData);

    /* Execute the javascript file. */
    if (executeJSFunction(cx, type->filePath, type->functionName, 1, parameters, &ret) == JS_TRUE){
        JSString *retString = JS_ValueToString(cx, ret);
        char *encryptedString = JS_GetStringBytes(retString);
        char *returnedString;


        /* We duplicate the string returned by JS_GetStringBytes because when we clean up the
        * Runtime and Context the string pointed to by encryptedString will be cleaned up and
        * invalid.  Therefore we need our own copy if we are going to use it after the cleanup.
        */
        returnedString = strdup(encryptedString);

        JS_DestroyContext(cx);
        JS_DestroyRuntime(rt);
        return returnedString;
    }

    /* Cleanup Spidermonkey */
    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    return strdup(data);
}


static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret){
    char *expandedFilename = expandTilda(file);
    struct stat fileDetails;
    JSObject *global=NULL;
    JSScript *script=NULL;


    if (stat(expandedFilename, &fileDetails) == -1){
        printf("Encryption code file '%s' does not exist.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }


    /* Create a global object */
    global = JS_NewObject(cx, &globalObjectClass, NULL, NULL);
    if (global == NULL){
        printf("Execution of encryptor failed: Cannot create global object.\n");
        free(expandedFilename);
        return JS_FALSE;
    }

    JS_InitStandardClasses(cx, global);

    script = JS_CompileFile(cx, global, expandedFilename);
    if (script == NULL){
        printf("Execution of encryptor failed: Cannot compile file '%s'.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }

    if (JS_CallFunctionName(cx, global, function, argc, argv, ret) == JS_FALSE){
        printf("Execution of encryptor failed: Cannot execute function '%s' with string parameter.\n", function);
        free(expandedFilename);
        return JS_FALSE;
    }

    free(expandedFilename);
    return JS_TRUE;
}

static void js_error_handler(JSContext *ctx, const char *msg, JSErrorReport *er){
    char *pointer=NULL;
    char *line=NULL;
    int len;

    if (er->linebuf != NULL){
        len = er->tokenptr - er->linebuf + 1;
        pointer = malloc(len);
        memset(pointer, '-', len);
        pointer[len-1]='\0';
        pointer[len-2]='^';

        len = strlen(er->linebuf)+1;
        line = malloc(len);
        strncpy(line, er->linebuf, len);
        line[len-1] = '\0';
    }
    else {
        len=0;
        pointer = malloc(1);
        line = malloc(1);
        pointer[0]='\0';
        line[0] = '\0';
    }

    while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n')){ line[len-1]='\0'; len--; }

    printf("JS Error: %s\nFile: %s:%u\n", msg, er->filename, er->lineno);
    if (line[0]){
        printf("%s\n%s\n", line, pointer);
    }

    free(pointer);
    free(line);
}

After adding that error reporting code I see the following error appear in the output:

JS Error: TypeError: undefined is not a function
File: (null):0
Execution of encryptor failed: Cannot execute function 'encryptRot13' with string parameter.

So, that isn't really helpful. I have no idea really what that error is trying to tell me. I decided to look back at the previous SpiderMonkey stuff I have done (hurray for my articles) and came up with the thought that I need to probably execute the script after compiling it, in order for the function to become defined and known to SpiderMonkey. As such, I add a simple call to JS_ExecuteScript after I compile the script. I don't really care about the return value (rval) as it is not the return value we want.

With that line added, my script appears to be working now. To ensure it is, I change my encryptRot13 function so it just returns the static string "Hello" and run the program. Sure enough, here is what I get as a result:

kicken@bonzi:~/data/personal/2008/01/25/files/r3$ ./jsencrypt rot13 <test.js
STDIN Data to Encrypt (with rot13):
//Lets test some sockets.
try {
    var socket = new Socket();
    Console.writeln("Socket object created successfully.");

    //Check the constants
    Console.writeln("SOL_TCP = " + Socket.SOL_TCP);

    //Connect to my website
    //socket.connect({});
    socket.connect({family:Socket.PF_INET, details:{port:80,address:"aoeex.com"}});

    //Make a request
    socket.write("GET / HTTP/1.0\r\nHost: aoeex.com\r\n\r\n");

    //Read the response
    while ((data=socket.read()).length > 0){
        //Read the banner
        Console.writeln(data);
    }

    //Close up shop
    socket.close();
}
catch (e){
    Console.writeln ("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
    Console.writeln("Caught exception. ");
    Console.writeln(e);
}

Encrypted data:

Hello

So, all I have to do is implement the rot13 in javascript, and my work here is done! As always, the files are all available for download below. Leave your comments below.

Attachments

  • r1.tar.bz2 -- This is the base project before SpiderMonkey has been implemented.
  • r2.tar.bz2 -- This is the project after SpiderMonkey has been implemented, but prior to the final bug fixes.
  • r3.tar.bz2 -- This is the final outcome of this tutorial, with a complete and working application.