Previous     Contents     Index     Next     
iPlanet Web Server, Enterprise Edition Server-Side JavaScript Guide



Chapter 7   Other JavaScript Functionality


This chapter describes additional server-side JavaScript functionality you can use to send email messages from you application, access the server file system, include external libraries in your application, or directly manipulate client requests and client responses.

This chapter contains the following sections:



Mail Service

Your application may need to send an email message. You use an instance of the SendMail class for this purpose. The only methods of SendMail are send, to send the message, and errorCode and errorMessage, to interpret an error.

For example, the following script sends mail to vpg with the specified subject and body for the message:


<server>
SMName = new SendMail();
SMName.To = "vpg@royalairways.com";
SMName.From = "thisapp@netscape.com";
SMName.Subject = "Here's the information you wanted";
SMName.Body = "sharm, maldives, phuket, coral sea, taveuni, maui,
   cocos island, marathon cay, san salvador";
SMName.send();
</server>


The following table describes the properties of the SendMail class. The To and From properties are required; all other properties are optional.


Table 7-1    Properties of the SendMail class  

To  

A comma-delimited list of primary recipients of the message.  

From  

The user name of the person sending the message.  

Cc  

A comma-delimited list of additional recipients of the message.  

Bcc  

A comma-delimited list of recipients of the message whose names should not be visible in the message.  

Smtpserver  

The mail (SMTP) server name. This property defaults to the value specified through the setting in the administration server.  

Subject  

The subject of the message.  

Body  

The text of the message.  

In addition to these properties, you can add any other properties you wish. All properties of the SendMail class are included in the header of the message when it is actually sent. For example, the following code sends a message to bill from vpg, setting vpg's organization field to Royal Airways. Replies to the message go to vpgboss.


mailObj["Reply-to"] = "vpgboss";
mailObj.Organization = "Royal Airways";
mailObj.From = "vpg";
mailObj.To = "bill";
mailObj.send();


For more information on predefined header fields, refer to RFC 822, the standard for the format of internet text messages.

The SendMail class allows you to send either simple text-only mail messages or complex MIME-compliant mail. You can also add attachments to your message. To send a MIME message, add a Content-type property to the SendMail object and set its value to the MIME type of the message.

For example, the following code segment sends a GIF image:


<server>
SMName = new SendMail();
SMName.To = "vpg@royalairways.com";
SMName.From = "thisapp@netscape.com";
SMName.Subject = "Here's the image file you wanted";
SMName["Content-type"] = "image/gif";
SMName["Content-Transfer-Encoding"] = "base64";

// In this next statement, image2.gif must be base 64 encoded.
// If you use uuencode to encode the GIF file, delete the header
// (for example, "begin 644 image2.gif") and the trailer ("end").
fileObj = new File("/usr/somebody/image2.gif");

openFlag = fileObj.open("r");
if ( openFlag ) {
   len = fileObj.getLength();
   SMName.Body = fileObj.read(len);
   SMName.send();
   }
</server>


Some MIME types may need more information. For example, if the content type is multipart/mixed, you must also specify a boundary separator for one or more different sets of data in the body. For example, the following code sends a multipart message containing two parts, both of which are plain text:


<server>
SMName = new SendMail();
SMName.To = "vpg@royalairways.com";
SMName.From = "thisapp@netscape.com";
SMName.Subject = "Here's the information you wanted";
SMName["Content-type"]
   = "multipart/mixed; boundary=\"simple boundary\"";
fileObj = new File("/usr/vpg/multi.txt");
openFlag = fileObj.open("r");
if ( openFlag ) {
   len = fileObj.getLength();
   SMName.Body = fileObj.read(len);
   SMName.send();
   }
</server>


Here the file multi.txt contains the following multipart message:


This is the place for preamble.
It is to be ignored.
It is a handy place for an explanatory note to non-MIME compliant readers.
--simple boundary

This is the first part of the body.
This does NOT end with a line break.

--simple boundary
Content-Type: text/plain; charset=us-ascii

This is the second part of the body.
It DOES end with a line break

