Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/parser/parameter.py: 70%

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

254 statements  

1"""Functions for parsing parameters.""" 

2 

3from __future__ import annotations 

4 

5import functools 

6import os 

7import re 

8from datetime import datetime, time 

9from typing import TYPE_CHECKING, Any, Protocol 

10from xml.etree.ElementTree import Element 

11 

12from icalendar.caselessdict import CaselessDict 

13from icalendar.compatibility import Self, deprecate_for_version_9 

14from icalendar.error import JCalParsingError 

15from icalendar.parser.string import validate_token 

16from icalendar.parser_tools import ( 

17 DEFAULT_ENCODING, 

18 SEQUENCE_TYPES, 

19) 

20from icalendar.timezone.tzid import tzid_from_dt 

21 

22if TYPE_CHECKING: 

23 from collections.abc import Callable, KeysView, Sequence 

24 

25 from icalendar.enums import VALUE 

26 from icalendar.parser.xcal.adapter import ElementAdapter 

27 from icalendar.prop import VPROPERTY 

28 

29 

30class HasToIcal(Protocol): 

31 """Protocol for objects with a to_ical method.""" 

32 

33 def to_ical(self) -> bytes: 

34 """Convert to iCalendar format.""" 

35 ... 

36 

37 

38def param_value( 

39 value: Sequence[str] | str | HasToIcal, always_quote: bool = False 

40) -> str: 

41 """Convert a parameter value to its iCalendar representation. 

42 

43 Applies :rfc:`6868` escaping and optionally quotes the value according 

44 to :rfc:`5545` parameter value formatting rules. 

45 

46 Parameters: 

47 value: The parameter value to convert. Can be a sequence, string, or 

48 object with a ``to_ical()`` method. 

49 always_quote: If ``True``, always enclose the value in double quotes. 

50 Defaults to ``False`` (only quote when necessary). 

51 

52 Returns: 

53 The formatted parameter value, escaped and quoted as needed. 

54 """ 

55 if isinstance(value, SEQUENCE_TYPES): 

56 return q_join(map(_rfc_6868_escape, value), always_quote=always_quote) 

57 if isinstance(value, str): 

58 return dquote(_rfc_6868_escape(value), always_quote=always_quote) 

59 return dquote(_rfc_6868_escape(value.to_ical().decode(DEFAULT_ENCODING))) 

60 

61 

62# Could be improved 

63 

64 

65UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7f",:;]') 

66QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7f"]') 

67 

68 

69def validate_param_value(value: str, quoted: bool = True) -> None: 

70 """Validate a parameter value for unsafe characters. 

71 

72 Checks parameter values for characters that are not allowed according to 

73 :rfc:`5545`. Uses different validation rules for quoted and unquoted values. 

74 

75 Parameters: 

76 value: The parameter value to validate. 

77 quoted: If ``True``, validate as a quoted value (allows more characters). 

78 If ``False``, validate as an unquoted value (stricter). 

79 Defaults to ``True``. 

80 

81 Raises: 

82 ValueError: If the value contains unsafe characters for its quote state. 

83 """ 

84 validator = QUNSAFE_CHAR if quoted else UNSAFE_CHAR 

85 if validator.findall(value): 

86 raise ValueError(value) 

87 

88 

89# chars presence of which in parameter value will be cause the value 

90# to be enclosed in double-quotes 

91QUOTABLE = re.compile("[,;:’]") # noqa: RUF001 

92 

93 

94def dquote(val: str, always_quote: bool = False) -> str: 

95 r"""Enclose parameter values in double quotes when needed. 

96 

97 Parameter values containing special characters ``,``, ``;``, 

98 ``:`` or ``'`` must be enclosed 

99 in double quotes according to :rfc:`5545`. Double-quote characters in the 

100 value are replaced with single quotes since they're forbidden in parameter 

101 values. Values ending with a backslash ``\`` are quoted, too, because 

102 the parser would otherwise read it as escaping the following delimiter. 

103 

104 Parameters: 

105 val: The parameter value to quote. 

106 always_quote: If ``True``, always enclose in quotes regardless of content. 

107 Defaults to ``False`` (only quote when necessary). 

108 

109 Returns: 

110 The value, enclosed in double quotes if needed or requested. 

111 """ 

112 # a double-quote character is forbidden to appear in a parameter value 

113 # so replace it with a single-quote character 

114 val = val.replace('"', "'") 

115 if QUOTABLE.search(val) or val.endswith("\\") or always_quote: 

