Chapter 4. Configuring for Chrome and a Server

In this chapter, we will move our application from its home in a test directory to a setting that is more consistent with commercial implementations.

We will create two key pieces for implementing our application:

  • A local XUL application communicating with a Personal Hypertext Processor (PHP) server

  • A remote XUL application being served by PHP scripts

Both implementations will use an SQL database to hold user and password information to conduct the authentication process. Figure 4-1 shows a block diagram illustrating the relationship of the main elements of our design.

NewsSearch information flow
Figure 4-1. NewsSearch information flow

Chrome Overview

This book uses the term XUL application to describe our NewsSearch project. This differs from conventional web applications that are designed to be served web pages that are rendered by web browsers. A XUL application, however, can be implemented as a page served to a XUL-capable browser, or it can be configured to run as an application local to the user’s machine. In the case of the latter, the files that comprise the application (JavaScript source, stylesheets, datafiles) are installed in a chrome directory and run as a chrome URL.

To date, the most popular XUL applications, such as the Firefox browser, Thunderbird mailer, and Sunbird calendar, are implemented as bundled applications running from the user’s chrome directory.

Web developers refer to traditional web applications as being subject to the rules of a security sandbox. Originating from the Java language’s security policy, the sandbox philosophy limits the reach of executable code to a certain area. For browsers, this means that unprivileged JavaScript can access data from a served document and from documents sharing its URL, but the browser infrastructure allows no access to the local filesystem or to potentially destructive operating system and network services.

There are cases when the local browser needs to store information on a user’s computer. This information may include bookmarks, runtime preferences, or other saved settings that would impair the user experience if not kept locally accessible; the sandbox is therefore expanded to include a controlled portion of the local filesystem. Applications that are registered within Firefox’s chrome are granted an area on the local disk to which full read/write access is granted.

In addition to meeting the security requirements imposed by the Firefox framework, a chrome application has a different look to it.

Before launching our code as a chrome application, we should change the testStyles.css stylesheet to provide a default background color for our window:

window {
 background-color:white;
}

(The default window color is needed when launching a chrome application from the command line.)

To see what our test application would look like as a chrome application, open a command window and launch Firefox from the command line, specifying the -chrome option along with the pathname to the source file. The -chrome option directs the framework to display the source as a chrome window, not as a browser window.

On an OS X machine, the code would look like this:

theUser% pwd
theUser% /Applications/Firefox.app/Contents/MacOS
theUser% ./firefox -chrome "file:Macintosh HD:tests:theWindowWithLogin.xul"

Our NewsSearch application now appears as a chrome window with none of the controls we associate with a browser (see Figure 4-2).

Application launched as a chrome window
Figure 4-2. Application launched as a chrome window

Simply launching an application with the -chrome option and a file URL will give us a chrome appearance, but to run it as a client/server chrome application, we will need to install the application as a chrome package, or serve the interface as a XUL page.

Running as a Local Installation

We will first look at what we need to install the application on the local machine and to communicate with a server through a form of HTTP request. For both versions, we must have an Apache web server running, PHP installed, and an SQL database up and running.

Chrome Directory Structure

An application registered as a chrome package is not required to be located in a specific directory; convention, however, has placed most chrome applications within Firefox’s chrome directory. The chrome directory is located in the same directory as the binary executable for Firefox. Most of the files in the chrome directory are .jar files, or Java archive files.

Tip

Although XUL applications use the Java archive as a preferred distribution medium, the source code files are not written in Java. XUL applications are developed as JavaScript and XUL source files.

To see the content of any of the archives, use the jar -tf command. For example, to view the contents of the inspector.jar file, change to the chrome directory and type the following:

jar -tf inspector.jar

If we were to do the same for all the archive files, we would see that they share a common directory structure:

Content

For user interface (XUL) files, stylesheets, and scripts

Skin

Stylesheets and images that collectively provide a theme to an appearance

Locale

To provide multiple-language support for interface widgets

For our application, we will work with only the content root.

Before we continue installing our application in the Firefox chrome directory, we need to understand that such an application is referred to through a special chrome URL. The form of this URL is:

chrome://<package>/<part>/<fileName>

The package and part names are consistent with conventions used for .jar files. This URL instructs the Firefox framework to scan its installed packages to access a filename located as part of a specific package. Once our application is registered, we would use a command line with the -chrome option (to open the file in a chrome window) or specify a chrome URL (to launch the application with chrome privileges):

firefox -chrome chrome://<package>/<part>/<filename>

Figure 4-3 illustrates an example directory structure.

Chrome directory structure
Figure 4-3. Chrome directory structure

Package Registration

Every time the Firefox browser launches, its framework looks for any file with an extension of .manifest to inform it of the chrome content packages, skins, or locales that need to be loaded. To inform Firefox of our NewsSearch application, we will use a text editor to create a line for a file named localApps.manifest (any unique filename with a .manifest extension will do) and save it in Firefox’s chrome directory:

content newssearch NewsSearch/content/newssearch.xul

The format of this line consists of space-delimited entries as follows:

  • The first field, content, declares the installation to be a content package. Other options include skin and locale.

  • The newssearch entry names the package.

  • The last entry is the source file location relative to the chrome directory.

We can now launch Firefox and type the chrome URL directly in the locator window to launch the same application, but within a browser window (see Figure 4-4).

Specifying the package name, and specifying the package type as a chrome URL
Figure 4-4. Specifying the package name, and specifying the package type as a chrome URL

Once we make this entry in the URL bar, Firefox will autocomplete the line to add the filename that matches the package name—in this case, our newssearch.xul file.

Alternatively, we could launch our application directly from the command line, using the -chrome option and a chrome URL that specifies our package name and package type:

firefox -chrome chrome://newssearch/content/

