You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

712 lines
18 KiB

4 years ago
  1. # coding: utf-8
  2. """
  3. Miscellaneous data helpers, including functions for converting integers to and
  4. from bytes and UTC timezone. Exports the following items:
  5. - OrderedDict()
  6. - int_from_bytes()
  7. - int_to_bytes()
  8. - timezone.utc
  9. - inet_ntop()
  10. - inet_pton()
  11. - uri_to_iri()
  12. - iri_to_uri()
  13. """
  14. from __future__ import unicode_literals, division, absolute_import, print_function
  15. import math
  16. import sys
  17. from datetime import datetime, date, time
  18. from ._errors import unwrap
  19. from ._iri import iri_to_uri, uri_to_iri # noqa
  20. from ._ordereddict import OrderedDict # noqa
  21. from ._types import type_name
  22. if sys.platform == 'win32':
  23. from ._inet import inet_ntop, inet_pton
  24. else:
  25. from socket import inet_ntop, inet_pton # noqa
  26. # Python 2
  27. if sys.version_info <= (3,):
  28. from datetime import timedelta, tzinfo
  29. py2 = True
  30. def int_to_bytes(value, signed=False, width=None):
  31. """
  32. Converts an integer to a byte string
  33. :param value:
  34. The integer to convert
  35. :param signed:
  36. If the byte string should be encoded using two's complement
  37. :param width:
  38. None == auto, otherwise an integer of the byte width for the return
  39. value
  40. :return:
  41. A byte string
  42. """
  43. # Handle negatives in two's complement
  44. is_neg = False
  45. if signed and value < 0:
  46. is_neg = True
  47. bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8)
  48. value = (value + (1 << bits)) % (1 << bits)
  49. hex_str = '%x' % value
  50. if len(hex_str) & 1:
  51. hex_str = '0' + hex_str
  52. output = hex_str.decode('hex')
  53. if signed and not is_neg and ord(output[0:1]) & 0x80:
  54. output = b'\x00' + output
  55. if width is not None:
  56. if is_neg:
  57. pad_char = b'\xFF'
  58. else:
  59. pad_char = b'\x00'
  60. output = (pad_char * (width - len(output))) + output
  61. elif is_neg and ord(output[0:1]) & 0x80 == 0:
  62. output = b'\xFF' + output
  63. return output
  64. def int_from_bytes(value, signed=False):
  65. """
  66. Converts a byte string to an integer
  67. :param value:
  68. The byte string to convert
  69. :param signed:
  70. If the byte string should be interpreted using two's complement
  71. :return:
  72. An integer
  73. """
  74. if value == b'':
  75. return 0
  76. num = long(value.encode("hex"), 16) # noqa
  77. if not signed:
  78. return num
  79. # Check for sign bit and handle two's complement
  80. if ord(value[0:1]) & 0x80:
  81. bit_len = len(value) * 8
  82. return num - (1 << bit_len)
  83. return num
  84. class utc(tzinfo): # noqa
  85. def tzname(self, _):
  86. return b'UTC+00:00'
  87. def utcoffset(self, _):
  88. return timedelta(0)
  89. def dst(self, _):
  90. return timedelta(0)
  91. class timezone(): # noqa
  92. utc = utc()
  93. # Python 3
  94. else:
  95. from datetime import timezone # noqa
  96. py2 = False
  97. def int_to_bytes(value, signed=False, width=None):
  98. """
  99. Converts an integer to a byte string
  100. :param value:
  101. The integer to convert
  102. :param signed:
  103. If the byte string should be encoded using two's complement
  104. :param width:
  105. None == auto, otherwise an integer of the byte width for the return
  106. value
  107. :return:
  108. A byte string
  109. """
  110. if width is None:
  111. if signed:
  112. if value < 0:
  113. bits_required = abs(value + 1).bit_length()
  114. else:
  115. bits_required = value.bit_length()
  116. if bits_required % 8 == 0:
  117. bits_required += 1
  118. else:
  119. bits_required = value.bit_length()
  120. width = math.ceil(bits_required / 8) or 1
  121. return value.to_bytes(width, byteorder='big', signed=signed)
  122. def int_from_bytes(value, signed=False):
  123. """
  124. Converts a byte string to an integer
  125. :param value:
  126. The byte string to convert
  127. :param signed:
  128. If the byte string should be interpreted using two's complement
  129. :return:
  130. An integer
  131. """
  132. return int.from_bytes(value, 'big', signed=signed)
  133. _DAYS_PER_MONTH_YEAR_0 = {
  134. 1: 31,
  135. 2: 29, # Year 0 was a leap year
  136. 3: 31,
  137. 4: 30,
  138. 5: 31,
  139. 6: 30,
  140. 7: 31,
  141. 8: 31,
  142. 9: 30,
  143. 10: 31,
  144. 11: 30,
  145. 12: 31
  146. }
  147. class extended_date(object):
  148. """
  149. A datetime.date-like object that can represent the year 0. This is just
  150. to handle 0000-01-01 found in some certificates.
  151. """
  152. year = None
  153. month = None
  154. day = None
  155. def __init__(self, year, month, day):
  156. """
  157. :param year:
  158. The integer 0
  159. :param month:
  160. An integer from 1 to 12
  161. :param day:
  162. An integer from 1 to 31
  163. """
  164. if year != 0:
  165. raise ValueError('year must be 0')
  166. if month < 1 or month > 12:
  167. raise ValueError('month is out of range')
  168. if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]:
  169. raise ValueError('day is out of range')
  170. self.year = year
  171. self.month = month
  172. self.day = day
  173. def _format(self, format):
  174. """
  175. Performs strftime(), always returning a unicode string
  176. :param format:
  177. A strftime() format string
  178. :return:
  179. A unicode string of the formatted date
  180. """
  181. format = format.replace('%Y', '0000')
  182. # Year 0 is 1BC and a leap year. Leap years repeat themselves
  183. # every 28 years. Because of adjustments and the proleptic gregorian
  184. # calendar, the simplest way to format is to substitute year 2000.
  185. temp = date(2000, self.month, self.day)
  186. if '%c' in format:
  187. c_out = temp.strftime('%c')
  188. # Handle full years
  189. c_out = c_out.replace('2000', '0000')
  190. c_out = c_out.replace('%', '%%')
  191. format = format.replace('%c', c_out)
  192. if '%x' in format:
  193. x_out = temp.strftime('%x')
  194. # Handle formats such as 08/16/2000 or 16.08.2000
  195. x_out = x_out.replace('2000', '0000')
  196. x_out = x_out.replace('%', '%%')
  197. format = format.replace('%x', x_out)
  198. return temp.strftime(format)
  199. def isoformat(self):
  200. """
  201. Formats the date as %Y-%m-%d
  202. :return:
  203. The date formatted to %Y-%m-%d as a unicode string in Python 3
  204. and a byte string in Python 2
  205. """
  206. return self.strftime('0000-%m-%d')
  207. def strftime(self, format):
  208. """
  209. Formats the date using strftime()
  210. :param format:
  211. The strftime() format string
  212. :return:
  213. The formatted date as a unicode string in Python 3 and a byte
  214. string in Python 2
  215. """
  216. output = self._format(format)
  217. if py2:
  218. return output.encode('utf-8')
  219. return output
  220. def replace(self, year=None, month=None, day=None):
  221. """
  222. Returns a new datetime.date or asn1crypto.util.extended_date
  223. object with the specified components replaced
  224. :return:
  225. A datetime.date or asn1crypto.util.extended_date object
  226. """
  227. if year is None:
  228. year = self.year
  229. if month is None:
  230. month = self.month
  231. if day is None:
  232. day = self.day
  233. if year > 0:
  234. cls = date
  235. else:
  236. cls = extended_date
  237. return cls(
  238. year,
  239. month,
  240. day
  241. )
  242. def __str__(self):
  243. if py2:
  244. return self.__bytes__()
  245. else:
  246. return self.__unicode__()
  247. def __bytes__(self):
  248. return self.__unicode__().encode('utf-8')
  249. def __unicode__(self):
  250. return self._format('%Y-%m-%d')
  251. def __eq__(self, other):
  252. if not isinstance(other, self.__class__):
  253. return False
  254. return self.__cmp__(other) == 0
  255. def __ne__(self, other):
  256. return not self.__eq__(other)
  257. def _comparison_error(self, other):
  258. raise TypeError(unwrap(
  259. '''
  260. An asn1crypto.util.extended_date object can only be compared to
  261. an asn1crypto.util.extended_date or datetime.date object, not %s
  262. ''',
  263. type_name(other)
  264. ))
  265. def __cmp__(self, other):
  266. if isinstance(other, date):
  267. return -1
  268. if not isinstance(other, self.__class__):
  269. self._comparison_error(other)
  270. st = (
  271. self.year,
  272. self.month,
  273. self.day
  274. )
  275. ot = (
  276. other.year,
  277. other.month,
  278. other.day
  279. )
  280. if st < ot:
  281. return -1
  282. if st > ot:
  283. return 1
  284. return 0
  285. def __lt__(self, other):
  286. return self.__cmp__(other) < 0
  287. def __le__(self, other):
  288. return self.__cmp__(other) <= 0
  289. def __gt__(self, other):
  290. return self.__cmp__(other) > 0
  291. def __ge__(self, other):
  292. return self.__cmp__(other) >= 0
  293. class extended_datetime(object):
  294. """
  295. A datetime.datetime-like object that can represent the year 0. This is just
  296. to handle 0000-01-01 found in some certificates.
  297. """
  298. year = None
  299. month = None
  300. day = None
  301. hour = None
  302. minute = None
  303. second = None
  304. microsecond = None
  305. tzinfo = None
  306. def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
  307. """
  308. :param year:
  309. The integer 0
  310. :param month:
  311. An integer from 1 to 12
  312. :param day:
  313. An integer from 1 to 31
  314. :param hour:
  315. An integer from 0 to 23
  316. :param minute:
  317. An integer from 0 to 59
  318. :param second:
  319. An integer from 0 to 59
  320. :param microsecond:
  321. An integer from 0 to 999999
  322. """
  323. if year != 0:
  324. raise ValueError('year must be 0')
  325. if month < 1 or month > 12:
  326. raise ValueError('month is out of range')
  327. if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]:
  328. raise ValueError('day is out of range')
  329. if hour < 0 or hour > 23:
  330. raise ValueError('hour is out of range')
  331. if minute < 0 or minute > 59:
  332. raise ValueError('minute is out of range')
  333. if second < 0 or second > 59:
  334. raise ValueError('second is out of range')
  335. if microsecond < 0 or microsecond > 999999:
  336. raise ValueError('microsecond is out of range')
  337. self.year = year
  338. self.month = month
  339. self.day = day
  340. self.hour = hour
  341. self.minute = minute
  342. self.second = second
  343. self.microsecond = microsecond
  344. self.tzinfo = tzinfo
  345. def date(self):
  346. """
  347. :return:
  348. An asn1crypto.util.extended_date of the date
  349. """
  350. return extended_date(self.year, self.month, self.day)
  351. def time(self):
  352. """
  353. :return:
  354. A datetime.time object of the time
  355. """
  356. return time(self.hour, self.minute, self.second, self.microsecond, self.tzinfo)
  357. def utcoffset(self):
  358. """
  359. :return:
  360. None or a datetime.timedelta() of the offset from UTC
  361. """
  362. if self.tzinfo is None:
  363. return None
  364. return self.tzinfo.utcoffset(self.replace(year=2000))
  365. def dst(self):
  366. """
  367. :return:
  368. None or a datetime.timedelta() of the daylight savings time offset
  369. """
  370. if self.tzinfo is None:
  371. return None
  372. return self.tzinfo.dst(self.replace(year=2000))
  373. def tzname(self):
  374. """
  375. :return:
  376. None or the name of the timezone as a unicode string in Python 3
  377. and a byte string in Python 2
  378. """
  379. if self.tzinfo is None:
  380. return None
  381. return self.tzinfo.tzname(self.replace(year=2000))
  382. def _format(self, format):
  383. """
  384. Performs strftime(), always returning a unicode string
  385. :param format:
  386. A strftime() format string
  387. :return:
  388. A unicode string of the formatted datetime
  389. """
  390. format = format.replace('%Y', '0000')
  391. # Year 0 is 1BC and a leap year. Leap years repeat themselves
  392. # every 28 years. Because of adjustments and the proleptic gregorian
  393. # calendar, the simplest way to format is to substitute year 2000.
  394. temp = datetime(
  395. 2000,
  396. self.month,
  397. self.day,
  398. self.hour,
  399. self.minute,
  400. self.second,
  401. self.microsecond,
  402. self.tzinfo
  403. )
  404. if '%c' in format:
  405. c_out = temp.strftime('%c')
  406. # Handle full years
  407. c_out = c_out.replace('2000', '0000')
  408. c_out = c_out.replace('%', '%%')
  409. format = format.replace('%c', c_out)
  410. if '%x' in format:
  411. x_out = temp.strftime('%x')
  412. # Handle formats such as 08/16/2000 or 16.08.2000
  413. x_out = x_out.replace('2000', '0000')
  414. x_out = x_out.replace('%', '%%')
  415. format = format.replace('%x', x_out)
  416. return temp.strftime(format)
  417. def isoformat(self, sep='T'):
  418. """
  419. Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the
  420. date and time portions
  421. :param set:
  422. A single character of the separator to place between the date and
  423. time
  424. :return:
  425. The formatted datetime as a unicode string in Python 3 and a byte
  426. string in Python 2
  427. """
  428. if self.microsecond == 0:
  429. return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S' % sep)
  430. return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S.%%f' % sep)
  431. def strftime(self, format):
  432. """
  433. Formats the date using strftime()
  434. :param format:
  435. The strftime() format string
  436. :return:
  437. The formatted date as a unicode string in Python 3 and a byte
  438. string in Python 2
  439. """
  440. output = self._format(format)
  441. if py2:
  442. return output.encode('utf-8')
  443. return output
  444. def replace(self, year=None, month=None, day=None, hour=None, minute=None,
  445. second=None, microsecond=None, tzinfo=None):
  446. """
  447. Returns a new datetime.datetime or asn1crypto.util.extended_datetime
  448. object with the specified components replaced
  449. :return:
  450. A datetime.datetime or asn1crypto.util.extended_datetime object
  451. """
  452. if year is None:
  453. year = self.year
  454. if month is None:
  455. month = self.month
  456. if day is None:
  457. day = self.day
  458. if hour is None:
  459. hour = self.hour
  460. if minute is None:
  461. minute = self.minute
  462. if second is None:
  463. second = self.second
  464. if microsecond is None:
  465. microsecond = self.microsecond
  466. if tzinfo is None:
  467. tzinfo = self.tzinfo
  468. if year > 0:
  469. cls = datetime
  470. else:
  471. cls = extended_datetime
  472. return cls(
  473. year,
  474. month,
  475. day,
  476. hour,
  477. minute,
  478. second,
  479. microsecond,
  480. tzinfo
  481. )
  482. def __str__(self):
  483. if py2:
  484. return self.__bytes__()
  485. else:
  486. return self.__unicode__()
  487. def __bytes__(self):
  488. return self.__unicode__().encode('utf-8')
  489. def __unicode__(self):
  490. format = '%Y-%m-%d %H:%M:%S'
  491. if self.microsecond != 0:
  492. format += '.%f'
  493. return self._format(format)
  494. def __eq__(self, other):
  495. if not isinstance(other, self.__class__):
  496. return False
  497. return self.__cmp__(other) == 0
  498. def __ne__(self, other):
  499. return not self.__eq__(other)
  500. def _comparison_error(self, other):
  501. """
  502. Raises a TypeError about the other object not being suitable for
  503. comparison
  504. :param other:
  505. The object being compared to
  506. """
  507. raise TypeError(unwrap(
  508. '''
  509. An asn1crypto.util.extended_datetime object can only be compared to
  510. an asn1crypto.util.extended_datetime or datetime.datetime object,
  511. not %s
  512. ''',
  513. type_name(other)
  514. ))
  515. def __cmp__(self, other):
  516. so = self.utcoffset()
  517. oo = other.utcoffset()
  518. if (so is not None and oo is None) or (so is None and oo is not None):
  519. raise TypeError("can't compare offset-naive and offset-aware datetimes")
  520. if isinstance(other, datetime):
  521. return -1
  522. if not isinstance(other, self.__class__):
  523. self._comparison_error(other)
  524. st = (
  525. self.year,
  526. self.month,
  527. self.day,
  528. self.hour,
  529. self.minute,
  530. self.second,
  531. self.microsecond,
  532. so
  533. )
  534. ot = (
  535. other.year,
  536. other.month,
  537. other.day,
  538. other.hour,
  539. other.minute,
  540. other.second,
  541. other.microsecond,
  542. oo
  543. )
  544. if st < ot:
  545. return -1
  546. if st > ot:
  547. return 1
  548. return 0
  549. def __lt__(self, other):
  550. return self.__cmp__(other) < 0
  551. def __le__(self, other):
  552. return self.__cmp__(other) <= 0
  553. def __gt__(self, other):
  554. return self.__cmp__(other) > 0
  555. def __ge__(self, other):
  556. return self.__cmp__(other) >= 0