apps/sbc/ParamReplacer.cpp
bb763d84
 /*
  * Copyright (C) 2010 Stefan Sayer
  *
  * This file is part of SEMS, a free SIP media server.
  *
  * SEMS is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
  * For a license to use the SEMS software under conditions
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
  * SEMS is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "ParamReplacer.h"
d5aa12fc
 #include "RegisterCache.h"
f567b79d
 #include "sip/parse_next_hop.h"
a5f4dd19
 #include "sip/parse_common.h"
bb763d84
 
 #include "log.h"
 #include "AmSipHeaders.h"
 #include "AmUtils.h"
c60b508e
 #include "SBC.h" // for RegexMapper SBCFactory::regex_mappings
6d247332
 #include <algorithm>
1b7f1d75
 #include <stdlib.h>
bb763d84
 
a5f4dd19
 int replaceParsedParam(const string& s, size_t p,
 			const AmUriParser& parsed, string& res) {
   int skip_chars=1;
bb763d84
   switch (s[p+1]) {
   case 'u': { // URI
     res+=parsed.uri_user+"@"+parsed.uri_host;
     if (!parsed.uri_port.empty())
       res+=":"+parsed.uri_port;
   } break;
   case 'U': res+=parsed.uri_user; break; // User
   case 'd': { // domain
     res+=parsed.uri_host;
     if (!parsed.uri_port.empty())
       res+=":"+parsed.uri_port;
   } break;
   case 'h': res+=parsed.uri_host; break; // host
   case 'p': res+=parsed.uri_port; break; // port
   case 'H': res+=parsed.uri_headers; break; // Headers
ae823636
   case 'P': { // Params
a5f4dd19
     if((s.length() > p+3) && (s[p+2] == '(')) {
       size_t skip_p = p+3;
       for (;skip_p<s.length() && s[skip_p] != ')';skip_p++) { }
       if (skip_p==s.length()) {
 	WARN("Error parsing $%cP() param replacement (unclosed brackets)\n",s[p]);
 	break;
       }
       string param_name = s.substr(p+3,skip_p-p-3);
       if(param_name.empty()) {
 	res+=parsed.uri_param;
98e4cd20
 	skip_chars = skip_p-p;
a5f4dd19
 	break;
       }
       
88c4d03b
       const string& uri_params = parsed.uri_param;
a5f4dd19
       const char* c = uri_params.c_str();
       list<sip_avp*> params;
       if(parse_gen_params(&params,&c,uri_params.length(),0) < 0) {
 	DBG("could not parse URI parameters");
 	free_gen_params(&params);
 	break;
       }
 
       string param;
       for(list<sip_avp*>::iterator it = params.begin(); 
 	  it != params.end(); it++) {
 
 	if(lower_cmp_n((*it)->name.s,(*it)->name.len,
 		       param_name.c_str(),param_name.length()))
 	  continue;
 
 	param = c2stlstr((*it)->value);
       }
       free_gen_params(&params);
       res+=param;
       skip_chars = skip_p-p;
ae823636
     }
a5f4dd19
     else {
       res+=parsed.uri_param; 
ae823636
     }
a5f4dd19
   } break;
62558156
   case 'n': res+=parsed.display_name; break; // Params
 
b461accc
   // case 't': { // tag
   //   map<string, string>::const_iterator it = parsed.params.find("tag");
   //   if (it != parsed.params.end())
   //     res+=it->second;
   // } break;
bb763d84
   default: WARN("unknown replace pattern $%c%c\n",
 		s[p], s[p+1]); break;
   };
a5f4dd19
 
   return skip_chars;
bb763d84
 }
 
fb9177f8
 /* Returns a url-decoded version of str */
 /* IMPORTANT: be sure to free() the returned string after use */
 char *url_encode(const char *str);
 
