Coverage for presidio_analyzer / predefined_recognizers / generic / mac_recognizer.py: 94%

17 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-29 09:03 +0000

1import re 

2from typing import List, Optional 

3 

4from presidio_analyzer import Pattern, PatternRecognizer 

5 

6 

7class MacAddressRecognizer(PatternRecognizer): 

8 """ 

9 Recognize MAC (Media Access Control) address using regex. 

10 

11 Supports three common MAC address formats: 

12 - Colon-separated: 00:1A:2B:3C:4D:5E 

13 - Hyphen-separated: 00-1A-2B-3C-4D-5E 

14 - Cisco format (dot-separated groups of 4): 0012.3456.789A 

15 

16 ref : 

17 - https://en.wikipedia.org/wiki/MAC_address#Notational_conventions 

18 - https://www.ieee802.org/1/files/public/docs2020/yangsters-smansfield-mac-address-format-0420-v01.pdf 

19 """ 

20 

21 PATTERNS = [ 

22 Pattern( 

23 "MAC_COLON_OR_HYPHEN", 

24 r"\b[0-9A-Fa-f]{2}([:-])(?:[0-9A-Fa-f]{2}\1){4}[0-9A-Fa-f]{2}\b", 

25 0.6, 

26 ), 

27 Pattern( 

28 "MAC_CISCO_DOT", 

29 r"\b[0-9A-Fa-f]{4}\.[0-9A-Fa-f]{4}\.[0-9A-Fa-f]{4}\b", 

30 0.6, 

31 ), 

32 ] 

33 

34 CONTEXT = ["mac", "mac address", "hardware address", "physical address", "ethernet"] 

35 

36 def __init__( 

37 self, 

38 patterns: Optional[List[Pattern]] = None, 

39 context: Optional[List[str]] = None, 

40 supported_language: str = "en", 

41 supported_entity: str = "MAC_ADDRESS", 

42 name: Optional[str] = None, 

43 ): 

44 patterns = patterns if patterns else self.PATTERNS 

45 context = context if context else self.CONTEXT 

46 super().__init__( 

47 supported_entity=supported_entity, 

48 patterns=patterns, 

49 context=context, 

50 supported_language=supported_language, 

51 name=name 

52 ) 

53 

54 def invalidate_result(self, pattern_text: str) -> bool: 

55 """ 

56 Check if the pattern text is a valid MAC address format. 

57 

58 :param pattern_text: Text detected as pattern by regex 

59 :return: True if invalidated (invalid MAC address) 

60 """ 

61 # Remove separators and validate hex characters and length 

62 cleaned = re.sub(r'[:\-.]', '', pattern_text) 

63 

64 # All characters must be valid hex 

65 if re.fullmatch(r"[0-9A-Fa-f]{12}", cleaned) is None: 

66 return True 

67 

68 # Optionally reject broadcast/multicast addresses if needed 

69 # Broadcast: FF:FF:FF:FF:FF:FF 

70 if cleaned.upper() == 'FFFFFFFFFFFF' or cleaned.upper() == '000000000000': 

71 return True 

72 

73 return False