/** * @file llmessagetemplateparser.cpp * @brief LLMessageTemplateParser implementation * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "llmessagetemplateparser.h" #include <boost/tokenizer.hpp> // What follows is a bunch of C functions to do validation. // Lets support a small subset of regular expressions here // Syntax is a string made up of: // a - checks against alphanumeric ([A-Za-z0-9]) // c - checks against character ([A-Za-z]) // f - checks against first variable character ([A-Za-z_]) // v - checks against variable ([A-Za-z0-9_]) // s - checks against sign of integer ([-0-9]) // d - checks against integer digit ([0-9]) // * - repeat last check // checks 'a' BOOL b_return_alphanumeric_ok(char c) { if ( ( (c < 'A') ||(c > 'Z')) &&( (c < 'a') ||(c > 'z')) &&( (c < '0') ||(c > '9'))) { return FALSE; } return TRUE; } // checks 'c' BOOL b_return_character_ok(char c) { if ( ( (c < 'A') ||(c > 'Z')) &&( (c < 'a') ||(c > 'z'))) { return FALSE; } return TRUE; } // checks 'f' BOOL b_return_first_variable_ok(char c) { if ( ( (c < 'A') ||(c > 'Z')) &&( (c < 'a') ||(c > 'z')) &&(c != '_')) { return FALSE; } return TRUE; } // checks 'v' BOOL b_return_variable_ok(char c) { if ( ( (c < 'A') ||(c > 'Z')) &&( (c < 'a') ||(c > 'z')) &&( (c < '0') ||(c > '9')) &&(c != '_')) { return FALSE; } return TRUE; } // checks 's' BOOL b_return_signed_integer_ok(char c) { if ( ( (c < '0') ||(c > '9')) &&(c != '-')) { return FALSE; } return TRUE; } // checks 'd' BOOL b_return_integer_ok(char c) { if ( (c < '0') ||(c > '9')) { return FALSE; } return TRUE; } BOOL (*gParseCheckCharacters[])(char c) = { b_return_alphanumeric_ok, b_return_character_ok, b_return_first_variable_ok, b_return_variable_ok, b_return_signed_integer_ok, b_return_integer_ok }; S32 get_checker_number(char checker) { switch(checker) { case 'a': return 0; case 'c': return 1; case 'f': return 2; case 'v': return 3; case 's': return 4; case 'd': return 5; case '*': return 9999; default: return -1; } } // check token based on passed simplified regular expression BOOL b_check_token(const char *token, const char *regexp) { S32 tptr, rptr = 0; S32 current_checker, next_checker = 0; current_checker = get_checker_number(regexp[rptr++]); if (current_checker == -1) { llerrs << "Invalid regular expression value!" << llendl; return FALSE; } if (current_checker == 9999) { llerrs << "Regular expression can't start with *!" << llendl; return FALSE; } for (tptr = 0; token[tptr]; tptr++) { if (current_checker == -1) { llerrs << "Input exceeds regular expression!\nDid you forget a *?" << llendl; return FALSE; } if (!gParseCheckCharacters[current_checker](token[tptr])) { return FALSE; } if (next_checker != 9999) { next_checker = get_checker_number(regexp[rptr++]); if (next_checker != 9999) { current_checker = next_checker; } } } return TRUE; } // C variable can be made up of upper or lower case letters, underscores, or numbers, but can't start with a number BOOL b_variable_ok(const char *token) { if (!b_check_token(token, "fv*")) { llwarns << "Token '" << token << "' isn't a variable!" << llendl; return FALSE; } return TRUE; } // An integer is made up of the digits 0-9 and may be preceded by a '-' BOOL b_integer_ok(const char *token) { if (!b_check_token(token, "sd*")) { llwarns << "Token isn't an integer!" << llendl; return FALSE; } return TRUE; } // An integer is made up of the digits 0-9 BOOL b_positive_integer_ok(const char *token) { if (!b_check_token(token, "d*")) { llwarns << "Token isn't an integer!" << llendl; return FALSE; } return TRUE; } // Done with C functions, here's the tokenizer. typedef boost::tokenizer< boost::char_separator<char> > tokenizer; LLTemplateTokenizer::LLTemplateTokenizer(const std::string & contents) : mStarted(false), mTokens() { boost::char_separator<char> newline("\r\n", "", boost::keep_empty_tokens); boost::char_separator<char> spaces(" \t"); U32 line_counter = 1; tokenizer line_tokens(contents, newline); for(tokenizer::iterator line_iter = line_tokens.begin(); line_iter != line_tokens.end(); ++line_iter, ++line_counter) { tokenizer word_tokens(*line_iter, spaces); for(tokenizer::iterator word_iter = word_tokens.begin(); word_iter != word_tokens.end(); ++word_iter) { if((*word_iter)[0] == '/') { break; // skip to end of line on comments } positioned_token pt;// = new positioned_token(); pt.str = std::string(*word_iter); pt.line = line_counter; mTokens.push_back(pt); } } mCurrent = mTokens.begin(); } void LLTemplateTokenizer::inc() { if(atEOF()) { error("trying to increment token of EOF"); } else if(mStarted) { ++mCurrent; } else { mStarted = true; mCurrent = mTokens.begin(); } } void LLTemplateTokenizer::dec() { if(mCurrent == mTokens.begin()) { if(mStarted) { mStarted = false; } else { error("trying to decrement past beginning of file"); } } else { mCurrent--; } } std::string LLTemplateTokenizer::get() const { if(atEOF()) { error("trying to get EOF"); } return mCurrent->str; } U32 LLTemplateTokenizer::line() const { if(atEOF()) { return 0; } return mCurrent->line; } bool LLTemplateTokenizer::atEOF() const { return mCurrent == mTokens.end(); } std::string LLTemplateTokenizer::next() { inc(); return get(); } bool LLTemplateTokenizer::want(const std::string & token) { if(atEOF()) return false; inc(); if(atEOF()) return false; if(get() != token) { dec(); // back up a step return false; } return true; } bool LLTemplateTokenizer::wantEOF() { // see if the next token is EOF if(atEOF()) return true; inc(); if(!atEOF()) { dec(); // back up a step return false; } return true; } void LLTemplateTokenizer::error(std::string message) const { if(atEOF()) { llerrs << "Unexpected end of file: " << message << llendl; } else { llerrs << "Problem parsing message template at line " << line() << ", with token '" << get() << "' : " << message << llendl; } } // Done with tokenizer, next is the parser. LLTemplateParser::LLTemplateParser(LLTemplateTokenizer & tokens): mVersion(0.f), mMessages() { // the version number should be the first thing in the file if (tokens.want("version")) { // version number std::string vers_string = tokens.next(); mVersion = (F32)atof(vers_string.c_str()); llinfos << "### Message template version " << mVersion << " ###" << llendl; } else { llerrs << "Version must be first in the message template, found " << tokens.next() << llendl; } while(LLMessageTemplate * templatep = parseMessage(tokens)) { if (templatep->getDeprecation() != MD_DEPRECATED) { mMessages.push_back(templatep); } else { delete templatep; } } if(!tokens.wantEOF()) { llerrs << "Expected end of template or a message, instead found: " << tokens.next() << " at " << tokens.line() << llendl; } } F32 LLTemplateParser::getVersion() const { return mVersion; } LLTemplateParser::message_iterator LLTemplateParser::getMessagesBegin() const { return mMessages.begin(); } LLTemplateParser::message_iterator LLTemplateParser::getMessagesEnd() const { return mMessages.end(); } // static LLMessageTemplate * LLTemplateParser::parseMessage(LLTemplateTokenizer & tokens) { LLMessageTemplate *templatep = NULL; if(!tokens.want("{")) { return NULL; } // name first std::string template_name = tokens.next(); // is name a legit C variable name if (!b_variable_ok(template_name.c_str())) { llerrs << "Not legit variable name: " << template_name << " at " << tokens.line() << llendl; } // ok, now get Frequency ("High", "Medium", or "Low") EMsgFrequency frequency = MFT_LOW; std::string freq_string = tokens.next(); if (freq_string == "High") { frequency = MFT_HIGH; } else if (freq_string == "Medium") { frequency = MFT_MEDIUM; } else if (freq_string == "Low" || freq_string == "Fixed") { frequency = MFT_LOW; } else { llerrs << "Expected frequency, got " << freq_string << " at " << tokens.line() << llendl; } // TODO more explicit checking here pls U32 message_number = strtoul(tokens.next().c_str(),NULL,0); switch (frequency) { case MFT_HIGH: break; case MFT_MEDIUM: message_number = (255 << 8) | message_number; break; case MFT_LOW: message_number = (255 << 24) | (255 << 16) | message_number; break; default: llerrs << "Unknown frequency enum: " << frequency << llendl; } templatep = new LLMessageTemplate( template_name.c_str(), message_number, frequency); // Now get trust ("Trusted", "NotTrusted") std::string trust = tokens.next(); if (trust == "Trusted") { templatep->setTrust(MT_TRUST); } else if (trust == "NotTrusted") { templatep->setTrust(MT_NOTRUST); } else { llerrs << "Bad trust " << trust << " at " << tokens.line() << llendl; } // get encoding std::string encoding = tokens.next(); if(encoding == "Unencoded") { templatep->setEncoding(ME_UNENCODED); } else if(encoding == "Zerocoded") { templatep->setEncoding(ME_ZEROCODED); } else { llerrs << "Bad encoding " << encoding << " at " << tokens.line() << llendl; } // get deprecation if(tokens.want("Deprecated")) { templatep->setDeprecation(MD_DEPRECATED); } else if (tokens.want("UDPDeprecated")) { templatep->setDeprecation(MD_UDPDEPRECATED); } else if (tokens.want("UDPBlackListed")) { templatep->setDeprecation(MD_UDPBLACKLISTED); } else if (tokens.want("NotDeprecated")) { // this is the default value, but it can't hurt to set it twice templatep->setDeprecation(MD_NOTDEPRECATED); } else { // It's probably a brace, let's just start block processing } while(LLMessageBlock * blockp = parseBlock(tokens)) { templatep->addBlock(blockp); } if(!tokens.want("}")) { llerrs << "Expecting closing } for message " << template_name << " at " << tokens.line() << llendl; } return templatep; } // static LLMessageBlock * LLTemplateParser::parseBlock(LLTemplateTokenizer & tokens) { LLMessageBlock * blockp = NULL; if(!tokens.want("{")) { return NULL; } // name first std::string block_name = tokens.next(); // is name a legit C variable name if (!b_variable_ok(block_name.c_str())) { llerrs << "not a legal block name: " << block_name << " at " << tokens.line() << llendl; } // now, block type ("Single", "Multiple", or "Variable") std::string block_type = tokens.next(); // which one is it? if (block_type == "Single") { // ok, we can create a block blockp = new LLMessageBlock(block_name.c_str(), MBT_SINGLE); } else if (block_type == "Multiple") { // need to get the number of repeats std::string repeats = tokens.next(); // is it a legal integer if (!b_positive_integer_ok(repeats.c_str())) { llerrs << "not a legal integer for block multiple count: " << repeats << " at " << tokens.line() << llendl; } // ok, we can create a block blockp = new LLMessageBlock(block_name.c_str(), MBT_MULTIPLE, atoi(repeats.c_str())); } else if (block_type == "Variable") { // ok, we can create a block blockp = new LLMessageBlock(block_name.c_str(), MBT_VARIABLE); } else { llerrs << "bad block type: " << block_type << " at " << tokens.line() << llendl; } while(LLMessageVariable * varp = parseVariable(tokens)) { blockp->addVariable(varp->getName(), varp->getType(), varp->getSize()); delete varp; } if(!tokens.want("}")) { llerrs << "Expecting closing } for block " << block_name << " at " << tokens.line() << llendl; } return blockp; } // static LLMessageVariable * LLTemplateParser::parseVariable(LLTemplateTokenizer & tokens) { LLMessageVariable * varp = NULL; if(!tokens.want("{")) { return NULL; } std::string var_name = tokens.next(); if (!b_variable_ok(var_name.c_str())) { llerrs << "Not a legit variable name: " << var_name << " at " << tokens.line() << llendl; } std::string var_type = tokens.next(); if (var_type == "U8") { varp = new LLMessageVariable(var_name.c_str(), MVT_U8, 1); } else if (var_type == "U16") { varp = new LLMessageVariable(var_name.c_str(), MVT_U16, 2); } else if (var_type == "U32") { varp = new LLMessageVariable(var_name.c_str(), MVT_U32, 4); } else if (var_type == "U64") { varp = new LLMessageVariable(var_name.c_str(), MVT_U64, 8); } else if (var_type == "S8") { varp = new LLMessageVariable(var_name.c_str(), MVT_S8, 1); } else if (var_type == "S16") { varp = new LLMessageVariable(var_name.c_str(), MVT_S16, 2); } else if (var_type == "S32") { varp = new LLMessageVariable(var_name.c_str(), MVT_S32, 4); } else if (var_type == "S64") { varp = new LLMessageVariable(var_name.c_str(), MVT_S64, 8); } else if (var_type == "F32") { varp = new LLMessageVariable(var_name.c_str(), MVT_F32, 4); } else if (var_type == "F64") { varp = new LLMessageVariable(var_name.c_str(), MVT_F64, 8); } else if (var_type == "LLVector3") { varp = new LLMessageVariable(var_name.c_str(), MVT_LLVector3, 12); } else if (var_type == "LLVector3d") { varp = new LLMessageVariable(var_name.c_str(), MVT_LLVector3d, 24); } else if (var_type == "LLVector4") { varp = new LLMessageVariable(var_name.c_str(), MVT_LLVector4, 16); } else if (var_type == "LLQuaternion") { varp = new LLMessageVariable(var_name.c_str(), MVT_LLQuaternion, 12); } else if (var_type == "LLUUID") { varp = new LLMessageVariable(var_name.c_str(), MVT_LLUUID, 16); } else if (var_type == "BOOL") { varp = new LLMessageVariable(var_name.c_str(), MVT_BOOL, 1); } else if (var_type == "IPADDR") { varp = new LLMessageVariable(var_name.c_str(), MVT_IP_ADDR, 4); } else if (var_type == "IPPORT") { varp = new LLMessageVariable(var_name.c_str(), MVT_IP_PORT, 2); } else if (var_type == "Fixed" || var_type == "Variable") { std::string variable_size = tokens.next(); if (!b_positive_integer_ok(variable_size.c_str())) { llerrs << "not a legal integer variable size: " << variable_size << " at " << tokens.line() << llendl; } EMsgVariableType type_enum; if(var_type == "Variable") { type_enum = MVT_VARIABLE; } else if(var_type == "Fixed") { type_enum = MVT_FIXED; } else { type_enum = MVT_FIXED; // removes a warning llerrs << "bad variable type: " << var_type << " at " << tokens.line() << llendl; } varp = new LLMessageVariable( var_name.c_str(), type_enum, atoi(variable_size.c_str())); } else { llerrs << "bad variable type:" << var_type << " at " << tokens.line() << llendl; } if(!tokens.want("}")) { llerrs << "Expecting closing } for variable " << var_name << " at " << tokens.line() << llendl; } return varp; }