Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/asn1crypto/util.py: 32%

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

272 statements  

1# coding: utf-8 

2 

3""" 

4Miscellaneous data helpers, including functions for converting integers to and 

5from bytes and UTC timezone. Exports the following items: 

6 

7 - OrderedDict() 

8 - int_from_bytes() 

9 - int_to_bytes() 

10 - timezone.utc 

11 - utc_with_dst 

12 - create_timezone() 

13 - inet_ntop() 

14 - inet_pton() 

15 - uri_to_iri() 

16 - iri_to_uri() 

17""" 

18 

19from __future__ import unicode_literals, division, absolute_import, print_function 

20 

21import math 

22import sys 

23from datetime import datetime, date, timedelta, tzinfo 

24 

25from ._errors import unwrap 

26from ._iri import iri_to_uri, uri_to_iri # noqa 

27from ._ordereddict import OrderedDict # noqa 

28from ._types import type_name 

29 

30if sys.platform == 'win32': 

31 from ._inet import inet_ntop, inet_pton 

32else: 

33 from socket import inet_ntop, inet_pton # noqa 

34 

35 

36# Python 2 

37if sys.version_info <= (3,): 

38 

39 def int_to_bytes(value, signed=False, width=None): 

40 """ 

41 Converts an integer to a byte string 

42 

43 :param value: 

44 The integer to convert 

45 

46 :param signed: 

47 If the byte string should be encoded using two's complement 

48 

49 :param width: 

50 If None, the minimal possible size (but at least 1), 

51 otherwise an integer of the byte width for the return value 

52 

53 :return: 

54 A byte string 

55 """ 

56 

57 if value == 0 and width == 0: 

58 return b'' 

59 

60 # Handle negatives in two's complement 

61 is_neg = False 

62 if signed and value < 0: 

63 is_neg = True 

64 bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8) 

65 value = (value + (1 << bits)) % (1 << bits) 

66 

67 hex_str = '%x' % value 

68 if len(hex_str) & 1: 

69 hex_str = '0' + hex_str 

70 

71 output = hex_str.decode('hex') 

72 

73 if signed and not is_neg and ord(output[0:1]) & 0x80: 

74 output = b'\x00' + output 

75 

76 if width is not None: 

77 if len(output) > width: 

78 raise OverflowError('int too big to convert') 

79 if is_neg: 

80 pad_char = b'\xFF' 

81 else: 

82 pad_char = b'\x00' 

83 output = (pad_char * (width - len(output))) + output 

84 elif is_neg and ord(output[0:1]) & 0x80 == 0: 

85 output = b'\xFF' + output 

86 

87 return output 

88 

89 def int_from_bytes(value, signed=False): 

90 """ 

91 Converts a byte string to an integer 

92 

93 :param value: 

94 The byte string to convert 

95 

96 :param signed: 

97 If the byte string should be interpreted using two's complement 

98 

99 :return: 

100 An integer 

101 """ 

102 

103 if value == b'': 

104 return 0 

105 

106 num = long(value.encode("hex"), 16) # noqa 

107 

108 if not signed: 

109 return num 

110 

111 # Check for sign bit and handle two's complement 

112 if ord(value[0:1]) & 0x80: 

113 bit_len = len(value) * 8 

114 return num - (1 << bit_len) 

115 

116 return num 

117 

118 class timezone(tzinfo): # noqa 

119 """ 

120 Implements datetime.timezone for py2. 

121 Only full minute offsets are supported. 

122 DST is not supported. 

123 """ 

124 

125 def __init__(self, offset, name=None): 

126 """ 

127 :param offset: 

128 A timedelta with this timezone's offset from UTC 

129 

130 :param name: 

131 Name of the timezone; if None, generate one. 

132 """ 

133 

134 if not timedelta(hours=-24) < offset < timedelta(hours=24): 

135 raise ValueError('Offset must be in [-23:59, 23:59]') 