116 return f'"{val}"' 

117 return val 

118 

119 

120def unescape_quoted_param_value(value: str) -> str: 

121 r"""Unescape :rfc:`2445#section-4.3.11`-compliant escapes in a quoted 

122 parameter value. 

123 

124 ``\"`` becomes a literal double quote, and ``\\`` becomes a literal 

125 backslash. A lone backslash is kept as-is. It's a valid 

126 :rfc:`5545#section-3.1` QSAFE-CHAR with no escape meaning. Values are 

127 scanned from left to right, resolving ``\\\"`` to ``\"``. 

128 """ 

129 out = [] 

130 i = 0 

131 length = len(value) 

132 while i < length: 

133 ch = value[i] 

134 if ch == "\\" and i + 1 < length and value[i + 1] in ('"', "\\"): 

135 out.append(value[i + 1]) 

136 i += 2 

137 else: 

138 out.append(ch) 

139 i += 1 

140 return "".join(out) 

141 

142 

143# parsing helper 

144 

145# A DQUOTE preceded by a backslash still closes its quoted section when a 

146# structural delimiter or the end of the string follows it. icalendar 

147# itself writes parameter values that end in a backslash as ``"…\"`` (see 

148# :func:`param_value`). :rfc:`5545` and :rfc:`6868` give the backslash no 

149# escape meaning inside parameter values. A backslash-escaped DQUOTE is 

150# honored as value content only when more value text follows it, the way 

151# :rfc:`2445`-style producers emit ``CN="Qu\"ote"``. 

152_STRUCTURAL_DELIMITERS = frozenset(";:,") 

153 

154 

155def closes_quoted_section(text: str, quote_index: int) -> bool: 

156 """Whether the DQUOTE at ``quote_index`` closes its quoted section. 

157 

158 The DQUOTE may be preceded by a literal backslash. If the next 

159 non-whitespace character after it is a structural delimiter (``;``, 

160 ``:``, or ``,``), nothing but whitespace, or the end of the string 

161 follows, then the backslash is content and the DQUOTE closes the quoted 

162 section. A DQUOTE followed by other text is an escaped quote inside 

163 the value instead. 

164 """ 

165 for ch in text[quote_index + 1 :]: 

166 if ch in " \t": 

167 continue 

168 return ch in _STRUCTURAL_DELIMITERS 

169 return True 

170 

171 

172def q_split(st: str, sep: str = ",", maxsplit: int = -1) -> list[str]: 

173 """Split a string on a separator, respecting double quotes. 

174 

175 Splits the string on the separator character, but ignores separators that 

176 appear inside double-quoted sections. This is needed for parsing parameter 

177 values that may contain quoted strings. 

178 

179 Parameters: 

180 st: The string to split. 

181 sep: The separator character. Defaults to ``,``. 

182 maxsplit: Maximum number of splits to perform. If ``-1`` (default), 

183 then perform all possible splits. 

184 

185 Returns: 

186 The split string parts. 

187 

188 Examples: 

189 .. code-block:: pycon 

190 

191 >>> from icalendar.parser import q_split 

192 >>> q_split('a,b,c') 

193 ['a', 'b', 'c'] 

194 >>> q_split('a,"b,c",d') 

195 ['a', '"b,c"', 'd'] 

196 >>> q_split('a;b;c', sep=';') 

197 ['a', 'b', 'c'] 

198 """ 

199 if maxsplit == 0: 

200 return [st] 

201 

202 result = [] 

203 cursor = 0 

204 length = len(st) 

205 inquote = 0 

206 escaped = False 

207 splits = 0 

208 for i, ch in enumerate(st): 

209 if inquote and escaped: 

210 # an escaped character inside a quoted section is content: 

211 # ``\"`` does not close the quote (RFC 2445-style input) — 

212 # unless a separator or the end of the string follows, which 

213 # makes the backslash a literal one and the DQUOTE the closer 

214 # (values ending in a backslash, as icalendar writes them). 

215 escaped = False 

216 if ch == '"' and closes_quoted_section(st, i): 

217 inquote = not inquote 

218 elif inquote and ch == "\\": 

219 escaped = True 

220 elif ch == '"': 

221 inquote = not inquote 

222 if not inquote and ch == sep: 

223 result.append(st[cursor:i]) 

224 cursor = i + 1 

225 splits += 1 

226 if i + 1 == length or splits == maxsplit: 

227 result.append(st[cursor:]) 

228 break 

229 return result 

