108 ('.csv', 'text/comma-separated-values'), |
109 ('.csv', 'text/comma-separated-values'), |
109 ('.rss', 'application/rss+xml'), |
110 ('.rss', 'application/rss+xml'), |
110 ('.text', 'text/plain'), |
111 ('.text', 'text/plain'), |
111 ('.wbmp', 'image/vnd.wap.wbmp')): |
112 ('.wbmp', 'image/vnd.wap.wbmp')): |
112 mimetypes.add_type(mime_type, ext) |
113 mimetypes.add_type(mime_type, ext) |
|
114 |
|
115 MAX_RUNTIME_RESPONSE_SIZE = 1 << 20 |
|
116 |
|
117 MAX_REQUEST_SIZE = 10 * 1024 * 1024 |
113 |
118 |
114 |
119 |
115 class Error(Exception): |
120 class Error(Exception): |
116 """Base-class for exceptions in this module.""" |
121 """Base-class for exceptions in this module.""" |
117 |
122 |
568 def FakeUname(): |
573 def FakeUname(): |
569 """Fake version of os.uname.""" |
574 """Fake version of os.uname.""" |
570 return ('Linux', '', '', '', '') |
575 return ('Linux', '', '', '', '') |
571 |
576 |
572 |
577 |
|
578 def FakeSetLocale(category, value=None, original_setlocale=locale.setlocale): |
|
579 """Fake version of locale.setlocale that only supports the default.""" |
|
580 if value not in (None, '', 'C', 'POSIX'): |
|
581 raise locale.Error, 'locale emulation only supports "C" locale' |
|
582 return original_setlocale(category, 'C') |
|
583 |
|
584 |
573 def IsPathInSubdirectories(filename, |
585 def IsPathInSubdirectories(filename, |
574 subdirectories, |
586 subdirectories, |
575 normcase=os.path.normcase): |
587 normcase=os.path.normcase): |
576 """Determines if a filename is contained within one of a set of directories. |
588 """Determines if a filename is contained within one of a set of directories. |
577 |
589 |
679 ALLOWED_FILES = set(os.path.normcase(filename) |
691 ALLOWED_FILES = set(os.path.normcase(filename) |
680 for filename in mimetypes.knownfiles |
692 for filename in mimetypes.knownfiles |
681 if os.path.isfile(filename)) |
693 if os.path.isfile(filename)) |
682 |
694 |
683 ALLOWED_DIRS = set([ |
695 ALLOWED_DIRS = set([ |
684 os.path.normcase(os.path.realpath(os.path.dirname(os.__file__))) |
696 os.path.normcase(os.path.realpath(os.path.dirname(os.__file__))), |
|
697 os.path.normcase(os.path.abspath(os.path.dirname(os.__file__))), |
685 ]) |
698 ]) |
686 |
699 |
687 NOT_ALLOWED_DIRS = set([ |
700 NOT_ALLOWED_DIRS = set([ |
688 |
701 |
689 |
702 |
711 hardened environment. |
724 hardened environment. |
712 |
725 |
713 Args: |
726 Args: |
714 root_path: Path to the root of the application. |
727 root_path: Path to the root of the application. |
715 """ |
728 """ |
716 FakeFile._application_paths = set(os.path.abspath(path) |
729 FakeFile._application_paths = (set(os.path.realpath(path) |
717 for path in application_paths) |
730 for path in application_paths) | |
|
731 set(os.path.abspath(path) |
|
732 for path in application_paths)) |
718 |
733 |
719 @staticmethod |
734 @staticmethod |
720 def IsFileAccessible(filename, normcase=os.path.normcase): |
735 def IsFileAccessible(filename, normcase=os.path.normcase): |
721 """Determines if a file's path is accessible. |
736 """Determines if a file's path is accessible. |
722 |
737 |
754 normcase=normcase)): |
769 normcase=normcase)): |
755 return True |
770 return True |
756 |
771 |
757 return False |
772 return False |
758 |
773 |
759 def __init__(self, filename, mode='r', **kwargs): |
774 def __init__(self, filename, mode='r', bufsize=-1, **kwargs): |
760 """Initializer. See file built-in documentation.""" |
775 """Initializer. See file built-in documentation.""" |
761 if mode not in FakeFile.ALLOWED_MODES: |
776 if mode not in FakeFile.ALLOWED_MODES: |
762 raise IOError('invalid mode: %s' % mode) |
777 raise IOError('invalid mode: %s' % mode) |
763 |
778 |
764 if not FakeFile.IsFileAccessible(filename): |
779 if not FakeFile.IsFileAccessible(filename): |
765 raise IOError(errno.EACCES, 'file not accessible') |
780 raise IOError(errno.EACCES, 'file not accessible') |
766 |
781 |
767 super(FakeFile, self).__init__(filename, mode, **kwargs) |
782 super(FakeFile, self).__init__(filename, mode, bufsize, **kwargs) |
768 |
783 |
769 |
784 |
770 class RestrictedPathFunction(object): |
785 class RestrictedPathFunction(object): |
771 """Enforces access restrictions for functions that have a file or |
786 """Enforces access restrictions for functions that have a file or |
772 directory path as their first argument.""" |
787 directory path as their first argument.""" |
1022 'socket', |
1037 'socket', |
1023 'tempfile', |
1038 'tempfile', |
1024 ] |
1039 ] |
1025 |
1040 |
1026 _MODULE_OVERRIDES = { |
1041 _MODULE_OVERRIDES = { |
|
1042 'locale': { |
|
1043 'setlocale': FakeSetLocale, |
|
1044 }, |
|
1045 |
1027 'os': { |
1046 'os': { |
1028 'listdir': RestrictedPathFunction(os.listdir), |
1047 'listdir': RestrictedPathFunction(os.listdir), |
1029 'lstat': RestrictedPathFunction(os.lstat), |
1048 |
|
1049 'lstat': RestrictedPathFunction(os.stat), |
1030 'stat': RestrictedPathFunction(os.stat), |
1050 'stat': RestrictedPathFunction(os.stat), |
1031 'uname': FakeUname, |
1051 'uname': FakeUname, |
1032 'urandom': FakeURandom, |
1052 'urandom': FakeURandom, |
1033 }, |
1053 }, |
1034 |
1054 |
1533 depth_count = module_fullname.count('.') |
1553 depth_count = module_fullname.count('.') |
1534 if cgi_path.endswith('__init__.py') or not cgi_path.endswith('.py'): |
1554 if cgi_path.endswith('__init__.py') or not cgi_path.endswith('.py'): |
1535 depth_count += 1 |
1555 depth_count += 1 |
1536 |
1556 |
1537 for index in xrange(depth_count): |
1557 for index in xrange(depth_count): |
1538 current_init_file = os.path.join(module_base, '__init__.py') |
1558 current_init_file = os.path.abspath( |
|
1559 os.path.join(module_base, '__init__.py')) |
1539 |
1560 |
1540 if not isfile(current_init_file): |
1561 if not isfile(current_init_file): |
1541 missing_init_files.append(current_init_file) |
1562 missing_init_files.append(current_init_file) |
1542 |
1563 |
1543 module_base = os.path.abspath(os.path.join(module_base, os.pardir)) |
1564 module_base = os.path.abspath(os.path.join(module_base, os.pardir)) |
2401 if require_indexes: |
2422 if require_indexes: |
2402 dev_appserver_index.SetupIndexes(config.application, root_path) |
2423 dev_appserver_index.SetupIndexes(config.application, root_path) |
2403 |
2424 |
2404 infile = cStringIO.StringIO(self.rfile.read( |
2425 infile = cStringIO.StringIO(self.rfile.read( |
2405 int(self.headers.get('content-length', 0)))) |
2426 int(self.headers.get('content-length', 0)))) |
|
2427 |
|
2428 request_size = len(infile.getvalue()) |
|
2429 if request_size > MAX_REQUEST_SIZE: |
|
2430 msg = ('HTTP request was too large: %d. The limit is: %d.' |
|
2431 % (request_size, MAX_REQUEST_SIZE)) |
|
2432 logging.error(msg) |
|
2433 self.send_response(httplib.REQUEST_ENTITY_TOO_LARGE, msg) |
|
2434 return |
|
2435 |
2406 outfile = cStringIO.StringIO() |
2436 outfile = cStringIO.StringIO() |
2407 try: |
2437 try: |
2408 dispatcher.Dispatch(self.path, |
2438 dispatcher.Dispatch(self.path, |
2409 None, |
2439 None, |
2410 self.headers, |
2440 self.headers, |
2414 finally: |
2444 finally: |
2415 self.module_manager.UpdateModuleFileModificationTimes() |
2445 self.module_manager.UpdateModuleFileModificationTimes() |
2416 |
2446 |
2417 outfile.flush() |
2447 outfile.flush() |
2418 outfile.seek(0) |
2448 outfile.seek(0) |
|
2449 |
2419 status_code, status_message, header_data, body = RewriteResponse(outfile) |
2450 status_code, status_message, header_data, body = RewriteResponse(outfile) |
|
2451 |
|
2452 runtime_response_size = len(outfile.getvalue()) |
|
2453 if runtime_response_size > MAX_RUNTIME_RESPONSE_SIZE: |
|
2454 status_code = 403 |
|
2455 status_message = 'Forbidden' |
|
2456 new_headers = [] |
|
2457 for header in header_data.split('\n'): |
|
2458 if not header.lower().startswith('content-length'): |
|
2459 new_headers.append(header) |
|
2460 header_data = '\n'.join(new_headers) |
|
2461 body = ('HTTP response was too large: %d. The limit is: %d.' |
|
2462 % (runtime_response_size, MAX_RUNTIME_RESPONSE_SIZE)) |
2420 |
2463 |
2421 except yaml_errors.EventListenerError, e: |
2464 except yaml_errors.EventListenerError, e: |
2422 title = 'Fatal error when loading application configuration' |
2465 title = 'Fatal error when loading application configuration' |
2423 msg = '%s:\n%s' % (title, str(e)) |
2466 msg = '%s:\n%s' % (title, str(e)) |
2424 logging.error(msg) |
2467 logging.error(msg) |