"""
"""
# Created on 2014.10.28
#
# Author: Giovanni Cannata
#
# Copyright 2014 - 2018 Giovanni Cannata
#
# This file is part of ldap3.
#
# ldap3 is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ldap3 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ldap3 in the COPYING and COPYING.LESSER files.
# If not, see .
import re
from binascii import hexlify
from uuid import UUID
from datetime import datetime, timedelta
from ...utils.conv import to_unicode
from ...core.timezone import OffsetTzInfo
def format_unicode(raw_value):
try:
if str is not bytes: # Python 3
return str(raw_value, 'utf-8', errors='strict')
else: # Python 2
return unicode(raw_value, 'utf-8', errors='strict')
except (TypeError, UnicodeDecodeError):
pass
return raw_value
def format_integer(raw_value):
try:
return int(raw_value)
except (TypeError, ValueError): # expected exceptions
pass
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
pass
return raw_value
def format_binary(raw_value):
try:
return bytes(raw_value)
except TypeError: # expected exceptions
pass
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
pass
return raw_value
def format_uuid(raw_value):
try:
return str(UUID(bytes=raw_value))
except (TypeError, ValueError):
return format_unicode(raw_value)
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
pass
return raw_value
def format_uuid_le(raw_value):
try:
return '{' + str(UUID(bytes_le=raw_value)) + '}'
except (TypeError, ValueError):
return format_unicode(raw_value)
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
pass
return raw_value
def format_boolean(raw_value):
if raw_value in [b'TRUE', b'true', b'True']:
return True
if raw_value in [b'FALSE', b'false', b'False']:
return False
return raw_value
def format_ad_timestamp(raw_value):
"""
Active Directory stores date/time values as the number of 100-nanosecond intervals
that have elapsed since the 0 hour on January 1, 1601 till the date/time that is being stored.
The time is always stored in Greenwich Mean Time (GMT) in the Active Directory.
"""
if raw_value == b'9223372036854775807': # max value to be stored in a 64 bit signed int
return datetime.max # returns datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
try:
timestamp = int(raw_value)
if timestamp < 0: # ad timestamp cannot be negative
return raw_value
except Exception:
return raw_value
try:
return datetime.fromtimestamp(timestamp / 10000000.0 - 11644473600, tz=OffsetTzInfo(0, 'UTC')) # forces true division in python 2
except (OSError, OverflowError, ValueError): # on Windows backwards timestamps are not allowed
try:
unix_epoch = datetime.fromtimestamp(0, tz=OffsetTzInfo(0, 'UTC'))
diff_seconds = timedelta(seconds=timestamp/10000000.0 - 11644473600)
return unix_epoch + diff_seconds
except Exception:
pass
except Exception:
pass
return raw_value
try: # uses regular expressions and the timezone class (python3.2 and later)
from datetime import timezone
time_format = re.compile(
r'''
^
(?P[0-9]{4})
(?P0[1-9]|1[0-2])
(?P0[1-9]|[12][0-9]|3[01])
(?P[01][0-9]|2[0-3])
(?:
(?P[0-5][0-9])
(?P[0-5][0-9]|60)?
)?
(?:
[.,]
(?P[0-9]+)
)?
(?:
Z
|
(?:
(?P[+-])
(?P[01][0-9]|2[0-3])
(?P[0-5][0-9])?
)
)
$
''',
re.VERBOSE
)
def format_time(raw_value):
try:
match = time_format.fullmatch(to_unicode(raw_value))
if match is None:
return raw_value
matches = match.groupdict()
offset = timedelta(
hours=int(matches['OffHour'] or 0),
minutes=int(matches['OffMinute'] or 0)
)
if matches['Offset'] == '-':
offset *= -1
# Python does not support leap second in datetime (!)
if matches['Second'] == '60':
matches['Second'] = '59'
# According to RFC, fraction may be applied to an Hour/Minute (!)
fraction = float('0.' + (matches['Fraction'] or '0'))
if matches['Minute'] is None:
fraction *= 60
minute = int(fraction)
fraction -= minute
else:
minute = int(matches['Minute'])
if matches['Second'] is None:
fraction *= 60
second = int(fraction)
fraction -= second
else:
second = int(matches['Second'])
microseconds = int(fraction * 1000000)
return datetime(
int(matches['Year']),
int(matches['Month']),
int(matches['Day']),
int(matches['Hour']),
minute,
second,
microseconds,
timezone(offset),
)
except Exception: # exceptions should be investigated, anyway the formatter return the raw_value
pass
return raw_value
except ImportError:
def format_time(raw_value):
"""
From RFC4517:
A value of the Generalized Time syntax is a character string
representing a date and time. The LDAP-specific encoding of a value
of this syntax is a restriction of the format defined in [ISO8601],
and is described by the following ABNF:
GeneralizedTime = century year month day hour
[ minute [ second / leap-second ] ]
[ fraction ]
g-time-zone
century = 2(%x30-39) ; "00" to "99"
year = 2(%x30-39) ; "00" to "99"
month = ( %x30 %x31-39 ) ; "01" (January) to "09"
/ ( %x31 %x30-32 ) ; "10" to "12"
day = ( %x30 %x31-39 ) ; "01" to "09"
/ ( %x31-32 %x30-39 ) ; "10" to "29"
/ ( %x33 %x30-31 ) ; "30" to "31"
hour = ( %x30-31 %x30-39 ) / ( %x32 %x30-33 ) ; "00" to "23"
minute = %x30-35 %x30-39 ; "00" to "59"
second = ( %x30-35 %x30-39 ) ; "00" to "59"
leap-second = ( %x36 %x30 ) ; "60"
fraction = ( DOT / COMMA ) 1*(%x30-39)
g-time-zone = %x5A ; "Z"
/ g-differential
g-differential = ( MINUS / PLUS ) hour [ minute ]
MINUS = %x2D ; minus sign ("-")
"""
if len(raw_value) < 10 or not all((c in b'0123456789+-,.Z' for c in raw_value)) or (b'Z' in raw_value and not raw_value.endswith(b'Z')): # first ten characters are mandatory and must be numeric or timezone or fraction
return raw_value
# sets position for fixed values
year = int(raw_value[0: 4])
month = int(raw_value[4: 6])
day = int(raw_value[6: 8])
hour = int(raw_value[8: 10])
minute = 0
second = 0
microsecond = 0
remain = raw_value[10:]
if remain and remain.endswith(b'Z'): # uppercase 'Z'
sep = b'Z'
elif b'+' in remain: # timezone can be specified with +hh[mm] or -hh[mm]
sep = b'+'
elif b'-' in remain:
sep = b'-'
else: # timezone not specified
return raw_value
time, _, offset = remain.partition(sep)
if time and (b'.' in time or b',' in time):
# fraction time
if time[0] in b',.':
minute = 6 * int(time[1] if str is bytes else chr(time[1])) # Python 2 / Python 3
elif time[2] in b',.':
minute = int(raw_value[10: 12])
second = 6 * int(time[3] if str is bytes else chr(time[3])) # Python 2 / Python 3
elif time[4] in b',.':
minute = int(raw_value[10: 12])
second = int(raw_value[12: 14])
microsecond = 100000 * int(time[5] if str is bytes else chr(time[5])) # Python 2 / Python 3
elif len(time) == 2: # mmZ format
minute = int(raw_value[10: 12])
elif len(time) == 0: # Z format
pass
elif len(time) == 4: # mmssZ
minute = int(raw_value[10: 12])
second = int(raw_value[12: 14])
else:
return raw_value
if sep == b'Z': # UTC
timezone = OffsetTzInfo(0, 'UTC')
else: # build timezone
try:
if len(offset) == 2:
timezone_hour = int(offset[:2])
timezone_minute = 0
elif len(offset) == 4:
timezone_hour = int(offset[:2])
timezone_minute = int(offset[2:4])
else: # malformed timezone
raise ValueError
except ValueError:
return raw_value
if timezone_hour > 23 or timezone_minute > 59: # invalid timezone
return raw_value
if str is not bytes: # Python 3
timezone = OffsetTzInfo((timezone_hour * 60 + timezone_minute) * (1 if sep == b'+' else -1), 'UTC' + str(sep + offset, encoding='utf-8'))
else: # Python 2
timezone = OffsetTzInfo((timezone_hour * 60 + timezone_minute) * (1 if sep == b'+' else -1), unicode('UTC' + sep + offset, encoding='utf-8'))
try:
return datetime(year=year,
month=month,
day=day,
hour=hour,
minute=minute,
second=second,
microsecond=microsecond,
tzinfo=timezone)
except (TypeError, ValueError):
pass
return raw_value
def format_time_with_0_year(raw_value):
try:
if raw_value.startswith(b'0000'):
return raw_value
except Exception:
try:
if raw_value.startswith('0000'):
return raw_value
except Exception:
pass
return format_time(raw_value)
def format_sid(raw_value):
"""
SID= "S-1-" IdentifierAuthority 1*SubAuthority
IdentifierAuthority= IdentifierAuthorityDec / IdentifierAuthorityHex
; If the identifier authority is < 2^32, the
; identifier authority is represented as a decimal
; number
; If the identifier authority is >= 2^32,
; the identifier authority is represented in
; hexadecimal
IdentifierAuthorityDec = 1*10DIGIT
; IdentifierAuthorityDec, top level authority of a
; security identifier is represented as a decimal number
IdentifierAuthorityHex = "0x" 12HEXDIG
; IdentifierAuthorityHex, the top-level authority of a
; security identifier is represented as a hexadecimal number
SubAuthority= "-" 1*10DIGIT
; Sub-Authority is always represented as a decimal number
; No leading "0" characters are allowed when IdentifierAuthority
; or SubAuthority is represented as a decimal number
; All hexadecimal digits must be output in string format,
; pre-pended by "0x"
Revision (1 byte): An 8-bit unsigned integer that specifies the revision level of the SID. This value MUST be set to 0x01.
SubAuthorityCount (1 byte): An 8-bit unsigned integer that specifies the number of elements in the SubAuthority array. The maximum number of elements allowed is 15.
IdentifierAuthority (6 bytes): A SID_IDENTIFIER_AUTHORITY structure that indicates the authority under which the SID was created. It describes the entity that created the SID. The Identifier Authority value {0,0,0,0,0,5} denotes SIDs created by the NT SID authority.
SubAuthority (variable): A variable length array of unsigned 32-bit integers that uniquely identifies a principal relative to the IdentifierAuthority. Its length is determined by SubAuthorityCount.
"""
try:
if str is not bytes: # Python 3
revision = int(raw_value[0])
sub_authority_count = int(raw_value[1])
identifier_authority = int.from_bytes(raw_value[2:8], byteorder='big')
if identifier_authority >= 4294967296: # 2 ^ 32
identifier_authority = hex(identifier_authority)
sub_authority = ''
i = 0
while i < sub_authority_count:
sub_authority += '-' + str(int.from_bytes(raw_value[8 + (i * 4): 12 + (i * 4)], byteorder='little')) # little endian
i += 1
else: # Python 2
revision = int(ord(raw_value[0]))
sub_authority_count = int(ord(raw_value[1]))
identifier_authority = int(hexlify(raw_value[2:8]), 16)
if identifier_authority >= 4294967296: # 2 ^ 32
identifier_authority = hex(identifier_authority)
sub_authority = ''
i = 0
while i < sub_authority_count:
sub_authority += '-' + str(int(hexlify(raw_value[11 + (i * 4): 7 + (i * 4): -1]), 16)) # little endian
i += 1
return 'S-' + str(revision) + '-' + str(identifier_authority) + sub_authority
except Exception: # any exception should be investigated, anyway the formatter return the raw_value
pass
return raw_value