136 

137 if offset.seconds % 60 or offset.microseconds: 

138 raise ValueError('Offset must be full minutes') 

139 

140 self._offset = offset 

141 

142 if name is not None: 

143 self._name = name 

144 elif not offset: 

145 self._name = 'UTC' 

146 else: 

147 self._name = 'UTC' + _format_offset(offset) 

148 

149 def __eq__(self, other): 

150 """ 

151 Compare two timezones 

152 

153 :param other: 

154 The other timezone to compare to 

155 

156 :return: 

157 A boolean 

158 """ 

159 

160 if type(other) != timezone: 

161 return False 

162 return self._offset == other._offset 

163 

164 def __getinitargs__(self): 

165 """ 

166 Called by tzinfo.__reduce__ to support pickle and copy. 

167 

168 :return: 

169 offset and name, to be used for __init__ 

170 """ 

171 

172 return self._offset, self._name 

173 

174 def tzname(self, dt): 

175 """ 

176 :param dt: 

177 A datetime object; ignored. 

178 

179 :return: 

180 Name of this timezone 

181 """ 

182 

183 return self._name 

184 

185 def utcoffset(self, dt): 

186 """ 

187 :param dt: 

188 A datetime object; ignored. 

189 

190 :return: 

191 A timedelta object with the offset from UTC 

192 """ 

193 

194 return self._offset 

195 

196 def dst(self, dt): 

197 """ 

198 :param dt: 

199 A datetime object; ignored. 

200 

201 :return: 

202 Zero timedelta 

203 """ 

204 

205 return timedelta(0) 

206 

207 timezone.utc = timezone(timedelta(0)) 

208 

209# Python 3 

210else: 

211 

212 from datetime import timezone # noqa 

213 

214 def int_to_bytes(value, signed=False, width=None): 

215 """ 

216 Converts an integer to a byte string 

217 

218 :param value: 

219 The integer to convert 

220 

221 :param signed: 

222 If the byte string should be encoded using two's complement 

223 

224 :param width: 

225 If None, the minimal possible size (but at least 1), 

226 otherwise an integer of the byte width for the return value 

227 

228 :return: 

229 A byte string 

230 """ 

231 

232 if width is None: 

233 if signed: 

234 if value < 0: 

235 bits_required = abs(value + 1).bit_length() 

236 else: 

237 bits_required = value.bit_length() 

238 if bits_required % 8 == 0: 

239 bits_required += 1 

240 else: 

241 bits_required = value.bit_length() 

242 width = math.ceil(bits_required / 8) or 1 

243 return value.to_bytes(width, byteorder='big', signed=signed) 

244 

245 def int_from_bytes(value, signed=False): 

246 """ 

247 Converts a byte string to an integer 

248 

249 :param value: 

250 The byte string to convert 

251 

252 :param signed: 

253 If the byte string should be interpreted using two's complement 

254 

255 :return: 

256 An integer 

257 """ 

258 

259 return int.from_bytes(value, 'big', signed=signed) 

260 

261 

262def _format_offset(off): 

263 """ 

264 Format a timedelta into "[+-]HH:MM" format or "" for None 

265 """ 

266 

267 if off is None: 

268 return '' 

269 mins = off.days * 24 * 60 + off.seconds // 60 

270 sign = '-' if mins < 0 else '+' 

271 return sign + '%02d:%02d' % divmod(abs(mins), 60) 

272 

273 

274class _UtcWithDst(tzinfo): 

275 """ 

276 Utc class where dst does not return None; required for astimezone 

277 """ 

278 

279 def tzname(self, dt): 

280 return 'UTC' 

281 

282 def utcoffset(self, dt): 

283 return timedelta(0) 

284 

285 def dst(self, dt): 

286 return timedelta(0) 

287 

288 

289utc_with_dst = _UtcWithDst() 

290 

291_timezone_cache = {} 

292 

293 

