thirdparty/google_appengine/google/appengine/tools/dev_appserver.py
changeset 1278 a7766286a7be
parent 828 f5fd65cc3bf3
child 2172 ac7bd3b467ff
equal deleted inserted replaced
1277:5c931bd3dc1e 1278:a7766286a7be
    81 from google.appengine.api import user_service_stub
    81 from google.appengine.api import user_service_stub
    82 from google.appengine.api import yaml_errors
    82 from google.appengine.api import yaml_errors
    83 from google.appengine.api.capabilities import capability_stub
    83 from google.appengine.api.capabilities import capability_stub
    84 from google.appengine.api.memcache import memcache_stub
    84 from google.appengine.api.memcache import memcache_stub
    85 
    85 
       
    86 from google.appengine import dist
       
    87 
    86 from google.appengine.tools import dev_appserver_index
    88 from google.appengine.tools import dev_appserver_index
    87 from google.appengine.tools import dev_appserver_login
    89 from google.appengine.tools import dev_appserver_login
    88 
    90 
    89 
    91 
    90 PYTHON_LIB_VAR = '$PYTHON_LIB'
    92 PYTHON_LIB_VAR = '$PYTHON_LIB'
   111                        ('.rss', 'application/rss+xml'),
   113                        ('.rss', 'application/rss+xml'),
   112                        ('.text', 'text/plain'),
   114                        ('.text', 'text/plain'),
   113                        ('.wbmp', 'image/vnd.wap.wbmp')):
   115                        ('.wbmp', 'image/vnd.wap.wbmp')):
   114   mimetypes.add_type(mime_type, ext)
   116   mimetypes.add_type(mime_type, ext)
   115 
   117 
   116 MAX_RUNTIME_RESPONSE_SIZE = 1 << 20
   118 MAX_RUNTIME_RESPONSE_SIZE = 10 << 20
   117 
   119 
   118 MAX_REQUEST_SIZE = 10 * 1024 * 1024
   120 MAX_REQUEST_SIZE = 10 * 1024 * 1024
       
   121 
       
   122 API_VERSION = '1'
   119 
   123 
   120 
   124 
   121 class Error(Exception):
   125 class Error(Exception):
   122   """Base-class for exceptions in this module."""
   126   """Base-class for exceptions in this module."""
   123 
   127 
   191       headers: Instance of mimetools.Message with headers from the request.
   195       headers: Instance of mimetools.Message with headers from the request.
   192       infile: File-like object with input data from the request.
   196       infile: File-like object with input data from the request.
   193       outfile: File-like object where output data should be written.
   197       outfile: File-like object where output data should be written.
   194       base_env_dict: Dictionary of CGI environment parameters if available.
   198       base_env_dict: Dictionary of CGI environment parameters if available.
   195         Defaults to None.
   199         Defaults to None.
       
   200 
       
   201     Returns:
       
   202       None if request handling is complete.
       
   203       Tuple (path, headers, input_file) for an internal redirect:
       
   204         path: Path of URL to redirect to.
       
   205         headers: Headers to send to other dispatcher.
       
   206         input_file: New input to send to new dispatcher.
   196     """
   207     """
   197     raise NotImplementedError
   208     raise NotImplementedError
       
   209 
       
   210   def EndRedirect(self, dispatched_output, original_output):
       
   211     """Process the end of an internal redirect.
       
   212 
       
   213     This method is called after all subsequent dispatch requests have finished.
       
   214     By default the output from the dispatched process is copied to the original.
       
   215 
       
   216     This will not be called on dispatchers that do not return an internal
       
   217     redirect.
       
   218 
       
   219     Args:
       
   220       dispatched_output: StringIO buffer containing the results from the
       
   221        dispatched
       
   222     """
       
   223     original_output.write(dispatched_output.read())
   198 
   224 
   199 
   225 
   200 class URLMatcher(object):
   226 class URLMatcher(object):
   201   """Matches an arbitrary URL using a list of URL patterns from an application.
   227   """Matches an arbitrary URL using a list of URL patterns from an application.
   202 
   228 
   344                       '\r\n'
   370                       '\r\n'
   345                       'Current logged in user %s is not '
   371                       'Current logged in user %s is not '
   346                       'authorized to view this page.'
   372                       'authorized to view this page.'
   347                       % (httplib.FORBIDDEN, email))
   373                       % (httplib.FORBIDDEN, email))
   348       else:
   374       else:
   349         dispatcher.Dispatch(relative_url,
   375         forward = dispatcher.Dispatch(relative_url,
   350                             matched_path,
   376                                       matched_path,
   351                             headers,
   377                                       headers,
   352                             infile,
   378                                       infile,
   353                             outfile,
   379                                       outfile,
   354                             base_env_dict=base_env_dict)
   380                                       base_env_dict=base_env_dict)
       
   381 
       
   382         if forward:
       
   383           new_path, new_headers, new_input = forward
       
   384           logging.info('Internal redirection to %s' % new_path)
       
   385           new_outfile = cStringIO.StringIO()
       
   386           self.Dispatch(new_path,
       
   387                         None,
       
   388                         new_headers,
       
   389                         new_input,
       
   390                         new_outfile,
       
   391                         dict(base_env_dict))
       
   392           new_outfile.seek(0)
       
   393           dispatcher.EndRedirect(new_outfile, outfile)
   355 
   394 
   356       return
   395       return
   357 
   396 
   358     outfile.write('Status: %d URL did not match\r\n'
   397     outfile.write('Status: %d URL did not match\r\n'
   359                   '\r\n'
   398                   '\r\n'
   512     env['HTTP_' + adjusted_name] = ', '.join(headers.getheaders(key))
   551     env['HTTP_' + adjusted_name] = ', '.join(headers.getheaders(key))
   513 
   552 
   514   return env
   553   return env
   515 
   554 
   516 
   555 
   517 def FakeTemporaryFile(*args, **kwargs):
       
   518   """Fake for tempfile.TemporaryFile that just uses StringIO."""
       
   519   return cStringIO.StringIO()
       
   520 
       
   521 
       
   522 def NotImplementedFake(*args, **kwargs):
   556 def NotImplementedFake(*args, **kwargs):
   523   """Fake for methods/functions that are not implemented in the production
   557   """Fake for methods/functions that are not implemented in the production
   524   environment.
   558   environment.
   525   """
   559   """
   526   raise NotImplementedError("This class/method is not available.")
   560   raise NotImplementedError("This class/method is not available.")
   575 def FakeUname():
   609 def FakeUname():
   576   """Fake version of os.uname."""
   610   """Fake version of os.uname."""
   577   return ('Linux', '', '', '', '')
   611   return ('Linux', '', '', '', '')
   578 
   612 
   579 
   613 
       
   614 def FakeUnlink(path):
       
   615   """Fake version of os.unlink."""
       
   616   if os.path.isdir(path):
       
   617     raise OSError(2, "Is a directory", path)
       
   618   else:
       
   619     raise OSError(1, "Operation not permitted", path)
       
   620 
       
   621 
       
   622 def FakeReadlink(path):
       
   623   """Fake version of os.readlink."""
       
   624   raise OSError(22, "Invalid argument", path)
       
   625 
       
   626 
       
   627 def FakeAccess(path, mode):
       
   628   """Fake version of os.access where only reads are supported."""
       
   629   if not os.path.exists(path) or mode != os.R_OK:
       
   630     return False
       
   631   else:
       
   632     return True
       
   633 
       
   634 
   580 def FakeSetLocale(category, value=None, original_setlocale=locale.setlocale):
   635 def FakeSetLocale(category, value=None, original_setlocale=locale.setlocale):
   581   """Fake version of locale.setlocale that only supports the default."""
   636   """Fake version of locale.setlocale that only supports the default."""
   582   if value not in (None, '', 'C', 'POSIX'):
   637   if value not in (None, '', 'C', 'POSIX'):
   583     raise locale.Error, 'locale emulation only supports "C" locale'
   638     raise locale.Error, 'locale emulation only supports "C" locale'
   584   return original_setlocale(category, 'C')
   639   return original_setlocale(category, 'C')
   713       os.path.dirname(os.__file__), 'site-packages', path)))
   768       os.path.dirname(os.__file__), 'site-packages', path)))
   714     for path in [
   769     for path in [
   715 
   770 
   716   ])
   771   ])
   717 
   772 
       
   773   _original_file = file
       
   774 
       
   775   _root_path = None
   718   _application_paths = None
   776   _application_paths = None
   719   _original_file = file
   777   _skip_files = None
       
   778   _static_file_config_matcher = None
       
   779 
       
   780   _availability_cache = {}
   720 
   781 
   721   @staticmethod
   782   @staticmethod
   722   def SetAllowedPaths(application_paths):
   783   def SetAllowedPaths(root_path, application_paths):
   723     """Sets the root path of the application that is currently running.
   784     """Configures which paths are allowed to be accessed.
   724 
   785 
   725     Must be called at least once before any file objects are created in the
   786     Must be called at least once before any file objects are created in the
   726     hardened environment.
   787     hardened environment.
   727 
   788 
   728     Args:
   789     Args:
   729       root_path: Path to the root of the application.
   790       root_path: Absolute path to the root of the application.
       
   791       application_paths: List of additional paths that the application may
       
   792                          access, this must include the App Engine runtime but
       
   793                          not the Python library directories.
   730     """
   794     """
   731     FakeFile._application_paths = (set(os.path.realpath(path)
   795     FakeFile._application_paths = (set(os.path.realpath(path)
   732                                        for path in application_paths) |
   796                                        for path in application_paths) |
   733                                    set(os.path.abspath(path)
   797                                    set(os.path.abspath(path)
   734                                        for path in application_paths))
   798                                        for path in application_paths))
       
   799     FakeFile._application_paths.add(root_path)
       
   800 
       
   801     FakeFile._root_path = os.path.join(root_path, '')
       
   802 
       
   803     FakeFile._availability_cache = {}
       
   804 
       
   805   @staticmethod
       
   806   def SetSkippedFiles(skip_files):
       
   807     """Sets which files in the application directory are to be ignored.
       
   808 
       
   809     Must be called at least once before any file objects are created in the
       
   810     hardened environment.
       
   811 
       
   812     Must be called whenever the configuration was updated.
       
   813 
       
   814     Args:
       
   815       skip_files: Object with .match() method (e.g. compiled regexp).
       
   816     """
       
   817     FakeFile._skip_files = skip_files
       
   818     FakeFile._availability_cache = {}
       
   819 
       
   820   @staticmethod
       
   821   def SetStaticFileConfigMatcher(static_file_config_matcher):
       
   822     """Sets StaticFileConfigMatcher instance for checking if a file is static.
       
   823 
       
   824     Must be called at least once before any file objects are created in the
       
   825     hardened environment.
       
   826 
       
   827     Must be called whenever the configuration was updated.
       
   828 
       
   829     Args:
       
   830       static_file_config_matcher: StaticFileConfigMatcher instance.
       
   831     """
       
   832     FakeFile._static_file_config_matcher = static_file_config_matcher
       
   833     FakeFile._availability_cache = {}
   735 
   834 
   736   @staticmethod
   835   @staticmethod
   737   def IsFileAccessible(filename, normcase=os.path.normcase):
   836   def IsFileAccessible(filename, normcase=os.path.normcase):
   738     """Determines if a file's path is accessible.
   837     """Determines if a file's path is accessible.
   739 
   838 
   740     SetAllowedPaths() must be called before this method or else all file
   839     SetAllowedPaths(), SetSkippedFiles() and SetStaticFileConfigMatcher() must
   741     accesses will raise an error.
   840     be called before this method or else all file accesses will raise an error.
   742 
   841 
   743     Args:
   842     Args:
   744       filename: Path of the file to check (relative or absolute). May be a
   843       filename: Path of the file to check (relative or absolute). May be a
   745         directory, in which case access for files inside that directory will
   844         directory, in which case access for files inside that directory will
   746         be checked.
   845         be checked.
   751     """
   850     """
   752     logical_filename = normcase(os.path.abspath(filename))
   851     logical_filename = normcase(os.path.abspath(filename))
   753 
   852 
   754     if os.path.isdir(logical_filename):
   853     if os.path.isdir(logical_filename):
   755       logical_filename = os.path.join(logical_filename, 'foo')
   854       logical_filename = os.path.join(logical_filename, 'foo')
       
   855 
       
   856     result = FakeFile._availability_cache.get(logical_filename)
       
   857     if result is None:
       
   858       result = FakeFile._IsFileAccessibleNoCache(logical_filename,
       
   859                                                  normcase=normcase)
       
   860       FakeFile._availability_cache[logical_filename] = result
       
   861     return result
       
   862 
       
   863   @staticmethod
       
   864   def _IsFileAccessibleNoCache(logical_filename, normcase=os.path.normcase):
       
   865     """Determines if a file's path is accessible.
       
   866 
       
   867     This is an internal part of the IsFileAccessible implementation.
       
   868 
       
   869     Args:
       
   870       logical_filename: Absolute path of the file to check.
       
   871       normcase: Used for dependency injection.
       
   872 
       
   873     Returns:
       
   874       True if the file is accessible, False otherwise.
       
   875     """
       
   876     if IsPathInSubdirectories(logical_filename, [FakeFile._root_path],
       
   877                               normcase=normcase):
       
   878       relative_filename = logical_filename[len(FakeFile._root_path):]
       
   879 
       
   880       if FakeFile._skip_files.match(relative_filename):
       
   881         logging.warning('Blocking access to skipped file "%s"',
       
   882                         logical_filename)
       
   883         return False
       
   884 
       
   885       if FakeFile._static_file_config_matcher.IsStaticFile(relative_filename):
       
   886         logging.warning('Blocking access to static file "%s"',
       
   887                         logical_filename)
       
   888         return False
   756 
   889 
   757     if logical_filename in FakeFile.ALLOWED_FILES:
   890     if logical_filename in FakeFile.ALLOWED_FILES:
   758       return True
   891       return True
   759 
   892 
   760     if IsPathInSubdirectories(logical_filename,
   893     if IsPathInSubdirectories(logical_filename,
   884       args: Positional format parameters for the logging message.
  1017       args: Positional format parameters for the logging message.
   885     """
  1018     """
   886     if HardenedModulesHook.ENABLE_LOGGING:
  1019     if HardenedModulesHook.ENABLE_LOGGING:
   887       indent = self._indent_level * '  '
  1020       indent = self._indent_level * '  '
   888       print >>sys.stderr, indent + (message % args)
  1021       print >>sys.stderr, indent + (message % args)
   889 
       
   890   EMPTY_MODULE_FILE = '<empty module>'
       
   891 
  1022 
   892   _WHITE_LIST_C_MODULES = [
  1023   _WHITE_LIST_C_MODULES = [
   893     'array',
  1024     'array',
   894     'binascii',
  1025     'binascii',
   895     'bz2',
  1026     'bz2',
   957     ],
  1088     ],
   958 
  1089 
   959 
  1090 
   960 
  1091 
   961     'os': [
  1092     'os': [
       
  1093       'access',
   962       'altsep',
  1094       'altsep',
   963       'curdir',
  1095       'curdir',
   964       'defpath',
  1096       'defpath',
   965       'devnull',
  1097       'devnull',
   966       'environ',
  1098       'environ',
  1005       'O_WRONLY',
  1137       'O_WRONLY',
  1006       'pardir',
  1138       'pardir',
  1007       'path',
  1139       'path',
  1008       'pathsep',
  1140       'pathsep',
  1009       'R_OK',
  1141       'R_OK',
       
  1142       'readlink',
       
  1143       'remove',
  1010       'SEEK_CUR',
  1144       'SEEK_CUR',
  1011       'SEEK_END',
  1145       'SEEK_END',
  1012       'SEEK_SET',
  1146       'SEEK_SET',
  1013       'sep',
  1147       'sep',
  1014       'stat',
  1148       'stat',
  1015       'stat_float_times',
  1149       'stat_float_times',
  1016       'stat_result',
  1150       'stat_result',
  1017       'strerror',
  1151       'strerror',
  1018       'TMP_MAX',
  1152       'TMP_MAX',
       
  1153       'unlink',
  1019       'urandom',
  1154       'urandom',
  1020       'walk',
  1155       'walk',
  1021       'WCOREDUMP',
  1156       'WCOREDUMP',
  1022       'WEXITSTATUS',
  1157       'WEXITSTATUS',
  1023       'WIFEXITED',
  1158       'WIFEXITED',
  1030       'W_OK',
  1165       'W_OK',
  1031       'X_OK',
  1166       'X_OK',
  1032     ],
  1167     ],
  1033   }
  1168   }
  1034 
  1169 
  1035   _EMPTY_MODULES = [
       
  1036     'imp',
       
  1037     'ftplib',
       
  1038     'select',
       
  1039     'socket',
       
  1040     'tempfile',
       
  1041   ]
       
  1042 
       
  1043   _MODULE_OVERRIDES = {
  1170   _MODULE_OVERRIDES = {
  1044     'locale': {
  1171     'locale': {
  1045       'setlocale': FakeSetLocale,
  1172       'setlocale': FakeSetLocale,
  1046     },
  1173     },
  1047 
  1174 
  1048     'os': {
  1175     'os': {
       
  1176       'access': FakeAccess,
  1049       'listdir': RestrictedPathFunction(os.listdir),
  1177       'listdir': RestrictedPathFunction(os.listdir),
  1050 
  1178 
  1051       'lstat': RestrictedPathFunction(os.stat),
  1179       'lstat': RestrictedPathFunction(os.stat),
       
  1180       'readlink': FakeReadlink,
       
  1181       'remove': FakeUnlink,
  1052       'stat': RestrictedPathFunction(os.stat),
  1182       'stat': RestrictedPathFunction(os.stat),
  1053       'uname': FakeUname,
  1183       'uname': FakeUname,
       
  1184       'unlink': FakeUnlink,
  1054       'urandom': FakeURandom,
  1185       'urandom': FakeURandom,
  1055     },
       
  1056 
       
  1057     'socket': {
       
  1058       'AF_INET': None,
       
  1059       'SOCK_STREAM': None,
       
  1060       'SOCK_DGRAM': None,
       
  1061       '_GLOBAL_DEFAULT_TIMEOUT': getattr(socket, '_GLOBAL_DEFAULT_TIMEOUT',
       
  1062                                          None),
       
  1063     },
       
  1064 
       
  1065     'tempfile': {
       
  1066       'TemporaryFile': FakeTemporaryFile,
       
  1067       'gettempdir': NotImplementedFake,
       
  1068       'gettempprefix': NotImplementedFake,
       
  1069       'mkdtemp': NotImplementedFake,
       
  1070       'mkstemp': NotImplementedFake,
       
  1071       'mktemp': NotImplementedFake,
       
  1072       'NamedTemporaryFile': NotImplementedFake,
       
  1073       'tempdir': NotImplementedFake,
       
  1074     },
  1186     },
  1075   }
  1187   }
  1076 
  1188 
  1077   _ENABLED_FILE_TYPES = (
  1189   _ENABLED_FILE_TYPES = (
  1078     imp.PKG_DIRECTORY,
  1190     imp.PKG_DIRECTORY,
  1105     self._indent_level = 0
  1217     self._indent_level = 0
  1106 
  1218 
  1107   @Trace
  1219   @Trace
  1108   def find_module(self, fullname, path=None):
  1220   def find_module(self, fullname, path=None):
  1109     """See PEP 302."""
  1221     """See PEP 302."""
  1110     if (fullname in ('cPickle', 'thread') or
  1222     if fullname in ('cPickle', 'thread'):
  1111         fullname in HardenedModulesHook._EMPTY_MODULES):
       
  1112       return self
  1223       return self
  1113 
  1224 
  1114     search_path = path
  1225     search_path = path
  1115     all_modules = fullname.split('.')
  1226     all_modules = fullname.split('.')
  1116     try:
  1227     try:
  1117       for index, current_module in enumerate(all_modules):
  1228       for index, current_module in enumerate(all_modules):
  1118         current_module_fullname = '.'.join(all_modules[:index + 1])
  1229         current_module_fullname = '.'.join(all_modules[:index + 1])
  1119         if current_module_fullname == fullname:
  1230         if (current_module_fullname == fullname and not
       
  1231             self.StubModuleExists(fullname)):
  1120           self.FindModuleRestricted(current_module,
  1232           self.FindModuleRestricted(current_module,
  1121                                     current_module_fullname,
  1233                                     current_module_fullname,
  1122                                     search_path)
  1234                                     search_path)
  1123         else:
  1235         else:
  1124           if current_module_fullname in self._module_dict:
  1236           if current_module_fullname in self._module_dict:
  1132             search_path = module.__path__
  1244             search_path = module.__path__
  1133     except CouldNotFindModuleError:
  1245     except CouldNotFindModuleError:
  1134       return None
  1246       return None
  1135 
  1247 
  1136     return self
  1248     return self
       
  1249 
       
  1250   def StubModuleExists(self, name):
       
  1251     """Check if the named module has a stub replacement."""
       
  1252     if name in sys.builtin_module_names:
       
  1253       name = 'py_%s' % name
       
  1254     if name in dist.__all__:
       
  1255       return True
       
  1256     return False
       
  1257 
       
  1258   def ImportStubModule(self, name):
       
  1259     """Import the stub module replacement for the specified module."""
       
  1260     if name in sys.builtin_module_names:
       
  1261       name = 'py_%s' % name
       
  1262     module = __import__(dist.__name__, {}, {}, [name])
       
  1263     return getattr(module, name)
  1137 
  1264 
  1138   @Trace
  1265   @Trace
  1139   def FixModule(self, module):
  1266   def FixModule(self, module):
  1140     """Prunes and overrides restricted module attributes.
  1267     """Prunes and overrides restricted module attributes.
  1141 
  1268 
  1332       ImportError exception if the module could not be loaded for whatever
  1459       ImportError exception if the module could not be loaded for whatever
  1333       reason (e.g., missing, not allowed).
  1460       reason (e.g., missing, not allowed).
  1334     """
  1461     """
  1335     module = self._imp.new_module(submodule_fullname)
  1462     module = self._imp.new_module(submodule_fullname)
  1336 
  1463 
  1337     if submodule_fullname in self._EMPTY_MODULES:
  1464     if submodule_fullname == 'thread':
  1338       module.__file__ = self.EMPTY_MODULE_FILE
       
  1339     elif submodule_fullname == 'thread':
       
  1340       module.__dict__.update(self._dummy_thread.__dict__)
  1465       module.__dict__.update(self._dummy_thread.__dict__)
  1341       module.__name__ = 'thread'
  1466       module.__name__ = 'thread'
  1342     elif submodule_fullname == 'cPickle':
  1467     elif submodule_fullname == 'cPickle':
  1343       module.__dict__.update(self._pickle.__dict__)
  1468       module.__dict__.update(self._pickle.__dict__)
  1344       module.__name__ = 'cPickle'
  1469       module.__name__ = 'cPickle'
  1345     elif submodule_fullname == 'os':
  1470     elif submodule_fullname == 'os':
  1346       module.__dict__.update(self._os.__dict__)
  1471       module.__dict__.update(self._os.__dict__)
  1347       self._module_dict['os.path'] = module.path
  1472       self._module_dict['os.path'] = module.path
       
  1473     elif self.StubModuleExists(submodule_fullname):
       
  1474       module = self.ImportStubModule(submodule_fullname)
  1348     else:
  1475     else:
  1349       source_file, pathname, description = self.FindModuleRestricted(submodule, submodule_fullname, search_path)
  1476       source_file, pathname, description = self.FindModuleRestricted(submodule, submodule_fullname, search_path)
  1350       module = self.LoadModuleRestricted(submodule_fullname,
  1477       module = self.LoadModuleRestricted(submodule_fullname,
  1351                                          source_file,
  1478                                          source_file,
  1352                                          pathname,
  1479                                          pathname,
  2002           regex = entry.upload + '$'
  2129           regex = entry.upload + '$'
  2003         else:
  2130         else:
  2004           path = entry.static_dir
  2131           path = entry.static_dir
  2005           if path[-1] == '/':
  2132           if path[-1] == '/':
  2006             path = path[:-1]
  2133             path = path[:-1]
  2007           regex = re.escape(path) + r'/(.*)'
  2134           regex = re.escape(path + os.path.sep) + r'(.*)'
  2008 
  2135 
  2009         try:
  2136         try:
  2010           path_re = re.compile(regex)
  2137           path_re = re.compile(regex)
  2011         except re.error, e:
  2138         except re.error, e:
  2012           raise InvalidAppConfigError('regex %s does not compile: %s' %
  2139           raise InvalidAppConfigError('regex %s does not compile: %s' %
  2018           expiration = self._default_expiration
  2145           expiration = self._default_expiration
  2019         else:
  2146         else:
  2020           expiration = appinfo.ParseExpiration(entry.expiration)
  2147           expiration = appinfo.ParseExpiration(entry.expiration)
  2021 
  2148 
  2022         self._patterns.append((path_re, entry.mime_type, expiration))
  2149         self._patterns.append((path_re, entry.mime_type, expiration))
       
  2150 
       
  2151   def IsStaticFile(self, path):
       
  2152     """Tests if the given path points to a "static" file.
       
  2153 
       
  2154     Args:
       
  2155       path: String containing the file's path relative to the app.
       
  2156 
       
  2157     Returns:
       
  2158       Boolean, True if the file was configured to be static.
       
  2159     """
       
  2160     for (path_re, _, _) in self._patterns:
       
  2161       if path_re.match(path):
       
  2162         return True
       
  2163     return False
  2023 
  2164 
  2024   def GetMimeType(self, path):
  2165   def GetMimeType(self, path):
  2025     """Returns the mime type that we should use when serving the specified file.
  2166     """Returns the mime type that we should use when serving the specified file.
  2026 
  2167 
  2027     Args:
  2168     Args:
  2141     'content-encoding', 'accept-encoding', 'transfer-encoding',
  2282     'content-encoding', 'accept-encoding', 'transfer-encoding',
  2142     'server', 'date',
  2283     'server', 'date',
  2143     ])
  2284     ])
  2144 
  2285 
  2145 
  2286 
  2146 def RewriteResponse(response_file):
  2287 def IgnoreHeadersRewriter(status_code, status_message, headers, body):
  2147   """Interprets server-side headers and adjusts the HTTP response accordingly.
  2288   """Ignore specific response headers.
       
  2289 
       
  2290   Certain response headers cannot be modified by an Application.  For a
       
  2291   complete list of these headers please see:
       
  2292 
       
  2293     http://code.google.com/appengine/docs/webapp/responseclass.html#Disallowed_HTTP_Response_Headers
       
  2294 
       
  2295   This rewriter simply removes those headers.
       
  2296   """
       
  2297   for h in _IGNORE_RESPONSE_HEADERS:
       
  2298     if h in headers:
       
  2299       del headers[h]
       
  2300 
       
  2301   return status_code, status_message, headers, body
       
  2302 
       
  2303 
       
  2304 def ParseStatusRewriter(status_code, status_message, headers, body):
       
  2305   """Parse status header, if it exists.
  2148 
  2306 
  2149   Handles the server-side 'status' header, which instructs the server to change
  2307   Handles the server-side 'status' header, which instructs the server to change
  2150   the HTTP response code accordingly. Handles the 'location' header, which
  2308   the HTTP response code accordingly. Handles the 'location' header, which
  2151   issues an HTTP 302 redirect to the client. Also corrects the 'content-length'
  2309   issues an HTTP 302 redirect to the client. Also corrects the 'content-length'
  2152   header to reflect actual content length in case extra information has been
  2310   header to reflect actual content length in case extra information has been
  2153   appended to the response body.
  2311   appended to the response body.
  2154 
  2312 
  2155   If the 'status' header supplied by the client is invalid, this method will
  2313   If the 'status' header supplied by the client is invalid, this method will
  2156   set the response to a 500 with an error message as content.
  2314   set the response to a 500 with an error message as content.
       
  2315   """
       
  2316   location_value = headers.getheader('location')
       
  2317   status_value = headers.getheader('status')
       
  2318   if status_value:
       
  2319     response_status = status_value
       
  2320     del headers['status']
       
  2321   elif location_value:
       
  2322     response_status = '%d Redirecting' % httplib.FOUND
       
  2323   else:
       
  2324     return status_code, status_message, headers, body
       
  2325 
       
  2326   status_parts = response_status.split(' ', 1)
       
  2327   status_code, status_message = (status_parts + [''])[:2]
       
  2328   try:
       
  2329     status_code = int(status_code)
       
  2330   except ValueError:
       
  2331     status_code = 500
       
  2332     body = cStringIO.StringIO('Error: Invalid "status" header value returned.')
       
  2333 
       
  2334   return status_code, status_message, headers, body
       
  2335 
       
  2336 
       
  2337 def CacheRewriter(status_code, status_message, headers, body):
       
  2338   """Update the cache header."""
       
  2339   if not 'Cache-Control' in headers:
       
  2340     headers['Cache-Control'] = 'no-cache'
       
  2341   return status_code, status_message, headers, body
       
  2342 
       
  2343 
       
  2344 def ContentLengthRewriter(status_code, status_message, headers, body):
       
  2345   """Rewrite the Content-Length header.
       
  2346 
       
  2347   Even though Content-Length is not a user modifiable header, App Engine
       
  2348   sends a correct Content-Length to the user based on the actual response.
       
  2349   """
       
  2350   current_position = body.tell()
       
  2351   body.seek(0, 2)
       
  2352 
       
  2353   headers['Content-Length'] = str(body.tell() - current_position)
       
  2354   body.seek(current_position)
       
  2355   return status_code, status_message, headers, body
       
  2356 
       
  2357 
       
  2358 def CreateResponseRewritersChain():
       
  2359   """Create the default response rewriter chain.
       
  2360 
       
  2361   A response rewriter is the a function that gets a final chance to change part
       
  2362   of the dev_appservers response.  A rewriter is not like a dispatcher in that
       
  2363   it is called after every request has been handled by the dispatchers
       
  2364   regardless of which dispatcher was used.
       
  2365 
       
  2366   The order in which rewriters are registered will be the order in which they
       
  2367   are used to rewrite the response.  Modifications from earlier rewriters
       
  2368   are used as input to later rewriters.
       
  2369 
       
  2370   A response rewriter is a function that can rewrite the request in any way.
       
  2371   Thefunction can returned modified values or the original values it was
       
  2372   passed.
       
  2373 
       
  2374   A rewriter function has the following parameters and return values:
       
  2375 
       
  2376     Args:
       
  2377       status_code: Status code of response from dev_appserver or previous
       
  2378         rewriter.
       
  2379       status_message: Text corresponding to status code.
       
  2380       headers: mimetools.Message instance with parsed headers.  NOTE: These
       
  2381         headers can contain its own 'status' field, but the default
       
  2382         dev_appserver implementation will remove this.  Future rewriters
       
  2383         should avoid re-introducing the status field and return new codes
       
  2384         instead.
       
  2385       body: File object containing the body of the response.  This position of
       
  2386         this file may not be at the start of the file.  Any content before the
       
  2387         files position is considered not to be part of the final body.
       
  2388 
       
  2389      Returns:
       
  2390       status_code: Rewritten status code or original.
       
  2391       status_message: Rewritter message or original.
       
  2392       headers: Rewritten/modified headers or original.
       
  2393       body: Rewritten/modified body or original.
       
  2394 
       
  2395   Returns:
       
  2396     List of response rewriters.
       
  2397   """
       
  2398   return [IgnoreHeadersRewriter,
       
  2399           ParseStatusRewriter,
       
  2400           CacheRewriter,
       
  2401           ContentLengthRewriter,
       
  2402   ]
       
  2403 
       
  2404 
       
  2405 def RewriteResponse(response_file, response_rewriters=None):
       
  2406   """Allows final rewrite of dev_appserver response.
       
  2407 
       
  2408   This function receives the unparsed HTTP response from the application
       
  2409   or internal handler, parses out the basic structure and feeds that structure
       
  2410   in to a chain of response rewriters.
       
  2411 
       
  2412   It also makes sure the final HTTP headers are properly terminated.
       
  2413 
       
  2414   For more about response rewriters, please see documentation for
       
  2415   CreateResponeRewritersChain.
  2157 
  2416 
  2158   Args:
  2417   Args:
  2159     response_file: File-like object containing the full HTTP response including
  2418     response_file: File-like object containing the full HTTP response including
  2160       the response code, all headers, and the request body.
  2419       the response code, all headers, and the request body.
  2161     gmtime: Function which returns current time in a format matching standard
  2420     response_rewriters: A list of response rewriters.  If none is provided it
  2162       time.gmtime().
  2421       will create a new chain using CreateResponseRewritersChain.
  2163 
  2422 
  2164   Returns:
  2423   Returns:
  2165     Tuple (status_code, status_message, header, body) where:
  2424     Tuple (status_code, status_message, header, body) where:
  2166       status_code: Integer HTTP response status (e.g., 200, 302, 404, 500)
  2425       status_code: Integer HTTP response status (e.g., 200, 302, 404, 500)
  2167       status_message: String containing an informational message about the
  2426       status_message: String containing an informational message about the
  2168         response code, possibly derived from the 'status' header, if supplied.
  2427         response code, possibly derived from the 'status' header, if supplied.
  2169       header: String containing the HTTP headers of the response, without
  2428       header: String containing the HTTP headers of the response, without
  2170         a trailing new-line (CRLF).
  2429         a trailing new-line (CRLF).
  2171       body: String containing the body of the response.
  2430       body: String containing the body of the response.
  2172   """
  2431   """
       
  2432   if response_rewriters is None:
       
  2433     response_rewriters = CreateResponseRewritersChain()
       
  2434 
       
  2435   status_code = 200
       
  2436   status_message = 'Good to go'
  2173   headers = mimetools.Message(response_file)
  2437   headers = mimetools.Message(response_file)
  2174 
  2438 
  2175   for h in _IGNORE_RESPONSE_HEADERS:
  2439   for response_rewriter in response_rewriters:
  2176     if h in headers:
  2440     status_code, status_message, headers, response_file = response_rewriter(
  2177       del headers[h]
  2441         status_code,
  2178 
  2442         status_message,
  2179   response_status = '%d Good to go' % httplib.OK
  2443         headers,
  2180 
  2444         response_file)
  2181   location_value = headers.getheader('location')
       
  2182   status_value = headers.getheader('status')
       
  2183   if status_value:
       
  2184     response_status = status_value
       
  2185     del headers['status']
       
  2186   elif location_value:
       
  2187     response_status = '%d Redirecting' % httplib.FOUND
       
  2188 
       
  2189   if not 'Cache-Control' in headers:
       
  2190     headers['Cache-Control'] = 'no-cache'
       
  2191 
       
  2192   status_parts = response_status.split(' ', 1)
       
  2193   status_code, status_message = (status_parts + [''])[:2]
       
  2194   try:
       
  2195     status_code = int(status_code)
       
  2196   except ValueError:
       
  2197     status_code = 500
       
  2198     body = 'Error: Invalid "status" header value returned.'
       
  2199   else:
       
  2200     body = response_file.read()
       
  2201 
       
  2202   headers['Content-Length'] = str(len(body))
       
  2203 
  2445 
  2204   header_list = []
  2446   header_list = []
  2205   for header in headers.headers:
  2447   for header in headers.headers:
  2206     header = header.rstrip('\n')
  2448     header = header.rstrip('\n')
  2207     header = header.rstrip('\r')
  2449     header = header.rstrip('\r')
  2208     header_list.append(header)
  2450     header_list.append(header)
  2209 
  2451 
  2210   header_data = '\r\n'.join(header_list) + '\r\n'
  2452   header_data = '\r\n'.join(header_list) + '\r\n'
  2211   return status_code, status_message, header_data, body
  2453   return status_code, status_message, header_data, response_file.read()
  2212 
  2454 
  2213 
  2455 
  2214 class ModuleManager(object):
  2456 class ModuleManager(object):
  2215   """Manages loaded modules in the runtime.
  2457   """Manages loaded modules in the runtime.
  2216 
  2458 
  2243       Path of the module's corresponding Python source file if it exists, or
  2485       Path of the module's corresponding Python source file if it exists, or
  2244       just the module's compiled Python file. If the module has an invalid
  2486       just the module's compiled Python file. If the module has an invalid
  2245       __file__ attribute, None will be returned.
  2487       __file__ attribute, None will be returned.
  2246       """
  2488       """
  2247     module_file = getattr(module, '__file__', None)
  2489     module_file = getattr(module, '__file__', None)
  2248     if not module_file or module_file == HardenedModulesHook.EMPTY_MODULE_FILE:
  2490     if module_file is None:
  2249       return None
  2491       return None
  2250 
  2492 
  2251     source_file = module_file[:module_file.rfind('py') + 2]
  2493     source_file = module_file[:module_file.rfind('py') + 2]
  2252 
  2494 
  2253     if is_file(source_file):
  2495     if is_file(source_file):
  2307   template_module = module_dict.get('google.appengine.ext.webapp.template')
  2549   template_module = module_dict.get('google.appengine.ext.webapp.template')
  2308   if template_module is not None:
  2550   if template_module is not None:
  2309     template_module.template_cache.clear()
  2551     template_module.template_cache.clear()
  2310 
  2552 
  2311 
  2553 
  2312 def CreateRequestHandler(root_path, login_url, require_indexes=False,
  2554 def CreateRequestHandler(root_path,
       
  2555                          login_url,
       
  2556                          require_indexes=False,
  2313                          static_caching=True):
  2557                          static_caching=True):
  2314   """Creates a new BaseHTTPRequestHandler sub-class for use with the Python
  2558   """Creates a new BaseHTTPRequestHandler sub-class for use with the Python
  2315   BaseHTTPServer module's HTTP server.
  2559   BaseHTTPServer module's HTTP server.
  2316 
  2560 
  2317   Python's built-in HTTP server does not support passing context information
  2561   Python's built-in HTTP server does not support passing context information
  2356 
  2600 
  2357     module_dict = application_module_dict
  2601     module_dict = application_module_dict
  2358     module_manager = ModuleManager(application_module_dict)
  2602     module_manager = ModuleManager(application_module_dict)
  2359 
  2603 
  2360     config_cache = application_config_cache
  2604     config_cache = application_config_cache
       
  2605 
       
  2606     rewriter_chain = CreateResponseRewritersChain()
  2361 
  2607 
  2362     def __init__(self, *args, **kwargs):
  2608     def __init__(self, *args, **kwargs):
  2363       """Initializer.
  2609       """Initializer.
  2364 
  2610 
  2365       Args:
  2611       Args:
  2430                                                  root_path,
  2676                                                  root_path,
  2431                                                  login_url)
  2677                                                  login_url)
  2432         config, explicit_matcher = LoadAppConfig(root_path, self.module_dict,
  2678         config, explicit_matcher = LoadAppConfig(root_path, self.module_dict,
  2433                                                  cache=self.config_cache,
  2679                                                  cache=self.config_cache,
  2434                                                  static_caching=static_caching)
  2680                                                  static_caching=static_caching)
       
  2681         if config.api_version != API_VERSION:
       
  2682           logging.error("API versions cannot be switched dynamically: %r != %r"
       
  2683                         % (config.api_version, API_VERSION))
       
  2684           sys.exit(1)
  2435         env_dict['CURRENT_VERSION_ID'] = config.version + ".1"
  2685         env_dict['CURRENT_VERSION_ID'] = config.version + ".1"
  2436         env_dict['APPLICATION_ID'] = config.application
  2686         env_dict['APPLICATION_ID'] = config.application
  2437         dispatcher = MatcherDispatcher(login_url,
  2687         dispatcher = MatcherDispatcher(login_url,
  2438                                        [implicit_matcher, explicit_matcher])
  2688                                        [implicit_matcher, explicit_matcher])
  2439 
  2689 
  2463           self.module_manager.UpdateModuleFileModificationTimes()
  2713           self.module_manager.UpdateModuleFileModificationTimes()
  2464 
  2714 
  2465         outfile.flush()
  2715         outfile.flush()
  2466         outfile.seek(0)
  2716         outfile.seek(0)
  2467 
  2717 
  2468         status_code, status_message, header_data, body = RewriteResponse(outfile)
  2718         status_code, status_message, header_data, body = RewriteResponse(outfile, self.rewriter_chain)
  2469 
  2719 
  2470         runtime_response_size = len(outfile.getvalue())
  2720         runtime_response_size = len(outfile.getvalue())
  2471         if runtime_response_size > MAX_RUNTIME_RESPONSE_SIZE:
  2721         if runtime_response_size > MAX_RUNTIME_RESPONSE_SIZE:
  2472           status_code = 403
  2722           status_code = 403
  2473           status_message = 'Forbidden'
  2723           status_message = 'Forbidden'
  2580     Instance of URLMatcher with the supplied URLMap objects properly loaded.
  2830     Instance of URLMatcher with the supplied URLMap objects properly loaded.
  2581   """
  2831   """
  2582   url_matcher = create_url_matcher()
  2832   url_matcher = create_url_matcher()
  2583   path_adjuster = create_path_adjuster(root_path)
  2833   path_adjuster = create_path_adjuster(root_path)
  2584   cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster)
  2834   cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster)
       
  2835   static_file_config_matcher = StaticFileConfigMatcher(url_map_list,
       
  2836                                                        path_adjuster,
       
  2837                                                        default_expiration)
  2585   file_dispatcher = create_file_dispatcher(path_adjuster,
  2838   file_dispatcher = create_file_dispatcher(path_adjuster,
  2586       StaticFileConfigMatcher(url_map_list, path_adjuster, default_expiration))
  2839                                            static_file_config_matcher)
       
  2840 
       
  2841   FakeFile.SetStaticFileConfigMatcher(static_file_config_matcher)
  2587 
  2842 
  2588   for url_map in url_map_list:
  2843   for url_map in url_map_list:
  2589     admin_only = url_map.login == appinfo.LOGIN_ADMIN
  2844     admin_only = url_map.login == appinfo.LOGIN_ADMIN
  2590     requires_login = url_map.login == appinfo.LOGIN_REQUIRED or admin_only
  2845     requires_login = url_map.login == appinfo.LOGIN_REQUIRED or admin_only
  2591 
  2846 
  2685         matcher = create_matcher(root_path,
  2940         matcher = create_matcher(root_path,
  2686                                  config.handlers,
  2941                                  config.handlers,
  2687                                  module_dict,
  2942                                  module_dict,
  2688                                  default_expiration)
  2943                                  default_expiration)
  2689 
  2944 
       
  2945         FakeFile.SetSkippedFiles(config.skip_files)
       
  2946 
  2690         if cache is not None:
  2947         if cache is not None:
  2691           cache.path = appinfo_path
  2948           cache.path = appinfo_path
  2692           cache.config = config
  2949           cache.config = config
  2693           cache.matcher = matcher
  2950           cache.matcher = matcher
  2694 
  2951 
  2866                  port,
  3123                  port,
  2867                  template_dir,
  3124                  template_dir,
  2868                  serve_address='',
  3125                  serve_address='',
  2869                  require_indexes=False,
  3126                  require_indexes=False,
  2870                  static_caching=True,
  3127                  static_caching=True,
  2871                  python_path_list=sys.path):
  3128                  python_path_list=sys.path,
       
  3129                  sdk_dir=os.path.dirname(os.path.dirname(google.__file__))):
  2872   """Creates an new HTTPServer for an application.
  3130   """Creates an new HTTPServer for an application.
       
  3131 
       
  3132   The sdk_dir argument must be specified for the directory storing all code for
       
  3133   the SDK so as to allow for the sandboxing of module access to work for any
       
  3134   and all SDK code. While typically this is where the 'google' package lives,
       
  3135   it can be in another location because of API version support.
  2873 
  3136 
  2874   Args:
  3137   Args:
  2875     root_path: String containing the path to the root directory of the
  3138     root_path: String containing the path to the root directory of the
  2876       application where the app.yaml file is.
  3139       application where the app.yaml file is.
  2877     login_url: Relative URL which should be used for handling user login/logout.
  3140     login_url: Relative URL which should be used for handling user login/logout.
  2880       are stored.
  3143       are stored.
  2881     serve_address: Address on which the server should serve.
  3144     serve_address: Address on which the server should serve.
  2882     require_indexes: True if index.yaml is read-only gospel; default False.
  3145     require_indexes: True if index.yaml is read-only gospel; default False.
  2883     static_caching: True if browser caching of static files should be allowed.
  3146     static_caching: True if browser caching of static files should be allowed.
  2884     python_path_list: Used for dependency injection.
  3147     python_path_list: Used for dependency injection.
       
  3148     sdk_dir: Directory where the SDK is stored.
  2885 
  3149 
  2886   Returns:
  3150   Returns:
  2887     Instance of BaseHTTPServer.HTTPServer that's ready to start accepting.
  3151     Instance of BaseHTTPServer.HTTPServer that's ready to start accepting.
  2888   """
  3152   """
  2889   absolute_root_path = os.path.realpath(root_path)
  3153   absolute_root_path = os.path.realpath(root_path)
  2890 
  3154 
  2891   SetupTemplates(template_dir)
  3155   SetupTemplates(template_dir)
  2892   FakeFile.SetAllowedPaths([absolute_root_path,
  3156   FakeFile.SetAllowedPaths(absolute_root_path,
  2893                             os.path.dirname(os.path.dirname(google.__file__)),
  3157                            [sdk_dir,
  2894                             template_dir])
  3158                             template_dir])
  2895 
  3159 
  2896   handler_class = CreateRequestHandler(absolute_root_path, login_url,
  3160   handler_class = CreateRequestHandler(absolute_root_path,
  2897                                        require_indexes, static_caching)
  3161                                        login_url,
       
  3162                                        require_indexes,
       
  3163                                        static_caching)
  2898 
  3164 
  2899   if absolute_root_path not in python_path_list:
  3165   if absolute_root_path not in python_path_list:
  2900     python_path_list.insert(0, absolute_root_path)
  3166     python_path_list.insert(0, absolute_root_path)
  2901 
  3167 
  2902   return BaseHTTPServer.HTTPServer((serve_address, port), handler_class)
  3168   return BaseHTTPServer.HTTPServer((serve_address, port), handler_class)