Erlang中的二进制字符串的棘手模式匹配

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

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

SpamAssassin回答(通过原始TCP)时,它将发送一个类似于以下内容的二进制字符串:

Erlang中的二进制字符串的棘手模式匹配

<<"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">>

我将要领取的物品加粗了:

  • BODY_SINGLE_WORD
  • DKIM_ADSP_NXDOMAIN
  • DOS_RCVD_IP_TWICE_C
  • HELO_MISC_IP
  • NO_FM_NAME_IP_HOSTN

然后我要像这样进行序列化: [>,>,…]

但这并不容易,术语没有常规的“定界符”,具有\ 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(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;

问题是我看不出如何以可接受和干净的方式实现这一目标

如果有人可以帮我,那将是非常非常感谢的。

您的

avagrace 回答:Erlang中的二进制字符串的棘手模式匹配

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

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)].

在Erlang shell中执行以上三行将返回:

[>,>,>,>,>]

这提供了理想的结果,但是由于它不做任何事情来尝试验证输入是否有效或匹配是否发生在有效边界上,因此可能并不安全。

一种可能更安全的方法取决于输入二进制类似于HTTP结果的事实,因此可以使用内置的Erlang解码器对其进行部分解析。下面的parse/1,2函数使用erlang:decode_packet/3从输入中提取信息:

parse(Data) ->
    {ok,Line,Rest} = erlang:decode_packet(line,Data,[]),parse(Line,Rest).
parse(<<"SPAMD/",_/binary>>,Data) ->
    parse(Data,[]);
parse(<<>>,Hdrs) ->
    Result = [{Key,Value} || {http_header,_,Key,Value} <- Hdrs],process_results(Result);
parse(Data,Hdrs) ->
    case erlang:decode_packet(httph,[]) of
        {ok,http_eoh,Rest} ->
            parse(Rest,Hdrs);
        {ok,Hdr,[Hdr|Hdrs]);
        Error ->
            Error
    end.

parse/1函数最初使用line解码器解码输入的第一行,并将结果传递到parse/2parse/2的第一子句与输入数据的初始行的"SPAMD/"前缀匹配,只是为了验证我们在正确的位置查找内容,然后递归调用parse/2并传递其余的{{ 1}}和一个空的累加器列表。 Data的第二和第三子句将数据视为HTTP标头。 parse/2的第二个子句在输入数据用尽时匹配;它将累积的标头列表映射到parse/2对的列表,并将其传递给下面描述的{Key,Value}函数,以完成数据提取。 process_results/1的第三子句尝试通过parse/2 HTTP标头解码器解码数据,累积每个匹配的标头,并忽略由{{1}产生的任何httph标头结尾标记}嵌入在输入中奇数处的序列。

对于问题中提供的输入数据,http_eoh函数最终将以下键值对列表传递给"\r\n"

parse/1,2

process_results/1函数首先匹配感兴趣的键[{'Content-Type',"multipart/mixed; boundary=\"----------=_5E10CA56.0200B819\""},{"Mime-Version","1.0"},{"X-Spam-Status","Yes,\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_results/1,2来查找该密钥并对其进行处理,如果看不到该密钥,则返回"X-Spam-Status"。第二个子句匹配所需的键,在空格,逗号,回车符,换行符,制表符和等号字符上拆分其关联值,并将拆分结果与空的累加器一起传递给process_results/1: >

{error,not_found}

对于问题中的输入数据,传递给process_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).

下面process_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","NO_FM_NAME_IP_HOSTN","autolearn","no","autolearn_force","version","3.4.2"]

final子句忽略不需要的数据。当字符串列表为空时,将调用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],[list_to_binary(V)|Results]); process_results([V="DOS_RCVD_IP_TWICE_C"|T],[list_to_binary(V)|Results]); process_results([V="HELO_MISC_IP"|T],[list_to_binary(V)|Results]); process_results([V="NO_FM_NAME_IP_HOSTN"|T],[list_to_binary(V)|Results]); process_results([_|T],Results). 的最终结果为:

{ok,[>,>,>,>,>]}} >

本文链接:https://www.f2er.com/2818578.html

大家都在问