// 
// 
// Copyright (C) 2004 SIPfoundry Inc.
// Licensed by SIPfoundry under the LGPL license.
// 
// Copyright (C) 2004 Pingtel Corp.
// Licensed to SIPfoundry under a Contributor Agreement.
// 
// $$
//////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES
#include <assert.h>
#include <stdlib.h>

// APPLICATION INCLUDES
#include "os/OsSysLog.h"
#include "os/OsDefs.h"
#include "utl/UtlRegex.h"
#include "net/Url.h"
#include "net/SipMessage.h"

#include "ForwardRules.h"

// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS

/* //////////////////////////// PUBLIC //////////////////////////////////// */
// Constructor
ForwardRules::ForwardRules()
{
   mDoc = NULL;

}

// Destructor
ForwardRules::~ForwardRules()
{
 
}
/* ============================ MANIPULATORS ============================== */
OsStatus ForwardRules::loadMappings(const UtlString configFileName,
                                  const UtlString mediaserver,
                                  const UtlString& voicemail,
                                  const UtlString& localhost)
{
   OsStatus currentStatus = OS_SUCCESS;

   if(mDoc) delete mDoc;

 	mDoc = new TiXmlDocument( configFileName.data() );
   if( !mDoc->LoadFile() )
   {
      UtlString parseError = mDoc->ErrorDesc();

      OsSysLog::add( FAC_SIP, PRI_ERR, "ERROR parsing forwardingrules '%s': %s"
                    ,configFileName.data(), parseError.data());

      return OS_NOT_FOUND;
   }
   
   if(!voicemail.isNull())
      mVoicemail.append(voicemail);
   
   if(!localhost.isNull())
      mLocalhost.append(localhost);
   
   if(!mediaserver.isNull())
      mMediaServer.append(mediaserver);
   
   return currentStatus;
}

void ForwardRules::buildDefaultRules(const char* domain,
                                     const char* hostname,
                                     const char* ipAddress,
                                     const char* fqhn,
                                     int localPort)
{
    if(mDoc) delete mDoc;

    mDoc = new TiXmlDocument();
    buildDefaultRules(domain,
                      hostname,
                      ipAddress,
                      fqhn,
                      localPort,
                      *mDoc);
    mDoc->Print();
}

