3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
20 """Import hook for dev_appserver.py."""
48 from google
.appengine
import dist
49 from google
.appengine
import dist27
as dist27
51 from google
.appengine
.api
import appinfo
54 SITE_PACKAGES
= os
.path
.normcase(os
.path
.join(os
.path
.dirname(os
.__file
__),
58 import google
.appengine
59 SDK_ROOT
= os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(
60 google
.appengine
.__file
__)))
63 CODING_COOKIE_RE
= re
.compile("coding[:=]\s*([-\w.]+)")
64 DEFAULT_ENCODING
= 'ascii'
68 """Fake version of os.urandom."""
71 bytes
+= chr(random
.randint(0, 255))
76 """Fake version of os.uname."""
77 return ('Linux', '', '', '', '')
81 """Fake version of os.unlink."""
82 if os
.path
.isdir(path
):
83 raise OSError(errno
.ENOENT
, "Is a directory", path
)
85 raise OSError(errno
.EPERM
, "Operation not permitted", path
)
88 def FakeReadlink(path
):
89 """Fake version of os.readlink."""
90 raise OSError(errno
.EINVAL
, "Invalid argument", path
)
93 def FakeAccess(path
, mode
):
94 """Fake version of os.access where only reads are supported."""
95 if not os
.path
.exists(path
) or mode
!= os
.R_OK
:
101 def FakeSetLocale(category
, value
=None, original_setlocale
=locale
.setlocale
):
102 """Fake version of locale.setlocale that only supports the default."""
103 if value
not in (None, '', 'C', 'POSIX'):
104 raise locale
.Error('locale emulation only supports "C" locale')
105 return original_setlocale(category
, 'C')
108 def FakeOpen(filename
, flags
, mode
=0777):
109 """Fake version of os.open."""
110 raise OSError(errno
.EPERM
, "Operation not permitted", filename
)
113 def FakeRename(src
, dst
):
114 """Fake version of os.rename."""
115 raise OSError(errno
.EPERM
, "Operation not permitted", src
)
118 def FakeUTime(path
, times
):
119 """Fake version of os.utime."""
120 raise OSError(errno
.EPERM
, "Operation not permitted", path
)
123 def FakeGetPlatform():
124 """Fake distutils.util.get_platform on OS/X. Pass-through otherwise."""
125 if sys
.platform
== 'darwin':
128 return distutils
.util
.get_platform()
131 def FakeCryptoRandomOSRNGnew(*args
, **kwargs
):
132 from Crypto
.Random
.OSRNG
import fallback
133 return fallback
.new(*args
, **kwargs
)
140 def NeedsMacOSXProxyFakes():
141 """Returns True if the MacOS X urllib fakes should be installed."""
142 return (sys
.platform
== 'darwin' and
143 (2, 6, 0) <= sys
.version_info
< (2, 6, 4))
146 if NeedsMacOSXProxyFakes():
147 def _FakeProxyBypassHelper(fn
,
148 original_module_dict
=sys
.modules
.copy(),
149 original_uname
=os
.uname
):
150 """Setups and restores the state for the Mac OS X urllib fakes."""
151 def Inner(*args
, **kwargs
):
152 current_uname
= os
.uname
153 current_meta_path
= sys
.meta_path
[:]
154 current_modules
= sys
.modules
.copy()
158 sys
.modules
.update(original_module_dict
)
159 sys
.meta_path
[:] = []
160 os
.uname
= original_uname
162 return fn(*args
, **kwargs
)
165 sys
.modules
.update(current_modules
)
166 os
.uname
= current_uname
167 sys
.meta_path
[:] = current_meta_path
171 @_FakeProxyBypassHelper
172 def FakeProxyBypassMacOSXSysconf(
174 original_proxy_bypass_macosx_sysconf
=urllib
.proxy_bypass_macosx_sysconf
):
175 """Fake for urllib.proxy_bypass_macosx_sysconf for Python 2.6.0 to 2.6.3."""
176 return original_proxy_bypass_macosx_sysconf(host
)
179 @_FakeProxyBypassHelper
180 def FakeGetProxiesMacOSXSysconf(
181 original_getproxies_macosx_sysconf
=urllib
.getproxies_macosx_sysconf
):
182 """Fake for urllib.getproxies_macosx_sysconf for Python 2.6.0 to 2.6.3."""
183 return original_getproxies_macosx_sysconf()
186 def IsPathInSubdirectories(filename
,
188 normcase
=os
.path
.normcase
):
189 """Determines if a filename is contained within one of a set of directories.
192 filename: Path of the file (relative or absolute).
193 subdirectories: Iterable collection of paths to subdirectories which the
194 given filename may be under.
195 normcase: Used for dependency injection.
198 True if the supplied filename is in one of the given sub-directories or
199 its hierarchy of children. False otherwise.
201 file_dir
= normcase(os
.path
.dirname(os
.path
.abspath(filename
)))
202 for parent
in subdirectories
:
203 fixed_parent
= normcase(os
.path
.abspath(parent
))
204 if os
.path
.commonprefix([file_dir
, fixed_parent
]) == fixed_parent
:
209 def GeneratePythonPaths(*p
):
210 """Generate all valid filenames for the given file.
213 p: Positional args are the folders to the file and finally the file
217 A list of strings representing the given path to a file with each valid
218 suffix for this python build.
220 suffixes
= imp
.get_suffixes()
221 return [os
.path
.join(*p
) + s
for s
, m
, t
in suffixes
]
224 class FakeFile(file):
225 """File sub-class that enforces the security restrictions of the production
229 ALLOWED_MODES
= frozenset(['r', 'rb', 'U', 'rU'])
232 ALLOWED_FILES
= set(os
.path
.normcase(filename
)
233 for filename
in mimetypes
.knownfiles
234 if os
.path
.isfile(filename
))
237 ALLOWED_FILES_RE
= set([re
.compile(r
'.*/python27.zip$')])
245 os
.path
.normcase(os
.path
.realpath(os
.path
.dirname(os
.__file
__))),
246 os
.path
.normcase(os
.path
.abspath(os
.path
.dirname(os
.__file
__))),
247 os
.path
.normcase(os
.path
.dirname(os
.path
.realpath(os
.__file
__))),
248 os
.path
.normcase(os
.path
.dirname(os
.path
.abspath(os
.__file
__))),
250 os_source_location
= inspect
.getsourcefile(os
)
252 if os_source_location
is not None:
256 ALLOWED_DIRS
.update([
257 os
.path
.normcase(os
.path
.realpath(os
.path
.dirname(os_source_location
))),
258 os
.path
.normcase(os
.path
.abspath(os
.path
.dirname(os_source_location
))),
259 os
.path
.normcase(os
.path
.dirname(os
.path
.realpath(os_source_location
))),
260 os
.path
.normcase(os
.path
.dirname(os
.path
.abspath(os_source_location
))),
266 NOT_ALLOWED_DIRS
= set([
281 ALLOWED_SITE_PACKAGE_DIRS
= set(
282 os
.path
.normcase(os
.path
.abspath(os
.path
.join(SITE_PACKAGES
, path
)))
287 ALLOWED_SITE_PACKAGE_FILES
= set(
288 os
.path
.normcase(os
.path
.abspath(os
.path
.join(
289 os
.path
.dirname(os
.__file
__), 'site-packages', path
)))
290 for path
in itertools
.chain(*[
292 [os
.path
.join('Crypto')],
293 GeneratePythonPaths('Crypto', '__init__'),
294 GeneratePythonPaths('Crypto', 'pct_warnings'),
295 [os
.path
.join('Crypto', 'Cipher')],
296 GeneratePythonPaths('Crypto', 'Cipher', '__init__'),
297 GeneratePythonPaths('Crypto', 'Cipher', 'blockalgo'),
298 GeneratePythonPaths('Crypto', 'Cipher', 'AES'),
299 GeneratePythonPaths('Crypto', 'Cipher', 'ARC2'),
300 GeneratePythonPaths('Crypto', 'Cipher', 'ARC4'),
301 GeneratePythonPaths('Crypto', 'Cipher', 'Blowfish'),
302 GeneratePythonPaths('Crypto', 'Cipher', 'CAST'),
303 GeneratePythonPaths('Crypto', 'Cipher', 'DES'),
304 GeneratePythonPaths('Crypto', 'Cipher', 'DES3'),
305 GeneratePythonPaths('Crypto', 'Cipher', 'PKCS1_OAEP'),
306 GeneratePythonPaths('Crypto', 'Cipher', 'PKCS1_v1_5'),
307 GeneratePythonPaths('Crypto', 'Cipher', 'XOR'),
308 [os
.path
.join('Crypto', 'Hash')],
309 GeneratePythonPaths('Crypto', 'Hash', '__init__'),
310 GeneratePythonPaths('Crypto', 'Hash', 'hashalgo'),
311 GeneratePythonPaths('Crypto', 'Hash', 'HMAC'),
312 GeneratePythonPaths('Crypto', 'Hash', 'MD2'),
313 GeneratePythonPaths('Crypto', 'Hash', 'MD4'),
314 GeneratePythonPaths('Crypto', 'Hash', 'MD5'),
315 GeneratePythonPaths('Crypto', 'Hash', 'SHA'),
316 GeneratePythonPaths('Crypto', 'Hash', 'SHA224'),
317 GeneratePythonPaths('Crypto', 'Hash', 'SHA256'),
318 GeneratePythonPaths('Crypto', 'Hash', 'SHA384'),
319 GeneratePythonPaths('Crypto', 'Hash', 'SHA512'),
320 GeneratePythonPaths('Crypto', 'Hash', 'RIPEMD'),
321 [os
.path
.join('Crypto', 'Protocol')],
322 GeneratePythonPaths('Crypto', 'Protocol', '__init__'),
323 GeneratePythonPaths('Crypto', 'Protocol', 'AllOrNothing'),
324 GeneratePythonPaths('Crypto', 'Protocol', 'Chaffing'),
325 GeneratePythonPaths('Crypto', 'Protocol', 'KDF'),
326 [os
.path
.join('Crypto', 'PublicKey')],
327 GeneratePythonPaths('Crypto', 'PublicKey', '__init__'),
328 GeneratePythonPaths('Crypto', 'PublicKey', 'DSA'),
329 GeneratePythonPaths('Crypto', 'PublicKey', '_DSA'),
330 GeneratePythonPaths('Crypto', 'PublicKey', 'ElGamal'),
331 GeneratePythonPaths('Crypto', 'PublicKey', 'RSA'),
332 GeneratePythonPaths('Crypto', 'PublicKey', '_RSA'),
333 GeneratePythonPaths('Crypto', 'PublicKey', 'pubkey'),
334 GeneratePythonPaths('Crypto', 'PublicKey', 'qNEW'),
335 GeneratePythonPaths('Crypto', 'PublicKey', '_slowmath'),
336 [os
.path
.join('Crypto', 'Random')],
337 GeneratePythonPaths('Crypto', 'Random', '__init__'),
338 GeneratePythonPaths('Crypto', 'Random', 'random'),
339 GeneratePythonPaths('Crypto', 'Random', '_UserFriendlyRNG'),
340 [os
.path
.join('Crypto', 'Random', 'OSRNG')],
341 GeneratePythonPaths('Crypto', 'Random', 'OSRNG', '__init__'),
342 GeneratePythonPaths('Crypto', 'Random', 'OSRNG', 'fallback'),
343 GeneratePythonPaths('Crypto', 'Random', 'OSRNG', 'nt'),
344 GeneratePythonPaths('Crypto', 'Random', 'OSRNG', 'posix'),
345 GeneratePythonPaths('Crypto', 'Random', 'OSRNG', 'rng_base'),
346 [os
.path
.join('Crypto', 'Random', 'Fortuna')],
347 GeneratePythonPaths('Crypto', 'Random', 'Fortuna', '__init__'),
348 GeneratePythonPaths('Crypto', 'Random', 'Fortuna',
349 'FortunaAccumulator'),
350 GeneratePythonPaths('Crypto', 'Random', 'Fortuna',
352 GeneratePythonPaths('Crypto', 'Random', 'Fortuna', 'SHAd256'),
353 [os
.path
.join('Crypto', 'Signature')],
354 GeneratePythonPaths('Crypto', 'Signature', '__init__'),
355 GeneratePythonPaths('Crypto', 'Signature', 'PKCS1_PSS'),
356 GeneratePythonPaths('Crypto', 'Signature', 'PKCS1_v1_5'),
357 [os
.path
.join('Crypto', 'Util')],
358 GeneratePythonPaths('Crypto', 'Util', '__init__'),
359 GeneratePythonPaths('Crypto', 'Util', 'asn1'),
360 GeneratePythonPaths('Crypto', 'Util', 'Counter'),
361 GeneratePythonPaths('Crypto', 'Util', 'RFC1751'),
362 GeneratePythonPaths('Crypto', 'Util', 'number'),
363 GeneratePythonPaths('Crypto', 'Util', '_number_new'),
364 GeneratePythonPaths('Crypto', 'Util', 'py3compat'),
365 GeneratePythonPaths('Crypto', 'Util', 'python_compat'),
366 GeneratePythonPaths('Crypto', 'Util', 'randpool'),
371 _original_file
= file
375 _application_paths
= None
377 _static_file_config_matcher
= None
380 _allow_skipped_files
= True
383 _availability_cache
= {}
386 def SetAllowedPaths(root_path
, application_paths
):
387 """Configures which paths are allowed to be accessed.
389 Must be called at least once before any file objects are created in the
390 hardened environment.
393 root_path: Absolute path to the root of the application.
394 application_paths: List of additional paths that the application may
395 access, this must include the App Engine runtime but
396 not the Python library directories.
400 FakeFile
._application
_paths
= (set(os
.path
.realpath(path
)
401 for path
in application_paths
) |
402 set(os
.path
.abspath(path
)
403 for path
in application_paths
))
404 FakeFile
._application
_paths
.add(root_path
)
407 FakeFile
._root
_path
= os
.path
.join(root_path
, '')
409 FakeFile
._availability
_cache
= {}
412 def SetAllowSkippedFiles(allow_skipped_files
):
413 """Configures access to files matching FakeFile._skip_files.
416 allow_skipped_files: Boolean whether to allow access to skipped files
418 FakeFile
._allow
_skipped
_files
= allow_skipped_files
419 FakeFile
._availability
_cache
= {}
422 def SetAllowedModule(name
):
423 """Allow the use of a module based on where it is located.
425 Meant to be used by use_library() so that it has a link back into the
426 trusted part of the interpreter.
429 name: Name of the module to allow.
431 stream
, pathname
, description
= imp
.find_module(name
)
432 pathname
= os
.path
.normcase(os
.path
.abspath(pathname
))
435 FakeFile
.ALLOWED_FILES
.add(pathname
)
436 FakeFile
.ALLOWED_FILES
.add(os
.path
.realpath(pathname
))
438 assert description
[2] == imp
.PKG_DIRECTORY
439 if pathname
.startswith(SITE_PACKAGES
):
440 FakeFile
.ALLOWED_SITE_PACKAGE_DIRS
.add(pathname
)
441 FakeFile
.ALLOWED_SITE_PACKAGE_DIRS
.add(os
.path
.realpath(pathname
))
443 FakeFile
.ALLOWED_DIRS
.add(pathname
)
444 FakeFile
.ALLOWED_DIRS
.add(os
.path
.realpath(pathname
))
447 def SetSkippedFiles(skip_files
):
448 """Sets which files in the application directory are to be ignored.
450 Must be called at least once before any file objects are created in the
451 hardened environment.
453 Must be called whenever the configuration was updated.
456 skip_files: Object with .match() method (e.g. compiled regexp).
458 FakeFile
._skip
_files
= skip_files
459 FakeFile
._availability
_cache
= {}
462 def SetStaticFileConfigMatcher(static_file_config_matcher
):
463 """Sets StaticFileConfigMatcher instance for checking if a file is static.
465 Must be called at least once before any file objects are created in the
466 hardened environment.
468 Must be called whenever the configuration was updated.
471 static_file_config_matcher: StaticFileConfigMatcher instance.
473 FakeFile
._static
_file
_config
_matcher
= static_file_config_matcher
474 FakeFile
._availability
_cache
= {}
477 def IsFileAccessible(filename
, normcase
=os
.path
.normcase
,
478 py27_optional
=False):
479 """Determines if a file's path is accessible.
481 SetAllowedPaths(), SetSkippedFiles() and SetStaticFileConfigMatcher() must
482 be called before this method or else all file accesses will raise an error.
485 filename: Path of the file to check (relative or absolute). May be a
486 directory, in which case access for files inside that directory will
488 normcase: Used for dependency injection.
489 py27_optional: Whether the filename being checked matches the name of an
490 optional python27 runtime library.
493 True if the file is accessible, False otherwise.
498 logical_filename
= normcase(os
.path
.abspath(filename
))
506 result
= FakeFile
._availability
_cache
.get(logical_filename
)
508 result
= FakeFile
._IsFileAccessibleNoCache
(logical_filename
,
510 py27_optional
=py27_optional
)
511 FakeFile
._availability
_cache
[logical_filename
] = result
515 def _IsFileAccessibleNoCache(logical_filename
, normcase
=os
.path
.normcase
,
516 py27_optional
=False):
517 """Determines if a file's path is accessible.
519 This is an internal part of the IsFileAccessible implementation.
522 logical_filename: Absolute path of the file to check.
523 normcase: Used for dependency injection.
524 py27_optional: Whether the filename being checked matches the name of an
525 optional python27 runtime library.
528 True if the file is accessible, False otherwise.
534 logical_dirfakefile
= logical_filename
536 if os
.path
.isdir(logical_filename
):
537 logical_dirfakefile
= os
.path
.join(logical_filename
, 'foo')
541 if IsPathInSubdirectories(logical_dirfakefile
, [FakeFile
._root
_path
],
544 relative_filename
= logical_dirfakefile
[len(FakeFile
._root
_path
):]
546 if not FakeFile
._allow
_skipped
_files
:
547 path
= relative_filename
553 path
= os
.path
.dirname(path
)
554 while path
!= os
.path
.dirname(path
):
555 if FakeFile
._skip
_files
.match(path
):
556 logging
.warning('Blocking access to skipped file "%s"',
559 path
= os
.path
.dirname(path
)
561 if FakeFile
._static
_file
_config
_matcher
.IsStaticFile(relative_filename
):
562 logging
.warning('Blocking access to static file "%s"',
571 if logical_filename
in FakeFile
.ALLOWED_FILES
:
574 for regex
in FakeFile
.ALLOWED_FILES_RE
:
575 match
= regex
.match(logical_filename
)
576 if match
and match
.end() == len(logical_filename
):
579 if logical_filename
in FakeFile
.ALLOWED_SITE_PACKAGE_FILES
:
582 if IsPathInSubdirectories(logical_dirfakefile
,
583 FakeFile
.ALLOWED_SITE_PACKAGE_DIRS
,
587 allowed_dirs
= FakeFile
._application
_paths | FakeFile
.ALLOWED_DIRS
588 if (IsPathInSubdirectories(logical_dirfakefile
,
590 normcase
=normcase
) and
591 not IsPathInSubdirectories(logical_dirfakefile
,
592 FakeFile
.NOT_ALLOWED_DIRS
,
598 def __init__(self
, filename
, mode
='r', bufsize
=-1, **kwargs
):
599 """Initializer. See file built-in documentation."""
600 if mode
not in FakeFile
.ALLOWED_MODES
:
605 raise IOError('invalid mode: %s' % mode
)
607 if not FakeFile
.IsFileAccessible(filename
):
608 raise IOError(errno
.EACCES
, 'file not accessible', filename
)
610 super(FakeFile
, self
).__init
__(filename
, mode
, bufsize
, **kwargs
)
614 dist
._library
.SetAllowedModule
= FakeFile
.SetAllowedModule
617 class RestrictedPathFunction(object):
618 """Enforces access restrictions for functions that have a file or
619 directory path as their first argument."""
623 def __init__(self
, original_func
):
627 original_func: Callable that takes as its first argument the path to a
628 file or directory on disk; all subsequent arguments may be variable.
630 self
._original
_func
= original_func
632 def __call__(self
, path
, *args
, **kwargs
):
633 """Enforces access permissions for the function passed to the constructor.
635 if not FakeFile
.IsFileAccessible(path
):
636 raise OSError(errno
.EACCES
, 'path not accessible', path
)
638 return self
._original
_func
(path
, *args
, **kwargs
)
641 def GetSubmoduleName(fullname
):
642 """Determines the leaf submodule name of a full module name.
645 fullname: Fully qualified module name, e.g. 'foo.bar.baz'
648 Submodule name, e.g. 'baz'. If the supplied module has no submodule (e.g.,
649 'stuff'), the returned value will just be that module name ('stuff').
651 return fullname
.rsplit('.', 1)[-1]
654 class CouldNotFindModuleError(ImportError):
655 """Raised when a module could not be found.
657 In contrast to when a module has been found, but cannot be loaded because of
658 hardening restrictions.
662 class Py27OptionalModuleError(ImportError):
663 """Raised for error conditions relating to optional Python 2.7 modules."""
667 """Call stack logging decorator for HardenedModulesHook class.
669 This decorator logs the call stack of the HardenedModulesHook class as
670 it executes, indenting logging messages based on the current stack depth.
673 func: the function to decorate.
676 The decorated function.
679 def Decorate(self
, *args
, **kwargs
):
682 args_to_show
.extend(str(argument
) for argument
in args
)
683 if kwargs
is not None:
684 args_to_show
.extend('%s=%s' % (key
, value
)
685 for key
, value
in kwargs
.iteritems())
687 args_string
= ', '.join(args_to_show
)
689 self
.log('Entering %s(%s)', func
.func_name
, args_string
)
690 self
._indent
_level
+= 1
692 return func(self
, *args
, **kwargs
)
694 self
._indent
_level
-= 1
695 self
.log('Exiting %s(%s)', func
.func_name
, args_string
)
700 class HardenedModulesHook(object):
701 """Meta import hook that restricts the modules used by applications to match
702 the production environment.
704 Module controls supported:
705 - Disallow native/extension modules from being loaded
706 - Disallow built-in and/or Python-distributed modules from being loaded
707 - Replace modules with completely empty modules
708 - Override specific module attributes
709 - Replace one module with another
711 After creation, this object should be added to the front of the sys.meta_path
712 list (which may need to be created). The sys.path_importer_cache dictionary
713 should also be cleared, to prevent loading any non-restricted modules.
715 See PEP302 for more info on how this works:
716 http://www.python.org/dev/peps/pep-0302/
721 ENABLE_LOGGING
= False
723 def log(self
, message
, *args
):
724 """Logs an import-related message to stderr, with indentation based on
725 current call-stack depth.
728 message: Logging format string.
729 args: Positional format parameters for the logging message.
731 if HardenedModulesHook
.ENABLE_LOGGING
:
732 indent
= self
._indent
_level
* ' '
733 print >>sys
.__stderr
__, indent
+ (message
% args
)
740 _WHITE_LIST_C_MODULES
= [
741 'py_streamhtmlparser',
772 '_Crypto_Cipher__AES',
773 '_Crypto_Cipher__ARC2',
774 '_Crypto_Cipher__ARC4',
775 '_Crypto_Cipher__Blowfish',
776 '_Crypto_Cipher__CAST',
777 '_Crypto_Cipher__DES',
778 '_Crypto_Cipher__DES3',
779 '_Crypto_Cipher__XOR',
782 '_Crypto_Hash__RIPEMD',
783 '_Crypto_Hash__SHA256',
847 _PY27_ALLOWED_MODULES
= [
878 __PY27_OPTIONAL_ALLOWED_MODULES
= {
882 'jinja2': ['_debugsupport', '_speedups'],
883 'lxml': ['etree', 'objectify'],
884 'markupsafe': ['_speedups'],
910 'PIL': ['_imaging', '_imagingcms', '_imagingft', '_imagingmath'],
919 __CRYPTO_CIPHER_ALLOWED_MODULES
= [
933 _WHITE_LIST_PARTIAL_MODULES
= {
934 'Crypto.Cipher.AES': __CRYPTO_CIPHER_ALLOWED_MODULES
,
935 'Crypto.Cipher.ARC2': __CRYPTO_CIPHER_ALLOWED_MODULES
,
936 'Crypto.Cipher.Blowfish': __CRYPTO_CIPHER_ALLOWED_MODULES
,
937 'Crypto.Cipher.CAST': __CRYPTO_CIPHER_ALLOWED_MODULES
,
938 'Crypto.Cipher.DES': __CRYPTO_CIPHER_ALLOWED_MODULES
,
939 'Crypto.Cipher.DES3': __CRYPTO_CIPHER_ALLOWED_MODULES
,
1035 '_get_exports_list',
1054 _MODULE_OVERRIDES
= {
1056 'setlocale': FakeSetLocale
,
1060 'access': FakeAccess
,
1061 'listdir': RestrictedPathFunction(os
.listdir
),
1063 'lstat': RestrictedPathFunction(os
.stat
),
1065 'readlink': FakeReadlink
,
1066 'remove': FakeUnlink
,
1067 'rename': FakeRename
,
1068 'stat': RestrictedPathFunction(os
.stat
),
1070 'unlink': FakeUnlink
,
1071 'urandom': FakeURandom
,
1081 'get_platform': FakeGetPlatform
,
1084 'Crypto.Random.OSRNG': {
1085 'new': FakeCryptoRandomOSRNGnew
,
1090 _ENABLED_FILE_TYPES
= (
1103 dummy_thread_module
=dummy_thread
,
1104 pickle_module
=pickle
):
1108 config: AppInfoExternal instance representing the parsed app.yaml file.
1109 module_dict: Module dictionary to use for managing system modules.
1110 Should be sys.modules.
1111 app_code_path: The absolute path to the application code on disk.
1112 imp_module, os_module, dummy_thread_module, etc.: References to
1113 modules that exist in the dev_appserver that must be used by this class
1114 in order to function, even if these modules have been unloaded from
1117 self
._config
= config
1118 self
._module
_dict
= module_dict
1119 self
._imp
= imp_module
1120 self
._os
= os_module
1121 self
._dummy
_thread
= dummy_thread_module
1122 self
._pickle
= pickle
1123 self
._indent
_level
= 0
1124 self
._app
_code
_path
= app_code_path
1125 self
._white
_list
_c
_modules
= list(self
._WHITE
_LIST
_C
_MODULES
)
1126 self
._white
_list
_partial
_modules
= dict(self
._WHITE
_LIST
_PARTIAL
_MODULES
)
1127 self
._enabled
_modules
= []
1129 if self
._config
and self
._config
.runtime
== 'python27':
1130 self
._white
_list
_c
_modules
.extend(self
._PY
27_ALLOWED
_MODULES
)
1135 self
._white
_list
_partial
_modules
['os'] = (
1136 list(self
._white
_list
_partial
_modules
['os']) +
1137 ['getpid', 'getuid', 'sys'])
1140 for k
in self
._white
_list
_partial
_modules
.keys():
1141 if k
.startswith('Crypto'):
1142 del self
._white
_list
_partial
_modules
[k
]
1145 webob_path
= os
.path
.join(SDK_ROOT
, 'lib', 'webob-1.1.1')
1146 if webob_path
not in sys
.path
:
1147 sys
.path
.insert(1, webob_path
)
1149 for libentry
in self
._config
.GetAllLibraries():
1150 self
._enabled
_modules
.append(libentry
.name
)
1151 extra
= self
.__PY
27_OPTIONAL
_ALLOWED
_MODULES
.get(libentry
.name
)
1152 logging
.debug('Enabling %s: %r', libentry
.name
, extra
)
1154 self
._white
_list
_c
_modules
.extend(extra
)
1155 if libentry
.name
== 'django':
1159 if 'django' not in self
._module
_dict
:
1160 version
= libentry
.version
1161 if version
== 'latest':
1162 django_library
= appinfo
._NAME
_TO
_SUPPORTED
_LIBRARY
['django']
1163 version
= django_library
.non_deprecated_versions
[-1]
1164 if google
.__name
__.endswith('3'):
1168 __import__('django.v' + version
.replace('.', '_'))
1171 sys
.modules
.pop('django', None)
1172 sitedir
= os
.path
.join(SDK_ROOT
,
1174 'django-%s' % version
)
1175 if os
.path
.isdir(sitedir
):
1176 logging
.debug('Enabling Django version %s at %s',
1178 sys
.path
[:] = [dirname
1179 for dirname
in sys
.path
1180 if not dirname
.startswith(os
.path
.join(
1181 SDK_ROOT
, 'lib', 'django'))]
1182 sys
.path
.insert(1, sitedir
)
1184 logging
.warn('Enabling Django version %s (no directory found)',
1186 elif libentry
.name
== 'endpoints':
1189 from google
.third_party
.apphosting
.python
.endpoints
import v1_0
1190 sys
.path
.append(os
.path
.dirname(v1_0
.__file
__))
1195 endpoints_path
= os
.path
.join(SDK_ROOT
, 'lib', 'endpoints-1.0')
1196 if endpoints_path
not in sys
.path
:
1197 sys
.path
.append(endpoints_path
)
1201 def find_module(self
, fullname
, path
=None):
1207 if fullname
in ('cPickle', 'thread'):
1211 all_modules
= fullname
.split('.')
1213 for index
, current_module
in enumerate(all_modules
):
1214 current_module_fullname
= '.'.join(all_modules
[:index
+ 1])
1215 if (current_module_fullname
== fullname
and not
1216 self
.StubModuleExists(fullname
)):
1223 self
.FindModuleRestricted(current_module
,
1224 current_module_fullname
,
1230 if current_module_fullname
in self
._module
_dict
:
1231 module
= self
._module
_dict
[current_module_fullname
]
1234 module
= self
.FindAndLoadModule(current_module
,
1235 current_module_fullname
,
1244 if hasattr(module
, '__path__'):
1245 search_path
= module
.__path
__
1246 except CouldNotFindModuleError
:
1258 except Py27OptionalModuleError
, err
:
1270 def StubModuleExists(self
, name
):
1271 """Check if the named module has a stub replacement."""
1272 if name
in sys
.builtin_module_names
:
1273 name
= 'py_%s' % name
1274 if self
._config
and self
._config
.runtime
== 'python27':
1275 if name
in dist27
.MODULE_OVERRIDES
:
1278 if name
in dist
.__all
__:
1282 def ImportStubModule(self
, name
):
1283 """Import the stub module replacement for the specified module."""
1284 if name
in sys
.builtin_module_names
:
1285 name
= 'py_%s' % name
1288 providing_dist
= dist
1289 if self
._config
and self
._config
.runtime
== 'python27':
1292 if name
in dist27
.__all__
:
1293 providing_dist
= dist27
1294 fullname
= '%s.%s' % (providing_dist
.__name
__, name
)
1295 __import__(fullname
, {}, {})
1296 return sys
.modules
[fullname
]
1299 def FixModule(self
, module
):
1300 """Prunes and overrides restricted module attributes.
1303 module: The module to prune. This should be a new module whose attributes
1304 reference back to the real module's __dict__ members.
1307 if module
.__name
__ in self
._white
_list
_partial
_modules
:
1308 allowed_symbols
= self
._white
_list
_partial
_modules
[module
.__name
__]
1309 for symbol
in set(module
.__dict
__) - set(allowed_symbols
):
1310 if not (symbol
.startswith('__') and symbol
.endswith('__')):
1311 del module
.__dict
__[symbol
]
1314 if module
.__name
__ in self
._MODULE
_OVERRIDES
:
1315 module
.__dict
__.update(self
._MODULE
_OVERRIDES
[module
.__name
__])
1317 if module
.__name
__ == 'urllib' and NeedsMacOSXProxyFakes():
1318 module
.__dict
__.update(
1319 {'proxy_bypass_macosx_sysconf': FakeProxyBypassMacOSXSysconf
,
1320 'getproxies_macosx_sysconf': FakeGetProxiesMacOSXSysconf
})
1323 def FindModuleRestricted(self
,
1327 """Locates a module while enforcing module import restrictions.
1330 submodule: The short name of the submodule (i.e., the last section of
1331 the fullname; for 'foo.bar' this would be 'bar').
1332 submodule_fullname: The fully qualified name of the module to find (e.g.,
1334 search_path: List of paths to search for to find this module. Should be
1335 None if the current sys.path should be used.
1338 Tuple (source_file, pathname, description) where:
1339 source_file: File-like object that contains the module; in the case
1340 of packages, this will be None, which implies to look at __init__.py.
1341 pathname: String containing the full path of the module on disk.
1342 description: Tuple returned by imp.find_module().
1343 However, in the case of an import using a path hook (e.g. a zipfile),
1344 source_file will be a PEP-302-style loader object, pathname will be None,
1345 and description will be a tuple filled with None values.
1348 ImportError exception if the requested module was found, but importing
1351 CouldNotFindModuleError exception if the request module could not even
1352 be found for import.
1354 if search_path
is None:
1357 search_path
= [None] + sys
.path
1359 py27_optional
= False
1360 py27_enabled
= False
1362 if self
._config
and self
._config
.runtime
== 'python27':
1365 topmodule
= submodule_fullname
.split('.')[0]
1366 if topmodule
in self
.__PY
27_OPTIONAL
_ALLOWED
_MODULES
:
1367 py27_optional
= True
1368 py27_enabled
= topmodule
in self
._enabled
_modules
1372 elif topmodule
== 'Crypto':
1373 py27_optional
= True
1374 py27_enabled
= 'pycrypto' in self
._enabled
_modules
1382 for path_entry
in search_path
:
1383 result
= self
.FindPathHook(submodule
, submodule_fullname
, path_entry
)
1384 if result
is not None:
1385 source_file
, pathname
, description
= result
1386 if description
== (None, None, None):
1409 suffix
, mode
, file_type
= description
1412 if (file_type
not in (self
._imp
.C_BUILTIN
, self
._imp
.C_EXTENSION
)):
1413 pkg_pathname
= pathname
1414 if file_type
== self
._imp
.PKG_DIRECTORY
:
1417 pkg_pathname
= os
.path
.join(pkg_pathname
, '__init__.py')
1418 if not FakeFile
.IsFileAccessible(
1419 pkg_pathname
, py27_optional
=py27_optional
):
1420 error_message
= 'Access to module file denied: %s' % pathname
1421 logging
.debug(error_message
)
1422 raise ImportError(error_message
)
1424 if (file_type
not in self
._ENABLED
_FILE
_TYPES
and
1425 submodule
not in self
._white
_list
_c
_modules
):
1426 error_message
= ('Could not import "%s": Disallowed C-extension '
1427 'or built-in module' % submodule_fullname
)
1428 logging
.debug(error_message
)
1429 raise ImportError(error_message
)
1431 if (py27_optional
and not py27_enabled
and
1432 not pathname
.startswith(self
._app
_code
_path
)):
1433 error_message
= ('Third party package %s not enabled.' % topmodule
)
1434 logging
.debug(error_message
)
1435 raise ImportError(error_message
)
1437 return source_file
, pathname
, description
1438 except ImportError, e
:
1445 if py27_optional
and submodule_fullname
== topmodule
:
1447 msg
= ('Third party package %s was enabled in app.yaml '
1448 'but not found on import. You may have to download '
1449 'and install it.' % topmodule
)
1451 msg
= ('Third party package %s must be included in the '
1452 '"libraries:" clause of your app.yaml file '
1453 'in order to be imported.' % topmodule
)
1458 raise Py27OptionalModuleError(msg
)
1470 self
.log('Could not find module "%s"', submodule_fullname
)
1471 raise CouldNotFindModuleError()
1474 def FindPathHook(self
, submodule
, submodule_fullname
, path_entry
):
1475 """Helper for FindModuleRestricted to find a module in a sys.path entry.
1480 path_entry: A single sys.path entry, or None representing the builtins.
1483 Either None (if nothing was found), or a triple (source_file, path_name,
1484 description). See the doc string for FindModuleRestricted() for the
1485 meaning of the latter.
1487 if path_entry
is None:
1489 if submodule_fullname
in sys
.builtin_module_names
:
1491 result
= self
._imp
.find_module(submodule
)
1496 source_file
, pathname
, description
= result
1497 suffix
, mode
, file_type
= description
1498 if file_type
== self
._imp
.C_BUILTIN
:
1507 if path_entry
in sys
.path_importer_cache
:
1508 importer
= sys
.path_importer_cache
[path_entry
]
1512 for hook
in sys
.path_hooks
:
1514 importer
= hook(path_entry
)
1521 sys
.path_importer_cache
[path_entry
] = importer
1523 if importer
is None:
1526 return self
._imp
.find_module(submodule
, [path_entry
])
1531 loader
= importer
.find_module(submodule_fullname
)
1532 if loader
is not None:
1537 return (loader
, None, (None, None, None))
1543 def LoadModuleRestricted(self
,
1548 """Loads a module while enforcing module import restrictions.
1550 As a byproduct, the new module will be added to the module dictionary.
1553 submodule_fullname: The fully qualified name of the module to find (e.g.,
1555 source_file: File-like object that contains the module's source code,
1556 or a PEP-302-style loader object.
1557 pathname: String containing the full path of the module on disk.
1558 description: Tuple returned by imp.find_module(), or (None, None, None)
1559 in case source_file is a PEP-302-style loader object.
1565 ImportError exception of the specified module could not be loaded for
1568 if description
== (None, None, None):
1571 return source_file
.load_module(submodule_fullname
)
1586 return self
._imp
.load_module(submodule_fullname
,
1593 if submodule_fullname
in self
._module
_dict
:
1594 del self
._module
_dict
[submodule_fullname
]
1598 if source_file
is not None:
1602 def FindAndLoadModule(self
,
1606 """Finds and loads a module, loads it, and adds it to the module dictionary.
1609 submodule: Name of the module to import (e.g., baz).
1610 submodule_fullname: Full name of the module to import (e.g., foo.bar.baz).
1611 search_path: Path to use for searching for this submodule. For top-level
1612 modules this should be None; otherwise it should be the __path__
1613 attribute from the parent package.
1616 A new module instance that has been inserted into the module dictionary
1617 supplied to __init__.
1620 ImportError exception if the module could not be loaded for whatever
1621 reason (e.g., missing, not allowed).
1623 module
= self
._imp
.new_module(submodule_fullname
)
1625 if submodule_fullname
== 'thread':
1626 module
.__dict
__.update(self
._dummy
_thread
.__dict
__)
1627 module
.__name
__ = 'thread'
1628 elif submodule_fullname
== 'cPickle':
1629 module
.__dict
__.update(self
._pickle
.__dict
__)
1630 module
.__name
__ = 'cPickle'
1631 elif submodule_fullname
== 'os':
1632 module
.__dict
__.update(self
._os
.__dict
__)
1633 elif submodule_fullname
== 'ssl':
1635 elif self
.StubModuleExists(submodule_fullname
):
1636 module
= self
.ImportStubModule(submodule_fullname
)
1638 source_file
, pathname
, description
= self
.FindModuleRestricted(submodule
, submodule_fullname
, search_path
)
1639 module
= self
.LoadModuleRestricted(submodule_fullname
,
1647 if (getattr(module
, '__path__', None) is not None and
1648 search_path
!= self
._app
_code
_path
):
1650 app_search_path
= os
.path
.join(self
._app
_code
_path
,
1651 *(submodule_fullname
.split('.')[:-1]))
1652 source_file
, pathname
, description
= self
.FindModuleRestricted(submodule
,
1657 module
.__path
__.append(pathname
)
1658 except ImportError, e
:
1664 module
.__loader
__ = self
1665 self
.FixModule(module
)
1666 if submodule_fullname
not in self
._module
_dict
:
1667 self
._module
_dict
[submodule_fullname
] = module
1668 if submodule_fullname
!= submodule
:
1669 parent_module
= self
._module
_dict
.get(
1670 submodule_fullname
[:-len(submodule
) - 1])
1673 if parent_module
and not hasattr(parent_module
, submodule
):
1674 setattr(parent_module
, submodule
, module
)
1676 if submodule_fullname
== 'os':
1685 os_path_name
= module
.path
.__name
__
1686 os_path
= self
.FindAndLoadModule(os_path_name
, os_path_name
, search_path
)
1689 self
._module
_dict
['os.path'] = os_path
1690 module
.__dict
__['path'] = os_path
1695 def GetParentPackage(self
, fullname
):
1696 """Retrieves the parent package of a fully qualified module name.
1699 fullname: Full name of the module whose parent should be retrieved (e.g.,
1703 Module instance for the parent or None if there is no parent module.
1706 ImportError exception if the module's parent could not be found.
1708 all_modules
= fullname
.split('.')
1709 parent_module_fullname
= '.'.join(all_modules
[:-1])
1710 if parent_module_fullname
:
1712 if self
.find_module(fullname
) is None:
1713 raise ImportError('Could not find module %s' % fullname
)
1715 return self
._module
_dict
[parent_module_fullname
]
1719 def GetParentSearchPath(self
, fullname
):
1720 """Determines the search path of a module's parent package.
1723 fullname: Full name of the module to look up (e.g., foo.bar).
1726 Tuple (submodule, search_path) where:
1727 submodule: The last portion of the module name from fullname (e.g.,
1728 if fullname is foo.bar, then this is bar).
1729 search_path: List of paths that belong to the parent package's search
1730 path or None if there is no parent package.
1733 ImportError exception if the module or its parent could not be found.
1735 submodule
= GetSubmoduleName(fullname
)
1736 parent_package
= self
.GetParentPackage(fullname
)
1738 if parent_package
is not None and hasattr(parent_package
, '__path__'):
1739 search_path
= parent_package
.__path
__
1740 return submodule
, search_path
1743 def GetModuleInfo(self
, fullname
):
1744 """Determines the path on disk and the search path of a module or package.
1747 fullname: Full name of the module to look up (e.g., foo.bar).
1750 Tuple (pathname, search_path, submodule) where:
1751 pathname: String containing the full path of the module on disk,
1752 or None if the module wasn't loaded from disk (e.g. from a zipfile).
1753 search_path: List of paths that belong to the found package's search
1754 path or None if found module is not a package.
1755 submodule: The relative name of the submodule that's being imported.
1757 submodule
, search_path
= self
.GetParentSearchPath(fullname
)
1758 source_file
, pathname
, description
= self
.FindModuleRestricted(submodule
, fullname
, search_path
)
1759 suffix
, mode
, file_type
= description
1760 module_search_path
= None
1761 if file_type
== self
._imp
.PKG_DIRECTORY
:
1762 module_search_path
= [pathname
]
1763 pathname
= os
.path
.join(pathname
, '__init__%spy' % os
.extsep
)
1764 return pathname
, module_search_path
, submodule
1767 def load_module(self
, fullname
):
1769 all_modules
= fullname
.split('.')
1770 submodule
= all_modules
[-1]
1771 parent_module_fullname
= '.'.join(all_modules
[:-1])
1773 if parent_module_fullname
and parent_module_fullname
in self
._module
_dict
:
1774 parent_module
= self
._module
_dict
[parent_module_fullname
]
1775 if hasattr(parent_module
, '__path__'):
1776 search_path
= parent_module
.__path
__
1778 return self
.FindAndLoadModule(submodule
, fullname
, search_path
)
1781 def is_package(self
, fullname
):
1782 """See PEP 302 extensions."""
1783 submodule
, search_path
= self
.GetParentSearchPath(fullname
)
1784 source_file
, pathname
, description
= self
.FindModuleRestricted(submodule
, fullname
, search_path
)
1785 suffix
, mode
, file_type
= description
1786 if file_type
== self
._imp
.PKG_DIRECTORY
:
1791 def get_source(self
, fullname
):
1792 """See PEP 302 extensions."""
1793 full_path
, search_path
, submodule
= self
.GetModuleInfo(fullname
)
1794 if full_path
is None:
1796 source_file
= open(full_path
)
1798 return source_file
.read()
1803 def get_code(self
, fullname
):
1804 """See PEP 302 extensions."""
1805 full_path
, search_path
, submodule
= self
.GetModuleInfo(fullname
)
1806 if full_path
is None:
1808 source_file
= open(full_path
)
1810 source_code
= source_file
.read()
1817 source_code
= source_code
.replace('\r\n', '\n')
1818 if not source_code
.endswith('\n'):
1824 encoding
= DEFAULT_ENCODING
1825 for line
in source_code
.split('\n', 2)[:2]:
1826 matches
= CODING_COOKIE_RE
.findall(line
)
1828 encoding
= matches
[0].lower()
1831 source_code
.decode(encoding
)
1833 return compile(source_code
, full_path
, 'exec')