0001 """Installation utilities for Python ISAPI filters and extensions.""" 0002 0003 # this code adapted from "Tomcat JK2 ISAPI redirector", part of Apache 0004 # Created July 2004, Mark Hammond. 0005 import sys, os, imp, shutil, stat 0006 from win32com.client import GetObject, Dispatch 0007 from win32com.client.gencache import EnsureModule, EnsureDispatch 0008 import pythoncom 0009 import winerror 0010 import traceback 0011 0012 _APP_INPROC = 0 0013 _APP_OUTPROC = 1 0014 _APP_POOLED = 2 0015 _IIS_OBJECT = "IIS://LocalHost/W3SVC" 0016 _IIS_SERVER = "IIsWebServer" 0017 _IIS_WEBDIR = "IIsWebDirectory" 0018 _IIS_WEBVIRTUALDIR = "IIsWebVirtualDir" 0019 _IIS_FILTERS = "IIsFilters" 0020 _IIS_FILTER = "IIsFilter" 0021 0022 _DEFAULT_SERVER_NAME = "Default Web Site" 0023 _DEFAULT_HEADERS = "X-Powered-By: Python" 0024 _DEFAULT_PROTECTION = _APP_POOLED 0025 0026 # Default is for 'execute' only access - ie, only the extension 0027 # can be used. This can be overridden via your install script. 0028 _DEFAULT_ACCESS_EXECUTE = True 0029 _DEFAULT_ACCESS_READ = False 0030 _DEFAULT_ACCESS_WRITE = False 0031 _DEFAULT_ACCESS_SCRIPT = False 0032 _DEFAULT_CONTENT_INDEXED = False 0033 _DEFAULT_ENABLE_DIR_BROWSING = False 0034 _DEFAULT_ENABLE_DEFAULT_DOC = False 0035 0036 is_debug_build = False 0037 for imp_ext, _, _ in imp.get_suffixes(): 0038 if imp_ext == "_d.pyd": 0039 is_debug_build = True 0040 break 0041 0042 this_dir = os.path.abspath(os.path.dirname(__file__)) 0043 0044 class FilterParameters: 0045 Name = None 0046 Description = None 0047 Path = None 0048 Server = None 0049 def __init__(self, **kw): 0050 self.__dict__.update(kw) 0051 0052 class VirtualDirParameters: 0053 Name = None # Must be provided. 0054 Description = None # defaults to Name 0055 AppProtection = _DEFAULT_PROTECTION 0056 Headers = _DEFAULT_HEADERS 0057 Path = None # defaults to WWW root. 0058 AccessExecute = _DEFAULT_ACCESS_EXECUTE 0059 AccessRead = _DEFAULT_ACCESS_READ 0060 AccessWrite = _DEFAULT_ACCESS_WRITE 0061 AccessScript = _DEFAULT_ACCESS_SCRIPT 0062 ContentIndexed = _DEFAULT_CONTENT_INDEXED 0063 EnableDirBrowsing = _DEFAULT_ENABLE_DIR_BROWSING 0064 EnableDefaultDoc = _DEFAULT_ENABLE_DEFAULT_DOC 0065 ScriptMaps = [] 0066 ScriptMapUpdate = "end" # can be 'start', 'end', 'replace' 0067 Server = None 0068 def __init__(self, **kw): 0069 self.__dict__.update(kw) 0070 0071 class ScriptMapParams: 0072 Extension = None 0073 Module = None 0074 Flags = 5 0075 Verbs = "" 0076 def __init__(self, **kw): 0077 self.__dict__.update(kw) 0078 0079 class ISAPIParameters: 0080 ServerName = _DEFAULT_SERVER_NAME 0081 # Description = None 0082 Filters = [] 0083 VirtualDirs = [] 0084 def __init__(self, **kw): 0085 self.__dict__.update(kw) 0086 0087 verbose = 1 # The level - 0 is quiet. 0088 def log(level, what): 0089 if verbose >= level: 0090 print what 0091 0092 # Convert an ADSI COM exception to the Win32 error code embedded in it. 0093 def _GetWin32ErrorCode(com_exc): 0094 hr, msg, exc, narg = com_exc 0095 # If we have more details in the 'exc' struct, use it. 0096 if exc: 0097 hr = exc[-1] 0098 if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32: 0099 raise 0100 return winerror.SCODE_CODE(hr) 0101 0102 class InstallationError(Exception): pass 0103 class ItemNotFound(InstallationError): pass 0104 class ConfigurationError(InstallationError): pass 0105 0106 def FindPath(options, server, name): 0107 if name.lower().startswith("iis://"): 0108 return name 0109 else: 0110 if name and name[0] != "/": 0111 name = "/"+name 0112 return FindWebServer(options, server)+"/ROOT"+name 0113 0114 def FindWebServer(options, server_desc): 0115 # command-line options get first go. 0116 if options.server: 0117 server = options.server 0118 # If the config passed by the caller doesn't specify one, use the default 0119 elif server_desc is None: 0120 server = _IIS_OBJECT+"/1" 0121 else: 0122 server = server_desc 0123 # Check it is good. 0124 try: 0125 GetObject(server) 0126 except pythoncom.com_error, details: 0127 hr, msg, exc, arg_err = details 0128 if exc and exc[2]: 0129 msg = exc[2] 0130 raise ItemNotFound, \ 0131 "WebServer %s: %s" % (server, msg) 0132 return server 0133 0134 def CreateDirectory(params, options): 0135 if not params.Name: 0136 raise ConfigurationError, "No Name param" 0137 slash = params.Name.rfind("/") 0138 if slash >= 0: 0139 parent = params.Name[:slash] 0140 name = params.Name[slash+1:] 0141 else: 0142 parent = "" 0143 name = params.Name 0144 webDir = GetObject(FindPath(options, params.Server, parent)) 0145 if parent: 0146 # Note that the directory won't be visible in the IIS UI 0147 # unless the directory exists on the filesystem. 0148 keyType = _IIS_WEBDIR 0149 else: 0150 keyType = _IIS_WEBVIRTUALDIR 0151 _CallHook(params, "PreInstall", options) 0152 try: 0153 newDir = webDir.Create(keyType, name) 0154 except pythoncom.com_error, details: 0155 rc = _GetWin32ErrorCode(details) 0156 if rc != winerror.ERROR_ALREADY_EXISTS: 0157 raise 0158 newDir = GetObject(FindPath(options, params.Server, params.Name)) 0159 log(2, "Updating existing directory '%s'..." % (params.Name,)) 0160 else: 0161 log(2, "Creating new directory '%s'..." % (params.Name,)) 0162 0163 friendly = params.Description or params.Name 0164 newDir.AppFriendlyName = friendly 0165 try: 0166 path = params.Path or webDir.Path 0167 newDir.Path = path 0168 except AttributeError: 0169 pass 0170 newDir.AppCreate2(params.AppProtection) 0171 newDir.HttpCustomHeaders = params.Headers 0172 0173 log(2, "Setting directory options...") 0174 newDir.AccessExecute = params.AccessExecute 0175 newDir.AccessRead = params.AccessRead 0176 newDir.AccessWrite = params.AccessWrite 0177 newDir.AccessScript = params.AccessScript 0178 newDir.ContentIndexed = params.ContentIndexed 0179 newDir.EnableDirBrowsing = params.EnableDirBrowsing 0180 newDir.EnableDefaultDoc = params.EnableDefaultDoc 0181 newDir.SetInfo() 0182 smp_items = [] 0183 for smp in params.ScriptMaps: 0184 item = "%s,%s,%s" % (smp.Extension, smp.Module, smp.Flags) 0185 # IIS gets upset if there is a trailing verb comma, but no verbs 0186 if smp.Verbs: 0187 item += "," + smp.Verbs 0188 smp_items.append(item) 0189 if params.ScriptMapUpdate == "replace": 0190 newDir.ScriptMaps = smp_items 0191 elif params.ScriptMapUpdate == "end": 0192 for item in smp_items: 0193 if item not in newDir.ScriptMaps: 0194 newDir.ScriptMaps = newDir.ScriptMaps + (item,) 0195 elif params.ScriptMapUpdate == "start": 0196 for item in smp_items: 0197 if item not in newDir.ScriptMaps: 0198 newDir.ScriptMaps = (item,) + newDir.ScriptMaps 0199 else: 0200 raise ConfigurationError, \ 0201 "Unknown ScriptMapUpdate option '%s'" % (params.ScriptMapUpdate,) 0202 newDir.SetInfo() 0203 _CallHook(params, "PostInstall", options, newDir) 0204 log(1, "Configured Virtual Directory: %s" % (params.Name,)) 0205 return newDir 0206 0207 def CreateISAPIFilter(filterParams, options): 0208 server = FindWebServer(options, filterParams.Server) 0209 _CallHook(filterParams, "PreInstall", options) 0210 try: 0211 filters = GetObject(server+"/Filters") 0212 except pythoncom.com_error, (hr, msg, exc, arg): 0213 # Brand new sites don't have the '/Filters' collection - create it. 0214 # Any errors other than 'not found' we shouldn't ignore. 0215 if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32 or \ 0216 winerror.HRESULT_CODE(hr) != winerror.ERROR_PATH_NOT_FOUND: 0217 raise 0218 server_ob = GetObject(server) 0219 filters = server_ob.Create(_IIS_FILTERS, "Filters") 0220 filters.FilterLoadOrder = "" 0221 filters.SetInfo() 0222 try: 0223 newFilter = filters.Create(_IIS_FILTER, filterParams.Name) 0224 log(2, "Created new ISAPI filter...") 0225 except pythoncom.com_error, (hr, msg, exc, narg): 0226 if exc is None or exc[-1]!=-2147024713: 0227 raise 0228 log(2, "Updating existing filter '%s'..." % (filterParams.Name,)) 0229 newFilter = GetObject(server+"/Filters/"+filterParams.Name) 0230 assert os.path.isfile(filterParams.Path) 0231 newFilter.FilterPath = filterParams.Path 0232 newFilter.FilterDescription = filterParams.Description 0233 newFilter.SetInfo() 0234 load_order = [b.strip() for b in filters.FilterLoadOrder.split(",")] 0235 if filterParams.Name not in load_order: 0236 load_order.append(filterParams.Name) 0237 filters.FilterLoadOrder = ",".join(load_order) 0238 filters.SetInfo() 0239 _CallHook(filterParams, "PostInstall", options, newFilter) 0240 log (1, "Configured Filter: %s" % (filterParams.Name,)) 0241 return newFilter 0242 0243 def DeleteISAPIFilter(filterParams, options): 0244 _CallHook(filterParams, "PreRemove", options) 0245 server = FindWebServer(options, filterParams.Server) 0246 filters = GetObject(server+"/Filters") 0247 try: 0248 filters.Delete(_IIS_FILTER, filterParams.Name) 0249 log(2, "Deleted ISAPI filter '%s'" % (filterParams.Name,)) 0250 except pythoncom.com_error, details: 0251 rc = _GetWin32ErrorCode(details) 0252 if rc != winerror.ERROR_PATH_NOT_FOUND: 0253 raise 0254 log(2, "ISAPI filter '%s' did not exist." % (filterParams.Name,)) 0255 if filterParams.Path: 0256 load_order = [b.strip() for b in filters.FilterLoadOrder.split(",")] 0257 if filterParams.Path in load_order: 0258 load_order.remove(filterParams.Path) 0259 filters.FilterLoadOrder = ",".join(load_order) 0260 filters.SetInfo() 0261 _CallHook(filterParams, "PostRemove", options) 0262 log (1, "Deleted Filter: %s" % (filterParams.Name,)) 0263 0264 def CheckLoaderModule(dll_name): 0265 suffix = "" 0266 if is_debug_build: suffix = "_d" 0267 template = os.path.join(this_dir, 0268 "PyISAPI_loader" + suffix + ".dll") 0269 if not os.path.isfile(template): 0270 raise ConfigurationError, \ 0271 "Template loader '%s' does not exist" % (template,) 0272 # We can't do a simple "is newer" check, as the DLL is specific to the 0273 # Python version. So we check the date-time and size are identical, 0274 # and skip the copy in that case. 0275 src_stat = os.stat(template) 0276 try: 0277 dest_stat = os.stat(dll_name) 0278 except os.error: 0279 same = 0 0280 else: 0281 same = src_stat[stat.ST_SIZE]==dest_stat[stat.ST_SIZE] and \ 0282 src_stat[stat.ST_MTIME]==dest_stat[stat.ST_MTIME] 0283 if not same: 0284 log(2, "Updating %s->%s" % (template, dll_name)) 0285 shutil.copyfile(template, dll_name) 0286 shutil.copystat(template, dll_name) 0287 else: 0288 log(2, "%s is up to date." % (dll_name,)) 0289 0290 def _CallHook(ob, hook_name, options, *extra_args): 0291 func = getattr(ob, hook_name, None) 0292 if func is not None: 0293 args = (ob,options) + extra_args 0294 func(*args) 0295 0296 def Install(params, options): 0297 _CallHook(params, "PreInstall", options) 0298 for vd in params.VirtualDirs: 0299 CreateDirectory(vd, options) 0300 0301 for filter_def in params.Filters: 0302 CreateISAPIFilter(filter_def, options) 0303 _CallHook(params, "PostInstall", options) 0304 0305 def Uninstall(params, options): 0306 _CallHook(params, "PreRemove", options) 0307 for vd in params.VirtualDirs: 0308 _CallHook(vd, "PreRemove", options) 0309 try: 0310 directory = GetObject(FindPath(options, vd.Server, vd.Name)) 0311 directory.AppUnload() 0312 parent = GetObject(directory.Parent) 0313 parent.Delete(directory.Class, directory.Name) 0314 except pythoncom.com_error, details: 0315 rc = _GetWin32ErrorCode(details) 0316 if rc != winerror.ERROR_PATH_NOT_FOUND: 0317 raise 0318 _CallHook(vd, "PostRemove", options) 0319 log (1, "Deleted Virtual Directory: %s" % (vd.Name,)) 0320 0321 for filter_def in params.Filters: 0322 DeleteISAPIFilter(filter_def, options) 0323 _CallHook(params, "PostRemove", options) 0324 0325 # Patch up any missing module names in the params, replacing them with 0326 # the DLL name that hosts this extension/filter. 0327 def _PatchParamsModule(params, dll_name, file_must_exist = True): 0328 if file_must_exist: 0329 if not os.path.isfile(dll_name): 0330 raise ConfigurationError, "%s does not exist" % (dll_name,) 0331 0332 # Patch up all references to the DLL. 0333 for f in params.Filters: 0334 if f.Path is None: f.Path = dll_name 0335 for d in params.VirtualDirs: 0336 for sm in d.ScriptMaps: 0337 if sm.Module is None: sm.Module = dll_name 0338 0339 def GetLoaderModuleName(mod_name): 0340 # find the name of the DLL hosting us. 0341 # By default, this is "_{module_base_name}.dll" 0342 if hasattr(sys, "frozen"): 0343 # What to do? The .dll knows its name, but this is likely to be 0344 # executed via a .exe, which does not know. 0345 base, ext = os.path.splitext(mod_name) 0346 path, base = os.path.split(base) 0347 # handle the common case of 'foo.exe'/'foow.exe' 0348 if base.endswith('w'): 0349 base = base[:-1] 0350 # For py2exe, we have '_foo.dll' as the standard pyisapi loader - but 0351 # 'foo.dll' is what we use (it just delegates). 0352 # So no leading '_' on the installed name. 0353 dll_name = os.path.abspath(os.path.join(path, base + ".dll")) 0354 else: 0355 base, ext = os.path.splitext(mod_name) 0356 path, base = os.path.split(base) 0357 dll_name = os.path.abspath(os.path.join(path, "_" + base + ".dll")) 0358 # Check we actually have it. 0359 if not hasattr(sys, "frozen"): 0360 CheckLoaderModule(dll_name) 0361 return dll_name 0362 0363 def InstallModule(conf_module_name, params, options): 0364 if not hasattr(sys, "frozen"): 0365 conf_module_name = os.path.abspath(conf_module_name) 0366 if not os.path.isfile(conf_module_name): 0367 raise ConfigurationError, "%s does not exist" % (conf_module_name,) 0368 0369 loader_dll = GetLoaderModuleName(conf_module_name) 0370 _PatchParamsModule(params, loader_dll) 0371 Install(params, options) 0372 0373 def UninstallModule(conf_module_name, params, options): 0374 loader_dll = GetLoaderModuleName(conf_module_name) 0375 _PatchParamsModule(params, loader_dll, False) 0376 Uninstall(params, options) 0377 0378 standard_arguments = { 0379 "install" : "Install the extension", 0380 "remove" : "Remove the extension" 0381 } 0382 0383 # We support 2 ways of extending our command-line/install support. 0384 # * Many of the installation items allow you to specify "PreInstall", 0385 # "PostInstall", "PreRemove" and "PostRemove" hooks 0386 # All hooks are called with the 'params' object being operated on, and 0387 # the 'optparser' options for this session (ie, the command-line options) 0388 # PostInstall for VirtualDirectories and Filters both have an additional 0389 # param - the ADSI object just created. 0390 # * You can pass your own option parser for us to use, and/or define a map 0391 # with your own custom arg handlers. It is a map of 'arg'->function. 0392 # The function is called with (options, log_fn, arg). The function's 0393 # docstring is used in the usage output. 0394 def HandleCommandLine(params, argv=None, conf_module_name = None, 0395 default_arg = "install", 0396 opt_parser = None, custom_arg_handlers = {}): 0397 """Perform installation or removal of an ISAPI filter or extension. 0398 0399 This module handles standard command-line options and configuration 0400 information, and installs, removes or updates the configuration of an 0401 ISAPI filter or extension. 0402 0403 You must pass your configuration information in params - all other 0404 arguments are optional, and allow you to configure the installation 0405 process. 0406 """ 0407 global verbose 0408 from optparse import OptionParser 0409 0410 argv = argv or sys.argv 0411 conf_module_name = conf_module_name or sys.argv[0] 0412 if opt_parser is None: 0413 # Build our own parser. 0414 parser = OptionParser(usage='') 0415 else: 0416 # The caller is providing their own filter, presumably with their 0417 # own options all setup. 0418 parser = opt_parser 0419 0420 # build a usage string if we don't have one. 0421 if not parser.get_usage(): 0422 all_args = standard_arguments.copy() 0423 for arg, handler in custom_arg_handlers.items(): 0424 all_args[arg] = handler.__doc__ 0425 arg_names = "|".join(all_args.keys()) 0426 usage_string = "%prog [options] [" + arg_names + "]\n" 0427 usage_string += "commands:\n" 0428 for arg, desc in all_args.items(): 0429 usage_string += " %-10s: %s" % (arg, desc) + "\n" 0430 parser.set_usage(usage_string[:-1]) 0431 0432 parser.add_option("-q", "--quiet", 0433 action="store_false", dest="verbose", default=True, 0434 help="don't print status messages to stdout") 0435 parser.add_option("-v", "--verbosity", action="count", 0436 dest="verbose", default=1, 0437 help="increase the verbosity of status messages") 0438 parser.add_option("", "--server", action="store", 0439 help="Specifies the IIS server to install/uninstall on." \ 0440 " Default is '%s/1'" % (_IIS_OBJECT,)) 0441 0442 (options, args) = parser.parse_args(argv[1:]) 0443 verbose = options.verbose 0444 if not args: 0445 args = [default_arg] 0446 try: 0447 for arg in args: 0448 if arg == "install": 0449 InstallModule(conf_module_name, params, options) 0450 log(1, "Installation complete.") 0451 elif arg in ["remove", "uninstall"]: 0452 UninstallModule(conf_module_name, params, options) 0453 log(1, "Uninstallation complete.") 0454 else: 0455 handler = custom_arg_handlers.get(arg, None) 0456 if handler is None: 0457 parser.error("Invalid arg '%s'" % (arg,)) 0458 handler(options, log, arg) 0459 except (ItemNotFound, InstallationError), details: 0460 if options.verbose > 1: 0461 traceback.print_exc() 0462 print "%s: %s" % (details.__class__.__name__, details) 0463
Generated by PyXR 0.9.4