src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Security/SecurityUtils.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9272
06ed98a19b79
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
16 16
17 class InvalidModulePath(Exception): 17 class InvalidModulePath(Exception):
18 """ 18 """
19 Class defining an exception for invalid module paths. 19 Class defining an exception for invalid module paths.
20 """ 20 """
21
21 pass 22 pass
22 23
23 24
24 def getModuleQualnameFromPath(path): 25 def getModuleQualnameFromPath(path):
25 """ 26 """
26 Function to get the module's qualified name by analysis of the 27 Function to get the module's qualified name by analysis of the
27 path. 28 path.
28 29
29 Resolve the absolute pathname and eliminate symlinks. This could result 30 Resolve the absolute pathname and eliminate symlinks. This could result
30 in an incorrect name if symlinks are used to restructure the python lib 31 in an incorrect name if symlinks are used to restructure the python lib
31 directory. 32 directory.
32 33
33 Starting from the right-most directory component look for __init__.py 34 Starting from the right-most directory component look for __init__.py
34 in the directory component. If it exists then the directory name is 35 in the directory component. If it exists then the directory name is
35 part of the module name. Move left to the subsequent directory 36 part of the module name. Move left to the subsequent directory
36 components until a directory is found without __init__.py. 37 components until a directory is found without __init__.py.
37 38
38 @param path path of the module to be analyzed 39 @param path path of the module to be analyzed
39 @type str 40 @type str
40 @return qualified name of the module 41 @return qualified name of the module
41 @rtype str 42 @rtype str
42 @exception InvalidModulePath raised to indicate an invalid module path 43 @exception InvalidModulePath raised to indicate an invalid module path
43 """ 44 """
44 (head, tail) = os.path.split(path) 45 (head, tail) = os.path.split(path)
45 if head == '' or tail == '': 46 if head == "" or tail == "":
46 raise InvalidModulePath('Invalid python file path: "{0}"' 47 raise InvalidModulePath(
47 ' Missing path or file name'.format(path)) 48 'Invalid python file path: "{0}"' " Missing path or file name".format(path)
48 49 )
50
49 qname = [os.path.splitext(tail)[0]] 51 qname = [os.path.splitext(tail)[0]]
50 while head not in ['/', '.', '']: 52 while head not in ["/", ".", ""]:
51 if os.path.isfile(os.path.join(head, '__init__.py')): 53 if os.path.isfile(os.path.join(head, "__init__.py")):
52 (head, tail) = os.path.split(head) 54 (head, tail) = os.path.split(head)
53 qname.insert(0, tail) 55 qname.insert(0, tail)
54 else: 56 else:
55 break 57 break
56 58
57 qualname = '.'.join(qname) 59 qualname = ".".join(qname)
58 return qualname 60 return qualname
59 61
60 62
61 def namespacePathJoin(namespace, name): 63 def namespacePathJoin(namespace, name):
62 """ 64 """
63 Function to extend a given namespace path. 65 Function to extend a given namespace path.
64 66
65 @param namespace namespace to be extended 67 @param namespace namespace to be extended
66 @type str 68 @type str
67 @param name node name to be appended 69 @param name node name to be appended
68 @type str 70 @type str
69 @return extended namespace 71 @return extended namespace
73 75
74 76
75 def namespacePathSplit(path): 77 def namespacePathSplit(path):
76 """ 78 """
77 Function to split a namespace path into a head and tail. 79 Function to split a namespace path into a head and tail.
78 80
79 Tail will be the last namespace path component and head will 81 Tail will be the last namespace path component and head will
80 be everything leading up to that in the path. This is similar to 82 be everything leading up to that in the path. This is similar to
81 os.path.split. 83 os.path.split.
82 84
83 @param path namespace path to be split 85 @param path namespace path to be split
84 @type str 86 @type str
85 @return tuple containing the namespace path head and tail 87 @return tuple containing the namespace path head and tail
86 @rtype tuple of (str, str) 88 @rtype tuple of (str, str)
87 """ 89 """
88 return tuple(path.rsplit('.', 1)) 90 return tuple(path.rsplit(".", 1))
89 91
90 92
91 def getAttrQualName(node, aliases): 93 def getAttrQualName(node, aliases):
92 """ 94 """
93 Function to get a the full name for the attribute node. 95 Function to get a the full name for the attribute node.
97 Attributes. This will give you how the code referenced the name but 99 Attributes. This will give you how the code referenced the name but
98 will not tell you what the name actually refers to. If we 100 will not tell you what the name actually refers to. If we
99 encounter a node without a static name we punt with an 101 encounter a node without a static name we punt with an
100 empty string. If this encounters something more complex, such as 102 empty string. If this encounters something more complex, such as
101 foo.mylist[0](a,b) we just return empty string. 103 foo.mylist[0](a,b) we just return empty string.
102 104
103 @param node attribute node to be treated 105 @param node attribute node to be treated
104 @type ast.Attribute 106 @type ast.Attribute
105 @param aliases dictionary of import aliases 107 @param aliases dictionary of import aliases
106 @type dict 108 @type dict
107 @return qualified name of the attribute 109 @return qualified name of the attribute
110 if isinstance(node, ast.Name): 112 if isinstance(node, ast.Name):
111 if node.id in aliases: 113 if node.id in aliases:
112 return aliases[node.id] 114 return aliases[node.id]
113 return node.id 115 return node.id
114 elif isinstance(node, ast.Attribute): 116 elif isinstance(node, ast.Attribute):
115 name = "{0}.{1}".format(getAttrQualName(node.value, aliases), 117 name = "{0}.{1}".format(getAttrQualName(node.value, aliases), node.attr)
116 node.attr)
117 if name in aliases: 118 if name in aliases:
118 return aliases[name] 119 return aliases[name]
119 return name 120 return name
120 else: 121 else:
121 return "" 122 return ""
122 123
123 124
124 def getCallName(node, aliases): 125 def getCallName(node, aliases):
125 """ 126 """
126 Function to extract the call name from an ast.Call node. 127 Function to extract the call name from an ast.Call node.
127 128
128 @param node node to extract information from 129 @param node node to extract information from
129 @type ast.Call 130 @type ast.Call
130 @param aliases dictionary of import aliases 131 @param aliases dictionary of import aliases
131 @type dict 132 @type dict
132 @return name of the ast.Call node 133 @return name of the ast.Call node
133 @rtype str 134 @rtype str
134 """ 135 """
135 if isinstance(node.func, ast.Name): 136 if isinstance(node.func, ast.Name):
136 if deepgetattr(node, 'func.id') in aliases: 137 if deepgetattr(node, "func.id") in aliases:
137 return aliases[deepgetattr(node, 'func.id')] 138 return aliases[deepgetattr(node, "func.id")]
138 return deepgetattr(node, 'func.id') 139 return deepgetattr(node, "func.id")
139 elif isinstance(node.func, ast.Attribute): 140 elif isinstance(node.func, ast.Attribute):
140 return getAttrQualName(node.func, aliases) 141 return getAttrQualName(node.func, aliases)
141 else: 142 else:
142 return "" 143 return ""
143 144
144 145
145 def getQualAttr(node, aliases): 146 def getQualAttr(node, aliases):
146 """ 147 """
147 Function to extract the qualified name from an ast.Attribute node. 148 Function to extract the qualified name from an ast.Attribute node.
148 149
149 @param node node to extract information from 150 @param node node to extract information from
150 @type ast.Attribute 151 @type ast.Attribute
151 @param aliases dictionary of import aliases 152 @param aliases dictionary of import aliases
152 @type dict 153 @type dict
153 @return qualified attribute name 154 @return qualified attribute name
154 @rtype str 155 @rtype str
155 """ 156 """
156 prefix = "" 157 prefix = ""
157 if isinstance(node, ast.Attribute): 158 if isinstance(node, ast.Attribute):
158 with contextlib.suppress(Exception): 159 with contextlib.suppress(Exception):
159 val = deepgetattr(node, 'value.id') 160 val = deepgetattr(node, "value.id")
160 prefix = ( 161 prefix = aliases[val] if val in aliases else deepgetattr(node, "value.id")
161 aliases[val] if val in aliases
162 else deepgetattr(node, 'value.id')
163 )
164 # Id we can't get the fully qualified name for an attr, just return 162 # Id we can't get the fully qualified name for an attr, just return
165 # its base name. 163 # its base name.
166 164
167 return "{0}.{1}".format(prefix, node.attr) 165 return "{0}.{1}".format(prefix, node.attr)
168 else: 166 else:
169 return "" 167 return ""
170 168
171 169
172 def deepgetattr(obj, attr): 170 def deepgetattr(obj, attr):
173 """ 171 """
174 Function to recurs through an attribute chain to get the ultimate value. 172 Function to recurs through an attribute chain to get the ultimate value.
175 173
176 @param obj reference to the object to be recursed 174 @param obj reference to the object to be recursed
177 @type ast.Name or ast.Attribute 175 @type ast.Name or ast.Attribute
178 @param attr attribute chain to be parsed 176 @param attr attribute chain to be parsed
179 @type ast.Attribute 177 @type ast.Attribute
180 @return ultimate value 178 @return ultimate value
181 @rtype ast.AST 179 @rtype ast.AST
182 """ 180 """
183 for key in attr.split('.'): 181 for key in attr.split("."):
184 obj = getattr(obj, key) 182 obj = getattr(obj, key)
185 return obj 183 return obj
186 184
187 185
188 def linerange(node): 186 def linerange(node):
189 """ 187 """
190 Function to get line number range from a node. 188 Function to get line number range from a node.
191 189
192 @param node node to extract a line range from 190 @param node node to extract a line range from
193 @type ast.AST 191 @type ast.AST
194 @return list containing the line number range 192 @return list containing the line number range
195 @rtype list of int 193 @rtype list of int
196 """ 194 """
197 strip = { 195 strip = {"body": None, "orelse": None, "handlers": None, "finalbody": None}
198 "body": None,
199 "orelse": None,
200 "handlers": None,
201 "finalbody": None
202 }
203 for key in strip: 196 for key in strip:
204 if hasattr(node, key): 197 if hasattr(node, key):
205 strip[key] = getattr(node, key) 198 strip[key] = getattr(node, key)
206 node.key = [] 199 node.key = []
207 200
208 lines_min = 9999999999 201 lines_min = 9999999999
209 lines_max = -1 202 lines_max = -1
210 for n in ast.walk(node): 203 for n in ast.walk(node):
211 if hasattr(n, 'lineno'): 204 if hasattr(n, "lineno"):
212 lines_min = min(lines_min, n.lineno) 205 lines_min = min(lines_min, n.lineno)
213 lines_max = max(lines_max, n.lineno) 206 lines_max = max(lines_max, n.lineno)
214 207
215 for key in strip: 208 for key in strip:
216 if strip[key] is not None: 209 if strip[key] is not None:
217 node.key = strip[key] 210 node.key = strip[key]
218 211
219 if lines_max > -1: 212 if lines_max > -1:
220 return list(range(lines_min, lines_max + 1)) 213 return list(range(lines_min, lines_max + 1))
221 214
222 return [0, 1] 215 return [0, 1]
223 216
224 217
225 def linerange_fix(node): 218 def linerange_fix(node):
226 """ 219 """
227 Function to get a line number range working around a known Python bug 220 Function to get a line number range working around a known Python bug
228 with multi-line strings. 221 with multi-line strings.
229 222
230 @param node node to extract a line range from 223 @param node node to extract a line range from
231 @type ast.AST 224 @type ast.AST
232 @return list containing the line number range 225 @return list containing the line number range
233 @rtype list of int 226 @rtype list of int
234 """ 227 """
235 # deal with multiline strings lineno behavior (Python issue #16806) 228 # deal with multiline strings lineno behavior (Python issue #16806)
236 lines = linerange(node) 229 lines = linerange(node)
237 if ( 230 if hasattr(node, "_securitySibling") and hasattr(node._securitySibling, "lineno"):
238 hasattr(node, '_securitySibling') and
239 hasattr(node._securitySibling, 'lineno')
240 ):
241 start = min(lines) 231 start = min(lines)
242 delta = node._securitySibling.lineno - start 232 delta = node._securitySibling.lineno - start
243 if delta > 1: 233 if delta > 1:
244 return list(range(start, node._securitySibling.lineno)) 234 return list(range(start, node._securitySibling.lineno))
245 235
246 return lines 236 return lines
247 237
248 238
249 def escapedBytesRepresentation(b): 239 def escapedBytesRepresentation(b):
250 """ 240 """
251 Function to escape bytes for comparison with other strings. 241 Function to escape bytes for comparison with other strings.
252 242
253 In practice it turns control characters into acceptable codepoints then 243 In practice it turns control characters into acceptable codepoints then
254 encodes them into bytes again to turn unprintable bytes into printable 244 encodes them into bytes again to turn unprintable bytes into printable
255 escape sequences. 245 escape sequences.
256 246
257 This is safe to do for the whole range 0..255 and result matches 247 This is safe to do for the whole range 0..255 and result matches
258 unicode_escape on a unicode string. 248 unicode_escape on a unicode string.
259 249
260 @param b bytes object to be escaped 250 @param b bytes object to be escaped
261 @type bytes 251 @type bytes
262 @return escaped bytes object 252 @return escaped bytes object
263 @rtype bytes 253 @rtype bytes
264 """ 254 """
265 return b.decode('unicode_escape').encode('unicode_escape') 255 return b.decode("unicode_escape").encode("unicode_escape")
266 256
267 257
268 def concatString(node, stop=None): 258 def concatString(node, stop=None):
269 """ 259 """
270 Function to build a string from an ast.BinOp chain. 260 Function to build a string from an ast.BinOp chain.
271 261
272 This will build a string from a series of ast.Str/ast.Constant nodes 262 This will build a string from a series of ast.Str/ast.Constant nodes
273 wrapped in ast.BinOp nodes. Something like "a" + "b" + "c" or "a %s" % val 263 wrapped in ast.BinOp nodes. Something like "a" + "b" + "c" or "a %s" % val
274 etc. The provided node can be any participant in the BinOp chain. 264 etc. The provided node can be any participant in the BinOp chain.
275 265
276 @param node node to be processed 266 @param node node to be processed
277 @type ast.BinOp or ast.Str/ast.Constant 267 @type ast.BinOp or ast.Str/ast.Constant
278 @param stop base node to stop at 268 @param stop base node to stop at
279 @type ast.BinOp or ast.Str/ast.Constant 269 @type ast.BinOp or ast.Str/ast.Constant
280 @return tuple containing the root node of the expression and the string 270 @return tuple containing the root node of the expression and the string
281 value 271 value
282 @rtype tuple of (ast.AST, str) 272 @rtype tuple of (ast.AST, str)
283 """ 273 """
274
284 def _get(node, bits, stop=None): 275 def _get(node, bits, stop=None):
285 if node != stop: 276 if node != stop:
286 bits.append( 277 bits.append(
287 _get(node.left, bits, stop) 278 _get(node.left, bits, stop)
288 if isinstance(node.left, ast.BinOp) 279 if isinstance(node.left, ast.BinOp)
291 bits.append( 282 bits.append(
292 _get(node.right, bits, stop) 283 _get(node.right, bits, stop)
293 if isinstance(node.right, ast.BinOp) 284 if isinstance(node.right, ast.BinOp)
294 else node.right 285 else node.right
295 ) 286 )
296 287
297 bits = [node] 288 bits = [node]
298 while isinstance(node._securityParent, ast.BinOp): 289 while isinstance(node._securityParent, ast.BinOp):
299 node = node._securityParent 290 node = node._securityParent
300 if isinstance(node, ast.BinOp): 291 if isinstance(node, ast.BinOp):
301 _get(node, bits, stop) 292 _get(node, bits, stop)
302 293
303 return ( 294 return (node, " ".join([x.s for x in bits if AstUtilities.isString(x)]))
304 node,
305 " ".join([x.s for x in bits if AstUtilities.isString(x)])
306 )
307 295
308 296
309 def getCalledName(node): 297 def getCalledName(node):
310 """ 298 """
311 Function to get the function name from an ast.Call node. 299 Function to get the function name from an ast.Call node.
312 300
313 An ast.Call node representing a method call will present differently to one 301 An ast.Call node representing a method call will present differently to one
314 wrapping a function call: thing.call() vs call(). This helper will grab the 302 wrapping a function call: thing.call() vs call(). This helper will grab the
315 unqualified call name correctly in either case. 303 unqualified call name correctly in either case.
316 304
317 @param node reference to the call node 305 @param node reference to the call node
318 @type ast.Call 306 @type ast.Call
319 @return function name of the node 307 @return function name of the node
320 @rtype str 308 @rtype str
321 """ 309 """
323 try: 311 try:
324 return func.attr if isinstance(func, ast.Attribute) else func.id 312 return func.attr if isinstance(func, ast.Attribute) else func.id
325 except AttributeError: 313 except AttributeError:
326 return "" 314 return ""
327 315
316
328 # 317 #
329 # eflag: noqa = M601 318 # eflag: noqa = M601

eric ide

mercurial