1"""URLParser 2 3URL parsing is done through objects which are subclasses of the `URLParser` 4class. `Application` delegates most of the URL parsing to these objects. 5 6Application has a single "root" URL parser, which is used to parse all URLs. 7This parser then can pass the request on to other parsers, usually taking off 8parts of the URL with each step. 9 10This root parser is generally `ContextParser`, which is instantiated and set 11up by `Application` (accessible through `Application.rootURLParser`). 12""" 13 14import os 15import re 16import sys 17 18from warnings import warn 19 20from HTTPExceptions import HTTPNotFound, HTTPMovedPermanently 21from MiscUtils.ParamFactory import ParamFactory 22from WebUtils.Funcs import urlDecode 23 24debug = False 25 26# Legal characters for use in a module name -- used when turning 27# an entire path into a module name. 28_moduleNameRE = re.compile('[^a-zA-Z_]') 29 30_globalApplication = None 31 32 33def application(): 34 """Returns the global Application.""" 35 return _globalApplication 36 37 38class URLParser(object): 39 """URLParser is the base class for all URL parsers. 40 41 Though its functionality is sparse, it may be expanded in the future. 42 Subclasses should implement a `parse` method, and may also want to 43 implement an `__init__` method with arguments that control how the 44 parser works (for instance, passing a starting path for the parser) 45 46 The `parse` method is where most of the work is done. It takes two 47 arguments -- the transaction and the portion of the URL that is still to 48 be parsed. The transaction may (and usually is) modified along the way. 49 The URL is passed through so that you can take pieces off the front, 50 and then pass the reduced URL to another parser. The method should return 51 a servlet (never None). 52 53 If you cannot find a servlet, or some other (somewhat) expected error 54 occurs, you should raise an exception. HTTPNotFound probably being the 55 most interesting. 56 """ 57 58 def __init__(self): 59 pass 60 61 def findServletForTransaction(self, trans): 62 """Returns a servlet for the transaction. 63 64 This is the top-level entry point, below it `parse` is used. 65 """ 66 return self.parse(trans, trans.request().urlPath()) 67 68 69class ContextParser(URLParser): 70 """Find the context of a request. 71 72 ContextParser uses the ``Application.config`` context settings to find 73 the context of the request. It then passes the request to a FileParser 74 rooted in the context path. 75 76 The context is the first element of the URL, or if no context matches 77 that then it is the ``default`` context (and the entire URL is passed 78 to the default context's FileParser). 79 80 There is generally only one ContextParser, which can be found as 81 ``application.rootURLParser()``. 82 """ 83 84 85 ## Init ## 86 87 def __init__(self, app): 88 """Create ContextParser. 89 90 ContextParser is usually created by Application, which 91 passes all requests to it. 92 93 In __init__ we take the ``Contexts`` setting from 94 Application.config and parse it slightly. 95 """ 96 URLParser.__init__(self) 97 # Need to set this here because we need for initialization, during 98 # which AppServer.globalAppServer.application() doesn't yet exist: 99 self._app = app 100 self._imp = app._imp 101 # self._context will be a dictionary of context names and context 102 # directories. It is set by `addContext`. 103 self._contexts = {} 104 # add all contexts except the default, which we save until the end 105 contexts = app.setting('Contexts') 106 defaultContext = '' 107 for name, path in contexts.items(): 108 path = os.path.normpath(path) # for Windows 109 if name == 'default': 110 defaultContext = path 111 else: 112 name = '/'.join(filter(lambda x: x, name.split('/'))) 113 self.addContext(name, path) 114 if not defaultContext: 115 # If no default context has been specified, and there is a unique 116 # context not built into Webware, use it as the default context. 117 for name in contexts: 118 if name.endswith('/Docs') or name in ( 119 'Admin', 'Docs', 'Examples', 'MKBrowser', 'Testing'): 120 continue 121 if defaultContext: 122 defaultContext = None 123 break 124 else: 125 defaultContext = name 126 if not defaultContext: 127 # otherwise, try using the following contexts if available 128 for defaultContext in ('Default', 'Examples', 'Docs'): 129 if defaultContext in contexts: 130 break 131 else: # if not available, refuse the temptation to guess 132 raise KeyError("No default context has been specified.") 133 if defaultContext in self._contexts: 134 self._defaultContext = defaultContext 135 else: 136 for name, path in self._contexts.items(): 137 if defaultContext == path: 138 self._defaultContext = name 139 break 140 else: 141 self.addContext('default', defaultContext) 142 self._defaultContext = 'default' 143 144 145 ## Context handling ## 146 147 def resolveDefaultContext(self, dest): 148 """Find default context. 149 150 Figure out if the default context refers to an existing context, 151 the same directory as an existing context, or a unique directory. 152 153 Returns the name of the context that the default context refers to, 154 or 'default' if the default context is unique. 155 """ 156 contexts = self._contexts 157 if dest in contexts: 158 # The default context refers to another context, 159 # not a unique context. Return the name of that context. 160 return dest 161 destPath = self.absContextPath(dest) 162 for name, path in contexts.items(): 163 if name != 'default' and self.absContextPath(path) == destPath: 164 # The default context has the same directory 165 # as another context, so it's still not unique 166 return name 167 # The default context has no other name 168 return 'default' 169 170 def addContext(self, name, path): 171 """Add a context to the system. 172 173 The context will be imported as a package, going by `name`, 174 from the given directory path. The directory doesn't have to match 175 the context name. 176 """ 177 if name == 'default': 178 dest = self.resolveDefaultContext(path) 179 self._defaultContext = dest 180 if dest != 'default': 181 # in this case default refers to an existing context, so 182 # there's not much to do 183 print 'Default context aliases to: %s' % (dest,) 184 return 185 186 e = None 187 try: 188 importAsName = name 189 localDir, packageName = os.path.split(path) 190 if importAsName in sys.modules: 191 mod = sys.modules[importAsName] 192 else: 193 try: 194 res = self._imp.find_module(packageName, [localDir]) 195 if not res: 196 raise ImportError 197 except ImportError as e: 198 if not str(e): 199 e = 'Could not import package' 200 # Maybe this happened because it had been forgotten 201 # to add the __init__.py file. So we try to create one: 202 if os.path.exists(path): 203 f = os.path.join(path, '__init__.py') 204 if (not os.path.exists(f) 205 and not os.path.exists(f + 'c') 206 and not os.path.exists(f + 'o')): 207 print ("Creating __init__.py file" 208 " for context '%s'" % name) 209 try: 210 open(f, 'w').write( 211 '# Auto-generated by WebKit' + os.linesep) 212 except Exception: 213 print ("Error: __init__.py file" 214 " could not be created.") 215 else: 216 res = self._imp.find_module(packageName, 217 [localDir]) 218 if res: 219 e = None 220 if e: 221 raise 222 mod = self._imp.load_module(name, *res) 223 except (ImportError, TypeError) as e: 224 # TypeError can be raised by imp.load_module() 225 # when the context path does not exist 226 pass 227 if e: 228 print 'Error loading context: %s: %s: dir=%s' % (name, e, path) 229 return 230 231 if hasattr(mod, 'contextInitialize'): 232 # @@ gat 2003-07-23: switched back to old method 233 # of passing application as first parameter 234 # to contextInitialize for backward compatibility 235 result = mod.contextInitialize( 236 application(), 237 os.path.normpath(os.path.join(os.getcwd(), path))) 238 # @@: funny hack...? 239 if result is not None and 'ContentLocation' in result: 240 path = result['ContentLocation'] 241 242 print 'Loading context: %s at %s' % (name, path) 243 self._contexts[name] = path 244 245 def absContextPath(self, path): 246 """Get absolute context path. 247 248 Resolves relative paths, which are assumed to be relative to the 249 Application's serverSidePath (the working directory). 250 """ 251 if os.path.isabs(path): 252 return path 253 else: 254 return self._app.serverSidePath(path) 255 256 257 ## Parsing ## 258 259 def parse(self, trans, requestPath): 260 """Parse request. 261 262 Get the context name, and dispatch to a FileParser rooted 263 in the context's path. 264 265 The context name and file path are stored in the request (accessible 266 through `Request.serverSidePath` and `Request.contextName`). 267 """ 268 # This is a hack... should probably go in the Transaction class: 269 trans._fileParserInitSeen = {} 270 # If there is no path, redirect to the root path: 271 req = trans.request() 272 if not requestPath: 273 p = req.servletPath() + '/' 274 q = req.queryString() 275 if q: 276 p += "?" + q 277 raise HTTPMovedPermanently(location=p) 278 # Determine the context name: 279 if req._absolutepath: 280 contextName = self._defaultContext 281 else: 282 context = filter(None, requestPath.split('/')) 283 if requestPath.endswith('/'): 284 context.append('') 285 parts = [] 286 while context: 287 contextName = '/'.join(context) 288 if contextName in self._contexts: 289 break 290 parts.insert(0, context.pop()) 291 if context: 292 if parts: 293 parts.insert(0, '') 294 requestPath = '/'.join(parts) 295 else: 296 requestPath = '' 297 else: 298 contextName = self._defaultContext 299 context = self._contexts[contextName] 300 req._serverSideContextPath = context 301 req._contextName = contextName 302 fpp = FileParser(context) 303 return fpp.parse(trans, requestPath) 304 305 306class _FileParser(URLParser): 307 """Parse requests to the filesystem. 308 309 FileParser dispatches to servlets in the filesystem, as well as providing 310 hooks to override the FileParser. 311 312 FileParser objects are threadsafe. A factory function is used to cache 313 FileParser instances, so for any one path only a single FileParser instance 314 will exist. The `_FileParser` class is the real class, and `FileParser` is 315 a factory that either returns an existent _FileParser object, or creates a 316 new one if none exists. 317 318 FileParser uses several settings from ``Application.config``, which are 319 persistent over the life of the application. These are set up in the 320 function `initApp`, as class variables. They cannot be set when the module 321 is loaded, because the Application is not yet set up, so `initApp` is 322 called in `Application.__init__`. 323 """ 324 325 326 ## Init ## 327 328 def __init__(self, path): 329 """Create a FileParser. 330 331 Each parsed directory has a FileParser instance associated with it 332 (``self._path``). 333 """ 334 URLParser.__init__(self) 335 self._path = path 336 self._initModule = None 337 338 339 ## Parsing ## 340 341 def parse(self, trans, requestPath): 342 """Return the servlet. 343 344 __init__ files will be used for various hooks 345 (see `parseInit` for more). 346 347 If the next part of the URL is a directory, it calls 348 ``FileParser(dirPath).parse(trans, restOfPath)`` where ``restOfPath`` 349 is `requestPath` with the first section of the path removed (the part 350 of the path that this FileParser just handled). 351 352 This uses `fileNamesForBaseName` to find files in its directory. 353 That function has several functions to define what files are ignored, 354 hidden, etc. See its documentation for more information. 355 """ 356 if debug: 357 print "FP(%r) parses %r" % (self._path, requestPath) 358 359 req = trans.request() 360 361 if req._absolutepath: 362 name = req._fsPath 363 restPart = req._extraURLPath 364 365 else: 366 # First decode the URL, since we are dealing with filenames here: 367 requestPath = urlDecode(requestPath) 368 369 result = self.parseInit(trans, requestPath) 370 if result is not None: 371 return result 372 373 if not requestPath or requestPath == '/': 374 return self.parseIndex(trans, requestPath) 375 376 if not requestPath.startswith('/'): 377 raise HTTPNotFound("Invalid path info: %s" % requestPath) 378 379 parts = requestPath[1:].split('/', 1) 380 nextPart = parts[0] 381 restPart = '/' + parts[1] if len(parts) > 1 else '' 382 383 baseName = os.path.join(self._path, nextPart) 384 if restPart and not self._extraPathInfo: 385 names = [baseName] 386 else: 387 names = self.filenamesForBaseName(baseName) 388 389 if len(names) > 1: 390 warn("More than one file matches %s in %s: %s" 391 % (requestPath, self._path, names)) 392 raise HTTPNotFound("Page is ambiguous") 393 elif not names: 394 return self.parseIndex(trans, requestPath) 395 396 name = names[0] 397 if os.path.isdir(name): 398 # directories are dispatched to FileParsers 399 # rooted in that directory 400 fpp = FileParser(name) 401 return fpp.parse(trans, restPart) 402 403 req._extraURLPath = restPart 404 405 if not self._extraPathInfo and restPart: 406 raise HTTPNotFound("Invalid extra path info: %s" % restPart) 407 408 req._serverSidePath = name 409 410 return ServletFactoryManager.servletForFile(trans, name) 411 412 def filenamesForBaseName(self, baseName): 413 """Find all files for a given base name. 414 415 Given a path, like ``/a/b/c``, searches for files in ``/a/b`` 416 that start with ``c``. The final name may include an extension, 417 which is less ambiguous; though if you ask for file.html, 418 and file.html.py exists, that file will be returned. 419 420 The files are filtered according to the settings ``FilesToHide``, 421 ``FilesToServe``, ``ExtensionsToIgnore`` and ``ExtensionsToServe``. 422 See the shouldServeFile() method for details on these settings. 423 424 All files that start with the given base name are returned 425 as a list. When the base name itself is part of the list or 426 when extensions are prioritized and such an extension is found 427 in the list, then the list will be reduced to only that entry. 428 429 Some settings are used to control the prioritization of filenames. 430 All settings are in ``Application.config``: 431 432 UseCascadingExtensions: 433 If true, then extensions will be prioritized. So if 434 extension ``.tmpl`` shows up in ExtensionCascadeOrder 435 before ``.html``, then even if filenames with both 436 extensions exist, only the .tmpl file will be returned. 437 ExtensionCascadeOrder: 438 A list of extensions, ordered by priority. 439 """ 440 if '*' in baseName: 441 return [] 442 443 fileStart = os.path.basename(baseName) 444 dirName = os.path.dirname(baseName) 445 filenames = [] 446 for filename in os.listdir(dirName): 447 if filename.startswith('.'): 448 continue 449 elif filename == fileStart: 450 if self.shouldServeFile(filename): 451 return [os.path.join(dirName, filename)] 452 elif (filename.startswith(fileStart) 453 and os.path.splitext(filename)[0] == fileStart): 454 if self.shouldServeFile(filename): 455 filenames.append(os.path.join(dirName, filename)) 456 457 if self._useCascading and len(filenames) > 1: 458 for extension in self._cascadeOrder: 459 if baseName + extension in filenames: 460 return [baseName + extension] 461 462 return filenames 463 464 def shouldServeFile(self, filename): 465 """Check if the file with the given filename should be served. 466 467 Some settings are used to control the filtering of filenames. 468 All settings are in ``Application.config``: 469 470 FilesToHide: 471 These files will be ignored, and even given a full 472 extension will not be used. Takes a glob. 473 FilesToServe: 474 If set, *only* files matching these globs will be 475 served, all other files will be ignored. 476 ExtensionsToIgnore: 477 Files with these extensions will be ignored, but if a 478 complete filename (with extension) is given the file 479 *will* be served (unlike FilesToHide). Extensions are 480 in the form ``".py"`` 481 ExtensionsToServe: 482 If set, only files with these extensions will be 483 served. Like FilesToServe, only doesn't use globs. 484 """ 485 ext = os.path.splitext(filename)[1] 486 if ext in self._toIgnore: 487 return False 488 if self._toServe and ext not in self._toServe: 489 return False 490 for regex in self._filesToHideRegexes: 491 if regex.match(filename): 492 return False 493 if self._filesToServeRegexes: 494 for regex in self._filesToServeRegexes: 495 if regex.match(filename): 496 break 497 else: 498 return False 499 return True 500 501 def parseIndex(self, trans, requestPath): 502 """Return index servlet. 503 504 Return the servlet for a directory index (i.e., ``Main`` or 505 ``index``). When `parse` encounters a directory and there's nothing 506 left in the URL, or when there is something left and no file matches 507 it, then it will try `parseIndex` to see if there's an index file. 508 509 That means that if ``/a/b/c`` is requested, and in ``/a`` there's no 510 file or directory matching ``b``, then it'll look for an index file 511 (like ``Main.py``), and that servlet will be returned. In fact, if 512 no ``a`` was found, and the default context had an index (like 513 ``index.html``) then that would be called with ``/a/b/c`` as 514 `HTTPRequest.extraURLPath`. If you don't want that to occur, you 515 should raise an HTTPNotFound in your no-extra-url-path-taking servlets. 516 517 The directory names are based off the ``Application.config`` setting 518 ``DirectoryFile``, which is a list of base names, by default 519 ``["Main", "index", "main", "Index"]``, which are searched in order. 520 A file with any extension is allowed, so the index can be an HTML file, 521 a PSP file, a Kid template, a Python servlet, etc. 522 """ 523 req = trans.request() 524 # If requestPath is empty, then we're missing the trailing slash: 525 if not requestPath: 526 p = req.serverURL() + '/' 527 q = req.queryString() 528 if q: 529 p += "?" + q 530 raise HTTPMovedPermanently(location=p) 531 if requestPath == '/': 532 requestPath = '' 533 for directoryFile in self._directoryFile: 534 basename = os.path.join(self._path, directoryFile) 535 names = self.filenamesForBaseName(basename) 536 if len(names) > 1 and self._useCascading: 537 for ext in self._cascadeOrder: 538 if basename + ext in names: 539 names = [basename + ext] 540 break 541 if len(names) > 1: 542 warn("More than one file matches the index file %s in %s: %s" 543 % (directoryFile, self._path, names)) 544 raise HTTPNotFound("Index page is ambiguous") 545 if names: 546 if requestPath and not self._extraPathInfo: 547 raise HTTPNotFound 548 req._serverSidePath = names[0] 549 req._extraURLPath = requestPath 550 return ServletFactoryManager.servletForFile(trans, names[0]) 551 raise HTTPNotFound("Index page not found") 552 553 def initModule(self): 554 """Get the __init__ module object for this FileParser's directory.""" 555 path = self._path 556 # if this directory is a context, return the context package 557 for context, dir in self._app.contexts().items(): 558 if dir == path: 559 # avoid reloading of the context package 560 return sys.modules.get(context) 561 name = 'WebKit_Cache_' + _moduleNameRE.sub('_', path) 562 try: 563 file, path, desc = self._imp.find_module('__init__', [path]) 564 return self._imp.load_module(name, file, path, desc) 565 except (ImportError, TypeError): 566 pass 567 568 def parseInit(self, trans, requestPath): 569 """Parse the __init__ file. 570 571 Returns the resulting servlet, or None if no __init__ hooks were found. 572 573 Hooks are put in by defining special functions or objects in your 574 __init__, with specific names: 575 576 `urlTransactionHook`: 577 A function that takes one argument (the transaction). 578 The return value from the function is ignored. You 579 can modify the transaction with this function, though. 580 581 `urlRedirect`: 582 A dictionary. Keys in the dictionary are source 583 URLs, the value is the path to redirect to, or a 584 `URLParser` object to which the transaction should 585 be delegated. 586 587 For example, if the URL is ``/a/b/c``, and we've already 588 parsed ``/a`` and are looking for ``b/c``, and we fine 589 `urlRedirect`` in a.__init__, then we'll look for a key 590 ``b`` in the dictionary. The value will be a directory 591 we should continue to (say, ``/destpath/``). We'll 592 then look for ``c`` in ``destpath``. 593 594 If a key '' (empty string) is in the dictionary, then 595 if no more specific key is found all requests will 596 be redirected to that path. 597 598 Instead of a string giving a path to redirect to, you 599 can also give a URLParser object, so that some portions 600 of the path are delegated to different parsers. 601 602 If no matching key is found, and there is no '' key, 603 then parsing goes on as usual. 604 605 `SubParser`: 606 This should be a class object. It will be instantiated, 607 and then `parse` will be called with it, delegating to 608 this instance. When instantiated, it will be passed 609 *this* FileParser instance; the parser can use this to 610 return control back to the FileParser after doing whatever 611 it wants to do. 612 613 You may want to use a line like this to handle the names:: 614 615 from ParserX import ParserX as SubParser 616 617 `urlParser`: 618 This should be an already instantiated URLParser-like 619 object. `parse(trans, requestPath)` will be called 620 on this instance. 621 622 `urlParserHook`: 623 Like `urlParser`, except the method 624 `parseHook(trans, requestPath, fileParser)` will 625 be called, where fileParser is this FileParser instance. 626 627 `urlJoins`: 628 Either a single path, or a list of paths. You can also 629 use URLParser objects, like with `urlRedirect`. 630 631 Each of these paths (or parsers) will be tried in 632 order. If it raises HTTPNotFound, then the next path 633 will be tried, ending with the current path. 634 635 Paths are relative to the current directory. If you 636 don't want the current directory to be a last resort, 637 you can include '.' in the joins. 638 """ 639 if self._initModule is None: 640 self._initModule = self.initModule() 641 mod = self._initModule 642 643 seen = trans._fileParserInitSeen.setdefault(self._path, set()) 644 645 if ('urlTransactionHook' not in seen 646 and hasattr(mod, 'urlTransactionHook')): 647 seen.add('urlTransactionHook') 648 mod.urlTransactionHook(trans) 649 650 if 'urlRedirect' not in seen and hasattr(mod, 'urlRedirect'): 651 # @@: do we need this shortcircuit? 652 seen.add('urlRedirect') 653 try: 654 nextPart, restPath = requestPath[1:].split('/', 1) 655 restPath = '/' + restPath 656 except ValueError: 657 nextPart = requestPath[1:] 658 restPath = '' 659 if nextPart in mod.urlRedirect: 660 redirTo = mod.urlRedirect[nextPart] 661 redirPath = restPath 662 elif '' in mod.urlRedirect: 663 redirTo = mod.urlRedirect[''] 664 redirPath = restPath 665 else: 666 redirTo = None 667 if redirTo: 668 if isinstance(redirTo, basestring): 669 fpp = FileParser(os.path.join(self._path, redirTo)) 670 else: 671 fpp = redirTo 672 return fpp.parse(trans, redirPath) 673 674 if 'SubParser' not in seen and hasattr(mod, 'SubParser'): 675 seen.add('SubParser') 676 pp = mod.SubParser(self) 677 return pp.parse(trans, requestPath) 678 679 if 'urlParser' not in seen and hasattr(mod, 'urlParser'): 680 seen.add('urlParser') 681 pp = mod.urlParser 682 return pp.parse(trans, requestPath) 683 684 if 'urlParserHook' not in seen and hasattr(mod, 'urlParserHook'): 685 seen.add('urlParserHook') 686 pp = mod.urlParserHook 687 return pp.parseHook(trans, requestPath, self) 688 689 if 'urlJoins' not in seen and hasattr(mod, 'urlJoins'): 690 seen.add('urlJoins') 691 joinPath = mod.urlJoins 692 if isinstance(joinPath, basestring): 693 joinPath = [joinPath] 694 for path in joinPath: 695 path = os.path.join(self._path, path) 696 if isinstance(path, basestring): 697 parser = FileParser(os.path.join(self._path, path)) 698 else: 699 parser = path 700 try: 701 return parser.parse(trans, requestPath) 702 except HTTPNotFound: 703 pass 704 705 return None 706 707FileParser = ParamFactory(_FileParser) 708 709 710class URLParameterParser(URLParser): 711 """Strips named parameters out of the URL. 712 713 E.g. in ``/path/SID=123/etc`` the ``SID=123`` will be removed from the URL, 714 and a field will be set in the request (so long as no field by that name 715 already exists -- if a field does exist the variable is thrown away). 716 These are put in the place of GET or POST variables. 717 718 It should be put in an __init__, like:: 719 720 from WebKit.URLParser import URLParameterParser 721 urlParserHook = URLParameterParser() 722 723 Or (slightly less efficient): 724 725 from WebKit.URLParser import URLParameterParser as SubParser 726 """ 727 728 729 ## Init ## 730 731 def __init__(self, fileParser=None): 732 self._fileParser = fileParser 733 734 735 ## Parsing ## 736 737 def parse(self, trans, requestPath): 738 """Delegates to `parseHook`.""" 739 return self.parseHook(trans, requestPath, self._fileParser) 740 741 @staticmethod 742 def parseHook(trans, requestPath, hook): 743 """Munges the path. 744 745 The `hook` is the FileParser object that originally called this -- 746 we just want to strip stuff out of the URL and then give it back to 747 the FileParser instance, which can actually find the servlet. 748 """ 749 parts = requestPath.split('/') 750 result = [] 751 req = trans.request() 752 for part in parts: 753 if '=' in part: 754 name, value = part.split('=', 1) 755 if not req.hasField(name): 756 req.setField(name, value) 757 else: 758 result.append(part) 759 return hook.parse(trans, '/'.join(result)) 760 761 762class ServletFactoryManagerClass(object): 763 """Manage servlet factories. 764 765 This singleton (called `ServletFactoryManager`) collects and manages 766 all the servlet factories that are installed. 767 768 See `addServletFactory` for adding new factories, and `servletForFile` 769 for getting the factories back. 770 """ 771 772 773 ## Init ## 774 775 def __init__(self): 776 self.reset() 777 778 def reset(self): 779 self._factories = [] 780 self._factoryExtensions = {} 781 782 783 ## Manager ## 784 785 def addServletFactory(self, factory): 786 """Add a new servlet factory. 787 788 Servlet factories can add themselves with:: 789 790 ServletFactoryManager.addServletFactory(factory) 791 792 The factory must have an `extensions` method, which should 793 return a list of extensions that the factory handles (like 794 ``['.ht']``). The special extension ``.*`` will match any 795 file if no other factory is found. See `ServletFactory` 796 for more information. 797 """ 798 799 self._factories.append(factory) 800 for ext in factory.extensions(): 801 assert ext not in self._factoryExtensions, ( 802 "Extension %s for factory %s was already used by factory %s" 803 % (repr(ext), factory.__name__, 804 self._factoryExtensions[ext].__name__)) 805 self._factoryExtensions[ext] = factory 806 807 def factoryForFile(self, path): 808 """Get a factory for a filename.""" 809 ext = os.path.splitext(path)[1] 810 if ext in self._factoryExtensions: 811 return self._factoryExtensions[ext] 812 if '.*' in self._factoryExtensions: 813 return self._factoryExtensions['.*'] 814 raise HTTPNotFound 815 816 def servletForFile(self, trans, path): 817 """Get a servlet for a filename and transaction. 818 819 Uses `factoryForFile` to find the factory, which 820 creates the servlet. 821 """ 822 factory = self.factoryForFile(path) 823 return factory.servletForTransaction(trans) 824 825ServletFactoryManager = ServletFactoryManagerClass() 826 827 828## Global Init ## 829 830def initApp(app): 831 """Initialize the application. 832 833 Installs the proper servlet factories, and gets some settings from 834 Application.config. Also saves the application in _globalApplication 835 for future calls to the application() function. 836 837 This needs to be called before any of the URLParser-derived classes 838 are instantiated. 839 """ 840 global _globalApplication 841 _globalApplication = app 842 from UnknownFileTypeServlet import UnknownFileTypeServletFactory 843 from ServletFactory import PythonServletFactory 844 845 ServletFactoryManager.reset() 846 for factory in [UnknownFileTypeServletFactory, PythonServletFactory]: 847 ServletFactoryManager.addServletFactory(factory(app)) 848 849 initParser(app) 850 851 852def initParser(app): 853 """Initialize the FileParser Class.""" 854 cls = _FileParser 855 cls._app = app 856 cls._imp = app._imp 857 cls._contexts = app.contexts 858 cls._filesToHideRegexes = [] 859 cls._filesToServeRegexes = [] 860 from fnmatch import translate as fnTranslate 861 for pattern in app.setting('FilesToHide'): 862 cls._filesToHideRegexes.append(re.compile(fnTranslate(pattern))) 863 for pattern in app.setting('FilesToServe'): 864 cls._filesToServeRegexes.append(re.compile(fnTranslate(pattern))) 865 cls._toIgnore = app.setting('ExtensionsToIgnore') 866 cls._toServe = app.setting('ExtensionsToServe') 867 cls._useCascading = app.setting('UseCascadingExtensions') 868 cls._cascadeOrder = app.setting('ExtensionCascadeOrder') 869 cls._directoryFile = app.setting('DirectoryFile') 870 cls._extraPathInfo = app.setting('ExtraPathInfo') 871