--simple boundary--
This is the epilogue. It is also to be ignored.


You can nest multipart messages. That is, if you have a message whose content type is multipart, you can include another multipart message in its body. In such cases, be careful to ensure that each nested multipart entity uses a different boundary delimiter.

For details on MIME types, refer to RFC 13411, the MIME standard. For more information on sending mail messages with JavaScript, see the description of this class in the Server-Side JavaScript Reference.



File System Service



JavaScript provides a File class that enables applications to write to the server's file system. This is useful for generating persistent HTML files and for storing information without using a database server. One of the main advantages of storing information in a file instead of in JavaScript objects is that the information is preserved even if the server goes down.


Security Considerations

Exercise caution when using the File class. A JavaScript application can read or write files anywhere the operating system allows, potentially including sensitive system files. You should be sure your application does not allow an intruder to read password files or other sensitive information or to write files at will. Take care that the filenames you pass to its methods cannot be modified by an intruder.

For example, do not use client or request properties as filenames, because the values may be accessible to an intruder through cookies or URLs. In such cases, the intruder can modify cookie or URL values to gain access to sensitive files.

For similar security reasons, Navigator does not provide automatic access to the file system of client machines. If needed, the user can save information directly to the client file system by making appropriate menu choices in Navigator.


Creating a File Object

To create an instance of the File class, use the standard JavaScript syntax for object creation:

fileObjectName = new File("path");

Here, fileObjectName is the name by which you refer to the file, and path is the complete file path. The path should be in the format of the server's file system, not a URL path.

You can display the name of a file by using the write function, with the File object as its argument. For example, the following statement displays the filename:

x = new File("\path\file.txt");
write(x);


Opening and Closing a File

Once you have created a File object, you use the open method to open the file so that you can read from it or write to it. The open method has the following syntax:

result = fileObjectName.open("mode");

This method returns true if the operation is a success and false otherwise. If the file is already open, the operation fails and the original file remains open.

The parameter mode is a string that specifies the mode in which to open the file. The following table describes how the file is opened for each mode.


Table 7-2    File-access modes  

Mode

Description

r  

Opens the file, if it exists, as a text file for reading and returns true. If the file does not exist, returns false.  

w  

Opens the file as a text file for writing. Creates a new (initially empty) text file whether or not the file exists.  

a  

Opens the file as a text file for appending (writing at the end of the file). If the file does not already exist, creates it.  

r+  

Opens the file as a text file for reading and writing. Reading and writing commence at the beginning of the file. If the file exists, returns true. If the file does not exist, returns false.  

w+  

Opens the file as a text file for reading and writing. Creates a new (initially empty) file whether or not the file already exists.  

a+  

Opens the file as a text file for reading and writing. Reading and writing commence at the end of the file. If the file does not exist, creates it.  

b  

When appended to any of the preceding modes, opens the file as a binary file rather than a text file. Applicable only on Windows operating systems.  

When an application has finished using a file, it can close the file by calling the close method. If the file is not open, close fails. This method returns true if successful and false otherwise.


Locking Files

Most applications can be accessed by many users simultaneously. In general, however, different users should not try to make simultaneous changes to the same file, because unexpected errors may result.

To prevent multiple users from modifying a file at the same time, use one of the locking mechanisms provided by the Session Management Service, as described in "Sharing Objects Safely with Locking." If one user has the file locked, other users of the application wait until the file becomes unlocked. In general, this means you should precede all file operations with lock and follow them with unlock.

If only one application can modify the same file, you can obtain the lock within the project object. If more than one application can access the same file, however, obtain the lock within the server object.

For example, suppose you have created a file called myFile. Then you could use it as follows:


if ( project.lock() ) {
   myFile.open("r");
   // ... use the file as needed ...
   myFile.close();
   project.unlock();
}


In this way, only one user of the application has modify to the file at one time. Alternatively, for finer locking control you could create your own instance of the Lock class to control access to a file. This is described in "Using Instances of Lock."


Working with Files

