changeset 2864 | 2e0b0af889be |
parent 2413 | d0b7dac5325c |
child 3031 | 7678f72140e6 |
2862:27971a13089f | 2864:2e0b0af889be |
---|---|
47 pass |
47 pass |
48 |
48 |
49 import dummy_thread |
49 import dummy_thread |
50 import email.Utils |
50 import email.Utils |
51 import errno |
51 import errno |
52 import heapq |
|
52 import httplib |
53 import httplib |
53 import imp |
54 import imp |
54 import inspect |
55 import inspect |
55 import itertools |
56 import itertools |
56 import locale |
57 import locale |
59 import mimetypes |
60 import mimetypes |
60 import os |
61 import os |
61 import pickle |
62 import pickle |
62 import pprint |
63 import pprint |
63 import random |
64 import random |
65 import select |
|
64 |
66 |
65 import re |
67 import re |
66 import sre_compile |
68 import sre_compile |
67 import sre_constants |
69 import sre_constants |
68 import sre_parse |
70 import sre_parse |
69 |
71 |
70 import mimetypes |
|
71 import socket |
72 import socket |
72 import sys |
73 import sys |
73 import time |
74 import time |
74 import traceback |
75 import traceback |
75 import types |
76 import types |
89 from google.appengine.api import user_service_stub |
90 from google.appengine.api import user_service_stub |
90 from google.appengine.api import yaml_errors |
91 from google.appengine.api import yaml_errors |
91 from google.appengine.api.capabilities import capability_stub |
92 from google.appengine.api.capabilities import capability_stub |
92 from google.appengine.api.labs.taskqueue import taskqueue_stub |
93 from google.appengine.api.labs.taskqueue import taskqueue_stub |
93 from google.appengine.api.memcache import memcache_stub |
94 from google.appengine.api.memcache import memcache_stub |
95 from google.appengine.api.xmpp import xmpp_service_stub |
|
94 |
96 |
95 from google.appengine import dist |
97 from google.appengine import dist |
96 |
98 |
97 from google.appengine.tools import dev_appserver_index |
99 from google.appengine.tools import dev_appserver_index |
98 from google.appengine.tools import dev_appserver_login |
100 from google.appengine.tools import dev_appserver_login |
109 SCRIPT_TEMPLATE = 'logging_console.js' |
111 SCRIPT_TEMPLATE = 'logging_console.js' |
110 MIDDLE_TEMPLATE = 'logging_console_middle.html' |
112 MIDDLE_TEMPLATE = 'logging_console_middle.html' |
111 FOOTER_TEMPLATE = 'logging_console_footer.html' |
113 FOOTER_TEMPLATE = 'logging_console_footer.html' |
112 |
114 |
113 DEFAULT_ENV = { |
115 DEFAULT_ENV = { |
114 'GATEWAY_INTERFACE': 'CGI/1.1', |
116 'GATEWAY_INTERFACE': 'CGI/1.1', |
115 'AUTH_DOMAIN': 'gmail.com', |
117 'AUTH_DOMAIN': 'gmail.com', |
116 'TZ': 'UTC', |
118 'TZ': 'UTC', |
117 } |
119 } |
120 |
|
121 DEFAULT_SELECT_DELAY = 30.0 |
|
118 |
122 |
119 for ext, mime_type in (('.asc', 'text/plain'), |
123 for ext, mime_type in (('.asc', 'text/plain'), |
120 ('.diff', 'text/plain'), |
124 ('.diff', 'text/plain'), |
121 ('.csv', 'text/comma-separated-values'), |
125 ('.csv', 'text/comma-separated-values'), |
122 ('.rss', 'application/rss+xml'), |
126 ('.rss', 'application/rss+xml'), |
132 |
136 |
133 SITE_PACKAGES = os.path.normcase(os.path.join(os.path.dirname(os.__file__), |
137 SITE_PACKAGES = os.path.normcase(os.path.join(os.path.dirname(os.__file__), |
134 'site-packages')) |
138 'site-packages')) |
135 |
139 |
136 |
140 |
141 |
|
137 class Error(Exception): |
142 class Error(Exception): |
138 """Base-class for exceptions in this module.""" |
143 """Base-class for exceptions in this module.""" |
139 |
144 |
145 |
|
140 class InvalidAppConfigError(Error): |
146 class InvalidAppConfigError(Error): |
141 """The supplied application configuration file is invalid.""" |
147 """The supplied application configuration file is invalid.""" |
142 |
148 |
149 |
|
143 class AppConfigNotFoundError(Error): |
150 class AppConfigNotFoundError(Error): |
144 """Application configuration file not found.""" |
151 """Application configuration file not found.""" |
145 |
152 |
153 |
|
146 class TemplatesNotLoadedError(Error): |
154 class TemplatesNotLoadedError(Error): |
147 """Templates for the debugging console were not loaded.""" |
155 """Templates for the debugging console were not loaded.""" |
156 |
|
148 |
157 |
149 |
158 |
150 def SplitURL(relative_url): |
159 def SplitURL(relative_url): |
151 """Splits a relative URL into its path and query-string components. |
160 """Splits a relative URL into its path and query-string components. |
152 |
161 |
157 Returns: |
166 Returns: |
158 Tuple (script_name, query_string) where: |
167 Tuple (script_name, query_string) where: |
159 script_name: Relative URL of the script that was accessed. |
168 script_name: Relative URL of the script that was accessed. |
160 query_string: String containing everything after the '?' character. |
169 query_string: String containing everything after the '?' character. |
161 """ |
170 """ |
162 scheme, netloc, path, query, fragment = urlparse.urlsplit(relative_url) |
171 (unused_scheme, unused_netloc, path, query, |
172 unused_fragment) = urlparse.urlsplit(relative_url) |
|
163 return path, query |
173 return path, query |
164 |
174 |
165 |
175 |
166 def GetFullURL(server_name, server_port, relative_url): |
176 def GetFullURL(server_name, server_port, relative_url): |
167 """Returns the full, original URL used to access the relative URL. |
177 """Returns the full, original URL used to access the relative URL. |
178 if str(server_port) != '80': |
188 if str(server_port) != '80': |
179 netloc = '%s:%s' % (server_name, server_port) |
189 netloc = '%s:%s' % (server_name, server_port) |
180 else: |
190 else: |
181 netloc = server_name |
191 netloc = server_name |
182 return 'http://%s%s' % (netloc, relative_url) |
192 return 'http://%s%s' % (netloc, relative_url) |
193 |
|
183 |
194 |
184 |
195 |
185 class URLDispatcher(object): |
196 class URLDispatcher(object): |
186 """Base-class for handling HTTP requests.""" |
197 """Base-class for handling HTTP requests.""" |
187 |
198 |
229 redirect. |
240 redirect. |
230 |
241 |
231 Args: |
242 Args: |
232 dispatched_output: StringIO buffer containing the results from the |
243 dispatched_output: StringIO buffer containing the results from the |
233 dispatched |
244 dispatched |
245 original_output: The original output file. |
|
234 """ |
246 """ |
235 original_output.write(dispatched_output.read()) |
247 original_output.write(dispatched_output.read()) |
236 |
248 |
237 |
249 |
238 class URLMatcher(object): |
250 class URLMatcher(object): |
265 matched by the regex if present. |
277 matched by the regex if present. |
266 requires_login: True if the user must be logged-in before accessing this |
278 requires_login: True if the user must be logged-in before accessing this |
267 URL; False if anyone can access this URL. |
279 URL; False if anyone can access this URL. |
268 admin_only: True if the user must be a logged-in administrator to |
280 admin_only: True if the user must be a logged-in administrator to |
269 access the URL; False if anyone can access the URL. |
281 access the URL; False if anyone can access the URL. |
282 |
|
283 Raises: |
|
284 TypeError: if dispatcher is not a URLDispatcher sub-class instance. |
|
285 InvalidAppConfigError: if regex isn't valid. |
|
270 """ |
286 """ |
271 if not isinstance(dispatcher, URLDispatcher): |
287 if not isinstance(dispatcher, URLDispatcher): |
272 raise TypeError('dispatcher must be a URLDispatcher sub-class') |
288 raise TypeError('dispatcher must be a URLDispatcher sub-class') |
273 |
289 |
274 if regex.startswith('^') or regex.endswith('$'): |
290 if regex.startswith('^') or regex.endswith('$'): |
292 The supplied relative_url may include the query string (i.e., the '?' |
308 The supplied relative_url may include the query string (i.e., the '?' |
293 character and everything following). |
309 character and everything following). |
294 |
310 |
295 Args: |
311 Args: |
296 relative_url: Relative URL being accessed in a request. |
312 relative_url: Relative URL being accessed in a request. |
313 split_url: Used for dependency injection. |
|
297 |
314 |
298 Returns: |
315 Returns: |
299 Tuple (dispatcher, matched_path, requires_login, admin_only), which are |
316 Tuple (dispatcher, matched_path, requires_login, admin_only), which are |
300 the corresponding values passed to AddURL when the matching URL pattern |
317 the corresponding values passed to AddURL when the matching URL pattern |
301 was added to this matcher. The matched_path will have back-references |
318 was added to this matcher. The matched_path will have back-references |
302 replaced using values matched by the URL pattern. If no match was found, |
319 replaced using values matched by the URL pattern. If no match was found, |
303 dispatcher will be None. |
320 dispatcher will be None. |
304 """ |
321 """ |
305 adjusted_url, query_string = split_url(relative_url) |
322 adjusted_url, unused_query_string = split_url(relative_url) |
306 |
323 |
307 for url_tuple in self._url_patterns: |
324 for url_tuple in self._url_patterns: |
308 url_re, dispatcher, path, requires_login, admin_only = url_tuple |
325 url_re, dispatcher, path, requires_login, admin_only = url_tuple |
309 the_match = url_re.match(adjusted_url) |
326 the_match = url_re.match(adjusted_url) |
310 |
327 |
321 |
338 |
322 Returns: |
339 Returns: |
323 A set of URLDispatcher objects. |
340 A set of URLDispatcher objects. |
324 """ |
341 """ |
325 return set([url_tuple[1] for url_tuple in self._url_patterns]) |
342 return set([url_tuple[1] for url_tuple in self._url_patterns]) |
343 |
|
326 |
344 |
327 |
345 |
328 class MatcherDispatcher(URLDispatcher): |
346 class MatcherDispatcher(URLDispatcher): |
329 """Dispatcher across multiple URLMatcher instances.""" |
347 """Dispatcher across multiple URLMatcher instances.""" |
330 |
348 |
336 """Initializer. |
354 """Initializer. |
337 |
355 |
338 Args: |
356 Args: |
339 login_url: Relative URL which should be used for handling user logins. |
357 login_url: Relative URL which should be used for handling user logins. |
340 url_matchers: Sequence of URLMatcher objects. |
358 url_matchers: Sequence of URLMatcher objects. |
341 get_user_info, login_redirect: Used for dependency injection. |
359 get_user_info: Used for dependency injection. |
360 login_redirect: Used for dependency injection. |
|
342 """ |
361 """ |
343 self._login_url = login_url |
362 self._login_url = login_url |
344 self._url_matchers = tuple(url_matchers) |
363 self._url_matchers = tuple(url_matchers) |
345 self._get_user_info = get_user_info |
364 self._get_user_info = get_user_info |
346 self._login_redirect = login_redirect |
365 self._login_redirect = login_redirect |
357 Matchers are checked in the order they were supplied to the constructor. |
376 Matchers are checked in the order they were supplied to the constructor. |
358 If no matcher matches, a 404 error will be written to the outfile. The |
377 If no matcher matches, a 404 error will be written to the outfile. The |
359 path variable supplied to this method is ignored. |
378 path variable supplied to this method is ignored. |
360 """ |
379 """ |
361 cookies = ', '.join(headers.getheaders('cookie')) |
380 cookies = ', '.join(headers.getheaders('cookie')) |
362 email, admin, user_id = self._get_user_info(cookies) |
381 email_addr, admin, user_id = self._get_user_info(cookies) |
363 |
382 |
364 for matcher in self._url_matchers: |
383 for matcher in self._url_matchers: |
365 dispatcher, matched_path, requires_login, admin_only = matcher.Match(relative_url) |
384 dispatcher, matched_path, requires_login, admin_only = matcher.Match( |
385 relative_url) |
|
366 if dispatcher is None: |
386 if dispatcher is None: |
367 continue |
387 continue |
368 |
388 |
369 logging.debug('Matched "%s" to %s with path %s', |
389 logging.debug('Matched "%s" to %s with path %s', |
370 relative_url, dispatcher, matched_path) |
390 relative_url, dispatcher, matched_path) |
371 |
391 |
372 if (requires_login or admin_only) and not email: |
392 if (requires_login or admin_only) and not email_addr: |
373 logging.debug('Login required, redirecting user') |
393 logging.debug('Login required, redirecting user') |
374 self._login_redirect( |
394 self._login_redirect(self._login_url, |
375 self._login_url, |
395 base_env_dict['SERVER_NAME'], |
376 base_env_dict['SERVER_NAME'], |
396 base_env_dict['SERVER_PORT'], |
377 base_env_dict['SERVER_PORT'], |
397 relative_url, |
378 relative_url, |
398 outfile) |
379 outfile) |
|
380 elif admin_only and not admin: |
399 elif admin_only and not admin: |
381 outfile.write('Status: %d Not authorized\r\n' |
400 outfile.write('Status: %d Not authorized\r\n' |
382 '\r\n' |
401 '\r\n' |
383 'Current logged in user %s is not ' |
402 'Current logged in user %s is not ' |
384 'authorized to view this page.' |
403 'authorized to view this page.' |
385 % (httplib.FORBIDDEN, email)) |
404 % (httplib.FORBIDDEN, email_addr)) |
386 else: |
405 else: |
387 forward = dispatcher.Dispatch(relative_url, |
406 forward = dispatcher.Dispatch(relative_url, |
388 matched_path, |
407 matched_path, |
389 headers, |
408 headers, |
390 infile, |
409 infile, |
391 outfile, |
410 outfile, |
392 base_env_dict=base_env_dict) |
411 base_env_dict=base_env_dict) |
393 |
412 |
394 if forward: |
413 if forward: |
395 new_path, new_headers, new_input = forward |
414 new_path, new_headers, new_input = forward |
396 logging.info('Internal redirection to %s' % new_path) |
415 logging.info('Internal redirection to %s', new_path) |
397 new_outfile = cStringIO.StringIO() |
416 new_outfile = cStringIO.StringIO() |
398 self.Dispatch(new_path, |
417 self.Dispatch(new_path, |
399 None, |
418 None, |
400 new_headers, |
419 new_headers, |
401 new_input, |
420 new_input, |
411 'Not found error: %s did not match any patterns ' |
430 'Not found error: %s did not match any patterns ' |
412 'in application configuration.' |
431 'in application configuration.' |
413 % (httplib.NOT_FOUND, relative_url)) |
432 % (httplib.NOT_FOUND, relative_url)) |
414 |
433 |
415 |
434 |
435 |
|
416 class ApplicationLoggingHandler(logging.Handler): |
436 class ApplicationLoggingHandler(logging.Handler): |
417 """Python Logging handler that displays the debugging console to users.""" |
437 """Python Logging handler that displays the debugging console to users.""" |
418 |
438 |
419 _COOKIE_NAME = '_ah_severity' |
439 _COOKIE_NAME = '_ah_severity' |
420 |
440 |
485 HTTP_COOKIE entry to see if the accessing user has any logging-related |
505 HTTP_COOKIE entry to see if the accessing user has any logging-related |
486 cookies set. |
506 cookies set. |
487 outfile: Output stream to which the console should be written if either |
507 outfile: Output stream to which the console should be written if either |
488 a debug parameter was supplied or a logging cookie is present. |
508 a debug parameter was supplied or a logging cookie is present. |
489 """ |
509 """ |
490 script_name, query_string = SplitURL(relative_url) |
510 unused_script_name, query_string = SplitURL(relative_url) |
491 param_dict = cgi.parse_qs(query_string, True) |
511 param_dict = cgi.parse_qs(query_string, True) |
492 cookie_dict = Cookie.SimpleCookie(env.get('HTTP_COOKIE', '')) |
512 cookie_dict = Cookie.SimpleCookie(env.get('HTTP_COOKIE', '')) |
493 if 'debug' not in param_dict and self._COOKIE_NAME not in cookie_dict: |
513 if 'debug' not in param_dict and self._COOKIE_NAME not in cookie_dict: |
494 return |
514 return |
495 |
515 |
552 env['CONTENT_TYPE'] = headers.getheader('content-type', |
572 env['CONTENT_TYPE'] = headers.getheader('content-type', |
553 'application/x-www-form-urlencoded') |
573 'application/x-www-form-urlencoded') |
554 env['CONTENT_LENGTH'] = headers.getheader('content-length', '') |
574 env['CONTENT_LENGTH'] = headers.getheader('content-length', '') |
555 |
575 |
556 cookies = ', '.join(headers.getheaders('cookie')) |
576 cookies = ', '.join(headers.getheaders('cookie')) |
557 email, admin, user_id = get_user_info(cookies) |
577 email_addr, admin, user_id = get_user_info(cookies) |
558 env['USER_EMAIL'] = email |
578 env['USER_EMAIL'] = email_addr |
559 env['USER_ID'] = user_id |
579 env['USER_ID'] = user_id |
560 if admin: |
580 if admin: |
561 env['USER_IS_ADMIN'] = '1' |
581 env['USER_IS_ADMIN'] = '1' |
562 |
582 |
563 for key in headers: |
583 for key in headers: |
581 |
601 |
582 def NotImplementedFake(*args, **kwargs): |
602 def NotImplementedFake(*args, **kwargs): |
583 """Fake for methods/functions that are not implemented in the production |
603 """Fake for methods/functions that are not implemented in the production |
584 environment. |
604 environment. |
585 """ |
605 """ |
586 raise NotImplementedError("This class/method is not available.") |
606 raise NotImplementedError('This class/method is not available.') |
587 |
607 |
588 |
608 |
589 class NotImplementedFakeClass(object): |
609 class NotImplementedFakeClass(object): |
590 """Fake class for classes that are not implemented in the production |
610 """Fake class for classes that are not implemented in the production env. |
591 environment. |
|
592 """ |
611 """ |
593 __init__ = NotImplementedFake |
612 __init__ = NotImplementedFake |
594 |
613 |
595 |
614 |
596 def IsEncodingsModule(module_name): |
615 def IsEncodingsModule(module_name): |
625 |
644 |
626 |
645 |
627 def FakeURandom(n): |
646 def FakeURandom(n): |
628 """Fake version of os.urandom.""" |
647 """Fake version of os.urandom.""" |
629 bytes = '' |
648 bytes = '' |
630 for i in xrange(n): |
649 for _ in range(n): |
631 bytes += chr(random.randint(0, 255)) |
650 bytes += chr(random.randint(0, 255)) |
632 return bytes |
651 return bytes |
633 |
652 |
634 |
653 |
635 def FakeUname(): |
654 def FakeUname(): |
663 if value not in (None, '', 'C', 'POSIX'): |
682 if value not in (None, '', 'C', 'POSIX'): |
664 raise locale.Error('locale emulation only supports "C" locale') |
683 raise locale.Error('locale emulation only supports "C" locale') |
665 return original_setlocale(category, 'C') |
684 return original_setlocale(category, 'C') |
666 |
685 |
667 |
686 |
668 def FakeOpen(file, flags, mode=0777): |
687 def FakeOpen(filename, flags, mode=0777): |
669 """Fake version of os.open.""" |
688 """Fake version of os.open.""" |
670 raise OSError(errno.EPERM, "Operation not permitted", file) |
689 raise OSError(errno.EPERM, "Operation not permitted", filename) |
671 |
690 |
672 |
691 |
673 def FakeRename(src, dst): |
692 def FakeRename(src, dst): |
674 """Fake version of os.rename.""" |
693 """Fake version of os.rename.""" |
675 raise OSError(errno.EPERM, "Operation not permitted", src) |
694 raise OSError(errno.EPERM, "Operation not permitted", src) |
709 if os.path.commonprefix([file_dir, fixed_parent]) == fixed_parent: |
728 if os.path.commonprefix([file_dir, fixed_parent]) == fixed_parent: |
710 return True |
729 return True |
711 return False |
730 return False |
712 |
731 |
713 SHARED_MODULE_PREFIXES = set([ |
732 SHARED_MODULE_PREFIXES = set([ |
714 'google', |
733 'google', |
715 'logging', |
734 'logging', |
716 'sys', |
735 'sys', |
717 'warnings', |
736 'warnings', |
718 |
737 |
719 |
738 |
720 |
739 |
721 |
740 |
722 're', |
741 're', |
723 'sre_compile', |
742 'sre_compile', |
724 'sre_constants', |
743 'sre_constants', |
725 'sre_parse', |
744 'sre_parse', |
726 |
745 |
727 |
746 |
728 |
747 |
729 |
748 |
730 'wsgiref', |
749 'wsgiref', |
731 ]) |
750 ]) |
732 |
751 |
733 NOT_SHARED_MODULE_PREFIXES = set([ |
752 NOT_SHARED_MODULE_PREFIXES = set([ |
734 'google.appengine.ext', |
753 'google.appengine.ext', |
735 ]) |
754 ]) |
736 |
755 |
737 |
756 |
738 def ModuleNameHasPrefix(module_name, prefix_set): |
757 def ModuleNameHasPrefix(module_name, prefix_set): |
739 """Determines if a module's name belongs to a set of prefix strings. |
758 """Determines if a module's name belongs to a set of prefix strings. |
786 |
805 |
787 return output_dict |
806 return output_dict |
788 |
807 |
789 |
808 |
790 def GeneratePythonPaths(*p): |
809 def GeneratePythonPaths(*p): |
791 """Generate all valid filenames for the given file |
810 """Generate all valid filenames for the given file. |
792 |
811 |
793 Args: |
812 Args: |
794 p: Positional args are the folders to the file and finally the file |
813 p: Positional args are the folders to the file and finally the file |
795 without a suffix. |
814 without a suffix. |
796 |
815 |
812 ALLOWED_FILES = set(os.path.normcase(filename) |
831 ALLOWED_FILES = set(os.path.normcase(filename) |
813 for filename in mimetypes.knownfiles |
832 for filename in mimetypes.knownfiles |
814 if os.path.isfile(filename)) |
833 if os.path.isfile(filename)) |
815 |
834 |
816 ALLOWED_DIRS = set([ |
835 ALLOWED_DIRS = set([ |
817 os.path.normcase(os.path.realpath(os.path.dirname(os.__file__))), |
836 os.path.normcase(os.path.realpath(os.path.dirname(os.__file__))), |
818 os.path.normcase(os.path.abspath(os.path.dirname(os.__file__))), |
837 os.path.normcase(os.path.abspath(os.path.dirname(os.__file__))), |
819 ]) |
838 ]) |
820 |
839 |
821 NOT_ALLOWED_DIRS = set([ |
840 NOT_ALLOWED_DIRS = set([ |
822 |
841 |
823 |
842 |
824 |
843 |
825 |
844 |
826 SITE_PACKAGES, |
845 SITE_PACKAGES, |
827 ]) |
846 ]) |
828 |
847 |
829 ALLOWED_SITE_PACKAGE_DIRS = set( |
848 ALLOWED_SITE_PACKAGE_DIRS = set( |
830 os.path.normcase(os.path.abspath(os.path.join(SITE_PACKAGES, path))) |
849 os.path.normcase(os.path.abspath(os.path.join(SITE_PACKAGES, path))) |
831 for path in [ |
850 for path in [ |
832 |
851 |
833 ]) |
852 ]) |
834 |
853 |
835 ALLOWED_SITE_PACKAGE_FILES = set( |
854 ALLOWED_SITE_PACKAGE_FILES = set( |
836 os.path.normcase(os.path.abspath(os.path.join( |
855 os.path.normcase(os.path.abspath(os.path.join( |
837 os.path.dirname(os.__file__), 'site-packages', path))) |
856 os.path.dirname(os.__file__), 'site-packages', path))) |
838 for path in itertools.chain(*[ |
857 for path in itertools.chain(*[ |
839 |
858 |
840 [os.path.join('Crypto')], |
859 [os.path.join('Crypto')], |
841 GeneratePythonPaths('Crypto', '__init__'), |
860 GeneratePythonPaths('Crypto', '__init__'), |
842 [os.path.join('Crypto', 'Cipher')], |
861 [os.path.join('Crypto', 'Cipher')], |
843 GeneratePythonPaths('Crypto', 'Cipher', '__init__'), |
862 GeneratePythonPaths('Crypto', 'Cipher', '__init__'), |
844 GeneratePythonPaths('Crypto', 'Cipher', 'AES'), |
863 GeneratePythonPaths('Crypto', 'Cipher', 'AES'), |
845 GeneratePythonPaths('Crypto', 'Cipher', 'ARC2'), |
864 GeneratePythonPaths('Crypto', 'Cipher', 'ARC2'), |
846 GeneratePythonPaths('Crypto', 'Cipher', 'ARC4'), |
865 GeneratePythonPaths('Crypto', 'Cipher', 'ARC4'), |
847 GeneratePythonPaths('Crypto', 'Cipher', 'Blowfish'), |
866 GeneratePythonPaths('Crypto', 'Cipher', 'Blowfish'), |
848 GeneratePythonPaths('Crypto', 'Cipher', 'CAST'), |
867 GeneratePythonPaths('Crypto', 'Cipher', 'CAST'), |
849 GeneratePythonPaths('Crypto', 'Cipher', 'DES'), |
868 GeneratePythonPaths('Crypto', 'Cipher', 'DES'), |
850 GeneratePythonPaths('Crypto', 'Cipher', 'DES3'), |
869 GeneratePythonPaths('Crypto', 'Cipher', 'DES3'), |
851 GeneratePythonPaths('Crypto', 'Cipher', 'XOR'), |
870 GeneratePythonPaths('Crypto', 'Cipher', 'XOR'), |
852 [os.path.join('Crypto', 'Hash')], |
871 [os.path.join('Crypto', 'Hash')], |
853 GeneratePythonPaths('Crypto', 'Hash', '__init__'), |
872 GeneratePythonPaths('Crypto', 'Hash', '__init__'), |
854 GeneratePythonPaths('Crypto', 'Hash', 'HMAC'), |
873 GeneratePythonPaths('Crypto', 'Hash', 'HMAC'), |
855 os.path.join('Crypto', 'Hash', 'MD2'), |
874 os.path.join('Crypto', 'Hash', 'MD2'), |
856 os.path.join('Crypto', 'Hash', 'MD4'), |
875 os.path.join('Crypto', 'Hash', 'MD4'), |
857 GeneratePythonPaths('Crypto', 'Hash', 'MD5'), |
876 GeneratePythonPaths('Crypto', 'Hash', 'MD5'), |
858 GeneratePythonPaths('Crypto', 'Hash', 'SHA'), |
877 GeneratePythonPaths('Crypto', 'Hash', 'SHA'), |
859 os.path.join('Crypto', 'Hash', 'SHA256'), |
878 os.path.join('Crypto', 'Hash', 'SHA256'), |
860 os.path.join('Crypto', 'Hash', 'RIPEMD'), |
879 os.path.join('Crypto', 'Hash', 'RIPEMD'), |
861 [os.path.join('Crypto', 'Protocol')], |
880 [os.path.join('Crypto', 'Protocol')], |
862 GeneratePythonPaths('Crypto', 'Protocol', '__init__'), |
881 GeneratePythonPaths('Crypto', 'Protocol', '__init__'), |
863 GeneratePythonPaths('Crypto', 'Protocol', 'AllOrNothing'), |
882 GeneratePythonPaths('Crypto', 'Protocol', 'AllOrNothing'), |
864 GeneratePythonPaths('Crypto', 'Protocol', 'Chaffing'), |
883 GeneratePythonPaths('Crypto', 'Protocol', 'Chaffing'), |
865 [os.path.join('Crypto', 'PublicKey')], |
884 [os.path.join('Crypto', 'PublicKey')], |
866 GeneratePythonPaths('Crypto', 'PublicKey', '__init__'), |
885 GeneratePythonPaths('Crypto', 'PublicKey', '__init__'), |
867 GeneratePythonPaths('Crypto', 'PublicKey', 'DSA'), |
886 GeneratePythonPaths('Crypto', 'PublicKey', 'DSA'), |
868 GeneratePythonPaths('Crypto', 'PublicKey', 'ElGamal'), |
887 GeneratePythonPaths('Crypto', 'PublicKey', 'ElGamal'), |
869 GeneratePythonPaths('Crypto', 'PublicKey', 'RSA'), |
888 GeneratePythonPaths('Crypto', 'PublicKey', 'RSA'), |
870 GeneratePythonPaths('Crypto', 'PublicKey', 'pubkey'), |
889 GeneratePythonPaths('Crypto', 'PublicKey', 'pubkey'), |
871 GeneratePythonPaths('Crypto', 'PublicKey', 'qNEW'), |
890 GeneratePythonPaths('Crypto', 'PublicKey', 'qNEW'), |
872 [os.path.join('Crypto', 'Util')], |
891 [os.path.join('Crypto', 'Util')], |
873 GeneratePythonPaths('Crypto', 'Util', '__init__'), |
892 GeneratePythonPaths('Crypto', 'Util', '__init__'), |
874 GeneratePythonPaths('Crypto', 'Util', 'RFC1751'), |
893 GeneratePythonPaths('Crypto', 'Util', 'RFC1751'), |
875 GeneratePythonPaths('Crypto', 'Util', 'number'), |
894 GeneratePythonPaths('Crypto', 'Util', 'number'), |
876 GeneratePythonPaths('Crypto', 'Util', 'randpool'), |
895 GeneratePythonPaths('Crypto', 'Util', 'randpool'), |
877 ])) |
896 ])) |
878 |
897 |
879 _original_file = file |
898 _original_file = file |
880 |
899 |
881 _root_path = None |
900 _root_path = None |
882 _application_paths = None |
901 _application_paths = None |
910 |
929 |
911 FakeFile._availability_cache = {} |
930 FakeFile._availability_cache = {} |
912 |
931 |
913 @staticmethod |
932 @staticmethod |
914 def SetAllowSkippedFiles(allow_skipped_files): |
933 def SetAllowSkippedFiles(allow_skipped_files): |
915 """Configures access to files matching FakeFile._skip_files |
934 """Configures access to files matching FakeFile._skip_files. |
916 |
935 |
917 Args: |
936 Args: |
918 allow_skipped_files: Boolean whether to allow access to skipped files |
937 allow_skipped_files: Boolean whether to allow access to skipped files |
919 """ |
938 """ |
920 FakeFile._allow_skipped_files = allow_skipped_files |
939 FakeFile._allow_skipped_files = allow_skipped_files |
1104 'stuff'), the returned value will just be that module name ('stuff'). |
1123 'stuff'), the returned value will just be that module name ('stuff'). |
1105 """ |
1124 """ |
1106 return fullname.rsplit('.', 1)[-1] |
1125 return fullname.rsplit('.', 1)[-1] |
1107 |
1126 |
1108 |
1127 |
1128 |
|
1109 class CouldNotFindModuleError(ImportError): |
1129 class CouldNotFindModuleError(ImportError): |
1110 """Raised when a module could not be found. |
1130 """Raised when a module could not be found. |
1111 |
1131 |
1112 In contrast to when a module has been found, but cannot be loaded because of |
1132 In contrast to when a module has been found, but cannot be loaded because of |
1113 hardening restrictions. |
1133 hardening restrictions. |
1114 """ |
1134 """ |
1115 |
1135 |
1116 |
1136 |
1117 def Trace(func): |
1137 def Trace(func): |
1118 """Decorator that logs the call stack of the HardenedModulesHook class as |
1138 """Call stack logging decorator for HardenedModulesHook class. |
1139 |
|
1140 This decorator logs the call stack of the HardenedModulesHook class as |
|
1119 it executes, indenting logging messages based on the current stack depth. |
1141 it executes, indenting logging messages based on the current stack depth. |
1120 """ |
1142 |
1121 def decorate(self, *args, **kwargs): |
1143 Args: |
1144 func: the function to decorate. |
|
1145 |
|
1146 Returns: |
|
1147 The decorated function. |
|
1148 """ |
|
1149 |
|
1150 def Decorate(self, *args, **kwargs): |
|
1122 args_to_show = [] |
1151 args_to_show = [] |
1123 if args is not None: |
1152 if args is not None: |
1124 args_to_show.extend(str(argument) for argument in args) |
1153 args_to_show.extend(str(argument) for argument in args) |
1125 if kwargs is not None: |
1154 if kwargs is not None: |
1126 args_to_show.extend('%s=%s' % (key, value) |
1155 args_to_show.extend('%s=%s' % (key, value) |
1134 return func(self, *args, **kwargs) |
1163 return func(self, *args, **kwargs) |
1135 finally: |
1164 finally: |
1136 self._indent_level -= 1 |
1165 self._indent_level -= 1 |
1137 self.log('Exiting %s(%s)', func.func_name, args_string) |
1166 self.log('Exiting %s(%s)', func.func_name, args_string) |
1138 |
1167 |
1139 return decorate |
1168 return Decorate |
1140 |
1169 |
1141 |
1170 |
1142 class HardenedModulesHook(object): |
1171 class HardenedModulesHook(object): |
1143 """Meta import hook that restricts the modules used by applications to match |
1172 """Meta import hook that restricts the modules used by applications to match |
1144 the production environment. |
1173 the production environment. |
1171 if HardenedModulesHook.ENABLE_LOGGING: |
1200 if HardenedModulesHook.ENABLE_LOGGING: |
1172 indent = self._indent_level * ' ' |
1201 indent = self._indent_level * ' ' |
1173 print >>sys.stderr, indent + (message % args) |
1202 print >>sys.stderr, indent + (message % args) |
1174 |
1203 |
1175 _WHITE_LIST_C_MODULES = [ |
1204 _WHITE_LIST_C_MODULES = [ |
1176 'AES', |
1205 'AES', |
1177 'ARC2', |
1206 'ARC2', |
1178 'ARC4', |
1207 'ARC4', |
1179 'Blowfish', |
1208 'Blowfish', |
1180 'CAST', |
1209 'CAST', |
1181 'DES', |
1210 'DES', |
1182 'DES3', |
1211 'DES3', |
1183 'MD2', |
1212 'MD2', |
1184 'MD4', |
1213 'MD4', |
1185 'RIPEMD', |
1214 'RIPEMD', |
1186 'SHA256', |
1215 'SHA256', |
1187 'XOR', |
1216 'XOR', |
1188 |
1217 |
1189 '_Crypto_Cipher__AES', |
1218 '_Crypto_Cipher__AES', |
1190 '_Crypto_Cipher__ARC2', |
1219 '_Crypto_Cipher__ARC2', |
1191 '_Crypto_Cipher__ARC4', |
1220 '_Crypto_Cipher__ARC4', |
1192 '_Crypto_Cipher__Blowfish', |
1221 '_Crypto_Cipher__Blowfish', |
1193 '_Crypto_Cipher__CAST', |
1222 '_Crypto_Cipher__CAST', |
1194 '_Crypto_Cipher__DES', |
1223 '_Crypto_Cipher__DES', |
1195 '_Crypto_Cipher__DES3', |
1224 '_Crypto_Cipher__DES3', |
1196 '_Crypto_Cipher__XOR', |
1225 '_Crypto_Cipher__XOR', |
1197 '_Crypto_Hash__MD2', |
1226 '_Crypto_Hash__MD2', |
1198 '_Crypto_Hash__MD4', |
1227 '_Crypto_Hash__MD4', |
1199 '_Crypto_Hash__RIPEMD', |
1228 '_Crypto_Hash__RIPEMD', |
1200 '_Crypto_Hash__SHA256', |
1229 '_Crypto_Hash__SHA256', |
1201 'array', |
1230 'array', |
1202 'binascii', |
1231 'binascii', |
1203 'bz2', |
1232 'bz2', |
1204 'cmath', |
1233 'cmath', |
1205 'collections', |
1234 'collections', |
1206 'crypt', |
1235 'crypt', |
1207 'cStringIO', |
1236 'cStringIO', |
1208 'datetime', |
1237 'datetime', |
1209 'errno', |
1238 'errno', |
1210 'exceptions', |
1239 'exceptions', |
1211 'gc', |
1240 'gc', |
1212 'itertools', |
1241 'itertools', |
1213 'math', |
1242 'math', |
1214 'md5', |
1243 'md5', |
1215 'operator', |
1244 'operator', |
1216 'posix', |
1245 'posix', |
1217 'posixpath', |
1246 'posixpath', |
1218 'pyexpat', |
1247 'pyexpat', |
1219 'sha', |
1248 'sha', |
1220 'struct', |
1249 'struct', |
1221 'sys', |
1250 'sys', |
1222 'time', |
1251 'time', |
1223 'timing', |
1252 'timing', |
1224 'unicodedata', |
1253 'unicodedata', |
1225 'zlib', |
1254 'zlib', |
1226 '_ast', |
1255 '_ast', |
1227 '_bisect', |
1256 '_bisect', |
1228 '_codecs', |
1257 '_codecs', |
1229 '_codecs_cn', |
1258 '_codecs_cn', |
1230 '_codecs_hk', |
1259 '_codecs_hk', |
1231 '_codecs_iso2022', |
1260 '_codecs_iso2022', |
1232 '_codecs_jp', |
1261 '_codecs_jp', |
1233 '_codecs_kr', |
1262 '_codecs_kr', |
1234 '_codecs_tw', |
1263 '_codecs_tw', |
1235 '_collections', |
1264 '_collections', |
1236 '_csv', |
1265 '_csv', |
1237 '_elementtree', |
1266 '_elementtree', |
1238 '_functools', |
1267 '_functools', |
1239 '_hashlib', |
1268 '_hashlib', |
1240 '_heapq', |
1269 '_heapq', |
1241 '_locale', |
1270 '_locale', |
1242 '_lsprof', |
1271 '_lsprof', |
1243 '_md5', |
1272 '_md5', |
1244 '_multibytecodec', |
1273 '_multibytecodec', |
1245 '_random', |
1274 '_random', |
1246 '_sha', |
1275 '_sha', |
1247 '_sha256', |
1276 '_sha256', |
1248 '_sha512', |
1277 '_sha512', |
1249 '_sre', |
1278 '_sre', |
1250 '_struct', |
1279 '_struct', |
1251 '_types', |
1280 '_types', |
1252 '_weakref', |
1281 '_weakref', |
1253 '__main__', |
1282 '__main__', |
1254 ] |
1283 ] |
1255 |
1284 |
1256 __CRYPTO_CIPHER_ALLOWED_MODULES = [ |
1285 __CRYPTO_CIPHER_ALLOWED_MODULES = [ |
1257 'MODE_CBC', |
1286 'MODE_CBC', |
1258 'MODE_CFB', |
1287 'MODE_CFB', |
1259 'MODE_CTR', |
1288 'MODE_CTR', |
1260 'MODE_ECB', |
1289 'MODE_ECB', |
1261 'MODE_OFB', |
1290 'MODE_OFB', |
1262 'block_size', |
1291 'block_size', |
1263 'key_size', |
1292 'key_size', |
1264 'new', |
1293 'new', |
1265 ] |
1294 ] |
1266 _WHITE_LIST_PARTIAL_MODULES = { |
1295 _WHITE_LIST_PARTIAL_MODULES = { |
1267 'Crypto.Cipher.AES': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1296 'Crypto.Cipher.AES': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1268 'Crypto.Cipher.ARC2': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1297 'Crypto.Cipher.ARC2': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1269 'Crypto.Cipher.Blowfish': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1298 'Crypto.Cipher.Blowfish': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1270 'Crypto.Cipher.CAST': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1299 'Crypto.Cipher.CAST': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1271 'Crypto.Cipher.DES': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1300 'Crypto.Cipher.DES': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1272 'Crypto.Cipher.DES3': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1301 'Crypto.Cipher.DES3': __CRYPTO_CIPHER_ALLOWED_MODULES, |
1273 |
1302 |
1274 'gc': [ |
1303 'gc': [ |
1275 'enable', |
1304 'enable', |
1276 'disable', |
1305 'disable', |
1277 'isenabled', |
1306 'isenabled', |
1278 'collect', |
1307 'collect', |
1279 'get_debug', |
1308 'get_debug', |
1280 'set_threshold', |
1309 'set_threshold', |
1281 'get_threshold', |
1310 'get_threshold', |
1282 'get_count' |
1311 'get_count' |
1283 ], |
1312 ], |
1284 |
1313 |
1285 |
1314 |
1286 |
1315 |
1287 'os': [ |
1316 'os': [ |
1288 'access', |
1317 'access', |
1289 'altsep', |
1318 'altsep', |
1290 'curdir', |
1319 'curdir', |
1291 'defpath', |
1320 'defpath', |
1292 'devnull', |
1321 'devnull', |
1293 'environ', |
1322 'environ', |
1294 'error', |
1323 'error', |
1295 'extsep', |
1324 'extsep', |
1296 'EX_NOHOST', |
1325 'EX_NOHOST', |
1297 'EX_NOINPUT', |
1326 'EX_NOINPUT', |
1298 'EX_NOPERM', |
1327 'EX_NOPERM', |
1299 'EX_NOUSER', |
1328 'EX_NOUSER', |
1300 'EX_OK', |
1329 'EX_OK', |
1301 'EX_OSERR', |
1330 'EX_OSERR', |
1302 'EX_OSFILE', |
1331 'EX_OSFILE', |
1303 'EX_PROTOCOL', |
1332 'EX_PROTOCOL', |
1304 'EX_SOFTWARE', |
1333 'EX_SOFTWARE', |
1305 'EX_TEMPFAIL', |
1334 'EX_TEMPFAIL', |
1306 'EX_UNAVAILABLE', |
1335 'EX_UNAVAILABLE', |
1307 'EX_USAGE', |
1336 'EX_USAGE', |
1308 'F_OK', |
1337 'F_OK', |
1309 'getcwd', |
1338 'getcwd', |
1310 'getcwdu', |
1339 'getcwdu', |
1311 'getenv', |
1340 'getenv', |
1312 'listdir', |
1341 'listdir', |
1313 'lstat', |
1342 'lstat', |
1314 'name', |
1343 'name', |
1315 'NGROUPS_MAX', |
1344 'NGROUPS_MAX', |
1316 'O_APPEND', |
1345 'O_APPEND', |
1317 'O_CREAT', |
1346 'O_CREAT', |
1318 'O_DIRECT', |
1347 'O_DIRECT', |
1319 'O_DIRECTORY', |
1348 'O_DIRECTORY', |
1320 'O_DSYNC', |
1349 'O_DSYNC', |
1321 'O_EXCL', |
1350 'O_EXCL', |
1322 'O_LARGEFILE', |
1351 'O_LARGEFILE', |
1323 'O_NDELAY', |
1352 'O_NDELAY', |
1324 'O_NOCTTY', |
1353 'O_NOCTTY', |
1325 'O_NOFOLLOW', |
1354 'O_NOFOLLOW', |
1326 'O_NONBLOCK', |
1355 'O_NONBLOCK', |
1327 'O_RDONLY', |
1356 'O_RDONLY', |
1328 'O_RDWR', |
1357 'O_RDWR', |
1329 'O_RSYNC', |
1358 'O_RSYNC', |
1330 'O_SYNC', |
1359 'O_SYNC', |
1331 'O_TRUNC', |
1360 'O_TRUNC', |
1332 'O_WRONLY', |
1361 'O_WRONLY', |
1333 'open', |
1362 'open', |
1334 'pardir', |
1363 'pardir', |
1335 'path', |
1364 'path', |
1336 'pathsep', |
1365 'pathsep', |
1337 'R_OK', |
1366 'R_OK', |
1338 'readlink', |
1367 'readlink', |
1339 'remove', |
1368 'remove', |
1340 'rename', |
1369 'rename', |
1341 'SEEK_CUR', |
1370 'SEEK_CUR', |
1342 'SEEK_END', |
1371 'SEEK_END', |
1343 'SEEK_SET', |
1372 'SEEK_SET', |
1344 'sep', |
1373 'sep', |
1345 'stat', |
1374 'stat', |
1346 'stat_float_times', |
1375 'stat_float_times', |
1347 'stat_result', |
1376 'stat_result', |
1348 'strerror', |
1377 'strerror', |
1349 'TMP_MAX', |
1378 'TMP_MAX', |
1350 'unlink', |
1379 'unlink', |
1351 'urandom', |
1380 'urandom', |
1352 'utime', |
1381 'utime', |
1353 'walk', |
1382 'walk', |
1354 'WCOREDUMP', |
1383 'WCOREDUMP', |
1355 'WEXITSTATUS', |
1384 'WEXITSTATUS', |
1356 'WIFEXITED', |
1385 'WIFEXITED', |
1357 'WIFSIGNALED', |
1386 'WIFSIGNALED', |
1358 'WIFSTOPPED', |
1387 'WIFSTOPPED', |
1359 'WNOHANG', |
1388 'WNOHANG', |
1360 'WSTOPSIG', |
1389 'WSTOPSIG', |
1361 'WTERMSIG', |
1390 'WTERMSIG', |
1362 'WUNTRACED', |
1391 'WUNTRACED', |
1363 'W_OK', |
1392 'W_OK', |
1364 'X_OK', |
1393 'X_OK', |
1365 ], |
1394 ], |
1366 } |
1395 } |
1367 |
1396 |
1368 _MODULE_OVERRIDES = { |
1397 _MODULE_OVERRIDES = { |
1369 'locale': { |
1398 'locale': { |
1370 'setlocale': FakeSetLocale, |
1399 'setlocale': FakeSetLocale, |
1371 }, |
1400 }, |
1372 |
1401 |
1373 'os': { |
1402 'os': { |
1374 'access': FakeAccess, |
1403 'access': FakeAccess, |
1375 'listdir': RestrictedPathFunction(os.listdir), |
1404 'listdir': RestrictedPathFunction(os.listdir), |
1376 |
1405 |
1377 'lstat': RestrictedPathFunction(os.stat), |
1406 'lstat': RestrictedPathFunction(os.stat), |
1378 'open': FakeOpen, |
1407 'open': FakeOpen, |
1379 'readlink': FakeReadlink, |
1408 'readlink': FakeReadlink, |
1380 'remove': FakeUnlink, |
1409 'remove': FakeUnlink, |
1381 'rename': FakeRename, |
1410 'rename': FakeRename, |
1382 'stat': RestrictedPathFunction(os.stat), |
1411 'stat': RestrictedPathFunction(os.stat), |
1383 'uname': FakeUname, |
1412 'uname': FakeUname, |
1384 'unlink': FakeUnlink, |
1413 'unlink': FakeUnlink, |
1385 'urandom': FakeURandom, |
1414 'urandom': FakeURandom, |
1386 'utime': FakeUTime, |
1415 'utime': FakeUTime, |
1387 }, |
1416 }, |
1388 |
1417 |
1389 'distutils.util': { |
1418 'distutils.util': { |
1390 'get_platform': FakeGetPlatform, |
1419 'get_platform': FakeGetPlatform, |
1391 }, |
1420 }, |
1392 } |
1421 } |
1393 |
1422 |
1394 _ENABLED_FILE_TYPES = ( |
1423 _ENABLED_FILE_TYPES = ( |
1395 imp.PKG_DIRECTORY, |
1424 imp.PKG_DIRECTORY, |
1396 imp.PY_SOURCE, |
1425 imp.PY_SOURCE, |
1397 imp.PY_COMPILED, |
1426 imp.PY_COMPILED, |
1398 imp.C_BUILTIN, |
1427 imp.C_BUILTIN, |
1399 ) |
1428 ) |
1400 |
1429 |
1401 def __init__(self, |
1430 def __init__(self, |
1402 module_dict, |
1431 module_dict, |
1403 imp_module=imp, |
1432 imp_module=imp, |
1820 source_code += '\n' |
1849 source_code += '\n' |
1821 |
1850 |
1822 return compile(source_code, full_path, 'exec') |
1851 return compile(source_code, full_path, 'exec') |
1823 |
1852 |
1824 |
1853 |
1854 |
|
1825 def ModuleHasValidMainFunction(module): |
1855 def ModuleHasValidMainFunction(module): |
1826 """Determines if a module has a main function that takes no arguments. |
1856 """Determines if a module has a main function that takes no arguments. |
1827 |
1857 |
1828 This includes functions that have arguments with defaults that are all |
1858 This includes functions that have arguments with defaults that are all |
1829 assigned, thus requiring no additional arguments in order to be called. |
1859 assigned, thus requiring no additional arguments in order to be called. |
1833 |
1863 |
1834 Returns: |
1864 Returns: |
1835 True if the module has a valid, reusable main function; False otherwise. |
1865 True if the module has a valid, reusable main function; False otherwise. |
1836 """ |
1866 """ |
1837 if hasattr(module, 'main') and type(module.main) is types.FunctionType: |
1867 if hasattr(module, 'main') and type(module.main) is types.FunctionType: |
1838 arg_names, var_args, var_kwargs, default_values = inspect.getargspec(module.main) |
1868 arg_names, var_args, var_kwargs, default_values = inspect.getargspec( |
1869 module.main) |
|
1839 if len(arg_names) == 0: |
1870 if len(arg_names) == 0: |
1840 return True |
1871 return True |
1841 if default_values is not None and len(arg_names) == len(default_values): |
1872 if default_values is not None and len(arg_names) == len(default_values): |
1842 return True |
1873 return True |
1843 return False |
1874 return False |
1876 |
1907 |
1877 Args: |
1908 Args: |
1878 cgi_path: Absolute path of the CGI module file on disk. |
1909 cgi_path: Absolute path of the CGI module file on disk. |
1879 module_fullname: Fully qualified Python module name used to import the |
1910 module_fullname: Fully qualified Python module name used to import the |
1880 cgi_path module. |
1911 cgi_path module. |
1912 isfile: Used for testing. |
|
1881 |
1913 |
1882 Returns: |
1914 Returns: |
1883 List containing the paths to the missing __init__.py files. |
1915 List containing the paths to the missing __init__.py files. |
1884 """ |
1916 """ |
1885 missing_init_files = [] |
1917 missing_init_files = [] |
1933 and has a main() function that can be reused, this will be None. |
1965 and has a main() function that can be reused, this will be None. |
1934 """ |
1966 """ |
1935 module_fullname = GetScriptModuleName(handler_path) |
1967 module_fullname = GetScriptModuleName(handler_path) |
1936 script_module = module_dict.get(module_fullname) |
1968 script_module = module_dict.get(module_fullname) |
1937 module_code = None |
1969 module_code = None |
1938 if script_module != None and ModuleHasValidMainFunction(script_module): |
1970 if script_module is not None and ModuleHasValidMainFunction(script_module): |
1939 logging.debug('Reusing main() function of module "%s"', module_fullname) |
1971 logging.debug('Reusing main() function of module "%s"', module_fullname) |
1940 else: |
1972 else: |
1941 if script_module is None: |
1973 if script_module is None: |
1942 script_module = imp.new_module(module_fullname) |
1974 script_module = imp.new_module(module_fullname) |
1943 script_module.__loader__ = import_hook |
1975 script_module.__loader__ = import_hook |
1944 |
1976 |
1945 try: |
1977 try: |
1946 module_code = import_hook.get_code(module_fullname) |
1978 module_code = import_hook.get_code(module_fullname) |
1947 full_path, search_path, submodule = import_hook.GetModuleInfo(module_fullname) |
1979 full_path, search_path, submodule = ( |
1980 import_hook.GetModuleInfo(module_fullname)) |
|
1948 script_module.__file__ = full_path |
1981 script_module.__file__ = full_path |
1949 if search_path is not None: |
1982 if search_path is not None: |
1950 script_module.__path__ = search_path |
1983 script_module.__path__ = search_path |
1951 except: |
1984 except: |
1952 exc_type, exc_value, exc_tb = sys.exc_info() |
1985 exc_type, exc_value, exc_tb = sys.exc_info() |
1953 import_error_message = str(exc_type) |
1986 import_error_message = str(exc_type) |
1954 if exc_value: |
1987 if exc_value: |
1955 import_error_message += ': ' + str(exc_value) |
1988 import_error_message += ': ' + str(exc_value) |
1956 |
1989 |
1957 logging.exception('Encountered error loading module "%s": %s', |
1990 logging.exception('Encountered error loading module "%s": %s', |
1958 module_fullname, import_error_message) |
1991 module_fullname, import_error_message) |
1959 missing_inits = FindMissingInitFiles(cgi_path, module_fullname) |
1992 missing_inits = FindMissingInitFiles(cgi_path, module_fullname) |
1960 if missing_inits: |
1993 if missing_inits: |
1961 logging.warning('Missing package initialization files: %s', |
1994 logging.warning('Missing package initialization files: %s', |
1962 ', '.join(missing_inits)) |
1995 ', '.join(missing_inits)) |
1963 else: |
1996 else: |
1987 |
2020 |
1988 return module_fullname, script_module, module_code |
2021 return module_fullname, script_module, module_code |
1989 |
2022 |
1990 |
2023 |
1991 def ExecuteOrImportScript(handler_path, cgi_path, import_hook): |
2024 def ExecuteOrImportScript(handler_path, cgi_path, import_hook): |
1992 """Executes a CGI script by importing it as a new module; possibly reuses |
2025 """Executes a CGI script by importing it as a new module. |
1993 the module's main() function if it is defined and takes no arguments. |
2026 |
2027 This possibly reuses the module's main() function if it is defined and |
|
2028 takes no arguments. |
|
1994 |
2029 |
1995 Basic technique lifted from PEP 338 and Python2.5's runpy module. See: |
2030 Basic technique lifted from PEP 338 and Python2.5's runpy module. See: |
1996 http://www.python.org/dev/peps/pep-0338/ |
2031 http://www.python.org/dev/peps/pep-0338/ |
1997 |
2032 |
1998 See the section entitled "Import Statements and the Main Module" to understand |
2033 See the section entitled "Import Statements and the Main Module" to understand |
2258 def __str__(self): |
2293 def __str__(self): |
2259 """Returns a string representation of this dispatcher.""" |
2294 """Returns a string representation of this dispatcher.""" |
2260 return 'Local CGI dispatcher for %s' % self._cgi_func |
2295 return 'Local CGI dispatcher for %s' % self._cgi_func |
2261 |
2296 |
2262 |
2297 |
2298 |
|
2263 class PathAdjuster(object): |
2299 class PathAdjuster(object): |
2264 """Adjusts application file paths to paths relative to the application or |
2300 """Adjusts application file paths to paths relative to the application or |
2265 external library directories.""" |
2301 external library directories.""" |
2266 |
2302 |
2267 def __init__(self, root_path): |
2303 def __init__(self, root_path): |
2271 root_path: Path to the root of the application running on the server. |
2307 root_path: Path to the root of the application running on the server. |
2272 """ |
2308 """ |
2273 self._root_path = os.path.abspath(root_path) |
2309 self._root_path = os.path.abspath(root_path) |
2274 |
2310 |
2275 def AdjustPath(self, path): |
2311 def AdjustPath(self, path): |
2276 """Adjusts application file path to paths relative to the application or |
2312 """Adjusts application file paths to relative to the application. |
2277 external library directories. |
2313 |
2314 More precisely this method adjusts application file path to paths |
|
2315 relative to the application or external library directories. |
|
2278 |
2316 |
2279 Handler paths that start with $PYTHON_LIB will be converted to paths |
2317 Handler paths that start with $PYTHON_LIB will be converted to paths |
2280 relative to the google directory. |
2318 relative to the google directory. |
2281 |
2319 |
2282 Args: |
2320 Args: |
2290 path[len(PYTHON_LIB_VAR) + 1:]) |
2328 path[len(PYTHON_LIB_VAR) + 1:]) |
2291 else: |
2329 else: |
2292 path = os.path.join(self._root_path, path) |
2330 path = os.path.join(self._root_path, path) |
2293 |
2331 |
2294 return path |
2332 return path |
2333 |
|
2295 |
2334 |
2296 |
2335 |
2297 class StaticFileConfigMatcher(object): |
2336 class StaticFileConfigMatcher(object): |
2298 """Keeps track of file/directory specific application configuration. |
2337 """Keeps track of file/directory specific application configuration. |
2299 |
2338 |
2380 |
2419 |
2381 Returns: |
2420 Returns: |
2382 String containing the mime type to use. Will be 'application/octet-stream' |
2421 String containing the mime type to use. Will be 'application/octet-stream' |
2383 if we have no idea what it should be. |
2422 if we have no idea what it should be. |
2384 """ |
2423 """ |
2385 for (path_re, mime_type, expiration) in self._patterns: |
2424 for (path_re, mimetype, unused_expiration) in self._patterns: |
2386 if mime_type is not None: |
2425 if mimetype is not None: |
2387 the_match = path_re.match(path) |
2426 the_match = path_re.match(path) |
2388 if the_match: |
2427 if the_match: |
2389 return mime_type |
2428 return mimetype |
2390 |
2429 |
2391 filename, extension = os.path.splitext(path) |
2430 unused_filename, extension = os.path.splitext(path) |
2392 return mimetypes.types_map.get(extension, 'application/octet-stream') |
2431 return mimetypes.types_map.get(extension, 'application/octet-stream') |
2393 |
2432 |
2394 def GetExpiration(self, path): |
2433 def GetExpiration(self, path): |
2395 """Returns the cache expiration duration to be users for the given file. |
2434 """Returns the cache expiration duration to be users for the given file. |
2396 |
2435 |
2398 path: String containing the file's path relative to the app. |
2437 path: String containing the file's path relative to the app. |
2399 |
2438 |
2400 Returns: |
2439 Returns: |
2401 Integer number of seconds to be used for browser cache expiration time. |
2440 Integer number of seconds to be used for browser cache expiration time. |
2402 """ |
2441 """ |
2403 for (path_re, mime_type, expiration) in self._patterns: |
2442 for (path_re, unused_mimetype, expiration) in self._patterns: |
2404 the_match = path_re.match(path) |
2443 the_match = path_re.match(path) |
2405 if the_match: |
2444 if the_match: |
2406 return expiration |
2445 return expiration |
2407 |
2446 |
2408 return self._default_expiration or 0 |
2447 return self._default_expiration or 0 |
2448 |
|
2409 |
2449 |
2410 |
2450 |
2411 |
2451 |
2412 def ReadDataFile(data_path, openfile=file): |
2452 def ReadDataFile(data_path, openfile=file): |
2413 """Reads a file on disk, returning a corresponding HTTP status and data. |
2453 """Reads a file on disk, returning a corresponding HTTP status and data. |
2546 |
2586 |
2547 def CacheRewriter(status_code, status_message, headers, body): |
2587 def CacheRewriter(status_code, status_message, headers, body): |
2548 """Update the cache header.""" |
2588 """Update the cache header.""" |
2549 if not 'Cache-Control' in headers: |
2589 if not 'Cache-Control' in headers: |
2550 headers['Cache-Control'] = 'no-cache' |
2590 headers['Cache-Control'] = 'no-cache' |
2591 if not 'Expires' in headers: |
|
2592 headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' |
|
2551 return status_code, status_message, headers, body |
2593 return status_code, status_message, headers, body |
2552 |
2594 |
2553 |
2595 |
2554 def ContentLengthRewriter(status_code, status_message, headers, body): |
2596 def ContentLengthRewriter(status_code, status_message, headers, body): |
2555 """Rewrite the Content-Length header. |
2597 """Rewrite the Content-Length header. |
2607 """ |
2649 """ |
2608 return [IgnoreHeadersRewriter, |
2650 return [IgnoreHeadersRewriter, |
2609 ParseStatusRewriter, |
2651 ParseStatusRewriter, |
2610 CacheRewriter, |
2652 CacheRewriter, |
2611 ContentLengthRewriter, |
2653 ContentLengthRewriter, |
2612 ] |
2654 ] |
2613 |
2655 |
2614 |
2656 |
2615 def RewriteResponse(response_file, response_rewriters=None): |
2657 def RewriteResponse(response_file, response_rewriters=None): |
2616 """Allows final rewrite of dev_appserver response. |
2658 """Allows final rewrite of dev_appserver response. |
2617 |
2659 |
2661 |
2703 |
2662 header_data = '\r\n'.join(header_list) + '\r\n' |
2704 header_data = '\r\n'.join(header_list) + '\r\n' |
2663 return status_code, status_message, header_data, response_file.read() |
2705 return status_code, status_message, header_data, response_file.read() |
2664 |
2706 |
2665 |
2707 |
2708 |
|
2666 class ModuleManager(object): |
2709 class ModuleManager(object): |
2667 """Manages loaded modules in the runtime. |
2710 """Manages loaded modules in the runtime. |
2668 |
2711 |
2669 Responsible for monitoring and reporting about file modification times. |
2712 Responsible for monitoring and reporting about file modification times. |
2670 Modules can be loaded from source or precompiled byte-code files. When a |
2713 Modules can be loaded from source or precompiled byte-code files. When a |
2693 |
2736 |
2694 Returns: |
2737 Returns: |
2695 Path of the module's corresponding Python source file if it exists, or |
2738 Path of the module's corresponding Python source file if it exists, or |
2696 just the module's compiled Python file. If the module has an invalid |
2739 just the module's compiled Python file. If the module has an invalid |
2697 __file__ attribute, None will be returned. |
2740 __file__ attribute, None will be returned. |
2698 """ |
2741 """ |
2699 module_file = getattr(module, '__file__', None) |
2742 module_file = getattr(module, '__file__', None) |
2700 if module_file is None: |
2743 if module_file is None: |
2701 return None |
2744 return None |
2702 |
2745 |
2703 source_file = module_file[:module_file.rfind('py') + 2] |
2746 source_file = module_file[:module_file.rfind('py') + 2] |
2725 return True |
2768 return True |
2726 |
2769 |
2727 return False |
2770 return False |
2728 |
2771 |
2729 def UpdateModuleFileModificationTimes(self): |
2772 def UpdateModuleFileModificationTimes(self): |
2730 """Records the current modification times of all monitored modules. |
2773 """Records the current modification times of all monitored modules.""" |
2731 """ |
|
2732 self._modification_times.clear() |
2774 self._modification_times.clear() |
2733 for name, module in self._modules.items(): |
2775 for name, module in self._modules.items(): |
2734 if not isinstance(module, types.ModuleType): |
2776 if not isinstance(module, types.ModuleType): |
2735 continue |
2777 continue |
2736 module_file = self.GetModuleFile(module) |
2778 module_file = self.GetModuleFile(module) |
2748 self._modules.clear() |
2790 self._modules.clear() |
2749 self._modules.update(self._default_modules) |
2791 self._modules.update(self._default_modules) |
2750 sys.path_hooks[:] = self._save_path_hooks |
2792 sys.path_hooks[:] = self._save_path_hooks |
2751 |
2793 |
2752 |
2794 |
2795 |
|
2753 def _ClearTemplateCache(module_dict=sys.modules): |
2796 def _ClearTemplateCache(module_dict=sys.modules): |
2754 """Clear template cache in webapp.template module. |
2797 """Clear template cache in webapp.template module. |
2755 |
2798 |
2756 Attempts to load template module. Ignores failure. If module loads, the |
2799 Attempts to load template module. Ignores failure. If module loads, the |
2757 template cache is cleared. |
2800 template cache is cleared. |
2801 |
|
2802 Args: |
|
2803 module_dict: Used for dependency injection. |
|
2758 """ |
2804 """ |
2759 template_module = module_dict.get('google.appengine.ext.webapp.template') |
2805 template_module = module_dict.get('google.appengine.ext.webapp.template') |
2760 if template_module is not None: |
2806 if template_module is not None: |
2761 template_module.template_cache.clear() |
2807 template_module.template_cache.clear() |
2808 |
|
2762 |
2809 |
2763 |
2810 |
2764 def CreateRequestHandler(root_path, |
2811 def CreateRequestHandler(root_path, |
2765 login_url, |
2812 login_url, |
2766 require_indexes=False, |
2813 require_indexes=False, |
2767 static_caching=True): |
2814 static_caching=True): |
2768 """Creates a new BaseHTTPRequestHandler sub-class for use with the Python |
2815 """Creates a new BaseHTTPRequestHandler sub-class. |
2769 BaseHTTPServer module's HTTP server. |
2816 |
2817 This class will be used with the Python BaseHTTPServer module's HTTP server. |
|
2770 |
2818 |
2771 Python's built-in HTTP server does not support passing context information |
2819 Python's built-in HTTP server does not support passing context information |
2772 along to instances of its request handlers. This function gets around that |
2820 along to instances of its request handlers. This function gets around that |
2773 by creating a sub-class of the handler in a closure that has access to |
2821 by creating a sub-class of the handler in a closure that has access to |
2774 this context information. |
2822 this context information. |
2790 index_yaml_updater = dev_appserver_index.IndexYamlUpdater(root_path) |
2838 index_yaml_updater = dev_appserver_index.IndexYamlUpdater(root_path) |
2791 |
2839 |
2792 application_config_cache = AppConfigCache() |
2840 application_config_cache = AppConfigCache() |
2793 |
2841 |
2794 class DevAppServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
2842 class DevAppServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
2795 """Dispatches URLs using patterns from a URLMatcher, which is created by |
2843 """Dispatches URLs using patterns from a URLMatcher. |
2796 loading an application's configuration file. Executes CGI scripts in the |
2844 |
2797 local process so the scripts can use mock versions of APIs. |
2845 The URLMatcher is created by loading an application's configuration file. |
2846 Executes CGI scripts in the local process so the scripts can use mock |
|
2847 versions of APIs. |
|
2798 |
2848 |
2799 HTTP requests that correctly specify a user info cookie |
2849 HTTP requests that correctly specify a user info cookie |
2800 (dev_appserver_login.COOKIE_NAME) will have the 'USER_EMAIL' environment |
2850 (dev_appserver_login.COOKIE_NAME) will have the 'USER_EMAIL' environment |
2801 variable set accordingly. If the user is also an admin, the |
2851 variable set accordingly. If the user is also an admin, the |
2802 'USER_IS_ADMIN' variable will exist and be set to '1'. If the user is not |
2852 'USER_IS_ADMIN' variable will exist and be set to '1'. If the user is not |
2817 |
2867 |
2818 def __init__(self, *args, **kwargs): |
2868 def __init__(self, *args, **kwargs): |
2819 """Initializer. |
2869 """Initializer. |
2820 |
2870 |
2821 Args: |
2871 Args: |
2822 args, kwargs: Positional and keyword arguments passed to the constructor |
2872 args: Positional arguments passed to the superclass constructor. |
2823 of the super class. |
2873 kwargs: Keyword arguments passed to the superclass constructor. |
2824 """ |
2874 """ |
2825 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs) |
2875 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs) |
2826 |
2876 |
2827 def version_string(self): |
2877 def version_string(self): |
2828 """Returns server's version string used for Server HTTP header""" |
2878 """Returns server's version string used for Server HTTP header.""" |
2829 return self.server_version |
2879 return self.server_version |
2830 |
2880 |
2831 def do_GET(self): |
2881 def do_GET(self): |
2832 """Handle GET requests.""" |
2882 """Handle GET requests.""" |
2833 self._HandleRequest() |
2883 self._HandleRequest() |
2860 """Handles any type of request and prints exceptions if they occur.""" |
2910 """Handles any type of request and prints exceptions if they occur.""" |
2861 server_name = self.headers.get('host') or self.server.server_name |
2911 server_name = self.headers.get('host') or self.server.server_name |
2862 server_name = server_name.split(':', 1)[0] |
2912 server_name = server_name.split(':', 1)[0] |
2863 |
2913 |
2864 env_dict = { |
2914 env_dict = { |
2865 'REQUEST_METHOD': self.command, |
2915 'REQUEST_METHOD': self.command, |
2866 'REMOTE_ADDR': self.client_address[0], |
2916 'REMOTE_ADDR': self.client_address[0], |
2867 'SERVER_SOFTWARE': self.server_version, |
2917 'SERVER_SOFTWARE': self.server_version, |
2868 'SERVER_NAME': server_name, |
2918 'SERVER_NAME': server_name, |
2869 'SERVER_PROTOCOL': self.protocol_version, |
2919 'SERVER_PROTOCOL': self.protocol_version, |
2870 'SERVER_PORT': str(self.server.server_port), |
2920 'SERVER_PORT': str(self.server.server_port), |
2871 } |
2921 } |
2872 |
2922 |
2873 full_url = GetFullURL(server_name, self.server.server_port, self.path) |
2923 full_url = GetFullURL(server_name, self.server.server_port, self.path) |
2874 if len(full_url) > MAX_URL_LENGTH: |
2924 if len(full_url) > MAX_URL_LENGTH: |
2875 msg = 'Requested URI too long: %s' % full_url |
2925 msg = 'Requested URI too long: %s' % full_url |
2887 login_url) |
2937 login_url) |
2888 config, explicit_matcher = LoadAppConfig(root_path, self.module_dict, |
2938 config, explicit_matcher = LoadAppConfig(root_path, self.module_dict, |
2889 cache=self.config_cache, |
2939 cache=self.config_cache, |
2890 static_caching=static_caching) |
2940 static_caching=static_caching) |
2891 if config.api_version != API_VERSION: |
2941 if config.api_version != API_VERSION: |
2892 logging.error("API versions cannot be switched dynamically: %r != %r" |
2942 logging.error( |
2893 % (config.api_version, API_VERSION)) |
2943 "API versions cannot be switched dynamically: %r != %r", |
2944 config.api_version, API_VERSION) |
|
2894 sys.exit(1) |
2945 sys.exit(1) |
2895 env_dict['CURRENT_VERSION_ID'] = config.version + ".1" |
2946 env_dict['CURRENT_VERSION_ID'] = config.version + ".1" |
2896 env_dict['APPLICATION_ID'] = config.application |
2947 env_dict['APPLICATION_ID'] = config.application |
2897 dispatcher = MatcherDispatcher(login_url, |
2948 dispatcher = MatcherDispatcher(login_url, |
2898 [implicit_matcher, explicit_matcher]) |
2949 [implicit_matcher, explicit_matcher]) |
2925 self.module_manager.UpdateModuleFileModificationTimes() |
2976 self.module_manager.UpdateModuleFileModificationTimes() |
2926 |
2977 |
2927 outfile.flush() |
2978 outfile.flush() |
2928 outfile.seek(0) |
2979 outfile.seek(0) |
2929 |
2980 |
2930 status_code, status_message, header_data, body = RewriteResponse(outfile, self.rewriter_chain) |
2981 status_code, status_message, header_data, body = ( |
2982 RewriteResponse(outfile, self.rewriter_chain)) |
|
2931 |
2983 |
2932 runtime_response_size = len(outfile.getvalue()) |
2984 runtime_response_size = len(outfile.getvalue()) |
2933 if runtime_response_size > MAX_RUNTIME_RESPONSE_SIZE: |
2985 if runtime_response_size > MAX_RUNTIME_RESPONSE_SIZE: |
2934 status_code = 403 |
2986 status_code = 403 |
2935 status_message = 'Forbidden' |
2987 status_message = 'Forbidden' |
2982 logging.info(format, *args) |
3034 logging.info(format, *args) |
2983 |
3035 |
2984 return DevAppServerRequestHandler |
3036 return DevAppServerRequestHandler |
2985 |
3037 |
2986 |
3038 |
3039 |
|
2987 def ReadAppConfig(appinfo_path, parse_app_config=appinfo.LoadSingleAppInfo): |
3040 def ReadAppConfig(appinfo_path, parse_app_config=appinfo.LoadSingleAppInfo): |
2988 """Reads app.yaml file and returns its app id and list of URLMap instances. |
3041 """Reads app.yaml file and returns its app id and list of URLMap instances. |
2989 |
3042 |
2990 Args: |
3043 Args: |
2991 appinfo_path: String containing the path to the app.yaml file. |
3044 appinfo_path: String containing the path to the app.yaml file. |
2999 URLMap instances, this function will raise an InvalidAppConfigError |
3052 URLMap instances, this function will raise an InvalidAppConfigError |
3000 exception. |
3053 exception. |
3001 """ |
3054 """ |
3002 try: |
3055 try: |
3003 appinfo_file = file(appinfo_path, 'r') |
3056 appinfo_file = file(appinfo_path, 'r') |
3004 except IOError, e: |
3057 except IOError, unused_e: |
3005 raise InvalidAppConfigError( |
3058 raise InvalidAppConfigError( |
3006 'Application configuration could not be read from "%s"' % appinfo_path) |
3059 'Application configuration could not be read from "%s"' % appinfo_path) |
3007 try: |
3060 try: |
3008 return parse_app_config(appinfo_file) |
3061 return parse_app_config(appinfo_file) |
3009 finally: |
3062 finally: |
3010 appinfo_file.close() |
3063 appinfo_file.close() |
3011 |
3064 |
3033 preserved between requests. This dictionary must be separate from the |
3086 preserved between requests. This dictionary must be separate from the |
3034 sys.modules dictionary. |
3087 sys.modules dictionary. |
3035 default_expiration: String describing default expiration time for browser |
3088 default_expiration: String describing default expiration time for browser |
3036 based caching of static files. If set to None this disallows any |
3089 based caching of static files. If set to None this disallows any |
3037 browser caching of static content. |
3090 browser caching of static content. |
3038 create_url_matcher, create_cgi_dispatcher, create_file_dispatcher, |
3091 create_url_matcher: Used for dependency injection. |
3092 create_cgi_dispatcher: Used for dependency injection. |
|
3093 create_file_dispatcher: Used for dependency injection. |
|
3039 create_path_adjuster: Used for dependency injection. |
3094 create_path_adjuster: Used for dependency injection. |
3095 normpath: Used for dependency injection. |
|
3040 |
3096 |
3041 Returns: |
3097 Returns: |
3042 Instance of URLMatcher with the supplied URLMap objects properly loaded. |
3098 Instance of URLMatcher with the supplied URLMap objects properly loaded. |
3099 |
|
3100 Raises: |
|
3101 InvalidAppConfigError: if the handler in url_map_list is an unknown type. |
|
3043 """ |
3102 """ |
3044 url_matcher = create_url_matcher() |
3103 url_matcher = create_url_matcher() |
3045 path_adjuster = create_path_adjuster(root_path) |
3104 path_adjuster = create_path_adjuster(root_path) |
3046 cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster) |
3105 cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster) |
3047 static_file_config_matcher = StaticFileConfigMatcher(url_map_list, |
3106 static_file_config_matcher = StaticFileConfigMatcher(url_map_list, |
3119 module_dict: Dictionary in which application-loaded modules should be |
3178 module_dict: Dictionary in which application-loaded modules should be |
3120 preserved between requests. This dictionary must be separate from the |
3179 preserved between requests. This dictionary must be separate from the |
3121 sys.modules dictionary. |
3180 sys.modules dictionary. |
3122 cache: Instance of AppConfigCache or None. |
3181 cache: Instance of AppConfigCache or None. |
3123 static_caching: True if browser caching of static files should be allowed. |
3182 static_caching: True if browser caching of static files should be allowed. |
3124 read_app_config, create_matcher: Used for dependency injection. |
3183 read_app_config: Used for dependency injection. |
3184 create_matcher: Used for dependency injection. |
|
3125 |
3185 |
3126 Returns: |
3186 Returns: |
3127 tuple: (AppInfoExternal, URLMatcher) |
3187 tuple: (AppInfoExternal, URLMatcher) |
3188 |
|
3189 Raises: |
|
3190 AppConfigNotFound: if an app.yaml file cannot be found. |
|
3128 """ |
3191 """ |
3129 for appinfo_path in [os.path.join(root_path, 'app.yaml'), |
3192 for appinfo_path in [os.path.join(root_path, 'app.yaml'), |
3130 os.path.join(root_path, 'app.yml')]: |
3193 os.path.join(root_path, 'app.yml')]: |
3131 |
3194 |
3132 if os.path.isfile(appinfo_path): |
3195 if os.path.isfile(appinfo_path): |
3179 A CronInfoExternal object. |
3242 A CronInfoExternal object. |
3180 |
3243 |
3181 Raises: |
3244 Raises: |
3182 If the config file is unreadable, empty or invalid, this function will |
3245 If the config file is unreadable, empty or invalid, this function will |
3183 raise an InvalidAppConfigError or a MalformedCronConfiguration exception. |
3246 raise an InvalidAppConfigError or a MalformedCronConfiguration exception. |
3184 """ |
3247 """ |
3185 try: |
3248 try: |
3186 croninfo_file = file(croninfo_path, 'r') |
3249 croninfo_file = file(croninfo_path, 'r') |
3187 except IOError, e: |
3250 except IOError, e: |
3188 raise InvalidAppConfigError( |
3251 raise InvalidAppConfigError( |
3189 'Cron configuration could not be read from "%s"' % croninfo_path) |
3252 'Cron configuration could not be read from "%s": %s' |
3253 % (croninfo_path, e)) |
|
3190 try: |
3254 try: |
3191 return parse_cron_config(croninfo_file) |
3255 return parse_cron_config(croninfo_file) |
3192 finally: |
3256 finally: |
3193 croninfo_file.close() |
3257 croninfo_file.close() |
3194 |
3258 |
3195 |
3259 |
3260 |
|
3196 def SetupStubs(app_id, **config): |
3261 def SetupStubs(app_id, **config): |
3197 """Sets up testing stubs of APIs. |
3262 """Sets up testing stubs of APIs. |
3198 |
3263 |
3199 Args: |
3264 Args: |
3200 app_id: Application ID being served. |
3265 app_id: Application ID being served. |
3266 config: keyword arguments. |
|
3201 |
3267 |
3202 Keywords: |
3268 Keywords: |
3203 root_path: Root path to the directory of the application which should |
3269 root_path: Root path to the directory of the application which should |
3204 contain the app.yaml, indexes.yaml, and queues.yaml files. |
3270 contain the app.yaml, indexes.yaml, and queues.yaml files. |
3205 login_url: Relative URL which should be used for handling user login/logout. |
3271 login_url: Relative URL which should be used for handling user login/logout. |
3254 dev_appserver_login.CONTINUE_PARAM) |
3320 dev_appserver_login.CONTINUE_PARAM) |
3255 fixed_logout_url = '%s&%s' % (fixed_login_url, |
3321 fixed_logout_url = '%s&%s' % (fixed_login_url, |
3256 dev_appserver_login.LOGOUT_PARAM) |
3322 dev_appserver_login.LOGOUT_PARAM) |
3257 |
3323 |
3258 apiproxy_stub_map.apiproxy.RegisterStub( |
3324 apiproxy_stub_map.apiproxy.RegisterStub( |
3259 'user', |
3325 'user', |
3260 user_service_stub.UserServiceStub(login_url=fixed_login_url, |
3326 user_service_stub.UserServiceStub(login_url=fixed_login_url, |
3261 logout_url=fixed_logout_url)) |
3327 logout_url=fixed_logout_url)) |
3262 |
3328 |
3263 apiproxy_stub_map.apiproxy.RegisterStub( |
3329 apiproxy_stub_map.apiproxy.RegisterStub( |
3264 'urlfetch', |
3330 'urlfetch', |
3265 urlfetch_stub.URLFetchServiceStub()) |
3331 urlfetch_stub.URLFetchServiceStub()) |
3266 |
3332 |
3267 apiproxy_stub_map.apiproxy.RegisterStub( |
3333 apiproxy_stub_map.apiproxy.RegisterStub( |
3268 'mail', |
3334 'mail', |
3269 mail_stub.MailServiceStub(smtp_host, |
3335 mail_stub.MailServiceStub(smtp_host, |
3270 smtp_port, |
3336 smtp_port, |
3271 smtp_user, |
3337 smtp_user, |
3272 smtp_password, |
3338 smtp_password, |
3273 enable_sendmail=enable_sendmail, |
3339 enable_sendmail=enable_sendmail, |
3274 show_mail_body=show_mail_body)) |
3340 show_mail_body=show_mail_body)) |
3275 |
3341 |
3276 apiproxy_stub_map.apiproxy.RegisterStub( |
3342 apiproxy_stub_map.apiproxy.RegisterStub( |
3277 'memcache', |
3343 'memcache', |
3278 memcache_stub.MemcacheServiceStub()) |
3344 memcache_stub.MemcacheServiceStub()) |
3279 |
3345 |
3280 apiproxy_stub_map.apiproxy.RegisterStub( |
3346 apiproxy_stub_map.apiproxy.RegisterStub( |
3281 'capability_service', |
3347 'capability_service', |
3282 capability_stub.CapabilityServiceStub()) |
3348 capability_stub.CapabilityServiceStub()) |
3283 |
3349 |
3284 apiproxy_stub_map.apiproxy.RegisterStub( |
3350 apiproxy_stub_map.apiproxy.RegisterStub( |
3285 'taskqueue', |
3351 'taskqueue', |
3286 taskqueue_stub.TaskQueueServiceStub(root_path=root_path)) |
3352 taskqueue_stub.TaskQueueServiceStub(root_path=root_path)) |
3353 |
|
3354 apiproxy_stub_map.apiproxy.RegisterStub( |
|
3355 'xmpp', |
|
3356 xmpp_service_stub.XmppServiceStub()) |
|
3357 |
|
3287 |
3358 |
3288 |
3359 |
3289 try: |
3360 try: |
3290 from google.appengine.api.images import images_stub |
3361 from google.appengine.api.images import images_stub |
3291 apiproxy_stub_map.apiproxy.RegisterStub( |
3362 apiproxy_stub_map.apiproxy.RegisterStub( |
3292 'images', |
3363 'images', |
3293 images_stub.ImagesServiceStub()) |
3364 images_stub.ImagesServiceStub()) |
3294 except ImportError, e: |
3365 except ImportError, e: |
3295 logging.warning('Could not initialize images API; you are likely missing ' |
3366 logging.warning('Could not initialize images API; you are likely missing ' |
3296 'the Python "PIL" module. ImportError: %s', e) |
3367 'the Python "PIL" module. ImportError: %s', e) |
3297 from google.appengine.api.images import images_not_implemented_stub |
3368 from google.appengine.api.images import images_not_implemented_stub |
3298 apiproxy_stub_map.apiproxy.RegisterStub('images', |
3369 apiproxy_stub_map.apiproxy.RegisterStub( |
3299 images_not_implemented_stub.ImagesNotImplementedServiceStub()) |
3370 'images', |
3371 images_not_implemented_stub.ImagesNotImplementedServiceStub()) |
|
3300 |
3372 |
3301 |
3373 |
3302 def CreateImplicitMatcher(module_dict, |
3374 def CreateImplicitMatcher(module_dict, |
3303 root_path, |
3375 root_path, |
3304 login_url, |
3376 login_url, |
3312 |
3384 |
3313 Args: |
3385 Args: |
3314 module_dict: Dictionary in the form used by sys.modules. |
3386 module_dict: Dictionary in the form used by sys.modules. |
3315 root_path: Path to the root of the application. |
3387 root_path: Path to the root of the application. |
3316 login_url: Relative URL which should be used for handling user login/logout. |
3388 login_url: Relative URL which should be used for handling user login/logout. |
3389 create_path_adjuster: Used for dependedency injection. |
|
3317 create_local_dispatcher: Used for dependency injection. |
3390 create_local_dispatcher: Used for dependency injection. |
3391 create_cgi_dispatcher: Used for dependedency injection. |
|
3318 |
3392 |
3319 Returns: |
3393 Returns: |
3320 Instance of URLMatcher with appropriate dispatchers. |
3394 Instance of URLMatcher with appropriate dispatchers. |
3321 """ |
3395 """ |
3322 url_matcher = URLMatcher() |
3396 url_matcher = URLMatcher() |
3391 port: Port to start the application server on. |
3465 port: Port to start the application server on. |
3392 template_dir: Path to the directory in which the debug console templates |
3466 template_dir: Path to the directory in which the debug console templates |
3393 are stored. |
3467 are stored. |
3394 serve_address: Address on which the server should serve. |
3468 serve_address: Address on which the server should serve. |
3395 require_indexes: True if index.yaml is read-only gospel; default False. |
3469 require_indexes: True if index.yaml is read-only gospel; default False. |
3470 allow_skipped_files: True if skipped files should be accessible. |
|
3396 static_caching: True if browser caching of static files should be allowed. |
3471 static_caching: True if browser caching of static files should be allowed. |
3397 python_path_list: Used for dependency injection. |
3472 python_path_list: Used for dependency injection. |
3398 sdk_dir: Directory where the SDK is stored. |
3473 sdk_dir: Directory where the SDK is stored. |
3399 |
3474 |
3400 Returns: |
3475 Returns: |
3413 require_indexes, |
3488 require_indexes, |
3414 static_caching) |
3489 static_caching) |
3415 |
3490 |
3416 if absolute_root_path not in python_path_list: |
3491 if absolute_root_path not in python_path_list: |
3417 python_path_list.insert(0, absolute_root_path) |
3492 python_path_list.insert(0, absolute_root_path) |
3418 |
3493 return HTTPServerWithScheduler((serve_address, port), handler_class) |
3419 return BaseHTTPServer.HTTPServer((serve_address, port), handler_class) |
3494 |
3495 |
|
3496 class HTTPServerWithScheduler(BaseHTTPServer.HTTPServer): |
|
3497 """A BaseHTTPServer subclass that calls a method at a regular interval.""" |
|
3498 |
|
3499 def __init__(self, server_address, request_handler_class): |
|
3500 """Constructor. |
|
3501 |
|
3502 Args: |
|
3503 server_address: the bind address of the server. |
|
3504 request_handler_class: class used to handle requests. |
|
3505 """ |
|
3506 BaseHTTPServer.HTTPServer.__init__(self, server_address, |
|
3507 request_handler_class) |
|
3508 self._events = [] |
|
3509 |
|
3510 def get_request(self, time_func=time.time, select_func=select.select): |
|
3511 """Overrides the base get_request call. |
|
3512 |
|
3513 Args: |
|
3514 time_func: used for testing. |
|
3515 select_func: used for testing. |
|
3516 |
|
3517 Returns: |
|
3518 a (socket_object, address info) tuple. |
|
3519 """ |
|
3520 while True: |
|
3521 if self._events: |
|
3522 current_time = time_func() |
|
3523 next_eta = self._events[0][0] |
|
3524 delay = next_eta - current_time |
|
3525 else: |
|
3526 delay = DEFAULT_SELECT_DELAY |
|
3527 readable, _, _ = select_func([self.socket], [], [], max(delay, 0)) |
|
3528 if readable: |
|
3529 return self.socket.accept() |
|
3530 current_time = time_func() |
|
3531 if self._events and current_time >= self._events[0][0]: |
|
3532 unused_eta, runnable = heapq.heappop(self._events) |
|
3533 runnable() |
|
3534 |
|
3535 def AddEvent(self, eta, runnable): |
|
3536 """Add a runnable event to be run at the specified time. |
|
3537 |
|
3538 Args: |
|
3539 eta: when to run the event, in seconds since epoch. |
|
3540 runnable: a callable object. |
|
3541 """ |
|
3542 heapq.heappush(self._events, (eta, runnable)) |