Merge branch 'main' into avm99963-monorail
Merged commit 4137ed7879acadbf891e8c471108acb874dae886.
GitOrigin-RevId: b6100ffc5b1da355a35f37b13fcaaf746ee8b307
diff --git a/framework/servlet.py b/framework/servlet.py
index 1ed6935..e1c0cf1 100644
--- a/framework/servlet.py
+++ b/framework/servlet.py
@@ -90,17 +90,6 @@
# 'cloudtrace', 'v1', credentials=credentials)
# except Exception as e:
# logging.warning('could not get trace service: %s', e)
-
-
-class MethodNotSupportedError(NotImplementedError):
- """An exception class for indicating that the method is not supported.
-
- Used by GatherPageData and ProcessFormData to indicate that GET and POST,
- respectively, are not supported methods on the given Servlet.
- """
- pass
-
-
class Servlet(webapp2.RequestHandler):
"""Base class for all Monorail servlets.
@@ -318,7 +307,7 @@
csp_supports_report_sample = (
(browser == 'Chrome' and browser_major_version >= 59) or
(browser == 'Opera' and browser_major_version >= 46))
- version_base = _VersionBaseURL(self.mr.request)
+ version_base = servlet_helpers.VersionBaseURL(self.mr.request)
self.response.headers.add(csp_header,
("default-src %(scheme)s ; "
"script-src"
@@ -355,7 +344,7 @@
with self.mr.profiler.Phase('rendering template'):
self._RenderResponse(page_data)
- except (MethodNotSupportedError, NotImplementedError) as e:
+ except (servlet_helpers.MethodNotSupportedError, NotImplementedError) as e:
# Instead of these pages throwing 500s display the 404 message and log.
# The motivation of this is to minimize 500s on the site to keep alerts
# meaningful during fuzzing. For more context see
@@ -374,7 +363,7 @@
logging.warning('mr.perms is %s', self.mr.perms)
if not self.mr.auth.user_id:
# If not logged in, let them log in
- url = _SafeCreateLoginURL(self.mr)
+ url = servlet_helpers.SafeCreateLoginURL(self.mr)
self.redirect(url, abort=True)
else:
# Display the missing permissions template.
@@ -388,12 +377,6 @@
self._missing_permissions_template.WriteResponse(
self.response, page_data, content_type=self.content_type)
- def SetCacheHeaders(self, response):
- """Set headers to allow the response to be cached."""
- headers = framework_helpers.StaticCacheHeaders()
- for name, value in headers:
- response.headers[name] = value
-
def GetTemplate(self, _page_data):
"""Get the template to use for writing the http response.
@@ -437,7 +420,7 @@
Returns:
String URL to redirect the user to, or None if response was already sent.
"""
- raise MethodNotSupportedError()
+ raise servlet_helpers.MethodNotSupportedError()
def post(self, **kwargs):
"""Parse the request, check base perms, and call form-specific code."""
@@ -607,7 +590,7 @@
project_thumbnail_url = ''
if project:
project_summary = project.summary
- project_alert = _CalcProjectAlert(project)
+ project_alert = servlet_helpers.CalcProjectAlert(project)
project_read_only = project.read_only_reason
project_home_page = project.home_page
project_thumbnail_url = tracker_views.LogoView(project).thumbnail_url
@@ -653,10 +636,10 @@
offer_saved_queries_subtab = (
viewing_self or mr.auth.user_pb and mr.auth.user_pb.is_site_admin)
- login_url = _SafeCreateLoginURL(mr)
- logout_url = _SafeCreateLogoutURL(mr)
+ login_url = servlet_helpers.SafeCreateLoginURL(mr)
+ logout_url = servlet_helpers.SafeCreateLogoutURL(mr)
logout_url_goto_home = users.create_logout_url('/')
- version_base = _VersionBaseURL(mr.request)
+ version_base = servlet_helpers.VersionBaseURL(mr.request)
base_data = {
# EZT does not have constants for True and False, so we pass them in.
@@ -697,7 +680,7 @@
'project':
project_view,
'project_is_restricted':
- ezt.boolean(_ProjectIsRestricted(mr)),
+ ezt.boolean(servlet_helpers.ProjectIsRestricted(mr)),
'offer_contributor_list':
ezt.boolean(permissions.CanViewContributorList(mr, mr.project)),
'logged_in_user':
@@ -784,10 +767,13 @@
mr.num,
'groupby':
mr.group_by_spec,
- 'q_field_size': (min(
- framework_constants.MAX_ARTIFACT_SEARCH_FIELD_SIZE,
- max(framework_constants.MIN_ARTIFACT_SEARCH_FIELD_SIZE,
- len(mr.query) + framework_constants.AUTOSIZE_STEP))),
+ 'q_field_size':
+ (
+ min(
+ framework_constants.MAX_ARTIFACT_SEARCH_FIELD_SIZE,
+ max(
+ framework_constants.MIN_ARTIFACT_SEARCH_FIELD_SIZE,
+ len(mr.query) + framework_constants.AUTOSIZE_STEP))),
'mode':
None, # Display mode, e.g., grid mode.
'ajah':
@@ -866,7 +852,7 @@
def GatherPageData(self, mr):
"""Return a dict of page-specific ezt data."""
- raise MethodNotSupportedError()
+ raise servlet_helpers.MethodNotSupportedError()
# pylint: disable=unused-argument
def GatherHelpData(self, mr, page_data):
@@ -907,7 +893,7 @@
def GatherDebugData(self, mr, page_data):
"""Return debugging info for display at the very bottom of the page."""
if mr.debug_enabled:
- debug = [_ContextDebugCollection('Page data', page_data)]
+ debug = [servlet_helpers.ContextDebugCollection('Page data', page_data)]
return {
'dbg': 'on',
'debug': debug,
@@ -939,109 +925,3 @@
now - framework_constants.VISIT_RESOLUTION):
user_pb.last_visit_timestamp = now
self.services.user.UpdateUser(mr.cnxn, user_pb.user_id, user_pb)
-
-
-def _CalcProjectAlert(project):
- """Return a string to be shown as red text explaning the project state."""
-
- project_alert = None
-
- if project.read_only_reason:
- project_alert = 'READ-ONLY: %s.' % project.read_only_reason
- if project.moved_to:
- project_alert = 'This project has moved to: %s.' % project.moved_to
- elif project.delete_time:
- delay_seconds = project.delete_time - time.time()
- delay_days = delay_seconds // framework_constants.SECS_PER_DAY
- if delay_days <= 0:
- project_alert = 'Scheduled for deletion today.'
- else:
- days_word = 'day' if delay_days == 1 else 'days'
- project_alert = (
- 'Scheduled for deletion in %d %s.' % (delay_days, days_word))
- elif project.state == project_pb2.ProjectState.ARCHIVED:
- project_alert = 'Project is archived: read-only by members only.'
-
- return project_alert
-
-
-class _ContextDebugItem(object):
- """Wrapper class to generate on-screen debugging output."""
-
- def __init__(self, key, val):
- """Store the key and generate a string for the value."""
- self.key = key
- if isinstance(val, list):
- nested_debug_strs = [self.StringRep(v) for v in val]
- self.val = '[%s]' % ', '.join(nested_debug_strs)
- else:
- self.val = self.StringRep(val)
-
- def StringRep(self, val):
- """Make a useful string representation of the given value."""
- try:
- return val.DebugString()
- except Exception:
- try:
- return str(val.__dict__)
- except Exception:
- return repr(val)
-
-
-class _ContextDebugCollection(object):
- """Attach a title to a dictionary for exporting as a table of debug info."""
-
- def __init__(self, title, collection):
- self.title = title
- self.collection = [_ContextDebugItem(key, collection[key])
- for key in sorted(collection.keys())]
-
-
-def _ProjectIsRestricted(mr):
- """Return True if the mr has a 'private' project."""
- return (mr.project and
- mr.project.access != project_pb2.ProjectAccess.ANYONE)
-
-
-def _SafeCreateLoginURL(mr, continue_url=None):
- """Make a login URL w/ a detailed continue URL, otherwise use a short one."""
- continue_url = continue_url or mr.current_page_url
- try:
- url = users.create_login_url(continue_url)
- except users.RedirectTooLongError:
- if mr.project_name:
- url = users.create_login_url('/p/%s' % mr.project_name)
- else:
- url = users.create_login_url('/')
-
- # Give the user a choice of existing accounts in their session
- # or the option to add an account, even if they are currently
- # signed in to exactly one account.
- if mr.auth.user_id:
- # Notice: this makes assuptions about the output of users.create_login_url,
- # which can change at any time. See https://crbug.com/monorail/3352.
- url = url.replace('/ServiceLogin', '/AccountChooser', 1)
- return url
-
-
-def _SafeCreateLogoutURL(mr):
- """Make a logout URL w/ a detailed continue URL, otherwise use a short one."""
- try:
- return users.create_logout_url(mr.current_page_url)
- except users.RedirectTooLongError:
- if mr.project_name:
- return users.create_logout_url('/p/%s' % mr.project_name)
- else:
- return users.create_logout_url('/')
-
-
-def _VersionBaseURL(request):
- """Return a version-specific URL that we use to load static assets."""
- if settings.local_mode:
- version_base = '%s://%s' % (request.scheme, request.host)
- else:
- version_base = '%s://%s-dot-%s' % (
- request.scheme, modules.get_current_version_name(),
- app_identity.get_default_version_hostname())
-
- return version_base