void ForwardRules::buildDefaultRules(const char* domain,
                                     const char* hostname,
                                     const char* ipAddress,
                                     const char* fqhn,
                                     int localPort,
                                     TiXmlDocument& xmlDoc)
{
    // Note: fqhn == fully qualified host name



    UtlString hostnamePort(hostname ? hostname : "localhost");
    UtlString domainPort(domain ? domain : "");
    UtlString ipAddressPort(ipAddress ? ipAddress : "127.0.0.1");
    UtlString fqhnPort(fqhn ? fqhn : "localhost");

    if(localPort == 5060) localPort = PORT_NONE;
    if(portIsValid(localPort))
    {
        char portString[40];
        sprintf(portString,":%d", localPort);
        hostnamePort.append(portString);
        domainPort.append(portString);
        ipAddressPort.append(portString);
        fqhnPort.append(portString);
    }

    UtlString sdsAddress(fqhn);
    sdsAddress.append(":5090");
    UtlString statusAddress(fqhn);
    statusAddress.append(":5110");
    UtlString regAddress(fqhn);
    regAddress.append(":5070");
    UtlString configAddress("sipuaconfig");
    UtlString configFqhnAddress(configAddress);
    configFqhnAddress.append(".");
    configFqhnAddress.append(domain);

    TiXmlElement routes("routes");
    TiXmlElement route("route");
    route.SetAttribute("mappingType", "local");

    TiXmlElement routeFromDomain("routeFrom");
    TiXmlText domainText(domainPort.data());

    TiXmlElement routeFromFqhn("routeFrom");
    TiXmlText fqhnText(fqhnPort.data());

    TiXmlElement routeFromHost("routeFrom");
    TiXmlText hostText(hostnamePort.data());

    TiXmlElement routeFromIp("routeFrom");
    TiXmlText ipText(ipAddressPort.data());

    TiXmlElement methodMatch("methodMatch");

    TiXmlElement methodPattern("methodPattern");
    TiXmlText subscribe("SUBSCRIBE");

    TiXmlElement fieldMatchConfig("fieldMatch");
    fieldMatchConfig.SetAttribute("fieldName", "Event");

    TiXmlElement fieldPatternConfig("fieldPattern");
    TiXmlText configEvent("sip-config");

    TiXmlElement routeToSds("routeTo");
    TiXmlText sdsText(sdsAddress.data());

    TiXmlElement fieldMatchStatus("fieldMatch");
    fieldMatchStatus.SetAttribute("fieldName", "Event");

    TiXmlElement fieldPatternStatus("fieldPattern");
    TiXmlText mwiEvent("message-summary*");

    TiXmlElement routeToStatus("routeTo");
    TiXmlText statusText(statusAddress.data());

    TiXmlElement routeToReg("routeTo");
    TiXmlText regText(regAddress.data());

    TiXmlElement routeConfig("route");

    TiXmlElement routeFromFqhnConfig("routeFrom");
    TiXmlText fqhnConfigText(configFqhnAddress.data());

    TiXmlElement routeFromConfig("routeFrom");
    TiXmlText configText(configAddress.data());

    // Link everyting up in reverse order as it TinyXml 
    // makes copies
    routeFromDomain.InsertEndChild(domainText);
    route.InsertEndChild(routeFromDomain);
    routeFromHost.InsertEndChild(hostText);
    route.InsertEndChild(routeFromHost);
    routeFromFqhn.InsertEndChild(fqhnText);
    route.InsertEndChild(routeFromFqhn);
    routeFromIp.InsertEndChild(ipText);
    route.InsertEndChild(routeFromIp);

    methodPattern.InsertEndChild(subscribe);
    methodMatch.InsertEndChild(methodPattern);

    fieldPatternStatus.InsertEndChild(mwiEvent);
    fieldMatchStatus.InsertEndChild(fieldPatternStatus);
    routeToStatus.InsertEndChild(statusText);
    fieldMatchStatus.InsertEndChild(routeToStatus);
    methodMatch.InsertEndChild(fieldMatchStatus);

    fieldPatternConfig.InsertEndChild(configEvent);
    fieldMatchConfig.InsertEndChild(fieldPatternConfig);
    routeToSds.InsertEndChild(sdsText);
    fieldMatchConfig.InsertEndChild(routeToSds);
    methodMatch.InsertEndChild(fieldMatchConfig);

    routeToReg.InsertEndChild(regText);
    methodMatch.InsertEndChild(routeToReg);
    route.InsertEndChild(methodMatch);

    route.InsertEndChild(routeToReg);

    routeFromFqhnConfig.InsertEndChild(fqhnConfigText);
    routeConfig.InsertEndChild(routeFromFqhnConfig);

    routeFromConfig.InsertEndChild(configText);
    routeConfig.InsertEndChild(routeFromConfig);

    routeConfig.InsertEndChild(routeToReg);

    routes.InsertEndChild(route);
    routes.InsertEndChild(routeConfig);

    xmlDoc.InsertEndChild(routes);

}

/* ============================ CREATORS ================================== */
OsStatus ForwardRules::getRoute(const Url& requestUri,
                                const SipMessage& request,
                                UtlString& routeToString,
                                UtlString& mappingType)