Figure 4-5 illustrates the results of launching our chrome application from the command line and entering correct login information, for both a Windows and an OS X implementation of the same code.

OS X and Windows presentation of newssearch.xul (after simulated login)
Figure 4-5. OS X and Windows presentation of newssearch.xul (after simulated login)

XUL-to-Server Communications

A user can communicate to a server using several models, including a XUL application that runs locally while obtaining needed server information through asynchronous HTTP requests. Using this approach, the local application is in complete control of the user interface, with the server providing only textual information in response to requests. Here are the steps for implementing this model:

  1. Configure a web server.

  2. Install a scripting language to provide logic that manages communications with a client application.

  3. Implement the communication connection between the application and the server.

  4. Add a database on the server to communicate with the scripting language.

Configuring the Server

We first must install a web server on our local machine; for this application we install Apache. Once the web server is installed and running, we should see the startup page shown in Figure 4-6 when we enter http://localhost/ in a browser’s URL field.

Apache startup page
Figure 4-6. Apache startup page

Apache is managed through the text file httpd.conf. On Unix systems, this is located in the /etc/http/ directory, whereas Windows systems will put this file in Apache’s conf directory. You can edit the file using a text editor.

The DocumentRoot directory is used to select the home directory for serving web pages. That entry, in httpd.conf, is operating-system-specific, and depends on the options we have specified during the installation:

DocumentRoot "<YourApacheDocumentRoot>"

If the installation was performed correctly, an entry should provide Apache with information about the directories served:

<Directory "YourApacheDocumentRoot">

Although we don’t need to make any changes to the httpd.conf file, we must note what these entries are in order to place our Common Gateway Interface (CGI) scripts in the correct directory. We will use the PHP scripting engine to provide the logic for our CGI.

We can install the PHP binaries into any directory. In a Windows environment, an example would be C:\php\, whereas a Unix install may be in /usr/local/php/. Regardless, we must make changes to Apache’s httpd.conf to properly serve PHP-generated output.

Depending on the version of PHP installed, entries in Apache’s configuration file will take one of two forms. In the first:

LoadModule phpn_module " <YourPHPInstallDirectory> "

n may be the version of PHP being installed.

The second form would instruct Apache to use an external configuration file to load the PHP libraries:

Include <YourPHPInstallDirectory>/httpd.conf.php

Regardless of the particular PHP version and operating system, we can check the installation by using a text editor to write a simple PHP script to echo version information:

 <?php
phpinfo(  );
?>

Save the preceding text as test.php in your Apache document root directory:

<YourApacheDocumentRoot>/test.php

Now type the URL into a browser to yield a page that looks like Figure 4-7.

Testing PHP installation
Figure 4-7. Testing PHP installation

With a PHP engine running on our server, we can now conduct some simple connection tests between our application and the server.

The Client/Server Protocol

This approach will use the XMLHttpRequest object to send inquiries to our PHP server, and PHP scripts to return textual responses to the client.

Conventional POST messages from a browser to a server result in the server generating an entire page to be displayed by the browser. This process is what causes many web applications to “freeze up” after the user presses a form submission button and waits for a response.

We will first use a simpler protocol to issue a request for data resembling “classic” client/server operations. The server provides only data; the client determines what to do with the data and how to render the interface.

We can write a JavaScript function to receive the server data and format HTML table data with responses, populate text areas, or otherwise modify the GUI without forcing a complete redraw of the page. For many applications that require only a partial update of the screen, this asynchronous approach reduces the waste incurred by conventional page-based updates.

The client-side request

Firefox supports the XMLHttpRequest, which is generally modeled after Microsoft’s IXMLRequest design. We can use the request to retrieve either simple text strings, or XML fragments that may be parsed and passed to the interface document’s Document Object Model (DOM) structure.

To use the request, JavaScript scripts create an instance of an XMLHttpRequest object to be sent to the server, passing the object a reference to a callback function to be invoked once the request is completed. The callback function then takes whatever action is determined by the response.

Table 4-1 summarizes the functions and properties of the XMLHttpRequest object most relevant to us for the upcoming exercise.

Table 4-1. XMLHttpRequest methods and properties

Method/Property

Description

open(sendMethod, sendURL, doAsync)

sendMethod = POST | GET.

sendURL = query string.

doAsync = if TRUE, return upon transmission of request.

.onreadystatechange

Function called when the request’s “ready” state changes.

send(null)

Sends the request to the server.

.readyState

Monitored within the callback; code == 4 signals that the request is completed.

.status

Monitored within the callback; code == 200 signals that the server was successful in executing the request.

.responseText

Accesses the text returned by the server to be parsed and processed in an application-specific format.

.responseXML

Accesses the text returned by the server when the text is presumed to consist of an XML document.

Before we start to code, we need to decide on a send/receive protocol between our client application and the server. We will use a very simple URL string to send commands to our server:

command=someCommand&param1=val1&param2=val2...

From our server, we will expect a comma-delimited string for a response:

RetCode=ReturnCode,retKey1=retVal1...

We will expect the first value token to be a case-insensitive true|false value to indicate the success of the command.

