Erlang中二进制字符串的复杂模式匹配

Erlang中二进制字符串的复杂模式匹配,erlang,pattern-matching,binary-data,Erlang,Pattern Matching,Binary Data,我正在使用Erlang在电子邮件服务器和Spamassassin之间发送消息 我想要实现的是检索SA完成的测试以生成报告(我正在执行某种邮件测试程序) 当SpamAssassin应答时(通过原始TCP),它会发送一个二进制字符串,如下所示: > 我将要挑选的项目用粗体表示: 单字正文 DKIM_ADSP_NXDOMAIN DOS_RCVD_IP_两次 HELO_MISC_IP 没有FM名称IP主机 然后我想这样序列化: [,…] 但这并不容易,术语没有规则的“定界符”,有\r\n或\r\

我正在使用Erlang在电子邮件服务器和Spamassassin之间发送消息

我想要实现的是检索SA完成的测试以生成报告(我正在执行某种邮件测试程序)

当SpamAssassin应答时(通过原始TCP),它会发送一个二进制字符串,如下所示:

>
我将要挑选的项目用粗体表示:

  • 单字正文
  • DKIM_ADSP_NXDOMAIN
  • DOS_RCVD_IP_两次
  • HELO_MISC_IP
  • 没有FM名称IP主机
然后我想这样序列化: [,…]

但这并不容易,术语没有规则的“定界符”,有\r\n或\r\n\t

我从这个表达式开始(在一个二进制字符串上拆分“,”),但结果是不完整的

split(BinaryString, ",", all),
case lists:member(<<"HELO_MISC_IP">>, Data3 ) of
            true -> ; %push the result in a database
            false -> ok
end;
split(二进制字符串,,,all),
案例列表:成员(,数据3)
正确->;%将结果推送到数据库中
false->ok
结束;
我希望我可以从另一个角度出发,通过递归使用循环(因为这是一种干净而好的循环方式),但对于我来说,这种情况看起来毫无意义

split(BinaryString, Idx, Acc) ->
case BinaryString of
    <<"tests=",_This:Idx/binary, Char, Tail/binary>> ->
                case lists:member(Char, BinaryString ) of
                    false ->
                        split(BinaryString, Idx+1, Acc);
                    true -> 
                           case Tail of
                                    <<Y/binary, _Tail/binary>> ->
                                    %doing something
                                    <<_Yop2/binary>> ->
                                    %doing somethin else
                           end
                 end;
split(二进制字符串、Idx、Acc)->
的大小写二进制字符串
->
案例列表:的成员(Char、BinaryString)
错误->
拆分(二进制字符串,Idx+1,Acc);
正确->
箱尾
->
%做某事
->
%做别的事
结束
结束;
问题是,我不知道如何以一种可接受和干净的方式实现这一点

如果有人能帮我一把,那将是非常可观的


您的

一个解决方案是匹配您要查找的二进制文件的部分:

Data = <<"SPAMD/1.1 0 EX_OK\r\nContent-length: 728\r\nSpam: True ; 6.3 / 5.0\r\n\r\nReceived: from localhost by debpub1.cs2cloud.internal\r\n\twith SpamAssassin (version 3.4.2);\r\n\tSat, 04 Jan 2020 18:24:37 +0100\r\nFrom: bibi <bibi@XXXXX.local>\r\nTo: <aZphki8N05@XXXXXXXX>\r\nSubject: i\r\nDate: Sat, 4 Jan 2020 18:24:36 +0100\r\nMessage-Id: <3b68dede-f1c3-4f04-62dc-f0b2de6e980a@PPPPPP.local>\r\nX-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on\r\n\tdebpub1.cs2cloud.internal\r\nX-Spam-Flag: YES\r\nX-Spam-Level: ******\r\nX-Spam-Status: Yes, score=6.3 required=5.0 tests=BODY_SINGLE_WORD,\r\n\tDKIM_ADSP_NXDOMAIN,DOS_RCVD_IP_TWICE_C,HELO_MISC_IP,\r\n\tNO_FM_NAME_IP_HOSTN autolearn=no autolearn_force=no version=3.4.2\r\nMIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary=\"----------=_5E10CA56.0200B819\"\r\n\r\n">>,
Matches = binary:compile_pattern([<<"BODY_SINGLE_WORD">>,<<"DKIM_ADSP_NXDOMAIN">>,<<"DOS_RCVD_IP_TWICE_C">>,<<"HELO_MISC_IP">>,<<"NO_FM_NAME_IP_HOSTN">>]),
[binary:part(Data, PosLen) || PosLen <- binary:matches(Data, Matches)].
parse/1
函数最初使用
line
解码器对输入的第一行进行解码,并将结果传递给
parse/2
parse/2
的第一个子句匹配输入数据初始行的
“SPAMD/”
前缀,只是为了验证我们查找的位置是否正确,然后递归调用
parse/2
传递剩余的
数据和空的累加器列表。
parse/2
的第二和第三个子句将数据视为HTTP头。
parse/2
的第二个子句在输入数据耗尽时匹配;它将累积的头列表映射到
{Key,Value}
对的列表,并将其传递给
过程\结果/1
函数,如下所述,以完成数据提取。
parse/2
的第三个子句尝试通过
httph
HTTP报头解码器解码数据,累加每个匹配的报头,并忽略由嵌入在输入中奇数处的
“\r\n”
序列产生的任何
HTTP\u eoh
报头结束标记