The File class has a number of methods that you can use once a file is open:

  • Positioning: setPosition, getPosition, eof. Use these methods to set and get the current pointer position in the file and determine whether the pointer is at the end of the file.

  • Reading from a file: read, readln, readByte.

  • Writing to a file: write, writeln, writeByte, flush.

  • Converting between binary and text formats: byteToString, stringToByte. Use these methods to convert a single number to a character and vice versa.

  • Informational methods: getLength, exists, error, clearError. Use these methods to get information about a file and to get and clear error status.

The following sections describe these methods.


Positioning Within a File

The physical file associated with a File object has a pointer that indicates the current position in the file. When you open a file, the pointer is either at the beginning or at the end of the file, depending on the mode you used to open it. In an empty file, the beginning and end of the file are the same.

The setPosition method positions the pointer within the file, returning true if successful and false otherwise.

fileObj.setPosition(position);
fileObj.setPosition(position, reference);

Here, fileObj is a File object, position is an integer indicating where to position the pointer, and reference indicates the reference point for position, as follows:

  • 0: relative to beginning of file

  • 1: relative to current position

  • 2: relative to end of file

  • Other (or unspecified): relative to beginning of file

The getPosition method returns the current position in the file, where the first byte in the file is always byte 0. This method returns -1 if there is an error.

fileObj.getPosition();

The eof method returns true if the pointer is at the end of the file and false otherwise. This method returns true after the first read operation that attempts to read past the end of the file.

fileObj.eof();


Reading from a File

Use the read, readln, and readByte methods to read from a file.

The read method reads the specified number of bytes from a file and returns a string.

fileObj.read(count);

Here, fileObj is a File object, and count is an integer specifying the number of bytes to read. If count specifies more bytes than are left in the file, then the method reads to the end of the file.

The readln method reads the next line from the file and returns it as a string.

fileObj.readln();

Here, fileObj is a File object. The line-separator characters (either \r\n on Windows or just \n on Unix or Macintosh) are not included in the string. The character \r is skipped; \n determines the actual end of the line. This compromise gets reasonable behavior on all platforms.

The readByte method reads the next byte from the file and returns the numeric value of the next byte, or -1.

fileObj.readByte();


Writing to a File

The methods for writing to a file are write, writeln, writeByte, and flush.

The write method writes a string to the file. It returns true if successful and false otherwise.

fileObj.write(string);

Here, fileObj is a File object, and string is a JavaScript string.

The writeln method writes a string to the file, followed by \n (\r\n in text mode on Windows). It returns true if the write was successful and false otherwise.

fileObj.writeln(string);

The writeByte method writes a byte to the file. It returns true if successful and false otherwise.

fileObj.writeByte(number);

Here, fileObj is a File object and number is a number.

When you use any of these methods, the file contents are buffered internally. The flush method writes the buffer to the file on disk. This method returns true if successful and false otherwise.

fileObj.flush();


Converting Data

There are two primary file formats: ASCII text and binary. The byteToString and stringToByte methods of the File class convert data between these formats.

The byteToString method converts a number into a one-character string. This method is static. You can use the File class object itself, and not an instance, to call this method.

File.byteToString(number);

If the argument is not a number, the method returns the empty string.

The stringToByte method converts the first character of its argument, a string, into a number. This method is also static.

File.stringToByte(string);

The method returns the numeric value of the first character, or 0.


Getting File Information

You can use several File methods to get information on files and to work with the error status.

The getLength method returns the number characters in a text file or the number of bytes in any other file. It returns -1 if there is an error.

fileObj.getLength();

The exists method returns true if the file exists and false otherwise.

fileObj.exists();

The error method returns the error status, or -1 if the file is not open or cannot be opened. The error status is a nonzero value if an error occurred and 0 otherwise (no error). Error status codes are platform dependent; refer to your operating system documentation.

fileObj.error();

The clearError method clears both the error status (the value of error) and the value of eof.

fileObj.clearError();


Example

Netscape servers include the Viewer sample application in its directory structure. Because this application allows you to view any files on the server, it is not automatically installed.

