提升C++正则表达式以匹配SIP标头
Boost C++ regular expression to match SIP header
我们有以下内容来匹配SIP Via标头。我们正在使用boost C++regex
Via: SIP/2.0/UDP 192.168.1.4:62486;rport;branch=z9hG4bK-524287-1---3ff9a622846c0a01;stck=3449406834;received=90.206.135.26
Regex:
std::regex g_Via;
g_Via("(^Via:\s+SIP/2\.0/UDP\s+)(((\w|\.|-)+):(\d+))((;\s*rport=(\d+))|(;stck=(\d+))|(;[^;\n\s]+)*)(\s*$)",std::regex_constants::icase)
std::match_results<std::string::const_iterator> result;
bool valid = std::regex_match(line, result, g_Via);
if(valid)
{
std::string rport = result[8].str();
std::string stckval = result[9].str();
// use these values
}
我们想要的是获取IP地址后的rpot、received和stck参数。我们可以使用上面的表达式来获取IP地址,但在获取单个参数时遇到问题。
rpot参数可以是:;rpot或;rpot=14838独立的或有价值的。
我们遇到的问题是参数,例如;分支=;received=可以在不同的位置
我不建议使用regex"解析"SIP头。
正如在评论中已经提到的,处理属性变得很难处理。此外,您会发现规范(rfc 2616/rfc 822)中有一些细微的细节,这些细节很难正确。
我之前已经使用Boost Spirit创建了一个SIP头解析器:
- 如何使用正则表达式解析SIP消息的多行标头
事实上,我已经直播了创建解析器的过程。以下是直播流的视频点播,以备您观看:第1部分、第2部分、第3部分和第4部分。
在这里使用解析器生成器的好处是,您最终不会得到原始匹配组,而是可以直接解析为对进一步处理有用的东西,例如
using Headers = std::map<std::string, std::string>;
在Coliru上直播
//#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <map>
using Headers = std::map<std::string, std::string>;
template <typename It> Headers parse_headers(It first, It last)
{
using namespace boost::spirit::qi;
auto& crlf = "rn";
auto& tspecials = " t><@,;:\"/][?=}{:";
rule<It, std::string()> token, value;
token = +~char_(tspecials); // FIXME? should filter CTLs
value = *(char_ - (crlf >> &(~blank | eoi)));
BOOST_SPIRIT_DEBUG_NODES((token)(value));
//value = *(omit[ crlf >> !(~blank | eoi) ] >> attr(' ') | (char_ - crlf));
Headers headers;
bool ok = phrase_parse(first, last, (token >> ':' >> value) % crlf >> omit[*lit(crlf)], blank, headers);
#ifdef DEBUG
if (ok) std::cerr << "DEBUG: Parse successn";
else std::cerr << "DEBUG: Parse failedn";
if (first!=last) std::cerr << "DEBUG: Remaining unparsed input: '" << std::string(first,last) << "'n";
#endif
if (ok && (first==last))
return headers;
throw std::runtime_error("Parse error in headersn"); // TODO FIXME
}
int main()
{
boost::spirit::istream_iterator iter(std::cin >> std::noskipws), end;
for (auto& header : parse_headers(iter, end)) {
std::cout << "Key: '" << header.first << "', Value: '" << header.second << "'n";
}
}
用于输入:
Via: SIP/2.0/UDP 10.10.1.99:5060;branch=z9hG4bK343bf628;rport
Contact: <sip:15@10.10.1.99>
Call-ID: 326371826c80e17e6cf6c29861eb2933@10.10.1.99
CSeq: 102 INVITE
User-Agent: Asterisk PBX
Max-Forwards: 70
Date: Wed, 06 Dec 2009 14:12:45 GMT
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY
Supported: replaces
Content-Type: application/sdp
Content-Length: 258
From: "Test 15" <sip:15@10.10.1.99>
; tag = fromtag
To: <sip:13@10.10.1.13>;tag=totag
它打印输出
Key: 'Allow', Value: 'INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY'
Key: 'CSeq', Value: '102 INVITE'
Key: 'Call-ID', Value: '326371826c80e17e6cf6c29861eb2933@10.10.1.99'
Key: 'Contact', Value: '<sip:15@10.10.1.99>'
Key: 'Content-Length', Value: '258'
Key: 'Content-Type', Value: 'application/sdp'
Key: 'Date', Value: 'Wed, 06 Dec 2009 14:12:45 GMT'
Key: 'From', Value: '"Test 15" <sip:15@10.10.1.99>
; tag = fromtag'
Key: 'Max-Forwards', Value: '70'
Key: 'Supported', Value: 'replaces'
Key: 'To', Value: '<sip:13@10.10.1.13>;tag=totag'
Key: 'User-Agent', Value: 'Asterisk PBX'
Key: 'Via', Value: 'SIP/2.0/UDP 10.10.1.99:5060;branch=z9hG4bK343bf628;rport'
根据您使用的语言,处理属性最好与正则表达式分开处理。您可以使用正则表达式提取每个属性(或者整个属性字符串——从第一个;
到最后的所有内容)。之后,可以使用;
作为分隔符来拆分属性字符串。例如,Python和PHP都有简单的函数来实现这一点(Python中的split()
和PHP中的explode()
)。然后,可以通过=
拆分每个属性,以将属性名称与属性值分开。
您使用了太多我无法跟踪的组,所以我删除了大部分。这将适用于您提到的任何一种语言。
(?<Named> groups)
在某些版本中可能不受支持,但您可以很容易地将其更改为普通的(group)
。我用它们是为了实用
Regex
^Via:s+SIP/2.0/UDPs+ # header
([-.w]+):(d+) # IP (group 1) and port (group 2)
(?: # ITERATE
(?<received>;received=[.d]+) # received (group "received")
| #
(?<rport>;rport # rport (group "rport")
(?:=(?<rportval>[0-9]+))? # with optional num (group "rportval")
) #
| #
(?<stck>;stck=d+) # stck (group "stck")
| #
;[^;ns=]+(?:=[^;]+)? # any other param (not captured)
)* # Repeat iteration *
s*$ # to EoL
一个衬垫:
^Via:s+SIP/2.0/UDPs+([-.w]+):(d+)(?:(?<received>;received=[.d]+)|(?<rport>;rport(?:=(?<rportval>[0-9]+))?)|(?<stck>;stck=d+)|;[^;ns=]+(?:=[^;]+)?)*s*$
代码
使用Boost.Regex:
#include <iostream>
#include <boost/regex.hpp>
using namespace std;
using namespace boost;
int main () {
string subject = "Via: SIP/2.0/UDP 192.168.1.4:62486;rport=12345;branch=z9hG4bK-524287-1---3ff9a622846c0a01;stck=3449406834;received=90.206.135.26";
string pattern = "^Via:\s+SIP/2\.0/UDP\s+([-.\w]+):([0-9]+)(?:(?<received>;received=[.0-9]+)|(?<rport>;rport(?:=(?<rportval>[0-9]+))?)|(?<stck>;stck=[0-9]+)|;[^;\n\s=]+(?:=[^;]*)?)*\s*$";
smatch match;
const regex re(pattern);
if (regex_search(subject, match, re)) {
string received = match["received"];
string rport = match["rport"];
string rportval = match["rportval"];
string stck = match["stck"];
cout << "rport = " << rport << endl << "rportval = " << rportval << endl;
} else {
cout << "NO MATCH" << endl;
}
return 0;
}
输出:
rport = ;rport=12345
rportval = 12345
rextester.com演示
- g++ 说函数不存在,即使包含正确的标头
- spdlog标头仅与外部fmt一起使用.spdlog错误:'内部':不是'fmt'
- 在CMake中使用find_package时,是否会显式包含标头
- 错误"Could not find Boost"(缺少:上下文标头)
- 如何在标头中声明(或定义)函数的问题
- 如何避免在仅标头库中C++类/变量重定义
- C++算法标头中,为什么要使用 "!(val < *first)" ?
- C++标头错误 C2238 意外标记";"
- std::initializer_list,大括号初始化和标头
- 在 c++ 中拆分类和标头中的继承,错误
- 如何确定哪个标头调用 c++ 中的另一个标头
- 如何在生成文件中添加多个标头 (HDR) 和对象?
- 我可以在运行时重新定义在 OpenCascade/OCCT 标头中定义的 c++ 静态常量吗?
- GCC,CMake,预编译标头和维护依赖项
- VisualStudio:使用 Suse Enterprise Server 12 SP5 时,不会下载远程库标头
- 一个标头库中的错误
- 可视化 Bazel C++预编译标头实现
- 如何在 CMake 中添加预编译标头用于 Visual Studio 生成器
- 使用 GN 构建预编译标头
- 提升C++正则表达式以匹配SIP标头