294def create_timezone(offset): 

295 """ 

296 Returns a new datetime.timezone object with the given offset. 

297 Uses cached objects if possible. 

298 

299 :param offset: 

300 A datetime.timedelta object; It needs to be in full minutes and between -23:59 and +23:59. 

301 

302 :return: 

303 A datetime.timezone object 

304 """ 

305 

306 try: 

307 tz = _timezone_cache[offset] 

308 except KeyError: 

309 tz = _timezone_cache[offset] = timezone(offset) 

310 return tz 

311 

312 

313class extended_date(object): 

314 """ 

315 A datetime.datetime-like object that represents the year 0. This is just 

316 to handle 0000-01-01 found in some certificates. Python's datetime does 

317 not support year 0. 

318 

319 The proleptic gregorian calendar repeats itself every 400 years. Therefore, 

320 the simplest way to format is to substitute year 2000. 

321 """ 

322 

323 def __init__(self, year, month, day): 

324 """ 

325 :param year: 

326 The integer 0 

327 

328 :param month: 

329 An integer from 1 to 12 

330 

331 :param day: 

332 An integer from 1 to 31 

333 """ 

334 

335 if year != 0: 

336 raise ValueError('year must be 0') 

337 

338 self._y2k = date(2000, month, day) 

339 

340 @property 

341 def year(self): 

342 """ 

343 :return: 

344 The integer 0 

345 """ 

346 

347 return 0 

348 

349 @property 

350 def month(self): 

351 """ 

352 :return: 

353 An integer from 1 to 12 

354 """ 

355 

356 return self._y2k.month 

357 

358 @property 

359 def day(self): 

360 """ 

361 :return: 

362 An integer from 1 to 31 

363 """ 

364 

365 return self._y2k.day 

366 

367 def strftime(self, format): 

368 """ 

369 Formats the date using strftime() 

370 

371 :param format: 

372 A strftime() format string 

373 

374 :return: 

375 A str, the formatted date as a unicode string 

376 in Python 3 and a byte string in Python 2 

377 """ 

378 

379 # Format the date twice, once with year 2000, once with year 4000. 

380 # The only differences in the result will be in the millennium. Find them and replace by zeros. 

381 y2k = self._y2k.strftime(format) 

382 y4k = self._y2k.replace(year=4000).strftime(format) 

383 return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k)) 

384 

385 def isoformat(self): 

386 """ 

387 Formats the date as %Y-%m-%d 

388 

389 :return: 

390 The date formatted to %Y-%m-%d as a unicode string in Python 3 

391 and a byte string in Python 2 

392 """ 

393 

394 return self.strftime('0000-%m-%d') 

395 

396 def replace(self, year=None, month=None, day=None): 

397 """ 

398 Returns a new datetime.date or asn1crypto.util.extended_date 

399 object with the specified components replaced 

400 

401 :return: 

402 A datetime.date or asn1crypto.util.extended_date object 

403 """ 

404 

405 if year is None: 

406 year = self.year 

407 if month is None: 

408 month = self.month 

409 if day is None: 

410 day = self.day 

411 

412 if year > 0: 

413 cls = date 

414 else: 

415 cls = extended_date 

416 

417 return cls( 

418 year, 

419 month, 

420 day 

421 ) 

422 

423 def __str__(self): 

424 """ 

425 :return: 

426 A str representing this extended_date, e.g. "0000-01-01" 

427 """ 

428 

429 return self.strftime('%Y-%m-%d') 

430 

431 def __eq__(self, other): 

432 """ 

433 Compare two extended_date objects 

434 

435 :param other: 

436 The other extended_date to compare to 

437 

438 :return: 

439 A boolean 

440 """ 

441 

442 # datetime.date object wouldn't compare equal because it can't be year 0 

443 if not isinstance(other, self.__class__): 

444 return False 

445 return self.__cmp__(other) == 0 

446 