230 

231 

232def q_join(lst: Sequence[str], sep: str = ",", always_quote: bool = False) -> str: 

233 """Join a list with a separator, quoting items as needed. 

234 

235 Joins list items with the separator, applying :func:`dquote` to each item 

236 to add double quotes when they contain special characters. 

237 

238 Parameters: 

239 lst: The list of items to join. 

240 sep: The separator to use. Defaults to ``,``. 

241 always_quote: If ``True``, always quote all items. Defaults to ``False`` 

242 (only quote when necessary). 

243 

244 Returns: 

245 The joined string with items quoted as needed. 

246 

247 Examples: 

248 .. code-block:: pycon 

249 

250 >>> from icalendar.parser import q_join 

251 >>> q_join(['a', 'b', 'c']) 

252 'a,b,c' 

253 >>> q_join(['plain', 'has,comma']) 

254 'plain,"has,comma"' 

255 """ 

256 return sep.join(dquote(itm, always_quote=always_quote) for itm in lst) 

257 

258 

259def _single_string_parameter(upper: bool = False): 

260 """Create a parameter getter/setter for a single string parameter. 

261 

262 Parameters: 

263 upper: Convert the return value of the property getter to uppercase. 

264 

265 Returns: 

266 The property for the parameter. 

267 """ 

268 

269 def decorator(func: Callable) -> property: 

270 name = func.__name__ 

271 

272 @functools.wraps(func) 

273 def fget(self: Parameters): 

274 """Get the value.""" 

275 value = self.get(name) 

276 if value is not None and upper: 

277 value = value.upper() 

278 return value 

279 

280 def fset(self: Parameters, value: str | None): 

281 """Set the value""" 

282 if value is None: 

283 fdel(self) 

284 else: 

285 if upper: 

286 value = value.upper() 

287 self[name] = value 

288 

289 def fdel(self: Parameters): 

290 """Delete the value.""" 

291 self.pop(name, None) 

292 

293 return property(fget, fset, fdel, doc=func.__doc__) 

294 

295 return decorator 

296 

297 

298single_string_parameter = deprecate_for_version_9(_single_string_parameter) 

299 

300 

301class Parameters(CaselessDict[str | list[str]]): 

302 """Parser and generator of Property parameter strings. 

303 

304 It knows nothing of datatypes. 

305 Its main concern is textual structure. 

306 

307 Examples: 

308 

309 Modify parameters: 

310 

311 .. code-block:: pycon 

312 

313 >>> from icalendar import Parameters 

314 >>> params = Parameters() 

315 >>> params['VALUE'] = 'TEXT' 

316 >>> params.value 

317 'TEXT' 

318 >>> params 

319 Parameters({'VALUE': 'TEXT'}) 

320 

321 Create new parameters: 

322 

323 .. code-block:: pycon 

324 

325 >>> params = Parameters(value="BINARY") 

326 >>> params.value 

327 'BINARY' 

328 

329 Set a default: 

330 

331 .. code-block:: pycon 

332 

333 >>> params = Parameters(value="BINARY", default_value="TEXT") 

334 >>> params 

335 Parameters({'VALUE': 'BINARY'}) 

336 

337 """ 

338 

339 def __init__(self, *args: Any, **kwargs: Any) -> None: 

340 """Create new parameters.""" 

341 if args and args[0] is None: 

342 # allow passing None 

343 args = args[1:] 

344 defaults = { 

345 key[8:]: kwargs.pop(key) 

346 for key in list(kwargs.keys()) 

347 if key.lower().startswith("default_") 

348 } 

349 super().__init__(*args, **kwargs) 

350 for key, value in defaults.items(): 

351 self.setdefault(key, value) 

352 

353 # The following paremeters must always be enclosed in double quotes 

354 always_quoted = ( 

355 "ALTREP", 

356 "DELEGATED-FROM", 

357 "DELEGATED-TO", 

358 "DIR", 

359 "MEMBER", 

360 "SENT-BY", 

361 # Part of X-APPLE-STRUCTURED-LOCATION 

362 "X-ADDRESS", 

363 "X-TITLE", 

364 # RFC 9253 

365 "LINKREL", 

366 ) 

367 # this is quoted should one of the values be present 

368 quote_also = { 

369 # This is escaped in the RFC 

370 "CN": " '", 

371 } 

372 

373 def params(self) -> KeysView[str]: 

374 """In RFC 5545 keys are called parameters, so this is to be consitent 

375 with the naming conventions. 

376 """ 