对于问题中提供的输入数据,
parse/1,2
函数最终将以下键值对列表传递给
process\u results/1

[{'Content-Type',"multipart/mixed; boundary=\"----------=_5E10CA56.0200B819\""},{"Mime-Version","1.0"},{"X-Spam-Status","Yes, score=6.3 required=5.0 tests=BODY_SINGLE_WORD,\r\n\tDKIM_ADSP_NXDOMAIN,DOS_RCVD_IP_TWICE_C,HELO_MISC_IP,\r\n\tNO_FM_NAME_IP_HOSTN autolearn=no autolearn_force=no version=3.4.2"},{"X-Spam-Level","******"},{"X-Spam-Flag","YES"},{"X-Spam-Checker-Version","SpamAssassin 3.4.2 (2018-09-13) on\r\n\tdebpub1.cs2cloud.internal"},{"Message-Id","<3b68dede-f1c3-4f04-62dc-f0b2de6e980a@PPPPPP.local>"},{'Date',"Sat, 4 Jan 2020 18:24:36 +0100"},{"Subject","i"},{"To","<aZphki8N05@XXXXXXXX>"},{'From',"bibi <bibi@XXXXX.local>"},{"Received","from localhost by debpub1.cs2cloud.internal\r\n\twith SpamAssassin (version 3.4.2);\r\n\tSat, 04 Jan 2020 18:24:37 +0100"},{"Spam","True ; 6.3 / 5.0"},{'Content-Length',"728"}]
对于问题中的输入数据,传递给
process\u results/2
的字符串列表为

["Yes","score","6.3","required","5.0","tests","BODY_SINGLE_WORD","\r\n","DKIM_ADSP_NXDOMAIN","DOS_RCVD_IP_TWICE_C","HELO_MISC_IP","\r\n","NO_FM_NAME_IP_HOSTN","autolearn","no","autolearn_force","no","version","3.4.2"]
下面的
process_results/2
子句递归遍历此字符串列表并累积匹配的结果。第二到第六个子句中的每一个都匹配我们寻找的一个值,并且每一个都在累加之前将匹配的字符串转换为二进制

process_results([], Results) ->
    {ok, lists:reverse(Results)};
process_results([V="BODY_SINGLE_WORD"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="DKIM_ADSP_NXDOMAIN"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="DOS_RCVD_IP_TWICE_C"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="HELO_MISC_IP"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="NO_FM_NAME_IP_HOSTN"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([_|T], Results) ->
    process_results(T, Results).
最后一个子句忽略不需要的数据。当字符串列表为空时,调用
process\u results/2
的第一个子句,它只返回反向累加器。对于问题中的输入数据,
process\u results/2
的最终结果是:

{好的,[,,,]}


@请将此答案标记为已解决您的问题。
process_results([]) ->
    {error, not_found};
process_results([{"X-Spam-Status", V}|_]) ->
    process_results(string:lexemes(V, " ,\r\n\t="), []);
process_results([_|T]) ->
    process_results(T).
["Yes","score","6.3","required","5.0","tests","BODY_SINGLE_WORD","\r\n","DKIM_ADSP_NXDOMAIN","DOS_RCVD_IP_TWICE_C","HELO_MISC_IP","\r\n","NO_FM_NAME_IP_HOSTN","autolearn","no","autolearn_force","no","version","3.4.2"]
process_results([], Results) ->
    {ok, lists:reverse(Results)};
process_results([V="BODY_SINGLE_WORD"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="DKIM_ADSP_NXDOMAIN"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="DOS_RCVD_IP_TWICE_C"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="HELO_MISC_IP"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([V="NO_FM_NAME_IP_HOSTN"|T], Results) ->
    process_results(T, [list_to_binary(V)|Results]);
process_results([_|T], Results) ->
    process_results(T, Results).