447 def __ne__(self, other): 

448 """ 

449 Compare two extended_date objects 

450 

451 :param other: 

452 The other extended_date to compare to 

453 

454 :return: 

455 A boolean 

456 """ 

457 

458 return not self.__eq__(other) 

459 

460 def _comparison_error(self, other): 

461 raise TypeError(unwrap( 

462 ''' 

463 An asn1crypto.util.extended_date object can only be compared to 

464 an asn1crypto.util.extended_date or datetime.date object, not %s 

465 ''', 

466 type_name(other) 

467 )) 

468 

469 def __cmp__(self, other): 

470 """ 

471 Compare two extended_date or datetime.date objects 

472 

473 :param other: 

474 The other extended_date object to compare to 

475 

476 :return: 

477 An integer smaller than, equal to, or larger than 0 

478 """ 

479 

480 # self is year 0, other is >= year 1 

481 if isinstance(other, date): 

482 return -1 

483 

484 if not isinstance(other, self.__class__): 

485 self._comparison_error(other) 

486 

487 if self._y2k < other._y2k: 

488 return -1 

489 if self._y2k > other._y2k: 

490 return 1 

491 return 0 

492 

493 def __lt__(self, other): 

494 return self.__cmp__(other) < 0 

495 

496 def __le__(self, other): 

497 return self.__cmp__(other) <= 0 

498 

499 def __gt__(self, other): 

500 return self.__cmp__(other) > 0 

501 

502 def __ge__(self, other): 

503 return self.__cmp__(other) >= 0 

504 

505 

506class extended_datetime(object): 

507 """ 

508 A datetime.datetime-like object that represents the year 0. This is just 

509 to handle 0000-01-01 found in some certificates. Python's datetime does 

510 not support year 0. 

511 

512 The proleptic gregorian calendar repeats itself every 400 years. Therefore, 

513 the simplest way to format is to substitute year 2000. 

514 """ 

515 

516 # There are 97 leap days during 400 years. 

517 DAYS_IN_400_YEARS = 400 * 365 + 97 

518 DAYS_IN_2000_YEARS = 5 * DAYS_IN_400_YEARS 

519 

520 def __init__(self, year, *args, **kwargs): 

521 """ 

522 :param year: 

523 The integer 0 

524 

525 :param args: 

526 Other positional arguments; see datetime.datetime. 

527 

528 :param kwargs: 

529 Other keyword arguments; see datetime.datetime. 

530 """ 

531 

532 if year != 0: 

533 raise ValueError('year must be 0') 

534 

535 self._y2k = datetime(2000, *args, **kwargs) 

536 

537 @property 

538 def year(self): 

539 """ 

540 :return: 

541 The integer 0 

542 """ 

543 

544 return 0 

545 

546 @property 

547 def month(self): 

548 """ 

549 :return: 

550 An integer from 1 to 12 

551 """ 

552 

553 return self._y2k.month 

554 

555 @property 

556 def day(self): 

557 """ 

558 :return: 

559 An integer from 1 to 31 

560 """ 

561 

562 return self._y2k.day 

563 

564 @property 

565 def hour(self): 

566 """ 

567 :return: 

568 An integer from 1 to 24 

569 """ 

570 

571 return self._y2k.hour 

572 

573 @property 

574 def minute(self): 

575 """ 

576 :return: 

577 An integer from 1 to 60 

578 """ 

579 

580 return self._y2k.minute 

581 

582 @property 

583 def second(self): 

584 """ 

585 :return: 

586 An integer from 1 to 60 

587 """ 

588 

589 return self._y2k.second 

590 

591 @property 

592 def microsecond(self): 

593 """ 

594 :return: 

595 An integer from 0 to 999999 

596 """ 

597 

598 return self._y2k.microsecond 

599 

600 @property 

601 def tzinfo(self): 

602 """ 

603 :return: 

604 If object is timezone aware, a datetime.tzinfo object, else None. 

605 """ 

