
\subsubsection{Idea}

We add a category to {\tt NSMutableDictionary} that contains a method
that initializes the dictionary with the variables that were passed to
the CGI script.

The procedure is simple.
\begin{enumerate}
\item Obtain the data from environment variables: if {\tt
REQUEST\_METHOD} is {\tt POST} read {\tt CONTENT\_LENGTH} bytes from
the standard input; if it is {\tt GET}, use the contents of {\tt
QUERY\_STRING.}
\item Split the data into key-value pairs. This is done by splitting
on the ampersand character {\tt \&.} The data consists of a sequence
of such pairs, where the equal sign {\tt =} denotes assignment, as in
\begin{center} {\tt key1=value1\&key2=value2.} \end{center}
\item Replace the plus character {\tt +} by a space and hexadecimal
escape sequences of the form {\tt \%XX} by the corresponding ASCII
character in both the key and the value.
\end{enumerate}

\subsubsection{Implementation}

We start by adding a method to {\tt NSString} that will carry out the
last of the steps that we described above. It will scan the respective
{\tt C} string and and write characters into a temporary buffer. We
allocate the same number of bytes as in the original string since it
can only get shorter.

\begin{lstlisting}{}
@implementation NSString (CGI)

- parseCGI
{
    int pos, length = [self length];
    const char *cstr = [self cString];

    NSZone *zone = NSDefaultMallocZone();

    char *rbuf, *rcur;
    NSString *result;

    rbuf = rcur =
        NSZoneMalloc(zone, length*sizeof(char));
\end{lstlisting}

We use a loop to do the actual processing. A plus character is recorded
as a space character. A complete hexadecimal escape sequence is passed
to {\tt sscanf} for conversion into an unsigned integer, the lowest
byte of which is recorded in the buffer.

\begin{lstlisting}{}
    for(pos=0; pos<length; pos++){
        if(cstr[pos]=='+'){
            *rcur++ = ' ';
        }
        else if(cstr[pos]=='%' && pos+2<length &&
                isxdigit(cstr[pos+1]) && isxdigit(cstr[pos+2])){
            char hex[3] = { cstr[pos+1], cstr[pos+2], 0 };
            unsigned int dec;
            sscanf(hex, "%x", &dec);
            pos += 2;

            *rcur++ = dec;
        }
        else{
            *rcur++ = cstr[pos];
        }
    }
    result = [NSString stringWithCString:rbuf length:rcur-rbuf];
    NSZoneFree(zone, rbuf);

    return result;
}
\end{lstlisting}

Finally the method converts the data into the result string and frees
the temporary buffer.

The method {\tt initWithCGI} (added to {\tt NSMutableString})
actutally creates the dictionary. We prepare for this by reading the
process environment and immediately consulting {\tt REQUEST\_METHOD}
to determine how to obtain the raw data. We will scan the data from
start to finish and declare three variables to maintain state during
the scan: {\tt prev}, the first character of the current key-value
pair, {\tt eqpos,} the position of the equal sign in the current pair,
and {\tt pos}, which gives the first position of the next pair or the
end of the data.

\begin{lstlisting}{}
@implementation NSMutableDictionary (CGI)

- (id)initWithCGI
{
    NSProcessInfo *procInfo = [NSProcessInfo processInfo];
    NSDictionary *env = [procInfo environment];
    
    NSString *method = [env objectForKey:@"REQUEST_METHOD"];

    int length, prev, pos, eqpos;
    const char *bytes;
\end{lstlisting}

We obtain the data first. {\tt REQUEST\_METHOD} tells us how. We raise
an exception if the data available on the standard input do not
contain the number of bytes specified in {\tt CONTENT\_LENGTH} (this
applies to {\tt POST} submissions). We initialize the dictionary by
preparing it to hold eight pairs (we could have chosen a different
capacity).

\begin{lstlisting}{}
    if([method isEqualToString:@"GET"]){
        NSString *str = [env objectForKey:@"QUERY_STRING"];

        length = [str length];
        bytes  = [str cString];
    }
    else if([method isEqualToString:@"POST"]){
        NSData *data;

        length = [[env objectForKey:@"CONTENT_LENGTH"] 
                     intValue];
        data = [[NSFileHandle fileHandleWithStandardInput]
                   readDataOfLength:length];
        if([data length]!=length){
            NSString *fmt = 
                @"expected %d characters on STDIN, got %d";
            [NSException raise:NSRangeException format:fmt,
                         length, [data length]];
        }
        bytes = [data bytes];
    }
    else{
        [NSException raise:NSInvalidArgumentException
                     format:@"request method not supported: %@", 
                     method];
    }

    [self initWithCapacity:8];
\end{lstlisting}

Now start the loop. We record the most recent occurence of the equal
sign for use in the key-value split.

\begin{lstlisting}{}
    for(prev=0, pos=0; pos<length; pos++){
        if(bytes[pos]=='='){
            eqpos = pos;
        }
\end{lstlisting}

An ampersand signals that a complete pair has been scanned, as does
having scanned up to the last character of the string. We compute the
length of the key string and the value string and make sure they
contain meaningful values, e.g. the key should not be empty.

\begin{lstlisting}{}
        if(bytes[pos]=='&' || (pos>0 && pos==length-1)){
            int klen, vlen;
            NSString *key, *value;
            id lookup;

            klen = eqpos-prev;
            vlen = pos-eqpos-1+(bytes[pos]=='&' ? 0 : 1);

            if(!(klen>0 && vlen>=0)){
                [NSException raise:NSInvalidArgumentException
                             format:@"malformed query/content"];
            }
\end{lstlisting}

It remains to convert the source bytes into Objective C strings and
parse them with {\tt parseCGI} as discussed previously.

\begin{lstlisting}{}
            key = 
                [[NSString 
                     stringWithCString:bytes+prev 
                     length:klen] parseCGI];
            value =
                [[NSString 
                     stringWithCString:bytes+eqpos+1 
                     length:vlen] parseCGI];
\end{lstlisting}

Now put the pair into the dictionary. There are three cases.
\begin{enumerate}
\item The key is not in the dictionary. We add the key-value pair.
\item The key is already in the dictionary but it is not an array. We
have found a second value for the key, and replace the object that it
refers to by an array containing the two values.
\item The key is in the dictionary and refers to an array. We add the
new value to the array.
\end{enumerate}

Multiple values for the same key arise when the state of a set of
checkboxes is submitted. We store the start position of the next pair
in {\tt prev} at the end of the body of the loop.

\begin{lstlisting}{}
            lookup = [self objectForKey:key];
            
            if(lookup==nil){
                [self setObject:value forKey:key];
            }
            else if([lookup isKindOfClass:
                                [NSMutableArray class]]==NO){
                [self setObject:[NSMutableArray 
                                    arrayWithObjects:value,
                                    lookup, nil]
                    forKey:key];
            }
            else{
                [lookup addObject:value];
            }

            prev = pos+1;
        }
    }

    return self;
}
\end{lstlisting}

The routine returns {\tt self}, which now contains all pairs that were
submitted.

This code was inspired by {\tt cgi-lib.pl} by Steven E. Brenner.



