URL处理中的Python正则表达式灾难性回溯

时间:2017-12-18 20:52:56

标签: python regex url backtracking

我想写一个正则表达式来捕获文本中的URL。现在,问题在于我用于捕获URL的任何正常的正则表达式,遇到一些带有某些URL的灾难性回溯

我在here尝试了“diegoperini”正则表达式,并且还在hereherehere中阅读了其他问题和答案。但是他们都没有解决我的问题。

我还有其他三个正则表达式:

Regex:
SIMPLE_URL_REGEX = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
WEB_URL_REGEX = r"""(?i)\b((?:https?:(?:/{1,3}|[a-z0-9%])|[a-z0-9.\-]+[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)/)(?:[^\s()<>{}\[\]]+|\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\))+(?:\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])|(?:(?<!@)[a-z0-9]+(?:[.\-][a-z0-9]+)*[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\b/?(?!@)))"""
ANY_URL_REGEX = r"""(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'\".,<>?«»“”‘’]))"""

简单URL正则表达式不会在我尝试的情况下被困,但也表现不佳并且错过了许多URL,其他两个表现更好但在某些情况下会被困。

现在,我的问题的一部分是非ASCII URL的编码,显然是通过解码文本来解决的:

try:
    meta = unquote(meta.encode('utf-8')).decode('utf-8')
except TypeError:
    meta = unquote(meta)

但是后来又出现了另一个有问题的网址。像这样的东西:

https://www.example.net/ar/book//%DA%A9-%D8%B3-(%D9%81-%DB%8C-%DB%8C-%DB%8C-%DB%8C-%DB%8C-%DB%8C-%D9%85)

这种情况很少见,但一旦发生,就会引发效率非常低的回溯。这种回溯导致程序无限期地停止响应。 (正如我在here中所读到的,问题是正则表达式模块不会释放GIL。)

考虑到所有这些信息,我有两个问题

  • 首先,是否有可能存在/是否存在正则表达式模式,用于匹配执行合理避免灾难性回溯完全

  • 第二,如果没有这样的正则表达式,是否还有另一种方法可以捕获正则表达式被捕获的情况并抛出异常或以其他方式绕过它?

2 个答案:

答案 0 :(得分:0)

这个使用\x{XXXX}符号表示Unicode字符,替换Java使用的任何内容。

此外,最大的问题是边界,围绕 URL。

下面的一个使用空白边界,但你可以删除它并试试运气。

"(?i)(?<!\\S)(?!mailto:)(?:[a-z]*:\\/\\/)?(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\x{a1}-\\x{ffff}0-9]+-?)*[a-z\\x{a1}-\\x{ffff}0-9]+)(?:\\.(?:[a-z\\x{a1}-\\x{ffff}0-9]+-?)*[a-z\\x{a1}-\\x{ffff}0-9]+)*(?:\\.(?:[a-z\\x{a1}-\\x{ffff}]{2,})))|localhost)(?::\\d{2,5})?(?:\\/[^\\s]*)?(?!\\S)"

格式化

 (?i)
 (?<! \S )
 (?! mailto: )
 (?:
      [a-z]* :
      \/\/
 )?
 (?:
      \S+ 
      (?: : \S* )?
      @
 )?
 (?:
      (?:
           (?:
                [1-9] \d? 
             |  1 \d\d 
             |  2 [01] \d 
             |  22 [0-3] 
           )
           (?:
                \.
                (?: 1? \d{1,2} | 2 [0-4] \d | 25 [0-5] )
           ){2}
           (?:
                \.
                (?:
                     [1-9] \d? 
                  |  1 \d\d 
                  |  2 [0-4] \d 
                  |  25 [0-4] 
                )
           )
        |  (?:
                (?: [a-z\x{a1}-\x{ffff}0-9]+ -? )*
                [a-z\x{a1}-\x{ffff}0-9]+ 
           )
           (?:
                \.
                (?: [a-z\x{a1}-\x{ffff}0-9]+ -? )*
                [a-z\x{a1}-\x{ffff}0-9]+ 
           )*
           (?:
                \.
                (?: [a-z\x{a1}-\x{ffff}]{2,} )
           )
      )
   |  localhost
 )
 (?: : \d{2,5} )?
 (?: \/ [^\s]* )?
 (?! \S )

答案 1 :(得分:0)

经过广泛搜索,我找到了解决问题的半解决方案。

此解决方案不会更改有问题的正则表达式,但在正则表达式停留在回溯中时会使用超时错误来引发异常。

我添加了包 timeout-decorator ,并写了类似这样的内容:

from timeout_decorator import timeout, TimeoutError

@timeout(seconds=RE_TIMEOUT)
def match_regex_timeout(compiled_regex, replacer, data):
    return compiled_regex.sub(replacer, data)

功能的使用将是这样的:

import logging
logger = logging.getLogger(__name__)

url_match = re.compile(url_regex, flags=re.MULTILINE)
replacer = ' URL '

try:
    text = match_regex_timeout(url_match, replacer, text)
except TimeoutError:
    logging.error('REGEX TIMEOUT ERROR: can not parse URL')
    text = remove_big_tokens(text)

其中主要是尝试解析文本,如果在预期的时间内没有这样做,则会使用删除大的文本标记,这可能是有问题的URL。