Viewer gives a good example of how to use the File class. If you install it, be sure to restrict access so that unauthorized persons cannot view files on your server. For information on restricting access to an application, see "Deploying an Application."

The following code from the viewer sample application creates a File class, opens it for reading, and generates HTML that echoes the lines in the file, with a hard line break after each line.


x = new File("\tmp\names.txt");
fileIsOpen = x.open("r");
if (fileIsOpen) {
   write("file name: " + x + "<BR>");
   while (!x.eof()) {
      line = x.readln();
      if (!x.eof())
         write(line+"<br>");
   }
   if (x.error() != 0)
      write("error reading file" + "<BR>");
   x.close();
}




Working with External Libraries



The recommended way to communicate with external applications is using LiveConnect, as described in Chapter 14 "LiveConnect Overview." However, you can also call functions written in languages such as C, C++, or Pascal and compiled into libraries on the server. Such functions are called native functions or external functions. Libraries of native functions, called external libraries, are dynamic link libraries on Windows operating systems and shared objects on Unix operating systems.



Note Be careful when using native functions with your application. Native functions can compromise security if the native program processes a command-line entry from the user (for example, a program that allows users to enter operating system or shell commands). This functionality is dangerous because an intruder can attach additional commands using semicolons to append multiple statements. It is best to avoid command-line input, unless you strictly check it.



Using native functions in an application is useful in these cases:

  • If you already have complex functions written in native code that you can use in your application.

  • If the application requires computation-intensive functions. In general, functions written in native code run faster than those written in JavaScript.

  • If the application requires some other task you cannot do in JavaScript.

The sample directory jsaccall contains source and header files illustrating how to call functions in external libraries from a JavaScript application.

In the Application Manager, you associate an external library with a particular application. However, once associated with any installed application, an external library can be used by all installed applications.

Follow these steps to use a native function library in a JavaScript application:

  1. Write and compile an external library of native functions in a form compatible with JavaScript. (See "Guidelines for Writing Native Functions.")

  2. With the Application Manager, identify the library to be used by installing a new application or modifying installation parameters for an existing application. Once you identify an external library using the Application Manager, all applications on the server can call external functions in that library. (See "Identifying Library Files.")

  3. Restart the server to load the library with your application. The functions in the external library are now available to all applications on the server.

  4. In your application, use the JavaScript functions registerCFunction to identify the library functions to be called and callC to call those functions. (See "Registering Native Functions" and "Using Native Functions in JavaScript.")

  5. Recompile and restart your application for the changes to take effect.



    Note You must restart your server to install a library to use with an application. You must restart the server any time you add new library files or change the names of the library files used by an application.




Guidelines for Writing Native Functions

Although you can write external libraries in any language, JavaScript uses C calling conventions. Your code must include the header file jsaccall.h provided in plugins\samples\js\jsaccall\.

This directory also includes the source code for a sample application that calls a C function defined in jsaccall.c. Refer to these files for more specific guidelines on writing C functions for use with JavaScript.

Functions to be called from JavaScript must be exported and must conform to this type definition:


typedef void (*LivewireUserCFunction)
   (int argc, struct LivewireCCallData argv[],
    struct LivewireCCallData* result, pblock* pb,
    Session* sn, Request* rq);



Identifying Library Files

Before you can run an application that uses native functions in external libraries, you must identify the library files. Using the Application Manager, you can identify libraries when you initially install an application (by clicking Add) or when you modify an application's installation parameters (by clicking Modify). For more information on identifying library files with the Application Manager, see "Installing a New Application."



Note After you enter the paths of library files in the Application Manager, you must restart your server for the changes to take effect. You must then be sure to compile and restart your application.



Once you have identified an external library using the Application Manager, all applications running on the server can call functions in the library (by using registerCFunction and callC).


Registering Native Functions

Use the JavaScript function registerCFunction to register a native function for use with a JavaScript application. This function has the following syntax:

registerCFunction(JSFunctionName, libraryPath, CFunctionName);