377 return self.keys() 

378 

379 def to_ical(self, sorted: bool = True) -> bytes: # noqa: A002 

380 """Returns an :rfc:`5545` representation of the parameters. 

381 

382 Parameters: 

383 sorted (bool): Sort the parameters before encoding. 

384 exclude_utc (bool): Exclude TZID if it is set to ``"UTC"`` 

385 """ 

386 result = [] 

387 items = list(self.items()) 

388 if sorted: 

389 items.sort() 

390 

391 for key, value in items: 

392 if key == "TZID" and value == "UTC": 

393 # The "TZID" property parameter MUST NOT be applied to DATE-TIME 

394 # properties whose time values are specified in UTC. 

395 continue 

396 upper_key = key.upper() 

397 check_quoteable_characters = self.quote_also.get(key.upper()) 

398 always_quote = upper_key in self.always_quoted or ( 

399 check_quoteable_characters 

400 and any(c in value for c in check_quoteable_characters) 

401 ) 

402 quoted_value = param_value(value, always_quote=always_quote) 

403 quoted_value = quoted_value.encode(DEFAULT_ENCODING) 

404 # CaselessDict keys are always unicode 

405 result.append(upper_key.encode(DEFAULT_ENCODING) + b"=" + quoted_value) 

406 return b";".join(result) 

407 

408 @classmethod 

409 def from_ical(cls, st: str, strict: bool = False) -> Self: 

410 """Parses the parameter format from ical text format.""" 

411 

412 # parse into strings 

413 result = cls() 

414 for param in q_split(st, ";"): 

415 try: 

416 key, val = q_split(param, "=", maxsplit=1) 

417 validate_token(key) 

418 # Property parameter values that are not in quoted 

419 # strings are case insensitive. 

420 string_values = [] 

421 for v in q_split(val, ","): 

422 if v.startswith('"') and v.endswith('"') and len(v) >= 2: 

423 # validate the still-escaped form: ``\"`` pairs are 

424 # removed first so the literal quote they unescape to 

425 # does not trip QUNSAFE_CHAR, while a raw unescaped 

426 # quote still does. Exactly the surrounding quotes 

427 # are removed — a trailing ``\"`` keeps its quote, 

428 # which ``str.strip('"')`` would also eat. 

429 v2 = v[1:-1] 

430 validate_param_value(v2.replace('\\"', ""), quoted=True) 

431 string_values.append( 

432 _rfc_6868_unescape(unescape_quoted_param_value(v2)) 

433 ) 

434 else: 

435 validate_param_value(v, quoted=False) 

436 if strict: 

437 string_values.append(_rfc_6868_unescape(v.upper())) 

438 else: 

439 string_values.append(_rfc_6868_unescape(v)) 

440 

441 if not string_values: 

442 string_values = [val] 

443 result[key] = ( 

444 string_values[0] if len(string_values) == 1 else string_values 

445 ) 

446 except ValueError as exc: # noqa: PERF203 

447 raise ValueError( 

448 f"{param!r} is not a valid parameter string: {exc}" 

449 ) from exc 

450 return result 

451 

452 @_single_string_parameter(upper=True) 

453 def value(self) -> VALUE | str | None: 

454 """The VALUE parameter from :rfc:`5545`. 

455 

456 Description: 

457 This parameter specifies the value type and format of 

458 the property value. The property values MUST be of a single value 

459 type. For example, a "RDATE" property cannot have a combination 

460 of DATE-TIME and TIME value types. 

461 

462 If the property's value is the default value type, then this 

463 parameter need not be specified. However, if the property's 

464 default value type is overridden by some other allowable value 

465 type, then this parameter MUST be specified. 

466 

467 Applications MUST preserve the value data for x-name and iana- 

468 token values that they don't recognize without attempting to 

469 interpret or parse the value data. 

470 

471 For convenience, using this property, the value will be converted to 

472 an uppercase string. 

473 

474 .. code-block:: pycon 

475 

476 >>> from icalendar import Parameters 

477 >>> params = Parameters() 

478 >>> params.value = "unknown" 

479 >>> params 

480 Parameters({'VALUE': 'UNKNOWN'}) 

481 

482 """ 

483 

484 def _parameter_value_to_jcal( 

485 self, value: str | float | list | VPROPERTY 

486 ) -> str | int | float | list[str] | list[int] | list[float]: 

487 """Convert a parameter value to jCal format. 

488 

489 Parameters: 

490 value: The parameter value 

491 

492 Returns: 

493 The jCal representation of the parameter value 

494 """ 

