From fd651a3b2bb6a85e8cae6d18f038d2f80b93fd47 Mon Sep 17 00:00:00 2001 From: paulc Date: Wed, 1 Dec 2004 00:54:04 +0000 Subject: [PATCH] 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 --- conf.d/regexroute.conf.sample | 21 +++-- engine/Configuration.cpp | 13 ++- engine/String.cpp | 25 +++++- modules/regexroute.cpp | 163 ++++++++++++++++++++-------------- modules/rmanager.cpp | 30 +++---- yatengine.h | 20 +++++ 6 files changed, 176 insertions(+), 96 deletions(-) diff --git a/conf.d/regexroute.conf.sample b/conf.d/regexroute.conf.sample index e9176a01..d621d6fb 100644 --- a/conf.d/regexroute.conf.sample +++ b/conf.d/regexroute.conf.sample @@ -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, diff --git a/engine/Configuration.cpp b/engine/Configuration.cpp index ccb881bf..e031dde7 100644 --- a/engine/Configuration.cpp +++ b/engine/Configuration.cpp @@ -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(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; diff --git a/engine/String.cpp b/engine/String.cpp index ef059551..93a1e831 100644 --- a/engine/String.cpp +++ b/engine/String.cpp @@ -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; } diff --git a/modules/regexroute.cpp b/modules/regexroute.cpp index 62f4abe2..3ca35eec 100644 --- a/modules/regexroute.cpp +++ b/modules/regexroute.cpp @@ -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(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; igetParam(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; }; diff --git a/modules/rmanager.cpp b/modules/rmanager.cpp index e4c6c739..129b242f 100644 --- a/modules/rmanager.cpp +++ b/modules/rmanager.cpp @@ -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()) diff --git a/yatengine.h b/yatengine.h index e6af2239..3ebfeae5 100644 --- a/yatengine.h +++ b/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