We will now change our newssearch.js file to implement a few modifications for our client/server model:

  • We will build a generic doServerRequest function to build an XMLHttpRequest object and send it to the server. To support that function, we will also create a commandArg object to hold name-value pairs of arguments that can be used to populate an array of arguments. The function will set the async flag in the open function to TRUE, meaning that control will immediately return to the application once the request is sent.

  • We will change our login-button command handler to call the newly created doServerRequest function.

  • We will change the logic to move successful and failed paths for login into separate functions.

  • We will write some JavaScript to add a callback function to interpret the response from the server and call the successful or failed login functions:

    function genericBtnHandler(event) {
    try { // try block
    var infoString = "Type = " + event.type + ",";
    infoString += "Target = " + event.target + ",";
    infoString += "Target.tagName = " + event.target.tagName + ",";
    infoString += "Target.id = " + event.target.id + ".";
    dump(infoString + "\n");
    } // try block
    catch (e) {
     alert("genericBtnHandler exception: " + e);
     }
    }
    
    function doLogin(event) {
    
    try { // try
    var theArgs = new Array;
    theArgs[0] = new commandArg("un",document.getElementById("userName").value);
    theArgs[1] = new commandArg("pd",document.getElementById("password").value);
    doServerRequest("login",theArgs);
     } // try
     catch (e) { //
      alert("doLogin exception: " + e);
     }//
    }
    //
    // Dynamically assign our event handler properties
    //
    function initialize(  ) {
    try {
     document.getElementById("B1").addEventListener("command",genericBtnHandler,true);
     document.getElementById("B2").addEventListener("command",genericBtnHandler,true);
     document.getElementById("B3").addEventListener("command",genericBtnHandler,true);
     //
     // Add a login script
     document.getElementById("loginButton").addEventListener("command",doLogin,true);
     }
     catch (e) {
      alert ("Exception: " + e);
      }
    }
    
    
    
    function loginOK(  ) {
     var theParent = document.getElementById("contentArea");
    
      while(theParent.childNodes.length != 0)
       theParent.removeChild(theParent.childNodes[0]);
    
    // Now re-create a welcome area
     theParent.style.backgroundColor = "LightSeaGreen";
     theParent.style.borderColor = "gray";
     theParent.style.borderStyle = "ridge";
     var leftSpacer = document.createElement("spacer");
     leftSpacer.setAttribute("flex","1");
     theParent.appendChild(leftSpacer);
     var newDescription = document.createElement("description");
     var newDescriptionText = document.createTextNode("Welcome!");
     newDescription.appendChild(newDescriptionText);
     theParent.appendChild(newDescription);
     var rightSpacer = document.createElement("spacer");
     rightSpacer.setAttribute("flex","1");
     theParent.appendChild(rightSpacer);
    }
    
    function commandArg(argKey,argValue) {
     this.key = argKey;
     this.value = argValue;
    }
    
    function loginFail(  ) {
     document.getElementById("msgDescription").style.backgroundColor="red";
     document.getElementById("msgDescription").style.color="white";
     document.getElementById("msgDescription").childNodes[0].nodeValue =
    "User not authenticated.";
     document.getElementById("userName").value = "";
     document.getElementById("password").value = "";
    
    }
    
    //
    //  CreateServerRequest
    //
    var theServerRequest;
    //
    // commandArgs is an array of arguments, each element
    // is converted into a PHP GET URL field
    function doServerRequest(commandString,commandArgs) {
    
     theServerRequest = new XMLHttpRequest(  );
     var theString ="http://localhost/doCommand.php?"+"&command="+commandString+"&";
     for (var i = 0; i < commandArgs.length; i++) { // build remaining parameters
       theString += commandArgs[i].key + "=" + commandArgs[i].value ;
      if (i != (commandArgs.length-1)) theString += "&";
     } // build remaining parameters
     theServerRequest.onreadystatechange = retrieveServerResponse;
     theServerRequest.open("GET",theString,true);
     theServerRequest.send(null);
    }
    
    
    
    function retrieveServerResponse(  ) {
     if (theServerRequest.readyState == 4) { // all done
      // Check return code
       if (theServerRequest.status == 200) { // request terminated OK
        alert("Response is " + theServerRequest.responseText);
        if (theServerRequest.responseText == "retcode=true") { // all OK
         loginOK(  );
        } // all OK
        else loginFail(  );
       } // request terminated OK
       else { // something is wrong
        alert("Response failed.");
       } // something is wrong
      } // all done
    }

We wrote the doServerRequest function to take an array of input arguments. The code then steps through the array to build a URL containing our complete GET request URL.

The request object calls the retrieveServerResponse function each time the state changes, and once it reaches "4" (a code that means the server has completed the request), it checks for a status of 200 (meaning the server successfully completed the request) and finally parses the text returned by the server to see whether the user is authenticated.

The server-side response

PHP scripts result in text being returned to a browser in one of two ways:

  • Any text not bracketed by the PHP directives <?php and ?> is passed directly to the browser.

  • PHP statements within the PHP tags send text to the browser through a function such as echo( ).

The parameters sent to a script from a browser’s GET request arrive as values in an associative array named $_GET. To assign a PHP variable (a token that begins with $) a parameter from a GET request, we would write:

$myValue = $_GET['keyString']

For now, our command processor script will presume only valid commands, and compare the command against our temporarily hardcoded strings to return a true or false response to the client. We can use any text editor to create the doCommand.php file:

<?php
$cmd   = trim($_GET['command']);
$uName = trim($_GET['un']);
$uPass = trim($_GET['pd']);

echo check_user($uName,$uPass);

function check_user($name,$pass) {
   if (($name == 'XULuser') &&
       ($pass == 'XULpass'))
       return 'retcode=true';
       else return 'retcode=false';
  }

?>

Save this file in the Apache document root directory.

The code illustrates assignment of the GET parameters to global variables. The trim function takes care of any leading or trailing spaces that may have been entered. The script then passes the username and password to a comparison function that returns true or false, which is echoed back to the client.

Repeating the same launch of our newssearch chrome package will yield the same results with our client-only code, but we are now operating in client/server mode.

When Things Go Wrong

If things don’t work out, we can take a few steps to quickly isolate the cause of the problem. We can use either JavaScript alert or dump commands to see what we are sending and what we are retrieving. We can also use simplified PHP scripts to return a known value to make certain the script is doing what we think.

Assuming we want to dump information to the screen, we could make the following changes to the doServerRequest function in our newssearch.js code:

theServerRequest.onreadystatechange = retrieveServerResponse;
 theServerRequest.open("GET",theString,true);
 dump("About to send " + theString + "\n");
 theServerRequest.send(null);

And we could report the entire string returned in our retrieveServerResponse function:

if (theServerRequest.status == 200) { // request terminated OK
    dump("Received from server: " + theServerRequest.responseText + "\n");
    if (theServerRequest.responseText == "true") { // all OK

We can also stub out our login check in doCommand.php just to return the parameters we received from the client:

/* echo check_user($uName,$uPass); */
echo 'Script has received: '."$cmd".','."$uName".','."$uPass";

We have removed the call to check_user in favor of a change to echo a string that concatenates (using the . operator in PHP) a simple message with the values for command, username, and password.

When we launch Firefox from the command line with the -console option, we will see our sent and retrieved strings displayed on the console:

About to send http://localhost/doCommand.php?&command=
  login&un=SomeUser&pd=SomePass
Received from server: Script has received: login,SomeUser,SomePass

For developers just beginning to work with PHP, including echo statements to use the browser to display variable values is often the best way to identify many of the most common PHP coding errors. Displaying variable values is one sure way to quickly identify problems related to misuse of the single quote (used to form strings with special characters), double quote (used to assemble strings that include the evaluation of variables), and terminating semicolons. Echoing variables is also a good tool to find misspelled variable names and the occasionally misplaced $ variable prefix.

We change our command parser to remove the debug echo statement and uncomment the call to check_user that will now be used with a database of valid user identifiers.

Adding a Database

Using hardcoded information in a script file may be appropriate for some limited applications, but the designer can improve the reliability of server code by allowing access to a dynamic information store without having to change any script files. Such a store can be maintained and updated to reflect the changing nature of business operations without risking any side effects caused by changes in server or client code.

Tip

Using a relational database is not a required element for this book’s sample application. If database use is inappropriate for some reason, we could write PHP “stub code” to mimic our database. The use of MySQL is included here as an example of an implementation that models true commercial applications.

The use of a relational database is the most commonly accepted technique to provide a secure and maintainable information store that server scripts can access in response to a client request. We will use the open source MySQL database engine to provide the store for our username and password information. The steps for this task are as follows:

  1. Create the NewsSearch database.

  2. Create a database user account to act as administrator.

  3. Create the tables for our account name and password.

  4. Create a database user account to act as a “guest” with read-only access to database information.

  5. Configure PHP to use libraries to access the database.

  6. Write the PHP scripts to communicate with the database in response to a client request.

Creating the database

Once we have installed MySQL, we create the database for our project.

Upon initial installation, MySQL has a root account that we can use to create a new table (refer to the glossary to set up a root password if you have not already done so). We log into the database with the following command:

mysql -u root -p

You will now be prompted for the root password. When you’re successfully logged in, you will see a welcome message that looks like this:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4 to server version: Version specific information
mysql>

We create a database with this command:

mysql> create database newssearch;
Query OK, 1 row affected (0.59 sec)

Creating account tables

Rather than having the PHP script compare usernames and passwords that are kept in the script, we can write code to access the database and compare its entries with the fields entered in the XUL interface.

Databases are organized as tables of information rows. Users can access row information through SQL SELECT statements that include qualifiers to help identify the information being sought. We can embed the SELECT statement in our PHP scripts that support the SQL access libraries.

The database administrator creates the database table, specifies its structure, and assigns privileges to restrict a user account’s ability to access and modify table data.

For our simple application, we will create a table named accounts to hold the username and password information. We will also add information to indicate a user’s status (e.g., active or inactive), and the time and date of the last login. The initial assumptions of the format for the row data are:

Username

Up to 40 characters (arbitrary)

Password

Up to 40 characters

Status

A string of up to 16 characters to support entries such as active, suspended, provisional (for temporary accounts), and terminated

Date

A string that describes the date and time of the last successful login session

The SQL statement to create such a table consists of a CREATE statement:

mysql> create table account (
    -> username char(40) not null primary key,
    -> password char(40) not null,
    -> last_session datetime,
    -> status char(32) not null);
Query OK, 0 rows affected (0.47 sec)

This statement creates our tables with rows named username, password, last_session, and status. The data types include character strings and a timestamp (an SQL-formatted statement for time of day). Additional qualifiers allow us to specify that the username will be unique (as a primary key), and which fields must have values specified when a table row is created.

We can see the results of our work through the describe command:

mysql> describe account;
+--------------+---+-----+-------------------+----------+
| Field      | Type      | Null | Key | Default   | Extra
+--------------+---------+-------------------+----------+
| username   | char(40)  |      | PRI |                 |
| password   | char(40)  |      |     |                 |
|last_session| datetime  | YES  |     |CURRENT_TIMESTAMP|
| status     | char(32)  |      |     |                 |
+--------------+-----------+------+-----+-----------------
4 rows in set (0.00 sec)
mysql>

We can now create the accounts for the application users—individuals who will be using our NewsSearch service.

Rather than storing the passwords for our users in plain text, we will add some security by using MySQL’s one-way encryption.

A one-way encryption scheme applies a function to input text to render an encrypted form of it in the database. The programmer or application scripts do not need to know what the encrypted output is, only that the encryption function is used consistently for comparison purposes. Should the database be compromised, there is no way for the intruder to reverse the encrypted data into the user-provided passwords.

A number of encryption functions are available for both PHP and MySQL. For this application, we will use MySQL’s function for the Secure Hash Algorithm (SHA): sha1('someEntry'). We can now create a couple of application users who will be in our authentication database.

Although we could use the MySQL command-line interpreter to do this, it is often easier to create a text file with the commands we need to manipulate the database. A createUser.sql file to create two users would look like this:

use newssearch;
insert into account values ('bugsbunny',sha1('wabbit'),'','active');
insert into account values ('elmerfudd',sha1('scwewy'),'','active');

We could read in this script file directly from the operating system command prompt:

%mysql -u root -p < createUsers.sql;
>password: 'sqlRootPassword'
%

We could also use the source command (\.) to enter the script name while within the MySQL interpreter. The following command shows how to load the script from the current working directory:

mysql> \. createUsers.sql

We can view the results of our script files by issuing a SELECT command:

mysql> select * from account;

The results will show the account information created by the script file, along with the encrypted passwords. The last_session entries will be initialized to 0, as no entry was entered in the INSERT statement.

Creating database user accounts

When the server software requests information from a database, the software will have to make its request through a MySQL account. We should set this account so that it has only the minimum privileges necessary for the task at hand. That means we limit a user’s read and write authorization to specific tables in the database.

The commands to manage account privileges involve specifying the account or user name, setting a boundary to a limited set of objects that are affected, and setting the privilege itself that describes what degree of access and modification is granted. The minimal, general form of a command to assign a privilege is as follows:

GRANT priv_type [(column_list)] [, priv_type [(column_list)]] ...
    ON [object_type] {tbl_name | * | *.* | db_name.*}
    TO user [IDENTIFIED BY [PASSWORD] 'password']
        [, user [IDENTIFIED BY [PASSWORD] 'password']] ...
    [REQUIRE
        NONE |
        [{SSL| X509}]
        [CIPHER 'cipher' [AND]]
        [ISSUER 'issuer' [AND]]
        [SUBJECT 'subject']]
    [WITH with_option [with_option] ...]

object_type =
    TABLE
  | FUNCTION
  | PROCEDURE

The priv_type parameter is one of a fairly substantial number of tokens that define the privilege being granted. The most familiar of these types includes SELECT (to select information from tables), INSERT (to insert new rows into tables), UPDATE (to modify table entries), and DELETE (to remove rows from a table).

The object_type parameter sets a boundary on the privilege being granted. The object type can range from * (all tables on all databases), to an entry of the form databaseName.tableName to further qualify the objects to which the GRANT statement applies.

To create a newssearch_guest account that can read any data from the table, you could specify the following script file to create the account:

use newssearch;
grant select on newssearch.account to
    newssearch_guest identified by 'nsgst';
grant update (last_session) on account to newssearch_guest;

Reading this script file (or typing it into the MySQL interpreter) will create a database user account, newssearch_guest, that can SELECT data only from the accounts table. The second statement adds UPDATE privileges to the database account to allow scripts to update the session information in the database. We now have enough information to turn our attention to the PHP script that accesses the database.

Connecting PHP to MySQL

To configure PHP to use the MySQL programming interface, we must modify PHP’s configuration file to load the MySQL libraries. The configuration file, php.ini, is located in the user’s PHP installation directory.

The specific switches to set depend on the versions of MySQL and PHP in use (check the most recent content of the MySQL and PHP web sites for specific entries). We will be using the MySQL Improved extension from our PHP scripts. The settings to enable the MySQL Improved extension are:

mysqli.max_links         "-1"
mysqli.default_port      "3306"
mysqli.default_socket    NULL
mysqli.default_host      NULL
mysqli.default_user      NULL
mysqli.default_pw        NULL

Once we have the correct settings, we can verify that Apache, PHP, and MySQL are up and running by using Firefox to open http://localhost/test.php. Scrolling down the window, we see the entries confirming a successful configuration (see Figure 4-8).

Successful configuration of MySQL Improved (mysqli) extension
Figure 4-8. Successful configuration of MySQL Improved (mysqli) extension

Calling the MySQLi API

We will use functions for the MySQLi library to compare the user input login and password against the entries in the accounts table.

Using the object-oriented approach to the MySQLi library, we create a database connection object and use that object reference to execute an SQL SELECT statement against the database.

The results of this statement are contained in a variable pointing to a result object. The result object will contain a collection of all the database rows that were selected. Table 4-2 shows a summary of the mysqli PHP calls that we will be using.

Table 4-2. MySQLi objects and methods

Object/Function

Use

$database= new mysqli('hostname',’username',’password',’databaseName');

Creates a database object reference by connecting to the database with the username and password specified. The database is identified by databaseName.

mysqli_connect_errno( )

mysqli_connect_error( )

If the database object creation fails, these functions are used to audit an error code, and to extract the connect_error text for reporting.

$ searchResults = $database->query(' queryString ')

Executes an SQL query, returning the result in a mysqli_result object.

$ searchResults ->num_rows

Returns the number of rows selected from the database as a result of the query.

$ row = $ searchResults ->fetch_assoc( )

Fetches the next row from the result object, returning the results as an associative array in which the keys to the array match the names of the row’s columns (e.g., to return the contents of a row’s “status” column, the PHP script would read $row["status"];).

$ searchResults ->close( )

Search results must be closed before attempting any additional queries.

$ database ->close( )

Scripts must close the database prior to exiting.

We will rewrite the PHP scripts to use the database to select the rows for the username and password supplied by the interface. If there is a match (if one row is returned), we will return the proper flag, along with the last time the user logged in.

On the interface, if the user is authenticated, we will display the last login time in the application’s status bar.

The PHP doCommand.php file now has a checkUser function that looks like this:

function check_user($name,$pass) {
   $database = new mysqli('localhost','newssearch_guest','nsgst','newssearch');
   if (mysqli_connect_errno(  )) { // failing case
      $retString = 'retcode=false,message='.mysqli_connect_error(  );
      return $retString;
      } // failing case

    $encryptPass = sha1($pass);

    $query = "select status,last_session from account where
        username = '$name' and password = '$encryptPass'";

    if ($theResult = $database->query("$query")) { // we have some kind of result

    if ($theResult->num_rows == 1) { // we have our user

       $theRow = $theResult->fetch_assoc(  ); // get the only row that exists
       $lastLogin = $theRow["last_session"];

       if ($theRow['status'] == 'active') { // all OK
        $retString='retcode=true,last_login='.$theRow['last_session'];
    //    update the session info
        $theResult->close(  );
        $curTime = date('c');
        $update = "update account set last_session = '$curTime' where
                          username = '$name'";
        $theResult = $database->query("$update");
        } // account is active

        else { // account not active
        $theResult->close(  );
        $retString = 'retcode=false,message=user account not active';
        } // account not active

      } // we have our user
      else { // user not found
       $theResult->close(  );
       $retString = 'retcode=false,message=user not found';
       } // user not cound


      } // we have some kind of result

      else { // no result returned
       $retString = 'retcode=false,message=invalid query';
      } // no results returned

    $database->close(  );
    return $retString;

}

Before building the command string, we see the call to encrypt the password prior to its comparison with the database. Our query statement returns columns for last_session and status. If one row is returned, there are statements to verify that the user has an “active” account before building a successful return code. We also call an UPDATE command on the database to set the last_session entry to the current date and time.

We can change the XUL interface slightly to add a horizontal box with a status area to show the time of the user’s last login. The file newssearch.xul now looks like this:

<?xml version="1.0"?>
<?xml-stylesheet href="testStyles.css" type="text/css"?>
<window
  id="theMainWindow"
  onload="initialize(  );"
  title="Test Window"
  orient="horizontal"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script  src="newssearch.js"/>
 <!-- main top level container -->
 <vbox flex="1">

 <hbox flex="1" >

 <!-- a container for some kind of list -->
 <vbox flex="1" >
 </vbox>

 <!-- container for messages and tool areas -->
 <vbox flex="2" >

 <!-- used to display message -->
 <hbox  id="contentArea"  flex="3" >

  <spacer flex="1"/>
  <vbox >  <!-- stack message and login controls vertically -->
    <spacer flex="1"/>
      <description id="msgDescription">
       Waiting for login.
      </description>
      <label value="User Name:" control="userName"/>
      <textbox id="userName"/>
      <label value="Password:" control="userName"/>
      <textbox id="password"  type="password" maxlength="8"/>
      <button id="loginButton" label="Logon"/>
     <spacer flex="1"/>
  </vbox>
  <spacer flex="1"/>

 </hbox>
 <!-- used to display message -->


  <!-- used to display typing area  -->
 <hbox flex="3" >
 </hbox>

  <!-- used to display tool area-->
 <hbox flex="1" >

  <spacer flex="1"/>

    <vbox>
     <spacer flex="1"/>
      <hbox>
       <button id="B1" label="B1" />
       <button id="B2" label="B2"/>
       <button id="B3" label="B3"/>
       <button label="B4"/>
      </hbox>
      <spacer flex="1"/>
    </vbox>

  <spacer flex="1"/>

 </hbox>

 </vbox>
 <!-- container for messages and tool areas -->

 </hbox>
 <!-- horizontal container for all content (except status info) -->


  <hbox >
   <!-- stack info and resizer horizontally -->
   <!-- right align our status bar -->
     <statusbar id="status-bar" class="chromeclass-status">
     <statusbarpanel id="status-text" />
   </statusbar>
  <spacer flex="1"/>
 </hbox>

  <!-- main container -->
 </vbox>
</window>

Within the JavaScript code, first we need to make certain we are responding to the correct command, so we add a global variable at the top of the source file to hold the last command issued:

var K_XUL_NAMESPACE = "http://www.mozilla.org/keymaster/
    gatekeeper/there.is.only.xul";

var USER_LOGGED_IN = 0;

var lastCommand = "";

and modify the doLogin function to save the last command sent:

function doLogin(event) {

try { // try
var theArgs = new Array;
theArgs[0] = new commandArg("un",document.getElementById("userName").value);
theArgs[1] = new commandArg("pd",document.getElementById("password").value);
lastCommand = "login";
doServerRequest("login",theArgs);
 } // try
 catch (e) { //
  alert("doLogin exception: " + e);
 }//
}

When processing the server response, we use the JavaScript split( ) function to break our comma-delimited responses into an array of name-value pairs. We then compare the value of the first entry to "true" before extracting the second returned value and setting the status text:

function retrieveServerResponse(  ) {
 if (theServerRequest.readyState == 4) { // all done
  // Check return code
   if (theServerRequest.status == 200) { // request terminated OK
    dump("Received from server: " + theServerRequest.responseText + "\n");

    //
    var theResults = theServerRequest.responseText.split(",");
    //

    var rCode = (theResults[0].substring((theResults[0].indexOf("=")+1),
                                          theResults[0].length)).toLowerCase(  );

    if (lastCommand == "login") { // process login command

     if (rCode == "true") { // everything OK, we know next parameter is
                            // session info
      var lastSession = "Last login was ";
      lastSession += (theResults[1].substring((theResults[1].indexOf("=")+1),
                                          theResults[1].length)).toLowerCase(  );
      loginOK(  );
      setStatusText(lastSession);

     } // everthing OK
     else { // user NG
      loginFail(  );
      setStatusText("No user logged in");
     } // user NG

    } // process login command


   } // request terminated OK
   else { // something is wrong
    alert("Response failed.");
   } // something is wrong
  } // all done
}

This version of the program will now log into the database, and if the username and password match (and the account is active), the welcome screen will include a status line along the bottom of the display reporting the last date and time of the user’s login:

Last login was 0000-00-00 00:00:00

All zeros appear because our initial script to create the database tables did not set an initial date in the database. Once we log in under a valid account name, the next login will yield a more welcoming message.

When Things Go Wrong

There are many “moving parts” to this integration involving XUL source, PHP statements, and SQL statements. Most of the problems at this stage will involve either syntax or logic errors with PHP, or problems with the structured SQL statements. These errors can be difficult to track down, but these suggestions may help:

  • Errors in PHP scripts often result in nothing being displayed on the browser screen. Try using the JavaScript dump function to unconditionally display the results of the XMLHttpRequest. Then change the PHP script to set a variable such as $returnString to some suspect variable and echo the variable. If nothing is returned to the JavaScript function, the syntax error occurs prior to the echo statement. Otherwise, we can use the variable to help identify any other syntax errors.

  • Errors with the SQL statements often involve syntax or an error in setting user privileges. When suspecting such problems, try using the MySQL command-line interface to type in a statement identical to the script-generated code to verify the expected results.

  • When all else fails, continue to simplify the code (e.g., use SELECT * from tableName) or use PHP stubs to build a string of debug trace statements that end up returned to the JavaScript function.

Serving XUL Files

Sometimes we may want to use a web server to deliver the XUL files. To implement our login user interface by placing the XUL file on our web server, do the following:

  1. Write a source XUL file that will be delivered upon a successful login and place that file along with stylesheets and JavaScript source files on the server.

  2. Configure the server to properly serve a XUL MIME type.

  3. Change the XUL file to a PHP script file.

  4. Add an HTML screen to read the user login and password.

  5. Modify our PHP doCommand function to call the PHP script that delivers the XUL source.

Creating a XUL File to Be Served

The XUL source to be served will actually be sent by a PHP script--but a good first step is to develop a XUL file that we will convert into PHP.

We copy the newssearch.js and NewsSearchStyle.css files into the Apache root directory.

We will copy the newssearch.xul file but rename it to startupScreen.xul. We also change the source file to remove the login areas, replacing them with the graphics that render a successful login screen:

<hbox  id="contentArea"
   style="borderColor:gray;
    border-style:ridge;background-color:LightSeaGreen;
    border-color:gray;" flex="3" >

  <spacer flex="1"/>
  <vbox >  <!-- stack message and login controls vertically -->
    <spacer flex="1"/>
      <description id="msgDescription">
       Welcome.
      </description>
      <spacer flex="1"/>
  </vbox>
  <spacer flex="1"/>

 </hbox>

Configuring the Server

Without “understanding” what to do with a XUL file, the Apache web server would deliver a XUL file to a browser as an XML text file. A browser receiving such a file most often just presents the source to the user of the browser.

We must add an entry to either the mime.types or the httpd.conf file for Apache:

application/vnd.mozilla.xul+xml         xul

After making this change, restarting the web server will allow us to enter the XUL file reference URL:

http://localhost/startupScreen.xul

The browser will now render the XUL interface shown earlier in our client/server implementation.

PHP Serving XUL

We will modify our code to read username and password information from the user, and if we have a valid user, we will render our XUL success screen. The first step to that process is to build an HTML interface to read our entries, and modify the PHP login script to return the XUL source rather than the simple text response.

The PHP interpreter processes any text between the PHP tags (<?php... ?>). All other text outside of the PHP tags is returned directly to the browser. PHP scripts build an HTML interface when a designer builds an HTML page and places PHP scripts where conditional processing will change the text returned to the browser.

Our first step to cut over to a served XUL file is to modify our original PHP script to output a standard FORM to read in the username and password. The POST action will send the data to the same script file (we will add logic to check for input values that will help flag different entry points to the script).

Once the script obtains the username and password, we will use the same logic to determine success or failure. Although our finished version will then serve a XUL interface, for now we will simply send our previous return code string to the browser.

The changes from the previous doCommand.php file to a doCommandXUL.php file are summarized as follows:

  • The variables that were obtained through $_GET variables are now obtained through $_POST variables (we will be using an HTML form that uses the POST method for input).

  • A check will be added to read the username and password variables. If they are blank, we will issue a login screen; if they are not blank, we will check the input information to see whether the user is authorized.

  • The login screen is designed to be a simple HTML table with input fields.

  • If the user is authorized, the script echoes the return code to the browser for display.

The PHP script file doCommandXUL.php now looks like this:

<?php

$uName = trim($_POST['un']);
$uPass = trim($_POST['pd']);

if (empty($uName) || empty($uPass))

{ // build our HTML login stuff

?>

<h1>REGISTERED NEWSSEARCH USERS ONLY!</h1>
<form method="post" action="doCommandXUL.php">
<table>
 <tr>
  <td>User name:</td>
  <td> <input type="text" name="un"/></td>
 </tr>
 <tr>
  <td>Password:</td>
  <td> <input type="password" name="pd"/></td>
 </tr>
  <tr>
  <td colspan="2" align="center"> <input type="submit" value="LOG IN"/></td>
 </tr>
</table>
</form>

<?php
}

else {
  echo check_user($uName,$uPass);
 }

?>

<?php

// Check user will make certain the user exists, and return
// true with the last login date in the command string
//
// Error conditions return false with a 'message' parameter
// set to the string returned by mysql
//
function check_user($name,$pass) {

   $database = new mysqli('localhost','newssearch_guest','nsgst','newssearch');
   if (mysqli_connect_errno(  )) { // failing case
      $retString = 'retcode=false,message='.mysqli_connect_error(  );
      return $retString;
      } // failing case

    $encryptPass = sha1($pass);

    $query = "select status,last_session from
      account where username = '$name' and
         password = '$encryptPass'";

    if ($theResult = $database->query("$query")) {
     // we have some kind of result

    if ($theResult->num_rows == 1) { // we have our user

       $theRow = $theResult->fetch_assoc(  );
       // get the only row that exists
       $lastLogin = $theRow["last_session"];

       if ($theRow['status'] == 'active') { // all OK
        $retString='retcode=true,last_login='.$theRow['last_session'];
    //    update the session info
        $theResult->close(  );
        $curTime = date('c');
        $update = "update account set last_session =
          '$curTime' where username = '$name'";
        $theResult = $database->query("$update");
        } // account is active

        else { // account not active
        $theResult->close(  );
        $retString = 'retcode=false,message=user account not active';
        } // account not active

      } // we have our user
      else { // user not found
       $theResult->close(  );
       $retString = 'retcode=false,message=user not found';
       } // user not found


      } // we have some kind of result

      else { // no result returned
       $retString = 'retcode=false,message=invalid query';
      } // no results returned

    $database->close(  );
    return $retString;

 }

?>

When we enter the URL for this file into a Firefox browser, and enter a valid username and password into the fields, we get a browser’s rendering of the return code generated by the check_user function, as shown in Figure 4-9.

PHP-served return code as HTML text
Figure 4-9. PHP-served return code as HTML text

The remaining step to this transition is to serve the XUL source to the user. By renaming our startupScreen.xul file to startupScreen.php, we can merge PHP statements into the XUL source to accomplish the required tasks to report the last login time for a registered user.

Using PHP require( )

This test application will use a PHP require function to insert the XUL source file into the output stream being returned to the user. The code will execute the same user test, and if the user is registered, a PHP variable will be set to the login time, and a require statement will be set to merge the XUL source file into the output stream. The XUL source file will be changed to pass the login time to the status label. Figure 4-10 illustrates the logic.

PHP login sequence
Figure 4-10. PHP login sequence

PHP serving XUL files

If we were to use only a require script to serve a XUL file, we would not be able to merge PHP statements to set the status field of our interface. But by renaming only the startupScreen.xul file to startupScreen.php, the PHP interpreter would send the XUL source file to the web server without sufficient information to instruct the browser of the required media type.

To allow PHP to properly serve the XUL file, we must make a change to our php.ini file:

short_open_tag  =   Off

This tells the PHP interpreter to ignore parsing the tags <? and ?>, except for those that include the PHP token. Otherwise, PHP would attempt to interpret the XML-structured XUL content.

Our renamed startupScreen.php file must also include a PHP directive to set the content type of the text being sent to the browser. The first line in our startupScreen.php file must be changed to:

<?php header("Content-type: application/vnd.mozilla.xul+xml"); ?>
   <?xml version="1.0"?>
   <?xml-stylesheet href="NewsSearchStyles.css" type="text/css"?>

Logic changes

Next, we change our doCommandXUL.php frontend script to execute the logic that decides what interface to serve. The only change from the last iteration of the code is to alter the path that actually checks the result if the user entered data into the name and password fields.

The code takes advantage of the PHP explode function to break up the returned string into an array of name-value pairs.

We use the PHP subst(inString,startingChar,lastChar) functions to break the result array’s strings into values, given the fact that we know the length of the names being used as tags. If the first returned value is true, we save a string that holds session information into a variable, and include the PHP file that holds the XUL interface content. If the first returned value is false, we build a message that flags an unregistered user:

else {
  $retString = check_user($uName,$uPass);
  $resArray = explode(',',$retString);
  if ($resArray[0] == 'retcode=true') {
     $lastLoginTime = 'Last login was '.substr($resArray[1],
        11,strlen($resArray[1]));  // extract last session
     require('startupScreen.php');
     }
    else { // invalid user, send rejection page
    echo '<h1>Sorry!</h1>';
    echo '<h2>You are not registered to use this service.</h2>';
    } // invalid user
 }

?>

We need to insert another subtle change into the newssearch.js file. Because the login button is no longer on our startup screen (the doCommandXUL.php file is generating it), we can no longer try to attach a login script upon initialization. We could edit the code to remove all initialization references, or (if we wanted to use the same file for both local and served versions) we could add a test to make certain the button exists before attaching an event listener:

function initialize(  ) {
try {
 document.getElementById("B1").
   addEventListener("command",genericBtnHandler,true);
 document.getElementById("B2").
   addEventListener("command",genericBtnHandler,true);
 document.getElementById("B3").
   addEventListener("command",genericBtnHandler,true);
 //
 // Add a login script

 if (document.getElementById("loginButton"))  {
   document.getElementById("loginButton").
       addEventListener("command",doLogin,true);
   }

 }
 catch (e) {
  alert ("Exception: " + e);
  }
}

Finally, we change the segment of the XUL source in our startupScreen.php file that manages the status label to include the PHP directives to substitute the $lastLoginTime variable into the label attribute of the status display:

<hbox >
   <!-- stack info and resize horizontally -->
   <!-- right align our status bar -->
     <statusbar id="status-bar" class="chromeclass-status">
     <statusbarpanel id="status-text"
     <?php
      echo('label=\''."$lastLoginTime".'\'/>');
      ?>
   </statusbar>
  <spacer flex="1"/>
 </hbox>

Now referencing the doCommandXUL.php file from the Firefox URL will present the same interaction with the user, except with the interface delivered from the web server. Figure 4-11 shows our XUL-served page for a successful login and a failed login.

Successful and failed logins from a XUL-served interface
Figure 4-11. Successful and failed logins from a XUL-served interface

Summary

We have now completed a number of exercises that demonstrate two of the most common implementations of a XUL application. These examples included:

  • Moving the application into a chrome package

  • Connecting the application to a server running PHP scripts

  • Using a relational database engine to store user account information

  • Implementing the same interface using XUL source served by PHP scripts

Although we can use any of these forms of implementation for our application, we will continue to focus on the client/server form. The use of the server to provide only textual information lends itself well to our design, in which the Firefox framework accepts the lion’s share of interface rendering, and leaves the server to focus on protocol, security, and content.

Get Programming Firefox now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.