495 if isinstance(value, list): 

496 return [self._parameter_value_to_jcal(v) for v in value] 

497 if hasattr(value, "to_jcal"): 

498 # proprty values respond to this 

499 jcal = value.to_jcal() 

500 # we only need the value part 

501 if len(jcal) == 4: 

502 return jcal[3] 

503 return jcal[3:] 

504 for t in (int, float, str): 

505 if isinstance(value, t): 

506 return t(value) 

507 raise TypeError( 

508 "Unsupported parameter value type for jCal conversion: " 

509 f"{type(value)} {value!r}" 

510 ) 

511 

512 def to_jcal(self, exclude_utc=False) -> dict[str, str]: 

513 """Return the jCal representation of the parameters. 

514 

515 Parameters: 

516 exclude_utc (bool): Exclude the TZID parameter if it is UTC 

517 """ 

518 jcal = { 

519 k.lower(): self._parameter_value_to_jcal(v) 

520 for k, v in self.items() 

521 if k.lower() != "value" 

522 } 

523 if exclude_utc and jcal.get("tzid") == "UTC": 

524 del jcal["tzid"] 

525 return jcal 

526 

527 @_single_string_parameter() 

528 def tzid(self) -> str | None: 

529 """The TZID parameter from :rfc:`5545`.""" 

530 

531 def is_utc(self) -> bool: 

532 """Whether the TZID parameter is UTC.""" 

533 return self.tzid == "UTC" 

534 

535 def update_tzid_from( 

536 self, dt: datetime | time | tuple[datetime, Any] | Any 

537 ) -> None: 

538 """Update the TZID parameter from a datetime object. 

539 

540 This sets the TZID parameter or deletes it according to the datetime. 

541 :rfc:`5545#section-3.2.19` prohibits TZID on UTC datetimes, 

542 which use the ``Z`` suffix instead. 

543 """ 

544 if isinstance(dt, tuple) and len(dt) >= 1: 

545 dt = dt[0] 

546 if isinstance(dt, (datetime, time)): 

547 tzid = tzid_from_dt(dt) 

548 # UTC uses Z suffix and does not appear as TZID parameter 

549 if tzid == "UTC": 

550 del self.tzid 

551 else: 

552 self.tzid = tzid 

553 

554 @classmethod 

555 def from_jcal(cls, jcal: dict[str : str | list[str]]): 

556 """Parse jCal parameters.""" 

557 if not isinstance(jcal, dict): 

558 raise JCalParsingError("The parameters must be a mapping.", cls) 

559 result = cls() 

560 for name, value in jcal.items(): 

561 if not isinstance(name, str): 

562 raise JCalParsingError( 

563 "All parameter names must be strings.", cls, value=name 

564 ) 

565 JCalParsingError.validate_jcal_token(name, "parameter name", cls) 

566 if not ( 

567 ( 

568 isinstance(value, list) 

569 and all(isinstance(v, (str, int, float)) for v in value) 

570 and value 

571 ) 

572 or isinstance(value, (str, int, float)) 

573 ): 

574 raise JCalParsingError( 

575 "Parameter values must be a string, integer or " 

576 "float or a list of those.", 

577 cls, 

578 name, 

579 value=value, 

580 ) 

581 result[name] = ( 

582 value[0] if isinstance(value, list) and len(value) == 1 else value 

583 ) 

584 return result 

585 

586 @classmethod 

587 def from_jcal_property(cls, jcal_property: list): 

588 """Create the parameters for a jCal property. 

589 

590 Parameters: 

591 jcal_property (list): The jCal property [name, params, value, ...] 

592 default_value (str, optional): The default value of the property. 

593 If this is given, the default value will not be set. 

594 """ 

595 if not isinstance(jcal_property, list) or len(jcal_property) < 4: 

596 raise JCalParsingError( 

597 "The property must be a list with at least 4 items.", cls 

598 ) 

599 jcal_params = jcal_property[1] 

600 with JCalParsingError.reraise_with_path_added(1): 

601 self = cls.from_jcal(jcal_params) 

602 if self.is_utc(): 

603 del self.tzid # we do not want this parameter 

604 return self 

605 

606 @classmethod 

607 def from_xcal(cls, element: Element | ElementAdapter) -> Parameters: 

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

609 

610 Parameters: 

611 element: The xCal element to parse. 

612 

613 Raises: 

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

615 

616 Returns: 

