Update of /cvsroot/pywin32/pywin32/isapi
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv17001/isapi
Added Files:
README.txt __init__.py install.py isapicon.py simple.py
threaded_extension.py
Log Message:
Rename pyisapi directory to isapi, so it matches what the user sees, as
part of the process of integrating it with the rest of the build.
--- NEW FILE: isapicon.py ---
"""Constants needed by ISAPI filters and extensions."""
# ======================================================================
# Copyright 2002-2003 by Blackdog Software Pty Ltd.
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Blackdog Software not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# BLACKDOG SOFTWARE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL BLACKDOG SOFTWARE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ======================================================================
# HTTP reply codes
HTTP_CONTINUE = 100
HTTP_SWITCHING_PROTOCOLS = 101
HTTP_PROCESSING = 102
HTTP_OK = 200
HTTP_CREATED = 201
HTTP_ACCEPTED = 202
HTTP_NON_AUTHORITATIVE = 203
HTTP_NO_CONTENT = 204
HTTP_RESET_CONTENT = 205
HTTP_PARTIAL_CONTENT = 206
HTTP_MULTI_STATUS = 207
HTTP_MULTIPLE_CHOICES = 300
HTTP_MOVED_PERMANENTLY = 301
HTTP_MOVED_TEMPORARILY = 302
HTTP_SEE_OTHER = 303
HTTP_NOT_MODIFIED = 304
HTTP_USE_PROXY = 305
HTTP_TEMPORARY_REDIRECT = 307
HTTP_BAD_REQUEST = 400
HTTP_UNAUTHORIZED = 401
HTTP_PAYMENT_REQUIRED = 402
HTTP_FORBIDDEN = 403
HTTP_NOT_FOUND = 404
HTTP_METHOD_NOT_ALLOWED = 405
HTTP_NOT_ACCEPTABLE = 406
HTTP_PROXY_AUTHENTICATION_REQUIRED= 407
HTTP_REQUEST_TIME_OUT = 408
HTTP_CONFLICT = 409
HTTP_GONE = 410
HTTP_LENGTH_REQUIRED = 411
HTTP_PRECONDITION_FAILED = 412
HTTP_REQUEST_ENTITY_TOO_LARGE = 413
HTTP_REQUEST_URI_TOO_LARGE = 414
HTTP_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_RANGE_NOT_SATISFIABLE = 416
HTTP_EXPECTATION_FAILED = 417
HTTP_UNPROCESSABLE_ENTITY = 422
HTTP_INTERNAL_SERVER_ERROR = 500
HTTP_NOT_IMPLEMENTED = 501
HTTP_BAD_GATEWAY = 502
HTTP_SERVICE_UNAVAILABLE = 503
HTTP_GATEWAY_TIME_OUT = 504
HTTP_VERSION_NOT_SUPPORTED = 505
HTTP_VARIANT_ALSO_VARIES = 506
HSE_STATUS_SUCCESS = 1
HSE_STATUS_SUCCESS_AND_KEEP_CONN = 2
HSE_STATUS_PENDING = 3
HSE_STATUS_ERROR = 4
SF_NOTIFY_SECURE_PORT = 0x00000001
SF_NOTIFY_NONSECURE_PORT = 0x00000002
SF_NOTIFY_READ_RAW_DATA = 0x00008000
SF_NOTIFY_PREPROC_HEADERS = 0x00004000
SF_NOTIFY_AUTHENTICATION = 0x00002000
SF_NOTIFY_URL_MAP = 0x00001000
SF_NOTIFY_ACCESS_DENIED = 0x00000800
SF_NOTIFY_SEND_RESPONSE = 0x00000040
SF_NOTIFY_SEND_RAW_DATA = 0x00000400
SF_NOTIFY_LOG = 0x00000200
SF_NOTIFY_END_OF_REQUEST = 0x00000080
SF_NOTIFY_END_OF_NET_SESSION = 0x00000100
SF_NOTIFY_ORDER_HIGH = 0x00080000
SF_NOTIFY_ORDER_MEDIUM = 0x00040000
SF_NOTIFY_ORDER_LOW = 0x00020000
SF_NOTIFY_ORDER_DEFAULT = SF_NOTIFY_ORDER_LOW
SF_NOTIFY_ORDER_MASK = (SF_NOTIFY_ORDER_HIGH | \
SF_NOTIFY_ORDER_MEDIUM | \
SF_NOTIFY_ORDER_LOW)
SF_STATUS_REQ_FINISHED = 134217728 # 0x8000000
SF_STATUS_REQ_FINISHED_KEEP_CONN = 134217728 + 1
SF_STATUS_REQ_NEXT_NOTIFICATION = 134217728 + 2
SF_STATUS_REQ_HANDLED_NOTIFICATION = 134217728 + 3
SF_STATUS_REQ_ERROR = 134217728 + 4
SF_STATUS_REQ_READ_NEXT = 134217728 + 5
--- NEW FILE: threaded_extension.py ---
"""An ISAPI extension base class implemented using a thread-pool."""
# $Id: threaded_extension.py,v 1.1 2004/10/06 05:11:52 mhammond Exp $
import sys
from isapi import isapicon
import isapi.simple
from win32file import GetQueuedCompletionStatus, CreateIoCompletionPort, \
PostQueuedCompletionStatus, CloseHandle
from win32security import SetThreadToken
from win32event import INFINITE
from pywintypes import OVERLAPPED
# Python 2.3 and earlier insists on "C" locale - if it isn't, subtle things
# break, such as floating point constants loaded from .pyc files.
# The threading module uses such floating-points as an argument to sleep(),
# resulting in extremely long sleeps when tiny intervals are specified.
# We can work around this by resetting the C locale before the import.
if sys.hexversion < 0x02040000:
import locale
locale.setlocale(locale.LC_NUMERIC, "C")
import threading
import traceback
ISAPI_REQUEST = 1
ISAPI_SHUTDOWN = 2
class WorkerThread(threading.Thread):
def __init__(self, extension, io_req_port):
self.running = False
self.io_req_port = io_req_port
self.extension = extension
threading.Thread.__init__(self)
def run(self):
self.running = True
while self.running:
errCode, bytes, key, overlapped = \
GetQueuedCompletionStatus(self.io_req_port, INFINITE)
if key == ISAPI_SHUTDOWN and overlapped is None:
break
# Let the parent extension handle the command.
dispatcher = self.extension.dispatch_map.get(key)
if dispatcher is None:
raise RuntimeError, "Bad request '%s'" % (key,)
dispatcher(errCode, bytes, key, overlapped)
def call_handler(self, cblock):
self.extension.Dispatch(cblock)
# A generic thread-pool based extension, using IO Completion Ports.
# Sub-classes can override one method to implement a simple extension, or
# may leverage the CompletionPort to queue their own requests, and implement a
# fully asynch extension.
class ThreadPoolExtension(isapi.simple.SimpleExtension):
"Base class for an ISAPI extension based around a thread-pool"
max_workers = 20
worker_shutdown_wait = 15000 # 15 seconds for workers to quit. XXX - per thread!!! Fix me!
def __init__(self):
self.workers = []
# extensible dispatch map, for sub-classes that need to post their
# own requests to the completion port.
# Each of these functions is called with the result of
# GetQueuedCompletionStatus for our port.
self.dispatch_map = {
ISAPI_REQUEST: self.DispatchConnection,
}
def GetExtensionVersion(self, vi):
isapi.simple.SimpleExtension.GetExtensionVersion(self, vi)
# As per Q192800, the CompletionPort should be created with the number
# of processors, even if the number of worker threads is much larger.
# Passing 0 means the system picks the number.
self.io_req_port = CreateIoCompletionPort(-1, None, 0, 0)
# start up the workers
self.workers = []
for i in range(self.max_workers):
worker = WorkerThread(self, self.io_req_port)
worker.start()
self.workers.append(worker)
def HttpExtensionProc(self, control_block):
overlapped = OVERLAPPED()
overlapped.object = control_block
PostQueuedCompletionStatus(self.io_req_port, 0, ISAPI_REQUEST, overlapped)
return isapicon.HSE_STATUS_PENDING
def TerminateExtension(self, status):
for worker in self.workers:
worker.running = False
for worker in self.workers:
PostQueuedCompletionStatus(self.io_req_port, 0, ISAPI_SHUTDOWN, None)
for worker in self.workers:
worker.join(self.worker_shutdown_wait)
self.dispatch_map = {} # break circles
CloseHandle(self.io_req_port)
# This is the one operation the base class supports - a simple
# Connection request. We setup the thread-token, and dispatch to the
# sub-class's 'Dispatch' method.
def DispatchConnection(self, errCode, bytes, key, overlapped):
control_block = overlapped.object
# setup the correct user for this request
hRequestToken = control_block.GetImpersonationToken()
SetThreadToken(None, hRequestToken)
try:
try:
self.Dispatch(control_block)
except:
self.HandleDispatchError(control_block)
finally:
# reset the security context
SetThreadToken(None, None)
def Dispatch(self, ecb):
"""Overridden by the sub-class to handle connection requests.
This class creates a thread-pool using a Windows completion port,
and dispatches requests via this port. Sub-classes can generally
implementeach connection request using blocking reads and writes, and
the thread-pool will still provide decent response to the end user.
The sub-class can set a max_workers attribute (default is 20). Note
that this generally does *not* mean 20 threads will all be concurrently
running, via the magic of Windows completion ports.
There is no default implementation - sub-classes must implement this.
"""
raise NotImplementedError, "sub-classes should override Dispatch"
def HandleDispatchError(self, ecb):
"""Handles errors in the Dispatch method.
When a Dispatch method call fails, this method is called to handle
the exception. The default implementation formats the traceback
in the browser.
"""
ecb.HttpStatusCode = isapicon.HSE_STATUS_ERROR
#control_block.LogData = "we failed!"
ecb.SendResponseHeaders("200 OK", "Content-type: text/html\r\n\r\n",
False)
exc_typ, exc_val, exc_tb = sys.exc_info()
limit = None
try:
try:
import cgi
print >> ecb
print >> ecb, "<H3>Traceback (most recent call last):</H3>"
list = traceback.format_tb(exc_tb, limit) + \
traceback.format_exception_only(exc_typ, exc_val)
print >> ecb, "<PRE>%s<B>%s</B></PRE>" % (
cgi.escape("".join(list[:-1])), cgi.escape(list[-1]),)
except:
print "FAILED to render the error message!"
traceback.print_exc()
print "ORIGINAL extension error:"
traceback.print_exception(exc_typ, exc_val, exc_tb)
finally:
# holding tracebacks in a local of a frame that may itself be
# part of a traceback used to be evil and cause leaks!
exc_tb = None
ecb.DoneWithSession()
--- NEW FILE: simple.py ---
"""Simple base-classes for extensions and filters.
None of the filter and extension functions are considered 'optional' by the
framework. These base-classes provide simple implementations for the
Initialize and Terminate functions, allowing you to omit them,
It is not necessary to use these base-classes - but if you don't, you
must ensure each of the required methods are implemented.
"""
class SimpleExtension:
"Base class for a a simple ISAPI extension"
def __init__(self):
pass
def GetExtensionVersion(self, vi):
"""Called by the ISAPI framework to get the extension version
The default implementation uses the classes docstring to
set the extension description."""
# nod to our reload capability - vi is None when we are reloaded.
if vi is not None:
vi.ExtensionDesc = self.__doc__
def HttpExtensionProc(self, control_block):
"""Called by the ISAPI framework for each extension request.
sub-classes must provide an implementation for this method.
"""
raise NotImplementedError, "sub-classes should override HttpExtensionProc"
def TerminateExtension(self, status):
"""Called by the ISAPI framework as the extension terminates.
"""
pass
class SimpleFilter:
"Base class for a a simple ISAPI filter"
filter_flags = None
def __init__(self):
pass
def GetFilterVersion(self, fv):
"""Called by the ISAPI framework to get the extension version
The default implementation uses the classes docstring to
set the extension description, and uses the classes
filter_flags attribute to set the ISAPI filter flags - you
must specify filter_flags in your class.
"""
if self.filter_flags is None:
raise RuntimeError, "You must specify the filter flags"
# nod to our reload capability - fv is None when we are reloaded.
if fv is not None:
fv.Flags = self.filter_flags
fv.FilterDesc = self.__doc__
def HttpFilterProc(self, fc):
"""Called by the ISAPI framework for each filter request.
sub-classes must provide an implementation for this method.
"""
raise NotImplementedError, "sub-classes should override HttpExtensionProc"
def TerminateFilter(self, status):
"""Called by the ISAPI framework as the filter terminates.
"""
pass
--- NEW FILE: __init__.py ---
# The Python ISAPI package.
# Exceptions thrown by the DLL framework.
class ISAPIError(Exception):
def __init__(self, errno, strerror = None, funcname = None):
# named attributes match IOError etc.
self.errno = errno
self.strerror = strerror
self.funcname = funcname
Exception.__init__(self, errno, strerror, funcname)
def __str__(self):
if self.strerror is None:
try:
import win32api
self.strerror = win32api.FormatMessage(self.errno).strip()
except:
self.strerror = "no error message is available"
# str() looks like a win32api error.
return str( (self.errno, self.strerror, self.funcname) )
class FilterError(ISAPIError):
pass
class ExtensionError(ISAPIError):
pass
# A little development aid - a filter or extension callback function can
# raise one of these exceptions, and the handler module will be reloaded.
# This means you can change your code without restarting IIS.
# After a reload, your filter/extension will have the GetFilterVersion/
# GetExtensionVersion function called, but with None as the first arg.
class InternalReloadException(Exception):
pass
--- NEW FILE: install.py ---
"""Installation utilities for Python ISAPI filters and extensions."""
# this code adapted from "Tomcat JK2 ISAPI redirector", part of Apache
# Created July 2004, Mark Hammond.
import sys, os, imp, shutil, stat
from win32com.client import GetObject, Dispatch
from win32com.client.gencache import EnsureModule, EnsureDispatch
import pythoncom
import winerror
import traceback
_APP_INPROC = 0
_APP_OUTPROC = 1
_APP_POOLED = 2
_IIS_OBJECT = "IIS://LocalHost/W3SVC"
_IIS_SERVER = "IIsWebServer"
_IIS_WEBDIR = "IIsWebDirectory"
_IIS_WEBVIRTUALDIR = "IIsWebVirtualDir"
_IIS_FILTERS = "IIsFilters"
_IIS_FILTER = "IIsFilter"
_DEFAULT_SERVER_NAME = "Default Web Site"
_DEFAULT_HEADERS = "X-Powered-By: Python"
_DEFAULT_PROTECTION = _APP_POOLED
# Default is for 'execute' only access - ie, only the extension
# can be used. This can be overridden via your install script.
_DEFAULT_ACCESS_EXECUTE = True
_DEFAULT_ACCESS_READ = False
_DEFAULT_ACCESS_WRITE = False
_DEFAULT_ACCESS_SCRIPT = False
_DEFAULT_CONTENT_INDEXED = False
_DEFAULT_ENABLE_DIR_BROWSING = False
_DEFAULT_ENABLE_DEFAULT_DOC = False
is_debug_build = False
for imp_ext, _, _ in imp.get_suffixes():
if imp_ext == "_d.pyd":
is_debug_build = True
break
this_dir = os.path.abspath(os.path.dirname(__file__))
class FilterParameters:
Name = None
Description = None
Path = None
Server = None
def __init__(self, **kw):
self.__dict__.update(kw)
class VirtualDirParameters:
Name = None # Must be provided.
Description = None # defaults to Name
AppProtection = _DEFAULT_PROTECTION
Headers = _DEFAULT_HEADERS
Path = None # defaults to WWW root.
AccessExecute = _DEFAULT_ACCESS_EXECUTE
AccessRead = _DEFAULT_ACCESS_READ
AccessWrite = _DEFAULT_ACCESS_WRITE
AccessScript = _DEFAULT_ACCESS_SCRIPT
ContentIndexed = _DEFAULT_CONTENT_INDEXED
EnableDirBrowsing = _DEFAULT_ENABLE_DIR_BROWSING
EnableDefaultDoc = _DEFAULT_ENABLE_DEFAULT_DOC
ScriptMaps = []
ScriptMapUpdate = "end" # can be 'start', 'end', 'replace'
Server = None
def __init__(self, **kw):
self.__dict__.update(kw)
class ScriptMapParams:
Extension = None
Module = None
Flags = 5
Verbs = ""
def __init__(self, **kw):
self.__dict__.update(kw)
class ISAPIParameters:
ServerName = _DEFAULT_SERVER_NAME
# Description = None
Filters = []
VirtualDirs = []
def __init__(self, **kw):
self.__dict__.update(kw)
verbose = 1 # The level - 0 is quiet.
def log(level, what):
if verbose >= level:
print what
# Convert an ADSI COM exception to the Win32 error code embedded in it.
def _GetWin32ErrorCode(com_exc):
hr, msg, exc, narg = com_exc
# If we have more details in the 'exc' struct, use it.
if exc:
hr = exc[-1]
if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32:
raise
return winerror.SCODE_CODE(hr)
class InstallationError(Exception): pass
class ItemNotFound(InstallationError): pass
class ConfigurationError(InstallationError): pass
def FindPath(options, server, name):
if name.lower().startswith("iis://"):
return name
else:
if name and name[0] != "/":
name = "/"+name
return FindWebServer(options, server)+"/ROOT"+name
def FindWebServer(options, server_desc):
# command-line options get first go.
if options.server:
server = options.server
# If the config passed by the caller doesn't specify one, use the default
elif server_desc is None:
server = _IIS_OBJECT+"/1"
else:
server = server_desc
# Check it is good.
try:
GetObject(server)
except pythoncom.com_error, details:
hr, msg, exc, arg_err = details
if exc and exc[2]:
msg = exc[2]
raise ItemNotFound, \
"WebServer %s: %s" % (server, msg)
return server
def CreateDirectory(params, options):
if not params.Name:
raise ConfigurationError, "No Name param"
slash = params.Name.rfind("/")
if slash >= 0:
parent = params.Name[:slash]
name = params.Name[slash+1:]
else:
parent = ""
name = params.Name
webDir = GetObject(FindPath(options, params.Server, parent))
if parent:
# Note that the directory won't be visible in the IIS UI
# unless the directory exists on the filesystem.
keyType = _IIS_WEBDIR
else:
keyType = _IIS_WEBVIRTUALDIR
_CallHook(params, "PreInstall", options)
try:
newDir = webDir.Create(keyType, name)
except pythoncom.com_error, details:
rc = _GetWin32ErrorCode(details)
if rc != winerror.ERROR_ALREADY_EXISTS:
raise
newDir = GetObject(FindPath(options, params.Server, params.Name))
log(2, "Updating existing directory '%s'..." % (params.Name,))
else:
log(2, "Creating new directory '%s'..." % (params.Name,))
friendly = params.Description or params.Name
newDir.AppFriendlyName = friendly
try:
path = params.Path or webDir.Path
newDir.Path = path
except AttributeError:
pass
newDir.AppCreate2(params.AppProtection)
newDir.HttpCustomHeaders = params.Headers
log(2, "Setting directory options...")
newDir.AccessExecute = params.AccessExecute
newDir.AccessRead = params.AccessRead
newDir.AccessWrite = params.AccessWrite
newDir.AccessScript = params.AccessScript
newDir.ContentIndexed = params.ContentIndexed
newDir.EnableDirBrowsing = params.EnableDirBrowsing
newDir.EnableDefaultDoc = params.EnableDefaultDoc
newDir.SetInfo()
smp_items = []
for smp in params.ScriptMaps:
item = "%s,%s,%s" % (smp.Extension, smp.Module, smp.Flags)
# IIS gets upset if there is a trailing verb comma, but no verbs
if smp.Verbs:
item += "," + smp.Verbs
smp_items.append(item)
if params.ScriptMapUpdate == "replace":
newDir.ScriptMaps = smp_items
elif params.ScriptMapUpdate == "end":
for item in smp_items:
if item not in newDir.ScriptMaps:
newDir.ScriptMaps = newDir.ScriptMaps + (item,)
elif params.ScriptMapUpdate == "start":
for item in smp_items:
if item not in newDir.ScriptMaps:
newDir.ScriptMaps = (item,) + newDir.ScriptMaps
else:
raise ConfigurationError, \
"Unknown ScriptMapUpdate option '%s'" % (params.ScriptMapUpdate,)
newDir.SetInfo()
_CallHook(params, "PostInstall", options, newDir)
log(1, "Configured Virtual Directory: %s" % (params.Name,))
return newDir
def CreateISAPIFilter(filterParams, options):
server = FindWebServer(options, filterParams.Server)
_CallHook(filterParams, "PreInstall", options)
try:
filters = GetObject(server+"/Filters")
except pythoncom.com_error, (hr, msg, exc, arg):
# Brand new sites don't have the '/Filters' collection - create it.
# Any errors other than 'not found' we shouldn't ignore.
if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32 or \
winerror.HRESULT_CODE(hr) != winerror.ERROR_PATH_NOT_FOUND:
raise
server_ob = GetObject(server)
filters = server_ob.Create(_IIS_FILTERS, "Filters")
filters.FilterLoadOrder = ""
filters.SetInfo()
try:
newFilter = filters.Create(_IIS_FILTER, filterParams.Name)
log(2, "Created new ISAPI filter...")
except pythoncom.com_error, (hr, msg, exc, narg):
if exc is None or exc[-1]!=-2147024713:
raise
log(2, "Updating existing filter '%s'..." % (filterParams.Name,))
newFilter = GetObject(server+"/Filters/"+filterParams.Name)
assert os.path.isfile(filterParams.Path)
newFilter.FilterPath = filterParams.Path
newFilter.FilterDescription = filterParams.Description
newFilter.SetInfo()
load_order = [b.strip() for b in filters.FilterLoadOrder.split(",")]
if filterParams.Name not in load_order:
load_order.append(filterParams.Name)
filters.FilterLoadOrder = ",".join(load_order)
filters.SetInfo()
_CallHook(filterParams, "PostInstall", options, newFilter)
log (1, "Configured Filter: %s" % (filterParams.Name,))
return newFilter
def DeleteISAPIFilter(filterParams, options):
_CallHook(filterParams, "PreRemove", options)
server = FindWebServer(options, filterParams.Server)
filters = GetObject(server+"/Filters")
try:
filters.Delete(_IIS_FILTER, filterParams.Name)
log(2, "Deleted ISAPI filter '%s'" % (filterParams.Name,))
except pythoncom.com_error, details:
rc = _GetWin32ErrorCode(details)
if rc != winerror.ERROR_PATH_NOT_FOUND:
raise
log(2, "ISAPI filter '%s' did not exist." % (filterParams.Name,))
if filterParams.Path:
load_order = [b.strip() for b in filters.FilterLoadOrder.split(",")]
if filterParams.Path in load_order:
load_order.remove(filterParams.Path)
filters.FilterLoadOrder = ",".join(load_order)
filters.SetInfo()
_CallHook(filterParams, "PostRemove", options)
log (1, "Deleted Filter: %s" % (filterParams.Name,))
def CheckLoaderModule(dll_name):
suffix = ""
if is_debug_build: suffix = "_d"
template = os.path.join(this_dir,
"PyISAPI_loader" + suffix + ".dll")
if not os.path.isfile(template):
raise ConfigurationError, \
"Template loader '%s' does not exist" % (template,)
# We can't do a simple "is newer" check, as the DLL is specific to the
# Python version. So we check the date-time and size are identical,
# and skip the copy in that case.
src_stat = os.stat(template)
try:
dest_stat = os.stat(dll_name)
except os.error:
same = 0
else:
same = src_stat[stat.ST_SIZE]==dest_stat[stat.ST_SIZE] and \
src_stat[stat.ST_MTIME]==dest_stat[stat.ST_MTIME]
if not same:
log(2, "Updating %s->%s" % (template, dll_name))
shutil.copyfile(template, dll_name)
shutil.copystat(template, dll_name)
else:
log(2, "%s is up to date." % (dll_name,))
def _CallHook(ob, hook_name, options, *extra_args):
func = getattr(ob, hook_name, None)
if func is not None:
args = (ob,options) + extra_args
func(*args)
def Install(params, options):
_CallHook(params, "PreInstall", options)
for vd in params.VirtualDirs:
CreateDirectory(vd, options)
for filter_def in params.Filters:
CreateISAPIFilter(filter_def, options)
_CallHook(params, "PostInstall", options)
def Uninstall(params, options):
_CallHook(params, "PreRemove", options)
for vd in params.VirtualDirs:
_CallHook(vd, "PreRemove", options)
try:
directory = GetObject(FindPath(options, vd.Server, vd.Name))
directory.AppUnload()
parent = GetObject(directory.Parent)
parent.Delete(directory.Class, directory.Name)
except pythoncom.com_error, details:
rc = _GetWin32ErrorCode(details)
if rc != winerror.ERROR_PATH_NOT_FOUND:
raise
_CallHook(vd, "PostRemove", options)
log (1, "Deleted Virtual Directory: %s" % (vd.Name,))
for filter_def in params.Filters:
DeleteISAPIFilter(filter_def, options)
_CallHook(params, "PostRemove", options)
# Patch up any missing module names in the params, replacing them with
# the DLL name that hosts this extension/filter.
def _PatchParamsModule(params, dll_name, file_must_exist = True):
if file_must_exist:
if not os.path.isfile(dll_name):
raise ConfigurationError, "%s does not exist" % (dll_name,)
# Patch up all references to the DLL.
for f in params.Filters:
if f.Path is None: f.Path = dll_name
for d in params.VirtualDirs:
for sm in d.ScriptMaps:
if sm.Module is None: sm.Module = dll_name
def GetLoaderModuleName(mod_name):
# find the name of the DLL hosting us.
# By default, this is "_{module_base_name}.dll"
if hasattr(sys, "frozen"):
# What to do? The .dll knows its name, but this is likely to be
# executed via a .exe, which does not know.
base, ext = os.path.splitext(mod_name)
path, base = os.path.split(base)
# handle the common case of 'foo.exe'/'foow.exe'
if base.endswith('w'):
base = base[:-1]
# For py2exe, we have '_foo.dll' as the standard pyisapi loader - but
# 'foo.dll' is what we use (it just delegates).
# So no leading '_' on the installed name.
dll_name = os.path.abspath(os.path.join(path, base + ".dll"))
else:
base, ext = os.path.splitext(mod_name)
path, base = os.path.split(base)
dll_name = os.path.abspath(os.path.join(path, "_" + base + ".dll"))
# Check we actually have it.
if not hasattr(sys, "frozen"):
CheckLoaderModule(dll_name)
return dll_name
def InstallModule(conf_module_name, params, options):
if not hasattr(sys, "frozen"):
conf_module_name = os.path.abspath(conf_module_name)
if not os.path.isfile(conf_module_name):
raise ConfigurationError, "%s does not exist" % (conf_module_name,)
loader_dll = GetLoaderModuleName(conf_module_name)
_PatchParamsModule(params, loader_dll)
Install(params, options)
def UninstallModule(conf_module_name, params, options):
loader_dll = GetLoaderModuleName(conf_module_name)
_PatchParamsModule(params, loader_dll, False)
Uninstall(params, options)
standard_arguments = {
"install" : "Install the extension",
"remove" : "Remove the extension"
}
# We support 2 ways of extending our command-line/install support.
# * Many of the installation items allow you to specify "PreInstall",
# "PostInstall", "PreRemove" and "PostRemove" hooks
# All hooks are called with the 'params' object being operated on, and
# the 'optparser' options for this session (ie, the command-line options)
# PostInstall for VirtualDirectories and Filters both have an additional
# param - the ADSI object just created.
# * You can pass your own option parser for us to use, and/or define a map
# with your own custom arg handlers. It is a map of 'arg'->function.
# The function is called with (options, log_fn, arg). The function's
# docstring is used in the usage output.
def HandleCommandLine(params, argv=None, conf_module_name = None,
default_arg = "install",
opt_parser = None, custom_arg_handlers = {}):
"""Perform installation or removal of an ISAPI filter or extension.
This module handles standard command-line options and configuration
information, and installs, removes or updates the configuration of an
ISAPI filter or extension.
You must pass your configuration information in params - all other
arguments are optional, and allow you to configure the installation
process.
"""
global verbose
from optparse import OptionParser
argv = argv or sys.argv
conf_module_name = conf_module_name or sys.argv[0]
if opt_parser is None:
# Build our own parser.
parser = OptionParser(usage='')
else:
# The caller is providing their own filter, presumably with their
# own options all setup.
parser = opt_parser
# build a usage string if we don't have one.
if not parser.get_usage():
all_args = standard_arguments.copy()
for arg, handler in custom_arg_handlers.items():
all_args[arg] = handler.__doc__
arg_names = "|".join(all_args.keys())
usage_string = "%prog [options] [" + arg_names + "]\n"
usage_string += "commands:\n"
for arg, desc in all_args.items():
usage_string += " %-10s: %s" % (arg, desc) + "\n"
parser.set_usage(usage_string[:-1])
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
parser.add_option("-v", "--verbosity", action="count",
dest="verbose", default=1,
help="increase the verbosity of status messages")
parser.add_option("", "--server", action="store",
help="Specifies the IIS server to install/uninstall on." \
" Default is '%s/1'" % (_IIS_OBJECT,))
(options, args) = parser.parse_args(argv[1:])
verbose = options.verbose
if not args:
args = [default_arg]
try:
for arg in args:
if arg == "install":
InstallModule(conf_module_name, params, options)
log(1, "Installation complete.")
elif arg in ["remove", "uninstall"]:
UninstallModule(conf_module_name, params, options)
log(1, "Uninstallation complete.")
else:
handler = custom_arg_handlers.get(arg, None)
if handler is None:
parser.error("Invalid arg '%s'" % (arg,))
handler(options, log, arg)
except (ItemNotFound, InstallationError), details:
if options.verbose > 1:
traceback.print_exc()
print "%s: %s" % (details.__class__.__name__, details)
--- NEW FILE: README.txt ---
A Python ISAPI extension. Contributed by Phillip Frantz, and is
Copyright 2002-2003 by Blackdog Software Pty Ltd.
See the 'samples' directory, and particularly samples\README.txt
You can find documentation in the PyWin32.chm file that comes with pywin32 -
you can open this from Pythonwin->Help, or from the start menu.
|