606 

607 return self._y2k.tzinfo 

608 

609 def utcoffset(self): 

610 """ 

611 :return: 

612 If object is timezone aware, a datetime.timedelta object, else None. 

613 """ 

614 

615 return self._y2k.utcoffset() 

616 

617 def time(self): 

618 """ 

619 :return: 

620 A datetime.time object 

621 """ 

622 

623 return self._y2k.time() 

624 

625 def date(self): 

626 """ 

627 :return: 

628 An asn1crypto.util.extended_date of the date 

629 """ 

630 

631 return extended_date(0, self.month, self.day) 

632 

633 def strftime(self, format): 

634 """ 

635 Performs strftime(), always returning a str 

636 

637 :param format: 

638 A strftime() format string 

639 

640 :return: 

641 A str of the formatted datetime 

642 """ 

643 

644 # Format the datetime twice, once with year 2000, once with year 4000. 

645 # The only differences in the result will be in the millennium. Find them and replace by zeros. 

646 y2k = self._y2k.strftime(format) 

647 y4k = self._y2k.replace(year=4000).strftime(format) 

648 return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k)) 

649 

650 def isoformat(self, sep='T'): 

651 """ 

652 Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the 

653 date and time portions 

654 

655 :param set: 

656 A single character of the separator to place between the date and 

657 time 

658 

659 :return: 

660 The formatted datetime as a unicode string in Python 3 and a byte 

661 string in Python 2 

662 """ 

663 

664 s = '0000-%02d-%02d%c%02d:%02d:%02d' % (self.month, self.day, sep, self.hour, self.minute, self.second) 

665 if self.microsecond: 

666 s += '.%06d' % self.microsecond 

667 return s + _format_offset(self.utcoffset()) 

668 

669 def replace(self, year=None, *args, **kwargs): 

670 """ 

671 Returns a new datetime.datetime or asn1crypto.util.extended_datetime 

672 object with the specified components replaced 

673 

674 :param year: 

675 The new year to substitute. None to keep it. 

676 

677 :param args: 

678 Other positional arguments; see datetime.datetime.replace. 

679 

680 :param kwargs: 

681 Other keyword arguments; see datetime.datetime.replace. 

682 

683 :return: 

684 A datetime.datetime or asn1crypto.util.extended_datetime object 

685 """ 

686 

687 if year: 

688 return self._y2k.replace(year, *args, **kwargs) 

689 

690 return extended_datetime.from_y2k(self._y2k.replace(2000, *args, **kwargs)) 

691 

692 def astimezone(self, tz): 

693 """ 

694 Convert this extended_datetime to another timezone. 

695 

696 :param tz: 

697 A datetime.tzinfo object. 

698 

699 :return: 

700 A new extended_datetime or datetime.datetime object 

701 """ 

702 

703 return extended_datetime.from_y2k(self._y2k.astimezone(tz)) 

704 

705 def timestamp(self): 

706 """ 

707 Return POSIX timestamp. Only supported in python >= 3.3 

708 

709 :return: 

710 A float representing the seconds since 1970-01-01 UTC. This will be a negative value. 

711 """ 

712 

713 return self._y2k.timestamp() - self.DAYS_IN_2000_YEARS * 86400 

714 

715 def __str__(self): 

716 """ 

717 :return: 

718 A str representing this extended_datetime, e.g. "0000-01-01 00:00:00.000001-10:00" 

719 """ 

720 

721 return self.isoformat(sep=' ') 

722 

723 def __eq__(self, other): 

724 """ 

725 Compare two extended_datetime objects 

726 

727 :param other: 

728 The other extended_datetime to compare to 

729 

730 :return: 

731 A boolean 

732 """ 

733 

734 # Only compare against other datetime or extended_datetime objects 

735 if not isinstance(other, (self.__class__, datetime)): 

736 return False 

737 