617 :Parameters: The parsed parameters. 

618 

619 Example: 

620 

621 This parses the parameters from an xCal string. 

622 

623 .. code-block:: pycon 

624 

625 >>> from icalendar import Parameters 

626 >>> from xml.etree.ElementTree import fromstring 

627 >>> xcal_string = ''' 

628 ... <parameters> 

629 ... <language> 

630 ... <text>en-US</text> 

631 ... </language> 

632 ... </parameters> 

633 ... ''' 

634 >>> xml_element = fromstring(xcal_string) 

635 >>> parameters = Parameters.from_xcal(xml_element) 

636 >>> parameters['language'] == 'en-US' 

637 True 

638 

639 """ 

640 from icalendar.parser.xcal.parameters import XCalParametersParser 

641 

642 parser = XCalParametersParser(element) 

643 return parser.parse_parameters() 

644 

645 def to_xcal(self, element: Element) -> None: 

646 """Add the xCal representation of the parameters according to :rfc:`6321`. 

647 

648 No parameters are added when they are empty or parameters are already present 

649 in the element. 

650 

651 Parameters: 

652 element: The parameters are added to this element as first child. 

653 """ 

654 if not self or (len(element) >= 1 and element[0].tag == "parameters"): 

655 return # exit quickly 

656 from icalendar.prop.factory import TypesFactory 

657 

658 result = Element("parameters") 

659 factory = TypesFactory.instance() 

660 for key in sorted(self): 

661 if key == "VALUE": 

662 continue 

663 value_factory = factory.for_property(key) 

664 param_element = Element(key.lower()) 

665 for value in self.get_multiple(key): 

666 # value is expected to be str or a subclass of str 

667 if hasattr(value, "to_xcal"): 

668 # str subclass 

669 value.to_xcal(param_element) 

670 else: 

671 parsed_value = value_factory.from_ical(value) 

672 if hasattr(parsed_value, "to_xcal"): 

673 # property type 

674 parsed_value.to_xcal(param_element) # type: ignore # noqa: PGH003 

675 else: 

676 # native type 

677 v_prop = value_factory(parsed_value) # type: ignore # noqa: PGH003 

678 v_prop.to_xcal(param_element) 

679 result.append(param_element) 

680 if len(result) > 0: 

681 # Parameters always go first. 

682 element.insert(0, result) 

683 

684 def get_multiple(self, key: str) -> list: 

685 """Get mulitple values as a list. 

686 

687 .. note:: 

688 

689 Do not modify the list. This is only for iteration. 

690 """ 

691 result = self.get(key, []) 

692 if not isinstance(result, list): 

693 return [result] 

694 return result 

695 

696 

697RFC_6868_UNESCAPE_REGEX = re.compile(r"\^\^|\^n|\^'") 

698 

699 

700def _rfc_6868_unescape(param_value: str) -> str: 

701 """Take care of :rfc:`6868` unescaping. 

702 

703 - ^^ -> ^ 

704 - ^n -> system specific newline 

705 - ^' -> " 

706 - ^ with others stay intact 

707 """ 

708 replacements = { 

709 "^^": "^", 

710 "^n": os.linesep, 

711 "^'": '"', 

712 } 

713 return RFC_6868_UNESCAPE_REGEX.sub( 

714 lambda m: replacements.get(m.group(0), m.group(0)), param_value 

715 ) 

716 

717 

718RFC_6868_ESCAPE_REGEX = re.compile(r'\^|\r\n|\r|\n|"') 

719 

720 

721def _rfc_6868_escape(param_value: str) -> str: 

722 """Take care of :rfc:`6868` escaping. 

723 

724 - ^ -> ^^ 

725 - " -> ^' 

726 - newline -> ^n 

727 """ 

728 replacements = { 

729 "^": "^^", 

730 "\n": "^n", 

731 "\r": "^n", 

732 "\r\n": "^n", 

733 '"': "^'", 

734 } 

735 return RFC_6868_ESCAPE_REGEX.sub( 

736 lambda m: replacements.get(m.group(0), m.group(0)), param_value 

737 ) 

738 

739 

740rfc_6868_escape = deprecate_for_version_9(_rfc_6868_escape) 

741rfc_6868_unescape = deprecate_for_version_9(_rfc_6868_unescape) 

742 

743 

744__all__ = [ 

745 "Parameters", 

746 "dquote", 

747 "param_value", 

748 "q_join", 

749 "q_split", 

750 "validate_param_value", 

751]