From f8fe689a81f906d1b91bb3220acde2a4ecb14c5b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 6 May 2024 05:01:46 +0200 Subject: Adding upstream version 6.0.4-dfsg. Signed-off-by: Daniel Baumann --- src/libs/xpcom18a4/python/server/.cvsignore | 2 + src/libs/xpcom18a4/python/server/__init__.py | 88 ++++++ src/libs/xpcom18a4/python/server/enumerator.py | 58 ++++ src/libs/xpcom18a4/python/server/factory.py | 68 +++++ src/libs/xpcom18a4/python/server/loader.py | 227 ++++++++++++++ src/libs/xpcom18a4/python/server/module.py | 108 +++++++ src/libs/xpcom18a4/python/server/policy.py | 398 +++++++++++++++++++++++++ 7 files changed, 949 insertions(+) create mode 100644 src/libs/xpcom18a4/python/server/.cvsignore create mode 100755 src/libs/xpcom18a4/python/server/__init__.py create mode 100755 src/libs/xpcom18a4/python/server/enumerator.py create mode 100755 src/libs/xpcom18a4/python/server/factory.py create mode 100755 src/libs/xpcom18a4/python/server/loader.py create mode 100755 src/libs/xpcom18a4/python/server/module.py create mode 100755 src/libs/xpcom18a4/python/server/policy.py (limited to 'src/libs/xpcom18a4/python/server') diff --git a/src/libs/xpcom18a4/python/server/.cvsignore b/src/libs/xpcom18a4/python/server/.cvsignore new file mode 100644 index 00000000..52e4e611 --- /dev/null +++ b/src/libs/xpcom18a4/python/server/.cvsignore @@ -0,0 +1,2 @@ +*.pyc +*.pyo diff --git a/src/libs/xpcom18a4/python/server/__init__.py b/src/libs/xpcom18a4/python/server/__init__.py new file mode 100755 index 00000000..48b15106 --- /dev/null +++ b/src/libs/xpcom18a4/python/server/__init__.py @@ -0,0 +1,88 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the Python XPCOM language bindings. +# +# The Initial Developer of the Original Code is +# Activestate Tool Corp. +# Portions created by the Initial Developer are Copyright (C) 2000 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Hammond +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +# The xpcom.server package. + +from xpcom.server.policy import DefaultPolicy +from xpcom import _xpcom + +# We define the concept of a single "tracer" object - similar to the single +# Python "trace hook" for debugging. Someone can set +# xpcom.server.tracer to some class/function, and it will be used in place +# of the real xpcom object. Presumably this "trace" object will delegate +# to the real object, but presumably also taking some other action, such +# as calling a profiler or debugger. +# tracer_unwrap is a function used to "unwrap" the tracer object. +# If is expected that tracer_unwrap will be called with an object +# previously returned by "tracer()". +tracer = tracer_unwrap = None + +# Wrap an instance in an interface (via a policy) +def WrapObject(ob, iid, policy = None, bWrapClient = 1): + """Called by the framework to attempt to wrap + an object in a policy. + If iid is None, it will use the first interface the object indicates it supports. + """ + if policy is None: + policy = DefaultPolicy + if tracer is not None: + ob = tracer(ob) + return _xpcom.WrapObject(policy( ob, iid ), iid, bWrapClient) + +# Unwrap a Python object back into the Python object +def UnwrapObject(ob): + if ob is None: + return None + ret = _xpcom.UnwrapObject(ob)._obj_ + if tracer_unwrap is not None: + ret = tracer_unwrap(ret) + return ret + +# Create the main module for the Python loader. +# This is a once only init process, and the returned object +# if used to load all other Python components. + +# This means that we keep all factories, modules etc implemented in +# Python! +def NS_GetModule( serviceManager, nsIFile ): + from . import loader + iid = _xpcom.IID_nsIModule + return WrapObject(loader.MakePythonComponentLoaderModule(serviceManager, nsIFile), iid, bWrapClient = 0) + +def _shutdown(): + from xpcom.server.policy import _shutdown + _shutdown() diff --git a/src/libs/xpcom18a4/python/server/enumerator.py b/src/libs/xpcom18a4/python/server/enumerator.py new file mode 100755 index 00000000..d3fb89a9 --- /dev/null +++ b/src/libs/xpcom18a4/python/server/enumerator.py @@ -0,0 +1,58 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the Python XPCOM language bindings. +# +# The Initial Developer of the Original Code is +# ActiveState Tool Corp. +# Portions created by the Initial Developer are Copyright (C) 2000, 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Hammond (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +from xpcom import components + +# This class is created by Python components when it +# needs to return an enumerator. +# For example, a component may implement a function: +# nsISimpleEnumerator enumSomething(); +# This could could simply say: +# return SimpleEnumerator([something1, something2, something3]) +class SimpleEnumerator: + _com_interfaces_ = [components.interfaces.nsISimpleEnumerator] + + def __init__(self, data): + self._data = data + self._index = 0 + + def hasMoreElements(self): + return self._index < len(self._data) + + def getNext(self): + self._index = self._index + 1 + return self._data[self._index-1] diff --git a/src/libs/xpcom18a4/python/server/factory.py b/src/libs/xpcom18a4/python/server/factory.py new file mode 100755 index 00000000..b65483dd --- /dev/null +++ b/src/libs/xpcom18a4/python/server/factory.py @@ -0,0 +1,68 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the Python XPCOM language bindings. +# +# The Initial Developer of the Original Code is +# ActiveState Tool Corp. +# Portions created by the Initial Developer are Copyright (C) 2000, 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Hammond (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +# Class factory +# +# Hardly worth its own source file! +import xpcom +from xpcom import components, nsError, _xpcom, logger + +class Factory: + _com_interfaces_ = components.interfaces.nsIFactory + # This will only ever be constructed via other Python code, + # so we can have ctor args. + def __init__(self, klass): + self.klass = klass + + def createInstance(self, outer, iid): + if outer is not None: + raise xpcom.ServerException(nsError.NS_ERROR_NO_AGGREGATION) + + logger.debug("Python Factory creating %s", self.klass.__name__) + try: + return self.klass() + except: + # An exception here may not be obvious to the user - none + # of their code has been called yet. It can be handy on + # failure to tell the user what class failed! + logger.error("Creation of class '%r' failed!\nException details follow\n", + self.klass) + # The framework itself will also report the error. + raise + + def lockServer(self, lock): + logger.debug("Python Factory LockServer called '%s'", lock) diff --git a/src/libs/xpcom18a4/python/server/loader.py b/src/libs/xpcom18a4/python/server/loader.py new file mode 100755 index 00000000..ebd42b61 --- /dev/null +++ b/src/libs/xpcom18a4/python/server/loader.py @@ -0,0 +1,227 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the Python XPCOM language bindings. +# +# The Initial Developer of the Original Code is +# Activestate Tool Corp. +# Portions created by the Initial Developer are Copyright (C) 2000 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Hammond +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import xpcom +from xpcom import components, logger +from . import module +import glob +import os +from xpcom.client import Component + +# Until we get interface constants. +When_Startup = 0 +When_Component = 1 +When_Timer = 2 + +def _has_good_attr(object, attr): + # Actually allows "None" to be specified to disable inherited attributes. + return getattr(object, attr, None) is not None + +def FindCOMComponents(py_module): + # For now, just run over all classes looking for likely candidates. + comps = [] + for name, object in list(py_module.__dict__.items()): + try: + if (type(object) == type or issubclass(object, object)) and \ + _has_good_attr(object, "_com_interfaces_") and \ + _has_good_attr(object, "_reg_clsid_") and \ + _has_good_attr(object, "_reg_contractid_"): + comps.append(object) + except TypeError: + # The issubclass call raises TypeError when the obj is not a class. + pass; + return comps + +def register_self(klass, compMgr, location, registryLocation, componentType): + pcl = PythonComponentLoader + from xpcom import _xpcom + svc = _xpcom.GetServiceManager().getServiceByContractID("@mozilla.org/categorymanager;1", components.interfaces.nsICategoryManager) + svc.addCategoryEntry("component-loader", pcl._reg_component_type_, pcl._reg_contractid_, 1, 1) + +class PythonComponentLoader: + _com_interfaces_ = components.interfaces.nsIComponentLoader + _reg_clsid_ = "{63B68B1E-3E62-45f0-98E3-5E0B5797970C}" # Never copy these! + _reg_contractid_ = "moz.pyloader.1" + _reg_desc_ = "Python component loader" + # Optional function which performs additional special registration + # Appears that no special unregistration is needed for ComponentLoaders, hence no unregister function. + _reg_registrar_ = (register_self,None) + # Custom attributes for ComponentLoader registration. + _reg_component_type_ = "script/python" + + def __init__(self): + self.com_modules = {} # Keyed by module's FQN as obtained from nsIFile.path + self.moduleFactory = module.Module + self.num_modules_this_register = 0 + + def _getCOMModuleForLocation(self, componentFile): + fqn = componentFile.path + mod = self.com_modules.get(fqn) + if mod is not None: + return mod + import ihooks, sys + base_name = os.path.splitext(os.path.basename(fqn))[0] + loader = ihooks.ModuleLoader() + + module_name_in_sys = "component:%s" % (base_name,) + stuff = loader.find_module(base_name, [componentFile.parent.path]) + assert stuff is not None, "Couldnt find the module '%s'" % (base_name,) + py_mod = loader.load_module( module_name_in_sys, stuff ) + + # Make and remember the COM module. + comps = FindCOMComponents(py_mod) + mod = self.moduleFactory(comps) + + self.com_modules[fqn] = mod + return mod + + def getFactory(self, clsid, location, type): + # return the factory + assert type == self._reg_component_type_, "Being asked to create an object not of my type:%s" % (type,) + # FIXME: how to do this without obsolete component manager? + cmo = components.manager.queryInterface(components.interfaces.nsIComponentManagerObsolete) + file_interface = cmo.specForRegistryLocation(location) + # delegate to the module. + m = self._getCOMModuleForLocation(file_interface) + return m.getClassObject(components.manager, clsid, components.interfaces.nsIFactory) + + def init(self, comp_mgr, registry): + # void + self.comp_mgr = comp_mgr + logger.debug("Python component loader init() called") + + # Called when a component of the appropriate type is registered, + # to give the component loader an opportunity to do things like + # annotate the registry and such. + def onRegister (self, clsid, type, className, proId, location, replace, persist): + logger.debug("Python component loader - onRegister() called") + + def autoRegisterComponents (self, when, directory): + directory_path = directory.path + self.num_modules_this_register = 0 + logger.debug("Auto-registering all Python components in '%s'", directory_path) + + # ToDo - work out the right thing here + # eg - do we recurse? + # - do we support packages? + entries = directory.directoryEntries + while entries.HasMoreElements(): + entry = entries.GetNext(components.interfaces.nsIFile) + if os.path.splitext(entry.path)[1]==".py": + try: + self.autoRegisterComponent(when, entry) + # Handle some common user errors + except xpcom.COMException as details: + from xpcom import nsError + # If the interface name does not exist, suppress the traceback + if details.errno==nsError.NS_ERROR_NO_INTERFACE: + logger.error("Registration of '%s' failed\n %s", + entry.leafName, details.message) + else: + logger.exception("Registration of '%s' failed!", entry.leafName) + except SyntaxError as details: + # Syntax error in source file - no useful traceback here either. + logger.error("Registration of '%s' failed\n %s", + entry.leafName, details) + except: + # All other exceptions get the full traceback. + logger.exception("Registration of '%s' failed.", entry.leafName) + + def autoRegisterComponent (self, when, componentFile): + # bool return + + # Check if we actually need to do anything + modtime = componentFile.lastModifiedTime + loader_mgr = components.manager.queryInterface(components.interfaces.nsIComponentLoaderManager) + if not loader_mgr.hasFileChanged(componentFile, None, modtime): + return 1 + + if self.num_modules_this_register == 0: + # New components may have just installed new Python + # modules into the main python directory (including new .pth files) + # So we ask Python to re-process our site directory. + # Note that the pyloader does the equivalent when loading. + try: + from xpcom import _xpcom + import site + NS_XPCOM_CURRENT_PROCESS_DIR="XCurProcD" + dirname = _xpcom.GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR) + dirname.append("python") + site.addsitedir(dirname.path) + except: + logger.exception("PyXPCOM loader failed to process site directory before component registration") + + self.num_modules_this_register += 1 + + # auto-register via the module. + m = self._getCOMModuleForLocation(componentFile) + m.registerSelf(components.manager, componentFile, None, self._reg_component_type_) + loader_mgr = components.manager.queryInterface(components.interfaces.nsIComponentLoaderManager) + loader_mgr.saveFileInfo(componentFile, None, modtime) + return 1 + + def autoUnregisterComponent (self, when, componentFile): + # bool return + # auto-unregister via the module. + m = self._getCOMModuleForLocation(componentFile) + loader_mgr = components.manager.queryInterface(components.interfaces.nsIComponentLoaderManager) + try: + m.unregisterSelf(components.manager, componentFile) + finally: + loader_mgr.removeFileInfo(componentFile, None) + return 1 + + def registerDeferredComponents (self, when): + # bool return + logger.debug("Python component loader - registerDeferred() called") + return 0 # no more to register + + def unloadAll (self, when): + # This is called at shutdown time - don't get too upset if an error + # results from logging due to the logfile being closed + try: + logger.debug("Python component loader being asked to unload all components!") + except: + # Evil blank except, but restricting to just catching IOError + # failure means custom logs could still screw us + pass + self.comp_mgr = None + self.com_modules = {} + +def MakePythonComponentLoaderModule(serviceManager, nsIFile): + from . import module + return module.Module( [PythonComponentLoader] ) diff --git a/src/libs/xpcom18a4/python/server/module.py b/src/libs/xpcom18a4/python/server/module.py new file mode 100755 index 00000000..73024e95 --- /dev/null +++ b/src/libs/xpcom18a4/python/server/module.py @@ -0,0 +1,108 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the Python XPCOM language bindings. +# +# The Initial Developer of the Original Code is ActiveState Tool Corp. +# Portions created by ActiveState Tool Corp. are Copyright (C) 2000, 2001 +# ActiveState Tool Corp. All Rights Reserved. +# +# Contributor(s): +# Mark Hammond (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +from xpcom import components +from xpcom import ServerException, Exception +from xpcom import nsError + +from . import factory + +import types +import os + +class Module: + _com_interfaces_ = components.interfaces.nsIModule + def __init__(self, comps): + # Build a map of classes we can provide factories for. + c = self.components = {} + for klass in comps: + c[components.ID(klass._reg_clsid_)] = klass + self.klassFactory = factory.Factory + + def getClassObject(self, compMgr, clsid, iid): + # Single retval result. + try: + klass = self.components[clsid] + except KeyError: + raise ServerException(nsError.NS_ERROR_FACTORY_NOT_REGISTERED) + + # We can ignore the IID - the auto-wrap process will automatically QI us. + return self.klassFactory(klass) + + def registerSelf(self, compMgr, location, loaderStr, componentType): + # void function. + fname = os.path.basename(location.path) + for klass in list(self.components.values()): + reg_contractid = klass._reg_contractid_ + print("Registering '%s' (%s)" % (reg_contractid, fname)) + reg_desc = getattr(klass, "_reg_desc_", reg_contractid) + compMgr = compMgr.queryInterface(components.interfaces.nsIComponentRegistrar) + compMgr.registerFactoryLocation(klass._reg_clsid_, + reg_desc, + reg_contractid, + location, + loaderStr, + componentType) + + # See if this class nominates custom register_self + extra_func = getattr(klass, "_reg_registrar_", (None,None))[0] + if extra_func is not None: + extra_func(klass, compMgr, location, loaderStr, componentType) + + def unregisterSelf(self, compMgr, location, loaderStr): + # void function. + for klass in list(self.components.values()): + ok = 1 + try: + compMgr.unregisterComponentSpec(klass._reg_clsid_, location) + except Exception: + ok = 0 + # Give the class a bash even if we failed! + extra_func = getattr(klass, "_reg_registrar_", (None,None))[1] + if extra_func is not None: + try: + extra_func(klass, compMgr, location, loaderStr) + except Exception: + ok = 0 + if ok: + print("Successfully unregistered", klass.__name__) + else: + print("Unregistration of", klass.__name__, "failed. (probably just not already registered)") + + def canUnload(self, compMgr): + # single bool result + return 0 # we can never unload! + diff --git a/src/libs/xpcom18a4/python/server/policy.py b/src/libs/xpcom18a4/python/server/policy.py new file mode 100755 index 00000000..7f84167c --- /dev/null +++ b/src/libs/xpcom18a4/python/server/policy.py @@ -0,0 +1,398 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Python XPCOM language bindings. +# +# The Initial Developer of the Original Code is +# ActiveState Tool Corp. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Hammond (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +from xpcom import xpcom_consts, _xpcom, client, nsError, logger +from xpcom import ServerException, COMException +import xpcom +import xpcom.server +import operator +import types +import logging +import sys + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int # pylint: disable=W0622,C0103 + + +IID_nsISupports = _xpcom.IID_nsISupports +IID_nsIVariant = _xpcom.IID_nsIVariant +XPT_MD_IS_GETTER = xpcom_consts.XPT_MD_IS_GETTER +XPT_MD_IS_SETTER = xpcom_consts.XPT_MD_IS_SETTER + +VARIANT_INT_TYPES = xpcom_consts.VTYPE_INT8, xpcom_consts.VTYPE_INT16, xpcom_consts.VTYPE_INT32, \ + xpcom_consts.VTYPE_UINT8, xpcom_consts.VTYPE_UINT16, xpcom_consts.VTYPE_INT32 +VARIANT_LONG_TYPES = xpcom_consts.VTYPE_INT64, xpcom_consts.VTYPE_UINT64 +VARIANT_FLOAT_TYPES = xpcom_consts.VTYPE_FLOAT, xpcom_consts.VTYPE_DOUBLE +VARIANT_STRING_TYPES = xpcom_consts.VTYPE_CHAR, xpcom_consts.VTYPE_CHAR_STR, xpcom_consts.VTYPE_STRING_SIZE_IS, \ + xpcom_consts.VTYPE_CSTRING +VARIANT_UNICODE_TYPES = xpcom_consts.VTYPE_WCHAR, xpcom_consts.VTYPE_DOMSTRING, xpcom_consts.VTYPE_WSTRING_SIZE_IS, \ + xpcom_consts.VTYPE_ASTRING + +_supports_primitives_map_ = {} # Filled on first use. + +_interface_sequence_types_ = tuple, list +if sys.version_info[0] <= 2: + _string_types_ = str, unicode +else: + _string_types_ = bytes, str +XPTI_GetInterfaceInfoManager = _xpcom.XPTI_GetInterfaceInfoManager + +def _GetNominatedInterfaces(obj): + ret = getattr(obj, "_com_interfaces_", None) + if ret is None: return None + # See if the user only gave one. + if type(ret) not in _interface_sequence_types_: + ret = [ret] + real_ret = [] + # For each interface, walk to the root of the interface tree. + iim = XPTI_GetInterfaceInfoManager() + for interface in ret: + # Allow interface name or IID. + interface_info = None + if type(interface) in _string_types_: + try: + interface_info = iim.GetInfoForName(interface) + except COMException: + pass + if interface_info is None: + # Allow a real IID + interface_info = iim.GetInfoForIID(interface) + real_ret.append(interface_info.GetIID()) + parent = interface_info.GetParent() + while parent is not None: + parent_iid = parent.GetIID() + if parent_iid == IID_nsISupports: + break + real_ret.append(parent_iid) + parent = parent.GetParent() + return real_ret + +## +## ClassInfo support +## +## We cache class infos by class +class_info_cache = {} + +def GetClassInfoForObject(ob): + if xpcom.server.tracer_unwrap is not None: + ob = xpcom.server.tracer_unwrap(ob) + klass = ob.__class__ + ci = class_info_cache.get(klass) + if ci is None: + ci = DefaultClassInfo(klass) + ci = xpcom.server.WrapObject(ci, _xpcom.IID_nsIClassInfo, bWrapClient = 0) + class_info_cache[klass] = ci + return ci + +class DefaultClassInfo: + _com_interfaces_ = _xpcom.IID_nsIClassInfo + def __init__(self, klass): + self.klass = klass + self.contractID = getattr(klass, "_reg_contractid_", None) + self.classDescription = getattr(klass, "_reg_desc_", None) + self.classID = getattr(klass, "_reg_clsid_", None) + self.implementationLanguage = 3 # Python - avoid lookups just for this + self.flags = 0 # what to do here?? + self.interfaces = None + + def get_classID(self): + if self.classID is None: + raise ServerException(nsError.NS_ERROR_NOT_IMPLEMENTED, "Class '%r' has no class ID" % (self.klass,)) + return self.classID + + def getInterfaces(self): + if self.interfaces is None: + self.interfaces = _GetNominatedInterfaces(self.klass) + return self.interfaces + + def getHelperForLanguage(self, language): + return None # Not sure what to do here. + +class DefaultPolicy: + def __init__(self, instance, iid): + self._obj_ = instance + self._nominated_interfaces_ = ni = _GetNominatedInterfaces(instance) + self._iid_ = iid + if ni is None: + raise ValueError("The object '%r' can not be used as a COM object" % (instance,)) + # This is really only a check for the user + if __debug__: + if iid != IID_nsISupports and iid not in ni: + # The object may delegate QI. + delegate_qi = getattr(instance, "_query_interface_", None) + # Perform the actual QI and throw away the result - the _real_ + # QI performed by the framework will set things right! + if delegate_qi is None or not delegate_qi(iid): + raise ServerException(nsError.NS_ERROR_NO_INTERFACE) + # Stuff for the magic interface conversion. + self._interface_info_ = None + self._interface_iid_map_ = {} # Cache - Indexed by (method_index, param_index) + + def _QueryInterface_(self, com_object, iid): + # Framework allows us to return a single boolean integer, + # or a COM object. + if iid in self._nominated_interfaces_: + # We return the underlying object re-wrapped + # in a new gateway - which is desirable, as one gateway should only support + # one interface (this wont affect the users of this policy - we can have as many + # gateways as we like pointing to the same Python objects - the users never + # see what object the call came in from. + # NOTE: We could have simply returned the instance and let the framework + # do the auto-wrap for us - but this way we prevent a round-trip back into Python + # code just for the autowrap. + return xpcom.server.WrapObject(self._obj_, iid, bWrapClient = 0) + + # Always support nsIClassInfo + if iid == _xpcom.IID_nsIClassInfo: + return GetClassInfoForObject(self._obj_) + + # See if the instance has a QI + # use lower-case "_query_interface_" as win32com does, and it doesnt really matter. + delegate = getattr(self._obj_, "_query_interface_", None) + if delegate is not None: + # The COM object itself doesnt get passed to the child + # (again, as win32com doesnt). It is rarely needed + # (in win32com, we dont even pass it to the policy, although we have identified + # one place where we should - for marshalling - so I figured I may as well pass it + # to the policy layer here, but no all the way down to the object. + return delegate(iid) + # Finally see if we are being queried for one of the "nsISupports primitives" + if not _supports_primitives_map_: + iim = _xpcom.XPTI_GetInterfaceInfoManager() + for (iid_name, attr, cvt) in _supports_primitives_data_: + special_iid = iim.GetInfoForName(iid_name).GetIID() + _supports_primitives_map_[special_iid] = (attr, cvt) + attr, cvt = _supports_primitives_map_.get(iid, (None,None)) + if attr is not None and hasattr(self._obj_, attr): + return xpcom.server.WrapObject(SupportsPrimitive(iid, self._obj_, attr, cvt), iid, bWrapClient = 0) + # Out of clever things to try! + return None # We dont support this IID. + + def _MakeInterfaceParam_(self, interface, iid, method_index, mi, param_index): + # Wrap a "raw" interface object in a nice object. The result of this + # function will be passed to one of the gateway methods. + if iid is None: + # look up the interface info - this will be true for all xpcom called interfaces. + if self._interface_info_ is None: + import xpcom.xpt + self._interface_info_ = xpcom.xpt.Interface( self._iid_ ) + iid = self._interface_iid_map_.get( (method_index, param_index)) + if iid is None: + iid = self._interface_info_.GetIIDForParam(method_index, param_index) + self._interface_iid_map_[(method_index, param_index)] = iid + # handle nsIVariant + if iid == IID_nsIVariant: + interface = interface.QueryInterface(iid) + dt = interface.dataType + if dt in VARIANT_INT_TYPES: + return interface.getAsInt32() + if dt in VARIANT_LONG_TYPES: + return interface.getAsInt64() + if dt in VARIANT_FLOAT_TYPES: + return interface.getAsFloat() + if dt in VARIANT_STRING_TYPES: + return interface.getAsStringWithSize() + if dt in VARIANT_UNICODE_TYPES: + return interface.getAsWStringWithSize() + if dt == xpcom_consts.VTYPE_BOOL: + return interface.getAsBool() + if dt == xpcom_consts.VTYPE_INTERFACE: + return interface.getAsISupports() + if dt == xpcom_consts.VTYPE_INTERFACE_IS: + return interface.getAsInterface() + if dt == xpcom_consts.VTYPE_EMPTY or dt == xpcom_consts.VTYPE_VOID: + return None + if dt == xpcom_consts.VTYPE_ARRAY: + return interface.getAsArray() + if dt == xpcom_consts.VTYPE_EMPTY_ARRAY: + return [] + if dt == xpcom_consts.VTYPE_ID: + return interface.getAsID() + # all else fails... + logger.warning("Warning: nsIVariant type %d not supported - returning a string", dt) + try: + return interface.getAsString() + except COMException: + logger.exception("Error: failed to get Variant as a string - returning variant object") + return interface + + return client.Component(interface, iid) + + def _CallMethod_(self, com_object, index, info, params): + #print "_CallMethod_", index, info, params + flags, name, param_descs, ret = info + assert ret[1][0] == xpcom_consts.TD_UINT32, "Expected an nsresult (%s)" % (ret,) + if XPT_MD_IS_GETTER(flags): + # Look for a function of that name + func = getattr(self._obj_, "get_" + name, None) + if func is None: + assert len(param_descs)==1 and len(params)==0, "Can only handle a single [out] arg for a default getter" + ret = getattr(self._obj_, name) # Let attribute error go here! + else: + ret = func(*params) + return 0, ret + elif XPT_MD_IS_SETTER(flags): + # Look for a function of that name + func = getattr(self._obj_, "set_" + name, None) + if func is None: + assert len(param_descs)==1 and len(params)==1, "Can only handle a single [in] arg for a default setter" + setattr(self._obj_, name, params[0]) # Let attribute error go here! + else: + func(*params) + return 0 + else: + # A regular method. + func = getattr(self._obj_, name) + return 0, func(*params) + + def _doHandleException(self, func_name, exc_info): + exc_val = exc_info[1] + is_server_exception = isinstance(exc_val, ServerException) + if is_server_exception: + # When a component raised an explicit COM exception, it is + # considered 'normal' - however, we still write a debug log + # record to help track these otherwise silent exceptions. + + # Note that Python 2.3 does not allow an explicit exc_info tuple + # and passing 'True' will not work as there is no exception pending. + # Trick things! + if logger.isEnabledFor(logging.DEBUG): + try: + if sys.version_info[0] <= 2: + exec('raise exc_info[0], exc_info[1], exc_info[2]') + else: + raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) + except: + logger.debug("'%s' raised COM Exception %s", + func_name, exc_val, exc_info = 1) + return exc_val.errno + # Unhandled exception - always print a warning and the traceback. + # As above, trick the logging module to handle Python 2.3 + try: + if sys.version_info[0] <= 2: + exec('raise exc_info[0], exc_info[1], exc_info[2]') + else: + raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) + except: + logger.exception("Unhandled exception calling '%s'", func_name) + return nsError.NS_ERROR_FAILURE + + # Called whenever an unhandled Python exception is detected as a result + # of _CallMethod_ - this exception may have been raised during the _CallMethod_ + # invocation, or after its return, but when unpacking the results + # eg, type errors, such as a Python integer being used as a string "out" param. + def _CallMethodException_(self, com_object, index, info, params, exc_info): + # Later we may want to have some smart "am I debugging" flags? + # Or maybe just delegate to the actual object - it's probably got the best + # idea what to do with them! + flags, name, param_descs, ret = info + exc_typ, exc_val, exc_tb = exc_info + # use the xpt module to get a better repr for the method. + # But if we fail, ignore it! + try: + import xpcom.xpt + m = xpcom.xpt.Method(info, index, None) + func_repr = m.Describe().lstrip() + except COMException: + func_repr = "%s(%r)" % (name, param_descs) + except: + # any other errors are evil!? Log it + self._doHandleException("", sys.exc_info()) + # And fall through to logging the original error. + return self._doHandleException(func_repr, exc_info) + + # Called whenever a gateway fails due to anything other than _CallMethod_. + # Really only used for the component loader etc objects, so most + # users should never see exceptions triggered here. + def _GatewayException_(self, name, exc_info): + return self._doHandleException(name, exc_info) + +if sys.version_info[0] <= 2: + _supports_primitives_data_ = [ + ("nsISupportsCString", "__str__", str), + ("nsISupportsString", "__unicode__", unicode), + ("nsISupportsPRUint64", "__long__", long), + ("nsISupportsPRInt64", "__long__", long), + ("nsISupportsPRUint32", "__int__", int), + ("nsISupportsPRInt32", "__int__", int), + ("nsISupportsPRUint16", "__int__", int), + ("nsISupportsPRInt16", "__int__", int), + ("nsISupportsPRUint8", "__int__", int), + ("nsISupportsPRBool", "__nonzero__", operator.truth), + ("nsISupportsDouble", "__float__", float), + ("nsISupportsFloat", "__float__", float), + ] +else: + _supports_primitives_data_ = [ + ("nsISupportsCString", "__str__", str), + ("nsISupportsString", "__unicode__", str), + ("nsISupportsPRUint64", "__long__", int), + ("nsISupportsPRInt64", "__long__", int), + ("nsISupportsPRUint32", "__int__", int), + ("nsISupportsPRInt32", "__int__", int), + ("nsISupportsPRUint16", "__int__", int), + ("nsISupportsPRInt16", "__int__", int), + ("nsISupportsPRUint8", "__int__", int), + ("nsISupportsPRBool", "__nonzero__", operator.truth), + ("nsISupportsDouble", "__float__", float), + ("nsISupportsFloat", "__float__", float), + ] + +# Support for the nsISupports primitives: +class SupportsPrimitive: + _com_interfaces_ = ["nsISupports"] + def __init__(self, iid, base_ob, attr_name, converter): + self.iid = iid + self.base_ob = base_ob + self.attr_name = attr_name + self.converter = converter + def _query_interface_(self, iid): + if iid == self.iid: + return 1 + return None + def get_data(self): + method = getattr(self.base_ob, self.attr_name) + val = method() + return self.converter(val) + def set_data(self, val): + raise ServerException(nsError.NS_ERROR_NOT_IMPLEMENTED) + def toString(self): + return str(self.get_data()) + +def _shutdown(): + class_info_cache.clear() -- cgit v1.2.3