thirdparty/google_appengine/google/appengine/tools/dev_appserver.py
changeset 2413 d0b7dac5325c
parent 2309 be1b94099f2d
child 2864 2e0b0af889be
equal deleted inserted replaced
2412:c61d96e72e6f 2413:d0b7dac5325c
    34 from google.appengine.tools import os_compat
    34 from google.appengine.tools import os_compat
    35 
    35 
    36 import __builtin__
    36 import __builtin__
    37 import BaseHTTPServer
    37 import BaseHTTPServer
    38 import Cookie
    38 import Cookie
       
    39 import base64
    39 import cStringIO
    40 import cStringIO
    40 import cgi
    41 import cgi
    41 import cgitb
    42 import cgitb
    42 
    43 
    43 try:
    44 try:
    86 from google.appengine.api import mail_stub
    87 from google.appengine.api import mail_stub
    87 from google.appengine.api import urlfetch_stub
    88 from google.appengine.api import urlfetch_stub
    88 from google.appengine.api import user_service_stub
    89 from google.appengine.api import user_service_stub
    89 from google.appengine.api import yaml_errors
    90 from google.appengine.api import yaml_errors
    90 from google.appengine.api.capabilities import capability_stub
    91 from google.appengine.api.capabilities import capability_stub
       
    92 from google.appengine.api.labs.taskqueue import taskqueue_stub
    91 from google.appengine.api.memcache import memcache_stub
    93 from google.appengine.api.memcache import memcache_stub
    92 
    94 
    93 from google.appengine import dist
    95 from google.appengine import dist
    94 
    96 
    95 from google.appengine.tools import dev_appserver_index
    97 from google.appengine.tools import dev_appserver_index
   125 MAX_RUNTIME_RESPONSE_SIZE = 10 << 20
   127 MAX_RUNTIME_RESPONSE_SIZE = 10 << 20
   126 
   128 
   127 MAX_REQUEST_SIZE = 10 * 1024 * 1024
   129 MAX_REQUEST_SIZE = 10 * 1024 * 1024
   128 
   130 
   129 API_VERSION = '1'
   131 API_VERSION = '1'
       
   132 
       
   133 SITE_PACKAGES = os.path.normcase(os.path.join(os.path.dirname(os.__file__),
       
   134                                               'site-packages'))
   130 
   135 
   131 
   136 
   132 class Error(Exception):
   137 class Error(Exception):
   133   """Base-class for exceptions in this module."""
   138   """Base-class for exceptions in this module."""
   134 
   139 
   515 
   520 
   516 
   521 
   517 _IGNORE_REQUEST_HEADERS = frozenset(['content-type', 'content-length',
   522 _IGNORE_REQUEST_HEADERS = frozenset(['content-type', 'content-length',
   518                                      'accept-encoding', 'transfer-encoding'])
   523                                      'accept-encoding', 'transfer-encoding'])
   519 
   524 
       
   525 
   520 def SetupEnvironment(cgi_path,
   526 def SetupEnvironment(cgi_path,
   521                      relative_url,
   527                      relative_url,
   522                      headers,
   528                      headers,
       
   529                      infile,
   523                      split_url=SplitURL,
   530                      split_url=SplitURL,
   524                      get_user_info=dev_appserver_login.GetUserInfo):
   531                      get_user_info=dev_appserver_login.GetUserInfo):
   525   """Sets up environment variables for a CGI.
   532   """Sets up environment variables for a CGI.
   526 
   533 
   527   Args:
   534   Args:
   528     cgi_path: Full file-system path to the CGI being executed.
   535     cgi_path: Full file-system path to the CGI being executed.
   529     relative_url: Relative URL used to access the CGI.
   536     relative_url: Relative URL used to access the CGI.
   530     headers: Instance of mimetools.Message containing request headers.
   537     headers: Instance of mimetools.Message containing request headers.
       
   538     infile: File-like object with input data from the request.
   531     split_url, get_user_info: Used for dependency injection.
   539     split_url, get_user_info: Used for dependency injection.
   532 
   540 
   533   Returns:
   541   Returns:
   534     Dictionary containing CGI environment variables.
   542     Dictionary containing CGI environment variables.
   535   """
   543   """
   556     if key in _IGNORE_REQUEST_HEADERS:
   564     if key in _IGNORE_REQUEST_HEADERS:
   557       continue
   565       continue
   558     adjusted_name = key.replace('-', '_').upper()
   566     adjusted_name = key.replace('-', '_').upper()
   559     env['HTTP_' + adjusted_name] = ', '.join(headers.getheaders(key))
   567     env['HTTP_' + adjusted_name] = ', '.join(headers.getheaders(key))
   560 
   568 
       
   569   PAYLOAD_HEADER = 'HTTP_X_APPENGINE_DEVELOPMENT_PAYLOAD'
       
   570   if PAYLOAD_HEADER in env:
       
   571     del env[PAYLOAD_HEADER]
       
   572     new_data = base64.standard_b64decode(infile.getvalue())
       
   573     infile.seek(0)
       
   574     infile.truncate()
       
   575     infile.write(new_data)
       
   576     infile.seek(0)
       
   577     env['CONTENT_LENGTH'] = str(len(new_data))
       
   578 
   561   return env
   579   return env
   562 
   580 
   563 
   581 
   564 def NotImplementedFake(*args, **kwargs):
   582 def NotImplementedFake(*args, **kwargs):
   565   """Fake for methods/functions that are not implemented in the production
   583   """Fake for methods/functions that are not implemented in the production
   803   NOT_ALLOWED_DIRS = set([
   821   NOT_ALLOWED_DIRS = set([
   804 
   822 
   805 
   823 
   806 
   824 
   807 
   825 
   808     os.path.normcase(os.path.join(os.path.dirname(os.__file__),
   826     SITE_PACKAGES,
   809                                   'site-packages'))
       
   810   ])
   827   ])
   811 
   828 
   812   ALLOWED_SITE_PACKAGE_DIRS = set(
   829   ALLOWED_SITE_PACKAGE_DIRS = set(
   813     os.path.normcase(os.path.abspath(os.path.join(
   830     os.path.normcase(os.path.abspath(os.path.join(SITE_PACKAGES, path)))
   814       os.path.dirname(os.__file__), 'site-packages', path)))
       
   815     for path in [
   831     for path in [
   816 
   832 
   817   ])
   833   ])
   818 
   834 
   819   ALLOWED_SITE_PACKAGE_FILES = set(
   835   ALLOWED_SITE_PACKAGE_FILES = set(
   903     """
   919     """
   904     FakeFile._allow_skipped_files = allow_skipped_files
   920     FakeFile._allow_skipped_files = allow_skipped_files
   905     FakeFile._availability_cache = {}
   921     FakeFile._availability_cache = {}
   906 
   922 
   907   @staticmethod
   923   @staticmethod
       
   924   def SetAllowedModule(name):
       
   925     """Allow the use of a module based on where it is located.
       
   926 
       
   927     Meant to be used by use_library() so that it has a link back into the
       
   928     trusted part of the interpreter.
       
   929 
       
   930     Args:
       
   931       name: Name of the module to allow.
       
   932     """
       
   933     stream, pathname, description = imp.find_module(name)
       
   934     pathname = os.path.normcase(os.path.abspath(pathname))
       
   935     if stream:
       
   936       stream.close()
       
   937       FakeFile.ALLOWED_FILES.add(pathname)
       
   938       FakeFile.ALLOWED_FILES.add(os.path.realpath(pathname))
       
   939     else:
       
   940       assert description[2] == imp.PKG_DIRECTORY
       
   941       if pathname.startswith(SITE_PACKAGES):
       
   942         FakeFile.ALLOWED_SITE_PACKAGE_DIRS.add(pathname)
       
   943         FakeFile.ALLOWED_SITE_PACKAGE_DIRS.add(os.path.realpath(pathname))
       
   944       else:
       
   945         FakeFile.ALLOWED_DIRS.add(pathname)
       
   946         FakeFile.ALLOWED_DIRS.add(os.path.realpath(pathname))
       
   947 
       
   948   @staticmethod
   908   def SetSkippedFiles(skip_files):
   949   def SetSkippedFiles(skip_files):
   909     """Sets which files in the application directory are to be ignored.
   950     """Sets which files in the application directory are to be ignored.
   910 
   951 
   911     Must be called at least once before any file objects are created in the
   952     Must be called at least once before any file objects are created in the
   912     hardened environment.
   953     hardened environment.
  1020 
  1061 
  1021     if not FakeFile.IsFileAccessible(filename):
  1062     if not FakeFile.IsFileAccessible(filename):
  1022       raise IOError(errno.EACCES, 'file not accessible', filename)
  1063       raise IOError(errno.EACCES, 'file not accessible', filename)
  1023 
  1064 
  1024     super(FakeFile, self).__init__(filename, mode, bufsize, **kwargs)
  1065     super(FakeFile, self).__init__(filename, mode, bufsize, **kwargs)
       
  1066 
       
  1067 
       
  1068 from google.appengine.dist import _library
       
  1069 _library.SetAllowedModule = FakeFile.SetAllowedModule
  1025 
  1070 
  1026 
  1071 
  1027 class RestrictedPathFunction(object):
  1072 class RestrictedPathFunction(object):
  1028   """Enforces access restrictions for functions that have a file or
  1073   """Enforces access restrictions for functions that have a file or
  1029   directory path as their first argument."""
  1074   directory path as their first argument."""
  2051 
  2096 
  2052   try:
  2097   try:
  2053     ClearAllButEncodingsModules(sys.modules)
  2098     ClearAllButEncodingsModules(sys.modules)
  2054     sys.modules.update(module_dict)
  2099     sys.modules.update(module_dict)
  2055     sys.argv = [cgi_path]
  2100     sys.argv = [cgi_path]
  2056     sys.stdin = infile
  2101     sys.stdin = cStringIO.StringIO(infile.getvalue())
  2057     sys.stdout = outfile
  2102     sys.stdout = outfile
  2058     os.environ.clear()
  2103     os.environ.clear()
  2059     os.environ.update(env)
  2104     os.environ.update(env)
  2060     before_path = sys.path[:]
  2105     before_path = sys.path[:]
  2061     cgi_dir = os.path.normpath(os.path.dirname(cgi_path))
  2106     cgi_dir = os.path.normpath(os.path.dirname(cgi_path))
  2151     try:
  2196     try:
  2152       env = {}
  2197       env = {}
  2153       if base_env_dict:
  2198       if base_env_dict:
  2154         env.update(base_env_dict)
  2199         env.update(base_env_dict)
  2155       cgi_path = self._path_adjuster.AdjustPath(path)
  2200       cgi_path = self._path_adjuster.AdjustPath(path)
  2156       env.update(self._setup_env(cgi_path, relative_url, headers))
  2201       env.update(self._setup_env(cgi_path, relative_url, headers, infile))
  2157       self._exec_cgi(self._root_path,
  2202       self._exec_cgi(self._root_path,
  2158                      path,
  2203                      path,
  2159                      cgi_path,
  2204                      cgi_path,
  2160                      env,
  2205                      env,
  2161                      infile,
  2206                      infile,
  2853                                        [implicit_matcher, explicit_matcher])
  2898                                        [implicit_matcher, explicit_matcher])
  2854 
  2899 
  2855         if require_indexes:
  2900         if require_indexes:
  2856           dev_appserver_index.SetupIndexes(config.application, root_path)
  2901           dev_appserver_index.SetupIndexes(config.application, root_path)
  2857 
  2902 
  2858         infile = cStringIO.StringIO(self.rfile.read(
  2903         infile = cStringIO.StringIO()
       
  2904         infile.write(self.rfile.read(
  2859             int(self.headers.get('content-length', 0))))
  2905             int(self.headers.get('content-length', 0))))
       
  2906         infile.seek(0)
  2860 
  2907 
  2861         request_size = len(infile.getvalue())
  2908         request_size = len(infile.getvalue())
  2862         if request_size > MAX_REQUEST_SIZE:
  2909         if request_size > MAX_REQUEST_SIZE:
  2863           msg = ('HTTP request was too large: %d.  The limit is: %d.'
  2910           msg = ('HTTP request was too large: %d.  The limit is: %d.'
  2864                  % (request_size, MAX_REQUEST_SIZE))
  2911                  % (request_size, MAX_REQUEST_SIZE))
  3151 
  3198 
  3152   Args:
  3199   Args:
  3153     app_id: Application ID being served.
  3200     app_id: Application ID being served.
  3154 
  3201 
  3155   Keywords:
  3202   Keywords:
       
  3203     root_path: Root path to the directory of the application which should
       
  3204         contain the app.yaml, indexes.yaml, and queues.yaml files.
  3156     login_url: Relative URL which should be used for handling user login/logout.
  3205     login_url: Relative URL which should be used for handling user login/logout.
  3157     datastore_path: Path to the file to store Datastore file stub data in.
  3206     datastore_path: Path to the file to store Datastore file stub data in.
  3158     history_path: Path to the file to store Datastore history in.
  3207     history_path: Path to the file to store Datastore history in.
  3159     clear_datastore: If the datastore and history should be cleared on startup.
  3208     clear_datastore: If the datastore and history should be cleared on startup.
  3160     smtp_host: SMTP host used for sending test mail.
  3209     smtp_host: SMTP host used for sending test mail.
  3166     remove: Used for dependency injection.
  3215     remove: Used for dependency injection.
  3167     trusted: True if this app can access data belonging to other apps.  This
  3216     trusted: True if this app can access data belonging to other apps.  This
  3168       behavior is different from the real app server and should be left False
  3217       behavior is different from the real app server and should be left False
  3169       except for advanced uses of dev_appserver.
  3218       except for advanced uses of dev_appserver.
  3170   """
  3219   """
       
  3220   root_path = config.get('root_path', None)
  3171   login_url = config['login_url']
  3221   login_url = config['login_url']
  3172   datastore_path = config['datastore_path']
  3222   datastore_path = config['datastore_path']
  3173   history_path = config['history_path']
  3223   history_path = config['history_path']
  3174   clear_datastore = config['clear_datastore']
  3224   clear_datastore = config['clear_datastore']
  3175   require_indexes = config.get('require_indexes', False)
  3225   require_indexes = config.get('require_indexes', False)
  3229 
  3279 
  3230   apiproxy_stub_map.apiproxy.RegisterStub(
  3280   apiproxy_stub_map.apiproxy.RegisterStub(
  3231     'capability_service',
  3281     'capability_service',
  3232     capability_stub.CapabilityServiceStub())
  3282     capability_stub.CapabilityServiceStub())
  3233 
  3283 
       
  3284   apiproxy_stub_map.apiproxy.RegisterStub(
       
  3285     'taskqueue',
       
  3286     taskqueue_stub.TaskQueueServiceStub(root_path=root_path))
       
  3287 
  3234 
  3288 
  3235   try:
  3289   try:
  3236     from google.appengine.api.images import images_stub
  3290     from google.appengine.api.images import images_stub
  3237     apiproxy_stub_map.apiproxy.RegisterStub(
  3291     apiproxy_stub_map.apiproxy.RegisterStub(
  3238       'images',
  3292       'images',
  3273   url_matcher.AddURL(login_url,
  3327   url_matcher.AddURL(login_url,
  3274                      login_dispatcher,
  3328                      login_dispatcher,
  3275                      '',
  3329                      '',
  3276                      False,
  3330                      False,
  3277                      False)
  3331                      False)
  3278 
       
  3279 
  3332 
  3280   admin_dispatcher = create_cgi_dispatcher(module_dict, root_path,
  3333   admin_dispatcher = create_cgi_dispatcher(module_dict, root_path,
  3281                                            path_adjuster)
  3334                                            path_adjuster)
  3282   url_matcher.AddURL('/_ah/admin(?:/.*)?',
  3335   url_matcher.AddURL('/_ah/admin(?:/.*)?',
  3283                      admin_dispatcher,
  3336                      admin_dispatcher,