{
    OsStatus currentStatus = OS_FAILED;

    // Get the "routes" element.
    TiXmlNode* prevRouteNode = mDoc->FirstChild( XML_TAG_ROUTES);
    if (!prevRouteNode)
    {
        OsSysLog::add(FAC_SIP, PRI_ERR, "UrlMapping::loadMappings - No child Node for Mappings");
        return OS_FILE_READ_FAILED;
    }

    currentStatus = parseRouteMatchContainer(requestUri,
                                            request,
                                            routeToString,
                                            mappingType,
                                            prevRouteNode);

    return currentStatus;

}
OsStatus ForwardRules::parseRouteMatchContainer(const Url& requestUri,
                                              const SipMessage& request,
                                              UtlString& routeToString,
                                              UtlString& mappingType,
                                              TiXmlNode* routesNode,
                                              TiXmlNode* previousRouteMatchNode)
{
   UtlString testHost;
   requestUri.getHostAddress(testHost);
   int testPort = requestUri.getHostPort();
   if(testPort == SIP_PORT)
   {
      testPort = PORT_NONE;
   }
   
   UtlBoolean routeMatchFound = false;
   OsStatus methodMatchFound = OS_FAILED;
   
  	TiXmlElement* routesElement = routesNode->ToElement();

   TiXmlNode* routeMatchNode = previousRouteMatchNode;
   // Iterate through routes container children looking for 
   // route tags
   while ( (routeMatchNode = routesElement->IterateChildren(routeMatchNode)) 
      && methodMatchFound != OS_SUCCESS)
   {
      mappingType.remove(0);

      // Skip non-elements
      if(routeMatchNode && routeMatchNode->Type() != TiXmlNode::ELEMENT)
      {
         continue;
      }

      // Skip non-route elements
      TiXmlElement* routeMatchElement = routeMatchNode->ToElement();
      UtlString tagValue =  routeMatchElement->Value();
      if(tagValue.compareTo(XML_TAG_ROUTEMATCH) != 0 )
      {
         continue;
      }

      const char* mappingTypePtr = 
          routeMatchElement->Attribute(XML_ATT_MAPPINGTYPE);

      //get the mapping Type attribute
      mappingType.append(mappingTypePtr ? mappingTypePtr : "");

      // Iterate through the route container's children looking
      // for routeFrom elements
      TiXmlNode* routeFromPatternNode = NULL;
      for( routeFromPatternNode = routeMatchElement->FirstChild( XML_TAG_ROUTEFROM);
         routeFromPatternNode;
         routeFromPatternNode = routeFromPatternNode->NextSibling( XML_TAG_ROUTEFROM ) )
      {
             // Skip non-elements
         if(routeFromPatternNode && routeFromPatternNode->Type() != TiXmlNode::ELEMENT)
         {
            continue;
         }

         //found routeFrom pattern tag
         TiXmlElement* routeFromPatternElement = routeFromPatternNode->ToElement();
         //get the host text value from it
         TiXmlNode* routeFromPatternText = routeFromPatternElement->FirstChild();
         if(routeFromPatternText && routeFromPatternText->Type() == TiXmlNode::TEXT)
         {
            TiXmlText* Xmlhost = routeFromPatternText->ToText();
            if (Xmlhost)
            {
               UtlString host = Xmlhost->Value();
               Url xmlUrl(host.data());
               UtlString xmlHost;
               xmlUrl.getHostAddress(xmlHost);
               int xmlPort = xmlUrl.getHostPort();

               // See if the host and port of the routeFrom elelment
               // match that of the URI
               if( (xmlHost.compareTo(testHost, UtlString::ignoreCase) == 0) &&
                  ((xmlPort == SIP_PORT && testPort == PORT_NONE) ||
                   xmlPort == testPort) )
               {
                  routeMatchFound = true;
                  previousRouteMatchNode = routeMatchNode;
                  // Find a match to the request method and recurse
                  // to find child element field(s) matches  and
                  // get the routeTo value
                  methodMatchFound = parseMethodMatchContainer(request,
                     routeToString,
                     routeMatchNode);

                  if( methodMatchFound == OS_SUCCESS)
                     break;
               }
            }
         }
      }
   }
   return methodMatchFound;
}