738 # Offset-naive and offset-aware datetimes are never the same 

739 if (self.tzinfo is None) != (other.tzinfo is None): 

740 return False 

741 

742 return self.__cmp__(other) == 0 

743 

744 def __ne__(self, other): 

745 """ 

746 Compare two extended_datetime objects 

747 

748 :param other: 

749 The other extended_datetime to compare to 

750 

751 :return: 

752 A boolean 

753 """ 

754 

755 return not self.__eq__(other) 

756 

757 def _comparison_error(self, other): 

758 """ 

759 Raises a TypeError about the other object not being suitable for 

760 comparison 

761 

762 :param other: 

763 The object being compared to 

764 """ 

765 

766 raise TypeError(unwrap( 

767 ''' 

768 An asn1crypto.util.extended_datetime object can only be compared to 

769 an asn1crypto.util.extended_datetime or datetime.datetime object, 

770 not %s 

771 ''', 

772 type_name(other) 

773 )) 

774 

775 def __cmp__(self, other): 

776 """ 

777 Compare two extended_datetime or datetime.datetime objects 

778 

779 :param other: 

780 The other extended_datetime or datetime.datetime object to compare to 

781 

782 :return: 

783 An integer smaller than, equal to, or larger than 0 

784 """ 

785 

786 if not isinstance(other, (self.__class__, datetime)): 

787 self._comparison_error(other) 

788 

789 if (self.tzinfo is None) != (other.tzinfo is None): 

790 raise TypeError("can't compare offset-naive and offset-aware datetimes") 

791 

792 diff = self - other 

793 zero = timedelta(0) 

794 if diff < zero: 

795 return -1 

796 if diff > zero: 

797 return 1 

798 return 0 

799 

800 def __lt__(self, other): 

801 return self.__cmp__(other) < 0 

802 

803 def __le__(self, other): 

804 return self.__cmp__(other) <= 0 

805 

806 def __gt__(self, other): 

807 return self.__cmp__(other) > 0 

808 

809 def __ge__(self, other): 

810 return self.__cmp__(other) >= 0 

811 

812 def __add__(self, other): 

813 """ 

814 Adds a timedelta 

815 

816 :param other: 

817 A datetime.timedelta object to add. 

818 

819 :return: 

820 A new extended_datetime or datetime.datetime object. 

821 """ 

822 

823 return extended_datetime.from_y2k(self._y2k + other) 

824 

825 def __sub__(self, other): 

826 """ 

827 Subtracts a timedelta or another datetime. 

828 

829 :param other: 

830 A datetime.timedelta or datetime.datetime or extended_datetime object to subtract. 

831 

832 :return: 

833 If a timedelta is passed, a new extended_datetime or datetime.datetime object. 

834 Else a datetime.timedelta object. 

835 """ 

836 

837 if isinstance(other, timedelta): 

838 return extended_datetime.from_y2k(self._y2k - other) 

839 

840 if isinstance(other, extended_datetime): 

841 return self._y2k - other._y2k 

842 

843 if isinstance(other, datetime): 

844 return self._y2k - other - timedelta(days=self.DAYS_IN_2000_YEARS) 

845 

846 return NotImplemented 

847 

848 def __rsub__(self, other): 

849 return -(self - other) 

850 

851 @classmethod 

852 def from_y2k(cls, value): 

853 """ 

854 Revert substitution of year 2000. 

855 

856 :param value: 

857 A datetime.datetime object which is 2000 years in the future. 

858 :return: 

859 A new extended_datetime or datetime.datetime object. 

860 """ 

861 

862 year = value.year - 2000 

863 

864 if year > 0: 

865 new_cls = datetime 

866 else: 

867 new_cls = cls 

868 

869 return new_cls( 

870 year, 

871 value.month, 

872 value.day, 

873 value.hour, 

874 value.minute, 

875 value.second, 

876 value.microsecond, 

877 value.tzinfo 

878 )