Modified and improved regexroute.
Added extra support methods in engine, fixed minor annoyances. git-svn-id: http://yate.null.ro/svn/yate/trunk@118 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
parent
536180ee0f
commit
fd651a3b2b
|
@ -34,9 +34,12 @@
|
|||
; Expressions are scanned from top to bottom; the first match returns the value
|
||||
; Each line must be of the form:
|
||||
; regexp=context_name
|
||||
; To match a message parameter you can use the format:
|
||||
; ${paramname}regexp=context_name
|
||||
; Strings captured with the regular expression construct \(...\) can be
|
||||
; inserted in the context name using \1, \2, \3, ... while \0 holds the entire
|
||||
; matched regexp even if no capture was used
|
||||
; Message parameters can be inserted in the context name using ${paramname}
|
||||
;
|
||||
; Example:
|
||||
;^$=empty
|
||||
|
@ -54,18 +57,24 @@
|
|||
; Expressions are scanned from top to bottom; the first match returns the value
|
||||
; Each line must be of the form:
|
||||
; regexp=target
|
||||
; To match a message parameter you can use the format:
|
||||
; ${paramname}regexp=target
|
||||
; Strings captured with the regular expression construct \(...\) can be
|
||||
; inserted in the target using \1, \2, \3, ...
|
||||
; Message parameters can be inserted in the target using ${paramname}
|
||||
;
|
||||
; First character of a matched target can have a special meaning
|
||||
; - returns immediately from the context without routing
|
||||
; < calls another context, returns at the next entry if the other context
|
||||
did not return successfully
|
||||
; > jumps another context, does not return to this context
|
||||
; ! modify the called string instead of specifying a target
|
||||
; First word of a matched target can have a special meaning
|
||||
; return - returns immediately from the context without routing
|
||||
; include - calls another context, returns at the next entry if the other
|
||||
; context did not return successfully
|
||||
; jump - jumps another context, does not return to this context
|
||||
; match - modify the matched string instead of specifying a target
|
||||
;
|
||||
; It is possible to set message parameters by appending them as name=value
|
||||
; while separating them with semicolons (;)
|
||||
; Please note that the match string is not changed together with the message
|
||||
; parameter from which it was copied; for example in routing stage using
|
||||
; "match 123" and ";called=123" have different effects
|
||||
;
|
||||
; Example:
|
||||
; route the emergency 112 and 911 numbers to POTS, any channel on an E1,
|
||||
|
|
|
@ -120,6 +120,17 @@ void Configuration::clearKey(const String §, const String &key)
|
|||
l->clearParam(key);
|
||||
}
|
||||
|
||||
void Configuration::addValue(const String §, const char *key, const char *value)
|
||||
{
|
||||
DDebug(DebugInfo,"Configuration::addValue(\"%s\",\"%s\",\"%s\")",sect.c_str(),key,value);
|
||||
ObjList *l = makeSectHolder(sect);
|
||||
if (!l)
|
||||
return;
|
||||
NamedList *n = static_cast<NamedList *>(l->get());
|
||||
if (n)
|
||||
n->addParam(key,value);
|
||||
}
|
||||
|
||||
void Configuration::setValue(const String §, const char *key, const char *value)
|
||||
{
|
||||
DDebug(DebugInfo,"Configuration::setValue(\"%s\",\"%s\",\"%s\")",sect.c_str(),key,value);
|
||||
|
@ -181,7 +192,7 @@ bool Configuration::load()
|
|||
}
|
||||
int q = s.find('=');
|
||||
if (q > 0)
|
||||
setValue(sect,s.substr(0,q).trimBlanks(),s.substr(q+1).trimBlanks());
|
||||
addValue(sect,s.substr(0,q).trimBlanks(),s.substr(q+1).trimBlanks());
|
||||
}
|
||||
::fclose(f);
|
||||
return true;
|
||||
|
|
|
@ -533,6 +533,19 @@ bool String::startsWith(const char *what, bool wordBreak) const
|
|||
return (::strncmp(m_string,what,l) == 0);
|
||||
}
|
||||
|
||||
bool String::startSkip(const char *what, bool wordBreak)
|
||||
{
|
||||
if (startsWith(what,wordBreak)) {
|
||||
const char *p = m_string + ::strlen(what);
|
||||
if (wordBreak)
|
||||
while (isWordBreak(*p))
|
||||
p++;
|
||||
assign(p);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool String::endsWith(const char *what, bool wordBreak) const
|
||||
{
|
||||
if (!(m_string && what && *what))
|
||||
|
@ -586,19 +599,23 @@ String String::replaceMatches(const String &templ) const
|
|||
for (;;) {
|
||||
pos = templ.find('\\',ofs);
|
||||
if (pos < 0) {
|
||||
s += templ.substr(ofs);
|
||||
s << templ.substr(ofs);
|
||||
break;
|
||||
}
|
||||
s += templ.substr(ofs,pos-ofs);
|
||||
s << templ.substr(ofs,pos-ofs);
|
||||
pos++;
|
||||
char c = templ[pos];
|
||||
if (c == '\\') {
|
||||
pos++;
|
||||
s += "\\";
|
||||
s << "\\";
|
||||
}
|
||||
else if ('0' <= c && c <= '9') {
|
||||
pos++;
|
||||
s += matchString(c - '0');
|
||||
s << matchString(c - '0');
|
||||
}
|
||||
else {
|
||||
pos++;
|
||||
s << "\\" << c;
|
||||
}
|
||||
ofs = pos;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,20 @@ static void setMessage(Message &msg, String &line)
|
|||
bool first = true;
|
||||
for (ObjList *p = strs; p; p=p->next()) {
|
||||
String *s = static_cast<String*>(p->get());
|
||||
if (s) {
|
||||
int p1;
|
||||
while ((p1 = s->find("${")) >= 0) {
|
||||
// handle ${paramname} replacements
|
||||
int p2 = s->find('}',p1+2);
|
||||
if (p2 > 0) {
|
||||
String v = s->substr(p1+2,p2-p1-2);
|
||||
v.trimBlanks();
|
||||
DDebug("RegexRoute",DebugAll,"Replacing parameter '%s'",
|
||||
v.c_str());
|
||||
*s = s->substr(0,p1) + msg.getValue(v) + s->substr(p2+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (first) {
|
||||
first = false;
|
||||
line = s ? *s : "";
|
||||
|
@ -67,12 +81,12 @@ static void setMessage(Message &msg, String &line)
|
|||
strs->destruct();
|
||||
}
|
||||
|
||||
static bool oneContext(Message &msg, String &called, const String &context, int depth = 0)
|
||||
static bool oneContext(Message &msg, String &str, const String &context, String &ret, int depth = 0)
|
||||
{
|
||||
if (!(context && *context))
|
||||
return false;
|
||||
if (depth > 5) {
|
||||
Debug("RegexRoute",DebugWarn,"Loop detected, current context '%s'",context.c_str());
|
||||
Debug("RegexRoute",DebugWarn,"Possible loop detected, current context '%s'",context.c_str());
|
||||
return false;
|
||||
}
|
||||
NamedList *l = s_cfg.getSection(context);
|
||||
|
@ -82,51 +96,75 @@ static bool oneContext(Message &msg, String &called, const String &context, int
|
|||
NamedString *n = l->getParam(i);
|
||||
if (n) {
|
||||
Regexp r(n->name());
|
||||
if (called.matches(r)) {
|
||||
String val = called.replaceMatches(*n);
|
||||
String val;
|
||||
if (r.startsWith("${")) {
|
||||
// handle special case ${paramname}regexp=value
|
||||
int p = r.find('}');
|
||||
if (p < 3) {
|
||||
Debug("RegexRoute",DebugWarn,"Invalid parameter match '%s' in rule #%u in context '%s'",
|
||||
r.c_str(),i+1,context.c_str());
|
||||
continue;
|
||||
}
|
||||
val = r.substr(2,p-2);
|
||||
r = r.substr(p+1);
|
||||
val.trimBlanks();
|
||||
r.trimBlanks();
|
||||
if (val.null() || r.null()) {
|
||||
Debug("RegexRoute",DebugWarn,"Missing parameter or rule in rule #%u in context '%s'",
|
||||
i+1,context.c_str());
|
||||
continue;
|
||||
}
|
||||
NDebug("RegexRoute",DebugAll,"Using message parameter '%s'",
|
||||
val.c_str());
|
||||
val = msg.getValue(val);
|
||||
}
|
||||
else
|
||||
val = str;
|
||||
val.trimBlanks();
|
||||
|
||||
if (val.matches(r)) {
|
||||
val = val.replaceMatches(*n);
|
||||
setMessage(msg,val);
|
||||
val.trimBlanks();
|
||||
switch (val[0]) {
|
||||
case 0:
|
||||
break;
|
||||
case '-':
|
||||
return false;
|
||||
case '>':
|
||||
val >> ">";
|
||||
val.trimBlanks();
|
||||
NDebug("RegexRoute",DebugAll,"Jumping to context '%s' by rule #%u '%s'",
|
||||
val.c_str(),i+1,r.c_str());
|
||||
return oneContext(msg,called,val,depth+1);
|
||||
case '<':
|
||||
val >> "<";
|
||||
val.trimBlanks();
|
||||
NDebug("RegexRoute",DebugAll,"Calling context '%s' by rule #%u '%s'",
|
||||
val.c_str(),i+1,r.c_str());
|
||||
if (oneContext(msg,called,val,depth+1)) {
|
||||
DDebug("RegexRoute",DebugAll,"Returning true from context '%s'", context.c_str());
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '!':
|
||||
val >> "!";
|
||||
val.trimBlanks();
|
||||
if (!val.null()) {
|
||||
NDebug("RegexRoute",DebugAll,"Setting called '%s' by rule #%u '%s'",
|
||||
val.c_str(),i+1,r.c_str());
|
||||
called = val;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DDebug("RegexRoute",DebugAll,"Routing call to '%s' in context '%s' via `%s' by rule #%u '%s'",
|
||||
called.c_str(),context.c_str(),val.c_str(),i+1,r.c_str());
|
||||
msg.retValue() = val;
|
||||
if (val.null()) {
|
||||
// special case: do nothing on empty target
|
||||
continue;
|
||||
}
|
||||
else if (val == "return") {
|
||||
NDebug("RegexRoute",DebugAll,"Returning false from context '%s'", context.c_str());
|
||||
return false;
|
||||
}
|
||||
else if (val.startSkip("jump")) {
|
||||
NDebug("RegexRoute",DebugAll,"Jumping to context '%s' by rule #%u '%s'",
|
||||
val.c_str(),i+1,n->name().c_str());
|
||||
return oneContext(msg,str,val,ret,depth+1);
|
||||
}
|
||||
else if (val.startSkip("include")) {
|
||||
NDebug("RegexRoute",DebugAll,"Including context '%s' by rule #%u '%s'",
|
||||
val.c_str(),i+1,n->name().c_str());
|
||||
if (oneContext(msg,str,val,ret,depth+1)) {
|
||||
DDebug("RegexRoute",DebugAll,"Returning true from context '%s'", context.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (val.startSkip("match")) {
|
||||
if (!val.null()) {
|
||||
NDebug("RegexRoute",DebugAll,"Setting match string '%s' by rule #%u '%s' in context '%s'",
|
||||
val.c_str(),i+1,n->name().c_str(),context.c_str());
|
||||
str = val;
|
||||
}
|
||||
}
|
||||
else {
|
||||
DDebug("RegexRoute",DebugAll,"Returning '%s' for '%s' in context '%s' by rule #%u '%s'",
|
||||
val.c_str(),str.c_str(),context.c_str(),i+1,n->name().c_str());
|
||||
ret = val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DDebug("RegexRoute",DebugAll,"Returning false from context '%s'", context.c_str());
|
||||
DDebug("RegexRoute",DebugAll,"Returning false at end of context '%s'", context.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -137,9 +175,11 @@ bool RouteHandler::received(Message &msg)
|
|||
if (called.null())
|
||||
return false;
|
||||
const char *context = msg.getValue("context","default");
|
||||
if (oneContext(msg,called,context)) {
|
||||
Debug(DebugInfo,"Routing call to '%s' in context '%s' via `%s' in %llu usec",
|
||||
called.c_str(),context,msg.retValue().c_str(),Time::now()-tmr);
|
||||
String ret;
|
||||
if (oneContext(msg,called,context,ret)) {
|
||||
Debug(DebugInfo,"Routing call to '%s' in context '%s' via '%s' in %llu usec",
|
||||
called.c_str(),context,ret.c_str(),Time::now()-tmr);
|
||||
msg.retValue() = ret;
|
||||
return true;
|
||||
}
|
||||
Debug(DebugInfo,"Could not route call to '%s' in context '%s', wasted %llu usec",
|
||||
|
@ -161,32 +201,25 @@ bool PrerouteHandler::received(Message &msg)
|
|||
// return immediately if there is already a context
|
||||
if (msg.getValue("context"))
|
||||
return false;
|
||||
// String s(msg.getValue("caller"));
|
||||
String s(msg.getValue("driver")); s+="/";
|
||||
s+=msg.getValue("span"); s+="/";
|
||||
s+=msg.getValue("channel"); s+="/";
|
||||
s+=msg.getValue("caller");
|
||||
|
||||
if (s.null())
|
||||
|
||||
String caller(msg.getValue("caller"));
|
||||
if (caller.null()) {
|
||||
caller << msg.getValue("driver") << "/";
|
||||
caller << msg.getValue("span") << "/";
|
||||
caller << msg.getValue("channel");
|
||||
}
|
||||
if (caller == "//")
|
||||
return false;
|
||||
NamedList *l = s_cfg.getSection("contexts");
|
||||
if (l) {
|
||||
unsigned int len = l->length();
|
||||
for (unsigned int i=0; i<len; i++) {
|
||||
NamedString *n = l->getParam(i);
|
||||
if (n) {
|
||||
Regexp r(n->name());
|
||||
if (s.matches(r)) {
|
||||
msg.addParam("context",s.replaceMatches(*n));
|
||||
Debug(DebugInfo,"Classifying caller '%s' in context '%s' by rule #%u '%s' in %llu usec",
|
||||
s.c_str(),msg.getValue("context"),i+1,r.c_str(),Time::now()-tmr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String ret;
|
||||
if (oneContext(msg,caller,"contexts",ret)) {
|
||||
Debug(DebugInfo,"Classifying caller '%s' in context '%s' in %llu usec",
|
||||
caller.c_str(),ret.c_str(),Time::now()-tmr);
|
||||
msg.addParam("context",ret);
|
||||
return true;
|
||||
}
|
||||
Debug(DebugInfo,"Could not classify call from '%s', wasted %llu usec",
|
||||
s.c_str(),Time::now()-tmr);
|
||||
caller.c_str(),Time::now()-tmr);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
|
@ -224,16 +224,6 @@ void Connection::run()
|
|||
}
|
||||
}
|
||||
|
||||
static bool startSkip(String &s, const char *keyword)
|
||||
{
|
||||
if (s.startsWith(keyword,true)) {
|
||||
s >> keyword;
|
||||
s.trimBlanks();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Connection::processLine(const char *line)
|
||||
{
|
||||
DDebug("RManager",DebugInfo,"processLine = %s",line);
|
||||
|
@ -242,7 +232,7 @@ void Connection::processLine(const char *line)
|
|||
if (str.null())
|
||||
return;
|
||||
|
||||
if (startSkip(str,"status"))
|
||||
if (str.startSkip("status"))
|
||||
{
|
||||
Message m("status");
|
||||
if (!str.null()) {
|
||||
|
@ -254,7 +244,7 @@ void Connection::processLine(const char *line)
|
|||
str << m.retValue() << "%%-status\n";
|
||||
write(str);
|
||||
}
|
||||
else if (startSkip(str,"drop"))
|
||||
else if (str.startSkip("drop"))
|
||||
{
|
||||
if (str.null()) {
|
||||
write(m_machine ? "%%=drop:fail=noarg\n" : "You must specify what connection to drop!\n");
|
||||
|
@ -276,7 +266,7 @@ void Connection::processLine(const char *line)
|
|||
str = (m_machine ? "%%=drop:fail:" : "Could not drop ") + str + "\n";
|
||||
write(str);
|
||||
}
|
||||
else if (startSkip(str,"call"))
|
||||
else if (str.startSkip("call"))
|
||||
{
|
||||
int pos = str.find(' ');
|
||||
if (pos <= 0) {
|
||||
|
@ -293,9 +283,9 @@ void Connection::processLine(const char *line)
|
|||
str = (m_machine ? "%%=call:fail:" : "Could not call ") + str + "\n";
|
||||
write(str);
|
||||
}
|
||||
else if (startSkip(str,"debug"))
|
||||
else if (str.startSkip("debug"))
|
||||
{
|
||||
if (startSkip(str,"level")) {
|
||||
if (str.startSkip("level")) {
|
||||
int dbg = debugLevel();
|
||||
str >> dbg;
|
||||
dbg = debugLevel(dbg);
|
||||
|
@ -312,31 +302,31 @@ void Connection::processLine(const char *line)
|
|||
}
|
||||
write(str);
|
||||
}
|
||||
else if (startSkip(str,"machine"))
|
||||
else if (str.startSkip("machine"))
|
||||
{
|
||||
str >> m_machine;
|
||||
str = "Machine mode: ";
|
||||
str += (m_machine ? "on\n" : "off\n");
|
||||
write(str);
|
||||
}
|
||||
else if (startSkip(str,"reload"))
|
||||
else if (str.startSkip("reload"))
|
||||
{
|
||||
write(m_machine ? "%%=reload\n" : "Reinitializing...\n");
|
||||
Engine::init();
|
||||
}
|
||||
else if (startSkip(str,"quit"))
|
||||
else if (str.startSkip("quit"))
|
||||
{
|
||||
write(m_machine ? "%%=quit\n" : "Goodbye!\n");
|
||||
cancel();
|
||||
}
|
||||
else if (startSkip(str,"stop"))
|
||||
else if (str.startSkip("stop"))
|
||||
{
|
||||
unsigned code = 0;
|
||||
str >> code;
|
||||
write(m_machine ? "%%=shutdown\n" : "Engine shutting down - bye!\n");
|
||||
Engine::halt(code);
|
||||
}
|
||||
else if (startSkip(str,"help") || startSkip(str,"?"))
|
||||
else if (str.startSkip("help") || str.startSkip("?"))
|
||||
{
|
||||
Message m("help");
|
||||
if (!str.null())
|
||||
|
|
20
yatengine.h
20
yatengine.h
|
@ -750,6 +750,18 @@ public:
|
|||
*/
|
||||
bool endsWith(const char *what, bool wordBreak = false) const;
|
||||
|
||||
/**
|
||||
* Checks if the string starts with a substring and removes it
|
||||
* @param what Substring to search for
|
||||
* @param wordBreak Check if a word boundary follows the substring;
|
||||
* this parameter defaults to True because the intended use of this
|
||||
* method is to separate commands from their parameters
|
||||
* @return True if the substring occurs at the beginning of the string
|
||||
* and also removes the substring; if wordBreak is True any word
|
||||
* breaking characters are also removed
|
||||
*/
|
||||
bool startSkip(const char *what, bool wordBreak = true);
|
||||
|
||||
/**
|
||||
* Checks if matches another string
|
||||
* @param value String to check for match
|
||||
|
@ -1301,6 +1313,14 @@ public:
|
|||
*/
|
||||
void clearKey(const String §, const String &key);
|
||||
|
||||
/**
|
||||
* Add the value of a key in a section.
|
||||
* @param sect Name of the section, will be created if missing
|
||||
* @param key Name of the key to add in the section
|
||||
* @param value Value to set in the key
|
||||
*/
|
||||
void addValue(const String §, const char *key, const char *value = 0);
|
||||
|
||||
/**
|
||||
* Set the value of a key in a section.
|
||||
* @param sect Name of the section, will be created if missing
|
||||
|
|
Loading…
Reference in New Issue