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

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

79 statements  

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

2 

3import base64 

4import binascii 

5from typing import ClassVar 

6from urllib.parse import quote 

7from xml.etree.ElementTree import Element, SubElement 

8 

9from icalendar.compatibility import Self 

10from icalendar.error import JCalParsingError, XCalParsingError 

11from icalendar.parser import Parameters 

12from icalendar.parser.xcal.base import XCalParser 

13from icalendar.parser.xcal.wrapper import from_xcal_wrapper 

14 

15 

16class vBinary: 

17 """Binary property values are Base64 encoded. 

18 

19 .. versionremoved:: 0.0.0 

20 Removed ``obj`` attribute, use :attr:`bytes` to access the binary content. 

21 

22 .. seealso:: :ref:`vbinary-obj-removed`. 

23 """ 

24 

25 default_value: ClassVar[str] = "BINARY" 

26 params: Parameters 

27 bytes: bytes 

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

29 

30 This is the authoritative storage method, which round-trips losslessly, including 

31 for non-UTF-8 data. 

32 """ 

33 

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

35 if isinstance(obj, str): 

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

37 else: 

38 self.bytes = obj 

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

40 if params: 

41 self.params.update(params) 

42 

43 def __repr__(self) -> str: 

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

45 

46 def to_ical(self) -> bytes: 

47 return base64.b64encode(self.bytes) 

48 

49 @staticmethod 

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

51 try: 

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

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

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

55 

56 @property 

57 def base64data(self) -> str: 

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

59 

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

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

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

63 

64 Returns: 

65 The Base64-encoded representation of the stored value. 

66 """ 

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

68 

69 @base64data.setter 

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

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

72 

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

74 payloads round-trip losslessly. 

75 

76 Parameters: 

77 value: A Base64-encoded string. 

78 

79 Raises: 

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

81 """ 

82 self.bytes = self.from_ical(value) 

83 

84 @property 

85 def uri(self) -> str: 

86 """Return this value as an RFC 2397 base64 data URI. 

87 

88 The media type comes from the current ``FMTTYPE`` parameter, or 

89 defaults to ``application/octet-stream`` when it is not set. See 

90 :rfc:`2397` for the data URI scheme. 

91 

92 Examples: 

93 >>> from icalendar import vBinary 

94 >>> vBinary(b"hello", params={"FMTTYPE": "text/plain"}).uri 

95 'data:text/plain;base64,aGVsbG8=' 

96 """ 

97 media_type = self.params.get("FMTTYPE") or "application/octet-stream" 

98 return f"data:{quote(media_type, safe='/;=')};base64,{self.base64data}" 

99 

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

101 """self == other""" 

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

103 

104 def __hash__(self) -> int: 

105 """Hash of the vBinary object.""" 

106 return hash(self.bytes) 

107 

108 @classmethod 

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

110 """Examples of vBinary.""" 

111 return [cls("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4")] 

112 

113 from icalendar.param import VALUE 

114 

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

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

117 params = self.params.to_jcal() 

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

119 # BASE64 is the only allowed encoding 

120 del params["encoding"] 

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

122 

123 @property 

124 def ical_value(self) -> bytes: 

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

126 

127 .. versionadded:: 7.1.0 

128 

129 .. versionchanged:: 7.1.3 

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

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

132 input. See :pr:`1356`. 

133 """ 

134 return self.bytes 

135 

136 @classmethod 

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

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

139 

140 Parameters: 

141 jcal_property: The jCal property to parse. 

142 

143 Raises: 

144 ~icalendar.error.JCalParsingError: If the provided jCal is invalid. 

145 """ 

146 JCalParsingError.validate_property(jcal_property, cls) 

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

148 return cls( 

149 cls.from_ical(jcal_property[3]), 

150 params=Parameters.from_jcal_property(jcal_property), 

151 ) 

152 

153 def to_xcal(self, element: Element): 

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

155 self.params.to_xcal(element) 

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

157 xml.text = self.base64data 

158 

159 @classmethod 

160 @from_xcal_wrapper 

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

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

163 

164 Parameters: 

165 xml: The XML to parse or a parser. 

166 

167 Raises: 

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

169 """ 

170 element = parser.parse_tag(cls.default_value) 

171 text = element.get_text_without_whitespace() 

172 try: 

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

174 except binascii.Error as e: 

175 raise XCalParsingError( 

176 "Expected base64 encoded data", 

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

178 element, 

179 ) from e 

180 return cls(binary, params=params) 

181 

182 

183__all__ = ["vBinary"]