1 | # ***** BEGIN LICENSE BLOCK *****
|
---|
2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
---|
3 | #
|
---|
4 | # The contents of this file are subject to the Mozilla Public License Version
|
---|
5 | # 1.1 (the "License"); you may not use this file except in compliance with
|
---|
6 | # the License. You may obtain a copy of the License at
|
---|
7 | # http://www.mozilla.org/MPL/
|
---|
8 | #
|
---|
9 | # Software distributed under the License is distributed on an "AS IS" basis,
|
---|
10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
---|
11 | # for the specific language governing rights and limitations under the
|
---|
12 | # License.
|
---|
13 | #
|
---|
14 | # The Original Code is Python XPCOM language bindings.
|
---|
15 | #
|
---|
16 | # The Initial Developer of the Original Code is
|
---|
17 | # ActiveState Tool Corp.
|
---|
18 | # Portions created by the Initial Developer are Copyright (C) 2001
|
---|
19 | # the Initial Developer. All Rights Reserved.
|
---|
20 | #
|
---|
21 | # Contributor(s):
|
---|
22 | # Mark Hammond <[email protected]> (original author)
|
---|
23 | #
|
---|
24 | # Alternatively, the contents of this file may be used under the terms of
|
---|
25 | # either the GNU General Public License Version 2 or later (the "GPL"), or
|
---|
26 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
---|
27 | # in which case the provisions of the GPL or the LGPL are applicable instead
|
---|
28 | # of those above. If you wish to allow use of your version of this file only
|
---|
29 | # under the terms of either the GPL or the LGPL, and not to allow others to
|
---|
30 | # use your version of this file under the terms of the MPL, indicate your
|
---|
31 | # decision by deleting the provisions above and replace them with the notice
|
---|
32 | # and other provisions required by the GPL or the LGPL. If you do not delete
|
---|
33 | # the provisions above, a recipient may use your version of this file under
|
---|
34 | # the terms of any one of the MPL, the GPL or the LGPL.
|
---|
35 | #
|
---|
36 | # ***** END LICENSE BLOCK *****
|
---|
37 |
|
---|
38 | from xpcom import xpcom_consts, _xpcom, client, nsError, logger
|
---|
39 | from xpcom import ServerException, COMException
|
---|
40 | import xpcom
|
---|
41 | import xpcom.server
|
---|
42 | import operator
|
---|
43 | import types
|
---|
44 | import logging
|
---|
45 | import sys
|
---|
46 |
|
---|
47 | # Python 3 hacks:
|
---|
48 | if sys.version_info[0] >= 3:
|
---|
49 | long = int # pylint: disable=W0622,C0103
|
---|
50 |
|
---|
51 |
|
---|
52 | IID_nsISupports = _xpcom.IID_nsISupports
|
---|
53 | IID_nsIVariant = _xpcom.IID_nsIVariant
|
---|
54 | XPT_MD_IS_GETTER = xpcom_consts.XPT_MD_IS_GETTER
|
---|
55 | XPT_MD_IS_SETTER = xpcom_consts.XPT_MD_IS_SETTER
|
---|
56 |
|
---|
57 | VARIANT_INT_TYPES = xpcom_consts.VTYPE_INT8, xpcom_consts.VTYPE_INT16, xpcom_consts.VTYPE_INT32, \
|
---|
58 | xpcom_consts.VTYPE_UINT8, xpcom_consts.VTYPE_UINT16, xpcom_consts.VTYPE_INT32
|
---|
59 | VARIANT_LONG_TYPES = xpcom_consts.VTYPE_INT64, xpcom_consts.VTYPE_UINT64
|
---|
60 | VARIANT_FLOAT_TYPES = xpcom_consts.VTYPE_FLOAT, xpcom_consts.VTYPE_DOUBLE
|
---|
61 | VARIANT_STRING_TYPES = xpcom_consts.VTYPE_CHAR, xpcom_consts.VTYPE_CHAR_STR, xpcom_consts.VTYPE_STRING_SIZE_IS, \
|
---|
62 | xpcom_consts.VTYPE_CSTRING
|
---|
63 | VARIANT_UNICODE_TYPES = xpcom_consts.VTYPE_WCHAR, xpcom_consts.VTYPE_DOMSTRING, xpcom_consts.VTYPE_WSTRING_SIZE_IS, \
|
---|
64 | xpcom_consts.VTYPE_ASTRING
|
---|
65 |
|
---|
66 | _supports_primitives_map_ = {} # Filled on first use.
|
---|
67 |
|
---|
68 | _interface_sequence_types_ = tuple, list
|
---|
69 | if sys.version_info[0] <= 2:
|
---|
70 | _string_types_ = str, unicode
|
---|
71 | else:
|
---|
72 | _string_types_ = bytes, str
|
---|
73 | XPTI_GetInterfaceInfoManager = _xpcom.XPTI_GetInterfaceInfoManager
|
---|
74 |
|
---|
75 | def _GetNominatedInterfaces(obj):
|
---|
76 | ret = getattr(obj, "_com_interfaces_", None)
|
---|
77 | if ret is None: return None
|
---|
78 | # See if the user only gave one.
|
---|
79 | if type(ret) not in _interface_sequence_types_:
|
---|
80 | ret = [ret]
|
---|
81 | real_ret = []
|
---|
82 | # For each interface, walk to the root of the interface tree.
|
---|
83 | iim = XPTI_GetInterfaceInfoManager()
|
---|
84 | for interface in ret:
|
---|
85 | # Allow interface name or IID.
|
---|
86 | interface_info = None
|
---|
87 | if type(interface) in _string_types_:
|
---|
88 | try:
|
---|
89 | interface_info = iim.GetInfoForName(interface)
|
---|
90 | except COMException:
|
---|
91 | pass
|
---|
92 | if interface_info is None:
|
---|
93 | # Allow a real IID
|
---|
94 | interface_info = iim.GetInfoForIID(interface)
|
---|
95 | real_ret.append(interface_info.GetIID())
|
---|
96 | parent = interface_info.GetParent()
|
---|
97 | while parent is not None:
|
---|
98 | parent_iid = parent.GetIID()
|
---|
99 | if parent_iid == IID_nsISupports:
|
---|
100 | break
|
---|
101 | real_ret.append(parent_iid)
|
---|
102 | parent = parent.GetParent()
|
---|
103 | return real_ret
|
---|
104 |
|
---|
105 | ##
|
---|
106 | ## ClassInfo support
|
---|
107 | ##
|
---|
108 | ## We cache class infos by class
|
---|
109 | class_info_cache = {}
|
---|
110 |
|
---|
111 | def GetClassInfoForObject(ob):
|
---|
112 | if xpcom.server.tracer_unwrap is not None:
|
---|
113 | ob = xpcom.server.tracer_unwrap(ob)
|
---|
114 | klass = ob.__class__
|
---|
115 | ci = class_info_cache.get(klass)
|
---|
116 | if ci is None:
|
---|
117 | ci = DefaultClassInfo(klass)
|
---|
118 | ci = xpcom.server.WrapObject(ci, _xpcom.IID_nsIClassInfo, bWrapClient = 0)
|
---|
119 | class_info_cache[klass] = ci
|
---|
120 | return ci
|
---|
121 |
|
---|
122 | class DefaultClassInfo:
|
---|
123 | _com_interfaces_ = _xpcom.IID_nsIClassInfo
|
---|
124 | def __init__(self, klass):
|
---|
125 | self.klass = klass
|
---|
126 | self.contractID = getattr(klass, "_reg_contractid_", None)
|
---|
127 | self.classDescription = getattr(klass, "_reg_desc_", None)
|
---|
128 | self.classID = getattr(klass, "_reg_clsid_", None)
|
---|
129 | self.implementationLanguage = 3 # Python - avoid lookups just for this
|
---|
130 | self.flags = 0 # what to do here??
|
---|
131 | self.interfaces = None
|
---|
132 |
|
---|
133 | def get_classID(self):
|
---|
134 | if self.classID is None:
|
---|
135 | raise ServerException(nsError.NS_ERROR_NOT_IMPLEMENTED, "Class '%r' has no class ID" % (self.klass,))
|
---|
136 | return self.classID
|
---|
137 |
|
---|
138 | def getInterfaces(self):
|
---|
139 | if self.interfaces is None:
|
---|
140 | self.interfaces = _GetNominatedInterfaces(self.klass)
|
---|
141 | return self.interfaces
|
---|
142 |
|
---|
143 | def getHelperForLanguage(self, language):
|
---|
144 | return None # Not sure what to do here.
|
---|
145 |
|
---|
146 | class DefaultPolicy:
|
---|
147 | def __init__(self, instance, iid):
|
---|
148 | self._obj_ = instance
|
---|
149 | self._nominated_interfaces_ = ni = _GetNominatedInterfaces(instance)
|
---|
150 | self._iid_ = iid
|
---|
151 | if ni is None:
|
---|
152 | raise ValueError("The object '%r' can not be used as a COM object" % (instance,))
|
---|
153 | # This is really only a check for the user
|
---|
154 | if __debug__:
|
---|
155 | if iid != IID_nsISupports and iid not in ni:
|
---|
156 | # The object may delegate QI.
|
---|
157 | delegate_qi = getattr(instance, "_query_interface_", None)
|
---|
158 | # Perform the actual QI and throw away the result - the _real_
|
---|
159 | # QI performed by the framework will set things right!
|
---|
160 | if delegate_qi is None or not delegate_qi(iid):
|
---|
161 | raise ServerException(nsError.NS_ERROR_NO_INTERFACE)
|
---|
162 | # Stuff for the magic interface conversion.
|
---|
163 | self._interface_info_ = None
|
---|
164 | self._interface_iid_map_ = {} # Cache - Indexed by (method_index, param_index)
|
---|
165 |
|
---|
166 | def _QueryInterface_(self, com_object, iid):
|
---|
167 | # Framework allows us to return a single boolean integer,
|
---|
168 | # or a COM object.
|
---|
169 | if iid in self._nominated_interfaces_:
|
---|
170 | # We return the underlying object re-wrapped
|
---|
171 | # in a new gateway - which is desirable, as one gateway should only support
|
---|
172 | # one interface (this wont affect the users of this policy - we can have as many
|
---|
173 | # gateways as we like pointing to the same Python objects - the users never
|
---|
174 | # see what object the call came in from.
|
---|
175 | # NOTE: We could have simply returned the instance and let the framework
|
---|
176 | # do the auto-wrap for us - but this way we prevent a round-trip back into Python
|
---|
177 | # code just for the autowrap.
|
---|
178 | return xpcom.server.WrapObject(self._obj_, iid, bWrapClient = 0)
|
---|
179 |
|
---|
180 | # Always support nsIClassInfo
|
---|
181 | if iid == _xpcom.IID_nsIClassInfo:
|
---|
182 | return GetClassInfoForObject(self._obj_)
|
---|
183 |
|
---|
184 | # See if the instance has a QI
|
---|
185 | # use lower-case "_query_interface_" as win32com does, and it doesnt really matter.
|
---|
186 | delegate = getattr(self._obj_, "_query_interface_", None)
|
---|
187 | if delegate is not None:
|
---|
188 | # The COM object itself doesnt get passed to the child
|
---|
189 | # (again, as win32com doesnt). It is rarely needed
|
---|
190 | # (in win32com, we dont even pass it to the policy, although we have identified
|
---|
191 | # one place where we should - for marshalling - so I figured I may as well pass it
|
---|
192 | # to the policy layer here, but no all the way down to the object.
|
---|
193 | return delegate(iid)
|
---|
194 | # Finally see if we are being queried for one of the "nsISupports primitives"
|
---|
195 | if not _supports_primitives_map_:
|
---|
196 | iim = _xpcom.XPTI_GetInterfaceInfoManager()
|
---|
197 | for (iid_name, attr, cvt) in _supports_primitives_data_:
|
---|
198 | special_iid = iim.GetInfoForName(iid_name).GetIID()
|
---|
199 | _supports_primitives_map_[special_iid] = (attr, cvt)
|
---|
200 | attr, cvt = _supports_primitives_map_.get(iid, (None,None))
|
---|
201 | if attr is not None and hasattr(self._obj_, attr):
|
---|
202 | return xpcom.server.WrapObject(SupportsPrimitive(iid, self._obj_, attr, cvt), iid, bWrapClient = 0)
|
---|
203 | # Out of clever things to try!
|
---|
204 | return None # We dont support this IID.
|
---|
205 |
|
---|
206 | def _MakeInterfaceParam_(self, interface, iid, method_index, mi, param_index):
|
---|
207 | # Wrap a "raw" interface object in a nice object. The result of this
|
---|
208 | # function will be passed to one of the gateway methods.
|
---|
209 | if iid is None:
|
---|
210 | # look up the interface info - this will be true for all xpcom called interfaces.
|
---|
211 | if self._interface_info_ is None:
|
---|
212 | import xpcom.xpt
|
---|
213 | self._interface_info_ = xpcom.xpt.Interface( self._iid_ )
|
---|
214 | iid = self._interface_iid_map_.get( (method_index, param_index))
|
---|
215 | if iid is None:
|
---|
216 | iid = self._interface_info_.GetIIDForParam(method_index, param_index)
|
---|
217 | self._interface_iid_map_[(method_index, param_index)] = iid
|
---|
218 | # handle nsIVariant
|
---|
219 | if iid == IID_nsIVariant:
|
---|
220 | interface = interface.QueryInterface(iid)
|
---|
221 | dt = interface.dataType
|
---|
222 | if dt in VARIANT_INT_TYPES:
|
---|
223 | return interface.getAsInt32()
|
---|
224 | if dt in VARIANT_LONG_TYPES:
|
---|
225 | return interface.getAsInt64()
|
---|
226 | if dt in VARIANT_FLOAT_TYPES:
|
---|
227 | return interface.getAsFloat()
|
---|
228 | if dt in VARIANT_STRING_TYPES:
|
---|
229 | return interface.getAsStringWithSize()
|
---|
230 | if dt in VARIANT_UNICODE_TYPES:
|
---|
231 | return interface.getAsWStringWithSize()
|
---|
232 | if dt == xpcom_consts.VTYPE_BOOL:
|
---|
233 | return interface.getAsBool()
|
---|
234 | if dt == xpcom_consts.VTYPE_INTERFACE:
|
---|
235 | return interface.getAsISupports()
|
---|
236 | if dt == xpcom_consts.VTYPE_INTERFACE_IS:
|
---|
237 | return interface.getAsInterface()
|
---|
238 | if dt == xpcom_consts.VTYPE_EMPTY or dt == xpcom_consts.VTYPE_VOID:
|
---|
239 | return None
|
---|
240 | if dt == xpcom_consts.VTYPE_ARRAY:
|
---|
241 | return interface.getAsArray()
|
---|
242 | if dt == xpcom_consts.VTYPE_EMPTY_ARRAY:
|
---|
243 | return []
|
---|
244 | if dt == xpcom_consts.VTYPE_ID:
|
---|
245 | return interface.getAsID()
|
---|
246 | # all else fails...
|
---|
247 | logger.warning("Warning: nsIVariant type %d not supported - returning a string", dt)
|
---|
248 | try:
|
---|
249 | return interface.getAsString()
|
---|
250 | except COMException:
|
---|
251 | logger.exception("Error: failed to get Variant as a string - returning variant object")
|
---|
252 | return interface
|
---|
253 |
|
---|
254 | return client.Component(interface, iid)
|
---|
255 |
|
---|
256 | def _CallMethod_(self, com_object, index, info, params):
|
---|
257 | #print "_CallMethod_", index, info, params
|
---|
258 | flags, name, param_descs, ret = info
|
---|
259 | assert ret[1][0] == xpcom_consts.TD_UINT32, "Expected an nsresult (%s)" % (ret,)
|
---|
260 | if XPT_MD_IS_GETTER(flags):
|
---|
261 | # Look for a function of that name
|
---|
262 | func = getattr(self._obj_, "get_" + name, None)
|
---|
263 | if func is None:
|
---|
264 | assert len(param_descs)==1 and len(params)==0, "Can only handle a single [out] arg for a default getter"
|
---|
265 | ret = getattr(self._obj_, name) # Let attribute error go here!
|
---|
266 | else:
|
---|
267 | ret = func(*params)
|
---|
268 | return 0, ret
|
---|
269 | elif XPT_MD_IS_SETTER(flags):
|
---|
270 | # Look for a function of that name
|
---|
271 | func = getattr(self._obj_, "set_" + name, None)
|
---|
272 | if func is None:
|
---|
273 | assert len(param_descs)==1 and len(params)==1, "Can only handle a single [in] arg for a default setter"
|
---|
274 | setattr(self._obj_, name, params[0]) # Let attribute error go here!
|
---|
275 | else:
|
---|
276 | func(*params)
|
---|
277 | return 0
|
---|
278 | else:
|
---|
279 | # A regular method.
|
---|
280 | func = getattr(self._obj_, name)
|
---|
281 | return 0, func(*params)
|
---|
282 |
|
---|
283 | def _doHandleException(self, func_name, exc_info):
|
---|
284 | exc_val = exc_info[1]
|
---|
285 | is_server_exception = isinstance(exc_val, ServerException)
|
---|
286 | if is_server_exception:
|
---|
287 | # When a component raised an explicit COM exception, it is
|
---|
288 | # considered 'normal' - however, we still write a debug log
|
---|
289 | # record to help track these otherwise silent exceptions.
|
---|
290 |
|
---|
291 | # Note that Python 2.3 does not allow an explicit exc_info tuple
|
---|
292 | # and passing 'True' will not work as there is no exception pending.
|
---|
293 | # Trick things!
|
---|
294 | if logger.isEnabledFor(logging.DEBUG):
|
---|
295 | try:
|
---|
296 | if sys.version_info[0] <= 2:
|
---|
297 | exec('raise exc_info[0], exc_info[1], exc_info[2]')
|
---|
298 | else:
|
---|
299 | raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
---|
300 | except:
|
---|
301 | logger.debug("'%s' raised COM Exception %s",
|
---|
302 | func_name, exc_val, exc_info = 1)
|
---|
303 | return exc_val.errno
|
---|
304 | # Unhandled exception - always print a warning and the traceback.
|
---|
305 | # As above, trick the logging module to handle Python 2.3
|
---|
306 | try:
|
---|
307 | if sys.version_info[0] <= 2:
|
---|
308 | exec('raise exc_info[0], exc_info[1], exc_info[2]')
|
---|
309 | else:
|
---|
310 | raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
---|
311 | except:
|
---|
312 | logger.exception("Unhandled exception calling '%s'", func_name)
|
---|
313 | return nsError.NS_ERROR_FAILURE
|
---|
314 |
|
---|
315 | # Called whenever an unhandled Python exception is detected as a result
|
---|
316 | # of _CallMethod_ - this exception may have been raised during the _CallMethod_
|
---|
317 | # invocation, or after its return, but when unpacking the results
|
---|
318 | # eg, type errors, such as a Python integer being used as a string "out" param.
|
---|
319 | def _CallMethodException_(self, com_object, index, info, params, exc_info):
|
---|
320 | # Later we may want to have some smart "am I debugging" flags?
|
---|
321 | # Or maybe just delegate to the actual object - it's probably got the best
|
---|
322 | # idea what to do with them!
|
---|
323 | flags, name, param_descs, ret = info
|
---|
324 | exc_typ, exc_val, exc_tb = exc_info
|
---|
325 | # use the xpt module to get a better repr for the method.
|
---|
326 | # But if we fail, ignore it!
|
---|
327 | try:
|
---|
328 | import xpcom.xpt
|
---|
329 | m = xpcom.xpt.Method(info, index, None)
|
---|
330 | func_repr = m.Describe().lstrip()
|
---|
331 | except COMException:
|
---|
332 | func_repr = "%s(%r)" % (name, param_descs)
|
---|
333 | except:
|
---|
334 | # any other errors are evil!? Log it
|
---|
335 | self._doHandleException("<building method repr>", sys.exc_info())
|
---|
336 | # And fall through to logging the original error.
|
---|
337 | return self._doHandleException(func_repr, exc_info)
|
---|
338 |
|
---|
339 | # Called whenever a gateway fails due to anything other than _CallMethod_.
|
---|
340 | # Really only used for the component loader etc objects, so most
|
---|
341 | # users should never see exceptions triggered here.
|
---|
342 | def _GatewayException_(self, name, exc_info):
|
---|
343 | return self._doHandleException(name, exc_info)
|
---|
344 |
|
---|
345 | if sys.version_info[0] <= 2:
|
---|
346 | _supports_primitives_data_ = [
|
---|
347 | ("nsISupportsCString", "__str__", str),
|
---|
348 | ("nsISupportsString", "__unicode__", unicode),
|
---|
349 | ("nsISupportsPRUint64", "__long__", long),
|
---|
350 | ("nsISupportsPRInt64", "__long__", long),
|
---|
351 | ("nsISupportsPRUint32", "__int__", int),
|
---|
352 | ("nsISupportsPRInt32", "__int__", int),
|
---|
353 | ("nsISupportsPRUint16", "__int__", int),
|
---|
354 | ("nsISupportsPRInt16", "__int__", int),
|
---|
355 | ("nsISupportsPRUint8", "__int__", int),
|
---|
356 | ("nsISupportsPRBool", "__nonzero__", operator.truth),
|
---|
357 | ("nsISupportsDouble", "__float__", float),
|
---|
358 | ("nsISupportsFloat", "__float__", float),
|
---|
359 | ]
|
---|
360 | else:
|
---|
361 | _supports_primitives_data_ = [
|
---|
362 | ("nsISupportsCString", "__str__", str),
|
---|
363 | ("nsISupportsString", "__unicode__", str),
|
---|
364 | ("nsISupportsPRUint64", "__long__", int),
|
---|
365 | ("nsISupportsPRInt64", "__long__", int),
|
---|
366 | ("nsISupportsPRUint32", "__int__", int),
|
---|
367 | ("nsISupportsPRInt32", "__int__", int),
|
---|
368 | ("nsISupportsPRUint16", "__int__", int),
|
---|
369 | ("nsISupportsPRInt16", "__int__", int),
|
---|
370 | ("nsISupportsPRUint8", "__int__", int),
|
---|
371 | ("nsISupportsPRBool", "__nonzero__", operator.truth),
|
---|
372 | ("nsISupportsDouble", "__float__", float),
|
---|
373 | ("nsISupportsFloat", "__float__", float),
|
---|
374 | ]
|
---|
375 |
|
---|
376 | # Support for the nsISupports primitives:
|
---|
377 | class SupportsPrimitive:
|
---|
378 | _com_interfaces_ = ["nsISupports"]
|
---|
379 | def __init__(self, iid, base_ob, attr_name, converter):
|
---|
380 | self.iid = iid
|
---|
381 | self.base_ob = base_ob
|
---|
382 | self.attr_name = attr_name
|
---|
383 | self.converter = converter
|
---|
384 | def _query_interface_(self, iid):
|
---|
385 | if iid == self.iid:
|
---|
386 | return 1
|
---|
387 | return None
|
---|
388 | def get_data(self):
|
---|
389 | method = getattr(self.base_ob, self.attr_name)
|
---|
390 | val = method()
|
---|
391 | return self.converter(val)
|
---|
392 | def set_data(self, val):
|
---|
393 | raise ServerException(nsError.NS_ERROR_NOT_IMPLEMENTED)
|
---|
394 | def toString(self):
|
---|
395 | return str(self.get_data())
|
---|
396 |
|
---|
397 | def _shutdown():
|
---|
398 | class_info_cache.clear()
|
---|