bb763d84
 string replaceParameters(const string& s,
 			 const char* r_type,
 			 const AmSipRequest& req,
0dae3311
 			 const SBCCallProfile* call_profile,
bb763d84
 			 const string& app_param,
8b38448e
 			 AmUriParser& ruri_parser, 
 			 AmUriParser& from_parser,
1b7f1d75
 			 AmUriParser& to_parser,
 			 bool rebuild_ruri,
 			 bool rebuild_from,
 			 bool rebuild_to) {
bb763d84
   string res;
   bool is_replaced = false;
   size_t p = 0;
c60b508e
   bool is_escaped = false;
1b7f1d75
   const string& used_hdrs = req.hdrs; //(hdrs == NULL) ? req.hdrs : *hdrs;
c60b508e
 
   // char last_char=' ';
bb763d84
   
   while (p<s.length()) {
     size_t skip_chars = 1;
c60b508e
 
     if (is_escaped) {
bb763d84
       switch (s[p]) {
c60b508e
       case 'r': res += '\r'; break;
       case 'n': res += '\n'; break;
       case 't': res += '\t'; break;
       default: res += s[p]; break;
       }
       is_escaped = false;
     } else { // not escaped
       if (s[p]=='\\') {
 	if (p==s.length()-1) {
 	  res += '\\'; // add single \ at the end
 	} else {
 	  is_escaped = true;
 	  is_replaced = true;
bb763d84
 	}
c60b508e
       } else if ((s[p]=='$') && (s.length() >= p+1)) {
 	is_replaced = true;
 	p++;
 	switch (s[p]) {
 	case 'f': { // from
 	  if ((s.length() == p+1) || (s[p+1] == '.')) {
1b7f1d75
 	    if (rebuild_from) {
 	      res += from_parser.nameaddr_str();
 	    } else {
 	      res += req.from;
 	    }
 
bb763d84
 	    break;
 	  }
 
b461accc
 	  if (s[p+1]=='t') { // $ft - from tag
 	    res += req.from_tag;
 	    break;
 	  }
 
c60b508e
 	  if (from_parser.uri.empty()) {
1b7f1d75
 	    from_parser.uri = req.from;
 	    if (!from_parser.parse_uri()) {
c60b508e
 	      WARN("Error parsing From URI '%s'\n", req.from.c_str());
 	      break;
 	    }
 	  }
bb763d84
 
a5f4dd19
 	  skip_chars=replaceParsedParam(s, p, from_parser, res);
bb763d84
 
c60b508e
 	}; break;
bb763d84
 
c60b508e
 	case 't': { // to
 	  if ((s.length() == p+1) || (s[p+1] == '.')) {
1b7f1d75
 	    if (rebuild_to) {
 	      res += to_parser.nameaddr_str();
 	    } else {
 	      res += req.to;
 	    }
bb763d84
 	    break;
 	  }
 
ea87bbb2
 	  if (s[p+1]=='t') { // $tt - to tag
 	    res += req.to_tag;
 	    break;
 	  }
 
c60b508e
 	  if (to_parser.uri.empty()) {
1b7f1d75
 	    to_parser.uri = req.to;
 	    if (!to_parser.parse_uri()) {
c60b508e
 	      WARN("Error parsing To URI '%s'\n", req.to.c_str());
 	      break;
 	    }
 	  }
bb763d84
 
a5f4dd19
 	  skip_chars=replaceParsedParam(s, p, to_parser, res);
bb763d84
 
c60b508e
 	}; break;
 
 	case 'r': { // r-uri
 	  if ((s.length() == p+1) || (s[p+1] == '.')) {
1b7f1d75
 	    if (rebuild_ruri) {
 	      res += ruri_parser.uri_str();
 	    } else {
 	      res += req.r_uri;
 	    }
bb763d84
 	    break;
 	  }
 
c60b508e
 	  if (ruri_parser.uri.empty()) {
 	    ruri_parser.uri = req.r_uri;
 	    if (!ruri_parser.parse_uri()) {
 	      WARN("Error parsing R-URI '%s'\n", req.r_uri.c_str());
 	      break;
 	    }
 	  }
a5f4dd19
 	  skip_chars=replaceParsedParam(s, p, ruri_parser, res);
c60b508e
 	}; break;
 
 	case 'c': { // call-id
 	  if ((s.length() == p+1) || (s[p+1] == 'i')) {
 	    res += req.callid;
 	    break;
 	  }
 	  WARN("unknown replacement $c%c\n", s[p+1]);
 	}; break;
 
 	case 's': { // source (remote)
 	  if (s.length() < p+1) {
 	    WARN("unknown replacement $s\n");
 	    break;
 	  }
 
 	  if (s[p+1] == 'i') { // $si source IP address
f567b79d
 	    res += req.remote_ip;
c60b508e
 	    break;
 	  } else if (s[p+1] == 'p') { // $sp source port
 	    res += int2str(req.remote_port);
 	    break;
 	  }
 
 	  WARN("unknown replacement $s%c\n", s[p+1]);
 	}; break;
 
f567b79d
 	case 'd': { // destination (remote UAS)
 	  if (s.length() < p+1) {
 	    WARN("unknown replacement $s\n");
 	    break;
 	  }
 
 	  if(!call_profile->next_hop.empty()) {
 	    cstring _next_hop = stl2cstr(call_profile->next_hop);
 	    list<sip_destination> dest_list;
 	    if(parse_next_hop(_next_hop,dest_list)) {
 	      WARN("parse_next_hop %.*s failed\n",
 		   _next_hop.len, _next_hop.s);
 	      break;
 	    }
 
 	    if(dest_list.size() == 0) {
 	      WARN("next-hop is not empty, but the resulting destination list is\n");
 	      break;
 	    }
 
 	    const sip_destination& dest = dest_list.front();
 	    if (s[p+1] == 'i') { // $di remote UAS IP address
 	      res += c2stlstr(dest.host);
 	      break;
 	    } else if (s[p+1] == 'p') { // $dp remote UAS port
 	      res += int2str(dest.port);
 	      break;
 	    }
 	    WARN("unknown replacement $d%c\n", s[p+1]);
 	    break;
 	  }
 
 	  if (ruri_parser.uri.empty()) {
 	    ruri_parser.uri = req.r_uri;
 	    if (!ruri_parser.parse_uri()) {
 	      WARN("Error parsing R-URI '%s'\n", req.r_uri.c_str());
 	      break;
 	    }
 	  }
 
 	  if (s[p+1] == 'i') { // $di remote UAS IP address
 	    res += ruri_parser.uri_host;
 	    break;
 	  } else if (s[p+1] == 'p') { // $dp remote UAS port
 	    res += ruri_parser.uri_port;
 	    break;
 	  }
 
 	  WARN("unknown replacement $d%c\n", s[p+1]);
 	}; break;
 
c60b508e
 	case 'R': { // received (local)
 	  if (s.length() < p+1) {
 	    WARN("unknown replacement $R\n");
 	    break;
 	  }
 
0b320584
 	  if (s[p+1] == 'i') { // $Ri received IP address
c60b508e
 	    res += req.local_ip.c_str();
 	    break;
0b320584
 	  } else if (s[p+1] == 'p') { // $Rp received port
c60b508e
 	    res += int2str(req.local_port);
 	    break;
0b320584
 	  } else if (s[p+1] == 'f') { // $Rf received interface id
 	    res += int2str(req.local_if);
e7ab6b9d
 	    break;
0b320584
 	  } else if (s[p+1] == 'n') { // $Rn received interface name
6d93ae38
 	    if (req.local_if < AmConfig::SIP_Ifs.size()) {
 	      res += AmConfig::SIP_Ifs[req.local_if].name;
0b320584
 	    }
e7ab6b9d
 	    break;
0b320584
 	  } else if (s[p+1] == 'I') { // $RI received interface public IP
6d93ae38
 	    if (req.local_if < AmConfig::SIP_Ifs.size()) {
 	      res += AmConfig::SIP_Ifs[req.local_if].PublicIP;
0b320584
 	    }
1b7f1d75
 	    break;
 	  }
 	  WARN("unknown replacement $R%c\n", s[p+1]);
c60b508e
 	}; break;
 
bf4f4354
 	case 'u': {// Reg-cached destination user
 	  if (s.length() < p+1) {
 	    WARN("unknown replacement $u\n");
 	    break;
 	  }
 
 	  // REG-Cache lookup
 	  AliasEntry alias_entry;
 	  const string& alias = req.user;
 
 	  if(!RegisterCache::instance()->findAliasEntry(alias, alias_entry)) {
 	    WARN("reg-cache: User '%s' not found",alias.c_str());
 	    break;
 	  }
 	  
 	  if(s[p+1] == 'c') {
 	    res += alias_entry.contact_uri;
 	    break;
 	  }
 	  else if(s[p+1] == 's') {
 	    res += alias_entry.source_ip;
 	    if(alias_entry.source_port != 5060)
 	      res += ":" + int2str(alias_entry.source_port);
 	    break;
 	  }
 	  else if(s[p+1] == 'i') {
 	    res += AmConfig::SIP_Ifs[alias_entry.local_if].name;
 	    break;
 	  }
 	  
 	  WARN("unknown replacement $u%c\n", s[p+1]);
 	} break;
 
d5aa12fc
 	case 'U': { // Reg-cached originating user
 	  if (s.length() < p+1) {
 	    WARN("unknown replacement $U\n");
 	    break;
 	  }
 	  if (s[p+1] == 'a') { // $Ua originating AoR
 	    AliasEntry ae;
 	    RegisterCache* reg_cache = RegisterCache::instance();
 	    if(reg_cache->findAEByContact(req.from_uri,req.remote_ip,
 					   req.remote_port,ae)) {
 	      res += ae.aor;
 	    }
 	    break;
 	  }
1b558f1b
 	  else if(s[p+1] == 'A') { // $UA originating alias
 	    AliasEntry ae;
 	    RegisterCache* reg_cache = RegisterCache::instance();
 
 	    string aor;
 	    if (from_parser.uri.empty())
 	      aor = req.from;
 	    else if(!rebuild_from)
 	      aor = from_parser.uri;
 	    else
 	      aor = from_parser.uri_str();
 
 	    aor = RegisterCache::canonicalize_aor(from_parser.uri_str());
 
 	    map<string,string> alias_map;
 	    if(reg_cache->getAorAliasMap(aor, alias_map) && !alias_map.empty()) {
 
 	      bool is_registered = false;  
 	      for(map<string,string>::iterator it = alias_map.begin();
 		  it != alias_map.end(); it++) {
 
 		AliasEntry alias_entry;
 		if(reg_cache->findAliasEntry(it->first,alias_entry)) {
 		  if((alias_entry.source_ip == req.remote_ip) &&
 		     (alias_entry.source_port == req.remote_port)) {
 		    DBG("matching entry for alias '%s' found (src=%s:%i)",
 			it->first.c_str(), 
 			alias_entry.source_ip.c_str(),
 			alias_entry.source_port);
 		    is_registered = true;
 		    res += it->first;
 		    break;
 		  }
 		  // else {
 		  //   DBG("alias '%s': source IP/port mismatch: %s:%i != %s:%i",
 		  // 	it->first.c_str(),
 		  // 	alias_entry.source_ip.c_str(),
 		  // 	alias_entry.source_port,
 		  // 	context.invite_req->remote_ip.c_str(),
 		  // 	context.invite_req->remote_port);
 		  // }
 		}
 	      }
 	      if(is_registered)
 		break;
 	    }
 	    DBG("AoR '%s' is not registered",aor.c_str());
 	    break;
 	  }
d5aa12fc
 	  WARN("unknown replacement $U%c\n", s[p+1]);
 	} break;
19b43ec3
 
bb763d84
 #define case_HDR(pv_char, pv_name, hdr_name)				\
c60b508e
 	  case pv_char: {						\
 	    AmUriParser uri_parser;					\
1b7f1d75
 	    uri_parser.uri = getHeader(used_hdrs, hdr_name);		\
c60b508e
 	    if ((s.length() == p+1) || (s[p+1] == '.')) {		\
1b7f1d75
 	      res += uri_parser.uri;					\
c60b508e
 	      break;							\
 	    }								\
1b7f1d75
 									\
 	    if (!uri_parser.parse_uri()) {				\
 	      WARN("Error parsing " pv_name " URI '%s'\n", uri_parser.uri.c_str()); \
c60b508e
 	      break;							\
 	    }								\
 	    if (s[p+1] == 'i') {					\
 	      res+=uri_parser.uri_user+"@"+uri_parser.uri_host;		\
 	      if (!uri_parser.uri_port.empty())				\
 		res+=":"+uri_parser.uri_port;				\
 	    } else {							\
a5f4dd19
 	      skip_chars=replaceParsedParam(s, p, uri_parser, res);	\
c60b508e
 	    }								\
 	  }; break;
bb763d84
 
c60b508e
 	  case_HDR('a', "PAI", SIP_HDR_P_ASSERTED_IDENTITY);  // P-Asserted-Identity
 	  case_HDR('p', "PPI", SIP_HDR_P_PREFERRED_IDENTITY); // P-Preferred-Identity
bb763d84
 
c60b508e
 	case 'P': { // app-params
 	  if (s[p+1] != '(') {
 	    WARN("Error parsing P param replacement (missing '(')\n");
 	    break;
 	  }
 	  if (s.length()<p+3) {
 	    WARN("Error parsing P param replacement (short string)\n");
bb763d84
 	    break;
 	  }
 
c60b508e
 	  size_t skip_p = p+2;
 	  for (;skip_p<s.length() && s[skip_p] != ')';skip_p++) { }
 	  if (skip_p==s.length()) {
 	    WARN("Error parsing P param replacement (unclosed brackets)\n");
bb763d84
 	    break;
 	  }
c60b508e
 	  string param_name = s.substr(p+2, skip_p-p-2);
 	  // DBG("param_name = '%s' (skip-p - p = %d)\n", param_name.c_str(), skip_p-p);
 	  res += get_header_keyvalue(app_param, param_name);
 	  skip_chars = skip_p-p;
 	} break;
bb763d84
 
0dae3311
 	case 'V': { // variable
 	  if (s[p+1] != '(') {
 	    WARN("Error parsing V variable replacement (missing '(')\n");
 	    break;
 	  }
 	  if (s.length()<p+3) {
 	    WARN("Error parsing V param replacement (short string)\n");
 	    break;
 	  }
 
 	  size_t skip_p = p+2;
 	  for (;skip_p<s.length() && s[skip_p] != ')';skip_p++) { }
 	  if (skip_p==s.length()) {
 	    WARN("Error parsing V param replacement (unclosed brackets)\n");
 	    break;
 	  }
 	  string var_name = s.substr(p+2, skip_p-p-2);
 	  // DBG("param_name = '%s' (skip-p - p = %d)\n", param_name.c_str(), skip_p-p);
 	  if (!call_profile) {
 	    WARN("no call_profile object when replacing variable '%s'\n", var_name.c_str());
 	  } else {
 	    const AmArg* val = NULL;
 	    size_t dotpos = var_name.find('.');
 	    string vn;
 	    if (dotpos != string::npos) {
 	      vn = var_name.substr(dotpos+1);
 	      var_name = var_name.substr(0, dotpos);
 	    }
 	    SBCVarMapConstIteratorT it = call_profile->cc_vars.find(var_name);
 	    if (it != call_profile->cc_vars.end()) {
 	      if (vn.empty()) {
 		val = &it->second;
 	      } else {
 		if (isArgStruct(it->second)) {
 		  // recursive replacement for call variable name (defined via GUI)
 		  if (vn.find('$') != string::npos)
1b7f1d75
 		    vn = replaceParameters(vn, "CallVar Subname", req, 
 					   call_profile, app_param, 
 					   ruri_parser, from_parser, to_parser,
 					   rebuild_ruri, rebuild_from, rebuild_to);
0dae3311
 		  val = &it->second[vn];
 		} else {
 		  DBG("CC variable '%s' has wrong type: '%s'\n",
 		      vn.c_str(), AmArg::print(it->second).c_str());
 		}
 	      }
 	      if (val != NULL) {
 		if (val->getType() == AmArg::CStr)
 		  res += val->asCStr();
 		else
 		  res += AmArg::print(*val);
 	      }
 	    } else {
 	      DBG("CC variable '%s' does not exist\n", var_name.c_str());
 	    }
 	  }
 
 	  skip_chars = skip_p-p;
 	} break;
 
c60b508e
 	case 'H': { // header
 	  size_t name_offset = 2;
 	  if (s[p+1] != '(') {
 	    if (s[p+2] != '(') {
 	      WARN("Error parsing H header replacement (missing '(')\n");
 	      break;
 	    }
 	    name_offset = 3;
 	  }
 	  if (s.length()<name_offset+1) {
 	    WARN("Error parsing H header replacement (short string)\n");
bb763d84
 	    break;
 	  }
 
c60b508e
 	  size_t skip_p = p+name_offset;
 	  for (;skip_p<s.length() && s[skip_p] != ')';skip_p++) { }
 	  if (skip_p==s.length()) {
 	    WARN("Error parsing H header replacement (unclosed brackets)\n");
 	    break;
 	  }
 	  string hdr_name = s.substr(p+name_offset, skip_p-p-name_offset);
 	  // DBG("param_name = '%s' (skip-p - p = %d)\n", param_name.c_str(), skip_p-p);
 	  if (name_offset == 2) {
 	    // full header
1b7f1d75
 	    res += getHeader(used_hdrs, hdr_name);
c60b508e
 	  } else {
 	    // parse URI and use component
 	    AmUriParser uri_parser;
1b7f1d75
 	    uri_parser.uri = getHeader(used_hdrs, hdr_name);
c60b508e
 	    if ((s[p+1] == '.')) {
1b7f1d75
 	      res += uri_parser.uri;
c60b508e
 	      break;
 	    }
 
1b7f1d75
 	    if (!uri_parser.parse_uri()) {
c60b508e
 	      WARN("Error parsing header %s URI '%s'\n",
1b7f1d75
 		   hdr_name.c_str(), uri_parser.uri.c_str());
c60b508e
 	      break;
 	    }
a5f4dd19
 	    //TODO: find out how to correct skip_chars correctly
 	    replaceParsedParam(s, p, uri_parser, res);
c60b508e
 	  }
 	  skip_chars = skip_p-p;
 	} break;
bb763d84
 
c60b508e
 	case 'M': { // regex map
 	  if (s[p+1] != '(') {
 	    WARN("Error parsing $M regex map replacement (missing '(')\n");
 	    break;
 	  }
 	  if (s.length()<p+3) {
 	    WARN("Error parsing $M regex map replacement (short string)\n");
 	    break;
 	  }
 
 	  size_t skip_p = p+2;
 	  skip_p = skip_to_end_of_brackets(s, skip_p);
 
 	  if (skip_p==s.length()) {
 	    WARN("Error parsing $M regex map replacement (unclosed brackets)\n");
 	    skip_chars = skip_p-p;
 	    break;
 	  }
 
 	  string map_str = s.substr(p+2, skip_p-p-2);
 	  size_t spos = map_str.rfind("=>");
 	  if (spos == string::npos) {
 	    skip_chars = skip_p-p;
 	    WARN("Error parsing $M regex map replacement: no => found in '%s'\n",
 		 map_str.c_str());
 	    break;
 	  }
 
 	  string map_val = map_str.substr(0, spos);
1b7f1d75
 	  string map_val_replaced = 
 	    replaceParameters(map_val, r_type, req, 
 			      call_profile, app_param,
 			      ruri_parser, from_parser, to_parser,
 			      rebuild_ruri, rebuild_from, rebuild_to);
 
c60b508e
 	  string mapping_name = map_str.substr(spos+2);
 
 	  string map_res; 
51b27fb9
 	  if (SBCFactory::instance()->regex_mappings.
c60b508e
 	      mapRegex(mapping_name, map_val_replaced.c_str(), map_res)) {
 	    DBG("matched regex mapping '%s' (orig '%s) in '%s'\n",
 		map_val_replaced.c_str(), map_val.c_str(), mapping_name.c_str());
 	    res+=map_res;
 	  } else {
 	    DBG("no match in regex mapping '%s' (orig '%s') in '%s'\n",
 		map_val_replaced.c_str(), map_val.c_str(), mapping_name.c_str());
 	  }
  
 	  skip_chars = skip_p-p;
 	} break;
 
6d247332
 	case '_': { // modify
 	  if (s.length()<p+4) { // $_O()
 	    WARN("Error parsing $_ modifier replacement (short string)\n");
 	    break;
 	  }
 
 	  char operation = s[p+1];
8951df37
 	  if (operation != 'U' && operation != 'l'
 	      && operation != 's' && operation != '5') {
6d247332
 	    WARN("Error parsing $_%c string modifier: unknown operator '%c'\n",
 		 operation, operation);
 	  }
 
 	  if (s[p+2] != '(') {
 	    WARN("Error parsing $U upcase replacement (missing '(')\n");
 	    break;
 	  }
 
 	  size_t skip_p = p+3;
 	  skip_p = skip_to_end_of_brackets(s, skip_p);
 
 	  if (skip_p==s.length()) {
 	    WARN("Error parsing $_ modifier (unclosed brackets)\n");
 	    skip_chars = skip_p-p;
 	    break;
 	  }
 
 	  string br_str = s.substr(p+3, skip_p-p-3);
1b7f1d75
 	  string br_str_replaced = 
 	    replaceParameters(br_str, "$_*(...)",
 			      req, call_profile, app_param,
 			      ruri_parser, from_parser, to_parser,
 			      rebuild_ruri, rebuild_from, rebuild_to);
 
6d247332
 	  br_str = br_str_replaced;
 	  switch(operation) {
 	  case 'u': // uppercase
 	    transform(br_str_replaced.begin(), br_str_replaced.end(),
 		      br_str_replaced.begin(), ::toupper); break;
 	  case 'l': // lowercase
 	    transform(br_str_replaced.begin(), br_str_replaced.end(),
 		      br_str_replaced.begin(), ::tolower); break;
 
 	  case 's': // size (string length)
 	    br_str_replaced = int2str((unsigned int)br_str.length());
 	    break;
 
 	  case '5': // md5
 	    br_str_replaced = calculateMD5(br_str);
 	    break;
 
1b7f1d75
 	  case 't': // extract 'transport' (last 3 characters)
 	    if (br_str.length() >= 4) {
 	      br_str_replaced = br_str.substr(br_str.length()-3);
 	    }
 	    break;
 
 	  case 'r': // random
 	    {
 	      int r_max;
 	      if (!str2int(br_str, r_max)){
 		WARN("Error parsing $_r(%s) for random value, returning 0\n", br_str.c_str());
 		br_str_replaced = "0";
 	      } else {
 		br_str_replaced = int2str(rand()%r_max);
 	      }
 	    }
 	    break;
 
 	  default:
 	    WARN("Error parsing $_%c string modifier: unknown operator '%c'\n",
 		 operation, operation);
 	    break;
6d247332
 	  }
 	  DBG("applied operator '%c': '%s' => '%s'\n", operation,
 	      br_str.c_str(), br_str_replaced.c_str());
 	  res+=br_str_replaced;
 
 	  skip_chars = skip_p-p;
 	} break;
 
fb9177f8
 	case 'm': // Request method
 	  res += req.method;
 	  break;
 
 	case '#': { // URL encoding
 	  if (s[p+1] != '(') {
 	    WARN("Error parsing $# URL encoding (missing '(')\n");
 	    break;
 	  }
 	  if (s.length()<p+3) {
 	    WARN("Error parsing $# URL encoding (short string)\n");
 	    break;
 	  }
 
 	  size_t skip_p = p+2;
 	  skip_p = skip_to_end_of_brackets(s, skip_p);
 
 	  if (skip_p==s.length()) {
 	    WARN("Error parsing $# URL encoding (unclosed brackets)\n");
 	    skip_chars = skip_p-p;
 	    break;
 	  }
 
 	  string expr_str = s.substr(p+2, skip_p-p-2);
1b7f1d75
 	  string expr_replaced = 
 	    replaceParameters(expr_str, r_type, req,
 			      call_profile, app_param, 
 			      ruri_parser, from_parser, to_parser,
 			      rebuild_ruri, rebuild_from, rebuild_to);
 
fb9177f8
 	  char* val_escaped = url_encode(expr_replaced.c_str());
 	  res += string(val_escaped);
 	  free(val_escaped);
 
 	  skip_chars = skip_p-p;
 	} break;
 
c60b508e
 	default: {
 	  WARN("unknown replace pattern $%c%c\n",
 	       s[p], s[p+1]);
 	}; break;
 	};
 
 	p+=skip_chars; // skip $.X
       } else {
 	res += s[p];
       }
     } // end not escaped
bb763d84
 
     p++;
   }
 
   if (is_replaced) {
     DBG("%s pattern replace: '%s' -> '%s'\n", r_type, s.c_str(), res.c_str());
   }
   return res;
 }
