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