/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2008 Apple Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
 
/*
    File:       UserAgentParser.cpp

    Contains:   Parse the User-Agent: entry of an RTSP request.
                    
    
    

*/

#include "StringParser.h"
#include "StringFormatter.h"
#include "StrPtrLen.h"
#include "UserAgentParser.h"

UserAgentParser::UserAgentFields UserAgentParser::sFieldIDs[] = 
{   /* fAttrName, len, id */
    { "qtid",   strlen("qtid"),     eQtid   },
    { "qtver",  strlen("qtver"),    eQtver  },
    { "lang",   strlen("lang"),     eLang   },
    { "os",     strlen("os"),       eOs     },
    { "osver",  strlen("osver"),    eOsver  },
    { "cpu",    strlen("cpu"),      eCpu    }
};

    
UInt8 UserAgentParser::sEOLWhitespaceEqualMask[] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9     // \t is a stop
    1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19    //'\r' & '\n' are stop conditions
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29
    0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //30-39   ' '  is a stop
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, //60-69   '=' is a stop
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249
    0, 0, 0, 0, 0, 0             //250-255
};

UInt8 UserAgentParser::sEOLSemicolonCloseParenMask[] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9     // \t is a stop
    1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19    //'\r' & '\n' are stop conditions
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //30-39   
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, //40-49   ')'  is a stop
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //50-59   ';' is a stop
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69  
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249
    0, 0, 0, 0, 0, 0             //250-255
};


void UserAgentParser::Parse(StrPtrLen *inStream)
{
    StrPtrLen tempID;
    StrPtrLen tempData;
    StringParser parser(inStream);
    StrPtrLen startFields;
        

    memset(&fFieldData,0,sizeof(fFieldData) );
    
    parser.ConsumeUntil(&startFields, '(' ); // search for '(' if not found does nothing
    
    // parse through everything between the '(' and ')'.
    while (startFields.Len != 0)
    {
        //stop when we reach an empty line.
        tempID.Set(NULL,0);
        tempData.Set(NULL,0);
        
        parser.ConsumeLength(NULL, 1); // step past '(' or ';' if not found or at end of line does nothing
        parser.ConsumeWhitespace(); // search for non-white space if not found does nothing
        parser.ConsumeUntil(&tempID, sEOLWhitespaceEqualMask ); // search for end of id (whitespace or =)if not found does nothing
        if (tempID.Len == 0) break;
    
        parser.ConsumeUntil(NULL, '=' ); // find the '='
        parser.ConsumeLength(NULL, 1); // step past if not found or at end of line does nothing
        parser.ConsumeUntil(&tempData, sEOLSemicolonCloseParenMask ); // search for end of data if not found does nothing
        if (tempData.Len == 0) break;
        
        StrPtrLen   testID;
        UInt32      fieldID;
        for (short testField = 0; testField < UserAgentParser::eNumAttributes; testField++)
        {
            testID.Set(sFieldIDs[testField].fFieldName,sFieldIDs[testField].fLen);
            fieldID = sFieldIDs[testField].fID;
            if ( (fFieldData[fieldID].fFound == false) && testID.Equal(tempID) ) 
            {   
                fFieldData[fieldID].fData = tempData;
                fFieldData[fieldID].fFound = true;              
            }
        }
        
    }
    // If we parsed the OS field but not the OSVer field then check and see if
    // the OS field contains the OS version. If it does copy it from there.
    // (e.g. 'os=Mac%209.2.2' or 'os=Windows%20NT%204.0'.)
    if (fFieldData[eOs].fFound && !fFieldData[eOsver].fFound)
    {
        UInt16 len = (UInt16)fFieldData[eOs].fData.Len;
        char* cp = (char*)fFieldData[eOs].fData.Ptr;
        // skip up to the blank space if it exists.
        // (i.e. the blank is URL encoded as '%20')
        while(*cp != '%')
        {
            len--;
            if (*cp == '\0' || len == 0)
            {
                // no blank space...so we're all done.
                return;
            }
            cp++;
        }
        // skip over the blank space.
        cp += 3; len -= 3;
        // the remaining string is the OS version.
        fFieldData[eOsver].fData.Set(cp, len);
        fFieldData[eOsver].fFound = true;
        // and truncate the version from the OS field.
        fFieldData[eOs].fData.Len -= len+3;
    }
}
