/
inputparse.py
325 lines (307 loc) · 11.7 KB
/
inputparse.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License
# as published by the Free Software Foundation.
#
# This library 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors: Michal Minar <miminar@redhat.com>
"""
Utilities and functions for parsing user input obtained from html forms.
"""
import base64
import pickle
import pywbem
import re
import zlib
class ReferenceDecodeError(ValueError):
"""
Exception raised, when parsing of object path failed.
"""
def __init__(self, key=None, path=None):
msg = "Could not decode compressed object path%s%s!"
if key is not None:
key = ' for key "%s"' % key
else:
key = ''
if path is not None:
path = ' "%s"' % str(path)
else:
path = ''
msg = msg % (path, key)
ValueError.__init__(self, msg)
def decode_reference(encoded_text):
"""
Decompress object path to python object.
"""
try:
return pickle.loads(zlib.decompress(base64.urlsafe_b64decode(
encoded_text)))
except Exception:
raise ReferenceDecodeError(path=encoded_text)
RE_INAME = re.compile(r"^((?P<namespace>[^:.=]+):)?"
r"((?P<classname>[a-zA-Z_0-9]+)\.)?(?P<keys>.*)")
# states of keybindings parser
#
# possible transitions:
# (KEY_NAME, '=') -> VALUE_START
# (KEY_NAME, ?) -> KEY_NAME
# (VALUE_START, '"') -> VALUE_STR
# (VALUE_START, ?) -> VALUE_SIMPLE
# (VALUE_SIMPLE, ',') -> KEY_NAME
# (VALUE_SIMPLE, ?) -> VALUE_SIMPLE
# (VALUE_STR, '"') -> VALUE_END
# (VALUE_STR, '\\') -> VALUE_ESC
# (VALUE_STR, ?) -> VALUE_STR
# (VALUE_ESC, ?) -> VALUE_STR
#
# KEYS_END is a final state
KEY_NAME, VALUE_START, VALUE_SIMPLE, VALUE_STR, VALUE_ESC, VALUE_END, \
NEXT_KEY, KEYS_END = range(8)
_RE_KEY_NAME = re.compile(r'([a-zA-Z_0-9]+)=')
_RE_VALUE = re.compile(r'([^"\\]+)')
_RE_VALUE_SIMPLE = re.compile(r'([-a-zA-Z_0-9.]+)')
_RE_INTEGER_TYPE = re.compile(r'^[su]int')
def value_str2pywbem(prop, value):
"""
Used by iname_str2pywbem function for transforming key value
to a pywbem object.
@param prop may be None, in that case type will be guessed
"""
if prop is not None:
prop_type = prop['type']
if (_RE_INTEGER_TYPE.match(prop_type) or prop_type == "boolean"):
if value[0] == '"':
value = value[1:]
if value[-1] == '"':
value = value[:-1]
if prop_type == "boolean":
value = True if value.lower() == "true" else False
else:
prop_type = 'string'
print("LOWERb=", value.lower())
if value.lower() in {'true', 'false'}:
prop_type = 'boolean'
value = True if value.lower() == "true" else False
else:
try:
int(value)
prop_type = 'uint16'
except ValueError:
try:
float(value)
prop_type = 'float'
except ValueError:
pass
return pywbem.cimvalue(value, prop_type)
def iname_str2pywbem(props, iname, classname=None, namespace=None):
"""
Parse object path given as string in format:
<namespace>:<classname>.<keybindings>
where
<namespace> and <classname> are optional
<keybindings> is a sequance of strings: key="value"
separated by ','
@props is a dictionary with format:
{ key_name, property_dictionary }
with informations about referenced class key properties;
preferrably it should be a NocaseDict
@note does not handle embedded references to objects
"""
match = RE_INAME.match(iname)
if not match:
raise ValueError("Invalid path!")
kwargs = {'classname':classname, 'namespace':namespace}
for k in ("namespace", "classname"):
if match.group(k):
kwargs[k] = match.group(k)
res = pywbem.CIMInstanceName(**kwargs)
state = KEY_NAME
value = ''
keybindings = match.group('keys')
pos = 0
letter = keybindings[pos] if len(keybindings) else None
while state != KEYS_END:
eol = pos >= len(keybindings) # end of input
letter = keybindings[pos] if not eol else None # current letter
if state == KEY_NAME:
if eol:
state = KEYS_END
else:
match = _RE_KEY_NAME.match(keybindings, pos)
if not match:
raise ValueError("Invalid path!")
key = match.group(1)
if props and not key in props:
raise ValueError(
"Invalid path: unknown key \"%s\""
" for instance of class \"%s\"!"%(
key, kwargs['classname']))
state = VALUE_START
pos = match.end()
elif state == VALUE_START:
if letter == '"':
state = VALUE_STR
pos += 1
else:
state = VALUE_SIMPLE
elif state == VALUE_SIMPLE:
match = _RE_VALUE_SIMPLE.match(keybindings, pos)
if not match:
raise ValueError("Invalid path!")
res[key] = value_str2pywbem(props.get(key, None), match.group(1))
pos = match.end()
state = NEXT_KEY
elif state == VALUE_STR:
if letter == '"':
res[key] = value_str2pywbem(props.get(key, None), value)
value = ''
state = VALUE_END
elif letter == '\\':
state = VALUE_ESC
pos += 1
else:
match = _RE_VALUE.match(keybindings, pos)
if not match:
raise ValueError("Invalid path:"
" expected '\"' or '\\'!")
pos = match.end()
value += match.group(1)
elif state == VALUE_ESC:
state = VALUE_STR
value += letter
pos += 1
elif state == VALUE_END:
if letter == '"':
state = NEXT_KEY
pos += 1
else:
raise ValueError("Invalid path: missing terminating '\"'!")
elif state == NEXT_KEY:
if eol:
state = KEYS_END
elif letter == ',':
state = KEY_NAME
pos += 1
else:
raise ValueError("Invalid path: expected ','!")
else:
assert False # something wrong in above code
if state != KEYS_END:
raise ValueError("Invalid path!")
if not len(res.keybindings):
raise ValueError("Invalid path: missing key-value pairs!")
return res
def formvalue2iname(param, prefix, formdata, suffix='', pop_used=False,
namespace=None):
"""
Obtain a single object path from form inputs.
Object path can be given as key values in separated input fields,
as a compressed string or as an raw path string.
@param suffix can be used to obtain the values from an item of
array, which has name of fields composed like this:
<prefix><param_name>-<item>.<key_name>
where <item> is an index to array, counted from zero
<key_name> is a name of key property of referenced class
suffix in this case will be "-<item>"
@param namespace is a namespace of referenced class
"""
if not isinstance(param['type'], dict):
raise ValueError("param must represent a reference to object")
param_name = prefix.lower()+param['name'].lower()+suffix
def get_value(name):
"""@return value from formdata and optionally remove it"""
res = formdata[name]
if pop_used:
del formdata[name]
return res
if ( param_name+'-reftype' in formdata
and formdata[param_name+"-reftype"] != 'compressed'):
namespace = param['type'].get('ns', namespace)
reftype = get_value(param_name+'-reftype')
if reftype == 'keys':
result = pywbem.CIMInstanceName(
classname=param['type']['className'],
namespace=namespace)
for key, ref_param in param['type']['keys'].items():
result[key] = formvalue2cimobject(ref_param,
param_name+'.', formdata, pop_used)
else: # raw object path
result = iname_str2pywbem(
param['type']['keys'], get_value(param_name),
classname=param['type']['className'],
namespace=namespace)
else:
if pop_used and param_name+'-reftype' in formdata:
del formdata[param_name+'-reftype']
value = get_value(param_name)
if not value:
return None
# base64 decode does not properly handle unicode
try:
if isinstance(value, str):
value = value.encode('utf-8')
assert isinstance(value, str), "formvalue2iname"
result = decode_reference(value)
except ReferenceDecodeError:
raise ReferenceDecodeError(param_name, value)
return result
def formvalue2cimobject(param, prefix, formdata, pop_used=False,
namespace=None):
"""
@param param is a dictionary created by get_class_item_details
@param formdata should be a NocaseDict
@param namespace is used as a fallback for reference types,
where namespace of refered class could not be obtained
@return value passable to pywbem methods
"""
prefix = prefix.lower()
param_name = param['name'].lower()
def value(name):
"""@return value of form with optional removal"""
res = formdata[prefix + name.lower()]
if pop_used:
del formdata[prefix + name.lower()]
return res
def process_simple_value(val):
"""@return value as a cim object"""
return pywbem.cimvalue(val, param['type'])
if param['is_array']:
result = []
if param['is_valuemap']:
for val in param['valuemap']:
if ("%s%s-%s"%(prefix, param_name, val)) in formdata:
result.append(process_simple_value(val))
else:
if not (prefix + param_name + '.size') in formdata:
return
if param['array_size'] is None:
size = int(value(param_name + '.size'))
else:
size = param['array_size']
for i in range(size):
if isinstance(param['type'], dict):
pywbem_val = formvalue2iname(param, prefix, formdata,
suffix='-'+str(i), pop_used=pop_used,
namespace=namespace)
else:
pywbem_val = process_simple_value(
value(param_name + '-' + str(i)))
result.append(pywbem_val)
else:
if prefix + param_name + '-null' in formdata:
return None
if isinstance(param['type'], dict):
result = formvalue2iname(param, prefix, formdata,
pop_used=pop_used, namespace=namespace)
else:
result = process_simple_value(value(param_name))
return result