1"""CAL-ADDRESS values from :rfc:`5545`."""
2
3from typing import Any, ClassVar
4
5from icalendar.compatibility import Self
6from icalendar.error import JCalParsingError
7from icalendar.parser import Parameters
8from icalendar.parser_tools import DEFAULT_ENCODING, to_unicode
9
10
11class vCalAddress(str):
12 r"""Calendar User Address
13
14 Value Name:
15 CAL-ADDRESS
16
17 Purpose:
18 This value type is used to identify properties that contain a
19 calendar user address.
20
21 Description:
22 The value is a URI as defined by [RFC3986] or any other
23 IANA-registered form for a URI. When used to address an Internet
24 email transport address for a calendar user, the value MUST be a
25 mailto URI, as defined by [RFC2368].
26
27 Example:
28 ``mailto:`` is in front of the address.
29
30 .. code-block:: ics
31
32 mailto:jane_doe@example.com
33
34 Parsing:
35
36 .. code-block:: pycon
37
38 >>> from icalendar import vCalAddress
39 >>> cal_address = vCalAddress.from_ical('mailto:jane_doe@example.com')
40 >>> cal_address
41 vCalAddress('mailto:jane_doe@example.com')
42
43 Encoding:
44
45 .. code-block:: pycon
46
47 >>> from icalendar import vCalAddress, Event
48 >>> event = Event()
49 >>> jane = vCalAddress("mailto:jane_doe@example.com")
50 >>> jane.name = "Jane"
51 >>> event["organizer"] = jane
52 >>> print(event.to_ical().decode().replace('\\r\\n', '\\n').strip())
53 BEGIN:VEVENT
54 ORGANIZER;CN=Jane:mailto:jane_doe@example.com
55 END:VEVENT
56 """
57
58 default_value: ClassVar[str] = "CAL-ADDRESS"
59 params: Parameters
60 __slots__ = ("params",)
61
62 def __new__(
63 cls,
64 value: str | bytes,
65 encoding: str = DEFAULT_ENCODING,
66 /,
67 params: dict[str, Any] | None = None,
68 ) -> Self:
69 value = to_unicode(value, encoding=encoding)
70 if "\r" in value or "\n" in value:
71 raise ValueError(
72 f"A CAL-ADDRESS value may not contain CR or LF characters: {value!r}"
73 )
74 self = super().__new__(cls, value)
75 self.params = Parameters(params)
76 return self
77
78 def __repr__(self) -> str:
79 return f"vCalAddress('{self}')"
80
81 def to_ical(self) -> bytes:
82 return self.encode(DEFAULT_ENCODING)
83
84 @classmethod
85 def from_ical(cls, ical: str | bytes) -> Self:
86 return cls(ical)
87
88 @property
89 def ical_value(self) -> str:
90 """The ``mailto:`` part of the address."""
91 return str(self)
92
93 @property
94 def email(self) -> str:
95 """The email address without ``mailto:`` at the start."""
96 if self.lower().startswith("mailto:"):
97 return self[7:]
98 return str(self)
99
100 from icalendar.param import (
101 CN,
102 CUTYPE,
103 DELEGATED_FROM,
104 DELEGATED_TO,
105 DIR,
106 LANGUAGE,
107 MEMBER,
108 PARTSTAT,
109 ROLE,
110 RSVP,
111 SENT_BY,
112 VALUE,
113 )
114
115 DELEGATED_FROM = DELEGATED_FROM
116 """Specify the calendar users that delegated their participation.
117
118 This parameter can be specified on properties with a
119 CAL-ADDRESS value type. This parameter specifies those calendar
120 users that have delegated their participation in a group-scheduled
121 event or to-do to the calendar user specified by the property.
122 The individual calendar address parameter values MUST each be
123 specified in a quoted-string.
124 """
125
126 DELEGATED_TO = DELEGATED_TO
127 """Specify the calendar users to whom participation was delegated.
128
129 This parameter can be specified on properties with a
130 CAL-ADDRESS value type. This parameter specifies those calendar
131 users to whom participation in a group-scheduled event or to-do was
132 delegated by the calendar user specified by the property.
133 The individual calendar address parameter values MUST each be
134 specified in a quoted-string.
135 """
136
137 MEMBER = MEMBER
138 """Specify the group or list membership of a calendar user.
139
140 This parameter can be specified on properties with a
141 CAL-ADDRESS value type. The parameter identifies the groups or
142 list membership for the calendar user specified by the property.
143 The parameter value is either a single calendar address in a
144 quoted-string or a COMMA-separated list of calendar addresses,
145 each in a quoted-string. The individual calendar address
146 parameter values MUST each be specified in a quoted-string.
147 """
148
149 name = CN
150
151 @staticmethod
152 def _get_email(email: str) -> str:
153 """Extract email and add mailto: prefix if needed.
154
155 Handles case-insensitive mailto: prefix checking.
156
157 Parameters:
158 email: Email string that may or may not have mailto: prefix
159
160 Returns:
161 Email string with mailto: prefix
162 """
163 if not email.lower().startswith("mailto:"):
164 return f"mailto:{email}"
165 return email
166
167 @classmethod
168 def new(
169 cls,
170 email: str,
171 /,
172 cn: str | None = None,
173 cutype: str | None = None,
174 delegated_from: str | None = None,
175 delegated_to: str | None = None,
176 directory: str | None = None,
177 language: str | None = None,
178 partstat: str | None = None,
179 role: str | None = None,
180 rsvp: bool | None = None, # noqa: FBT001, RUF100
181 sent_by: str | None = None,
182 ) -> Self:
183 """Create a new vCalAddress with RFC 5545 parameters.
184
185 Creates a vCalAddress instance with automatic mailto: prefix handling
186 and support for all standard RFC 5545 parameters.
187
188 Parameters:
189 email: The email address (mailto: prefix added automatically if missing)
190 cn: Common Name parameter
191 cutype: Calendar user type (INDIVIDUAL, GROUP, RESOURCE, ROOM)
192 delegated_from: Email of the calendar user that delegated
193 delegated_to: Email of the calendar user that was delegated to
194 directory: Reference to directory information
195 language: Language for text values
196 partstat: Participation status (NEEDS-ACTION, ACCEPTED, DECLINED, etc.)
197 role: Role (REQ-PARTICIPANT, OPT-PARTICIPANT, NON-PARTICIPANT, CHAIR)
198 rsvp: Whether RSVP is requested
199 sent_by: Email of the calendar user acting on behalf of this user
200
201 Returns:
202 vCalAddress: A new calendar address with specified parameters
203
204 Raises:
205 TypeError: If email is not a string
206
207 Examples:
208 Basic usage:
209
210 >>> from icalendar.prop import vCalAddress
211 >>> addr = vCalAddress.new("test@test.com")
212 >>> str(addr)
213 'mailto:test@test.com'
214
215 With parameters:
216
217 >>> addr = vCalAddress.new("test@test.com", cn="Test User", role="CHAIR")
218 >>> addr.params["CN"]
219 'Test User'
220 >>> addr.params["ROLE"]
221 'CHAIR'
222 """
223 if not isinstance(email, str):
224 raise TypeError(f"Email must be a string, not {type(email).__name__}")
225
226 # Handle mailto: prefix (case-insensitive)
227 email_with_prefix = cls._get_email(email)
228
229 # Create the address
230 addr = cls(email_with_prefix)
231
232 # Set parameters if provided
233 if cn is not None:
234 addr.params["CN"] = cn
235 if cutype is not None:
236 addr.params["CUTYPE"] = cutype
237 if delegated_from is not None:
238 addr.params["DELEGATED-FROM"] = cls._get_email(delegated_from)
239 if delegated_to is not None:
240 addr.params["DELEGATED-TO"] = cls._get_email(delegated_to)
241 if directory is not None:
242 addr.params["DIR"] = directory
243 if language is not None:
244 addr.params["LANGUAGE"] = language
245 if partstat is not None:
246 addr.params["PARTSTAT"] = partstat
247 if role is not None:
248 addr.params["ROLE"] = role
249 if rsvp is not None:
250 addr.params["RSVP"] = "TRUE" if rsvp else "FALSE"
251 if sent_by is not None:
252 addr.params["SENT-BY"] = cls._get_email(sent_by)
253
254 return addr
255
256 def to_jcal(self, name: str) -> list:
257 """Return this property in jCal format."""
258 return [name, self.params.to_jcal(), self.VALUE.lower(), self.ical_value]
259
260 @classmethod
261 def examples(cls) -> list[Self]:
262 """Examples of vCalAddress."""
263 return [cls.new("you@example.org", cn="You There")]
264
265 @classmethod
266 def from_jcal(cls, jcal_property: list) -> Self:
267 """Parse jCal from :rfc:`7265`.
268
269 Parameters:
270 jcal_property: The jCal property to parse.
271
272 Raises:
273 ~error.JCalParsingError: If the provided jCal is invalid.
274 """
275 JCalParsingError.validate_property(jcal_property, cls)
276 JCalParsingError.validate_value_type(jcal_property[3], str, cls, 3)
277 return cls(
278 jcal_property[3],
279 params=Parameters.from_jcal_property(jcal_property),
280 )
281
282
283__all__ = ["vCalAddress"]