1414 lines
38 KiB
C++
1414 lines
38 KiB
C++
#if defined(HAVE_CONFIG_H)
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <set>
|
|
|
|
#include "resip/stack/Embedded.hxx"
|
|
#include "resip/stack/Helper.hxx"
|
|
#include "resip/stack/NameAddr.hxx"
|
|
#include "resip/stack/SipMessage.hxx"
|
|
#include "resip/stack/Symbols.hxx"
|
|
#include "resip/stack/UnknownParameter.hxx"
|
|
#include "resip/stack/Uri.hxx"
|
|
#include "rutil/DataStream.hxx"
|
|
#include "rutil/DnsUtil.hxx"
|
|
#include "rutil/Logger.hxx"
|
|
#include "rutil/ParseBuffer.hxx"
|
|
//#include "rutil/WinLeakCheck.hxx" // not compatible with placement new used below
|
|
|
|
using namespace resip;
|
|
|
|
#define RESIPROCATE_SUBSYSTEM Subsystem::SIP
|
|
#define HANDLE_CHARACTER_ESCAPING //undef for old behaviour
|
|
|
|
static bool initAllTables()
|
|
{
|
|
Uri::getUserEncodingTable();
|
|
Uri::getPasswordEncodingTable();
|
|
Uri::getLocalNumberTable();
|
|
Uri::getGlobalNumberTable();
|
|
return true;
|
|
}
|
|
|
|
const bool Uri::tablesMightBeInitialized(initAllTables());
|
|
|
|
Uri::Uri(PoolBase* pool)
|
|
: ParserCategory(pool),
|
|
mScheme(Data::Share, Symbols::DefaultSipScheme),
|
|
mPort(0),
|
|
mHostCanonicalized(false)
|
|
{
|
|
}
|
|
|
|
Uri::Uri(const HeaderFieldValue& hfv, Headers::Type type, PoolBase* pool) :
|
|
ParserCategory(hfv, type, pool),
|
|
mPort(0),
|
|
mHostCanonicalized(false)
|
|
{}
|
|
|
|
|
|
static const Data parseContext("Uri constructor");
|
|
Uri::Uri(const Data& data)
|
|
: ParserCategory(),
|
|
mScheme(Symbols::DefaultSipScheme),
|
|
mPort(0),
|
|
mHostCanonicalized(false)
|
|
{
|
|
HeaderFieldValue hfv(data.data(), data.size());
|
|
// must copy because parse creates overlays
|
|
Uri tmp(hfv, Headers::UNKNOWN);
|
|
tmp.checkParsed();
|
|
*this = tmp;
|
|
}
|
|
|
|
Uri::Uri(const Uri& rhs,
|
|
PoolBase* pool)
|
|
: ParserCategory(rhs, pool),
|
|
mScheme(rhs.mScheme),
|
|
mHost(rhs.mHost),
|
|
mUser(rhs.mUser),
|
|
mUserParameters(rhs.mUserParameters),
|
|
mPort(rhs.mPort),
|
|
mPassword(rhs.mPassword),
|
|
mHostCanonicalized(rhs.mHostCanonicalized),
|
|
mEmbeddedHeadersText(rhs.mEmbeddedHeadersText.get() ? new Data(*rhs.mEmbeddedHeadersText) : 0),
|
|
mEmbeddedHeaders(rhs.mEmbeddedHeaders.get() ? new SipMessage(*rhs.mEmbeddedHeaders) : 0)
|
|
{}
|
|
|
|
|
|
Uri::~Uri()
|
|
{}
|
|
|
|
// RFC 3261 19.1.6
|
|
#if 0 // deprecated
|
|
Uri
|
|
Uri::fromTel(const Uri& tel, const Data& host)
|
|
{
|
|
assert(tel.scheme() == Symbols::Tel);
|
|
|
|
Uri u;
|
|
u.scheme() = Symbols::Sip;
|
|
u.user() = tel.user();
|
|
u.host() = host;
|
|
u.param(p_user) = Symbols::Phone;
|
|
|
|
// need to sort the user parameters
|
|
if (!tel.userParameters().empty())
|
|
{
|
|
DebugLog(<< "Uri::fromTel: " << tel.userParameters());
|
|
Data isub;
|
|
Data postd;
|
|
|
|
int totalSize = 0;
|
|
std::set<Data> userParameters;
|
|
|
|
ParseBuffer pb(tel.userParameters().data(), tel.userParameters().size());
|
|
while (true)
|
|
{
|
|
const char* anchor = pb.position();
|
|
pb.skipToChar(Symbols::SEMI_COLON[0]);
|
|
Data param = pb.data(anchor);
|
|
// !dlb! not supposed to lowercase extension parameters
|
|
param.lowercase();
|
|
totalSize += param.size() + 1;
|
|
|
|
if (param.prefix(Symbols::Isub))
|
|
{
|
|
isub = param;
|
|
}
|
|
else if (param.prefix(Symbols::Postd))
|
|
{
|
|
postd = param;
|
|
}
|
|
else
|
|
{
|
|
userParameters.insert(param);
|
|
}
|
|
if (pb.eof())
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
pb.skipChar();
|
|
}
|
|
}
|
|
|
|
u.userParameters().reserve(totalSize);
|
|
if (!isub.empty())
|
|
{
|
|
u.userParameters() = isub;
|
|
}
|
|
if (!postd.empty())
|
|
{
|
|
if (!u.userParameters().empty())
|
|
{
|
|
u.userParameters() += Symbols::SEMI_COLON[0];
|
|
}
|
|
u.userParameters() += postd;
|
|
}
|
|
|
|
for(std::set<Data>::const_iterator i = userParameters.begin();
|
|
i != userParameters.end(); ++i)
|
|
{
|
|
DebugLog(<< "Adding param: " << *i);
|
|
if (!u.userParameters().empty())
|
|
{
|
|
u.userParameters() += Symbols::SEMI_COLON[0];
|
|
}
|
|
u.userParameters() += *i;
|
|
}
|
|
}
|
|
|
|
return u;
|
|
}
|
|
#endif // deprecated
|
|
|
|
Uri
|
|
Uri::fromTel(const Uri& tel, const Uri& hostUri)
|
|
{
|
|
assert(tel.scheme() == Symbols::Tel);
|
|
|
|
Uri u(hostUri);
|
|
u.scheme() = Symbols::Sip;
|
|
u.user() = tel.user();
|
|
u.param(p_user) = Symbols::Phone;
|
|
|
|
// need to sort the user parameters
|
|
if (!tel.userParameters().empty())
|
|
{
|
|
DebugLog(<< "Uri::fromTel: " << tel.userParameters());
|
|
Data isub;
|
|
Data postd;
|
|
|
|
int totalSize = 0;
|
|
std::set<Data> userParameters;
|
|
|
|
ParseBuffer pb(tel.userParameters().data(), tel.userParameters().size());
|
|
while (true)
|
|
{
|
|
const char* anchor = pb.position();
|
|
pb.skipToChar(Symbols::SEMI_COLON[0]);
|
|
Data param = pb.data(anchor);
|
|
// !dlb! not supposed to lowercase extension parameters
|
|
param.lowercase();
|
|
totalSize += (int)param.size() + 1;
|
|
|
|
if (param.prefix(Symbols::Isub))
|
|
{
|
|
isub = param;
|
|
}
|
|
else if (param.prefix(Symbols::Postd))
|
|
{
|
|
postd = param;
|
|
}
|
|
else
|
|
{
|
|
userParameters.insert(param);
|
|
}
|
|
if (pb.eof())
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
pb.skipChar();
|
|
}
|
|
}
|
|
|
|
u.userParameters().reserve(totalSize);
|
|
if (!isub.empty())
|
|
{
|
|
u.userParameters() = isub;
|
|
}
|
|
if (!postd.empty())
|
|
{
|
|
if (!u.userParameters().empty())
|
|
{
|
|
u.userParameters() += Symbols::SEMI_COLON[0];
|
|
}
|
|
u.userParameters() += postd;
|
|
}
|
|
|
|
for(std::set<Data>::const_iterator i = userParameters.begin();
|
|
i != userParameters.end(); ++i)
|
|
{
|
|
DebugLog(<< "Adding param: " << *i);
|
|
if (!u.userParameters().empty())
|
|
{
|
|
u.userParameters() += Symbols::SEMI_COLON[0];
|
|
}
|
|
u.userParameters() += *i;
|
|
}
|
|
}
|
|
|
|
return u;
|
|
}
|
|
|
|
bool
|
|
Uri::isEnumSearchable() const
|
|
{
|
|
checkParsed();
|
|
int digits = 0;
|
|
|
|
if(mUser.size() < 4)
|
|
{
|
|
StackLog(<< "user part of Uri empty or too short for E.164");
|
|
return false;
|
|
}
|
|
|
|
// E.164 numbers must begin with a + and have at least
|
|
// 3 digits
|
|
if(mUser[0] != '+')
|
|
{
|
|
StackLog(<< "user part of Uri does not begin with `+' or too short");
|
|
return false;
|
|
}
|
|
|
|
// count the digits (skip the leading `+')
|
|
for(const char* i=user().begin() + 1; i!= user().end(); i++)
|
|
{
|
|
if(isdigit(*i))
|
|
digits++;
|
|
else
|
|
if(*i != '-')
|
|
{
|
|
StackLog(<< "user part of Uri contains non-digit: " << *i);
|
|
return false; // Only digits and '-' permitted
|
|
}
|
|
}
|
|
if(digits > 15)
|
|
{
|
|
// E.164 only permits 15 digits in a phone number
|
|
StackLog(<< "user part of Uri contains more than 15 digits");
|
|
return false;
|
|
}
|
|
|
|
DebugLog(<< "is in E.164 format for ENUM: " << mUser);
|
|
return true;
|
|
}
|
|
|
|
std::vector<Data>
|
|
Uri::getEnumLookups(const std::vector<Data>& suffixes) const
|
|
{
|
|
std::vector<Data> results;
|
|
Data prefix;
|
|
if (isEnumSearchable())
|
|
{
|
|
// skip the leading +
|
|
for (const char* i=user().end()-1 ; i!= user().begin(); --i)
|
|
{
|
|
if (isdigit(*i))
|
|
{
|
|
prefix += *i;
|
|
prefix += Symbols::DOT;
|
|
}
|
|
}
|
|
StackLog(<< "E.164 number reversed for ENUM query: " << prefix);
|
|
for (std::vector<Data>::const_iterator j=suffixes.begin(); j != suffixes.end(); ++j)
|
|
{
|
|
results.push_back(prefix + *j);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
|
|
bool
|
|
Uri::hasEmbedded() const
|
|
{
|
|
checkParsed();
|
|
return (mEmbeddedHeadersText.get() && !mEmbeddedHeadersText->empty()) || mEmbeddedHeaders.get() != 0;
|
|
}
|
|
|
|
void
|
|
Uri::removeEmbedded()
|
|
{
|
|
checkParsed();
|
|
mEmbeddedHeaders.reset();
|
|
mEmbeddedHeadersText.reset();
|
|
}
|
|
|
|
|
|
|
|
Uri&
|
|
Uri::operator=(const Uri& rhs)
|
|
{
|
|
if (this != &rhs)
|
|
{
|
|
ParserCategory::operator=(rhs);
|
|
mScheme = rhs.mScheme;
|
|
mHost = rhs.mHost;
|
|
mHostCanonicalized=rhs.mHostCanonicalized;
|
|
mUser = rhs.mUser;
|
|
mUserParameters = rhs.mUserParameters;
|
|
mPort = rhs.mPort;
|
|
mPassword = rhs.mPassword;
|
|
if (rhs.mEmbeddedHeaders.get() != 0)
|
|
{
|
|
mEmbeddedHeaders.reset(new SipMessage(*rhs.mEmbeddedHeaders));
|
|
}
|
|
else if(rhs.mEmbeddedHeadersText.get() != 0)
|
|
{
|
|
if(!mEmbeddedHeadersText.get())
|
|
{
|
|
mEmbeddedHeadersText.reset(new Data(*rhs.mEmbeddedHeadersText));
|
|
}
|
|
else
|
|
{
|
|
// !bwc! Data::operator= is smart enough to handle this safely.
|
|
*mEmbeddedHeadersText = *rhs.mEmbeddedHeadersText;
|
|
}
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
@class OrderUnknownParameters
|
|
@brief used as a comparator for sorting purposes
|
|
*/
|
|
class OrderUnknownParameters
|
|
{
|
|
public:
|
|
/**
|
|
@brief constructor ; never called explicitly
|
|
*/
|
|
OrderUnknownParameters() { notUsed=false; };
|
|
|
|
/**
|
|
@brief empty destructor
|
|
*/
|
|
~OrderUnknownParameters() {};
|
|
|
|
/**
|
|
@brief used as a comparator for sorting purposes
|
|
This does a straight Data comparison for name and returns true/false
|
|
@param p1 pointer to parameter 1
|
|
@param p2 pointer to parameter 2
|
|
@return true if p1->getName() is less than p2->getName()
|
|
else return false
|
|
*/
|
|
bool operator()(const Parameter* p1, const Parameter* p2) const
|
|
{
|
|
return dynamic_cast<const UnknownParameter*>(p1)->getName() < dynamic_cast<const UnknownParameter*>(p2)->getName();
|
|
}
|
|
|
|
private:
|
|
bool notUsed;
|
|
};
|
|
|
|
bool
|
|
Uri::operator==(const Uri& other) const
|
|
{
|
|
checkParsed();
|
|
other.checkParsed();
|
|
|
|
// compare hosts
|
|
if (DnsUtil::isIpV6Address(mHost) &&
|
|
DnsUtil::isIpV6Address(other.mHost))
|
|
{
|
|
|
|
// compare canonicalized IPV6 addresses
|
|
|
|
// update canonicalized if host changed
|
|
if (!mHostCanonicalized)
|
|
{
|
|
mHost = DnsUtil::canonicalizeIpV6Address(mHost);
|
|
mHostCanonicalized=true;
|
|
}
|
|
|
|
// update canonicalized if host changed
|
|
if (!other.mHostCanonicalized)
|
|
{
|
|
other.mHost = DnsUtil::canonicalizeIpV6Address(other.mHost);
|
|
other.mHostCanonicalized=true;
|
|
}
|
|
|
|
if (mHost != other.mHost)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isEqualNoCase(mHost, other.mHost))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (isEqualNoCase(mScheme, other.mScheme) &&
|
|
((isEqualNoCase(mScheme, Symbols::Sip) || isEqualNoCase(mScheme, Symbols::Sips)) ? mUser == other.mUser : isEqualNoCase(mUser, other.mUser)) &&
|
|
isEqualNoCase(mUserParameters,other.mUserParameters) &&
|
|
mPassword == other.mPassword &&
|
|
mPort == other.mPort)
|
|
{
|
|
for (ParameterList::const_iterator it = mParameters.begin(); it != mParameters.end(); ++it)
|
|
{
|
|
Parameter* otherParam = other.getParameterByEnum((*it)->getType());
|
|
|
|
switch ((*it)->getType())
|
|
{
|
|
case ParameterTypes::user:
|
|
{
|
|
if (!(otherParam &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(otherParam)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case ParameterTypes::ttl:
|
|
{
|
|
if (!(otherParam &&
|
|
(dynamic_cast<UInt32Parameter*>(*it)->value() ==
|
|
dynamic_cast<UInt32Parameter*>(otherParam)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case ParameterTypes::method:
|
|
{
|
|
// this should possibly be case sensitive, but is allowed to be
|
|
// case insensitive for robustness.
|
|
|
|
if (otherParam)
|
|
{
|
|
DataParameter* dp1 = dynamic_cast<DataParameter*>(*it);
|
|
DataParameter* dp2 = dynamic_cast<DataParameter*>(otherParam);
|
|
(void)dp1;
|
|
(void)dp2;
|
|
// ?bwc? It looks like we're just assuming the dynamic_cast
|
|
// will succeed everywhere else; why are we bothering to
|
|
// assert()?
|
|
assert(dp1);
|
|
assert(dp2);
|
|
}
|
|
if (!(otherParam &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(otherParam)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case ParameterTypes::maddr:
|
|
{
|
|
if (!(otherParam &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(otherParam)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case ParameterTypes::transport:
|
|
{
|
|
if (!(otherParam &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(otherParam)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
// the parameters that follow don't affect comparison if only present
|
|
// in one of the URI's
|
|
case ParameterTypes::lr:
|
|
break;
|
|
default:
|
|
break;
|
|
//treat as unknown parameter?
|
|
}
|
|
}
|
|
|
|
// now check the other way, sigh
|
|
for (ParameterList::const_iterator it = other.mParameters.begin(); it != other.mParameters.end(); ++it)
|
|
{
|
|
Parameter* param = getParameterByEnum((*it)->getType());
|
|
switch ((*it)->getType())
|
|
{
|
|
case ParameterTypes::user:
|
|
{
|
|
if (!(param &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(param)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case ParameterTypes::ttl:
|
|
{
|
|
if (!(param &&
|
|
(dynamic_cast<UInt32Parameter*>(*it)->value() ==
|
|
dynamic_cast<UInt32Parameter*>(param)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case ParameterTypes::method:
|
|
{
|
|
// this should possilby be case sensitive, but is allowed to be
|
|
// case insensitive for robustness.
|
|
if (!(param &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(param)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case ParameterTypes::maddr:
|
|
{
|
|
if (!(param &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(param)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case ParameterTypes::transport:
|
|
{
|
|
if (!(param &&
|
|
isEqualNoCase(dynamic_cast<DataParameter*>(*it)->value(),
|
|
dynamic_cast<DataParameter*>(param)->value())))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
// the parameters that follow don't affect comparison if only present
|
|
// in one of the URI's
|
|
case ParameterTypes::lr:
|
|
break;
|
|
default:
|
|
break;
|
|
//treat as unknown parameter?
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OrderUnknownParameters orderUnknown;
|
|
|
|
#if defined(__SUNPRO_CC) || defined(WIN32) || defined(__sun__)
|
|
// The Solaris Forte STL implementation does not support the
|
|
// notion of a list.sort() function taking a BinaryPredicate.
|
|
// The hacky workaround is to load the Parameter pointers into
|
|
// an STL set which does support an ordering function.
|
|
|
|
typedef std::set<Parameter*, OrderUnknownParameters> ParameterSet;
|
|
ParameterSet unA, unB;
|
|
|
|
for (ParameterList::const_iterator i = mUnknownParameters.begin();
|
|
i != mUnknownParameters.end(); ++i)
|
|
{
|
|
unA.insert(*i);
|
|
}
|
|
for (ParameterList::const_iterator i = other.mUnknownParameters.begin();
|
|
i != other.mUnknownParameters.end(); ++i)
|
|
{
|
|
unB.insert(*i);
|
|
}
|
|
|
|
ParameterSet::iterator a = unA.begin();
|
|
ParameterSet::iterator b = unB.begin();
|
|
#else
|
|
// .dlb. more efficient to copy to vector for sorting?
|
|
// Uri comparison is expensive; consider caching? ugh
|
|
ParameterList unA = mUnknownParameters;
|
|
ParameterList unB = other.mUnknownParameters;
|
|
|
|
sort(unA.begin(), unA.end(), orderUnknown);
|
|
sort(unB.begin(), unB.end(), orderUnknown);
|
|
|
|
ParameterList::iterator a = unA.begin();
|
|
ParameterList::iterator b = unB.begin();
|
|
#endif
|
|
|
|
while(a != unA.end() && b != unB.end())
|
|
{
|
|
if (orderUnknown(*a, *b))
|
|
{
|
|
++a;
|
|
}
|
|
else if (orderUnknown(*b, *a))
|
|
{
|
|
++b;
|
|
}
|
|
else
|
|
{
|
|
if (!isEqualNoCase(dynamic_cast<UnknownParameter*>(*a)->value(),
|
|
dynamic_cast<UnknownParameter*>(*b)->value()))
|
|
{
|
|
return false;
|
|
}
|
|
++a;
|
|
++b;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Uri::operator!=(const Uri& other) const
|
|
{
|
|
return !(*this == other);
|
|
}
|
|
|
|
bool
|
|
Uri::operator<(const Uri& other) const
|
|
{
|
|
other.checkParsed();
|
|
checkParsed();
|
|
if (mUser < other.mUser)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (mUser > other.mUser)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (mUserParameters < other.mUserParameters)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (mUserParameters > other.mUserParameters)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// !bwc! Canonicalize before we compare! Jeez...
|
|
if (!mHostCanonicalized)
|
|
{
|
|
if(DnsUtil::isIpV6Address(mHost))
|
|
{
|
|
mHost = DnsUtil::canonicalizeIpV6Address(mHost);
|
|
}
|
|
else
|
|
{
|
|
mHost.lowercase();
|
|
}
|
|
mHostCanonicalized=true;
|
|
}
|
|
|
|
if (!other.mHostCanonicalized)
|
|
{
|
|
if(DnsUtil::isIpV6Address(other.mHost))
|
|
{
|
|
other.mHost = DnsUtil::canonicalizeIpV6Address(other.mHost);
|
|
}
|
|
else
|
|
{
|
|
other.mHost.lowercase();
|
|
}
|
|
other.mHostCanonicalized=true;
|
|
}
|
|
|
|
if (mHost < other.mHost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (mHost > other.mHost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return mPort < other.mPort;
|
|
}
|
|
|
|
bool
|
|
Uri::aorEqual(const resip::Uri& rhs) const
|
|
{
|
|
checkParsed();
|
|
rhs.checkParsed();
|
|
|
|
if (!mHostCanonicalized)
|
|
{
|
|
if(DnsUtil::isIpV6Address(mHost))
|
|
{
|
|
mHost = DnsUtil::canonicalizeIpV6Address(mHost);
|
|
}
|
|
else
|
|
{
|
|
mHost.lowercase();
|
|
}
|
|
mHostCanonicalized=true;
|
|
}
|
|
|
|
if (!rhs.mHostCanonicalized)
|
|
{
|
|
if(DnsUtil::isIpV6Address(rhs.mHost))
|
|
{
|
|
rhs.mHost = DnsUtil::canonicalizeIpV6Address(rhs.mHost);
|
|
}
|
|
else
|
|
{
|
|
rhs.mHost.lowercase();
|
|
}
|
|
rhs.mHostCanonicalized=true;
|
|
}
|
|
|
|
return (mUser==rhs.mUser) && (mHost==rhs.mHost) && (mPort==rhs.mPort) &&
|
|
(isEqualNoCase(mScheme,rhs.mScheme));
|
|
}
|
|
|
|
void
|
|
Uri::getAorInternal(bool dropScheme, bool addPort, Data& aor) const
|
|
{
|
|
checkParsed();
|
|
// canonicalize host
|
|
|
|
addPort = addPort && mPort!=0;
|
|
|
|
bool hostIsIpV6Address = false;
|
|
if(!mHostCanonicalized)
|
|
{
|
|
if (DnsUtil::isIpV6Address(mHost))
|
|
{
|
|
mHost = DnsUtil::canonicalizeIpV6Address(mHost);
|
|
hostIsIpV6Address = true;
|
|
}
|
|
else
|
|
{
|
|
mHost.lowercase();
|
|
}
|
|
}
|
|
|
|
// !bwc! Maybe reintroduce caching of aor. (Would use a bool instead of the
|
|
// mOldX cruft)
|
|
// @:10000
|
|
aor.clear();
|
|
aor.reserve((dropScheme ? 0 : mScheme.size()+1)
|
|
+ mUser.size() + mHost.size() + 7);
|
|
if(!dropScheme)
|
|
{
|
|
aor += mScheme;
|
|
aor += ':';
|
|
}
|
|
|
|
if (!mUser.empty())
|
|
{
|
|
#ifdef HANDLE_CHARACTER_ESCAPING
|
|
{
|
|
oDataStream str(aor);
|
|
mUser.escapeToStream(str, getUserEncodingTable());
|
|
}
|
|
#else
|
|
aor += mUser;
|
|
#endif
|
|
if(!mHost.empty())
|
|
{
|
|
aor += Symbols::AT_SIGN;
|
|
}
|
|
}
|
|
|
|
if(hostIsIpV6Address && addPort)
|
|
{
|
|
aor += Symbols::LS_BRACKET;
|
|
aor += mHost;
|
|
aor += Symbols::RS_BRACKET;
|
|
}
|
|
else
|
|
{
|
|
aor += mHost;
|
|
}
|
|
|
|
if(addPort)
|
|
{
|
|
aor += Symbols::COLON;
|
|
aor += Data(mPort);
|
|
}
|
|
}
|
|
|
|
Data
|
|
Uri::getAOR(bool addPort) const
|
|
{
|
|
Data result;
|
|
getAorInternal(false, addPort, result);
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
Uri::userIsTelephoneSubscriber() const
|
|
{
|
|
try
|
|
{
|
|
ParseBuffer pb(mUser);
|
|
pb.assertNotEof();
|
|
const char* anchor=pb.position();
|
|
bool local=false;
|
|
if(*pb.position()=='+')
|
|
{
|
|
// Might be a global phone number
|
|
pb.skipChar();
|
|
pb.skipChars(getGlobalNumberTable());
|
|
}
|
|
else
|
|
{
|
|
pb.skipChars(getLocalNumberTable());
|
|
local=true;
|
|
}
|
|
|
|
Data dialString(pb.data(anchor));
|
|
if(dialString.empty())
|
|
{
|
|
pb.fail(__FILE__, __LINE__, "Dial string is empty.");
|
|
}
|
|
|
|
// ?bwc? More dial-string checking? For instance, +/ (or simply /) is not
|
|
// a valid dial-string according to the BNF; the string must contain at
|
|
// least one actual digit (or in the local number case, one hex digit or
|
|
// '*' or '#'. Interestingly, this means that stuff like ///*/// is
|
|
// valid)
|
|
|
|
// Dial string looks ok so far; now look for params (there must be a
|
|
// phone-context param if this is a local number, otherwise there might
|
|
// or might not be one)
|
|
if(local || !pb.eof())
|
|
{
|
|
// The only thing that can be here is a ';'. If it does, we're going
|
|
// to say it is good enough for us. If something in the parameter
|
|
// string is malformed, it'll get caught when/if
|
|
// getUserAsTelephoneSubscriber() is called.
|
|
pb.skipChar(';');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch(ParseException& /*e*/)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Token
|
|
Uri::getUserAsTelephoneSubscriber() const
|
|
{
|
|
// !bwc! Ugly. Someday, refactor all this lazy-parser stuff and make it
|
|
// possible to control ownership explicitly.
|
|
// Set this up as lazy-parsed, to prevent exceptions from being thrown.
|
|
HeaderFieldValue temp(mUser.data(), mUser.size());
|
|
Token tempToken(temp, Headers::NONE);
|
|
// tempToken does not own the HeaderFieldValue temp, and temp does not own
|
|
// its buffer.
|
|
|
|
// Here's the voodoo; invoking operator= makes a deep copy of the stuff in
|
|
// tempToken, with result owning the memory, and result is in the unparsed
|
|
// state.
|
|
Token result = tempToken;
|
|
return result;
|
|
}
|
|
|
|
void
|
|
Uri::setUserAsTelephoneSubscriber(const Token& telephoneSubscriber)
|
|
{
|
|
mUser.clear();
|
|
oDataStream str(mUser);
|
|
str << telephoneSubscriber;
|
|
}
|
|
|
|
Data
|
|
Uri::getAorNoPort() const
|
|
{
|
|
Data result;
|
|
getAorInternal(true, false, result);
|
|
return result;
|
|
}
|
|
|
|
Data
|
|
Uri::getAor() const
|
|
{
|
|
Data result;
|
|
getAorInternal(true, true, result);
|
|
return result;
|
|
}
|
|
|
|
Uri
|
|
Uri::getAorAsUri(TransportType transportTypeToRemoveDefaultPort) const
|
|
{
|
|
//.dcm. -- tel conversion?
|
|
checkParsed();
|
|
Uri ret;
|
|
ret.scheme() = mScheme;
|
|
ret.user() = mUser;
|
|
ret.host() = mHost;
|
|
|
|
// Remove any default ports (if required)
|
|
if(transportTypeToRemoveDefaultPort == UDP ||
|
|
transportTypeToRemoveDefaultPort == TCP)
|
|
{
|
|
if(mPort != Symbols::DefaultSipPort)
|
|
{
|
|
ret.port() = mPort;
|
|
}
|
|
}
|
|
else if (transportTypeToRemoveDefaultPort == TLS ||
|
|
transportTypeToRemoveDefaultPort == DTLS)
|
|
{
|
|
if(mPort != Symbols::DefaultSipsPort)
|
|
{
|
|
ret.port() = mPort;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret.port() = mPort;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
Uri::parse(ParseBuffer& pb)
|
|
{
|
|
pb.skipWhitespace();
|
|
const char* start = pb.position();
|
|
pb.skipToOneOf(":@");
|
|
|
|
pb.assertNotEof();
|
|
|
|
pb.data(mScheme, start);
|
|
pb.skipChar(Symbols::COLON[0]);
|
|
mScheme.schemeLowercase();
|
|
|
|
if (mScheme==Symbols::Tel)
|
|
{
|
|
const char* anchor = pb.position();
|
|
static std::bitset<256> delimiter=Data::toBitset("\r\n\t ;>");
|
|
pb.skipToOneOf(delimiter);
|
|
pb.data(mUser, anchor);
|
|
if (!pb.eof() && *pb.position() == Symbols::SEMI_COLON[0])
|
|
{
|
|
anchor = pb.skipChar();
|
|
pb.skipToOneOf(ParseBuffer::Whitespace, Symbols::RA_QUOTE);
|
|
pb.data(mUserParameters, anchor);
|
|
}
|
|
return;
|
|
}
|
|
|
|
start = pb.position();
|
|
static std::bitset<256> userPortOrPasswordDelim(Data::toBitset("@:\""));
|
|
// stop at double-quote to prevent matching an '@' in a quoted string param.
|
|
pb.skipToOneOf(userPortOrPasswordDelim);
|
|
if (!pb.eof())
|
|
{
|
|
const char* atSign=0;
|
|
if (*pb.position() == Symbols::COLON[0])
|
|
{
|
|
// Either a password, or a port
|
|
const char* afterColon = pb.skipChar();
|
|
pb.skipToOneOf("@\"");
|
|
if(!pb.eof() && *pb.position() == Symbols::AT_SIGN[0])
|
|
{
|
|
atSign=pb.position();
|
|
// password
|
|
#ifdef HANDLE_CHARACTER_ESCAPING
|
|
pb.dataUnescaped(mPassword, afterColon);
|
|
#else
|
|
pb.data(mPassword, afterColon);
|
|
#endif
|
|
pb.reset(afterColon-1);
|
|
}
|
|
else
|
|
{
|
|
// port. No user part.
|
|
pb.reset(start);
|
|
}
|
|
}
|
|
else if(*pb.position() == Symbols::AT_SIGN[0])
|
|
{
|
|
atSign=pb.position();
|
|
}
|
|
else
|
|
{
|
|
// Only a hostpart
|
|
pb.reset(start);
|
|
}
|
|
|
|
if(atSign)
|
|
{
|
|
#ifdef HANDLE_CHARACTER_ESCAPING
|
|
pb.dataUnescaped(mUser, start);
|
|
#else
|
|
pb.data(mUser, start);
|
|
#endif
|
|
pb.reset(atSign);
|
|
start = pb.skipChar();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pb.reset(start);
|
|
}
|
|
|
|
mHostCanonicalized=false;
|
|
static std::bitset<256> hostDelimiter(Data::toBitset("\r\n\t :;?>"));
|
|
if (*start == '[')
|
|
{
|
|
start = pb.skipChar();
|
|
pb.skipToChar(']');
|
|
pb.data(mHost, start);
|
|
// .bwc. We do not save this canonicalization, since we weren't doing so
|
|
// before. This may change soon.
|
|
Data canonicalizedHost=DnsUtil::canonicalizeIpV6Address(mHost);
|
|
if(canonicalizedHost.empty())
|
|
{
|
|
// .bwc. So the V6 addy is garbage.
|
|
throw ParseException("Unparsable V6 address (note, this might"
|
|
" be unparsable because IPV6 support is not"
|
|
" enabled)","Uri",__FILE__,
|
|
__LINE__);
|
|
}
|
|
pb.skipChar();
|
|
pb.skipToOneOf(hostDelimiter);
|
|
}
|
|
else
|
|
{
|
|
pb.skipToOneOf(hostDelimiter);
|
|
pb.data(mHost, start);
|
|
}
|
|
|
|
if (!pb.eof() && *pb.position() == ':')
|
|
{
|
|
start = pb.skipChar();
|
|
mPort = pb.uInt32();
|
|
}
|
|
else
|
|
{
|
|
mPort = 0;
|
|
}
|
|
|
|
parseParameters(pb);
|
|
|
|
if (!pb.eof() && *pb.position() == Symbols::QUESTION[0])
|
|
{
|
|
const char* anchor = pb.position();
|
|
pb.skipToOneOf(">;", ParseBuffer::Whitespace);
|
|
if(!mEmbeddedHeadersText.get()) mEmbeddedHeadersText.reset(new Data);
|
|
pb.data(*mEmbeddedHeadersText, anchor);
|
|
}
|
|
}
|
|
|
|
ParserCategory*
|
|
Uri::clone() const
|
|
{
|
|
return new Uri(*this);
|
|
}
|
|
|
|
ParserCategory*
|
|
Uri::clone(void* location) const
|
|
{
|
|
return new (location) Uri(*this);
|
|
}
|
|
|
|
ParserCategory*
|
|
Uri::clone(PoolBase* pool) const
|
|
{
|
|
return new (pool) Uri(*this);
|
|
}
|
|
|
|
void Uri::setUriUserEncoding(unsigned char c, bool encode)
|
|
{
|
|
getUserEncodingTable()[c] = encode;
|
|
}
|
|
|
|
void Uri::setUriPasswordEncoding(unsigned char c, bool encode)
|
|
{
|
|
getPasswordEncodingTable()[c] = encode;
|
|
}
|
|
|
|
// should not encode user parameters unless its a tel?
|
|
EncodeStream&
|
|
Uri::encodeParsed(EncodeStream& str) const
|
|
{
|
|
str << mScheme << Symbols::COLON;
|
|
if (!mUser.empty())
|
|
{
|
|
#ifdef HANDLE_CHARACTER_ESCAPING
|
|
mUser.escapeToStream(str, getUserEncodingTable());
|
|
#else
|
|
str << mUser;
|
|
#endif
|
|
if (!mUserParameters.empty())
|
|
{
|
|
str << Symbols::SEMI_COLON[0] << mUserParameters;
|
|
}
|
|
if (!mPassword.empty())
|
|
{
|
|
str << Symbols::COLON;
|
|
#ifdef HANDLE_CHARACTER_ESCAPING
|
|
mPassword.escapeToStream(str, getPasswordEncodingTable());
|
|
#else
|
|
str << mPassword;
|
|
#endif
|
|
}
|
|
}
|
|
if (!mHost.empty())
|
|
{
|
|
if (!mUser.empty())
|
|
{
|
|
str << Symbols::AT_SIGN;
|
|
}
|
|
if (DnsUtil::isIpV6Address(mHost))
|
|
{
|
|
str << '[' << mHost << ']';
|
|
}
|
|
else
|
|
{
|
|
str << mHost;
|
|
}
|
|
}
|
|
if (mPort != 0)
|
|
{
|
|
str << Symbols::COLON << mPort;
|
|
}
|
|
encodeParameters(str);
|
|
encodeEmbeddedHeaders(str);
|
|
|
|
return str;
|
|
}
|
|
|
|
SipMessage&
|
|
Uri::embedded()
|
|
{
|
|
checkParsed();
|
|
if (mEmbeddedHeaders.get() == 0)
|
|
{
|
|
this->mEmbeddedHeaders.reset(new SipMessage());
|
|
if (mEmbeddedHeadersText.get() && !mEmbeddedHeadersText->empty())
|
|
{
|
|
ParseBuffer pb(mEmbeddedHeadersText->data(), mEmbeddedHeadersText->size());
|
|
this->parseEmbeddedHeaders(pb);
|
|
}
|
|
}
|
|
|
|
return *mEmbeddedHeaders;
|
|
}
|
|
|
|
const SipMessage&
|
|
Uri::embedded() const
|
|
{
|
|
Uri* ncthis = const_cast<Uri*>(this);
|
|
return ncthis->embedded();
|
|
}
|
|
|
|
static const Data bodyData("Body");
|
|
void
|
|
Uri::parseEmbeddedHeaders(ParseBuffer& pb)
|
|
{
|
|
DebugLog(<< "Uri::parseEmbeddedHeaders");
|
|
if (!pb.eof() && *pb.position() == Symbols::QUESTION[0])
|
|
{
|
|
pb.skipChar();
|
|
}
|
|
|
|
const char* anchor;
|
|
Data headerName;
|
|
Data headerContents;
|
|
|
|
bool first = true;
|
|
while (!pb.eof())
|
|
{
|
|
if (first)
|
|
{
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
pb.skipChar(Symbols::AMPERSAND[0]);
|
|
}
|
|
|
|
anchor = pb.position();
|
|
pb.skipToChar(Symbols::EQUALS[0]);
|
|
pb.data(headerName, anchor);
|
|
// .dlb. in theory, need to decode header name
|
|
|
|
anchor = pb.skipChar(Symbols::EQUALS[0]);
|
|
pb.skipToChar(Symbols::AMPERSAND[0]);
|
|
pb.data(headerContents, anchor);
|
|
|
|
unsigned int len;
|
|
char* decodedContents = Embedded::decode(headerContents, len);
|
|
mEmbeddedHeaders->addBuffer(decodedContents);
|
|
|
|
if (isEqualNoCase(bodyData, headerName))
|
|
{
|
|
mEmbeddedHeaders->setBody(decodedContents, len);
|
|
}
|
|
else
|
|
{
|
|
DebugLog(<< "Uri::parseEmbeddedHeaders(" << headerName << ", " << Data(decodedContents, len) << ")");
|
|
mEmbeddedHeaders->addHeader(Headers::getType(headerName.data(), (int)headerName.size()),
|
|
headerName.data(), (int)headerName.size(),
|
|
decodedContents, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
EncodeStream&
|
|
Uri::encodeEmbeddedHeaders(EncodeStream& str) const
|
|
{
|
|
if (mEmbeddedHeaders.get())
|
|
{
|
|
mEmbeddedHeaders->encodeEmbedded(str);
|
|
}
|
|
else if(mEmbeddedHeadersText.get())
|
|
{
|
|
// never decoded
|
|
str << *mEmbeddedHeadersText;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
Data
|
|
Uri::toString() const
|
|
{
|
|
Data out;
|
|
{
|
|
oDataStream dataStream(out);
|
|
this->encodeParsed(dataStream);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
ParameterTypes::Factory Uri::ParameterFactories[ParameterTypes::MAX_PARAMETER]={0};
|
|
|
|
Parameter*
|
|
Uri::createParam(ParameterTypes::Type type, ParseBuffer& pb, const std::bitset<256>& terminators, PoolBase* pool)
|
|
{
|
|
if(type > ParameterTypes::UNKNOWN && type < ParameterTypes::MAX_PARAMETER && ParameterFactories[type])
|
|
{
|
|
return ParameterFactories[type](type, pb, terminators, pool);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
Uri::exists(const Param<Uri>& paramType) const
|
|
{
|
|
checkParsed();
|
|
bool ret = getParameterByEnum(paramType.getTypeNum()) != NULL;
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
Uri::remove(const Param<Uri>& paramType)
|
|
{
|
|
checkParsed();
|
|
removeParameterByEnum(paramType.getTypeNum());
|
|
}
|
|
|
|
#define defineParam(_enum, _name, _type, _RFC_ref_ignored) \
|
|
_enum##_Param::DType& \
|
|
Uri::param(const _enum##_Param& paramType) \
|
|
{ \
|
|
checkParsed(); \
|
|
_enum##_Param::Type* p = \
|
|
static_cast<_enum##_Param::Type*>(getParameterByEnum(paramType.getTypeNum())); \
|
|
if (!p) \
|
|
{ \
|
|
p = new _enum##_Param::Type(paramType.getTypeNum()); \
|
|
mParameters.push_back(p); \
|
|
} \
|
|
return p->value(); \
|
|
} \
|
|
\
|
|
const _enum##_Param::DType& \
|
|
Uri::param(const _enum##_Param& paramType) const \
|
|
{ \
|
|
checkParsed(); \
|
|
_enum##_Param::Type* p = \
|
|
static_cast<_enum##_Param::Type*>(getParameterByEnum(paramType.getTypeNum())); \
|
|
if (!p) \
|
|
{ \
|
|
InfoLog(<< "Missing parameter " _name " " << ParameterTypes::ParameterNames[paramType.getTypeNum()]); \
|
|
DebugLog(<< *this); \
|
|
throw Exception("Missing parameter " _name, __FILE__, __LINE__); \
|
|
} \
|
|
return p->value(); \
|
|
}
|
|
|
|
defineParam(ob,"ob",ExistsParameter,"RFC 5626");
|
|
defineParam(gr, "gr", ExistsOrDataParameter, "RFC 5627");
|
|
defineParam(comp, "comp", DataParameter, "RFC 3486");
|
|
defineParam(duration, "duration", UInt32Parameter, "RFC 4240");
|
|
defineParam(lr, "lr", ExistsParameter, "RFC 3261");
|
|
defineParam(maddr, "maddr", DataParameter, "RFC 3261");
|
|
defineParam(method, "method", DataParameter, "RFC 3261");
|
|
defineParam(transport, "transport", DataParameter, "RFC 3261");
|
|
defineParam(ttl, "ttl", UInt32Parameter, "RFC 3261");
|
|
defineParam(user, "user", DataParameter, "RFC 3261, 4967");
|
|
defineParam(extension, "ext", DataParameter, "RFC 3966"); // Token is used when ext is a user-parameter
|
|
defineParam(sigcompId, "sigcomp-id", QuotedDataParameter, "RFC 5049");
|
|
defineParam(rinstance, "rinstance", DataParameter, "proprietary (resip)");
|
|
defineParam(addTransport, "addTransport", ExistsParameter, "RESIP INTERNAL");
|
|
|
|
#undef defineParam
|
|
|
|
HashValueImp(resip::Uri, resip::Data::from(data).hash());
|
|
|
|
/* ====================================================================
|
|
* The Vovida Software License, Version 1.0
|
|
*
|
|
* Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* 3. The names "VOCAL", "Vovida Open Communication Application Library",
|
|
* and "Vovida Open Communication Application Library (VOCAL)" must
|
|
* not be used to endorse or promote products derived from this
|
|
* software without prior written permission. For written
|
|
* permission, please contact vocal@vovida.org.
|
|
*
|
|
* 4. Products derived from this software may not be called "VOCAL", nor
|
|
* may "VOCAL" appear in their name, without prior written
|
|
* permission of Vovida Networks, Inc.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
|
|
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
|
|
* NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
|
|
* IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
* DAMAGE.
|
|
*
|
|
* ====================================================================
|
|
*
|
|
* This software consists of voluntary contributions made by Vovida
|
|
* Networks, Inc. and many individuals on behalf of Vovida Networks,
|
|
* Inc. For more information on Vovida Networks, Inc., please see
|
|
* <http://www.vovida.org/>.
|
|
*
|
|
*/
|