codekingpro/portable-devtools
114k
1"""2"""3 4# Created on 2014.04.265#6# Author: Giovanni Cannata7#8# Copyright 2014 - 2020 Giovanni Cannata9#10# This file is part of ldap3.11#12# ldap3 is free software: you can redistribute it and/or modify13# it under the terms of the GNU Lesser General Public License as published14# by the Free Software Foundation, either version 3 of the License, or15# (at your option) any later version.16#17# ldap3 is distributed in the hope that it will be useful,18# but WITHOUT ANY WARRANTY; without even the implied warranty of19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the20# GNU Lesser General Public License for more details.21#22# You should have received a copy of the GNU Lesser General Public License23# along with ldap3 in the COPYING and COPYING.LESSER files.24# If not, see <http://www.gnu.org/licenses/>.25 26from base64 import b64encode, b64decode27import datetime28import re29 30from .. import SEQUENCE_TYPES, STRING_TYPES, NUMERIC_TYPES, get_config_parameter31from ..utils.ciDict import CaseInsensitiveDict32from ..core.exceptions import LDAPDefinitionError33 34 35def to_unicode(obj, encoding=None, from_server=False):36 """Try to convert bytes (and str in python2) to unicode.37 Return object unmodified if python3 string, else raise an exception38 """39 conf_default_client_encoding = get_config_parameter('DEFAULT_CLIENT_ENCODING')40 conf_default_server_encoding = get_config_parameter('DEFAULT_SERVER_ENCODING')41 conf_additional_server_encodings = get_config_parameter('ADDITIONAL_SERVER_ENCODINGS')42 conf_additional_client_encodings = get_config_parameter('ADDITIONAL_CLIENT_ENCODINGS')43 if isinstance(obj, NUMERIC_TYPES):44 obj = str(obj)45 46 if isinstance(obj, (bytes, bytearray)):47 if from_server: # data from server48 if encoding is None:49 encoding = conf_default_server_encoding50 try:51 return obj.decode(encoding)52 except UnicodeDecodeError:53 for encoding in conf_additional_server_encodings: # AD could have DN not encoded in utf-8 (even if this is not allowed by RFC4510)54 try:55 return obj.decode(encoding)56 except UnicodeDecodeError:57 pass58 raise UnicodeError("Unable to convert server data to unicode: %r" % obj)59 else: # data from client60 if encoding is None:61 encoding = conf_default_client_encoding62 try:63 return obj.decode(encoding)64 except UnicodeDecodeError:65 for encoding in conf_additional_client_encodings: # tries additional encodings66 try:67 return obj.decode(encoding)68 except UnicodeDecodeError:69 pass70 raise UnicodeError("Unable to convert client data to unicode: %r" % obj)71 72 if isinstance(obj, STRING_TYPES): # python3 strings, python 2 unicode73 return obj74 75 raise UnicodeError("Unable to convert type %s to unicode: %r" % (obj.__class__.__name__, obj))76 77 78def to_raw(obj, encoding='utf-8'):79 """Tries to convert to raw bytes from unicode"""80 if isinstance(obj, NUMERIC_TYPES):81 obj = str(obj)82 83 if not (isinstance(obj, bytes)):84 if isinstance(obj, SEQUENCE_TYPES):85 return [to_raw(element) for element in obj]86 elif isinstance(obj, STRING_TYPES):87 return obj.encode(encoding)88 return obj89 90 91def escape_filter_chars(text, encoding=None):92 """ Escape chars mentioned in RFC4515. """93 if encoding is None:94 encoding = get_config_parameter('DEFAULT_ENCODING')95 96 try:97 text = to_unicode(text, encoding)98 escaped = text.replace('\\', '\\5c')99 escaped = escaped.replace('*', '\\2a')100 escaped = escaped.replace('(', '\\28')101 escaped = escaped.replace(')', '\\29')102 escaped = escaped.replace('\x00', '\\00')103 except Exception: # probably raw bytes values, return escaped bytes value104 escaped = to_unicode(escape_bytes(text))105 # escape all octets greater than 0x7F that are not part of a valid UTF-8106 # escaped = ''.join(c if c <= ord(b'\x7f') else escape_bytes(to_raw(to_unicode(c, encoding))) for c in escaped)107 return escaped108 109 110def unescape_filter_chars(text, encoding=None):111 """ unescape chars mentioned in RFC4515. """112 if encoding is None:113 encoding = get_config_parameter('DEFAULT_ENCODING')114 115 unescaped = to_raw(text, encoding)116 unescaped = unescaped.replace(b'\\5c', b'\\')117 unescaped = unescaped.replace(b'\\5C', b'\\')118 unescaped = unescaped.replace(b'\\2a', b'*')119 unescaped = unescaped.replace(b'\\2A', b'*')120 unescaped = unescaped.replace(b'\\28', b'(')121 unescaped = unescaped.replace(b'\\29', b')')122 unescaped = unescaped.replace(b'\\00', b'\x00')123 return unescaped124 125 126def escape_bytes(bytes_value):127 """ Convert a byte sequence to a properly escaped for LDAP (format BACKSLASH HEX HEX) string"""128 if bytes_value:129 if str is not bytes: # Python 3130 if isinstance(bytes_value, str):131 bytes_value = bytearray(bytes_value, encoding='utf-8')132 escaped = '\\'.join([('%02x' % int(b)) for b in bytes_value])133 else: # Python 2134 if isinstance(bytes_value, unicode):135 bytes_value = bytes_value.encode('utf-8')136 escaped = '\\'.join([('%02x' % ord(b)) for b in bytes_value])137 else:138 escaped = ''139 140 return ('\\' + escaped) if escaped else ''141 142 143def prepare_for_stream(value):144 if str is not bytes: # Python 3145 return value146 else: # Python 2147 return value.decode()148 149 150def json_encode_b64(obj):151 try:152 return dict(encoding='base64', encoded=b64encode(obj))153 except Exception as e:154 raise LDAPDefinitionError('unable to encode ' + str(obj) + ' - ' + str(e))155 156 157# noinspection PyProtectedMember158def check_json_dict(json_dict):159 # needed for python 2160 161 for k, v in json_dict.items():162 if isinstance(v, dict):163 check_json_dict(v)164 elif isinstance(v, CaseInsensitiveDict):165 check_json_dict(v._store)166 elif isinstance(v, SEQUENCE_TYPES):167 for i, e in enumerate(v):168 if isinstance(e, dict):169 check_json_dict(e)170 elif isinstance(e, CaseInsensitiveDict):171 check_json_dict(e._store)172 else:173 v[i] = format_json(e)174 else:175 json_dict[k] = format_json(v)176 177 178def json_hook(obj):179 if hasattr(obj, 'keys') and len(list(obj.keys())) == 2 and 'encoding' in obj.keys() and 'encoded' in obj.keys():180 return b64decode(obj['encoded'])181 182 return obj183 184 185# noinspection PyProtectedMember186def format_json(obj, iso_format=False):187 if isinstance(obj, CaseInsensitiveDict):188 return obj._store189 190 if isinstance(obj, datetime.datetime):191 return str(obj)192 193 if isinstance(obj, int):194 return obj195 196 if isinstance(obj, datetime.timedelta):197 if iso_format:198 return obj.isoformat()199 return str(obj)200 201 if str is bytes: # Python 2202 if isinstance(obj, long): # long exists only in python2203 return obj204 205 try:206 if str is not bytes: # Python 3207 if isinstance(obj, bytes):208 # return check_escape(str(obj, 'utf-8', errors='strict'))209 return str(obj, 'utf-8', errors='strict')210 raise LDAPDefinitionError('unable to serialize ' + str(obj))211 else: # Python 2212 if isinstance(obj, unicode):213 return obj214 else:215 # return unicode(check_escape(obj))216 return unicode(obj)217 except (TypeError, UnicodeDecodeError):218 pass219 220 try:221 return json_encode_b64(bytes(obj))222 except Exception:223 pass224 225 raise LDAPDefinitionError('unable to serialize ' + str(obj))226 227 228def is_filter_escaped(text):229 if not type(text) == ((str is not bytes) and str or unicode): # requires str for Python 3 and unicode for Python 2230 raise ValueError('unicode input expected')231 232 return all(c not in text for c in '()*\0') and not re.search('\\\\([^0-9a-fA-F]|(.[^0-9a-fA-F]))', text)233 234 235def ldap_escape_to_bytes(text):236 bytesequence = bytearray()237 i = 0238 try:239 if isinstance(text, STRING_TYPES):240 while i < len(text):241 if text[i] == '\\':242 if len(text) > i + 2:243 try:244 bytesequence.append(int(text[i+1:i+3], 16))245 i += 3246 continue247 except ValueError:248 pass249 bytesequence.append(92) # "\" ASCII code250 else:251 raw = to_raw(text[i])252 for c in raw:253 bytesequence.append(c)254 i += 1255 elif isinstance(text, (bytes, bytearray)):256 while i < len(text):257 if text[i] == 92: # "\" ASCII code258 if len(text) > i + 2:259 try:260 bytesequence.append(int(text[i + 1:i + 3], 16))261 i += 3262 continue263 except ValueError:264 pass265 bytesequence.append(92) # "\" ASCII code266 else:267 bytesequence.append(text[i])268 i += 1269 except Exception:270 raise LDAPDefinitionError('badly formatted LDAP byte escaped sequence')271 272 return bytes(bytesequence)273 