thirdparty/google_appengine/google/appengine/tools/dev_appserver.py
changeset 109 620f9b141567
child 149 f2e327a7c5de
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/tools/dev_appserver.py	Tue Aug 26 21:49:54 2008 +0000
@@ -0,0 +1,2649 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Pure-Python application server for testing applications locally.
+
+Given a port and the paths to a valid application directory (with an 'app.yaml'
+file), the external library directory, and a relative URL to use for logins,
+creates an HTTP server that can be used to test an application locally. Uses
+stubs instead of actual APIs when SetupStubs() is called first.
+
+Example:
+  root_path = '/path/to/application/directory'
+  login_url = '/login'
+  port = 8080
+  template_dir = '/path/to/appserver/templates'
+  server = dev_appserver.CreateServer(root_path, login_url, port, template_dir)
+  server.serve_forever()
+"""
+
+
+import os
+os.environ['TZ'] = 'UTC'
+import time
+if hasattr(time, 'tzset'):
+  time.tzset()
+
+import __builtin__
+import BaseHTTPServer
+import Cookie
+import cStringIO
+import cgi
+import cgitb
+import dummy_thread
+import errno
+import httplib
+import imp
+import inspect
+import itertools
+import logging
+import mimetools
+import mimetypes
+import pickle
+import pprint
+import random
+
+import re
+import sre_compile
+import sre_constants
+import sre_parse
+
+import mimetypes
+import socket
+import sys
+import urlparse
+import urllib
+import traceback
+import types
+
+import google
+from google.pyglib import gexcept
+
+from google.appengine.api import apiproxy_stub_map
+from google.appengine.api import appinfo
+from google.appengine.api import datastore_admin
+from google.appengine.api import datastore_file_stub
+from google.appengine.api import urlfetch_stub
+from google.appengine.api import mail_stub
+from google.appengine.api import user_service_stub
+from google.appengine.api import yaml_errors
+from google.appengine.api.memcache import memcache_stub
+
+from google.appengine.tools import dev_appserver_index
+from google.appengine.tools import dev_appserver_login
+
+
+PYTHON_LIB_VAR = '$PYTHON_LIB'
+DEVEL_CONSOLE_PATH = PYTHON_LIB_VAR + '/google/appengine/ext/admin'
+
+FILE_MISSING_EXCEPTIONS = frozenset([errno.ENOENT, errno.ENOTDIR])
+
+MAX_URL_LENGTH = 2047
+
+HEADER_TEMPLATE = 'logging_console_header.html'
+SCRIPT_TEMPLATE = 'logging_console.js'
+MIDDLE_TEMPLATE = 'logging_console_middle.html'
+FOOTER_TEMPLATE = 'logging_console_footer.html'
+
+DEFAULT_ENV = {
+  'GATEWAY_INTERFACE': 'CGI/1.1',
+  'AUTH_DOMAIN': 'gmail.com',
+  'TZ': 'UTC',
+}
+
+for ext, mime_type in (('.asc', 'text/plain'),
+                       ('.diff', 'text/plain'),
+                       ('.csv', 'text/comma-separated-values'),
+                       ('.rss', 'application/rss+xml'),
+                       ('.text', 'text/plain'),
+                       ('.wbmp', 'image/vnd.wap.wbmp')):
+  mimetypes.add_type(mime_type, ext)
+
+
+class Error(Exception):
+  """Base-class for exceptions in this module."""
+
+class InvalidAppConfigError(Error):
+  """The supplied application configuration file is invalid."""
+
+class AppConfigNotFoundError(Error):
+  """Application configuration file not found."""
+
+class TemplatesNotLoadedError(Error):
+  """Templates for the debugging console were not loaded."""
+
+
+def SplitURL(relative_url):
+  """Splits a relative URL into its path and query-string components.
+
+  Args:
+    relative_url: String containing the relative URL (often starting with '/')
+      to split. Should be properly escaped as www-form-urlencoded data.
+
+  Returns:
+    Tuple (script_name, query_string) where:
+      script_name: Relative URL of the script that was accessed.
+      query_string: String containing everything after the '?' character.
+  """
+  scheme, netloc, path, query, fragment = urlparse.urlsplit(relative_url)
+  return path, query
+
+
+def GetFullURL(server_name, server_port, relative_url):
+  """Returns the full, original URL used to access the relative URL.
+
+  Args:
+    server_name: Name of the local host, or the value of the 'host' header
+      from the request.
+    server_port: Port on which the request was served (string or int).
+    relative_url: Relative URL that was accessed, including query string.
+
+  Returns:
+    String containing the original URL.
+  """
+  if str(server_port) != '80':
+    netloc = '%s:%s' % (server_name, server_port)
+  else:
+    netloc = server_name
+  return 'http://%s%s' % (netloc, relative_url)
+
+
+class URLDispatcher(object):
+  """Base-class for handling HTTP requests."""
+
+  def Dispatch(self,
+               relative_url,
+               path,
+               headers,
+               infile,
+               outfile,
+               base_env_dict=None):
+    """Dispatch and handle an HTTP request.
+
+    base_env_dict should contain at least these CGI variables:
+      REQUEST_METHOD, REMOTE_ADDR, SERVER_SOFTWARE, SERVER_NAME,
+      SERVER_PROTOCOL, SERVER_PORT
+
+    Args:
+      relative_url: String containing the URL accessed.
+      path: Local path of the resource that was matched; back-references will be
+        replaced by values matched in the relative_url. Path may be relative
+        or absolute, depending on the resource being served (e.g., static files
+        will have an absolute path; scripts will be relative).
+      headers: Instance of mimetools.Message with headers from the request.
+      infile: File-like object with input data from the request.
+      outfile: File-like object where output data should be written.
+      base_env_dict: Dictionary of CGI environment parameters if available.
+        Defaults to None.
+    """
+    raise NotImplementedError
+
+
+class URLMatcher(object):
+  """Matches an arbitrary URL using a list of URL patterns from an application.
+
+  Each URL pattern has an associated URLDispatcher instance and path to the
+  resource's location on disk. See AddURL for more details. The first pattern
+  that matches an inputted URL will have its associated values returned by
+  Match().
+  """
+
+  def __init__(self):
+    """Initializer."""
+    self._url_patterns = []
+
+  def AddURL(self, regex, dispatcher, path, requires_login, admin_only):
+    """Adds a URL pattern to the list of patterns.
+
+    If the supplied regex starts with a '^' or ends with a '$' an
+    InvalidAppConfigError exception will be raised. Start and end symbols
+    and implicitly added to all regexes, meaning we assume that all regexes
+    consume all input from a URL.
+
+    Args:
+      regex: String containing the regular expression pattern.
+      dispatcher: Instance of URLDispatcher that should handle requests that
+        match this regex.
+      path: Path on disk for the resource. May contain back-references like
+        r'\1', r'\2', etc, which will be replaced by the corresponding groups
+        matched by the regex if present.
+      requires_login: True if the user must be logged-in before accessing this
+        URL; False if anyone can access this URL.
+      admin_only: True if the user must be a logged-in administrator to
+        access the URL; False if anyone can access the URL.
+    """
+    if not isinstance(dispatcher, URLDispatcher):
+      raise TypeError, 'dispatcher must be a URLDispatcher sub-class'
+
+    if regex.startswith('^') or regex.endswith('$'):
+      raise InvalidAppConfigError, 'regex starts with "^" or ends with "$"'
+
+    adjusted_regex = '^%s$' % regex
+
+    try:
+      url_re = re.compile(adjusted_regex)
+    except re.error, e:
+      raise InvalidAppConfigError, 'regex invalid: %s' % e
+
+    match_tuple = (url_re, dispatcher, path, requires_login, admin_only)
+    self._url_patterns.append(match_tuple)
+
+  def Match(self,
+            relative_url,
+            split_url=SplitURL):
+    """Matches a URL from a request against the list of URL patterns.
+
+    The supplied relative_url may include the query string (i.e., the '?'
+    character and everything following).
+
+    Args:
+      relative_url: Relative URL being accessed in a request.
+
+    Returns:
+      Tuple (dispatcher, matched_path, requires_login, admin_only), which are
+      the corresponding values passed to AddURL when the matching URL pattern
+      was added to this matcher. The matched_path will have back-references
+      replaced using values matched by the URL pattern. If no match was found,
+      dispatcher will be None.
+    """
+    adjusted_url, query_string = split_url(relative_url)
+
+    for url_tuple in self._url_patterns:
+      url_re, dispatcher, path, requires_login, admin_only = url_tuple
+      the_match = url_re.match(adjusted_url)
+
+      if the_match:
+        adjusted_path = the_match.expand(path)
+        return dispatcher, adjusted_path, requires_login, admin_only
+
+    return None, None, None, None
+
+  def GetDispatchers(self):
+    """Retrieves the URLDispatcher objects that could be matched.
+
+    Should only be used in tests.
+
+    Returns:
+      A set of URLDispatcher objects.
+    """
+    return set([url_tuple[1] for url_tuple in self._url_patterns])
+
+
+class MatcherDispatcher(URLDispatcher):
+  """Dispatcher across multiple URLMatcher instances."""
+
+  def __init__(self,
+               login_url,
+               url_matchers,
+               get_user_info=dev_appserver_login.GetUserInfo,
+               login_redirect=dev_appserver_login.LoginRedirect):
+    """Initializer.
+
+    Args:
+      login_url: Relative URL which should be used for handling user logins.
+      url_matchers: Sequence of URLMatcher objects.
+      get_user_info, login_redirect: Used for dependency injection.
+    """
+    self._login_url = login_url
+    self._url_matchers = tuple(url_matchers)
+    self._get_user_info = get_user_info
+    self._login_redirect = login_redirect
+
+  def Dispatch(self,
+               relative_url,
+               path,
+               headers,
+               infile,
+               outfile,
+               base_env_dict=None):
+    """Dispatches a request to the first matching dispatcher.
+
+    Matchers are checked in the order they were supplied to the constructor.
+    If no matcher matches, a 404 error will be written to the outfile. The
+    path variable supplied to this method is ignored.
+    """
+    cookies = ', '.join(headers.getheaders('cookie'))
+    email, admin = self._get_user_info(cookies)
+
+    for matcher in self._url_matchers:
+      dispatcher, matched_path, requires_login, admin_only = matcher.Match(relative_url)
+      if dispatcher is None:
+        continue
+
+      logging.debug('Matched "%s" to %s with path %s',
+                    relative_url, dispatcher, matched_path)
+
+      if (requires_login or admin_only) and not email:
+        logging.debug('Login required, redirecting user')
+        self._login_redirect(
+          self._login_url,
+          base_env_dict['SERVER_NAME'],
+          base_env_dict['SERVER_PORT'],
+          relative_url,
+          outfile)
+      elif admin_only and not admin:
+        outfile.write('Status: %d Not authorized\r\n'
+                      '\r\n'
+                      'Current logged in user %s is not '
+                      'authorized to view this page.'
+                      % (httplib.FORBIDDEN, email))
+      else:
+        dispatcher.Dispatch(relative_url,
+                            matched_path,
+                            headers,
+                            infile,
+                            outfile,
+                            base_env_dict=base_env_dict)
+
+      return
+
+    outfile.write('Status: %d URL did not match\r\n'
+                  '\r\n'
+                  'Not found error: %s did not match any patterns '
+                  'in application configuration.'
+                  % (httplib.NOT_FOUND, relative_url))
+
+
+class ApplicationLoggingHandler(logging.Handler):
+  """Python Logging handler that displays the debugging console to users."""
+
+  _COOKIE_NAME = '_ah_severity'
+
+  _TEMPLATES_INITIALIZED = False
+  _HEADER = None
+  _SCRIPT = None
+  _MIDDLE = None
+  _FOOTER = None
+
+  @staticmethod
+  def InitializeTemplates(header, script, middle, footer):
+    """Initializes the templates used to render the debugging console.
+
+    This method must be called before any ApplicationLoggingHandler instances
+    are created.
+
+    Args:
+      header: The header template that is printed first.
+      script: The script template that is printed after the logging messages.
+      middle: The middle element that's printed before the footer.
+      footer; The last element that's printed at the end of the document.
+    """
+    ApplicationLoggingHandler._HEADER = header
+    ApplicationLoggingHandler._SCRIPT = script
+    ApplicationLoggingHandler._MIDDLE = middle
+    ApplicationLoggingHandler._FOOTER = footer
+    ApplicationLoggingHandler._TEMPLATES_INITIALIZED = True
+
+  @staticmethod
+  def AreTemplatesInitialized():
+    """Returns True if InitializeTemplates has been called, False otherwise."""
+    return ApplicationLoggingHandler._TEMPLATES_INITIALIZED
+
+  def __init__(self, *args, **kwargs):
+    """Initializer.
+
+    Args:
+      args, kwargs: See logging.Handler.
+
+    Raises:
+      TemplatesNotLoadedError exception if the InitializeTemplates method was
+      not called before creating this instance.
+    """
+    if not self._TEMPLATES_INITIALIZED:
+      raise TemplatesNotLoadedError
+
+    logging.Handler.__init__(self, *args, **kwargs)
+    self._record_list = []
+    self._start_time = time.time()
+
+  def emit(self, record):
+    """Called by the logging module each time the application logs a message.
+
+    Args:
+      record: logging.LogRecord instance corresponding to the newly logged
+        message.
+    """
+    self._record_list.append(record)
+
+  def AddDebuggingConsole(self, relative_url, env, outfile):
+    """Prints an HTML debugging console to an output stream, if requested.
+
+    Args:
+      relative_url: Relative URL that was accessed, including the query string.
+        Used to determine if the parameter 'debug' was supplied, in which case
+        the console will be shown.
+      env: Dictionary containing CGI environment variables. Checks for the
+        HTTP_COOKIE entry to see if the accessing user has any logging-related
+        cookies set.
+      outfile: Output stream to which the console should be written if either
+        a debug parameter was supplied or a logging cookie is present.
+    """
+    script_name, query_string = SplitURL(relative_url)
+    param_dict = cgi.parse_qs(query_string, True)
+    cookie_dict = Cookie.SimpleCookie(env.get('HTTP_COOKIE', ''))
+    if 'debug' not in param_dict and self._COOKIE_NAME not in cookie_dict:
+      return
+
+    outfile.write(self._HEADER)
+    for record in self._record_list:
+      self._PrintRecord(record, outfile)
+
+    outfile.write(self._MIDDLE)
+    outfile.write(self._SCRIPT)
+    outfile.write(self._FOOTER)
+
+  def _PrintRecord(self, record, outfile):
+    """Prints a single logging record to an output stream.
+
+    Args:
+      record: logging.LogRecord instance to print.
+      outfile: Output stream to which the LogRecord should be printed.
+    """
+    message = cgi.escape(record.getMessage())
+    level_name = logging.getLevelName(record.levelno).lower()
+    level_letter = level_name[:1].upper()
+    time_diff = record.created - self._start_time
+    outfile.write('<span class="_ah_logline_%s">\n' % level_name)
+    outfile.write('<span class="_ah_logline_%s_prefix">%2.5f %s &gt;</span>\n'
+                  % (level_name, time_diff, level_letter))
+    outfile.write('%s\n' % message)
+    outfile.write('</span>\n')
+
+
+_IGNORE_HEADERS = frozenset(['content-type', 'content-length'])
+
+def SetupEnvironment(cgi_path,
+                     relative_url,
+                     headers,
+                     split_url=SplitURL,
+                     get_user_info=dev_appserver_login.GetUserInfo):
+  """Sets up environment variables for a CGI.
+
+  Args:
+    cgi_path: Full file-system path to the CGI being executed.
+    relative_url: Relative URL used to access the CGI.
+    headers: Instance of mimetools.Message containing request headers.
+    split_url, get_user_info: Used for dependency injection.
+
+  Returns:
+    Dictionary containing CGI environment variables.
+  """
+  env = DEFAULT_ENV.copy()
+
+  script_name, query_string = split_url(relative_url)
+
+  env['SCRIPT_NAME'] = ''
+  env['QUERY_STRING'] = query_string
+  env['PATH_INFO'] = urllib.unquote(script_name)
+  env['PATH_TRANSLATED'] = cgi_path
+  env['CONTENT_TYPE'] = headers.getheader('content-type',
+                                          'application/x-www-form-urlencoded')
+  env['CONTENT_LENGTH'] = headers.getheader('content-length', '')
+
+  cookies = ', '.join(headers.getheaders('cookie'))
+  email, admin = get_user_info(cookies)
+  env['USER_EMAIL'] = email
+  if admin:
+    env['USER_IS_ADMIN'] = '1'
+
+  for key in headers:
+    if key in _IGNORE_HEADERS:
+      continue
+    adjusted_name = key.replace('-', '_').upper()
+    env['HTTP_' + adjusted_name] = ', '.join(headers.getheaders(key))
+
+  return env
+
+
+def FakeTemporaryFile(*args, **kwargs):
+  """Fake for tempfile.TemporaryFile that just uses StringIO."""
+  return cStringIO.StringIO()
+
+
+def NotImplementedFake(*args, **kwargs):
+  """Fake for methods/classes that are not implemented in the production
+  environment.
+  """
+  raise NotImplementedError("This class/method is not available.")
+
+
+def IsEncodingsModule(module_name):
+  """Determines if the supplied module is related to encodings in any way.
+
+  Encodings-related modules cannot be reloaded, so they need to be treated
+  specially when sys.modules is modified in any way.
+
+  Args:
+    module_name: Absolute name of the module regardless of how it is imported
+      into the local namespace (e.g., foo.bar.baz).
+
+  Returns:
+    True if it's an encodings-related module; False otherwise.
+  """
+  if (module_name in ('codecs', 'encodings') or
+      module_name.startswith('encodings.')):
+    return True
+  return False
+
+
+def ClearAllButEncodingsModules(module_dict):
+  """Clear all modules in a module dictionary except for those modules that
+  are in any way related to encodings.
+
+  Args:
+    module_dict: Dictionary in the form used by sys.modules.
+  """
+  for module_name in module_dict.keys():
+    if not IsEncodingsModule(module_name):
+      del module_dict[module_name]
+
+
+def FakeURandom(n):
+  """Fake version of os.urandom."""
+  bytes = ''
+  for i in xrange(n):
+    bytes += chr(random.randint(0, 255))
+  return bytes
+
+
+def FakeUname():
+  """Fake version of os.uname."""
+  return ('Linux', '', '', '', '')
+
+
+def IsPathInSubdirectories(filename,
+                           subdirectories,
+                           normcase=os.path.normcase):
+  """Determines if a filename is contained within one of a set of directories.
+
+  Args:
+    filename: Path of the file (relative or absolute).
+    subdirectories: Iterable collection of paths to subdirectories which the
+      given filename may be under.
+    normcase: Used for dependency injection.
+
+  Returns:
+    True if the supplied filename is in one of the given sub-directories or
+    its hierarchy of children. False otherwise.
+  """
+  file_dir = normcase(os.path.dirname(os.path.abspath(filename)))
+  for parent in subdirectories:
+    fixed_parent = normcase(os.path.abspath(parent))
+    if os.path.commonprefix([file_dir, fixed_parent]) == fixed_parent:
+      return True
+  return False
+
+SHARED_MODULE_PREFIXES = set([
+  'google',
+  'logging',
+  'sys',
+  'warnings',
+
+
+
+
+  're',
+  'sre_compile',
+  'sre_constants',
+  'sre_parse',
+
+
+
+
+  'wsgiref',
+])
+
+NOT_SHARED_MODULE_PREFIXES = set([
+  'google.appengine.ext',
+])
+
+
+def ModuleNameHasPrefix(module_name, prefix_set):
+  """Determines if a module's name belongs to a set of prefix strings.
+
+  Args:
+    module_name: String containing the fully qualified module name.
+    prefix_set: Iterable set of module name prefixes to check against.
+
+  Returns:
+    True if the module_name belongs to the prefix set or is a submodule of
+    any of the modules specified in the prefix_set. Otherwise False.
+  """
+  for prefix in prefix_set:
+    if prefix == module_name:
+      return True
+
+    if module_name.startswith(prefix + '.'):
+      return True
+
+  return False
+
+
+def SetupSharedModules(module_dict):
+  """Creates a module dictionary for the hardened part of the process.
+
+  Module dictionary will contain modules that should be shared between the
+  hardened and unhardened parts of the process.
+
+  Args:
+    module_dict: Module dictionary from which existing modules should be
+      pulled (usually sys.modules).
+
+  Returns:
+    A new module dictionary.
+  """
+  output_dict = {}
+  for module_name, module in module_dict.iteritems():
+    if module is None:
+      continue
+
+    if IsEncodingsModule(module_name):
+      output_dict[module_name] = module
+      continue
+
+    shared_prefix = ModuleNameHasPrefix(module_name, SHARED_MODULE_PREFIXES)
+    banned_prefix = ModuleNameHasPrefix(module_name, NOT_SHARED_MODULE_PREFIXES)
+
+    if shared_prefix and not banned_prefix:
+      output_dict[module_name] = module
+
+  return output_dict
+
+
+class FakeFile(file):
+  """File sub-class that enforces the security restrictions of the production
+  environment.
+  """
+
+  ALLOWED_MODES = frozenset(['r', 'rb', 'U', 'rU'])
+
+  ALLOWED_FILES = set(os.path.normcase(filename)
+                      for filename in mimetypes.knownfiles
+                      if os.path.isfile(filename))
+
+  ALLOWED_DIRS = set([
+    os.path.normcase(os.path.abspath(os.path.dirname(os.__file__)))
+  ])
+
+  NOT_ALLOWED_DIRS = set([
+
+
+
+
+    os.path.normcase(os.path.join(os.path.dirname(os.__file__),
+                                  'site-packages'))
+  ])
+
+  ALLOWED_SITE_PACKAGE_DIRS = set(
+    os.path.normcase(os.path.abspath(os.path.join(
+      os.path.dirname(os.__file__), 'site-packages', path)))
+    for path in [
+
+  ])
+
+  _application_paths = None
+  _original_file = file
+
+  @staticmethod
+  def SetAllowedPaths(application_paths):
+    """Sets the root path of the application that is currently running.
+
+    Must be called at least once before any file objects are created in the
+    hardened environment.
+
+    Args:
+      root_path: Path to the root of the application.
+    """
+    FakeFile._application_paths = set(os.path.abspath(path)
+                                      for path in application_paths)
+
+  @staticmethod
+  def IsFileAccessible(filename, normcase=os.path.normcase):
+    """Determines if a file's path is accessible.
+
+    SetAllowedPaths() must be called before this method or else all file
+    accesses will raise an error.
+
+    Args:
+      filename: Path of the file to check (relative or absolute). May be a
+        directory, in which case access for files inside that directory will
+        be checked.
+      normcase: Used for dependency injection.
+
+    Returns:
+      True if the file is accessible, False otherwise.
+    """
+    logical_filename = normcase(os.path.abspath(filename))
+
+    if os.path.isdir(logical_filename):
+      logical_filename = os.path.join(logical_filename, 'foo')
+
+    if logical_filename in FakeFile.ALLOWED_FILES:
+      return True
+
+    if IsPathInSubdirectories(logical_filename,
+                              FakeFile.ALLOWED_SITE_PACKAGE_DIRS,
+                              normcase=normcase):
+      return True
+
+    allowed_dirs = FakeFile._application_paths | FakeFile.ALLOWED_DIRS
+    if (IsPathInSubdirectories(logical_filename,
+                               allowed_dirs,
+                               normcase=normcase) and
+        not IsPathInSubdirectories(logical_filename,
+                                   FakeFile.NOT_ALLOWED_DIRS,
+                                   normcase=normcase)):
+      return True
+
+    return False
+
+  def __init__(self, filename, mode='r', **kwargs):
+    """Initializer. See file built-in documentation."""
+    if mode not in FakeFile.ALLOWED_MODES:
+      raise IOError('invalid mode: %s' % mode)
+
+    if not FakeFile.IsFileAccessible(filename):
+      raise IOError(errno.EACCES, 'file not accessible')
+
+    super(FakeFile, self).__init__(filename, mode, **kwargs)
+
+
+class RestrictedPathFunction(object):
+  """Enforces access restrictions for functions that have a file or
+  directory path as their first argument."""
+
+  _original_os = os
+
+  def __init__(self, original_func):
+    """Initializer.
+
+    Args:
+      original_func: Callable that takes as its first argument the path to a
+        file or directory on disk; all subsequent arguments may be variable.
+    """
+    self._original_func = original_func
+
+  def __call__(self, path, *args, **kwargs):
+    """Enforces access permissions for the function passed to the constructor.
+    """
+    if not FakeFile.IsFileAccessible(path):
+      raise OSError(errno.EACCES, 'path not accessible')
+
+    return self._original_func(path, *args, **kwargs)
+
+
+def GetSubmoduleName(fullname):
+  """Determines the leaf submodule name of a full module name.
+
+  Args:
+    fullname: Fully qualified module name, e.g. 'foo.bar.baz'
+
+  Returns:
+    Submodule name, e.g. 'baz'. If the supplied module has no submodule (e.g.,
+    'stuff'), the returned value will just be that module name ('stuff').
+  """
+  return fullname.rsplit('.', 1)[-1]
+
+
+class CouldNotFindModuleError(ImportError):
+  """Raised when a module could not be found.
+
+  In contrast to when a module has been found, but cannot be loaded because of
+  hardening restrictions.
+  """
+
+
+def Trace(func):
+  """Decorator that logs the call stack of the HardenedModulesHook class as
+  it executes, indenting logging messages based on the current stack depth.
+  """
+  def decorate(self, *args, **kwargs):
+    args_to_show = []
+    if args is not None:
+      args_to_show.extend(str(argument) for argument in args)
+    if kwargs is not None:
+      args_to_show.extend('%s=%s' % (key, value)
+                          for key, value in kwargs.iteritems())
+
+    args_string = ', '.join(args_to_show)
+
+    self.log('Entering %s(%s)', func.func_name, args_string)
+    self._indent_level += 1
+    try:
+      return func(self, *args, **kwargs)
+    finally:
+      self._indent_level -= 1
+      self.log('Exiting %s(%s)', func.func_name, args_string)
+
+  return decorate
+
+
+class HardenedModulesHook(object):
+  """Meta import hook that restricts the modules used by applications to match
+  the production environment.
+
+  Module controls supported:
+  - Disallow native/extension modules from being loaded
+  - Disallow built-in and/or Python-distributed modules from being loaded
+  - Replace modules with completely empty modules
+  - Override specific module attributes
+  - Replace one module with another
+
+  After creation, this object should be added to the front of the sys.meta_path
+  list (which may need to be created). The sys.path_importer_cache dictionary
+  should also be cleared, to prevent loading any non-restricted modules.
+
+  See PEP302 for more info on how this works:
+    http://www.python.org/dev/peps/pep-0302/
+  """
+
+  ENABLE_LOGGING = False
+
+  def log(self, message, *args):
+    """Logs an import-related message to stderr, with indentation based on
+    current call-stack depth.
+
+    Args:
+      message: Logging format string.
+      args: Positional format parameters for the logging message.
+    """
+    if HardenedModulesHook.ENABLE_LOGGING:
+      indent = self._indent_level * '  '
+      print >>sys.stderr, indent + (message % args)
+
+  EMPTY_MODULE_FILE = '<empty module>'
+
+  _WHITE_LIST_C_MODULES = [
+    'array',
+    'binascii',
+    'bz2',
+    'cmath',
+    'collections',
+    'crypt',
+    'cStringIO',
+    'datetime',
+    'errno',
+    'exceptions',
+    'gc',
+    'itertools',
+    'math',
+    'md5',
+    'operator',
+    'posix',
+    'posixpath',
+    'pyexpat',
+    'sha',
+    'struct',
+    'sys',
+    'time',
+    'timing',
+    'unicodedata',
+    'zlib',
+    '_bisect',
+    '_codecs',
+    '_codecs_cn',
+    '_codecs_hk',
+    '_codecs_iso2022',
+    '_codecs_jp',
+    '_codecs_kr',
+    '_codecs_tw',
+    '_csv',
+    '_elementtree',
+    '_functools',
+    '_hashlib',
+    '_heapq',
+    '_locale',
+    '_lsprof',
+    '_md5',
+    '_multibytecodec',
+    '_random',
+    '_sha',
+    '_sha256',
+    '_sha512',
+    '_sre',
+    '_struct',
+    '_types',
+    '_weakref',
+    '__main__',
+  ]
+
+  _WHITE_LIST_PARTIAL_MODULES = {
+    'gc': [
+      'enable',
+      'disable',
+      'isenabled',
+      'collect',
+      'get_debug',
+      'set_threshold',
+      'get_threshold',
+      'get_count'
+    ],
+
+
+
+    'os': [
+      'altsep',
+      'curdir',
+      'defpath',
+      'devnull',
+      'environ',
+      'error',
+      'extsep',
+      'EX_NOHOST',
+      'EX_NOINPUT',
+      'EX_NOPERM',
+      'EX_NOUSER',
+      'EX_OK',
+      'EX_OSERR',
+      'EX_OSFILE',
+      'EX_PROTOCOL',
+      'EX_SOFTWARE',
+      'EX_TEMPFAIL',
+      'EX_UNAVAILABLE',
+      'EX_USAGE',
+      'F_OK',
+      'getcwd',
+      'getcwdu',
+      'getenv',
+      'listdir',
+      'lstat',
+      'name',
+      'NGROUPS_MAX',
+      'O_APPEND',
+      'O_CREAT',
+      'O_DIRECT',
+      'O_DIRECTORY',
+      'O_DSYNC',
+      'O_EXCL',
+      'O_LARGEFILE',
+      'O_NDELAY',
+      'O_NOCTTY',
+      'O_NOFOLLOW',
+      'O_NONBLOCK',
+      'O_RDONLY',
+      'O_RDWR',
+      'O_RSYNC',
+      'O_SYNC',
+      'O_TRUNC',
+      'O_WRONLY',
+      'pardir',
+      'path',
+      'pathsep',
+      'R_OK',
+      'SEEK_CUR',
+      'SEEK_END',
+      'SEEK_SET',
+      'sep',
+      'stat',
+      'stat_float_times',
+      'stat_result',
+      'strerror',
+      'TMP_MAX',
+      'urandom',
+      'walk',
+      'WCOREDUMP',
+      'WEXITSTATUS',
+      'WIFEXITED',
+      'WIFSIGNALED',
+      'WIFSTOPPED',
+      'WNOHANG',
+      'WSTOPSIG',
+      'WTERMSIG',
+      'WUNTRACED',
+      'W_OK',
+      'X_OK',
+    ],
+  }
+
+  _EMPTY_MODULES = [
+    'imp',
+    'ftplib',
+    'select',
+    'socket',
+    'tempfile',
+  ]
+
+  _MODULE_OVERRIDES = {
+    'os': {
+      'listdir': RestrictedPathFunction(os.listdir),
+      'lstat': RestrictedPathFunction(os.lstat),
+      'stat': RestrictedPathFunction(os.stat),
+      'uname': FakeUname,
+      'urandom': FakeURandom,
+    },
+
+    'socket': {
+      'AF_INET': None,
+      'SOCK_STREAM': None,
+      'SOCK_DGRAM': None,
+    },
+
+    'tempfile': {
+      'TemporaryFile': FakeTemporaryFile,
+      'gettempdir': NotImplementedFake,
+      'gettempprefix': NotImplementedFake,
+      'mkdtemp': NotImplementedFake,
+      'mkstemp': NotImplementedFake,
+      'mktemp': NotImplementedFake,
+      'NamedTemporaryFile': NotImplementedFake,
+      'tempdir': NotImplementedFake,
+    },
+  }
+
+  _ENABLED_FILE_TYPES = (
+    imp.PKG_DIRECTORY,
+    imp.PY_SOURCE,
+    imp.PY_COMPILED,
+    imp.C_BUILTIN,
+  )
+
+  def __init__(self,
+               module_dict,
+               imp_module=imp,
+               os_module=os,
+               dummy_thread_module=dummy_thread,
+               pickle_module=pickle):
+    """Initializer.
+
+    Args:
+      module_dict: Module dictionary to use for managing system modules.
+        Should be sys.modules.
+      imp_module, os_module, dummy_thread_module, pickle_module: References to
+        modules that exist in the dev_appserver that must be used by this class
+        in order to function, even if these modules have been unloaded from
+        sys.modules.
+    """
+    self._module_dict = module_dict
+    self._imp = imp_module
+    self._os = os_module
+    self._dummy_thread = dummy_thread_module
+    self._pickle = pickle
+    self._indent_level = 0
+
+  @Trace
+  def find_module(self, fullname, path=None):
+    """See PEP 302."""
+    if (fullname in ('cPickle', 'thread') or
+        fullname in HardenedModulesHook._EMPTY_MODULES):
+      return self
+
+    search_path = path
+    all_modules = fullname.split('.')
+    try:
+      for index, current_module in enumerate(all_modules):
+        current_module_fullname = '.'.join(all_modules[:index + 1])
+        if current_module_fullname == fullname:
+          self.FindModuleRestricted(current_module,
+                                    current_module_fullname,
+                                    search_path)
+        else:
+          if current_module_fullname in self._module_dict:
+            module = self._module_dict[current_module_fullname]
+          else:
+            module = self.FindAndLoadModule(current_module,
+                                            current_module_fullname,
+                                            search_path)
+
+          if hasattr(module, '__path__'):
+            search_path = module.__path__
+    except CouldNotFindModuleError:
+      return None
+
+    return self
+
+  @Trace
+  def FixModule(self, module):
+    """Prunes and overrides restricted module attributes.
+
+    Args:
+      module: The module to prune. This should be a new module whose attributes
+        reference back to the real module's __dict__ members.
+    """
+    if module.__name__ in self._WHITE_LIST_PARTIAL_MODULES:
+      allowed_symbols = self._WHITE_LIST_PARTIAL_MODULES[module.__name__]
+      for symbol in set(module.__dict__) - set(allowed_symbols):
+        if not (symbol.startswith('__') and symbol.endswith('__')):
+          del module.__dict__[symbol]
+
+    if module.__name__ in self._MODULE_OVERRIDES:
+      module.__dict__.update(self._MODULE_OVERRIDES[module.__name__])
+
+  @Trace
+  def FindModuleRestricted(self,
+                           submodule,
+                           submodule_fullname,
+                           search_path):
+    """Locates a module while enforcing module import restrictions.
+
+    Args:
+      submodule: The short name of the submodule (i.e., the last section of
+        the fullname; for 'foo.bar' this would be 'bar').
+      submodule_fullname: The fully qualified name of the module to find (e.g.,
+        'foo.bar').
+      search_path: List of paths to search for to find this module. Should be
+        None if the current sys.path should be used.
+
+    Returns:
+      Tuple (source_file, pathname, description) where:
+        source_file: File-like object that contains the module; in the case
+          of packages, this will be None, which implies to look at __init__.py.
+        pathname: String containing the full path of the module on disk.
+        description: Tuple returned by imp.find_module().
+
+    Raises:
+      ImportError exception if the requested module was found, but importing
+      it is disallowed.
+
+      CouldNotFindModuleError exception if the request module could not even
+      be found for import.
+    """
+    try:
+      source_file, pathname, description = self._imp.find_module(submodule, search_path)
+    except ImportError:
+      self.log('Could not find module "%s"', submodule_fullname)
+      raise CouldNotFindModuleError()
+
+    suffix, mode, file_type = description
+
+    if (file_type not in (self._imp.C_BUILTIN, self._imp.C_EXTENSION) and
+        not FakeFile.IsFileAccessible(pathname)):
+      error_message = 'Access to module file denied: %s' % pathname
+      logging.debug(error_message)
+      raise ImportError(error_message)
+
+    if (file_type not in self._ENABLED_FILE_TYPES and
+        submodule not in self._WHITE_LIST_C_MODULES):
+      error_message = ('Could not import "%s": Disallowed C-extension '
+                       'or built-in module' % submodule_fullname)
+      logging.debug(error_message)
+      raise ImportError(error_message)
+
+    return source_file, pathname, description
+
+  @Trace
+  def LoadModuleRestricted(self,
+                           submodule_fullname,
+                           source_file,
+                           pathname,
+                           description):
+    """Loads a module while enforcing module import restrictions.
+
+    As a byproduct, the new module will be added to the module dictionary.
+
+    Args:
+      submodule_fullname: The fully qualified name of the module to find (e.g.,
+        'foo.bar').
+      source_file: File-like object that contains the module's source code.
+      pathname: String containing the full path of the module on disk.
+      description: Tuple returned by imp.find_module().
+
+    Returns:
+      The new module.
+
+    Raises:
+      ImportError exception of the specified module could not be loaded for
+      whatever reason.
+    """
+    try:
+      try:
+        return self._imp.load_module(submodule_fullname,
+                                     source_file,
+                                     pathname,
+                                     description)
+      except:
+        if submodule_fullname in self._module_dict:
+          del self._module_dict[submodule_fullname]
+        raise
+
+    finally:
+      if source_file is not None:
+        source_file.close()
+
+  @Trace
+  def FindAndLoadModule(self,
+                        submodule,
+                        submodule_fullname,
+                        search_path):
+    """Finds and loads a module, loads it, and adds it to the module dictionary.
+
+    Args:
+      submodule: Name of the module to import (e.g., baz).
+      submodule_fullname: Full name of the module to import (e.g., foo.bar.baz).
+      search_path: Path to use for searching for this submodule. For top-level
+        modules this should be None; otherwise it should be the __path__
+        attribute from the parent package.
+
+    Returns:
+      A new module instance that has been inserted into the module dictionary
+      supplied to __init__.
+
+    Raises:
+      ImportError exception if the module could not be loaded for whatever
+      reason (e.g., missing, not allowed).
+    """
+    module = self._imp.new_module(submodule_fullname)
+
+    if submodule_fullname in self._EMPTY_MODULES:
+      module.__file__ = self.EMPTY_MODULE_FILE
+    elif submodule_fullname == 'thread':
+      module.__dict__.update(self._dummy_thread.__dict__)
+      module.__name__ = 'thread'
+    elif submodule_fullname == 'cPickle':
+      module.__dict__.update(self._pickle.__dict__)
+      module.__name__ = 'cPickle'
+    elif submodule_fullname == 'os':
+      module.__dict__.update(self._os.__dict__)
+      self._module_dict['os.path'] = module.path
+    else:
+      source_file, pathname, description = self.FindModuleRestricted(submodule, submodule_fullname, search_path)
+      module = self.LoadModuleRestricted(submodule_fullname,
+                                         source_file,
+                                         pathname,
+                                         description)
+
+    module.__loader__ = self
+    self.FixModule(module)
+    if submodule_fullname not in self._module_dict:
+      self._module_dict[submodule_fullname] = module
+
+    return module
+
+  @Trace
+  def GetParentPackage(self, fullname):
+    """Retrieves the parent package of a fully qualified module name.
+
+    Args:
+      fullname: Full name of the module whose parent should be retrieved (e.g.,
+        foo.bar).
+
+    Returns:
+      Module instance for the parent or None if there is no parent module.
+
+    Raise:
+      ImportError exception if the module's parent could not be found.
+    """
+    all_modules = fullname.split('.')
+    parent_module_fullname = '.'.join(all_modules[:-1])
+    if parent_module_fullname:
+      if self.find_module(fullname) is None:
+        raise ImportError('Could not find module %s' % fullname)
+
+      return self._module_dict[parent_module_fullname]
+    return None
+
+  @Trace
+  def GetParentSearchPath(self, fullname):
+    """Determines the search path of a module's parent package.
+
+    Args:
+      fullname: Full name of the module to look up (e.g., foo.bar).
+
+    Returns:
+      Tuple (submodule, search_path) where:
+        submodule: The last portion of the module name from fullname (e.g.,
+          if fullname is foo.bar, then this is bar).
+        search_path: List of paths that belong to the parent package's search
+          path or None if there is no parent package.
+
+    Raises:
+      ImportError exception if the module or its parent could not be found.
+    """
+    submodule = GetSubmoduleName(fullname)
+    parent_package = self.GetParentPackage(fullname)
+    search_path = None
+    if parent_package is not None and hasattr(parent_package, '__path__'):
+      search_path = parent_package.__path__
+    return submodule, search_path
+
+  @Trace
+  def GetModuleInfo(self, fullname):
+    """Determines the path on disk and the search path of a module or package.
+
+    Args:
+      fullname: Full name of the module to look up (e.g., foo.bar).
+
+    Returns:
+      Tuple (pathname, search_path, submodule) where:
+        pathname: String containing the full path of the module on disk.
+        search_path: List of paths that belong to the found package's search
+          path or None if found module is not a package.
+        submodule: The relative name of the submodule that's being imported.
+    """
+    submodule, search_path = self.GetParentSearchPath(fullname)
+    source_file, pathname, description = self.FindModuleRestricted(submodule, fullname, search_path)
+    suffix, mode, file_type = description
+    module_search_path = None
+    if file_type == self._imp.PKG_DIRECTORY:
+      module_search_path = [pathname]
+      pathname = os.path.join(pathname, '__init__%spy' % os.extsep)
+    return pathname, module_search_path, submodule
+
+  @Trace
+  def load_module(self, fullname):
+    """See PEP 302."""
+    all_modules = fullname.split('.')
+    submodule = all_modules[-1]
+    parent_module_fullname = '.'.join(all_modules[:-1])
+    search_path = None
+    if parent_module_fullname and parent_module_fullname in self._module_dict:
+      parent_module = self._module_dict[parent_module_fullname]
+      if hasattr(parent_module, '__path__'):
+        search_path = parent_module.__path__
+
+    return self.FindAndLoadModule(submodule, fullname, search_path)
+
+  @Trace
+  def is_package(self, fullname):
+    """See PEP 302 extensions."""
+    submodule, search_path = self.GetParentSearchPath(fullname)
+    source_file, pathname, description = self.FindModuleRestricted(submodule, fullname, search_path)
+    suffix, mode, file_type = description
+    if file_type == self._imp.PKG_DIRECTORY:
+      return True
+    return False
+
+  @Trace
+  def get_source(self, fullname):
+    """See PEP 302 extensions."""
+    full_path, search_path, submodule = self.GetModuleInfo(fullname)
+    source_file = open(full_path)
+    try:
+      return source_file.read()
+    finally:
+      source_file.close()
+
+  @Trace
+  def get_code(self, fullname):
+    """See PEP 302 extensions."""
+    full_path, search_path, submodule = self.GetModuleInfo(fullname)
+    source_file = open(full_path)
+    try:
+      source_code = source_file.read()
+    finally:
+      source_file.close()
+
+    source_code = source_code.replace('\r\n', '\n')
+    if not source_code.endswith('\n'):
+      source_code += '\n'
+
+    return compile(source_code, full_path, 'exec')
+
+
+def ModuleHasValidMainFunction(module):
+  """Determines if a module has a main function that takes no arguments.
+
+  This includes functions that have arguments with defaults that are all
+  assigned, thus requiring no additional arguments in order to be called.
+
+  Args:
+    module: A types.ModuleType instance.
+
+  Returns:
+    True if the module has a valid, reusable main function; False otherwise.
+  """
+  if hasattr(module, 'main') and type(module.main) is types.FunctionType:
+    arg_names, var_args, var_kwargs, default_values = inspect.getargspec(module.main)
+    if len(arg_names) == 0:
+      return True
+    if default_values is not None and len(arg_names) == len(default_values):
+      return True
+  return False
+
+
+def GetScriptModuleName(handler_path):
+  """Determines the fully-qualified Python module name of a script on disk.
+
+  Args:
+    handler_path: CGI path stored in the application configuration (as a path
+      like 'foo/bar/baz.py'). May contain $PYTHON_LIB references.
+
+  Returns:
+    String containing the corresponding module name (e.g., 'foo.bar.baz').
+  """
+  if handler_path.startswith(PYTHON_LIB_VAR + '/'):
+    handler_path = handler_path[len(PYTHON_LIB_VAR):]
+  handler_path = os.path.normpath(handler_path)
+
+  extension_index = handler_path.rfind('.py')
+  if extension_index != -1:
+    handler_path = handler_path[:extension_index]
+  module_fullname = handler_path.replace(os.sep, '.')
+  module_fullname = module_fullname.strip('.')
+  module_fullname = re.sub('\.+', '.', module_fullname)
+
+  if module_fullname.endswith('.__init__'):
+    module_fullname = module_fullname[:-len('.__init__')]
+
+  return module_fullname
+
+
+def FindMissingInitFiles(cgi_path, module_fullname, isfile=os.path.isfile):
+  """Determines which __init__.py files are missing from a module's parent
+  packages.
+
+  Args:
+    cgi_path: Absolute path of the CGI module file on disk.
+    module_fullname: Fully qualified Python module name used to import the
+      cgi_path module.
+
+  Returns:
+    List containing the paths to the missing __init__.py files.
+  """
+  missing_init_files = []
+
+  if cgi_path.endswith('.py'):
+    module_base = os.path.dirname(cgi_path)
+  else:
+    module_base = cgi_path
+
+  depth_count = module_fullname.count('.')
+  if cgi_path.endswith('__init__.py') or not cgi_path.endswith('.py'):
+    depth_count += 1
+
+  for index in xrange(depth_count):
+    current_init_file = os.path.join(module_base, '__init__.py')
+
+    if not isfile(current_init_file):
+      missing_init_files.append(current_init_file)
+
+    module_base = os.path.abspath(os.path.join(module_base, os.pardir))
+
+  return missing_init_files
+
+
+def LoadTargetModule(handler_path,
+                     cgi_path,
+                     import_hook,
+                     module_dict=sys.modules):
+  """Loads a target CGI script by importing it as a Python module.
+
+  If the module for the target CGI script has already been loaded before,
+  the new module will be loaded in its place using the same module object,
+  possibly overwriting existing module attributes.
+
+  Args:
+    handler_path: CGI path stored in the application configuration (as a path
+      like 'foo/bar/baz.py'). Should not have $PYTHON_LIB references.
+    cgi_path: Absolute path to the CGI script file on disk.
+    import_hook: Instance of HardenedModulesHook to use for module loading.
+    module_dict: Used for dependency injection.
+
+  Returns:
+    Tuple (module_fullname, script_module, module_code) where:
+      module_fullname: Fully qualified module name used to import the script.
+      script_module: The ModuleType object corresponding to the module_fullname.
+        If the module has not already been loaded, this will be an empty
+        shell of a module.
+      module_code: Code object (returned by compile built-in) corresponding
+        to the cgi_path to run. If the script_module was previously loaded
+        and has a main() function that can be reused, this will be None.
+  """
+  module_fullname = GetScriptModuleName(handler_path)
+  script_module = module_dict.get(module_fullname)
+  module_code = None
+  if script_module != None and ModuleHasValidMainFunction(script_module):
+    logging.debug('Reusing main() function of module "%s"', module_fullname)
+  else:
+    if script_module is None:
+      script_module = imp.new_module(module_fullname)
+      script_module.__loader__ = import_hook
+
+    try:
+      module_code = import_hook.get_code(module_fullname)
+      full_path, search_path, submodule = import_hook.GetModuleInfo(module_fullname)
+      script_module.__file__ = full_path
+      if search_path is not None:
+        script_module.__path__ = search_path
+    except:
+      exc_type, exc_value, exc_tb = sys.exc_info()
+      import_error_message = str(exc_type)
+      if exc_value:
+        import_error_message += ': ' + str(exc_value)
+
+      logging.error('Encountered error loading module "%s": %s',
+                    module_fullname, import_error_message)
+      missing_inits = FindMissingInitFiles(cgi_path, module_fullname)
+      if missing_inits:
+        logging.warning('Missing package initialization files: %s',
+                        ', '.join(missing_inits))
+      else:
+        logging.error('Parent package initialization files are present, '
+                      'but must be broken')
+
+      independent_load_successful = True
+
+      if not os.path.isfile(cgi_path):
+        independent_load_successful = False
+      else:
+        try:
+          source_file = open(cgi_path)
+          try:
+            module_code = compile(source_file.read(), cgi_path, 'exec')
+            script_module.__file__ = cgi_path
+          finally:
+            source_file.close()
+
+        except OSError:
+          independent_load_successful = False
+
+      if not independent_load_successful:
+        raise exc_type, exc_value, exc_tb
+
+    module_dict[module_fullname] = script_module
+
+  return module_fullname, script_module, module_code
+
+
+def ExecuteOrImportScript(handler_path, cgi_path, import_hook):
+  """Executes a CGI script by importing it as a new module; possibly reuses
+  the module's main() function if it is defined and takes no arguments.
+
+  Basic technique lifted from PEP 338 and Python2.5's runpy module. See:
+    http://www.python.org/dev/peps/pep-0338/
+
+  See the section entitled "Import Statements and the Main Module" to understand
+  why a module named '__main__' cannot do relative imports. To get around this,
+  the requested module's path could be added to sys.path on each request.
+
+  Args:
+    handler_path: CGI path stored in the application configuration (as a path
+      like 'foo/bar/baz.py'). Should not have $PYTHON_LIB references.
+    cgi_path: Absolute path to the CGI script file on disk.
+    import_hook: Instance of HardenedModulesHook to use for module loading.
+
+  Returns:
+    True if the response code had an error status (e.g., 404), or False if it
+    did not.
+
+  Raises:
+    Any kind of exception that could have been raised when loading the target
+    module, running a target script, or executing the application code itself.
+  """
+  module_fullname, script_module, module_code = LoadTargetModule(
+      handler_path, cgi_path, import_hook)
+  script_module.__name__ = '__main__'
+  sys.modules['__main__'] = script_module
+  try:
+    if module_code:
+      exec module_code in script_module.__dict__
+    else:
+      script_module.main()
+
+    sys.stdout.flush()
+    sys.stdout.seek(0)
+    try:
+      headers = mimetools.Message(sys.stdout)
+    finally:
+      sys.stdout.seek(0, 2)
+    status_header = headers.get('status')
+    error_response = False
+    if status_header:
+      try:
+        status_code = int(status_header.split(' ', 1)[0])
+        error_response = status_code >= 400
+      except ValueError:
+        error_response = True
+
+    if not error_response:
+      try:
+        parent_package = import_hook.GetParentPackage(module_fullname)
+      except Exception:
+        parent_package = None
+
+      if parent_package is not None:
+        submodule = GetSubmoduleName(module_fullname)
+        setattr(parent_package, submodule, script_module)
+
+    return error_response
+  finally:
+    script_module.__name__ = module_fullname
+
+
+def ExecuteCGI(root_path,
+               handler_path,
+               cgi_path,
+               env,
+               infile,
+               outfile,
+               module_dict,
+               exec_script=ExecuteOrImportScript):
+  """Executes Python file in this process as if it were a CGI.
+
+  Does not return an HTTP response line. CGIs should output headers followed by
+  the body content.
+
+  The modules in sys.modules should be the same before and after the CGI is
+  executed, with the specific exception of encodings-related modules, which
+  cannot be reloaded and thus must always stay in sys.modules.
+
+  Args:
+    root_path: Path to the root of the application.
+    handler_path: CGI path stored in the application configuration (as a path
+      like 'foo/bar/baz.py'). May contain $PYTHON_LIB references.
+    cgi_path: Absolute path to the CGI script file on disk.
+    env: Dictionary of environment variables to use for the execution.
+    infile: File-like object to read HTTP request input data from.
+    outfile: FIle-like object to write HTTP response data to.
+    module_dict: Dictionary in which application-loaded modules should be
+      preserved between requests. This removes the need to reload modules that
+      are reused between requests, significantly increasing load performance.
+      This dictionary must be separate from the sys.modules dictionary.
+    exec_script: Used for dependency injection.
+  """
+  old_module_dict = sys.modules.copy()
+  old_builtin = __builtin__.__dict__.copy()
+  old_argv = sys.argv
+  old_stdin = sys.stdin
+  old_stdout = sys.stdout
+  old_env = os.environ.copy()
+  old_cwd = os.getcwd()
+  old_file_type = types.FileType
+  reset_modules = False
+
+  try:
+    ClearAllButEncodingsModules(sys.modules)
+    sys.modules.update(module_dict)
+    sys.argv = [cgi_path]
+    sys.stdin = infile
+    sys.stdout = outfile
+    os.environ.clear()
+    os.environ.update(env)
+    before_path = sys.path[:]
+    os.chdir(os.path.dirname(cgi_path))
+
+    hook = HardenedModulesHook(sys.modules)
+    sys.meta_path = [hook]
+    if hasattr(sys, 'path_importer_cache'):
+      sys.path_importer_cache.clear()
+
+    __builtin__.file = FakeFile
+    __builtin__.open = FakeFile
+    types.FileType = FakeFile
+
+    __builtin__.buffer = NotImplementedFake
+
+    logging.debug('Executing CGI with env:\n%s', pprint.pformat(env))
+    try:
+      reset_modules = exec_script(handler_path, cgi_path, hook)
+    except SystemExit, e:
+      logging.debug('CGI exited with status: %s', e)
+    except:
+      reset_modules = True
+      raise
+
+  finally:
+    sys.meta_path = []
+    sys.path_importer_cache.clear()
+
+    _ClearTemplateCache(sys.modules)
+
+    module_dict.update(sys.modules)
+    ClearAllButEncodingsModules(sys.modules)
+    sys.modules.update(old_module_dict)
+
+    __builtin__.__dict__.update(old_builtin)
+    sys.argv = old_argv
+    sys.stdin = old_stdin
+    sys.stdout = old_stdout
+
+    sys.path[:] = before_path
+
+    os.environ.clear()
+    os.environ.update(old_env)
+    os.chdir(old_cwd)
+
+    types.FileType = old_file_type
+
+
+class CGIDispatcher(URLDispatcher):
+  """Dispatcher that executes Python CGI scripts."""
+
+  def __init__(self,
+               module_dict,
+               root_path,
+               path_adjuster,
+               setup_env=SetupEnvironment,
+               exec_cgi=ExecuteCGI,
+               create_logging_handler=ApplicationLoggingHandler):
+    """Initializer.
+
+    Args:
+      module_dict: Dictionary in which application-loaded modules should be
+        preserved between requests. This dictionary must be separate from the
+        sys.modules dictionary.
+      path_adjuster: Instance of PathAdjuster to use for finding absolute
+        paths of CGI files on disk.
+      setup_env, exec_cgi, create_logging_handler: Used for dependency
+        injection.
+    """
+    self._module_dict = module_dict
+    self._root_path = root_path
+    self._path_adjuster = path_adjuster
+    self._setup_env = setup_env
+    self._exec_cgi = exec_cgi
+    self._create_logging_handler = create_logging_handler
+
+  def Dispatch(self,
+               relative_url,
+               path,
+               headers,
+               infile,
+               outfile,
+               base_env_dict=None):
+    """Dispatches the Python CGI."""
+    handler = self._create_logging_handler()
+    logging.getLogger().addHandler(handler)
+    before_level = logging.root.level
+    try:
+      env = {}
+      if base_env_dict:
+        env.update(base_env_dict)
+      cgi_path = self._path_adjuster.AdjustPath(path)
+      env.update(self._setup_env(cgi_path, relative_url, headers))
+      self._exec_cgi(self._root_path,
+                     path,
+                     cgi_path,
+                     env,
+                     infile,
+                     outfile,
+                     self._module_dict)
+      handler.AddDebuggingConsole(relative_url, env, outfile)
+    finally:
+      logging.root.level = before_level
+      logging.getLogger().removeHandler(handler)
+
+  def __str__(self):
+    """Returns a string representation of this dispatcher."""
+    return 'CGI dispatcher'
+
+
+class LocalCGIDispatcher(CGIDispatcher):
+  """Dispatcher that executes local functions like they're CGIs.
+
+  The contents of sys.modules will be preserved for local CGIs running this
+  dispatcher, but module hardening will still occur for any new imports. Thus,
+  be sure that any local CGIs have loaded all of their dependent modules
+  _before_ they are executed.
+  """
+
+  def __init__(self, module_dict, path_adjuster, cgi_func):
+    """Initializer.
+
+    Args:
+      module_dict: Passed to CGIDispatcher.
+      path_adjuster: Passed to CGIDispatcher.
+      cgi_func: Callable function taking no parameters that should be
+        executed in a CGI environment in the current process.
+    """
+    self._cgi_func = cgi_func
+
+    def curried_exec_script(*args, **kwargs):
+      cgi_func()
+      return False
+
+    def curried_exec_cgi(*args, **kwargs):
+      kwargs['exec_script'] = curried_exec_script
+      return ExecuteCGI(*args, **kwargs)
+
+    CGIDispatcher.__init__(self,
+                           module_dict,
+                           '',
+                           path_adjuster,
+                           exec_cgi=curried_exec_cgi)
+
+  def Dispatch(self, *args, **kwargs):
+    """Preserves sys.modules for CGIDispatcher.Dispatch."""
+    self._module_dict.update(sys.modules)
+    CGIDispatcher.Dispatch(self, *args, **kwargs)
+
+  def __str__(self):
+    """Returns a string representation of this dispatcher."""
+    return 'Local CGI dispatcher for %s' % self._cgi_func
+
+
+class PathAdjuster(object):
+  """Adjusts application file paths to paths relative to the application or
+  external library directories."""
+
+  def __init__(self, root_path):
+    """Initializer.
+
+    Args:
+      root_path: Path to the root of the application running on the server.
+    """
+    self._root_path = os.path.abspath(root_path)
+
+  def AdjustPath(self, path):
+    """Adjusts application file path to paths relative to the application or
+    external library directories.
+
+    Handler paths that start with $PYTHON_LIB will be converted to paths
+    relative to the google directory.
+
+    Args:
+      path: File path that should be adjusted.
+
+    Returns:
+      The adjusted path.
+    """
+    if path.startswith(PYTHON_LIB_VAR):
+      path = os.path.join(os.path.dirname(os.path.dirname(google.__file__)),
+                          path[len(PYTHON_LIB_VAR) + 1:])
+    else:
+      path = os.path.join(self._root_path, path)
+
+    return path
+
+
+class StaticFileMimeTypeMatcher(object):
+  """Computes mime type based on URLMap and file extension.
+
+  To determine the mime type, we first see if there is any mime-type property
+  on each URLMap entry. If non is specified, we use the mimetypes module to
+  guess the mime type from the file path extension, and use
+  application/octet-stream if we can't find the mimetype.
+  """
+
+  def __init__(self,
+               url_map_list,
+               path_adjuster):
+    """Initializer.
+
+    Args:
+      url_map_list: List of appinfo.URLMap objects.
+        If empty or None, then we always use the mime type chosen by the
+        mimetypes module.
+      path_adjuster: PathAdjuster object used to adjust application file paths.
+    """
+    self._patterns = []
+
+    if url_map_list:
+      for entry in url_map_list:
+        if entry.mime_type is None:
+          continue
+        handler_type = entry.GetHandlerType()
+        if handler_type not in (appinfo.STATIC_FILES, appinfo.STATIC_DIR):
+          continue
+
+        if handler_type == appinfo.STATIC_FILES:
+          regex = entry.upload
+        else:
+          static_dir = entry.static_dir
+          if static_dir[-1] == '/':
+            static_dir = static_dir[:-1]
+          regex = '/'.join((entry.static_dir, r'(.*)'))
+
+        adjusted_regex = r'^%s$' % path_adjuster.AdjustPath(regex)
+        try:
+          path_re = re.compile(adjusted_regex)
+        except re.error, e:
+          raise InvalidAppConfigError('regex does not compile: %s' % e)
+
+        self._patterns.append((path_re, entry.mime_type))
+
+  def GetMimeType(self, path):
+    """Returns the mime type that we should use when serving the specified file.
+
+    Args:
+      path: String containing the file's path on disk.
+
+    Returns:
+      String containing the mime type to use. Will be 'application/octet-stream'
+      if we have no idea what it should be.
+    """
+    for (path_re, mime_type) in self._patterns:
+      the_match = path_re.match(path)
+      if the_match:
+        return mime_type
+
+    filename, extension = os.path.splitext(path)
+    return mimetypes.types_map.get(extension, 'application/octet-stream')
+
+
+def ReadDataFile(data_path, openfile=file):
+  """Reads a file on disk, returning a corresponding HTTP status and data.
+
+  Args:
+    data_path: Path to the file on disk to read.
+    openfile: Used for dependency injection.
+
+  Returns:
+    Tuple (status, data) where status is an HTTP response code, and data is
+      the data read; will be an empty string if an error occurred or the
+      file was empty.
+  """
+  status = httplib.INTERNAL_SERVER_ERROR
+  data = ""
+
+  try:
+    data_file = openfile(data_path, 'rb')
+    try:
+      data = data_file.read()
+    finally:
+      data_file.close()
+      status = httplib.OK
+  except (OSError, IOError), e:
+    logging.error('Error encountered reading file "%s":\n%s', data_path, e)
+    if e.errno in FILE_MISSING_EXCEPTIONS:
+      status = httplib.NOT_FOUND
+    else:
+      status = httplib.FORBIDDEN
+
+  return status, data
+
+
+class FileDispatcher(URLDispatcher):
+  """Dispatcher that reads data files from disk."""
+
+  def __init__(self,
+               path_adjuster,
+               static_file_mime_type_matcher,
+               read_data_file=ReadDataFile):
+    """Initializer.
+
+    Args:
+      path_adjuster: Instance of PathAdjuster to use for finding absolute
+        paths of data files on disk.
+      static_file_mime_type_matcher: StaticFileMimeTypeMatcher object.
+      read_data_file: Used for dependency injection.
+    """
+    self._path_adjuster = path_adjuster
+    self._static_file_mime_type_matcher = static_file_mime_type_matcher
+    self._read_data_file = read_data_file
+
+  def Dispatch(self,
+               relative_url,
+               path,
+               headers,
+               infile,
+               outfile,
+               base_env_dict=None):
+    """Reads the file and returns the response status and data."""
+    full_path = self._path_adjuster.AdjustPath(path)
+    status, data = self._read_data_file(full_path)
+    content_type = self._static_file_mime_type_matcher.GetMimeType(full_path)
+
+    outfile.write('Status: %d\r\n' % status)
+    outfile.write('Content-type: %s\r\n' % content_type)
+    outfile.write('\r\n')
+    outfile.write(data)
+
+  def __str__(self):
+    """Returns a string representation of this dispatcher."""
+    return 'File dispatcher'
+
+
+def RewriteResponse(response_file):
+  """Interprets server-side headers and adjusts the HTTP response accordingly.
+
+  Handles the server-side 'status' header, which instructs the server to change
+  the HTTP response code accordingly. Handles the 'location' header, which
+  issues an HTTP 302 redirect to the client. Also corrects the 'content-length'
+  header to reflect actual content length in case extra information has been
+  appended to the response body.
+
+  If the 'status' header supplied by the client is invalid, this method will
+  set the response to a 500 with an error message as content.
+
+  Args:
+    response_file: File-like object containing the full HTTP response including
+      the response code, all headers, and the request body.
+
+  Returns:
+    Tuple (status_code, status_message, header, body) where:
+      status_code: Integer HTTP response status (e.g., 200, 302, 404, 500)
+      status_message: String containing an informational message about the
+        response code, possibly derived from the 'status' header, if supplied.
+      header: String containing the HTTP headers of the response, without
+        a trailing new-line (CRLF).
+      body: String containing the body of the response.
+  """
+  headers = mimetools.Message(response_file)
+
+  response_status = '%d Good to go' % httplib.OK
+
+  location_value = headers.getheader('location')
+  status_value = headers.getheader('status')
+  if status_value:
+    response_status = status_value
+    del headers['status']
+  elif location_value:
+    response_status = '%d Redirecting' % httplib.FOUND
+
+  if not 'Cache-Control' in headers:
+    headers['Cache-Control'] = 'no-cache'
+
+  status_parts = response_status.split(' ', 1)
+  status_code, status_message = (status_parts + [''])[:2]
+  try:
+    status_code = int(status_code)
+  except ValueError:
+    status_code = 500
+    body = 'Error: Invalid "status" header value returned.'
+  else:
+    body = response_file.read()
+
+  headers['content-length'] = str(len(body))
+
+  header_list = []
+  for header in headers.headers:
+    header = header.rstrip('\n')
+    header = header.rstrip('\r')
+    header_list.append(header)
+
+  header_data = '\r\n'.join(header_list) + '\r\n'
+  return status_code, status_message, header_data, body
+
+
+class ModuleManager(object):
+  """Manages loaded modules in the runtime.
+
+  Responsible for monitoring and reporting about file modification times.
+  Modules can be loaded from source or precompiled byte-code files.  When a
+  file has source code, the ModuleManager monitors the modification time of
+  the source file even if the module itself is loaded from byte-code.
+  """
+
+  def __init__(self, modules):
+    """Initializer.
+
+    Args:
+      modules: Dictionary containing monitored modules.
+    """
+    self._modules = modules
+    self._default_modules = self._modules.copy()
+
+    self._modification_times = {}
+
+  @staticmethod
+  def GetModuleFile(module, is_file=os.path.isfile):
+    """Helper method to try to determine modules source file.
+
+    Args:
+      module: Module object to get file for.
+      is_file: Function used to determine if a given path is a file.
+
+    Returns:
+      Path of the module's corresponding Python source file if it exists, or
+      just the module's compiled Python file. If the module has an invalid
+      __file__ attribute, None will be returned.
+      """
+    module_file = getattr(module, '__file__', None)
+    if not module_file or module_file == HardenedModulesHook.EMPTY_MODULE_FILE:
+      return None
+
+    source_file = module_file[:module_file.rfind('py') + 2]
+
+    if is_file(source_file):
+      return source_file
+    return module.__file__
+
+  def AreModuleFilesModified(self):
+    """Determines if any monitored files have been modified.
+
+    Returns:
+      True if one or more files have been modified, False otherwise.
+    """
+    for name, (mtime, fname) in self._modification_times.iteritems():
+      if name not in self._modules:
+        continue
+
+      module = self._modules[name]
+
+      if not os.path.isfile(fname):
+        return True
+
+      if mtime != os.path.getmtime(fname):
+        return True
+
+    return False
+
+  def UpdateModuleFileModificationTimes(self):
+    """Records the current modification times of all monitored modules.
+    """
+    self._modification_times.clear()
+    for name, module in self._modules.items():
+      if not isinstance(module, types.ModuleType):
+        continue
+      module_file = self.GetModuleFile(module)
+      if not module_file:
+        continue
+      try:
+        self._modification_times[name] = (os.path.getmtime(module_file),
+                                          module_file)
+      except OSError, e:
+        if e.errno not in FILE_MISSING_EXCEPTIONS:
+          raise e
+
+  def ResetModules(self):
+    """Clear modules so that when request is run they are reloaded."""
+    self._modules.clear()
+    self._modules.update(self._default_modules)
+
+
+def _ClearTemplateCache(module_dict=sys.modules):
+  """Clear template cache in webapp.template module.
+
+  Attempts to load template module.  Ignores failure.  If module loads, the
+  template cache is cleared.
+  """
+  template_module = module_dict.get('google.appengine.ext.webapp.template')
+  if template_module is not None:
+    template_module.template_cache.clear()
+
+
+def CreateRequestHandler(root_path, login_url, require_indexes=False):
+  """Creates a new BaseHTTPRequestHandler sub-class for use with the Python
+  BaseHTTPServer module's HTTP server.
+
+  Python's built-in HTTP server does not support passing context information
+  along to instances of its request handlers. This function gets around that
+  by creating a sub-class of the handler in a closure that has access to
+  this context information.
+
+  Args:
+    root_path: Path to the root of the application running on the server.
+    login_url: Relative URL which should be used for handling user logins.
+    require_indexes: True if index.yaml is read-only gospel; default False.
+
+  Returns:
+    Sub-class of BaseHTTPRequestHandler.
+  """
+  application_module_dict = SetupSharedModules(sys.modules)
+
+  if require_indexes:
+    index_yaml_updater = None
+  else:
+    index_yaml_updater = dev_appserver_index.IndexYamlUpdater(root_path)
+
+  class DevAppServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+    """Dispatches URLs using patterns from a URLMatcher, which is created by
+    loading an application's configuration file. Executes CGI scripts in the
+    local process so the scripts can use mock versions of APIs.
+
+    HTTP requests that correctly specify a user info cookie
+    (dev_appserver_login.COOKIE_NAME) will have the 'USER_EMAIL' environment
+    variable set accordingly. If the user is also an admin, the
+    'USER_IS_ADMIN' variable will exist and be set to '1'. If the user is not
+    logged in, 'USER_EMAIL' will be set to the empty string.
+
+    On each request, raises an InvalidAppConfigError exception if the
+    application configuration file in the directory specified by the root_path
+    argument is invalid.
+    """
+    server_version = 'Development/1.0'
+
+    module_dict = application_module_dict
+    module_manager = ModuleManager(application_module_dict)
+
+    def __init__(self, *args, **kwargs):
+      """Initializer.
+
+      Args:
+        args, kwargs: Positional and keyword arguments passed to the constructor
+          of the super class.
+      """
+      BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
+
+    def do_GET(self):
+      """Handle GET requests."""
+      self._HandleRequest()
+
+    def do_POST(self):
+      """Handles POST requests."""
+      self._HandleRequest()
+
+    def do_PUT(self):
+      """Handle PUT requests."""
+      self._HandleRequest()
+
+    def do_HEAD(self):
+      """Handle HEAD requests."""
+      self._HandleRequest()
+
+    def do_OPTIONS(self):
+      """Handles OPTIONS requests."""
+      self._HandleRequest()
+
+    def do_DELETE(self):
+      """Handle DELETE requests."""
+      self._HandleRequest()
+
+    def do_TRACE(self):
+      """Handles TRACE requests."""
+      self._HandleRequest()
+
+    def _HandleRequest(self):
+      """Handles any type of request and prints exceptions if they occur."""
+      server_name = self.headers.get('host') or self.server.server_name
+      server_name = server_name.split(':', 1)[0]
+
+      env_dict = {
+        'REQUEST_METHOD': self.command,
+        'REMOTE_ADDR': self.client_address[0],
+        'SERVER_SOFTWARE': self.server_version,
+        'SERVER_NAME': server_name,
+        'SERVER_PROTOCOL': self.protocol_version,
+        'SERVER_PORT': str(self.server.server_port),
+      }
+
+      full_url = GetFullURL(server_name, self.server.server_port, self.path)
+      if len(full_url) > MAX_URL_LENGTH:
+        msg = 'Requested URI too long: %s' % full_url
+        logging.error(msg)
+        self.send_response(httplib.REQUEST_URI_TOO_LONG, msg)
+        return
+
+      tbhandler = cgitb.Hook(file=self.wfile).handle
+      try:
+        if self.module_manager.AreModuleFilesModified():
+          self.module_manager.ResetModules()
+
+        implicit_matcher = CreateImplicitMatcher(self.module_dict,
+                                                 root_path,
+                                                 login_url)
+        config, explicit_matcher = LoadAppConfig(root_path, self.module_dict)
+        env_dict['CURRENT_VERSION_ID'] = config.version + ".1"
+        env_dict['APPLICATION_ID'] = config.application
+        dispatcher = MatcherDispatcher(login_url,
+                                       [implicit_matcher, explicit_matcher])
+
+        if require_indexes:
+          dev_appserver_index.SetupIndexes(config.application, root_path)
+
+        infile = cStringIO.StringIO(self.rfile.read(
+            int(self.headers.get('content-length', 0))))
+        outfile = cStringIO.StringIO()
+        try:
+          dispatcher.Dispatch(self.path,
+                              None,
+                              self.headers,
+                              infile,
+                              outfile,
+                              base_env_dict=env_dict)
+        finally:
+          self.module_manager.UpdateModuleFileModificationTimes()
+
+        outfile.flush()
+        outfile.seek(0)
+        status_code, status_message, header_data, body = RewriteResponse(outfile)
+
+      except yaml_errors.EventListenerError, e:
+        title = 'Fatal error when loading application configuration'
+        msg = '%s:\n%s' % (title, str(e))
+        logging.error(msg)
+        self.send_response(httplib.INTERNAL_SERVER_ERROR, title)
+        self.wfile.write('Content-Type: text/html\n\n')
+        self.wfile.write('<pre>%s</pre>' % cgi.escape(msg))
+      except:
+        msg = 'Exception encountered handling request'
+        logging.exception(msg)
+        self.send_response(httplib.INTERNAL_SERVER_ERROR, msg)
+        tbhandler()
+      else:
+        try:
+          self.send_response(status_code, status_message)
+          self.wfile.write(header_data)
+          self.wfile.write('\r\n')
+          if self.command != 'HEAD':
+            self.wfile.write(body)
+          elif body:
+            logging.warning('Dropping unexpected body in response '
+                            'to HEAD request')
+        except (IOError, OSError), e:
+          if e.errno != errno.EPIPE:
+            raise e
+        except socket.error, e:
+          if len(e.args) >= 1 and e.args[0] != errno.EPIPE:
+            raise e
+        else:
+          if index_yaml_updater is not None:
+            index_yaml_updater.UpdateIndexYaml()
+
+    def log_error(self, format, *args):
+      """Redirect error messages through the logging module."""
+      logging.error(format, *args)
+
+    def log_message(self, format, *args):
+      """Redirect log messages through the logging module."""
+      logging.info(format, *args)
+
+  return DevAppServerRequestHandler
+
+
+def ReadAppConfig(appinfo_path, parse_app_config=appinfo.LoadSingleAppInfo):
+  """Reads app.yaml file and returns its app id and list of URLMap instances.
+
+  Args:
+    appinfo_path: String containing the path to the app.yaml file.
+    parse_app_config: Used for dependency injection.
+
+  Returns:
+    AppInfoExternal instance.
+
+  Raises:
+    If the config file could not be read or the config does not contain any
+    URLMap instances, this function will raise an InvalidAppConfigError
+    exception.
+  """
+  try:
+    appinfo_file = file(appinfo_path, 'r')
+    try:
+      return parse_app_config(appinfo_file)
+    finally:
+      appinfo_file.close()
+  except IOError, e:
+    raise InvalidAppConfigError(
+      'Application configuration could not be read from "%s"' % appinfo_path)
+
+
+def CreateURLMatcherFromMaps(root_path,
+                             url_map_list,
+                             module_dict,
+                             create_url_matcher=URLMatcher,
+                             create_cgi_dispatcher=CGIDispatcher,
+                             create_file_dispatcher=FileDispatcher,
+                             create_path_adjuster=PathAdjuster):
+  """Creates a URLMatcher instance from URLMap.
+
+  Creates all of the correct URLDispatcher instances to handle the various
+  content types in the application configuration.
+
+  Args:
+    root_path: Path to the root of the application running on the server.
+    url_map_list: List of appinfo.URLMap objects to initialize this
+      matcher with. Can be an empty list if you would like to add patterns
+      manually.
+    module_dict: Dictionary in which application-loaded modules should be
+      preserved between requests. This dictionary must be separate from the
+      sys.modules dictionary.
+    create_url_matcher, create_cgi_dispatcher, create_file_dispatcher,
+    create_path_adjuster: Used for dependency injection.
+
+  Returns:
+    Instance of URLMatcher with the supplied URLMap objects properly loaded.
+  """
+  url_matcher = create_url_matcher()
+  path_adjuster = create_path_adjuster(root_path)
+  cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster)
+  file_dispatcher = create_file_dispatcher(path_adjuster,
+      StaticFileMimeTypeMatcher(url_map_list, path_adjuster))
+
+  for url_map in url_map_list:
+    admin_only = url_map.login == appinfo.LOGIN_ADMIN
+    requires_login = url_map.login == appinfo.LOGIN_REQUIRED or admin_only
+
+    handler_type = url_map.GetHandlerType()
+    if handler_type == appinfo.HANDLER_SCRIPT:
+      dispatcher = cgi_dispatcher
+    elif handler_type in (appinfo.STATIC_FILES, appinfo.STATIC_DIR):
+      dispatcher = file_dispatcher
+    else:
+      raise InvalidAppConfigError('Unknown handler type "%s"' % handler_type)
+
+    regex = url_map.url
+    path = url_map.GetHandler()
+    if handler_type == appinfo.STATIC_DIR:
+      if regex[-1] == r'/':
+        regex = regex[:-1]
+      if path[-1] == os.path.sep:
+        path = path[:-1]
+      regex = '/'.join((re.escape(regex), '(.*)'))
+      if os.path.sep == '\\':
+        backref = r'\\1'
+      else:
+        backref = r'\1'
+      path = os.path.normpath(path) + os.path.sep + backref
+
+    url_matcher.AddURL(regex,
+                       dispatcher,
+                       path,
+                       requires_login, admin_only)
+
+  return url_matcher
+
+
+def LoadAppConfig(root_path,
+                  module_dict,
+                  read_app_config=ReadAppConfig,
+                  create_matcher=CreateURLMatcherFromMaps):
+  """Creates a Matcher instance for an application configuration file.
+
+  Raises an InvalidAppConfigError exception if there is anything wrong with
+  the application configuration file.
+
+  Args:
+    root_path: Path to the root of the application to load.
+    module_dict: Dictionary in which application-loaded modules should be
+      preserved between requests. This dictionary must be separate from the
+      sys.modules dictionary.
+    read_url_map, create_matcher: Used for dependency injection.
+
+  Returns:
+     tuple: (AppInfoExternal, URLMatcher)
+  """
+
+  for appinfo_path in [os.path.join(root_path, 'app.yaml'),
+                       os.path.join(root_path, 'app.yml')]:
+
+    if os.path.isfile(appinfo_path):
+      try:
+        config = read_app_config(appinfo_path, appinfo.LoadSingleAppInfo)
+
+        matcher = create_matcher(root_path,
+                                 config.handlers,
+                                 module_dict)
+
+        return (config, matcher)
+      except gexcept.AbstractMethod:
+        pass
+
+  raise AppConfigNotFoundError
+
+
+def SetupStubs(app_id, **config):
+  """Sets up testing stubs of APIs.
+
+  Args:
+    app_id: Application ID being served.
+
+  Keywords:
+    login_url: Relative URL which should be used for handling user login/logout.
+    datastore_path: Path to the file to store Datastore file stub data in.
+    history_path: Path to the file to store Datastore history in.
+    clear_datastore: If the datastore and history should be cleared on startup.
+    smtp_host: SMTP host used for sending test mail.
+    smtp_port: SMTP port.
+    smtp_user: SMTP user.
+    smtp_password: SMTP password.
+    enable_sendmail: Whether to use sendmail as an alternative to SMTP.
+    show_mail_body: Whether to log the body of emails.
+    remove: Used for dependency injection.
+  """
+  login_url = config['login_url']
+  datastore_path = config['datastore_path']
+  history_path = config['history_path']
+  clear_datastore = config['clear_datastore']
+  require_indexes = config.get('require_indexes', False)
+  smtp_host = config.get('smtp_host', None)
+  smtp_port = config.get('smtp_port', 25)
+  smtp_user = config.get('smtp_user', '')
+  smtp_password = config.get('smtp_password', '')
+  enable_sendmail = config.get('enable_sendmail', False)
+  show_mail_body = config.get('show_mail_body', False)
+  remove = config.get('remove', os.remove)
+
+  if clear_datastore:
+    for path in (datastore_path, history_path):
+      if os.path.lexists(path):
+        logging.info('Attempting to remove file at %s', path)
+        try:
+          remove(path)
+        except OSError, e:
+          logging.warning('Removing file failed: %s', e)
+
+  apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()
+
+  datastore = datastore_file_stub.DatastoreFileStub(
+      app_id, datastore_path, history_path, require_indexes=require_indexes)
+  apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore)
+
+  fixed_login_url = '%s?%s=%%s' % (login_url,
+                                   dev_appserver_login.CONTINUE_PARAM)
+  fixed_logout_url = '%s&%s' % (fixed_login_url,
+                                dev_appserver_login.LOGOUT_PARAM)
+
+  apiproxy_stub_map.apiproxy.RegisterStub(
+    'user',
+    user_service_stub.UserServiceStub(login_url=fixed_login_url,
+                                      logout_url=fixed_logout_url))
+
+  apiproxy_stub_map.apiproxy.RegisterStub(
+    'urlfetch',
+    urlfetch_stub.URLFetchServiceStub())
+
+  apiproxy_stub_map.apiproxy.RegisterStub(
+    'mail',
+    mail_stub.MailServiceStub(smtp_host,
+                              smtp_port,
+                              smtp_user,
+                              smtp_password,
+                              enable_sendmail=enable_sendmail,
+                              show_mail_body=show_mail_body))
+
+  apiproxy_stub_map.apiproxy.RegisterStub(
+    'memcache',
+    memcache_stub.MemcacheServiceStub())
+
+  try:
+    from google.appengine.api.images import images_stub
+    apiproxy_stub_map.apiproxy.RegisterStub(
+      'images',
+      images_stub.ImagesServiceStub())
+  except ImportError, e:
+    logging.warning('Could not initialize images API; you are likely missing '
+                    'the Python "PIL" module. ImportError: %s', e)
+    from google.appengine.api.images import images_not_implemented_stub
+    apiproxy_stub_map.apiproxy.RegisterStub('images',
+      images_not_implemented_stub.ImagesNotImplementedServiceStub())
+
+
+def CreateImplicitMatcher(module_dict,
+                          root_path,
+                          login_url,
+                          create_path_adjuster=PathAdjuster,
+                          create_local_dispatcher=LocalCGIDispatcher,
+                          create_cgi_dispatcher=CGIDispatcher):
+  """Creates a URLMatcher instance that handles internal URLs.
+
+  Used to facilitate handling user login/logout, debugging, info about the
+  currently running app, etc.
+
+  Args:
+    module_dict: Dictionary in the form used by sys.modules.
+    root_path: Path to the root of the application.
+    login_url: Relative URL which should be used for handling user login/logout.
+    create_local_dispatcher: Used for dependency injection.
+
+  Returns:
+    Instance of URLMatcher with appropriate dispatchers.
+  """
+  url_matcher = URLMatcher()
+  path_adjuster = create_path_adjuster(root_path)
+
+  login_dispatcher = create_local_dispatcher(sys.modules, path_adjuster,
+                                             dev_appserver_login.main)
+  url_matcher.AddURL(login_url,
+                     login_dispatcher,
+                     '',
+                     False,
+                     False)
+
+
+  admin_dispatcher = create_cgi_dispatcher(module_dict, root_path,
+                                           path_adjuster)
+  url_matcher.AddURL('/_ah/admin(?:/.*)?',
+                     admin_dispatcher,
+                     DEVEL_CONSOLE_PATH,
+                     False,
+                     False)
+
+  return url_matcher
+
+
+def SetupTemplates(template_dir):
+  """Reads debugging console template files and initializes the console.
+
+  Does nothing if templates have already been initialized.
+
+  Args:
+    template_dir: Path to the directory containing the templates files.
+
+  Raises:
+    OSError or IOError if any of the template files could not be read.
+  """
+  if ApplicationLoggingHandler.AreTemplatesInitialized():
+    return
+
+  try:
+    header = open(os.path.join(template_dir, HEADER_TEMPLATE)).read()
+    script = open(os.path.join(template_dir, SCRIPT_TEMPLATE)).read()
+    middle = open(os.path.join(template_dir, MIDDLE_TEMPLATE)).read()
+    footer = open(os.path.join(template_dir, FOOTER_TEMPLATE)).read()
+  except (OSError, IOError):
+    logging.error('Could not read template files from %s', template_dir)
+    raise
+
+  ApplicationLoggingHandler.InitializeTemplates(header, script, middle, footer)
+
+
+def CreateServer(root_path,
+                 login_url,
+                 port,
+                 template_dir,
+                 serve_address='',
+                 require_indexes=False,
+                 python_path_list=sys.path):
+  """Creates an new HTTPServer for an application.
+
+  Args:
+    root_path: String containing the path to the root directory of the
+      application where the app.yaml file is.
+    login_url: Relative URL which should be used for handling user login/logout.
+    port: Port to start the application server on.
+    template_dir: Path to the directory in which the debug console templates
+      are stored.
+    serve_address: Address on which the server should serve.
+    require_indexes: True if index.yaml is read-only gospel; default False.
+    python_path_list: Used for dependency injection.
+
+  Returns:
+    Instance of BaseHTTPServer.HTTPServer that's ready to start accepting.
+  """
+  absolute_root_path = os.path.abspath(root_path)
+
+  SetupTemplates(template_dir)
+  FakeFile.SetAllowedPaths([absolute_root_path,
+                            os.path.dirname(os.path.dirname(google.__file__)),
+                            template_dir])
+
+  handler_class = CreateRequestHandler(absolute_root_path, login_url,
+                                       require_indexes)
+
+  if absolute_root_path not in python_path_list:
+    python_path_list.insert(0, absolute_root_path)
+
+  return BaseHTTPServer.HTTPServer((serve_address, port), handler_class)