Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/redirect/redirect.py b/redirect/redirect.py
new file mode 100644
index 0000000..10398f6
--- /dev/null
+++ b/redirect/redirect.py
@@ -0,0 +1,94 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Redirect Middleware for Monorail.
+
+Handles traffic redirection before hitting main monorail app.
+"""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import flask
+from redirect import redirect_utils
+from redirect import redirectissue
+
+
+class RedirectMiddleware(object):
+
+  def __init__(self, main_app, redirect_app):
+    self._main_app = main_app
+    self._redirect_app = redirect_app
+
+  def __call__(self, environ, start_response):
+    # Run the redirect app first.
+    response = flask.Response.from_app(self._redirect_app, environ)
+    if response.status_code == 404:
+      # If it returns 404, run the main app.
+      return self._main_app(environ, start_response)
+    # Otherwise, return the response from the redirect app.
+    app_iter, status, headers = response.get_wsgi_response(environ)
+    start_response(status, headers)
+    return app_iter
+
+
+def GenerateRedirectApp():
+  redirect_app = flask.Flask(__name__)
+
+  def PreCheckHandler():
+    # Should not redirect away from monorail if param set.
+    r = flask.request
+    no_redirect = 'no_tracker_redirect' in r.args
+    if no_redirect:
+      flask.abort(404)
+  redirect_app.before_request(PreCheckHandler)
+
+  def IssueList(project_name):
+    redirect_url = redirect_utils.GetRedirectURL(project_name)
+    if redirect_url:
+      query_string = redirect_utils.GetSearchQuery(
+          project_name, flask.request.args)
+      return flask.redirect(redirect_url + '/issues?' + query_string)
+    flask.abort(404)
+
+  redirect_app.route('/p/<string:project_name>/')(IssueList)
+  redirect_app.route('/p/<string:project_name>/issues/')(IssueList)
+  redirect_app.route('/p/<string:project_name>/issues/list')(IssueList)
+  redirect_app.route('/p/<string:project_name>/issues/list_new')(IssueList)
+
+  def IssueDetail(project_name):
+    local_id = flask.request.args.get('id', type=int)
+    if not local_id:
+      flask.abort(404)
+
+    redirect_url = _GenerateIssueDetailRedirectURL(local_id, project_name)
+    if redirect_url:
+      return flask.render_template('redirect.html', base_url=redirect_url)
+    flask.abort(404)
+  redirect_app.route('/p/<string:project_name>/issues/detail')(IssueDetail)
+
+  def IssueCreate(project_name):
+    redirect_url = redirect_utils.GetRedirectURL(project_name)
+    if redirect_url:
+      query_string = redirect_utils.GetNewIssueParams(
+          flask.request.args, project_name)
+      return flask.redirect(redirect_url + '/new?' + query_string)
+    flask.abort(404)
+  redirect_app.route('/p/<string:project_name>/issues/entry')(IssueCreate)
+  redirect_app.route('/p/<string:project_name>/issues/entry_new')(IssueCreate)
+
+  return redirect_app
+
+
+def _GenerateIssueDetailRedirectURL(local_id, project_name):
+  redirect_base_url = redirect_utils.GetRedirectURL(project_name)
+  if not redirect_base_url:
+    return None
+
+  if local_id > redirect_utils.MAX_MONORAIL_ISSUE_ID:
+    return redirect_base_url + '/' + str(local_id)
+
+  tracker_id = redirectissue.RedirectIssue.Get(project_name, local_id)
+  if tracker_id:
+    return redirect_base_url + '/' + tracker_id
+  return None