Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style |
| 3 | # license that can be found in the LICENSE file or at |
| 4 | # https://developers.google.com/open-source/licenses/bsd |
| 5 | |
| 6 | """This file sets up all the urls for monorail pages.""" |
| 7 | from __future__ import print_function |
| 8 | from __future__ import division |
| 9 | from __future__ import absolute_import |
| 10 | |
| 11 | import logging |
| 12 | import webapp2 |
| 13 | import settings |
| 14 | |
| 15 | from components import prpc |
| 16 | |
| 17 | from features import autolink |
| 18 | from features import dateaction |
| 19 | from features import banspammer |
| 20 | from features import hotlistcreate |
| 21 | from features import hotlistdetails |
| 22 | from features import hotlistissues |
| 23 | from features import hotlistissuescsv |
| 24 | from features import hotlistpeople |
| 25 | from features import filterrules |
| 26 | from features import pubsub |
| 27 | from features import userhotlists |
| 28 | from features import inboundemail |
| 29 | from features import notify |
| 30 | from features import rerankhotlist |
| 31 | from features import savedqueries |
| 32 | from features import spammodel |
| 33 | from features import spamtraining |
| 34 | from features import componentexport |
| 35 | |
| 36 | from framework import banned |
| 37 | from framework import clientmon |
| 38 | from framework import csp_report |
| 39 | from framework import deleteusers |
| 40 | from framework import excessiveactivity |
| 41 | from framework import trimvisitedpages |
| 42 | from framework import framework_bizobj |
| 43 | from framework import reap |
| 44 | from framework import registerpages_helpers |
| 45 | from framework import ts_mon_js |
| 46 | from framework import urls |
| 47 | from framework import warmup |
| 48 | |
| 49 | from project import peopledetail |
| 50 | from project import peoplelist |
| 51 | from project import project_constants |
| 52 | from project import projectadmin |
| 53 | from project import projectadminadvanced |
| 54 | from project import projectexport |
| 55 | from project import projectsummary |
| 56 | from project import projectupdates |
| 57 | from project import redirects |
| 58 | |
| 59 | from search import backendnonviewable |
| 60 | from search import backendsearch |
| 61 | |
| 62 | from services import cachemanager_svc |
| 63 | from services import client_config_svc |
| 64 | |
| 65 | from sitewide import custom_404 |
| 66 | from sitewide import groupadmin |
| 67 | from sitewide import groupcreate |
| 68 | from sitewide import groupdetail |
| 69 | from sitewide import grouplist |
| 70 | from sitewide import hostinghome |
| 71 | from sitewide import moved |
| 72 | from sitewide import projectcreate |
| 73 | from sitewide import userprofile |
| 74 | from sitewide import usersettings |
| 75 | from sitewide import userclearbouncing |
| 76 | from sitewide import userupdates |
| 77 | |
| 78 | from tracker import componentcreate |
| 79 | from tracker import componentdetail |
| 80 | from tracker import fieldcreate |
| 81 | from tracker import fielddetail |
| 82 | from tracker import issueadmin |
| 83 | from tracker import issueadvsearch |
| 84 | from tracker import issueattachment |
| 85 | from tracker import issueattachmenttext |
| 86 | from tracker import issuebulkedit |
| 87 | from tracker import webcomponentspage |
| 88 | from tracker import issuedetailezt |
| 89 | from tracker import issueentry |
| 90 | from tracker import issueentryafterlogin |
| 91 | from tracker import issueexport |
| 92 | from tracker import issueimport |
| 93 | from tracker import issueoriginal |
| 94 | from tracker import issuereindex |
| 95 | from tracker import issuetips |
| 96 | from tracker import spam |
| 97 | from tracker import templatecreate |
| 98 | from tracker import templatedetail |
| 99 | from tracker import fltconversion |
| 100 | |
| 101 | from api import api_routes as api_routes_v0 |
| 102 | from api.v3 import api_routes as api_routes_v3 |
| 103 | |
| 104 | |
| 105 | class ServletRegistry(object): |
| 106 | |
| 107 | _PROJECT_NAME_REGEX = project_constants.PROJECT_NAME_PATTERN |
| 108 | _USERNAME_REGEX = r'[-+\w=.%]+(@([-a-z0-9]+\.)*[a-z0-9]+)?' |
| 109 | _HOTLIST_ID_NAME_REGEX = r'\d+|[a-zA-Z][-0-9a-zA-Z\.]*' |
| 110 | |
| 111 | def __init__(self): |
| 112 | self.routes = [] |
| 113 | |
| 114 | def _AddRoute(self, path_regex, servlet_class, method, does_write=False): |
| 115 | """Add a GET or POST handler to our webapp2 route list. |
| 116 | |
| 117 | Args: |
| 118 | path_regex: string with webapp2 URL template regex. |
| 119 | servlet_class: a subclass of class Servlet. |
| 120 | method: string 'GET' or 'POST'. |
| 121 | does_write: True if the servlet could write to the database, we skip |
| 122 | registering such servlets when the site is in read_only mode. GET |
| 123 | handlers never write. Most, but not all, POST handlers do write. |
| 124 | """ |
| 125 | if settings.read_only and does_write: |
| 126 | logging.info('Not registring %r because site is read-only', path_regex) |
| 127 | # TODO(jrobbins): register a helpful error page instead. |
| 128 | else: |
| 129 | self.routes.append( |
| 130 | webapp2.Route(path_regex, handler=servlet_class, methods=[method])) |
| 131 | |
| 132 | def _SetupServlets(self, spec_dict, base='', post_does_write=True): |
| 133 | """Register each of the given servlets.""" |
| 134 | for get_uri, servlet_class in spec_dict.items(): |
| 135 | self._AddRoute(base + get_uri, servlet_class, 'GET') |
| 136 | post_uri = get_uri + ('edit.do' if get_uri.endswith('/') else '.do') |
| 137 | self._AddRoute(base + post_uri, servlet_class, 'POST', |
| 138 | does_write=post_does_write) |
| 139 | |
| 140 | def _SetupProjectServlets(self, spec_dict, post_does_write=True): |
| 141 | """Register each of the given servlets in the project URI space.""" |
| 142 | self._SetupServlets( |
| 143 | spec_dict, base='/p/<project_name:%s>' % self._PROJECT_NAME_REGEX, |
| 144 | post_does_write=post_does_write) |
| 145 | |
| 146 | def _SetupUserServlets(self, spec_dict, post_does_write=True): |
| 147 | """Register each of the given servlets in the user URI space.""" |
| 148 | self._SetupServlets( |
| 149 | spec_dict, base='/u/<viewed_username:%s>' % self._USERNAME_REGEX, |
| 150 | post_does_write=post_does_write) |
| 151 | |
| 152 | def _SetupGroupServlets(self, spec_dict, post_does_write=True): |
| 153 | """Register each of the given servlets in the user group URI space.""" |
| 154 | self._SetupServlets( |
| 155 | spec_dict, base='/g/<viewed_username:%s>' % self._USERNAME_REGEX, |
| 156 | post_does_write=post_does_write) |
| 157 | |
| 158 | def _SetupUserHotlistServlets(self, spec_dict, post_does_write=True): |
| 159 | """ Register given user hotlist servlets in the user URI space.""" |
| 160 | self._SetupServlets( |
| 161 | spec_dict, |
| 162 | base ='/u/<viewed_username:%s>/hotlists/<hotlist_id:%s>' |
| 163 | % (self._USERNAME_REGEX, self._HOTLIST_ID_NAME_REGEX), |
| 164 | post_does_write=post_does_write) |
| 165 | |
| 166 | def Register(self, services): |
| 167 | """Register all the monorail request handlers.""" |
| 168 | self._RegisterFrameworkHandlers() |
| 169 | self._RegisterSitewideHandlers() |
| 170 | self._RegisterProjectHandlers() |
| 171 | self._RegisterIssueHandlers() |
| 172 | self._RegisterWebComponentsHanders() |
| 173 | self._RegisterRedirects() |
| 174 | self._RegisterInboundMail() |
| 175 | |
| 176 | # Register pRPC API routes |
| 177 | prpc_server = prpc.Server( |
| 178 | allowed_origins=client_config_svc.GetAllowedOriginsSet()) |
| 179 | api_routes_v0.RegisterApiHandlers(prpc_server, services) |
| 180 | api_routes_v3.RegisterApiHandlers(prpc_server, services) |
| 181 | self.routes.extend(prpc_server.get_routes()) |
| 182 | |
| 183 | autolink.RegisterAutolink(services) |
| 184 | # Error pages should be the last to register. |
| 185 | self._RegisterErrorPages() |
| 186 | return self.routes |
| 187 | |
| 188 | def _RegisterProjectHandlers(self): |
| 189 | """Register page and form handlers that operate within a project.""" |
| 190 | |
| 191 | self._SetupServlets({ |
| 192 | # Note: the following are at URLS that are not externally accessible. |
| 193 | urls.NOTIFY_RULES_DELETED_TASK: notify.NotifyRulesDeletedTask, |
| 194 | }) |
| 195 | self._SetupProjectServlets({ |
| 196 | urls.ADMIN_INTRO: projectsummary.ProjectSummary, |
| 197 | urls.PEOPLE_LIST: peoplelist.PeopleList, |
| 198 | urls.PEOPLE_DETAIL: peopledetail.PeopleDetail, |
| 199 | urls.UPDATES_LIST: projectupdates.ProjectUpdates, |
| 200 | urls.ADMIN_META: projectadmin.ProjectAdmin, |
| 201 | urls.ADMIN_ADVANCED: projectadminadvanced.ProjectAdminAdvanced, |
| 202 | urls.ADMIN_EXPORT: projectexport.ProjectExport, |
| 203 | urls.ADMIN_EXPORT_JSON: projectexport.ProjectExportJSON, |
| 204 | }) |
| 205 | |
| 206 | def _RegisterIssueHandlers(self): |
| 207 | """Register page and form handlers for the issue tracker.""" |
| 208 | self._SetupServlets({ |
| 209 | # Note: the following are at URLs that are not externaly accessible. |
| 210 | urls.BACKEND_SEARCH: backendsearch.BackendSearch, |
| 211 | urls.BACKEND_NONVIEWABLE: backendnonviewable.BackendNonviewable, |
| 212 | urls.RECOMPUTE_DERIVED_FIELDS_TASK: |
| 213 | filterrules.RecomputeDerivedFieldsTask, |
| 214 | urls.REINDEX_QUEUE_CRON: filterrules.ReindexQueueCron, |
| 215 | urls.NOTIFY_ISSUE_CHANGE_TASK: notify.NotifyIssueChangeTask, |
| 216 | urls.NOTIFY_BLOCKING_CHANGE_TASK: notify.NotifyBlockingChangeTask, |
| 217 | urls.NOTIFY_BULK_CHANGE_TASK: notify.NotifyBulkChangeTask, |
| 218 | urls.NOTIFY_APPROVAL_CHANGE_TASK: notify.NotifyApprovalChangeTask, |
| 219 | urls.OUTBOUND_EMAIL_TASK: notify.OutboundEmailTask, |
| 220 | urls.SPAM_DATA_EXPORT_TASK: spammodel.TrainingDataExportTask, |
| 221 | urls.DATE_ACTION_CRON: dateaction.DateActionCron, |
| 222 | urls.SPAM_TRAINING_CRON: spamtraining.TrainSpamModelCron, |
| 223 | urls.PUBLISH_PUBSUB_ISSUE_CHANGE_TASK: |
| 224 | pubsub.PublishPubsubIssueChangeTask, |
| 225 | urls.ISSUE_DATE_ACTION_TASK: dateaction.IssueDateActionTask, |
| 226 | urls.COMPONENT_DATA_EXPORT_CRON: |
| 227 | componentexport.ComponentTrainingDataExport, |
| 228 | urls.COMPONENT_DATA_EXPORT_TASK: |
| 229 | componentexport.ComponentTrainingDataExportTask, |
| 230 | urls.FLT_ISSUE_CONVERSION_TASK: fltconversion.FLTConvertTask, |
| 231 | }) |
| 232 | |
| 233 | self._SetupProjectServlets( |
| 234 | { |
| 235 | urls.ISSUE_APPROVAL: |
| 236 | registerpages_helpers.MakeRedirectInScope( |
| 237 | urls.ISSUE_DETAIL, 'p', keep_qs=True), |
| 238 | urls.ISSUE_LIST: |
| 239 | webcomponentspage.WebComponentsPage, |
| 240 | urls.ISSUE_LIST_NEW_TEMP: |
| 241 | registerpages_helpers.MakeRedirectInScope( |
| 242 | urls.ISSUE_LIST, 'p', keep_qs=True), |
| 243 | urls.ISSUE_REINDEX: |
| 244 | issuereindex.IssueReindex, |
| 245 | urls.ISSUE_DETAIL_FLIPPER_NEXT: |
| 246 | issuedetailezt.FlipperNext, |
| 247 | urls.ISSUE_DETAIL_FLIPPER_PREV: |
| 248 | issuedetailezt.FlipperPrev, |
| 249 | urls.ISSUE_DETAIL_FLIPPER_LIST: |
| 250 | issuedetailezt.FlipperList, |
| 251 | urls.ISSUE_DETAIL_FLIPPER_INDEX: |
| 252 | issuedetailezt.FlipperIndex, |
| 253 | urls.ISSUE_DETAIL_LEGACY: |
| 254 | registerpages_helpers.MakeRedirectInScope( |
| 255 | urls.ISSUE_DETAIL, 'p', keep_qs=True), |
| 256 | urls.ISSUE_WIZARD: |
| 257 | webcomponentspage.WebComponentsPage, |
| 258 | urls.ISSUE_ENTRY: |
| 259 | issueentry.IssueEntry, |
| 260 | urls.ISSUE_ENTRY_NEW: |
| 261 | webcomponentspage.WebComponentsPage, |
| 262 | urls.ISSUE_ENTRY_AFTER_LOGIN: |
| 263 | issueentryafterlogin.IssueEntryAfterLogin, |
| 264 | urls.ISSUE_TIPS: |
| 265 | issuetips.IssueSearchTips, |
| 266 | urls.ISSUE_ATTACHMENT: |
| 267 | issueattachment.AttachmentPage, |
| 268 | urls.ISSUE_ATTACHMENT_TEXT: |
| 269 | issueattachmenttext.AttachmentText, |
| 270 | urls.ISSUE_BULK_EDIT: |
| 271 | issuebulkedit.IssueBulkEdit, |
| 272 | urls.COMPONENT_CREATE: |
| 273 | componentcreate.ComponentCreate, |
| 274 | urls.COMPONENT_DETAIL: |
| 275 | componentdetail.ComponentDetail, |
| 276 | urls.FIELD_CREATE: |
| 277 | fieldcreate.FieldCreate, |
| 278 | urls.FIELD_DETAIL: |
| 279 | fielddetail.FieldDetail, |
| 280 | urls.TEMPLATE_CREATE: |
| 281 | templatecreate.TemplateCreate, |
| 282 | urls.TEMPLATE_DETAIL: |
| 283 | templatedetail.TemplateDetail, |
| 284 | urls.WIKI_LIST: |
| 285 | redirects.WikiRedirect, |
| 286 | urls.WIKI_PAGE: |
| 287 | redirects.WikiRedirect, |
| 288 | urls.SOURCE_PAGE: |
| 289 | redirects.SourceRedirect, |
| 290 | urls.ADMIN_STATUSES: |
| 291 | issueadmin.AdminStatuses, |
| 292 | urls.ADMIN_LABELS: |
| 293 | issueadmin.AdminLabels, |
| 294 | urls.ADMIN_RULES: |
| 295 | issueadmin.AdminRules, |
| 296 | urls.ADMIN_TEMPLATES: |
| 297 | issueadmin.AdminTemplates, |
| 298 | urls.ADMIN_COMPONENTS: |
| 299 | issueadmin.AdminComponents, |
| 300 | urls.ADMIN_VIEWS: |
| 301 | issueadmin.AdminViews, |
| 302 | urls.ISSUE_ORIGINAL: |
| 303 | issueoriginal.IssueOriginal, |
| 304 | urls.ISSUE_EXPORT: |
| 305 | issueexport.IssueExport, |
| 306 | urls.ISSUE_EXPORT_JSON: |
| 307 | issueexport.IssueExportJSON, |
| 308 | urls.ISSUE_IMPORT: |
| 309 | issueimport.IssueImport, |
| 310 | urls.SPAM_MODERATION_QUEUE: |
| 311 | spam.ModerationQueue, |
| 312 | }) |
| 313 | |
| 314 | # GETs for /issues/detail are now handled by the web components page. |
| 315 | base = '/p/<project_name:%s>' % self._PROJECT_NAME_REGEX |
| 316 | self._AddRoute(base + urls.ISSUE_DETAIL, |
| 317 | webcomponentspage.WebComponentsPage, 'GET') |
| 318 | |
| 319 | self._SetupUserServlets({ |
| 320 | urls.SAVED_QUERIES: savedqueries.SavedQueries, |
| 321 | urls.HOTLISTS: userhotlists.UserHotlists, |
| 322 | }) |
| 323 | |
| 324 | user_hotlists_redir = registerpages_helpers.MakeRedirectInScope( |
| 325 | urls.HOTLISTS, 'u', keep_qs=True) |
| 326 | self._SetupUserServlets({ |
| 327 | '/hotlists/': user_hotlists_redir, |
| 328 | }) |
| 329 | |
| 330 | # These servlets accept POST, but never write to the database, so they can |
| 331 | # still be used when the site is read-only. |
| 332 | self._SetupProjectServlets({ |
| 333 | urls.ISSUE_ADVSEARCH: issueadvsearch.IssueAdvancedSearch, |
| 334 | }, post_does_write=False) |
| 335 | |
| 336 | list_redir = registerpages_helpers.MakeRedirectInScope( |
| 337 | urls.ISSUE_LIST, 'p', keep_qs=True) |
| 338 | self._SetupProjectServlets({ |
| 339 | '': list_redir, |
| 340 | '/': list_redir, |
| 341 | '/issues': list_redir, |
| 342 | '/issues/': list_redir, |
| 343 | }) |
| 344 | |
| 345 | list_redir = registerpages_helpers.MakeRedirect(urls.ISSUE_LIST) |
| 346 | self._SetupServlets({ |
| 347 | '/issues': list_redir, |
| 348 | '/issues/': list_redir, |
| 349 | }) |
| 350 | |
| 351 | def _RegisterFrameworkHandlers(self): |
| 352 | """Register page and form handlers for framework functionality.""" |
| 353 | self._SetupServlets({ |
| 354 | urls.CSP_REPORT: csp_report.CSPReportPage, |
| 355 | |
| 356 | # These are only shown to users if specific conditions are met. |
| 357 | urls.EXCESSIVE_ACTIVITY: excessiveactivity.ExcessiveActivity, |
| 358 | urls.BANNED: banned.Banned, |
| 359 | urls.PROJECT_MOVED: moved.ProjectMoved, |
| 360 | |
| 361 | # These are not externally accessible |
| 362 | urls.RAMCACHE_CONSOLIDATE_CRON: cachemanager_svc.RamCacheConsolidate, |
| 363 | urls.REAP_CRON: reap.Reap, |
| 364 | urls.SPAM_DATA_EXPORT_CRON: spammodel.TrainingDataExport, |
| 365 | urls.LOAD_API_CLIENT_CONFIGS_CRON: ( |
| 366 | client_config_svc.LoadApiClientConfigs), |
| 367 | urls.CLIENT_MON: clientmon.ClientMonitor, |
| 368 | urls.TRIM_VISITED_PAGES_CRON: trimvisitedpages.TrimVisitedPages, |
| 369 | urls.TS_MON_JS: ts_mon_js.MonorailTSMonJSHandler, |
| 370 | urls.WARMUP: warmup.Warmup, |
| 371 | urls.START: warmup.Start, |
| 372 | urls.STOP: warmup.Stop |
| 373 | }) |
| 374 | |
| 375 | def _RegisterSitewideHandlers(self): |
| 376 | """Register page and form handlers that aren't associated with projects.""" |
| 377 | self._SetupServlets({ |
| 378 | urls.PROJECT_CREATE: projectcreate.ProjectCreate, |
| 379 | # The user settings page is a site-wide servlet, not under /u/. |
| 380 | urls.USER_SETTINGS: usersettings.UserSettings, |
| 381 | urls.HOSTING_HOME: hostinghome.HostingHome, |
| 382 | urls.GROUP_CREATE: groupcreate.GroupCreate, |
| 383 | urls.GROUP_LIST: grouplist.GroupList, |
| 384 | urls.GROUP_DELETE: grouplist.GroupList, |
| 385 | urls.HOTLIST_CREATE: hotlistcreate.HotlistCreate, |
| 386 | urls.BAN_SPAMMER_TASK: banspammer.BanSpammerTask, |
| 387 | urls.WIPEOUT_SYNC_CRON: deleteusers.WipeoutSyncCron, |
| 388 | urls.SEND_WIPEOUT_USER_LISTS_TASK: deleteusers.SendWipeoutUserListsTask, |
| 389 | urls.DELETE_WIPEOUT_USERS_TASK: deleteusers.DeleteWipeoutUsersTask, |
| 390 | urls.DELETE_USERS_TASK: deleteusers.DeleteUsersTask, |
| 391 | }) |
| 392 | |
| 393 | self._SetupUserServlets({ |
| 394 | urls.USER_PROFILE: userprofile.UserProfile, |
| 395 | urls.USER_PROFILE_POLYMER: userprofile.UserProfilePolymer, |
| 396 | urls.BAN_USER: userprofile.BanUser, |
| 397 | urls.BAN_SPAMMER: banspammer.BanSpammer, |
| 398 | urls.USER_CLEAR_BOUNCING: userclearbouncing.UserClearBouncing, |
| 399 | urls.USER_UPDATES_PROJECTS: userupdates.UserUpdatesProjects, |
| 400 | urls.USER_UPDATES_DEVELOPERS: userupdates.UserUpdatesDevelopers, |
| 401 | urls.USER_UPDATES_MINE: userupdates.UserUpdatesIndividual, |
| 402 | }) |
| 403 | |
| 404 | self._SetupUserHotlistServlets({ |
| 405 | urls.HOTLIST_ISSUES: hotlistissues.HotlistIssues, |
| 406 | urls.HOTLIST_ISSUES_CSV: hotlistissuescsv.HotlistIssuesCsv, |
| 407 | urls.HOTLIST_PEOPLE: hotlistpeople.HotlistPeopleList, |
| 408 | urls.HOTLIST_DETAIL: hotlistdetails.HotlistDetails, |
| 409 | urls.HOTLIST_RERANK_JSON: rerankhotlist.RerankHotlistIssue, |
| 410 | }) |
| 411 | |
| 412 | profile_redir = registerpages_helpers.MakeRedirectInScope( |
| 413 | urls.USER_PROFILE, 'u') |
| 414 | self._SetupUserServlets({'': profile_redir}) |
| 415 | |
| 416 | self._SetupGroupServlets({ |
| 417 | urls.GROUP_DETAIL: groupdetail.GroupDetail, |
| 418 | urls.GROUP_ADMIN: groupadmin.GroupAdmin, |
| 419 | }) |
| 420 | |
| 421 | def _RegisterWebComponentsHanders(self): |
| 422 | """Register page handlers that are handled by WebComponentsPage.""" |
| 423 | self._AddRoute('/', webcomponentspage.ProjectListPage, 'GET') |
| 424 | self._AddRoute( |
| 425 | '/hotlists<unused:.*>', webcomponentspage.WebComponentsPage, 'GET') |
| 426 | self._AddRoute('/users<unused:.*>', webcomponentspage.WebComponentsPage, |
| 427 | 'GET') |
| 428 | |
| 429 | def _RegisterRedirects(self): |
| 430 | """Register redirects among pages inside monorail.""" |
| 431 | redirect = registerpages_helpers.MakeRedirect('/') |
| 432 | self._SetupServlets( |
| 433 | { |
| 434 | '/projects/': redirect, |
| 435 | '/projects': redirect, |
| 436 | '/hosting/': redirect, |
| 437 | '/hosting': redirect, |
| 438 | '/p': redirect, |
| 439 | '/p/': redirect, |
| 440 | '/u': redirect, |
| 441 | '/u/': redirect, |
| 442 | '/': redirect, |
| 443 | }) |
| 444 | |
| 445 | redirect = registerpages_helpers.MakeRedirectInScope( |
| 446 | urls.PEOPLE_LIST, 'p') |
| 447 | self._SetupProjectServlets({ |
| 448 | '/people': redirect, |
| 449 | '/people/': redirect, |
| 450 | }) |
| 451 | |
| 452 | redirect = registerpages_helpers.MakeRedirect(urls.GROUP_LIST) |
| 453 | self._SetupServlets({'/g': redirect}) |
| 454 | |
| 455 | group_redir = registerpages_helpers.MakeRedirectInScope( |
| 456 | urls.USER_PROFILE, 'g') |
| 457 | self._SetupGroupServlets({'': group_redir}) |
| 458 | |
| 459 | def _RegisterInboundMail(self): |
| 460 | """Register a handler for inbound email and email bounces.""" |
| 461 | self.routes.append(webapp2.Route( |
| 462 | '/_ah/mail/<project_addr:.+>', |
| 463 | handler=inboundemail.InboundEmail, |
| 464 | methods=['POST', 'GET'])) |
| 465 | self.routes.append(webapp2.Route( |
| 466 | '/_ah/bounce', |
| 467 | handler=inboundemail.BouncedEmail, |
| 468 | methods=['POST', 'GET'])) |
| 469 | |
| 470 | def _RegisterErrorPages(self): |
| 471 | """Register handlers for errors.""" |
| 472 | self._AddRoute( |
| 473 | '/p/<project_name:%s>/<unrecognized:.+>' % self._PROJECT_NAME_REGEX, |
| 474 | custom_404.ErrorPage, 'GET') |