Here, JSFunctionName is the name of the function as it will be called in JavaScript with the callC function. The libraryPath parameter is the full pathname of the library, using the conventions of your operating system and the CFunctionName parameter is the name of the C function as it is defined in the library. In this method call, you must use the exact case shown in the Application Manager, even on NT.



Note Backslash (\) is a special character in JavaScript, so you must use either forward slash (/) or a double backslash (\\) to separate Windows directory and filenames in libraryPath.



This function returns true if it registers the function successfully and false otherwise. The function might fail if JavaScript cannot find the library at the specified location or the specified function inside the library.

An application must use registerCFunction to register a function before it can use callC to call it. Once the application registers the function, it can call the function any number of times. A good place to register functions is in an application's initial page.


Using Native Functions in JavaScript

Once your application has registered a function, it can use callC to call it. This function has the following syntax:

callC(JSFunctionName, arguments);

Here, JSFunctionName is the name of the function as it was identified with registerCFunction and arguments is a comma-delimited list of arguments to the native function. The arguments can be any JavaScript values: strings, numbers, Boolean values, objects, or null. The number of arguments must match the number of arguments required by the external function. Although you can specify a JavaScript object as an argument, doing so is rarely useful, because the object is converted to a string before being passed to the external function.

This function returns a string value returned by the external function. The callC function can return only string values.

The jsaccall sample JavaScript application illustrates the use of native functions. The jsaccall directory includes C source code (in jsaccall.c) that defines a C function named mystuff_EchoCCallArguments. This function accepts any number of arguments and then returns a string containing HTML listing the arguments. This sample illustrates calling C functions from a JavaScript application and returning values.

To run jsaccall, you must compile jsaccall.c with your C compiler. Command lines for several common compilers are provided in the comments in the file.

The following JavaScript statements (taken from jsaccall.html) register the C function as echoCCallArguments in JavaScript, call the function echoCCallArguments, and then generate HTML based on the value returned by the function.


var isRegistered = registerCFunction("echoCCallArguments",
   "c:\\mycode\\mystuff.dll", "mystuff_EchoCCallArguments");
if (isRegistered == true) {
   var returnValue = callC("echoCCallArguments",
      "first arg",
      42,
      true,
      "last arg");
   write(returnValue);
}
else {
   write("registerCFunction() returned false, "
      + "check server error log for details")
}


The echoCCallArguments function creates a string result containing HTML that reports both the type and the value of each of the JavaScript arguments passed to it. If the registerCFunction returns true, the code above generates this HTML:


argc = 4<BR>
argv[0].tag: string; value = first arg<BR>
argv[1].tag: double; value = 42<BR>
argv[2].tag: boolean; value = true<BR>
argv[3].tag: string; value = last arg<BR>




Request and Response Manipulation



A typical request sent by the client to the server has no content type. The JavaScript runtime engine automatically handles such requests. However, if the user submits a form, then the client automatically puts a content type into the header to tell the server how to interpret the extra form data. That content type is usually application/x-www-form-urlencoded. The runtime engine also automatically handles requests with this content type. In these situations, you rarely need direct access to the request or response header. If, however, your application uses a different content type, it must be able to manipulate the request header itself.

Conversely, the typical response sent from the server to the client has the text/html content type. The runtime engine automatically adds that content type to its responses. If you want a different content type in the response, you must provide it yourself.

To support these needs, the JavaScript runtime engine on the server allows your application to access (1) the header of any request and (2) the raw body of a request that has a nonstandard content type. You already control the body of the response through the SERVER tag and your HTML tags. The functionality described in this section also allows you to control the header of the response.

You can use this functionality for various purposes. For example, as described in "Using Cookies," you can communicate between the client and server processes using cookies. Also, you can use this functionality to support a file upload.

The World Wide Web Consortium publishes online information about the HTTP protocol and information that can be sent using that protocol. See, for example, HTTP Specifications and Drafts.


Request Header

To access the name/value pairs of the header of the client request, use the httpHeader method of the request object. This method returns an object whose properties and values correspond to the name/value pairs of the header.

For example, if the request contains a cookie, header["cookie"] or header.cookie is its value. The cookie property, containing all of the cookie's name/value pairs (with the values encoded as described in "Using Cookies"), must be parsed by your application.



