[Chapter 4] 4.4 Decoding Forms in Other Languages
Author: Shishir Gundavaram
Date: Thu Aug 28 17:50:21 1997

#

| Chapter 4 Forms and CGI |

## 4.4 Decoding Forms in Other Languages

Since Perl contains powerful pattern-matching operators and string manipulation functions, it is very simple to decode form information. Unfortunately, this process is not as easy when dealing with other high-level languages, as most of them lack these kinds of operators. However, there are various libraries of functions on the Internet that make the decoding process easier, as well as the uncgi program (http://www.hyperion.com/~koreth/uncgi.html).

### C Shell (csh)

It is difficult to decode form information using native C shell commands. csh was not designed to perform this type of string manipulation. As a result, you have to use external programs to achieve the task. The easiest and most versatile package available for handling form queries is uncgi, which decodes the form information and stores them in environment variables that can be accessed not only by csh, but also by any other language, such as Perl, Tcl, and C/C++. For example, if the form contains two text fields, named "user" and "age," uncgi will place the form data in the variables WWW_user and WWW_age, respectively. Here is a simple form and a csh CGI script to handle the information:

    <HTML>
    <HEAD><TITLE>Simple C Shell and uncgi Example</TITLE></HEAD>
    <BODY>
    <H1>Simple C Shell and uncgi Example</H1>
    <HR>
    <FORM ACTION="/cgi-bin/uncgi/simple.csh" METHOD="POST">
    Enter name: <INPUT TYPE="text" NAME="name" SIZE=40><BR>
    Age: <INPUT TYPE="text" NAME="age" SIZE=3 MAXLENGTH=3><BR>
    What do you like:<BR>
    <SELECT NAME="drink" MULTIPLE>
    <OPTION>Coffee
    <OPTION>Tea
    <OPTION>Soft Drink
    <OPTION>Alcohol
    <OPTION>Milk
    <OPTION>Water
    </SELECT>
    <P>
    <INPUT TYPE="submit" VALUE="Submit the form">
    <INPUT TYPE="reset"  VALUE="Clear all fields">
    </FORM>
    <HR>
    </BODY>
    </HTML>

Notice the URL associated with the ACTION attribute! It points to the uncgi executable, with extra path information (your program name). The server executes uncgi, which then invokes your program based on the path information. Remember, your program does not necessarily have to be a csh script; it can be a program written in any language. Now, let's look at the program.

    #!/usr/local/bin/csh
    echo "Content-type: text/plain"
    echo ""

The usual header information is printed out.

    if ($?WWW_name) then
        echo "Hi $WWW_name -- Nice to meet you."
    else
        echo "Don't want to tell me your name, huh?"
        echo "I know you are calling in from $REMOTE_HOST."
        echo ""
    endif

uncgi takes the information in the "name" text entry field and places it in the environment variable WWW_name.

In csh, environment variables are accessed by prefixing a "$" to the name (e.g., $REMOTE_HOST). When checking for the existence of variables, however, you must use the C shell's $? construct. I use $? in the conditional to check for the existence of WWW_Name. You cannot check for the existence of data directly:

    if ($WWW_name) then
        ....
    else
        ....
    endif

If the user did not enter any data into the "name" text entry field, uncgi will not set a corresponding environment variable. If you then try to check for data using the method shown above, the C shell will give you an error indicating the variable does not exist.

The same procedure is applied to the "age" text entry field.

    if ($?WWW_age) then
        echo "You are $WWW_age years old."
    else
        echo "Are you shy about your age?"
    endif
    echo ""
    if ($?WWW_drink) then
        echo "You like: $WWW_drink" | tr '#' ''
    else
        echo "I guess you don't like any fluids."
    endif
    exit(0)

Here is another important point to remember. Since the form contains a scrolled list with the multiple selection property, uncgi will place all the selected values in the variable, separated by the " #" symbol. The UNIX command tr converts the "#" character to the space character within the variable for viewing purposes.

### C/C++

There are a few form decoding function libraries for C and C++. These include the previously mentioned uncgi library, and Enterprise Integration Technologies Corporation's (EIT) libcgi. Both of them are simple to use.

#### C/C++ decoding using uncgi

Let's look at an example using uncgi (assuming the HTML form is the same as the one used in the previous example):

    #include <stdio.h>
    #include <stdlib.h>

These two libraries--standard I/O and standard library--are used in the following program. The getenv function, used to access environment variables, is declared in stdlib.h.

    void main (void)
    {
        char *name,
             *age,
             *drink,
             *remote_host;
        printf ("Content-type: text/plain\n\n");

        uncgi();

Four variables are declared to store environment variable data. The uncgi function retrieves the form information and stores it in environment variables. For example, a form variable called name, would be stored in the environment variable WWW_name.

        name = getenv ("WWW_name");
        age = getenv ("WWW_age");
        drink = getenv ("WWW_drink");
        remote_host = getenv ("REMOTE_HOST");

The getenv standard library function reads the environment variables, and returns a string containing the appropriate information.

        if (name == NULL) {
            printf ("Don't want to tell me your name, huh?\n");
            printf ("I know you are calling in from %s.\n\n", remote_host);
        } else {
            printf ("Hi %s -- Nice to meet you.\n", name);
        }

        if (age == NULL) {
            printf ("Are you shy about your age?\n");
        } else {
            printf ("You are %s years old.\n", age);
        }

        printf ("\n");

Depending on the user information in the form, various informational messages are output.

        if (drink == NULL) {
            printf ("I guess you don't like any fluids.\n");
        } else {
            printf ("You like: ");

            while (*drink != '\0') {
                if (*drink == '#') {
                    printf (" ");
                } else {
                    printf ("%c", *drink);
                }
                ++drink;
            }

            printf ("\n");
        }

        exit(0);
    }

The program checks each character in order to convert the "#" symbols to spaces. If the character is a "#" symbol, a space is output. Otherwise, the character itself is displayed. This process takes up eight lines of code, and is difficult to implement when compared to Perl. In Perl, it can be done simply like this:

    $drink =~ s/#/ /g;

This example points out one of the major deficiencies of C for CGI program design: pattern matching.

#### C/C++ decoding using libcgi

Now, let's look at another example in C. But this time, we will use EIT's libcgi library, which you can get from http://wsk.eit.com/wsk/dist/doc/libcgi/libcgi.html.

    #include <stdio.h>
    #include "cgi.h"

The header file cgi.h contains the prototypes for the functions in the library. Simply put, the file--like all the other header files--contains a list of all the functions and their arguments.

    cgi_main (cgi_info *cgi)
    {
        char *name,
             *age,
             *drink,
             *remote_host;

Notice that there is no main function in this program. The libcgi library actually contains the main function, which fills a struct called cgi_info with environment variables and data retrieved from the form. It passes this struct to your cgi_main function. In the function I've written here, the variable cgi refers to that struct:

        form_entry *form_data;

The variable type form_entry is a linked list that is meant to hold key/value pairs, and is defined in the library. In this program, form_data is declared to be of type form_entry.

        print_mimeheader ("text/plain");

The print_mimeheader function is used to output a specific MIME header. Technically, this function is not any different from doing the following:

        print "Content-type: text/plain\n\n";

However, the function does simplify things a bit, in that the programmer does not have to worry about accidentally forgetting to output the two newline characters after the MIME header.

        form_data = get_form_entries (cgi);
        name = parmval (form_data, "name");
        age = parmval (form_data, "age");
        drink = parmval (form_data, "drink");

The get_form_entries function parses the cgi struct for form information, and places it in the variable form_data. The function takes care of decoding the hexadecimal characters in the input. The parmval function retrieves the value corresponding to each form variable (key).

        if (name == NULL) {
            printf ("Don't want to tell me your name, huh?\n");
            printf ("I know you are calling in from %s.\n\n", cgi->remote_host);
        } else {
            printf ("Hi %s -- Nice to meet you.\n", name);
        }

Notice how the REMOTE_HOST environment variable is accessed. The libcgi library places all the environment variable information into the cgi struct.

Of course, you can still use the getenv function to retrieve environment information.

        if (age == NULL) {
            printf ("Are you shy about your age?\n");
        } else {
            printf ("You are %s years old.\n", age);
        }

        printf ("\n");

        if (drink == NULL) {
            printf ("I guess you don't like any fluids.\n");
        } else {
            printf ("You like: %s", drink);
            printf ("\n");
        }

        free_form_entries (form_data);
        exit(0);
    }

Unfortunately, this library does not handle multiple keys properly. For example, if the form has multiple checkboxes with the same variable name, libcgi will return just one value for a specific key.

Once the form processing is complete, you should call the free_form_entries function to remove the linked list from memory.

In addition to the functions discussed, libcgi offers numerous other ones to aid in form processing. One of the functions that you might find useful is the mcode function. Here is an example illustrating this function:

    switch (mcode (cgi)) {
        case MCODE_GET:
            printf("Request Method: GET\n");
            break;
        case MCODE_POST:
            printf("Request Method: POST\n");
            break;
        default:
            printf("Unrecognized method: %s\n", cgi->request_method);
    }

The mcode function reads the REQUEST_METHOD information from the cgi struct and returns a code identifying the type of request.

### Tcl

Unlike C/C++, Tcl does contain semi-efficient pattern matching functions. These functions can be used to decode form information. However, according to benchmark test results posted in comp.lang.perl, the regular expression functions as implemented in Tcl are quite inefficient, especially when compared to Perl. But you are not limited to writing form decoding routines in Tcl, because you can still use uncgi.

Tcl, like Perl, has been extended to include the gd graphics library. In this section, we'll see how Tcl works with gd graphics, and along the way learn how to decode input either by invoking uncgi or by spinning our own Tcl code. We'll write a trivial program to display color text, just like the Perl program earlier in the chapter.

    #!/usr/local/bin/gdtcl
    puts "Content-type: image/gif\n"
    set font_height 16
    set font_length 8
    set color $env(WWW_color)

In Tcl, variables are declared with the set command. The font height and length are set to 16 and 8, respectively. And color is equated to the environment variable WWW_color-set by uncgi. The env array is equivalent to Perl's ENV associative array. The "$" sign instructs Tcl to substitute the value of the variable. If we did not include the "$" sign, the variable would be set to the literal string "env(WWW_color)".

    if {[info exists env(WWW_message)]} {
        set message $env(WWW_message)
    } else {
        set message "This is an example of $color text"
    }

This block of code sets the message to be displayed. If the user submitted a message, the variable message is set to it. Otherwise, a default message is output.

For people not familiar with Tcl syntax and commands, the info command can use some explanation. It has to appear in square brackets which tell Tcl to execute the command and pass the return value back to the if command. info exists checks whether a variable has been defined, and returns a true or false value.

    set message_length  [string length $message]
    set x  [expr $message_length * $font_length]
    set y  $font_height

Here we determine the width and height of the image, assigning those values to x and y. The string length command determines how many characters are in the string. This value, temporarily stored in message_length, must be multiplied by the font length to get the total number of pixels in the message. To do basic arithmetic, Tcl offers the expr command.

    set image  [gd create $x $y]
    set white  [gd color new $image 255 255 255]

The gd create command requires the user to specify the length and height of the image. The image is created, and the handle to it is stored in the variable image. The background color is set to white. Although the gd commands in Tcl have a slightly different syntax than those in Perl, their operation is identical.

    if {[string compare $color "Red"] == 0} {
        set color_index [list 255 0 0]
    } elseif {[string compare $color "Blue"] == 0} {
        set color_index [list 0 0 255]
    } elseif {[string compare $color "Green"] == 0} {
        set color_index [list 0 255 0]
    } elseif {[string compare $color "Yellow"] == 0} {
        set color_index [list 255 255 0]
    } elseif {[string compare $color "Orange"] == 0} {
        set color_index [list 255 165 0]
    } elseif {[string compare $color "Purple"] == 0} {
        set color_index [list 160 32 240]
    } elseif {[string compare $color "Brown"] == 0} {
        set color_index [list 165 42 42]
    } elseif {[string compare $color "Black"] == 0} {
        set color_index [list 0 0 0]
    }

This is merely a group of if-then-else statements that determine the RGB color index for the user-selected color. The string compare function compares its two arguments and returns either -1, 0, or 1, to indicate that the first argument is greater than, equal to, or less than the second argument, respectively.

The color has to be a list of three values, not just a string. That is the purpose of the list command in brackets. It creates a list--a construct similar to regular arrays in Perl--and returns it to the set command, which assigns the list to the color_index variable.

    set selected_color  [gd color new $image $color_index]
    gd color transparent $image $white
    gd text $image $selected_color large 0 0 $env(WWW_message)
    gd writeGIF $image stdout

The chosen color is selected, and the image background is made transparent. A message is output at coordinate (0, 0), and the entire GIF image is sent to standard output.

    flush stdout
    gd destroy $image
    exit 0

The standard output buffer is flushed before exiting, to ensure that the entire image is sent to the browser. Finally, the image handle is destroyed.

In this program, we've relied on uncgi to do the hard parsing that Tcl is somewhat weak at. The result is a simple and fully functional handler for a form. But for people who want to do everything in Tcl, here is how to decode a form:

    set webmaster {shishir@bu.edu}

The variable webmaster is set. Notice the braces around the expression indicating no variable substitution.

    proc return_error { status keyword message } {
        global webmaster
        puts "Content-type: text/html"
        puts "Status: $status $keyword\n"
        puts "<title>CGI Program - Unexpected Error</title>"
        puts "<H1>$keyword</H1>"
        puts "<HR>$message</HR>"
        puts "Please contact $webmaster for more information"
    }

The keyword proc is used to define a procedure. The variables inside the first set of braces represent the arguments passed by the user. There is a big difference between Perl subroutines and Tcl procedures. Here are the two ways in which Tcl is different:

- Global values are not available within the procedure default. Before referring to a variable from a higher procedure, you have to declare it with the global command. You can also affect commands in higher-level procedures through the upvar command, which we'll look at in a moment.

- All variables declared inside a procedure are considered local, and are removed after the procedure terminates.

In this procedure, the global variable webmaster is used. The procedure puts out an error message that reflects the arguments passed.

    proc parse_form_data { form_info } {
        global env
        upvar $form_info FORM_DATA

The procedure parse_form_data is identical to its Perl counterpart at the beginning of this chapter. The environment variable array env is accessed in this procedure with the global statement. The upvar keyword allows you to create a local reference to the array passed to this subroutine. Inside the subroutine, the array referenced by form_info is accessed through FORM_DATA.

        set request_method $env(REQUEST_METHOD)
        if {[string compare $request_method "POST"] == 0} {
            set query_string [read stdin $env(CONTENT_LENGTH)]
        } elseif {[string compare $request_method "GET"] == 0} {
            set query_string $env(QUERY_STRING)
        } else {
            return_error 500 {Server Error} {Server uses unsupported method}
            exit 1
        }

This process should look familiar. The type of request determines how form information is loaded into the query_string variable. If there is an unrecognized method, the procedure return_error is called with a status of 500-Server Error.

        set key_value_pairs [split $query_string &]

The query string is split on the "&" character. If there are multiple variables-as is the case with most forms--the variable key_value_pairs will represent a list.

        foreach key_value $key_value_pairs {

The foreach loop structure iterates through each key-value pair. Notice that there is no "$" sign in front of the variable key_value. This indicates that key_value will be set each time through the loop. On the other hand, the value of the variable key_value_pairs will be substituted because of the dollar sign. If there is no dollar sign in front of key_value_pairs, Tcl will give you an error indicating that a valid list needs to be specified. This concept is very important, as many programmers forget the dollar sign when it is required, and accidentally insert it when it is not required.

            set pair [split $key_value =]
            set key [lindex $pair 0]
            set value [lindex $pair 1]

The first command divides the key from the value to create a two-element list. This list is assigned to the variable pair. Since list indexes start at zero, the key will be in list item zero and the value in list item 1. We use the lindex command to extract the key and then the value.

            regsub -all {\+} $value { } value

The regsub function substitutes characters within a string. This line of code is equivalent to the following line in Perl:

    $value =~ s/\+/ /g;

The -all switch replaces all occurrences of the pattern within the string. In this example, Tcl looks for the plus sign (the first argument) in $value (the second), replaces it with a space (the third), and writes the information back into the variable value (the fourth). You may be confused because the first value has a dollar sign while the second does not. This is because the first time around Tcl is dereferencing the variable--taking input data from it. The second time, it is storing output back into the variable, an operation that requires you to specify the variable directly rather than dereference it.

            while {[regexp {%[0-9A-Fa-f][0-9A-Fa-f]} $value matched]} {
                scan $matched "%%%x" hex
                set symbol [ctype char $hex]
                regsub -all $matched $value $symbol value
            }

This while loop decodes the hexadecimal characters. The regexp command is used to search value for the pattern "%..", which signifies a three-character string starting with the "%" character. The matched string is placed in the variable matched. This is like using parentheses in a regular expression to isolate and mark a group of characters, but the syntax is simpler. The first string that matches %.. gets assigned to matched. Then, the scan command with the "%%%x" argument converts the hexadecimal number to a decimal number. The ctype char command converts the decimal number to its ASCII equivalent. Finally, regsub replaces the hexadecimal string with the ASCII character. This process is quite tedious, especially when we compare it to Perl:

    $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;

Now, let's look at the final part of the program:

            if {[info exists FORM_DATA($key)]} {
                append FORM_DATA($key) "\0" $FORM_DATA($key)
            } else {
                set FORM_DATA($key) $value
            }
        }
    }

Remember that we started this procedure by assigning FORM_DATA to whatever variable is passed to the procedure. Now we create an entry in FORM_DATA for every key, the key being used as an index into the array. The value becomes the data that the key points to. By checking for an existing key with an if statement, we allow form variables to have multiple values, which is necessary for scrolled lists and multiple checkboxes. As in our Perl version, we put multiple values into a single array element with a null character in between.

Now, how do we call these procedures? Suppose you have two fields on your form--name and age. You could access these variables by doing the following:

    parse_form_data simple_form
    puts "Your name is: $simple_form(name) and your age is: $simple_form(age)"

The parse_form_data procedure takes the form information and places it in the simple_form array. You can then look at and manipulate data in simple_form just like any other array. OA

### Visual Basic

As we discussed in Chapter 2, Input to the Common Gateway Interface, the WebSite server for Windows NT and Windows 95--as well as the Windows 3.1 HTTP server--passes form information to the CGI program through a Windows profile file. The developer, Bob Denny, designed a library for decoding form information in Visual Basic. Let's use this library to decode some forms. But first, here is the HTML code for creating the form:

    <HTML>
    <HEAD><TITLE>Health/Exercise Survey</TITLE></HEAD>
    <BODY>
    <H1>Health/Exercise Survey</H1>
    <HR>
    <FORM ACTION="\cgi-win\exercise.exe" METHOD="POST">
    <EM>What is your name?</EM><BR>
    <INPUT TYPE="text" NAME="name" SIZE=40><BR>
    <P>
    <EM>Do you exercise regularly?</EM><BR>
    <INPUT TYPE="radio" NAME="regular" VALUE="Yes">Yes<BR>
    <INPUT TYPE="radio" NAME="regular" VALUE="No">No<BR>
    <P>
    <EM>Why do you exercise?</EM><BR>
    <INPUT TYPE="radio" NAME="why" VALUE="health">Health Benefits<BR>
    <INPUT TYPE="radio" NAME="why" VALUE="athlete">Athletic Training<BR>
    <INPUT TYPE="radio" NAME="why" VALUE="forced">Forced upon you<BR>
    <INPUT TYPE="radio" NAME="why" VALUE="enjoy">Enjoyment<BR>
    <INPUT TYPE="radio" NAME="why" VALUE="other">Other reasons<BR>
    <P>
    <EM>What sport do you <B>primarily</B> participate in?</EM><BR>
    <SELECT NAME="sports" SIZE=1>
    <OPTION>Tennis
    <OPTION>Swimming
    <OPTION>Basketball
    <OPTION>Running/Walking
    <OPTION>Cycling
    <OPTION>Skating/Rollerblading
    <OPTION>Skiing
    <OPTION>Climbing Stairs
    <OPTION>Jumping Rope
    <OPTION>Other
    </SELECT>
    <P>
    <EM>How often do you exercise?</EM><BR>
    <INPUT TYPE="radio" NAME="interval" VALUE="0">Not at all<BR>
    <INPUT TYPE="radio" NAME="interval" VALUE="1">Once a week<BR>
    <INPUT TYPE="radio" NAME="interval" VALUE="3">Three times a week<BR>
    <INPUT TYPE="radio" NAME="interval" VALUE="5">Five times a week<BR>
    <INPUT TYPE="radio" NAME="interval" VALUE="7">Every day of the week<BR>
    <P>
    <INPUT TYPE="submit" VALUE="Submit the form">
    <INPUT TYPE="reset"  VALUE="Clear all fields">
    </FORM>
    <HR>
    </BODY>
    </HTML>

Now let's build a Visual Basic CGI program to decode the form information and store the results in a data file. The program needs to be compiled before it can be used.

    Public Sub CGI_Main()

This program uses the CGI.BAS library to decode the form information. The function Main(), which in turn calls the CGI_Main(), is defined in the library.

        Dim intCtr As Integer
        Dim intFN As String
        Dim message As String

We define three variables that we will use later in the program: intCtr, intFN, and message.

        intFN = FreeFile
        Open "survey.dat" for APPEND as #intFN

The variable intFN holds an unused file handle, thanks to the FreeFile function. We then proceed to use this handle to open the file "survey.dat" in append mode; if the file does not exist, it is created.

        Print #intFN, "Results from " & CGI_RemoteHost
        Print #intFN, "-----< Start of Data >-----"

Information is output to the file by specifying the file handle with the Print statement. Visual Basic is a case-insensitive language-unlike most of the languages we have discussed so far. The variable CGI_RemoteHost represents the environment variable REMOTE_HOST.

        For intCtr = 0 To CGI_NumFormTuples - 1
           Select Case CGI_FormTuples(intCtr).key
              Case "name"
                 message = "Subject name: "
              Case "regular"
                 message = "Regular exercise: "
              Case "why"
                 message = "Reason for exercise: "
              Case "sports"
                 message = "Primarily participates in: "
              Case "interval"
                 message = "Exercise frequency: "
           End Select
           Print #intFN, message & CGI_FormTuples(intCtr).value
        Next

Unlike Perl or Tcl, Visual Basic does not have support for arrays with string indexes. As a result, you cannot have an "array(key) = value" construct. Instead, the form values are placed in a simple struct, such that the key and the value share the same numerical index.

In this case, the integer variable CGI_NumFormTuples represents the number of key-value pairs. The loop iterates through each pair and outputs a message based on the value of the key. The key and value are stored in CGI_FormTuples(index).key and CGI_FormTuples(index).value, respectively.

        Print #intFN, "-----< End of Data >-----"
        Close #intFN

The end-of-data message is output to the file, and the file is closed.

        Send ("Content-type: text/html")
        Send ("")
        Send ("<TITLE>Thanks for filling out the survey!</TITLE>")
        Send ("<H1>Thank You!</H1>")
        Send ("<HR>")
        Send ("Thanks for taking the time to fill out the form.")
        Send ("We really appreciate it!")
    End Sub

The Send function is used to output text to the server. It prints the message you specify to the file handle represented by the server.

### AppleScript

On the Macintosh, you can use either AppleScript or MacPerl to write CGI applications. Since we've looked at enough Perl examples, let's write an example in AppleScript. There are two main reasons for using AppleScript for CGI applications. First, it is quite easy to use, and the syntax looks like plain English. And second, many libraries have been designed to aid in CGI application development. Now, here is an AppleScript program that accomplishes the same task as the Visual Basic example presented earlier.

    set survey_file to "Macintosh HD:survey.dat"

The variable survey_file contains the path to the data file. This syntax is equal to:

    survey_file = "Macintosh HD:survey.dat"

The ":" character is the directory separator on the Mac, just as UNIX uses a slash and Windows uses a backslash.

    set crlf to (ASCII character 13) & (ASCII character 10)
    set http_header to  "HTTP/1.0 200 OK" & crlf & -
                        "Server: WebSTAR/1.0 ID/ACGI" & crlf & -
                        "MIME-Version: 1.0" & crlf & -
                           "Content-type: text/html" & -
                        crlf & crlf

The HTTP header that we will send to the server is defined. Notice that this is a complete response. The WebSTAR server requires that all CGI applications send a complete response. You might also be wondering why the regular newline character (\n) is not used to separate individual lines. The official HTTP specification requires that servers send "\r\n", but most UNIX browsers accept "\n", while WebSTAR does not.

    on `event WWWsdoc' path_args -
        given `class post':post_args, `class add':client_address

As explained in Chapter 2, Input to the Common Gateway Interface, this construct is used to check for an AppleEvent from WebSTAR, and to set the appropriate variables. Not all the information sent with the AppleEvent is stored in variables, however, as this program does not require most of the information. The only data that we need is the form data--passed as "POST"--and the remote address of the client.

        set post_args_without_plus to dePlus post_args
        set decoded_post_args to Decode URL post_args_without_plus

All the "+" signs in the form data are converted to spaces using the dePlus osax (Open Scripting Architecture eXtension)--which is an external program written in a high-level language, such as C. Technically, you can also accomplish the task in AppleScript, but using an osax is more efficient. Also, the form data is decoded using the Decode URL osax, and stored in decoded_post_args.

        set name       to findNamedArgument(decoded_post_args, "name")
        set regular    to findNamedArgument(decoded_post_args, "regular")
        set why        to findNamedArgument(decoded_post_args, "why")
        set sports     to findNamedArgument(decoded_post_args, "sports")
        set interval   to findNamedArgument(decoded_post_args, "interval")

The findNamedArgument function retrieves the form information for a specific field. All of the fields that comprise the form are separated and stored.

        try
            set survey_file_handle to open file alias survey_file
            position file survey_file at (get file length survey_file)
        on error
            create file survey_file owner "ttxt"
            set survey_file_handle to open file alias survey_file
        end try

These statements set up an error handler. AppleScript will try to execute the commands in the first block, but if an error occurs, the commands in the next block will be executed. Initially, the program tries to open the data file and store the file handle in survey_file_handle. If it is successful, the position command places the pointer at the end of the file. On the other hand, if there is an error, a new file is created and opened. The owner of the new file is set to TeachText ("ttxt")--a simple Macintosh file editor--so that it can be read by any text editor.

        set survey_output to "Results from " & client_address & crlf & -
                             "-----< Start of Data >-----" & crlf & -
                             "Subject name: " & name & crlf & -
                             "Regular exercise: " & regular & crlf & -
                             "Reason for exercise: " & why & crlf & -
                             "Primarily participates in: " & -
                               sports & crlf & -
                             "Exercise frequency: " & interval & crlf & -
                             "-----< End of Data >-----" & crlf

The information that will be written to the data file is built, and stored in survey_output.

        write file survey_file_handle text survey_output
        close file survey_file_handle

The information is written to the file as text, and the file is closed.

        set thank_you to http_header & -
                    "<TITLE>Thanks for filling out the survey!</TITLE>" & -
                    "<H1>Thank You!</H1>" & "<HR>" & -
                    "Thanks for taking the time to fill out the form." & -
                    "We really appreciate it!"
        return thank_you
    end `event WWWsdoc'

Finally, the return statement sends the thank-you message back to the client.

    on findNamedArgument(theText, theArg)
        try
            set oldDelims to AppleScript's text item delimiters
            set AppleScript's text item delimiters to "&"
            set numItems to (count of text items in theText)

            repeat with textCount from 1 to numItems
                set thisItem to text item textCount of theText
                try
                    set AppleScript's text item delimiters to "="
                                set argName to (first text item of thisItem)
                                if argName = theArg then
                        set resItem to (second text item of thisItem)
                        exit repeat
                                else
                                        set resItem to ""
                                end if
                                set AppleScript's text item delimiters to "&"
                on error
                                set AppleScript's text item delimiters to "&"
                end try
            end repeat

            set AppleScript's text item delimiters to oldDelims
        on error
            set AppleScript's text item delimiters to oldDelims
            set resItem to ""
        end try
        return resItem
    end findNamedArgument

This function iterates through the form information and returns the value for a specified key. It was written by Maggie Burke (mburke@umassd.edu) from the Integrated Math Tools Project. Do not worry about how this works at this moment. Doesn't it look like English?

In reality, splitting a key-value pair using this function is not the most efficient way to accomplish the task; every time you call the function, it has to iterate through the information until it finds the specified key.

Designing Applications Using Forms in Perl |  | Server Side Includes