OsStatus ForwardRules::parseMethodMatchContainer(const SipMessage& request,
                                                 UtlString& routeToString,
                                                 TiXmlNode* routeMatchNode,
                                                 TiXmlNode* previousMethodMatchNode)
{
 
   OsStatus fieldMatchFound = OS_FAILED;
   UtlBoolean methodMatchFound = false;
   UtlString method;
   request.getRequestMethod(&method);

   TiXmlNode* methodMatchNode = previousMethodMatchNode;
   TiXmlElement* routeMatchElement = routeMatchNode->ToElement();

   // Iterate through the children of the routeFrom container
   // looking for methodMatch elements
   while ( (methodMatchNode = routeMatchElement->IterateChildren( methodMatchNode)) 
      && (fieldMatchFound != OS_SUCCESS) ) 
   {
       // Skip non-elements
      if(methodMatchNode && methodMatchNode->Type() != TiXmlNode::ELEMENT)
      {
         continue;
      }

      // Skip non-methodMatch elements
      UtlString tagValue = methodMatchNode->Value();
      if(tagValue.compareTo(XML_TAG_METHODMATCH) != 0 )
      {
         continue;
      }

      //found methodPattern tag
      TiXmlElement* methodMatchElement = methodMatchNode->ToElement();
      TiXmlNode* methodPatternNode = NULL;
      // Iteratore through the children of the methodMatch element
      // looking for the first methodPattern element that matches
      for( methodPatternNode = methodMatchElement->FirstChild( XML_TAG_METHODPATTERN);
         methodPatternNode;
         methodPatternNode = methodPatternNode->NextSibling(XML_TAG_METHODPATTERN ) )
      {
         // Skip non-elements
         if(methodPatternNode && methodPatternNode->Type() != TiXmlNode::ELEMENT)
         {
            continue;
         }

         TiXmlElement* methodPatternElement = methodPatternNode->ToElement();

         // Get the value contained in the methodPattern element
         TiXmlNode* methodPatternText = methodPatternElement->FirstChild();
         if(methodPatternText && methodPatternText->Type() == TiXmlNode::TEXT)
         {
            TiXmlText* XmlMethod = methodPatternText->ToText();
            if (XmlMethod)
            {
                // If the method of the request matches the method in
                // the methodMatch element
               UtlString methodString = XmlMethod->Value();
               if (methodString.compareTo(method, UtlString::ignoreCase) == 0 )
               {
                  // Found a matching method, see if there is a fieldMatch
                  // with a fieldName attribute that matches the fields
                  // in the message
                  methodMatchFound = true;
                  fieldMatchFound = parseFieldMatchContainer(request,
                     routeToString,
                     methodMatchNode);

                  if(fieldMatchFound == OS_SUCCESS)
                  {
                     break;
                  }

                  // None of the fields matched, see if the methodMatch
                  // element has an immediate child routeTo element.
                  // This is the "default" if none of the fieldMatches
                  // matched.
                  else
                  {
                      fieldMatchFound = getRouteTo(routeToString, 
                          methodMatchElement);
                      if(fieldMatchFound == OS_SUCCESS)
                      {
                          break;
                      }
                  }
               }
            }
         }
      }
   }

   if(fieldMatchFound == OS_FAILED)
   {
      // if none of the method match were successfull or if no methodMatch node present
      // get the default routeTo for this routeNode.
      fieldMatchFound = getRouteTo(routeToString,
            routeMatchNode);
   }
   return fieldMatchFound;
}

