text: /*
   * sippres.cxx
   *
   * SIP Presence classes for Opal
   *
   * Open Phone Abstraction Library (OPAL)
   *
   * Copyright (c) 2009 Post Increment
   *
   * The contents of this file are subject to the Mozilla Public License
   * Version 1.0 (the "License"); you may not use this file except in
   * compliance with the License. You may obtain a copy of the License at
   * http://www.mozilla.org/MPL/
   *
   * Software distributed under the License is distributed on an "AS IS"
   * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   * the License for the specific language governing rights and limitations
   * under the License.
   *
   * The Original Code is Open Phone Abstraction Library.
   *
   * The Initial Developer of the Original Code is Post Increment
   *
   * Contributor(s): ______________________________________.
   *
   * $Revision: 22858 $
   * $Author: csoutheren $
   * $Date: 2009-06-12 22:50:19 +1000 (Fri, 12 Jun 2009) $
   */
  
  /*
   * This code implements all or part of the following RFCs
   *
   * RFC 3856 "A Presence Event Package for the Session Initiation Protocol (SIP)"
   * RFC 3857 "A Watcher Information Event Template-Package for the Session Initiation Protocol (SIP)"
   * RFC 3858 "An Extensible Markup Language (XML) Based Format for Watcher Information"
   * RFC 3863 "Presence Information Data Format (PIDF)"
   * RFC 4825 "The Extensible Markup Language (XML) Configuration Access Protocol (XCAP)"
   * RFC 4827 "An Extensible Markup Language (XML) Configuration Access Protocol (XCAP) Usage for Manipulating Presence Document Contents"
   * RFC 5025 "Presence Authorization Rules"
   *
   * This code does not implement the following RFCs
   *
   * RFC 5263 "Session Initiation Protocol (SIP) Extension for Partial Notification of Presence Information"
   *
   */
  
  
  #include <ptlib.h>
  #include <opal_config.h>
  
  #if OPAL_SIP_PRESENCE
  
  #include <sip/sippres.h>
  #include <ptclib/pdns.h>
  #include <ptclib/pxml.h>
  #include <ptclib/random.h>
  
  
  PFACTORY_CREATE(PFactory<OpalPresentity>, SIP_Presentity, "sip", false);
  PFACTORY_SYNONYM(PFactory<OpalPresentity>, SIP_Presentity, pres, "pres");
  
  const PCaselessString & SIP_Presentity::PIDFEntityKey()    { static const PConstCaselessString s("PIDF-Entity");    return s; }
  const PCaselessString & SIP_Presentity::SubProtocolKey()   { static const PConstCaselessString s("Sub-Protocol");   return s; }
  const PCaselessString & SIP_Presentity::PresenceAgentKey() { static const PConstCaselessString s("Presence Agent"); return s; }
  const PCaselessString & SIP_Presentity::TransportKey()     { static const PConstCaselessString s("Transport");      return s; }
  const PCaselessString & SIP_Presentity::XcapRootKey()      { static const PConstCaselessString s("XCAP Root");      return s; }
  const PCaselessString & SIP_Presentity::XcapAuthIdKey()    { static const PConstCaselessString s("XCAP Auth ID");   return s; }
  const PCaselessString & SIP_Presentity::XcapPasswordKey()  { static const PConstCaselessString s("XCAP Password");  return s; }
  const PCaselessString & SIP_Presentity::XcapAuthAuidKey()  { static const PConstCaselessString s("XCAP AUID");      return s; }
  const PCaselessString & SIP_Presentity::XcapAuthFileKey()  { static const PConstCaselessString s("XCAP AuthFile");  return s; }
  const PCaselessString & SIP_Presentity::XcapBuddyListKey() { static const PConstCaselessString s("XCAP BuddyList"); return s; }
  
  static struct SIPAttributeInfo {
    const char * m_name;
    const char * m_type;
  } const AttributeInfo[] = {
    /*  0 */ { SIP_Presentity::PIDFEntityKey(),    "URL" },
    /*  1 */ { SIP_Presentity::SubProtocolKey(),   "Enum\nPeerToPeer,Agent,XCAP,OMA\nAgent" },
    /*  2 */ { SIP_Presentity::PresenceAgentKey(), "String" },
    /*  3 */ { SIP_Presentity::TransportKey(),     "Enum\nUDP,TCP,TLS\nTCP" },
    /*  4 */ { OpalPresentity::AuthNameKey(),      "String" },
    /*  5 */ { OpalPresentity::AuthPasswordKey(),  "Password" },
    /*  6 */ { SIP_Presentity::XcapRootKey(),      "URL" },
    /*  7 */ { SIP_Presentity::XcapAuthIdKey(),    "String" },
    /*  8 */ { SIP_Presentity::XcapPasswordKey(),  "Password" },
    /*  9 */ { SIP_Presentity::XcapAuthAuidKey(),  "String" },
    /* 10 */ { SIP_Presentity::XcapAuthFileKey(),  "String" },
    /* 11 */ { SIP_Presentity::XcapBuddyListKey(), "String" },
    /* 12 */ { OpalPresentity::TimeToLiveKey(),    "Integer\n1,999999999\n300" }
  };
  
  OPAL_PRESENTITY_COMMAND(OpalSetLocalPresenceCommand,     SIP_Presentity, Internal_SendLocalPresence);
  OPAL_PRESENTITY_COMMAND(OpalSubscribeToPresenceCommand,  SIP_Presentity, Internal_SubscribeToPresence);
  OPAL_PRESENTITY_COMMAND(SIPWatcherInfoCommand,           SIP_Presentity, Internal_SubscribeToWatcherInfo);
  OPAL_PRESENTITY_COMMAND(OpalAuthorisationRequestCommand, SIP_Presentity, Internal_AuthorisationRequest);
  
  static const char * const AuthNames[OpalPresentity::NumAuthorisations] = { "allow", "block", "polite-block", "confirm", "remove" };
  
  static atomic<unsigned> NextRuleId(PRandom::Number());
  
  
  //////////////////////////////////////////////////////////////////////////////////////
  
  SIP_Presentity::SIP_Presentity()
    : m_endpoint(NULL)
    , m_subProtocol(e_OMA)
    , m_watcherInfoVersion(-1)
  {
    m_attributes.Set(SubProtocolKey, "Agent");
  }
  
  
  SIP_Presentity::SIP_Presentity(const SIP_Presentity & other)
    : OpalPresentityWithCommandThread(other)
    , PValidatedNotifierTarget(other)
    , m_endpoint(NULL)
    , m_watcherInfoVersion(-1)
  {
  }
  
  
  SIP_Presentity::~SIP_Presentity()
  {
    Close();
  }
  
  
  PStringArray SIP_Presentity::GetAttributeNames() const
  {
    PStringArray names;
    for (PINDEX i = 0; i < PARRAYSIZE(AttributeInfo); ++i)
      names += AttributeInfo[i].m_name;
    return names;
  }
  
  
  PStringArray SIP_Presentity::GetAttributeTypes() const
  {
    PStringArray types;
    for (PINDEX i = 0; i < PARRAYSIZE(AttributeInfo); ++i)
      types += AttributeInfo[i].m_type;
    return types;
  }
  
  
  bool SIP_Presentity::Open()
  {
    if (!OpalPresentityWithCommandThread::Open())
      return false;
  
    // find the endpoint
    m_endpoint = m_manager->FindEndPointAs<SIPEndPoint>("sip");
    if (m_endpoint == NULL) {
      PTRACE(1, "SIPPres\tCannot open SIP_Presentity without sip endpoint");
      return false;
    }
  
    PCaselessString subProto = m_attributes.Get(SubProtocolKey);
    if (subProto == "PeerToPeer")
      m_subProtocol = e_PeerToPeer;
    else if (subProto == "Agent")
      m_subProtocol = e_WithAgent;
    else if (subProto == "XCAP")
      m_subProtocol = e_XCAP;
    else if (subProto == "OMA")
      m_subProtocol = e_OMA;
    else {
      PTRACE(1, "SIPPres\tUnknown sub-protocol \"" << subProto << '"');
      return false;
    }
  
    if (m_subProtocol == e_PeerToPeer) {
      m_presenceAgentURL = PString::Empty();
      PTRACE(3, "SIPPres\tUsing peer to peer mode for " << m_aor);
    }
    else {
      // find presence server for Presentity as per RFC 3861
      // if not found, look for default presence server setting
      // if none, use hostname portion of domain name
      PCaselessString presenceAgent = m_attributes.Get(PresenceAgentKey);
      if (presenceAgent.IsEmpty()) {
        presenceAgent = m_aor.GetHostPort();
  
  #if OPAL_PTLIB_DNS_RESOLVER
        if (m_aor.GetScheme() == "pres") {
          PStringList hosts;
          bool found = PDNS::LookupSRV(m_aor.GetHostName(), "_pres._sip", hosts) && !hosts.IsEmpty();
          PTRACE(2, "SIPPres\tSRV lookup for '_pres._sip." << m_aor.GetHostName()
                 << "' " << (found ? "succeeded" : "failed"));
          if (found)
            presenceAgent = hosts.front();
        }
  #endif // OPAL_PTLIB_DNS_RESOLVER
      }
  
      m_presenceAgentURL.Parse(presenceAgent);
      m_presenceAgentURL.SetParamVar("transport", m_attributes.Get(TransportKey, "tcp").ToLower());
  
      m_defaultRootURL.Parse(m_attributes.Get(XcapRootKey), "http");
      if (m_defaultRootURL.IsEmpty()) {
        presenceAgent.Splice("http://", 0, presenceAgent.NumCompare("sip:") == EqualTo ? 4 : 0);
        presenceAgent += '/';
        m_defaultRootURL.Parse(presenceAgent);
        m_defaultRootURL.SetPort(0);
      }
  
      PTRACE(3, "SIPPres\tPresentity " << m_aor << " using agent " << m_presenceAgentURL << " and XCAP root " << m_defaultRootURL);
    }
  
    m_watcherSubscriptionAOR.MakeEmpty();
    m_watcherInfoVersion = -1;
  
    StartThread();
  
    // subscribe to presence watcher infoformation
    SendCommand(CreateCommand<SIPWatcherInfoCommand>());
  
    return true;
  }
  
  
  bool SIP_Presentity::Close()
  {
    if (!OpalPresentityWithCommandThread::Close())
      return false;
  
    StopThread();
  
    if (!m_publishedTupleId.IsEmpty()) {
      SIP_Presentity_OpalSetLocalPresenceCommand cmd;
      cmd.m_state = OpalPresenceInfo::NoPresence;
      Internal_SendLocalPresence(cmd);
    }
  
    m_notificationMutex.Wait();
  
    PString watcherSubscriptionAOR = m_watcherSubscriptionAOR;
    m_watcherSubscriptionAOR.MakeEmpty();
  
    StringMap presenceIdByAor = m_presenceIdByAor;
    m_watcherAorById.clear();
    m_presenceIdByAor.clear();
    m_presenceAorById.clear();
    m_authorisationIdByAor.clear();
  
    m_notificationMutex.Signal();
  
    if (!watcherSubscriptionAOR.IsEmpty()) {
      PTRACE(3, "SIPPres\t'" << m_aor << "' sending final unsubscribe for own presence watcher");
      m_endpoint->Unsubscribe(SIPSubscribe::Presence | SIPSubscribe::Watcher, watcherSubscriptionAOR, true);
    }
  
    for (StringMap::iterator subs = presenceIdByAor.begin(); subs != presenceIdByAor.end(); ++subs) {
      PTRACE(3, "SIPPres\t'" << m_aor << "' sending final unsubscribe to " << subs->first);
      m_endpoint->Unsubscribe(SIPSubscribe::Presence, subs->second, true);
    }
  
    PTRACE(4, "SIPPres\t'" << m_aor << "' awaiting unsubscriptions to complete.");
    while (m_endpoint->IsSubscribed(SIPSubscribe::Presence | SIPSubscribe::Watcher, watcherSubscriptionAOR, true))
      PThread::Sleep(100);
    for (StringMap::iterator subs = presenceIdByAor.begin(); subs != presenceIdByAor.end(); ++subs) {
      while (m_endpoint->IsSubscribed(SIPSubscribe::Presence, subs->second, true))
        PThread::Sleep(100);
    }
  
    m_endpoint = NULL;
    PTRACE(3, "SIPPres\t'" << m_aor << "' closed.");
    return true;
  }
  
  
  void SIP_Presentity::Internal_SubscribeToPresence(const OpalSubscribeToPresenceCommand & cmd)
  {
    if (cmd.m_subscribe) {
      if (m_presenceIdByAor.find(cmd.m_presentity) != m_presenceIdByAor.end()) {
        PTRACE(3, "SIPPres\t'" << m_aor << "' already subscribed to presence of '" << cmd.m_presentity << '\'');
        return;
      }
  
      PTRACE(3, "SIPPres\t'" << m_aor << "' subscribing to presence of '" << cmd.m_presentity << '\'');
  
      // subscribe to the presence event on the presence server
      SIPSubscribe::Params param(SIPSubscribe::Presence);
  
      param.m_localAddress    = m_aor.AsString();
      param.m_addressOfRecord = cmd.m_presentity;
      param.m_remoteAddress   = m_presenceAgentURL;
      param.m_authID          = m_attributes.Get(OpalPresentity::AuthNameKey, m_aor.GetUserName());
      param.m_password        = m_attributes.Get(OpalPresentity::AuthPasswordKey);
      param.m_expire          = GetExpiryTime();
      param.m_contentType     = "application/pidf+xml";
      param.m_eventList       = true;
  
      param.m_onSubcribeStatus = PCREATE_NOTIFIER(OnPresenceSubscriptionStatus);
      param.m_onNotify         = PCREATE_NOTIFIER(OnPresenceNotify);
  
      PString id;
      if (m_endpoint->Subscribe(param, id, false)) {
        m_presenceIdByAor[cmd.m_presentity] = id;
        m_presenceAorById[id] = cmd.m_presentity;
      }
    }
    else {
      StringMap::iterator id = m_presenceIdByAor.find(cmd.m_presentity);
      if (id == m_presenceIdByAor.end()) {
        PTRACE(3, "SIPPres\t'" << m_aor << "' already unsubscribed to presence of '" << cmd.m_presentity << '\'');
        return;
      }
  
      PTRACE(3, "SIPPres\t'" << m_aor << "' unsubscribing to presence of '" << cmd.m_presentity << '\'');
      m_endpoint->Unsubscribe(SIPSubscribe::Presence, id->second);
    }
  }
  
  
  void SIP_Presentity::OnPresenceSubscriptionStatus(SIPSubscribeHandler &, const SIPSubscribe::SubscriptionStatus & status)
  {
    if (status.m_reason/100 == 1)
      return;
  
    m_notificationMutex.Wait();
  
    PString id = status.m_handler->GetCallID();
    StringMap::iterator aor = m_presenceAorById.find(id);
    if (aor == m_presenceAorById.end()) {
      PTRACE(2, "SIPPres\t'" << m_aor << "' " << "recieved subscription status for unknown ID " << id);
    }
    else {
      SIPPresenceInfo info(status.m_reason, status.m_wasSubscribing);
      SetPIDFEntity(info.m_entity);
      info.m_target = aor->second;
  
      OnPresenceChange(info);
  
      if (!status.m_wasSubscribing) {
        m_presenceIdByAor.erase(aor->second);
        m_presenceAorById.erase(aor);
      }
    }
  
    m_notificationMutex.Signal();
  }
  
  
  void SIP_Presentity::OnPresenceNotify(SIPSubscribeHandler &, SIPSubscribe::NotifyCallbackInfo & notifyInfo)
  {
    list<SIPPresenceInfo> infoList;
    if (!SIPPresenceInfo::ParseNotify(notifyInfo, infoList))
      return;
  
    // send 200 OK now, and flag caller NOT to send the response
    notifyInfo.SendResponse();
  
    m_notificationMutex.Wait();
    for (list<SIPPresenceInfo>::iterator it = infoList.begin(); it != infoList.end(); ++it) {
      SetPIDFEntity(it->m_target);
      PTRACE(3, "SIPPres\t'" << m_aor << "' request for presence of '" << it->m_entity << "' is " << it->m_state);
      OnPresenceChange(*it);
    }
    m_notificationMutex.Signal();
  }
  
  
  unsigned SIP_Presentity::GetExpiryTime() const
  {
    int ttl = m_attributes.Get(OpalPresentity::TimeToLiveKey, "300").AsInteger();
    return ttl > 0 ? ttl : 300;
  }
  
  
  void SIP_Presentity::Internal_SubscribeToWatcherInfo(const SIPWatcherInfoCommand & cmd)
  {
    if (m_subProtocol == e_PeerToPeer) {
      PTRACE(3, "SIPPres\tRequires agent to do watcher, aor=" << m_aor);
      return;
    }
  
    if (cmd.m_unsubscribe) {
      if (m_watcherSubscriptionAOR.IsEmpty()) {
        PTRACE(3, "SIPPres\tAlredy unsubscribed presence watcher for " << m_aor);
        return;
      }
  
      PTRACE(3, "SIPPres\t'" << m_aor << "' sending unsubscribe for own presence watcher");
      m_endpoint->Unsubscribe(SIPSubscribe::Presence | SIPSubscribe::Watcher, m_watcherSubscriptionAOR);
      return;
    }
  
    PString aorStr = m_aor.AsString();
    PTRACE(3, "SIPPres\t'" << aorStr << "' sending subscribe for own presence.watcherinfo");
  
    // subscribe to the presence.winfo event on the presence server
    SIPSubscribe::Params param(SIPSubscribe::Presence | SIPSubscribe::Watcher);
    param.m_contentType      = "application/watcherinfo+xml";
    param.m_localAddress     = aorStr;
    param.m_addressOfRecord  = aorStr;
    param.m_remoteAddress    = m_presenceAgentURL;
    param.m_authID           = m_attributes.Get(OpalPresentity::AuthNameKey, m_aor.GetUserName());
    param.m_password         = m_attributes.Get(OpalPresentity::AuthPasswordKey);
    param.m_expire           = GetExpiryTime();
    param.m_onSubcribeStatus = PCREATE_NOTIFIER(OnWatcherInfoSubscriptionStatus);
    param.m_onNotify         = PCREATE_NOTIFIER(OnWatcherInfoNotify);
  
    m_endpoint->Subscribe(param, m_watcherSubscriptionAOR);
  }
  
  
  void SIP_Presentity::SetPIDFEntity(PURL & entity)
  {
    if (entity.Parse(m_attributes.Get(SIP_Presentity::PIDFEntityKey), "pres")) {
      PTRACE(4, "SIPPres\tPIDF entity set via attribute to " << entity);
      return;
    }
  
    if (m_aor.GetScheme() == "pres") {
      entity = m_aor;
      PTRACE(4, "SIPPres\tPIDF entity set via AOR to " << entity);
    }
  
    if (entity.Parse(m_aor.GetUserName() + '@' + m_aor.GetHostName(), "pres")) {
      PTRACE(4, "SIPPres\tPIDF entity derived from AOR as " << entity);
      return;
    }
  
    entity = m_aor;
    PTRACE(4, "SIPPres\tPIDF entity set via failsafe AOR of " << entity);
  }
  
  
  void SIP_Presentity::OnWatcherInfoSubscriptionStatus(SIPSubscribeHandler &, const SIPSubscribe::SubscriptionStatus & status)
  {
    if (status.m_reason/100 == 1)
      return;
  
    SIPPresenceInfo info(status.m_reason, status.m_wasSubscribing);
    SetPIDFEntity(info.m_entity);
    info.m_target = info.m_entity;
  
    m_notificationMutex.Wait();
  
    OnPresenceChange(info);
  
    if (!status.m_wasSubscribing)
      m_watcherSubscriptionAOR.MakeEmpty();
  
    m_notificationMutex.Signal();
  }
  
  
  void SIP_Presentity::OnWatcherInfoNotify(SIPSubscribeHandler &, SIPSubscribe::NotifyCallbackInfo & notifyInfo)
  {
    static PXML::ValidationInfo const WatcherValidation[] = {
      { PXML::RequiredNonEmptyAttribute,  "id"  },
      { PXML::RequiredAttributeWithValue, "status",  { "pending\nactive\nwaiting\nterminated" } },
      { PXML::RequiredAttributeWithValue, "event",   { "subscribe\napproved\ndeactivated\nprobation\nrejected\ntimeout\ngiveup" } },
      { PXML::EndOfValidationList }
    };
  
    static PXML::ValidationInfo const WatcherListValidation[] = {
      { PXML::RequiredNonEmptyAttribute,  "resource" },
      { PXML::RequiredAttributeWithValue, "package", { "presence" } },
  
      { PXML::Subtree,                    "watcher", { WatcherValidation } , 0 },
      { PXML::EndOfValidationList }
    };
  
    static PXML::ValidationInfo const WatcherInfoValidation[] = {
      { PXML::SetDefaultNamespace,        "urn:ietf:params:xml:ns:watcherinfo" },
      { PXML::ElementName,                "watcherinfo", },
      { PXML::RequiredNonEmptyAttribute,  "version"},
      { PXML::RequiredAttributeWithValue, "state",   { "full\npartial" } },
  
      { PXML::Subtree,                    "watcher-list", { WatcherListValidation }, 0 },
      { PXML::EndOfValidationList }
    };
  
    PXML xml;
    if (!notifyInfo.LoadAndValidate(xml, WatcherInfoValidation))
      return;
  
    // send 200 OK now, and flag caller NOT to send the response
    notifyInfo.SendResponse();
  
    PXMLElement * rootElement = xml.GetRootElement();
  
    int version = rootElement->GetAttribute("version").AsUnsigned();
  
    PWaitAndSignal mutex(m_notificationMutex);
  
    // check version number
    if (m_watcherInfoVersion >= 0 && version <= m_watcherInfoVersion) {
      PTRACE(3, "SIPPres\t'" << m_aor << "' received repeated NOTIFY for own presence.watcherinfo, already processed");
      return;
    }
  
    // if this is a full list of watcher info, we can empty out our pending lists
    if (rootElement->GetAttribute("state") *= "full") {
      PTRACE(3, "SIPPres\t'" << m_aor << "' received full watcher list for own presence.watcherinfo");
      m_watcherAorById.clear();
    }
    else if (m_watcherInfoVersion < 0) {
      PTRACE(2, "SIPPres\t'" << m_aor << "' received partial watcher list for own presence.watcherinfo, but awaiting full list");
      return;
    }
    else if (version != m_watcherInfoVersion+1) {
      PTRACE(2, "SIPPres\t'" << m_aor << "' received partial watcher list for own presence.watcherinfo, but have missing sequence number, resubscribing");
      m_watcherInfoVersion = -1;
      SendCommand(CreateCommand<SIPWatcherInfoCommand>());
      return;
    }
    else {
      PTRACE(3, "SIPPres\t'" << m_aor << "' received partial watcher list for own presence.watcherinfo");
    }
  
    m_watcherInfoVersion = version;
  
    // go through list of watcher information
    PINDEX watcherListIndex = 0;
    PXMLElement * watcherList;
    while ((watcherList = rootElement->GetElement("watcher-list", watcherListIndex++)) != NULL) {
      PINDEX watcherIndex = 0;
      PXMLElement * watcher;
      while ((watcher = watcherList->GetElement("watcher", watcherIndex++)) != NULL)
        OnReceivedWatcherStatus(watcher);
    }
  }
  
  
  void SIP_Presentity::OnReceivedWatcherStatus(PXMLElement * watcher)
  {
    PString id     = watcher->GetAttribute("id");
    PString status = watcher->GetAttribute("status");
  
    AuthorisationRequest authreq;
    authreq.m_presentity = watcher->GetData().Trim();
  
    StringMap::iterator existingAOR = m_watcherAorById.find(id);
  
    // save pending subscription status from this user
    if (status == "pending") {
      if (existingAOR != m_watcherAorById.end()) {
        PTRACE(3, "SIPPres\t'" << m_aor << "' received followup to request from '" << authreq.m_presentity << "' for access to presence information");
      } 
      else {
        m_watcherAorById[id] = authreq.m_presentity;
        PTRACE(3, "SIPPres\t'" << authreq.m_presentity << "' has requested access to presence information of '" << m_aor << '\'');
        OnAuthorisationRequest(authreq);
      }
    }
    else {
      PTRACE(3, "SIPPres\t'" << m_aor << "' has received event '" << watcher->GetAttribute("event")
             << "', status '" << status << "', for '" << authreq.m_presentity << '\'');
    }
  }
  
  
  static atomic<uint32_t> g_idNumber;
  
  void SIP_Presentity::Internal_SendLocalPresence(const OpalSetLocalPresenceCommand & cmd)
  {
    PTRACE(3, "SIPPres\t'" << m_aor << "' sending own presence " << cmd.m_state << "/" << cmd.m_note);
  
    SIPPresenceInfo sipPresence(cmd);
  
    sipPresence.m_personId = PString(++g_idNumber);
    SetPIDFEntity(sipPresence.m_entity);
    sipPresence.m_contact =  m_aor;  // As required by OMA-TS-Presence_SIMPLE-V2_0-20090917-C
    sipPresence.m_presenceAgent = m_presenceAgentURL.AsString();
  
    if (!sipPresence.m_service.IsEmpty())
      m_publishedTupleId = sipPresence.m_service;
    else {
      if (m_publishedTupleId.IsEmpty())
        m_publishedTupleId = PGloballyUniqueID().AsString();
      sipPresence.m_service = m_publishedTupleId;
    }
  
    if (m_subProtocol != e_PeerToPeer)
      m_endpoint->PublishPresence(sipPresence, m_open ? GetExpiryTime() : 0);
    else
      m_endpoint->Notify(m_aor, SIPSubscribe::Presence, sipPresence.AsXML());
  }
  
  
  bool SIP_Presentity::ChangeAuthNode(XCAPClient & xcap, const OpalAuthorisationRequestCommand & cmd)
  {
    PString ruleId = m_authorisationIdByAor[cmd.m_presentity];
  
    XCAPClient::NodeSelector node;
    node.SetNamespace("urn:ietf:params:xml:ns:pres-rules", "pr");
    node.SetNamespace("urn:ietf:params:xml:ns:common-policy", "cr");
    node.AddElement("cr:ruleset");
    node.AddElement("cr:rule", "id", ruleId);
    xcap.SetNode(node);
  
    if (cmd.m_authorisation == AuthorisationRemove) {
      if (xcap.DeleteXml()) {
        PTRACE(3, "SIPPres\tRule id=" << ruleId << " removed for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
      }
      else {
        PTRACE(3, "SIPPres\tCould not remove rule id=" << ruleId
               << " for '" << cmd.m_presentity << "' at '" << m_aor << "\'\n"
               << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
      }
      return true;
    }
  
    PXML xml;
    if (!xcap.GetXml(xml)) {
      PTRACE(3, "SIPPres\tCould not locate existing rule id=" << ruleId
             << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
      return false;
    }
  
    PXMLElement * root, * actions, * subHandling = NULL;
    if ((root = xml.GetRootElement()) == NULL ||
        (actions = root->GetElement("cr:actions")) == NULL ||
        (subHandling = actions->GetElement("pr:sub-handling")) == NULL) {
      PTRACE(2, "SIPPres\tInvalid XML in existing rule id=" << ruleId
             << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
      return false;
    }
  
    if (subHandling->GetData() *= AuthNames[cmd.m_authorisation]) {
      PTRACE(3, "SIPPres\tRule id=" << ruleId << " already set to "
             << AuthNames[cmd.m_authorisation] << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
      return true;
    }
  
    // Adjust the authorisation
    subHandling->SetData(AuthNames[cmd.m_authorisation]);
  
    if (xcap.PutXml(xml)) {
      PTRACE(3, "SIPPres\tRule id=" << ruleId << " changed to"
             << AuthNames[cmd.m_authorisation] << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
      return true;
    }
  
    PTRACE(3, "SIPPres\tCould not change existing rule id=" << ruleId
           << " for '" << cmd.m_presentity << "' at '" << m_aor << "\'\n"
           << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
    return false;
  }
  
  
  void SIP_Presentity::Internal_AuthorisationRequest(const OpalAuthorisationRequestCommand & cmd)
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to do authorisation, aor=" << m_aor);
      return;
    }
  
    XCAPClient xcap;
    xcap.SetRoot(m_defaultRootURL);
    xcap.SetApplicationUniqueID(m_attributes.Get(XcapAuthAuidKey,
                  m_subProtocol == e_OMA ? "org.openmobilealliance.pres-rules" : "pres-rules")); // As per RFC5025/9.1
    xcap.SetContentType("application/auth-policy+xml");   // As per RFC5025/9.4
    xcap.SetUserIdentifier(m_aor.AsString());             // As per RFC5025/9.7
    xcap.SetAuthenticationInfo(m_attributes.Get(XcapAuthIdKey, m_attributes.Get(AuthNameKey, xcap.GetUserIdentifier())),
                               m_attributes.Get(XcapPasswordKey, m_attributes.Get(AuthPasswordKey)));
    xcap.SetFilename(m_attributes.Get(XcapAuthFileKey, m_subProtocol == e_OMA ? "pres-rules" : "index"));
  
    // See if we can use teh quick method
    if (m_authorisationIdByAor.find(cmd.m_presentity) != m_authorisationIdByAor.end()) {
      if (ChangeAuthNode(xcap, cmd))
        return;
    }
  
    PXML xml;
    PXMLElement * element;
  
    // Could not find rule element quickly, get the whole document
    if (!xcap.GetXml(xml)) {
      if (xcap.GetLastResponseCode() != PHTTP::NotFound) {
        PTRACE(2, "SIPPres\tUnexpected error getting rule file for '"
               << cmd.m_presentity << "' at '" << m_aor << "\'\n"
               << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
        return;
      }
  
      // No whole document, create a fresh one
      xml.SetOptions(PXML::NoOptions);
      element = xml.SetRootElement("cr:ruleset");
      element->SetAttribute("xmlns:pr", "urn:ietf:params:xml:ns:pres-rules");
      element->SetAttribute("xmlns:cr", "urn:ietf:params:xml:ns:common-policy");
  
      element = element->AddElement("rule", "id", "presence_allow_own");
  
      element->AddElement("cr:conditions")->AddElement("cr:identity")->AddElement("cr:one")->SetAttribute("id", m_aor.AsString());
      element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationPermitted]);
      element = element->AddElement("cr:transformations");
      element->AddElement("pr:provide-services")->AddElement("pr:all-services");
      element->AddElement("pr:provide-persons")->AddElement("pr:all-persons");
      element->AddElement("pr:provide-all-attributes");
      if (m_subProtocol == e_OMA) {
        PXMLElement * root = xml.GetRootElement();
        root->SetAttribute("xmlns:ocp", "urn:oma:xml:xdm:common-policy");
  
        element = root->AddElement("cr:rule", "id", "wp_prs_block_anonymous");
        element->AddElement("cr:conditions")->AddElement("ocp:anonymous-request");
        element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationDenied]);
  
        element = root->AddElement("cr:rule", "id", "wp_prs_unlisted");
        element->AddElement("cr:conditions")->AddElement("ocp:other-identity");
        element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationConfirming]);
  
        // Use an xcap object to calculate horrible URL
        XCAPClient xcap;
        InitBuddyXcap(xcap, PString::Empty(), "oma_grantedcontacts");
  
        element = root->AddElement("cr:rule", "id", "wp_prs_grantedcontacts");
        element->AddElement("cr:conditions")->AddElement("ocp:external-list")->AddElement("ocp:entry", "anc", xcap.BuildURL().AsString());
        element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationPermitted]);
        element = element->AddElement("cr:transformations");
        element->AddElement("pr:provide-services")->AddElement("pr:all-services");
        element->AddElement("pr:provide-persons")->AddElement("pr:all-persons");
        element->AddElement("pr:provide-all-attributes");
  
        InitBuddyXcap(xcap, PString::Empty(), "oma_blockedcontacts");
  
        element = root->AddElement("cr:rule", "id", "wp_prs_blockedcontacts");
        element->AddElement("cr:conditions")->AddElement("ocp:external-list")->AddElement("ocp:entry", "anc", xcap.BuildURL().AsString());
        element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationDenied]);
      }
  
      if (!xcap.PutXml(xml)) {
        PTRACE(2, "SIPPres\tCould not add new rule file for '" << m_aor << "\'\n"
               << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
        return;
      }
  
      PTRACE(3, "SIPPres\tNew rule file created for '" << m_aor << '\'');
    }
  
    // Extract all the rules from it
    m_authorisationIdByAor.clear();
  
    bool existingRuleForWatcher = false;
  
    PINDEX ruleIndex = 0;
    while ((element = xml.GetElement("cr:rule", ruleIndex++)) != NULL) {
      PString ruleId = element->GetAttribute("id");
      if ((element = element->GetElement("cr:conditions")) != NULL &&
          (element = element->GetElement("cr:identity")) != NULL &&
          (element = element->GetElement("cr:one")) != NULL) {
        PString watcher = element->GetAttribute("id");
  
        m_authorisationIdByAor[watcher] = ruleId;
        PTRACE(4, "SIPPres\tGetting rule id=" << ruleId << " for '" << watcher << "' at '" << m_aor << '\'');
  
        if (watcher == cmd.m_presentity)
          existingRuleForWatcher = true;
      }
    }
  
    if (existingRuleForWatcher) {
      ChangeAuthNode(xcap, cmd);
      return;
    }
  
    // Create new rule with id as per http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName
    PString newRuleId(PString::Printf, "wp_prs%s_one_%lu",
                      cmd.m_authorisation == AuthorisationPermitted ? "_allow" : "",
                      ++NextRuleId);
  
    xml.SetOptions(PXML::FragmentOnly);
    element = xml.SetRootElement("cr:rule");
    element->SetAttribute("xmlns:pr", "urn:ietf:params:xml:ns:pres-rules");
    element->SetAttribute("xmlns:cr", "urn:ietf:params:xml:ns:common-policy");
    element->SetAttribute("id", newRuleId);
  
    element->AddElement("cr:conditions")->AddElement("cr:identity")->AddElement("cr:one")->SetAttribute("id", cmd.m_presentity);
    element->AddElement("cr:actions")->AddElement("pr:sub-handling")->SetData(AuthNames[cmd.m_authorisation]);
    element = element->AddElement("cr:transformations");
    element->AddElement("pr:provide-services")->AddElement("pr:all-services");
    element->AddElement("pr:provide-persons")->AddElement("pr:all-persons");
    element->AddElement("pr:provide-all-attributes");
  
    XCAPClient::NodeSelector node;
    node.SetNamespace("urn:ietf:params:xml:ns:pres-rules", "pr");
    node.SetNamespace("urn:ietf:params:xml:ns:common-policy", "cr");
    node.AddElement("cr:ruleset");
    node.AddElement("cr:rule", "id", newRuleId);
    xcap.SetNode(node);
  
    if (xcap.PutXml(xml)) {
      PTRACE(3, "SIPPres\tNew rule set to" << AuthNames[cmd.m_authorisation] << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
      m_authorisationIdByAor[cmd.m_presentity] = newRuleId;
      return;
    }
  
    PTRACE(2, "SIPPres\tCould not add new rule for '" << cmd.m_presentity << "' at '" << m_aor << "\'\n"
           << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
  }
  
  
  void SIP_Presentity::InitBuddyXcap(XCAPClient & xcap, const PString & entryName, const PString & listName)
  {
    xcap.SetRoot(m_defaultRootURL);
    xcap.SetApplicationUniqueID("resource-lists");            // As per RFC5025/9.1
    xcap.SetContentType("application/resource-lists+xml");    // As per RFC5025/9.4
    xcap.SetUserIdentifier(m_aor.AsString());                 // As per RFC5025/9.7
    xcap.SetAuthenticationInfo(m_attributes.Get(XcapAuthIdKey, m_attributes.Get(OpalPresentity::AuthNameKey, xcap.GetUserIdentifier())),
                               m_attributes.Get(XcapPasswordKey, m_attributes.Get(AuthPasswordKey)));
    xcap.SetFilename("index");
  
    XCAPClient::NodeSelector node;
    node.SetNamespace("urn:ietf:params:xml:ns:resource-lists");
    node.AddElement("resource-lists");
    node.AddElement("list", "name", listName.IsEmpty() ? m_attributes.Get(XcapBuddyListKey,
                                          m_subProtocol == e_OMA ? "oma_buddylist" : "buddylist") : listName);
  
    if (!entryName.IsEmpty())
      node.AddElement("entry", "uri", entryName);
  
    xcap.SetNode(node);
  }
  
  
  static void BuddyInfoToXML(const OpalPresentity::BuddyInfo & buddy, PXMLElement * parent)
  {
    PXMLElement * element = parent->GetName() == "entry" ? parent : parent->AddElement("entry");
    element->SetAttribute("uri", buddy.m_presentity);
  
    if (!buddy.m_displayName.IsEmpty())
      element->AddElement("display-name", buddy.m_displayName);
  }
  
  
  static bool XMLToBuddyInfo(const PXMLElement * element, OpalPresentity::BuddyInfo & buddy)
  {
    if (element == NULL || element->GetName() != "entry")
      return false;
  
    buddy.m_presentity = element->GetAttribute("uri");
  
    PXMLElement * itemElement;
    if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:display-name")) != NULL)
      buddy.m_displayName = itemElement->GetData();
  
  #if P_VCARD
    if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:card")) != NULL) {
      PURL url;
      if (url.Parse(itemElement->GetData())) {
        PString str;
        if (url.LoadResource(str))
          buddy.m_vCard.Parse(str);
      }
    }
  #endif // P_VCARD
  
    if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:icon")) != NULL)
      buddy.m_icon = itemElement->GetData();
  
    if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:map")) != NULL)
      buddy.m_map = itemElement->GetData();
  
    if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:sound")) != NULL)
      buddy.m_sound = itemElement->GetData();
  
    if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:homepage")) != NULL)
      buddy.m_homePage = itemElement->GetData();
  
    buddy.m_contentType = "application/resource-lists+xml";
    buddy.m_rawXML = element->AsString();
    return true;
  }
  
  
  static bool RecursiveGetBuddyList(OpalPresentity::BuddyList & buddies, XCAPClient & xcap, const PURL & url)
  {
    if (url.IsEmpty())
      return false;
  
    PXML xml;
    if (!xcap.GetXml(url, xml))
      return false;
  
    PXMLElement * element;
    PINDEX idx = 0;
    while ((element = xml.GetElement("entry", idx++)) != NULL) {
      OpalPresentity::BuddyInfo buddy;
      if (XMLToBuddyInfo(element, buddy))
        buddies.push_back(buddy);
    }
  
    idx = 0;
    while ((element = xml.GetElement("external", idx++)) != NULL)
      RecursiveGetBuddyList(buddies, xcap, element->GetAttribute("anchor"));
  
    idx = 0;
    while ((element = xml.GetElement("entry-ref", idx++)) != NULL) {
      PURL url(xcap.GetRoot());
      url.SetPathStr(url.GetPathStr() + element->GetAttribute("ref"));
      RecursiveGetBuddyList(buddies, xcap, url);
    }
  
    return true;
  }
  
  
  OpalPresentity::BuddyStatus SIP_Presentity::GetBuddyListEx(BuddyList & buddies)
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
      return BuddyStatus_ListFeatureNotImplemented;
    }
  
    XCAPClient xcap;
    InitBuddyXcap(xcap);
    if (RecursiveGetBuddyList(buddies, xcap, xcap.BuildURL()) ||
        !buddies.empty() ||
        xcap.GetLastResponseCode() == PHTTP::NotFound)
      return BuddyStatus_OK;
  
    return BuddyStatus_GenericFailure;
  }
  
  
  OpalPresentity::BuddyStatus SIP_Presentity::SetBuddyListEx(const BuddyList & buddies)
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
      return BuddyStatus_ListFeatureNotImplemented;
    }
  
    PXML xml(PXML::FragmentOnly);
  
    PString buddyListKey = m_subProtocol == e_OMA ? "oma_buddylist" : "buddylist";
    PXMLElement * root = xml.SetRootElement("list");
    root->SetAttribute("xmlns", "urn:ietf:params:xml:ns:resource-lists");
    root->SetAttribute("name", m_attributes.Get(XcapBuddyListKey, buddyListKey));
  
    for (BuddyList::const_iterator it = buddies.begin(); it != buddies.end(); ++it)
      BuddyInfoToXML(*it, root);
  
    XCAPClient xcap;
    InitBuddyXcap(xcap);
  
    if (xcap.PutXml(xml))
      return BuddyStatus_OK;
  
    if (xcap.GetLastResponseCode() == PHTTP::Conflict && xcap.GetLastResponseInfo().Find("Parent") != P_MAX_INDEX) {
      // Got "Parent does not exist" error, so need to add whole file
      xml.SetOptions(PXML::NoOptions);
      root = xml.SetRootElement("resource-lists");
      root->SetAttribute("xmlns", "urn:ietf:params:xml:ns:resource-lists");
  
      PXMLElement * listElement = root->AddElement("list", "name", m_attributes.Get(XcapBuddyListKey, buddyListKey));
  
      for (BuddyList::const_iterator it = buddies.begin(); it != buddies.end(); ++it)
        BuddyInfoToXML(*it, listElement);
  
      xcap.ClearNode();
      if (xcap.PutXml(xml))
        return BuddyStatus_OK;
    }
  
    PTRACE(2, "SIPPres\tError setting buddy list of '" << m_aor << "\'\n"
           << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
  
    return BuddyStatus_GenericFailure;
  }
  
  
  OpalPresentity::BuddyStatus SIP_Presentity::DeleteBuddyListEx()
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
      return BuddyStatus_ListFeatureNotImplemented;
    }
  
    XCAPClient xcap;
    InitBuddyXcap(xcap);
  
    if (xcap.DeleteXml())
      return BuddyStatus_OK;
  
    PTRACE(2, "SIPPres\tError deleting buddy list of '" << m_aor << "\'\n"
           << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
    return BuddyStatus_GenericFailure;
  }
  
  
  OpalPresentity::BuddyStatus SIP_Presentity::GetBuddyEx(BuddyInfo & buddy)
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
      return BuddyStatus_ListFeatureNotImplemented;
    }
  
    XCAPClient xcap;
    InitBuddyXcap(xcap, buddy.m_presentity);
  
    PXML xml;
    if (xcap.GetXml(xml) && XMLToBuddyInfo(xml.GetRootElement(), buddy))
      return BuddyStatus_OK;
    else
      return BuddyStatus_GenericFailure;
  }
  
  
  OpalPresentity::BuddyStatus SIP_Presentity::SetBuddyEx(const BuddyInfo & buddy)
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
      return BuddyStatus_ListFeatureNotImplemented;
    }
  
    if (buddy.m_presentity.IsEmpty())
      return BuddyStatus_GenericFailure;
  
    XCAPClient xcap;
    InitBuddyXcap(xcap, buddy.m_presentity);
  
    PXML xml(PXML::FragmentOnly);
    BuddyInfoToXML(buddy, xml.SetRootElement("entry"));
  
    if (xcap.PutXml(xml))
      return BuddyStatus_OK;
  
    if (xcap.GetLastResponseCode() != PHTTP::Conflict || xcap.GetLastResponseInfo().Find("Parent") == P_MAX_INDEX) {
      PTRACE(2, "SIPPres\tError setting buddy '" << buddy.m_presentity << "' of '" << m_aor << "\'\n"
             << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
      return BuddyStatus_GenericFailure;
    }
  
    // Got "Parent does not exist" error, so need to add whole list
    BuddyList buddies;
    buddies.push_back(buddy);
    return SetBuddyListEx(buddies);
  }
  
  
  OpalPresentity::BuddyStatus SIP_Presentity::DeleteBuddyEx(const PURL & presentity)
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
      return BuddyStatus_ListFeatureNotImplemented;
    }
  
    XCAPClient xcap;
    InitBuddyXcap(xcap, presentity);
  
    if (xcap.DeleteXml())
      return BuddyStatus_OK;
  
    PTRACE(2, "SIPPres\tError deleting buddy '" << presentity << "' of '" << m_aor << "\'\n"
           << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
    return BuddyStatus_GenericFailure;
  }
  
  
  OpalPresentity::BuddyStatus SIP_Presentity::SubscribeBuddyListEx(PINDEX & numSuccessful, bool subscribe)
  {
    if (m_subProtocol < e_XCAP) {
      PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
      return BuddyStatus_ListFeatureNotImplemented;
    }
  
    PXML xml;
    XCAPClient xcap;
    xcap.SetRoot(m_defaultRootURL);
    xcap.SetApplicationUniqueID("rls-services");
    xcap.SetContentType("application/rls-services+xml");
    xcap.SetUserIdentifier(m_aor.AsString());
    xcap.SetAuthenticationInfo(m_attributes.Get(XcapAuthIdKey, m_attributes.Get(AuthNameKey, xcap.GetUserIdentifier())),
                               m_attributes.Get(XcapPasswordKey, m_attributes.Get(AuthPasswordKey)));
    xcap.SetFilename("index");
  
    PString serviceURI = xcap.GetUserIdentifier() + ";pres-list=oma_buddylist";
  
    if (!xcap.GetXml(xml)) {
      if (xcap.GetLastResponseCode() != PHTTP::NotFound) {
        PTRACE(2, "SIPPres\tUnexpected error getting rls-services file for at '" << m_aor << "\'\n"
               << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
        return OpalPresentity::SubscribeBuddyListEx(numSuccessful, subscribe); // Do individual subscribes
      }
  
      // No file at all, add the root element.
      xml.SetRootElement("rls-services")->SetAttribute("xmlns", "urn:ietf:params:xml:ns:rls-services");
    }
    else {
      // Have file, see if have specific service
      PXMLElement * element = xml.GetElement("service", "uri", serviceURI);
      if (element != NULL) {
        PTRACE(4, "SIPPres\tConfirmed rls-services entry for '" << serviceURI << "\' is\n" << xml);
        numSuccessful = P_MAX_INDEX;
        return SubscribeToPresence(serviceURI, subscribe) ? BuddyStatus_OK : BuddyStatus_GenericFailure;
      }
  
      // Nope, so add it
    }
  
    // Added service to existing XML or newly rooted one
    PXMLElement * element = xml.GetRootElement()->AddElement("service");
    element->SetAttribute("uri", serviceURI);
  
    // Calculate the horrible URL using a temporary XCAP client
    XCAPClient xcapTemp;
    InitBuddyXcap(xcapTemp);
    element->AddElement("resource-list")->SetData(xcapTemp.BuildURL().AsString());
  
    element->AddElement("packages")->AddElement("package")->SetData("presence");
  
    if (xcap.PutXml(xml)) {
      numSuccessful = P_MAX_INDEX;
      return SubscribeToPresence(serviceURI, subscribe) ? BuddyStatus_OK : BuddyStatus_GenericFailure;
    }
  
    PTRACE(2, "SIPPres\tCould not add new rls-services entry for '" << m_aor << "\'\n"
           << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
  
    return OpalPresentity::SubscribeBuddyListEx(numSuccessful, subscribe); // Do individual subscribes
  }
  
  
  //////////////////////////////////////////////////////////////
  
  XCAPClient::XCAPClient()
    : m_global(false)
    , m_filename("index")
  {
  }
  
  
  PURL XCAPClient::BuildURL()
  {
    PURL uri(m_root);                              // XCAP root
  
    uri.AppendPath(m_auid);                        // Application Unique ID
  
    uri.AppendPath(m_global ? "global" : "users"); // RFC4825/6.2, The path segment after the AUID MUST either be "global" or "users".
  
    if (!m_global)
      uri.AppendPath(m_xui);                       // XCAP User Identifier
  
    if (!m_filename.IsEmpty()) {
      uri.AppendPath(m_filename);                        // Final resource name
      m_node.AddToURL(uri);
    }
  
    return uri;
  }
  
  
  static bool HasNode(const PURL & url)
  {
    const PStringArray & path = url.GetPath();
    for (PINDEX i = 0; i < path.GetSize(); ++i) {
      if (path[i] == "~~")
        return true;
    }
  
    return false;
  }
  
  
  bool XCAPClient::GetXml(const PURL & url, PXML & xml)
  {
    bool hasNode = HasNode(url);
  
    PString body;
    if (!GetTextDocument(url, body, hasNode ? "application/xcap-el+xml" : m_contentType)) {
      PTRACE(3, "SIPPres\tError getting buddy list at '" << url << "\'\n"
             << GetLastResponseCode() << ' '  << GetLastResponseInfo());
      return false;
    }
  
    if (xml.Load(body, hasNode ? PXML::FragmentOnly : PXML::NoOptions))
      return true;
  
    PTRACE(2, "XCAP\tError parsing XML for '" << url << "\'\n"
              "Line " << xml.GetErrorLine() << ", Column " << xml.GetErrorColumn() << ": " << xml.GetErrorString());
    return false;
  }
  
  
  bool XCAPClient::PutXml(const PURL & url, const PXML & xml)
  {
    PStringStream strm;
    strm << xml;
    return PutTextDocument(url, strm, HasNode(url) ? "application/xcap-el+xml" : m_contentType);
  }
  
  
  PString XCAPClient::ElementSelector::AsString() const
  {
    PStringStream strm;
  
    strm << m_name;
  
    if (!m_position.IsEmpty())
      strm << '[' << m_position << ']';
  
    if (!m_attribute.IsEmpty())
      strm << "[@" << m_attribute << "=\"" << m_value << "\"]";
  
    return strm;
  }
  
  
  void XCAPClient::NodeSelector::AddToURL(PURL & uri) const
  {
    if (empty())
      return;
  
    uri.AppendPath("~~");                      // Node selector
  
    for (const_iterator it = begin(); it != end(); ++it)
      uri.AppendPath(it->AsString());
  
    if (m_namespaces.empty())
      return;
  
    PStringStream query;
    for (std::map<PString, PString>::const_iterator it = m_namespaces.begin(); it != m_namespaces.end(); ++it) {
      query << "xmlns(";
      if (!it->first.IsEmpty())
        query << it->first << '=';
      query << it->second << ')';
    }
  
    // Non-standard format query parameter, we fake that by having one
    // dictionary element at key value empty string
    uri.SetQueryVar(PString::Empty(), query);
  }
  
  
  //////////////////////////////////////////////////////////////
  
  
  
  #endif // OPAL_SIP_PRESENCE
  