fb9177f8
 
 
 //
 // URL encoding functions
 //
 // source code from http://www.geekhideout.com/urlcode.shtml
 //
 
 /* Converts a hex character to its integer value */
 char from_hex(char ch) {
   return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
 }
 
 /* Converts an integer value to its hex character*/
 char to_hex(char code) {
   static char hex[] = "0123456789abcdef";
   return hex[code & 15];
 }
 
 /* Returns a url-encoded version of str */
 /* IMPORTANT: be sure to free() the returned string after use */
 char *url_encode(const char *str) {
 
   const char* pstr = str;
   char* buf = (char*)malloc(strlen(str) * 3 + 1);
   char* pbuf = buf;
 
   while (*pstr) {
     if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || 
 	*pstr == '.' || *pstr == '~') 
       *pbuf++ = *pstr;
     else if (*pstr == ' ') 
       *pbuf++ = '+';
     else 
       *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
     pstr++;
   }
   *pbuf = '\0';
   return buf;
 }
 
 /* Returns a url-decoded version of str */
 /* IMPORTANT: be sure to free() the returned string after use */
 char *url_decode(char *str) {
   char *pstr = str, *buf = (char*)malloc(strlen(str) + 1), *pbuf = buf;
   while (*pstr) {
     if (*pstr == '%') {
       if (pstr[1] && pstr[2]) {
         *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
         pstr += 2;
       }
     } else if (*pstr == '+') { 
       *pbuf++ = ' ';
     } else {
       *pbuf++ = *pstr;
     }
     pstr++;
   }
   *pbuf = '\0';
   return buf;
 }