Note

If the client object maintenance technique of an application is configured to use server-cookie, the client properties of the application are restored in subsequent requests only if the same URL is used. For example, even if 123.456.78.90 is the IP address for myserver, the following two requests are not considered to be the same:

http://myserver/hangman
http://123.456.78.90/hangman

The following code prints the properties and values of the header:

var header = request.httpHeader();
var count = 0;
var i;

for (i in header ) {
   write(count + ". " + i + " " + header[i] + "<br>\n");
   count++;
}

If you submitted a form using the GET method, your output might look like this:

0. connection Keep-Alive
1. user-agent Mozilla/4.1b1 (WinNT; I)
2. host piccolo:2020
3. accept image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*

If you used the POST method to submit your form, your output might look like this: 0. referer http://piccolo:2020/world/hello.html
1. connection Keep-Alive
2. user-agent Mozilla/4.1b1 (WinNT; I)
3. host piccolo:2020
4. accept image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
5. cookie NETSCAPE_LIVEWIRE.oldname=undefined; NETSCAPE_LIVEWIRE.number=0
6. content-type multipart/form-data; boundary=---------------------------79741602416605
7. content-length 208




Request Body

For normal HTML requests, the content type of the request is application/x-www-form-urlencoded. Upon receiving a request with this content type, the JavaScript runtime engine on the server processes the request using the data in the body of the request. In this situation, you cannot directly access the raw data of the request body. (Of course, you can access its content through the request and client objects constructed by the runtime engine.)

If, however, the request has any other content type, the runtime engine does not automatically process the request body. In this situation, it is up to your application to decide what to do with the content.

Presumably, another page of your application posted the request for this page. Therefore, your application must expect to receive unusual content types and should know how to handle them.

To access the body of a request, you use the getPostData method of the request object. This method takes as its parameter the number of characters of the body to return. If you specify 0, it returns the entire body. The return value is a string containing the requested characters. If there is no available data, the method returns the empty string.

You can use this method to get all of the characters at once, or you can read chunks of data. Think of the body of the request as a stream of characters. As you read them, you can only go forward; you can't read the same characters multiple times.

To assign the entire request body to the postData variable, you can use the following statement:

postData = request.getPostData(0);

If you specify 0 as the parameter, the method gets the entire request. You can explicitly find out how many characters are in the information using the header's content-length property, as follows:

length = parseInt(header["content-length"], 10);

To get the request body in smaller chunks, you can specify a different parameter. For example, the following code processes the request body in chunks of 20 characters:


var length = parseInt(header["content-length"], 10);
var i = 0;

while (i < length) {
   postData = request.getPostData(20);
   // ...process postData...
   i = i + 20;
}


Of course, this would be a sensible approach only if you knew that chunks consisting of 20 characters of information were meaningful in the request body.


Response Header

If the response you send to the client uses a custom content type, you should encode this content type in the response header. The JavaScript runtime engine automatically adds the default content type (text/html) to the response header. If you want a custom header, you must first remove the old default content type from the header and then add the new one. You do so with the addResponseHeader and deleteResponseHeader functions.

For example, if your response uses royalairways-format as a custom content type, you would specify it this way:

deleteResponseHeader("content-type");
addResponseHeader("content-type","royalairways-format");

You can use the addResponseHeader function to add any other information you want to the response header.



Note

Remember that the header is sent with the first part of the response. Therefore, you should call these functions early in the script on each page. In particular, you should ensure that the response header is set before any of these happen:

  • The runtime engine generates 64KB of content for the HTML page (it automatically flushes the output buffer at this point).

  • You call the flush function to clear the output buffer.

  • You call the redirect function to change client requests.



For more information, see "Flushing the Output Buffer" and "Runtime Processing on the Server."


Previous     Contents     Index     Next     
Copyright © 2000 Sun Microsystems, Inc. Some preexisting portions Copyright © 2000 Netscape Communications Corp. All rights reserved.

Last Updated August 09, 2000