/* * Copyright (C) 2006 iptego GmbH * * 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. This program is released under * the GPL with the additional exemption that compiling, linking, * and/or using OpenSSL is allowed. * * 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 "AmUriParser.h" #include "log.h" // Not on Solaris! #if !defined (__SVR4) && !defined (__sun) #include #endif #include using namespace std; bool AmUriParser::isEqual(const AmUriParser& c) const { return (uri_user == c.uri_user) && (!strcasecmp(uri_host.c_str(), c.uri_host.c_str())) && (uri_port == c.uri_port); } /* * Skip display name part */ static inline int skip_name(const string& s, unsigned int pos) { size_t i; int last_wsp, quoted = 0; for(i = pos; i < s.length(); i++) { char c = s[i]; if (!quoted) { if ((c == ' ') || (c == '\t')) { last_wsp = i; } else { if (c == '<') { return i; } if (c == '\"') { quoted = 1; } } } else { if ((c == '\"') && (s[i-1] != '\\')) quoted = 0; } } if (quoted) { ERROR("skip_name(): Closing quote missing in name part of URI\n"); return -1; } return pos; // no name to skip } #define ST1 1 /* Basic state */ #define ST2 2 /* Quoted */ #define ST3 3 /* Angle quoted */ #define ST4 4 /* Angle quoted and quoted */ #define ST5 5 /* Escape in quoted */ #define ST6 6 /* Escape in angle quoted and quoted */ /* * Skip URI, stops when , (next contact) * or ; (parameter) is found */ static inline int skip_uri(const string& s, unsigned int pos) { unsigned int len = s.length() - pos; unsigned int p = pos; register int st = ST1; while(len) { switch(s[p]) { case ',': case ';': if (st == ST1) return p; break; case '\"': switch(st) { case ST1: st = ST2; break; case ST2: st = ST1; break; case ST3: st = ST4; break; case ST4: st = ST3; break; case ST5: st = ST2; break; case ST6: st = ST4; break; } break; case '<': switch(st) { case ST1: st = ST3; break; case ST3: DBG("ERROR skip_uri(): Second < found\n"); return -1; case ST5: st = ST2; break; case ST6: st = ST4; break; } break; case '>': switch(st) { case ST1: DBG("ERROR skip_uri(): > is first\n"); return -2; case ST3: st = ST1; break; case ST5: st = ST2; break; case ST6: st = ST4; break; } break; case '\\': switch(st) { case ST2: st = ST5; break; case ST4: st = ST6; break; case ST5: st = ST2; break; case ST6: st = ST4; break; } break; default: break; } p++; len--; } if (st != ST1) { DBG("ERROR skip_uri(): < or \" not closed\n"); return -3; } return p; } enum { uS0= 0, // start uSPROT, // protocol uSUSER, // user uSHOST, // host uSHOSTWSP, // wsp after host uSPORT, // port uSPORTWSP, // wsp after port uSHDR, // header uSHDRWSP, // wsp after header uSPARAM, // params uSPARAMWSP, // wsp after params uS6 // end }; /** * parse uri into user, host, port, param * */ bool AmUriParser::parse_uri() { // assuming user@host size_t pos; int st = uS0; size_t p1 = 0; int eq = 0; const char* sip_prot = "SIP:"; uri_user = ""; uri_host = ""; uri_port = ""; uri_param = ""; if (uri.empty()) return false; pos = skip_name(uri, 0); while (pos': { uri_host = uri.substr(p1+1, pos-p1-1); st = uS6; p1 = pos; }; break; }; } break; case uSHOST: { switch (c) { case ':': { uri_host = uri.substr(p1+1, pos-p1-1); st = uSPORT; p1 = pos; } break; case '?': { uri_host = uri.substr(p1+1, pos-p1-1); st = uSHDR; p1 = pos; } break; case ';': { uri_host = uri.substr(p1+1, pos-p1-1); st = uSPARAM; p1 = pos; } break; case '>': { uri_host = uri.substr(p1+1, pos-p1-1); st = uS6; p1 = pos; } break; case ' ': case '\t': { uri_host = uri.substr(p1+1, pos-p1-1); st = uSHOSTWSP; p1 = pos; } break; }; } break; case uSHOSTWSP: { switch (c) { case ':': { st = uSPORT; p1 = pos; } break; case '?': { st = uSHDR; p1 = pos; } break; case ';': { st = uSPARAM; p1 = pos; } break; case '>': { st = uS6; p1 = pos; } break; } }; break; case uSPORT: { switch (c) { case ';': { uri_port = uri.substr(p1+1, pos-p1-1); st = uSPARAM; p1 = pos; } break; case '?': { uri_port = uri.substr(p1+1, pos-p1-1); st = uSHDR; p1 = pos; } break; case '>': { uri_port = uri.substr(p1+1, pos-p1-1); st = uS6; p1 = pos; } break; case ' ': case '\t':{ uri_port = uri.substr(p1+1, pos-p1-1); st = uSPORTWSP; p1 = pos; } break; }; } break; case uSPORTWSP: { switch (c) { case '?': { st = uSHDR; p1 = pos; } break; case ';': { st = uSPARAM; p1 = pos; } break; case '>': { st = uS6; p1 = pos; } break; }; } break; case uSHDR: { switch (c) { case '>': { uri_headers = uri.substr(p1+1, pos-p1-1); st = uS6; p1 = pos; } break; case '\t': case ' ': { uri_headers = uri.substr(p1+1, pos-p1-1); st = uSHDRWSP; p1 = pos; } break; } } break; case uSHDRWSP: { switch (c) { case ';': { st = uSPARAM; p1 = pos; }; break; case '>': { st = uS6; p1 = pos; } break; } } break; case uSPARAM: { switch (c) { case '>': { uri_param = uri.substr(p1+1, pos-p1-1); st = uS6; p1 = pos; } break; case '?': { uri_param = uri.substr(p1+1, pos-p1-1); st = uSHDR; p1 = pos; } break; case '\t': case ' ': { uri_param = uri.substr(p1+1, pos-p1-1); st = uSPARAMWSP; p1 = pos; } break; }; } break; case uSPARAMWSP: { switch (c) { case '>': { st = uS6; p1 = pos; } break; }; } break; }; // DBG("(2) c = %c, st = %d\n", c, st); pos++; } switch(st) { case uSUSER: case uSHOST: uri_host = uri.substr(p1+1, pos-p1-1); break; case uSPORT: uri_port = uri.substr(p1+1, pos-p1-1); break; case uSHDR: uri_headers = uri.substr(p1+1, pos-p1-1); break; case uSPARAM: uri_param = uri.substr(p1+1, pos-p1-1); break; case uS0: case uSPROT: { DBG("ERROR while parsing uri\n"); return false; } break; }; return true; } static bool nocase_equals(const string &a, const string &b) { if (a.length() != b.length()) return false; return strcasecmp(a.c_str(), b.c_str()) == 0; } static void add_param(map ¶ms, const string &name, const string &value) { // convert known parameter names to lower // (we can not use case insensitive map because parameters can be defined as // case sensitive (RFC 3261: "Unless otherwise stated in the definition of // a particular header field, field values, parameter names, and parameter // values are case-insensitive.)" static const string expires("expires"); if (nocase_equals(name, expires)) { params[expires] = value; return; } params[name] = value; } #define pS0 0 // start #define pS1 1 // name #define pS2 2 // val /** * parse params in param map * */ bool AmUriParser::parse_params(const string& line, int& pos) { size_t p1=pos, p2=pos; int st = 0; int quoted = false; char last_c = ' '; bool hit_comma = false; params.clear(); while((size_t)pos < line.length()) { char c = line[pos]; if (!quoted) { if (c == ',') { hit_comma = true; break; } if (c == '\"') { quoted = 1; } else if (c == '=') { p2 = pos; st = pS2; } else if (c == ';') { if (st == pS1) { add_param(params, line.substr(p1, pos-p1), ""); st = pS0; p1 = pos; } else if (st == pS2) { add_param(params, line.substr(p1, p2-p1), line.substr(p2+1, pos-p2-1)); st = pS0; p1 = pos; } } else { if (st == pS0) { st = pS1; p1 = pos; } } } else { if ((c == '\"') && (last_c != '\\')) quoted = 0; } last_c = c; pos++; } if (st == pS2) { if (hit_comma) add_param(params, line.substr(p1, p2-p1), line.substr(p2+1, pos-p2 -1)); else add_param(params, line.substr(p1, p2-p1), line.substr(p2+1, pos-p2)); } else if (st == pS1) { add_param(params, line.substr(p1, pos-p1), ""); } return true; } bool AmUriParser::parse_nameaddr(const string& line) { size_t pos=0; size_t end=0; return parse_contact(line, pos, end); } bool AmUriParser::parse_contact(const string& line, size_t pos, size_t& end) { int p0 = skip_name(line, pos); if (p0 < 0) { return false; } if ((size_t)p0 > pos) { // save display name size_t dn_b = pos; size_t dn_e = p0; while (dn_b < line.size() && line[dn_b] == ' ') { dn_b++; } // skip leading WS while (dn_e > 0 && line[dn_e-1] == ' ') dn_e--; // skip trailing WS if (dn_e > dn_b) { if (line[dn_e-1] == '"' && line[dn_b] == '"') { dn_b++; dn_e--; // skip quotes } display_name = line.substr(dn_b, dn_e - dn_b); } } int p1 = skip_uri(line, p0); if (p1 < 0) { return false; } // if (p1 < 0) return false; uri = line.substr(p0, p1-p0); if (!parse_uri()) { return false; } parse_params(line, p1); end = p1; return true; } string AmUriParser::add_param_to_param_list(const string& param_name, const string& param_value, const string& param_list) { string list_of_params(param_list); string param = param_name; if (!param_value.empty()) param += "=" + param_value; // if param_string empty - set it if (list_of_params.empty()) { list_of_params = param; } else { // check if parameter already exists; if yes - replace it size_t start = 0, end = 0, eq = 0, length = 0; bool replaced = false; do { // get next param end = list_of_params.find_first_of(';', start); length = (end == string::npos) ? list_of_params.size() - start : end - start; // it the param empty? eq = list_of_params.substr(start, length).find('='); if (eq != string::npos) { // non-empty param found if (list_of_params.substr(start, eq) == param_name) { list_of_params.replace(start, length, param); replaced = true; break; } } else { // empty param found if (list_of_params.substr(start, length) == param_name) { list_of_params.replace(start, length, param); replaced = true; break; } } start = end + 1; } while (end != string::npos && start != string::npos); // if parameter doesn't exist - append it if (!replaced) list_of_params.append(";" + param); } return list_of_params; } void AmUriParser::dump() const { DBG("--- Uri Info --- \n"); DBG(" uri '%s'\n", uri.c_str()); DBG(" display_name '%s'\n", display_name.c_str()); DBG(" uri_user '%s'\n", uri_user.c_str()); DBG(" uri_host '%s'\n", uri_host.c_str()); DBG(" uri_port '%s'\n", uri_port.c_str()); DBG(" uri_hdr '%s'\n", uri_headers.c_str()); DBG(" uri_param '%s'\n", uri_param.c_str()); for (map::const_iterator it = params.begin(); it != params.end(); it++) { if (it->second.empty()) DBG(" param '%s'\n", it->first.c_str()); else DBG(" param '%s'='%s'\n", it->first.c_str(), it->second.c_str()); } DBG("-------------------- \n"); } string AmUriParser::uri_str() const { string res = canon_uri_str(); if(!uri_param.empty()) { res += ";" + uri_param; } if (!uri_headers.empty()) { res+="?" + uri_headers; } return res; } string AmUriParser::canon_uri_str() const { string res = "sip:"; // fixme: always SIP... if(!uri_user.empty()) { res += uri_user + "@"; } res += uri_host; if(!uri_port.empty()) { res += ":" + uri_port; } return res; } string AmUriParser::nameaddr_str() const { string res = "<" + uri_str() + ">"; if(!display_name.empty()) res = "\"" + display_name + "\" " + res; for (map::const_iterator it = params.begin(); it != params.end(); it++) { res += ";"+it->first; if (!it->second.empty()) res += "="+it->second; } return res; } string AmUriParser::print() { return nameaddr_str(); }