123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
/*******************************************************************************

        copyright:      Copyright (c) 2004 Kris Bell. All rights reserved

        license:        BSD style: $(LICENSE)
        
        version:        Initial release: April 2004      
        
        author:         Kris, John Reimer

*******************************************************************************/

module tango.net.http.HttpStack;

private import  tango.core.Exception;

/******************************************************************************

        Unix doesn't appear to have a memicmp() ... JJR notes that the
        strncasecmp() is available instead.

******************************************************************************/

version (Win32)
        {
        extern (C) int memicmp (const(char) *, const(char) *, size_t);
        }

version (Posix) 
        {
        extern (C) int strncasecmp (const(char) *, const(char)*, size_t);
        }

extern (C) void* memmove (void* dst, const(void)* src, int n);


/******************************************************************************

        Internal representation of a token

******************************************************************************/

class Token
{
        private const(char)[] value;

        Token set (const(char)[] value)
        {
                this.value = value;
                return this;
        }

        override string toString ()
        {
                return value.idup;
        }
}

/******************************************************************************

        A stack of Tokens, used for capturing http headers. The tokens
        themselves are typically mapped onto the content of a Buffer, 
        or some other external content, so there's minimal allocation 
        involved (typically zero).

******************************************************************************/

class HttpStack
{
        private int     depth;
        private Token[] tokens;

        private static const int MaxHttpStackSize = 256;

        /**********************************************************************

                Construct a HttpStack with the specified initial size. 
                The stack will later be resized as necessary.

        **********************************************************************/

        this (int size = 10)
        {
                tokens = new Token[0];
                resize (tokens, size);
        }

        /**********************************************************************

                Clone this stack of tokens

        **********************************************************************/

        HttpStack clone ()
        {
                // setup a new HttpStack of the same depth
                HttpStack clone = new HttpStack(depth);
                
                clone.depth = depth;

                // duplicate the content of each original token
                for (int i=0; i < depth; ++i)
                     clone.tokens[i].set (tokens[i].toString());

                return clone;
        }

        /**********************************************************************

                Iterate over all tokens in stack

        **********************************************************************/

        int opApply (scope int delegate(ref Token) dg)
        {
                int result = 0;

                for (int i=0; i < depth; ++i)
                     if ((result = dg (tokens[i])) != 0)
                          break;
                return result;
        }

        /**********************************************************************

                Pop the stack all the way back to zero

        **********************************************************************/

        final void reset ()
        {
                depth = 0;
        }

        /**********************************************************************

                Scan the tokens looking for the first one with a matching
                name. Returns the matching Token, or null if there is no
                such match.

        **********************************************************************/

        final Token findToken (const(char)[] match)
        {
                Token tok;

                for (int i=0; i < depth; ++i)
                    {
                    tok = tokens[i];
                    if (isMatch (tok, match))
                        return tok;
                    }
                return null;
        }

        /**********************************************************************

                Scan the tokens looking for the first one with a matching
                name, and remove it. Returns true if a match was found, or
                false if not.

        **********************************************************************/

        final bool removeToken (const(char)[] match)
        {
                for (int i=0; i < depth; ++i)
                     if (isMatch (tokens[i], match))
                        {
                        tokens[i].value = null;
                        return true;
                        }
                return false;
        }

        /**********************************************************************

                Return the current stack depth

        **********************************************************************/

        @property final int size ()
        {       
                return depth;
        }

        /**********************************************************************

                Push a new token onto the stack, and set it content to 
                that provided. Returns the new Token.

        **********************************************************************/

        final Token push (const(char)[] content)
        {
                return push().set (content);  
        }

        /**********************************************************************

                Push a new token onto the stack, and set it content to 
                be that of the specified token. Returns the new Token.

        **********************************************************************/

        final Token push (ref Token token)
        {
                return push (token.toString());  
        }

        /**********************************************************************

                Push a new token onto the stack, and return it.

        **********************************************************************/

        final Token push ()
        {
                if (depth == tokens.length)
                    resize (tokens, depth * 2);
                return tokens[depth++];
        }

        /**********************************************************************

                Pop the stack by one.

        **********************************************************************/

        final void pop ()
        {
                if (depth)
                    --depth;
                else
                   throw new IOException ("illegal attempt to pop Token stack");
        }

        /**********************************************************************

                See if the given token matches the specified text. The 
                two must match the minimal extent exactly.

        **********************************************************************/

        final static bool isMatch (ref Token token, const(char)[] match)
        {
                const(char)[] target = token.toString();

                size_t length = target.length;
                if (length > match.length)
                    length = match.length;

                if (length is 0)
                    return false;

                version (Win32)
                         return memicmp (target.ptr, match.ptr, length) is 0;
                version (Posix)
                         return strncasecmp (target.ptr, match.ptr, length) is 0;
        }
        
        /**********************************************************************

                Resize this stack by extending the array.

        **********************************************************************/

        final static void resize (ref Token[] tokens, int size)
        {
                size_t i = tokens.length;

                // this should *never* realistically happen 
                if (size > MaxHttpStackSize)
                    throw new IOException ("Token stack exceeds maximum depth");

                for (tokens.length=size; i < tokens.length; ++i)
                     tokens[i] = new Token();
        }
}