1
# Copyright 2011 OpenStack Foundation.
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5
# not use this file except in compliance with the License. You may obtain
6
# a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
# License for the specific language governing permissions and limitations
17
System-level utilities and helper functions.
27
from ironic.openstack.common.gettextutils import _
30
UNIT_PREFIX_EXPONENT = {
42
'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
43
'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
46
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
47
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
49
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
50
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
53
# NOTE(flaper87): The following 3 globals are used by `mask_password`
54
_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
56
# NOTE(ldbragst): Let's build a list of regex objects using the list of
57
# _SANITIZE_KEYS we already have. This way, we only have to add the new key
58
# to the list of _SANITIZE_KEYS and we can generate regular expressions
59
# for XML and JSON automatically.
60
_SANITIZE_PATTERNS = []
61
_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
62
r'(<%(key)s>).*?(</%(key)s>)',
63
r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
64
r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])',
65
r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?[\'"])'
67
r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)']
69
for key in _SANITIZE_KEYS:
70
for pattern in _FORMAT_PATTERNS:
71
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
72
_SANITIZE_PATTERNS.append(reg_ex)
75
def int_from_bool_as_string(subject):
76
"""Interpret a string as a boolean and return either 1 or 0.
80
('True', 'true', 'On', 'on', '1')
82
is interpreted as a boolean True.
84
Useful for JSON-decoded stuff and config file parsing
86
return bool_from_string(subject) and 1 or 0
89
def bool_from_string(subject, strict=False, default=False):
90
"""Interpret a string as a boolean.
92
A case-insensitive match is performed such that strings matching 't',
93
'true', 'on', 'y', 'yes', or '1' are considered True and, when
94
`strict=False`, anything else returns the value specified by 'default'.
96
Useful for JSON-decoded stuff and config file parsing.
98
If `strict=True`, unrecognized values, including None, will raise a
99
ValueError which is useful when parsing values passed in from an API call.
100
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
102
if not isinstance(subject, six.string_types):
103
subject = six.text_type(subject)
105
lowered = subject.strip().lower()
107
if lowered in TRUE_STRINGS:
109
elif lowered in FALSE_STRINGS:
112
acceptable = ', '.join(
113
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
114
msg = _("Unrecognized value '%(val)s', acceptable values are:"
115
" %(acceptable)s") % {'val': subject,
116
'acceptable': acceptable}
117
raise ValueError(msg)
122
def safe_decode(text, incoming=None, errors='strict'):
123
"""Decodes incoming text/bytes string using `incoming` if they're not
126
:param incoming: Text's current encoding
127
:param errors: Errors handling policy. See here for valid
128
values http://docs.python.org/2/library/codecs.html
129
:returns: text or a unicode `incoming` encoded
130
representation of it.
131
:raises TypeError: If text is not an instance of str
133
if not isinstance(text, (six.string_types, six.binary_type)):
134
raise TypeError("%s can't be decoded" % type(text))
136
if isinstance(text, six.text_type):
140
incoming = (sys.stdin.encoding or
141
sys.getdefaultencoding())
144
return text.decode(incoming, errors)
145
except UnicodeDecodeError:
146
# Note(flaper87) If we get here, it means that
147
# sys.stdin.encoding / sys.getdefaultencoding
148
# didn't return a suitable encoding to decode
149
# text. This happens mostly when global LANG
150
# var is not set correctly and there's no
151
# default encoding. In this case, most likely
152
# python will use ASCII or ANSI encoders as
153
# default encodings but they won't be capable
154
# of decoding non-ASCII characters.
156
# Also, UTF-8 is being used since it's an ASCII
158
return text.decode('utf-8', errors)
161
def safe_encode(text, incoming=None,
162
encoding='utf-8', errors='strict'):
163
"""Encodes incoming text/bytes string using `encoding`.
165
If incoming is not specified, text is expected to be encoded with
166
current python's default encoding. (`sys.getdefaultencoding`)
168
:param incoming: Text's current encoding
169
:param encoding: Expected encoding for text (Default UTF-8)
170
:param errors: Errors handling policy. See here for valid
171
values http://docs.python.org/2/library/codecs.html
172
:returns: text or a bytestring `encoding` encoded
173
representation of it.
174
:raises TypeError: If text is not an instance of str
176
if not isinstance(text, (six.string_types, six.binary_type)):
177
raise TypeError("%s can't be encoded" % type(text))
180
incoming = (sys.stdin.encoding or
181
sys.getdefaultencoding())
183
if isinstance(text, six.text_type):
184
return text.encode(encoding, errors)
185
elif text and encoding != incoming:
186
# Decode text before encoding it with `encoding`
187
text = safe_decode(text, incoming, errors)
188
return text.encode(encoding, errors)
193
def string_to_bytes(text, unit_system='IEC', return_int=False):
194
"""Converts a string into an float representation of bytes.
196
The units supported for IEC ::
198
Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
199
KB, KiB, MB, MiB, GB, GiB, TB, TiB
201
The units supported for SI ::
203
kb(it), Mb(it), Gb(it), Tb(it)
206
Note that the SI unit system does not support capital letter 'K'
208
:param text: String input for bytes size conversion.
209
:param unit_system: Unit system for byte size conversion.
210
:param return_int: If True, returns integer representation of text
211
in bytes. (default: decimal)
212
:returns: Numerical representation of text in bytes.
213
:raises ValueError: If text has an invalid value.
217
base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
219
msg = _('Invalid unit system: "%s"') % unit_system
220
raise ValueError(msg)
221
match = reg_ex.match(text)
223
magnitude = float(match.group(1))
224
unit_prefix = match.group(2)
225
if match.group(3) in ['b', 'bit']:
228
msg = _('Invalid string format: %s') % text
229
raise ValueError(msg)
233
res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
235
return int(math.ceil(res))
239
def to_slug(value, incoming=None, errors="strict"):
242
Convert to lowercase, remove non-word characters, and convert spaces
245
Inspired by Django's `slugify` filter.
247
:param value: Text to slugify
248
:param incoming: Text's current encoding
249
:param errors: Errors handling policy. See here for valid
250
values http://docs.python.org/2/library/codecs.html
251
:returns: slugified unicode representation of `value`
252
:raises TypeError: If text is not an instance of str
254
value = safe_decode(value, incoming, errors)
255
# NOTE(aababilov): no need to use safe_(encode|decode) here:
256
# encodings are always "ascii", error handling is always "ignore"
257
# and types are always known (first: unicode; second: str)
258
value = unicodedata.normalize("NFKD", value).encode(
259
"ascii", "ignore").decode("ascii")
260
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
261
return SLUGIFY_HYPHENATE_RE.sub("-", value)
264
def mask_password(message, secret="***"):
265
"""Replace password with 'secret' in message.
267
:param message: The string which includes security information.
268
:param secret: value with which to replace passwords.
269
:returns: The unicode value of message with the password fields masked.
273
>>> mask_password("'adminPass' : 'aaaaa'")
274
"'adminPass' : '***'"
275
>>> mask_password("'admin_pass' : 'aaaaa'")
276
"'admin_pass' : '***'"
277
>>> mask_password('"password" : "aaaaa"')
279
>>> mask_password("'original_password' : 'aaaaa'")
280
"'original_password' : '***'"
281
>>> mask_password("u'original_password' : u'aaaaa'")
282
"u'original_password' : u'***'"
284
message = six.text_type(message)
286
# NOTE(ldbragst): Check to see if anything in message contains any key
287
# specified in _SANITIZE_KEYS, if not then just return the message since
288
# we don't have to mask any passwords.
289
if not any(key in message for key in _SANITIZE_KEYS):
292
secret = r'\g<1>' + secret + r'\g<2>'
293
for pattern in _SANITIZE_PATTERNS:
294
message = re.sub(pattern, secret, message)