Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/binary.py: 55%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

83 statements  

1"""BINARY values from :rfc:`5545`.""" 

2 

3import base64 

4import binascii 

5from typing import ClassVar 

6from xml.etree.ElementTree import Element, SubElement 

7 

8from icalendar.compatibility import Self, deprecate_for_version_8 

9from icalendar.error import JCalParsingError, XCalParsingError 

10from icalendar.parser import Parameters 

11from icalendar.parser.xcal.base import XCalParser 

12from icalendar.parser.xcal.wrapper import from_xcal_wrapper 

13from icalendar.parser_tools import to_unicode 

14 

15 

16class vBinary: 

17 """Binary property values are Base64 encoded.""" 

18 

19 default_value: ClassVar[str] = "BINARY" 

20 params: Parameters 

21 bytes: bytes 

22 """The raw binary value of the BINARY property. 

23 

24 This is the authoritative storage and round-trips losslessly, including 

25 for non-UTF-8 data. Use this instead of the deprecated :attr:`obj`. 

26 """ 

27 

28 def __init__(self, obj: str | bytes, params: dict[str, str] | None = None) -> None: 

29 if isinstance(obj, str): 

30 self.bytes = obj.encode("utf-8") 

31 else: 

32 self.bytes = obj 

33 self.params = Parameters(encoding="BASE64", value="BINARY") 

34 if params: 

35 self.params.update(params) 

36 

37 def __repr__(self) -> str: 

38 return f"vBinary({self.to_ical()})" 

39 

40 def to_ical(self) -> bytes: 

41 return base64.b64encode(self.bytes) 

42 

43 @staticmethod 

44 def from_ical(ical: str | bytes) -> bytes: 

45 try: 

46 return base64.b64decode(ical, validate=True) 

47 except (binascii.Error, ValueError) as e: 

48 raise ValueError("Not valid base 64 encoding.") from e 

49 

50 @property 

51 def base64data(self) -> str: 

52 """The Base64-encoded string view of this value. 

53 

54 This is the same string that :meth:`to_ical` produces, exposed as 

55 a plain :class:`str` instead of :class:`bytes` so you don't need to 

56 call :func:`base64.b64encode` yourself. See :issue:`1550`. 

57 

58 Returns: 

59 The Base64-encoded representation of the stored value. 

60 """ 

61 return self.to_ical().decode("ascii") 

62 

63 @base64data.setter 

64 def base64data(self, value: str) -> None: 

65 """Set this value from a Base64-encoded string. 

66 

67 The decoded raw bytes are stored in :attr:`bytes`, so non-UTF-8 

68 payloads round-trip losslessly. 

69 

70 Parameters: 

71 value: A Base64-encoded string. 

72 

73 Raises: 

74 ValueError: If ``value`` isn't valid Base64. 

75 """ 

76 self.bytes = self.from_ical(value) 

77 

78 @property 

79 @deprecate_for_version_8 

80 def obj(self) -> str: 

81 """Deprecated string view of the value. 

82 

83 .. deprecated:: 7.1.3 

84 Use :attr:`bytes` for the raw binary value. ``obj`` decodes 

85 :attr:`bytes` as text and is lossy for non-UTF-8 data. It will 

86 be removed in icalendar 8. 

87 """ 

88 return to_unicode(self.bytes) 

89 

90 @obj.setter 

91 @deprecate_for_version_8 

92 def obj(self, value: str | bytes) -> None: 

93 self.bytes = value.encode("utf-8") if isinstance(value, str) else value 

94 

95 def __eq__(self, other: object) -> bool: 

96 """self == other""" 

97 return isinstance(other, vBinary) and self.bytes == other.bytes 

98 

99 def __hash__(self) -> int: 

100 """Hash of the vBinary object.""" 

101 return hash(self.bytes) 

102 

103 @classmethod 

104 def examples(cls) -> list[Self]: 

105 """Examples of vBinary.""" 

106 return [cls("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4")] 

107 

108 from icalendar.param import VALUE 

109 

110 def to_jcal(self, name: str) -> list: 

111 """The jCal representation of this property according to :rfc:`7265`.""" 

112 params = self.params.to_jcal() 

113 if params.get("encoding") == "BASE64": 

114 # BASE64 is the only allowed encoding 

115 del params["encoding"] 

116 return [name, params, self.VALUE.lower(), self.base64data] 

117 

118 @property 

119 def ical_value(self) -> bytes: 

120 """The raw ``bytes`` value of the BINARY property. 

121 

122 .. versionadded:: 7.1.0 

123 

124 .. versionchanged:: 7.1.3 

125 Returns the raw stored bytes. Previously the stored value was 

126 Base64-decoded, which raised :class:`ValueError` for non-Base64 

127 input. See :pr:`1356`. 

128 """ 

129 return self.bytes 

130 

131 @classmethod 

132 def from_jcal(cls, jcal_property: list) -> Self: 

133 """Parse jCal from :rfc:`7265` to a vBinary. 

134 

135 Parameters: 

136 jcal_property: The jCal property to parse. 

137 

138 Raises: 

139 ~error.JCalParsingError: If the provided jCal is invalid. 

140 """ 

141 JCalParsingError.validate_property(jcal_property, cls) 

142 JCalParsingError.validate_value_type(jcal_property[3], str, cls, 3) 

143 return cls( 

144 cls.from_ical(jcal_property[3]), 

145 params=Parameters.from_jcal_property(jcal_property), 

146 ) 

147 

148 def to_xcal(self, element: Element): 

149 """The xCal representation of this property according to :rfc:`6321`.""" 

150 self.params.to_xcal(element) 

151 xml = SubElement(element, self.default_value.lower()) 

152 xml.text = self.base64data 

153 

154 @classmethod 

155 @from_xcal_wrapper 

156 def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self: 

157 """Parse xCal from :rfc:`6321`. 

158 

159 Parameters: 

160 xml: The XML to parse or a parser. 

161 

162 Raises: 

163 ~error.XCalParsingError: If the provided xCal is invalid. 

164 """ 

165 element = parser.parse_tag(cls.default_value) 

166 text = element.get_text_without_whitespace() 

167 try: 

168 binary = base64.b64decode(text, validate=True) 

169 except binascii.Error as e: 

170 raise XCalParsingError( 

171 "Expected base64 encoded data", 

172 None, # do not print potentially huge chunks of data 

173 element, 

174 ) from e 

175 return cls(binary, params=params) 

176 

177 

178__all__ = ["vBinary"]