OsStatus ForwardRules::parseFieldMatchContainer(const SipMessage& request,
                                                UtlString& routeToString,
                                                TiXmlNode* methodMatchNode,
                                                TiXmlNode* previousFieldMatchNode)
{
  
   OsStatus getRouteFound = OS_FAILED;
   UtlBoolean fieldPatternFound = false;

   TiXmlNode* fieldMatchNode = previousFieldMatchNode;

   while ( (fieldMatchNode = methodMatchNode->IterateChildren( fieldMatchNode))
      && (getRouteFound != OS_SUCCESS)   ) 
   {

      UtlBoolean fieldNameMatches = false;
      UtlBoolean noFieldPatternRequired = false;

      if(fieldMatchNode && fieldMatchNode->Type() != TiXmlNode::ELEMENT)
      {
         continue;
      }

      UtlString tagValue = fieldMatchNode->Value();
      if(tagValue.compareTo(XML_TAG_FIELDMATCH) != 0 )
      {
         continue;
      }

      TiXmlElement* fieldMatchElement  = fieldMatchNode->ToElement();
      TiXmlNode* fieldPatternNode = NULL;
      UtlBoolean fieldPatternPresent = false;
      UtlString fieldName;
      const char* fieldValuePtr = NULL;

      //check for fieldName parameter , if present check if it matches
      if(fieldMatchElement->Attribute(XML_ATT_FIELDNAME))
      {
         UtlString fieldNameXml = fieldMatchElement->Attribute(XML_ATT_FIELDNAME);
         // If field name is specified, 
         // check if it exists in the message
         if(!fieldNameXml.isNull())
         {
             fieldValuePtr = 
                 request.getHeaderValue(0, fieldNameXml.data());

             if(fieldValuePtr)
             {
                fieldNameMatches = true;
             }
         }
      }
      else
      {
         noFieldPatternRequired = true;
      }

      if(fieldNameMatches && !noFieldPatternRequired)
      {
         //get the user text value from it
         for( fieldPatternNode = fieldMatchElement->FirstChild( XML_TAG_FIELDPATTERN);
            fieldPatternNode;
            fieldPatternNode = fieldPatternNode->NextSibling(XML_TAG_FIELDPATTERN ) )
         {
            fieldPatternPresent = true;
            TiXmlElement* fieldPatternElement = fieldPatternNode->ToElement();

            TiXmlNode* fieldPatternText = fieldPatternElement->FirstChild();
            if(fieldPatternText)
            {
               try
               {
                  RegEx fieldPatternXml(fieldPatternText->Value(),PCRE_ANCHORED);

                  if (fieldPatternXml.Search(fieldValuePtr))
                  {
                     fieldPatternFound = true;
                  }
               }
               catch(const char * ErrorMsg)
               {
                  OsSysLog::add(FAC_SIP, PRI_ERR,
                                "Illegal regular expression <fieldPattern>%s</fieldPattern>"
                                " in forwardingrules.xml: %s",
                                fieldPatternText->Value() ,ErrorMsg
                                );
               }
            }
         }
      }
      if( (fieldNameMatches && (fieldPatternFound || !fieldPatternPresent))
          || noFieldPatternRequired )
      {
            //get the routeTo field
         getRouteFound = getRouteTo(routeToString,
            fieldMatchNode);
      }

   }
   return getRouteFound;
}
OsStatus ForwardRules::getRouteTo(UtlString& RouteToString,
                                  TiXmlNode* nodeWithRouteToChild)
{
   
   OsStatus currentStatus = OS_FAILED;
   nodeWithRouteToChild->ToElement();
   TiXmlNode* routeToNode = NULL;
   TiXmlNode* routeToText = NULL;

   //get the user text value from it
   routeToNode = nodeWithRouteToChild->FirstChild( XML_TAG_ROUTETO);
   if(routeToNode)
   {
      currentStatus = OS_SUCCESS;
      if(routeToNode && routeToNode->Type() != TiXmlNode::ELEMENT)
      {
         return currentStatus;
      }

      TiXmlElement* routeToElement = routeToNode->ToElement();
      routeToText = routeToElement->FirstChild();
      if(routeToText && routeToText->Type() == TiXmlNode::TEXT)
      {
         TiXmlText* routeTo = routeToText->ToText();
         if (routeTo)
         {
            currentStatus = OS_SUCCESS;
            RouteToString.append(routeTo->Value());
         }
      }         
   }
   return currentStatus;
}


syntax highlighted by Code2HTML, v. 0.9.1