diff --git a/scripts/__init_script__.php b/scripts/__init_script__.php index 814acebc46..ba3c9562ae 100644 --- a/scripts/__init_script__.php +++ b/scripts/__init_script__.php @@ -1,31 +1,35 @@ array( 'Aphront304Response' => 'aphront/response/304', 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontAttachedFileView' => 'view/control/attachedfile', 'AphrontCSRFException' => 'aphront/exception/csrf', 'AphrontCalendarMonthView' => 'applications/calendar/view/month', 'AphrontContextBarView' => 'view/layout/contextbar', 'AphrontController' => 'aphront/controller', 'AphrontCrumbsView' => 'view/layout/crumbs', 'AphrontDatabaseConnection' => 'storage/connection/base', 'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration', 'AphrontDefaultApplicationController' => 'aphront/default/controller', 'AphrontDialogResponse' => 'aphront/response/dialog', 'AphrontDialogView' => 'view/dialog', 'AphrontErrorView' => 'view/form/error', 'AphrontException' => 'aphront/exception/base', 'AphrontFilePreviewView' => 'view/layout/filepreview', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormDividerControl' => 'view/form/control/divider', 'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormLayoutView' => 'view/form/layout', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormPasswordControl' => 'view/form/control/password', 'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha', 'AphrontFormSelectControl' => 'view/form/control/select', 'AphrontFormStaticControl' => 'view/form/control/static', 'AphrontFormSubmitControl' => 'view/form/control/submit', 'AphrontFormTextAreaControl' => 'view/form/control/textarea', 'AphrontFormTextControl' => 'view/form/control/text', 'AphrontFormToggleButtonsControl' => 'view/form/control/togglebuttons', 'AphrontFormTokenizerControl' => 'view/form/control/tokenizer', 'AphrontFormView' => 'view/form/base', 'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist', 'AphrontHeadsupActionView' => 'view/layout/headsup/action', 'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated', 'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__', 'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts', 'AphrontListFilterView' => 'view/layout/listfilter', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontNullView' => 'view/null', 'AphrontPageView' => 'view/page/base', 'AphrontPagerView' => 'view/control/pager', 'AphrontPanelView' => 'view/layout/panel', 'AphrontQueryAccessDeniedException' => 'storage/exception/accessdenied', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 'AphrontQueryCountException' => 'storage/exception/count', 'AphrontQueryDuplicateKeyException' => 'storage/exception/duplicatekey', 'AphrontQueryException' => 'storage/exception/base', 'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing', 'AphrontQueryParameterException' => 'storage/exception/parameter', 'AphrontQueryRecoverableException' => 'storage/exception/recoverable', 'AphrontRedirectException' => 'aphront/exception/redirect', 'AphrontRedirectResponse' => 'aphront/response/redirect', 'AphrontReloadResponse' => 'aphront/response/reload', 'AphrontRequest' => 'aphront/request', 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontResponse' => 'aphront/response/base', + 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/scopeguard', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontTokenizerTemplateView' => 'view/control/tokenizer', 'AphrontTypeaheadTemplateView' => 'view/control/typeahead', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', + 'AphrontWriteGuard' => 'aphront/writeguard', 'CelerityAPI' => 'infrastructure/celerity/api', 'CelerityResourceController' => 'infrastructure/celerity/controller', 'CelerityResourceMap' => 'infrastructure/celerity/map', 'CelerityStaticResourceResponse' => 'infrastructure/celerity/response', 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_conduit_getcertificate_Method' => 'applications/conduit/method/conduit/getcertificate', 'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping', 'ConduitAPI_daemon_launched_Method' => 'applications/conduit/method/daemon/launched', 'ConduitAPI_daemon_log_Method' => 'applications/conduit/method/daemon/log', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/createrevision', 'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/find', 'ConduitAPI_differential_getalldiffs_Method' => 'applications/conduit/method/differential/getalldiffs', 'ConduitAPI_differential_getcommitmessage_Method' => 'applications/conduit/method/differential/getcommitmessage', 'ConduitAPI_differential_getcommitpaths_Method' => 'applications/conduit/method/differential/getcommitpaths', 'ConduitAPI_differential_getdiff_Method' => 'applications/conduit/method/differential/getdiff', 'ConduitAPI_differential_getrevision_Method' => 'applications/conduit/method/differential/getrevision', 'ConduitAPI_differential_getrevisionfeedback_Method' => 'applications/conduit/method/differential/getrevisionfeedback', 'ConduitAPI_differential_markcommitted_Method' => 'applications/conduit/method/differential/markcommitted', 'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_differential_updaterevision_Method' => 'applications/conduit/method/differential/updaterevision', 'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'applications/conduit/method/differential/updatetaskrevisionassoc', 'ConduitAPI_differential_updateunitresults_Method' => 'applications/conduit/method/differential/updateunitresults', 'ConduitAPI_diffusion_getcommits_Method' => 'applications/conduit/method/diffusion/getcommits', 'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'applications/conduit/method/diffusion/getrecentcommitsbypath', 'ConduitAPI_feed_publish_Method' => 'applications/conduit/method/feed/publish', 'ConduitAPI_file_download_Method' => 'applications/conduit/method/file/download', 'ConduitAPI_file_info_Method' => 'applications/conduit/method/file/info', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_maniphest_info_Method' => 'applications/conduit/method/maniphest/info', 'ConduitAPI_paste_Method' => 'applications/conduit/method/paste/base', 'ConduitAPI_paste_create_Method' => 'applications/conduit/method/paste/create', 'ConduitAPI_paste_info_Method' => 'applications/conduit/method/paste/info', 'ConduitAPI_path_getowners_Method' => 'applications/conduit/method/path/getowners', 'ConduitAPI_slowvote_info_Method' => 'applications/conduit/method/slowvote/info', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitAPI_user_whoami_Method' => 'applications/conduit/method/user/whoami', 'ConduitException' => 'applications/conduit/protocol/exception', 'DarkConsole' => 'aphront/console/api', 'DarkConsoleConfigPlugin' => 'aphront/console/plugin/config', 'DarkConsoleController' => 'aphront/console/controller', 'DarkConsoleCore' => 'aphront/console/core', 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog', 'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/api', 'DarkConsolePlugin' => 'aphront/console/plugin/base', 'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request', 'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services', 'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof', 'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api', 'DatabaseConfigurationProvider' => 'applications/base/storage/configuration', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialApplyPatchFieldSpecification' => 'applications/differential/field/specification/applypatch', 'DifferentialArcanistProjectFieldSpecification' => 'applications/differential/field/specification/arcanistproject', 'DifferentialAuthorFieldSpecification' => 'applications/differential/field/specification/author', 'DifferentialAuxiliaryField' => 'applications/differential/storage/auxiliaryfield', 'DifferentialBlameRevisionFieldSpecification' => 'applications/differential/field/specification/blamerev', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', 'DifferentialCCsFieldSpecification' => 'applications/differential/field/specification/ccs', 'DifferentialChangeType' => 'applications/differential/constants/changetype', 'DifferentialChangeset' => 'applications/differential/storage/changeset', 'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview', 'DifferentialChangesetListView' => 'applications/differential/view/changesetlistview', 'DifferentialChangesetParser' => 'applications/differential/parser/changeset', 'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview', 'DifferentialComment' => 'applications/differential/storage/comment', 'DifferentialCommentEditor' => 'applications/differential/editor/comment', 'DifferentialCommentMail' => 'applications/differential/mail/comment', 'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview', 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialCommitMessage' => 'applications/differential/parser/commitmessage', 'DifferentialCommitMessageData' => 'applications/differential/data/commitmessage', 'DifferentialCommitMessageField' => 'applications/differential/data/commitmessage', 'DifferentialCommitMessageModifier' => 'applications/differential/data/commitmessage', 'DifferentialCommitMessageParserException' => 'applications/differential/parser/commitmessage/exception', 'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/commits', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDefaultFieldSelector' => 'applications/differential/field/selector/default', 'DifferentialDependenciesFieldSpecification' => 'applications/differential/field/specification/dependencies', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffCreateController' => 'applications/differential/controller/diffcreate', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', 'DifferentialExceptionMail' => 'applications/differential/mail/exception', 'DifferentialExportPatchFieldSpecification' => 'applications/differential/field/specification/exportpatch', 'DifferentialFieldDataNotAvailableException' => 'applications/differential/field/exception/notavailable', 'DifferentialFieldSelector' => 'applications/differential/field/selector/base', 'DifferentialFieldSpecification' => 'applications/differential/field/specification/base', 'DifferentialFieldSpecificationIncompleteException' => 'applications/differential/field/exception/incomplete', 'DifferentialFieldValidationException' => 'applications/differential/field/exception/validation', 'DifferentialGitSVNIDFieldSpecification' => 'applications/differential/field/specification/gitsvnid', 'DifferentialHostFieldSpecification' => 'applications/differential/field/specification/host', 'DifferentialHunk' => 'applications/differential/storage/hunk', 'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment', 'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit', 'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/inlinecommentpreview', 'DifferentialInlineCommentView' => 'applications/differential/view/inlinecomment', 'DifferentialLinesFieldSpecification' => 'applications/differential/field/specification/lines', 'DifferentialLintFieldSpecification' => 'applications/differential/field/specification/lint', 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 'DifferentialMail' => 'applications/differential/mail/base', 'DifferentialManiphestTasksFieldSpecification' => 'applications/differential/field/specification/maniphesttasks', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 'DifferentialPathFieldSpecification' => 'applications/differential/field/specification/path', 'DifferentialPrimaryPaneView' => 'applications/differential/view/primarypane', 'DifferentialReplyHandler' => 'applications/differential/replyhandler', 'DifferentialRevertPlanFieldSpecification' => 'applications/differential/field/specification/revertplan', 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 'DifferentialReviewedByFieldSpecification' => 'applications/differential/field/specification/reviewedby', 'DifferentialReviewersFieldSpecification' => 'applications/differential/field/specification/reviewers', 'DifferentialRevision' => 'applications/differential/storage/revision', 'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist', 'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionDetailRenderer' => 'applications/differential/controller/customrenderer', 'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionIDFieldSpecification' => 'applications/differential/field/specification/revisionid', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionListData' => 'applications/differential/data/revisionlist', 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 'DifferentialRevisionStatusFieldSpecification' => 'applications/differential/field/specification/revisionstatus', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialSubscribeController' => 'applications/differential/controller/subscribe', 'DifferentialSummaryFieldSpecification' => 'applications/differential/field/specification/summary', 'DifferentialTasksAttacher' => 'applications/differential/tasks', 'DifferentialTestPlanFieldSpecification' => 'applications/differential/field/specification/testplan', 'DifferentialTitleFieldSpecification' => 'applications/differential/field/specification/title', 'DifferentialUnitFieldSpecification' => 'applications/differential/field/specification/unit', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'DifferentialUnitTestResult' => 'applications/differential/constants/unittestresult', 'DifferentialViewTime' => 'applications/differential/storage/viewtime', 'DiffusionBranchInformation' => 'applications/diffusion/data/branch', 'DiffusionBranchQuery' => 'applications/diffusion/query/branch/base', 'DiffusionBranchTableView' => 'applications/diffusion/view/branchtable', 'DiffusionBrowseController' => 'applications/diffusion/controller/browse', 'DiffusionBrowseFileController' => 'applications/diffusion/controller/file', 'DiffusionBrowseQuery' => 'applications/diffusion/query/browse/base', 'DiffusionBrowseTableView' => 'applications/diffusion/view/browsetable', 'DiffusionChangeController' => 'applications/diffusion/controller/change', 'DiffusionCommitChangeTableView' => 'applications/diffusion/view/commitchangetable', 'DiffusionCommitController' => 'applications/diffusion/controller/commit', 'DiffusionCommitListController' => 'applications/diffusion/controller/commitlist', 'DiffusionController' => 'applications/diffusion/controller/base', 'DiffusionDiffController' => 'applications/diffusion/controller/diff', 'DiffusionDiffQuery' => 'applications/diffusion/query/diff/base', 'DiffusionFileContent' => 'applications/diffusion/data/filecontent', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/base', 'DiffusionGitBranchQuery' => 'applications/diffusion/query/branch/git', 'DiffusionGitBrowseQuery' => 'applications/diffusion/query/browse/git', 'DiffusionGitDiffQuery' => 'applications/diffusion/query/diff/git', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/git', 'DiffusionGitHistoryQuery' => 'applications/diffusion/query/history/git', 'DiffusionGitLastModifiedQuery' => 'applications/diffusion/query/lastmodified/git', 'DiffusionGitPathIDQuery' => 'applications/diffusion/query/pathid/base', 'DiffusionGitRequest' => 'applications/diffusion/request/git', 'DiffusionHistoryController' => 'applications/diffusion/controller/history', 'DiffusionHistoryQuery' => 'applications/diffusion/query/history/base', 'DiffusionHistoryTableView' => 'applications/diffusion/view/historytable', 'DiffusionHomeController' => 'applications/diffusion/controller/home', 'DiffusionLastModifiedController' => 'applications/diffusion/controller/lastmodified', 'DiffusionLastModifiedQuery' => 'applications/diffusion/query/lastmodified/base', 'DiffusionPathChange' => 'applications/diffusion/data/pathchange', 'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base', 'DiffusionPathCompleteController' => 'applications/diffusion/controller/pathcomplete', 'DiffusionPathValidateController' => 'applications/diffusion/controller/pathvalidate', 'DiffusionRepositoryController' => 'applications/diffusion/controller/repository', 'DiffusionRepositoryPath' => 'applications/diffusion/data/repositorypath', 'DiffusionRequest' => 'applications/diffusion/request/base', 'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/svn', 'DiffusionSvnDiffQuery' => 'applications/diffusion/query/diff/svn', 'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/svn', 'DiffusionSvnHistoryQuery' => 'applications/diffusion/query/history/svn', 'DiffusionSvnLastModifiedQuery' => 'applications/diffusion/query/lastmodified/svn', 'DiffusionSvnRequest' => 'applications/diffusion/request/svn', 'DiffusionView' => 'applications/diffusion/view/base', 'HeraldAction' => 'applications/herald/storage/action', 'HeraldActionConfig' => 'applications/herald/config/action', 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/apply', 'HeraldCommitAdapter' => 'applications/herald/adapter/commit', 'HeraldCondition' => 'applications/herald/storage/condition', 'HeraldConditionConfig' => 'applications/herald/config/condition', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/condition', 'HeraldContentTypeConfig' => 'applications/herald/config/contenttype', 'HeraldController' => 'applications/herald/controller/base', 'HeraldDAO' => 'applications/herald/storage/base', 'HeraldDeleteController' => 'applications/herald/controller/delete', 'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential', 'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun', 'HeraldEffect' => 'applications/herald/engine/effect', 'HeraldEngine' => 'applications/herald/engine/engine', 'HeraldFieldConfig' => 'applications/herald/config/field', 'HeraldHomeController' => 'applications/herald/controller/home', 'HeraldInvalidConditionException' => 'applications/herald/engine/engine/exception', 'HeraldInvalidFieldException' => 'applications/herald/engine/engine/exception', 'HeraldNewController' => 'applications/herald/controller/new', 'HeraldObjectAdapter' => 'applications/herald/adapter/base', 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/object', 'HeraldRecursiveConditionsException' => 'applications/herald/engine/engine/exception', 'HeraldRepetitionPolicyConfig' => 'applications/herald/config/repetitionpolicy', 'HeraldRule' => 'applications/herald/storage/rule', 'HeraldRuleController' => 'applications/herald/controller/rule', 'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule', 'HeraldTestConsoleController' => 'applications/herald/controller/test', 'HeraldTranscript' => 'applications/herald/storage/transcript/base', 'HeraldTranscriptController' => 'applications/herald/controller/transcript', 'HeraldTranscriptListController' => 'applications/herald/controller/transcriptlist', 'HeraldValueTypeConfig' => 'applications/herald/config/valuetype', 'Javelin' => 'infrastructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'LiskIsolationTestCase' => 'storage/lisk/dao/__tests__', 'LiskIsolationTestDAO' => 'storage/lisk/dao/__tests__', 'LiskIsolationTestDAOException' => 'storage/lisk/dao/__tests__', 'ManiphestAuxiliaryFieldDefaultSpecification' => 'applications/maniphest/auxiliaryfield/default', 'ManiphestAuxiliaryFieldSpecification' => 'applications/maniphest/auxiliaryfield/base', 'ManiphestAuxiliaryFieldTypeException' => 'applications/maniphest/auxiliaryfield/typeexception', 'ManiphestAuxiliaryFieldValidationException' => 'applications/maniphest/auxiliaryfield/validationexception', 'ManiphestConstants' => 'applications/maniphest/constants/base', 'ManiphestController' => 'applications/maniphest/controller/base', 'ManiphestDAO' => 'applications/maniphest/storage/base', 'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/task', 'ManiphestReplyHandler' => 'applications/maniphest/replyhandler', 'ManiphestTask' => 'applications/maniphest/storage/task', 'ManiphestTaskAuxiliaryStorage' => 'applications/maniphest/storage/auxiliary', 'ManiphestTaskDescriptionChangeController' => 'applications/maniphest/controller/descriptionchange', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail', 'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit', 'ManiphestTaskExtensions' => 'applications/maniphest/extensions/base', 'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist', 'ManiphestTaskListView' => 'applications/maniphest/view/tasklist', 'ManiphestTaskOwner' => 'applications/maniphest/constants/owner', 'ManiphestTaskPriority' => 'applications/maniphest/constants/priority', 'ManiphestTaskProject' => 'applications/maniphest/storage/taskproject', 'ManiphestTaskQuery' => 'applications/maniphest/query', 'ManiphestTaskStatus' => 'applications/maniphest/constants/status', 'ManiphestTaskSubscriber' => 'applications/maniphest/storage/subscriber', 'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary', 'ManiphestTransaction' => 'applications/maniphest/storage/transaction', 'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction', 'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist', 'ManiphestTransactionPreviewController' => 'applications/maniphest/controller/transactionpreview', 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave', 'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype', 'ManiphestView' => 'applications/maniphest/view/base', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/browse', 'PhabricatorCalendarController' => 'applications/calendar/controller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', 'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/token', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console', 'PhabricatorConduitController' => 'applications/conduit/controller/base', 'PhabricatorConduitDAO' => 'applications/conduit/storage/base', 'PhabricatorConduitLogController' => 'applications/conduit/controller/log', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', 'PhabricatorConduitTokenController' => 'applications/conduit/controller/token', 'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorCountdownController' => 'applications/countdown/controller/base', 'PhabricatorCountdownDAO' => 'applications/countdown/storage/base', 'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/delete', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/edit', 'PhabricatorCountdownListController' => 'applications/countdown/controller/list', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/view', 'PhabricatorDaemon' => 'infrastructure/daemon/base', 'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/combined', 'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/console', 'PhabricatorDaemonControl' => 'infrastructure/daemon/control', 'PhabricatorDaemonController' => 'applications/daemon/controller/base', 'PhabricatorDaemonDAO' => 'infrastructure/daemon/storage/base', 'PhabricatorDaemonLog' => 'infrastructure/daemon/storage/log', 'PhabricatorDaemonLogEvent' => 'infrastructure/daemon/storage/event', 'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/daemonlogevents', 'PhabricatorDaemonLogListController' => 'applications/daemon/controller/loglist', 'PhabricatorDaemonLogListView' => 'applications/daemon/view/daemonloglist', 'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/logview', 'PhabricatorDaemonReference' => 'infrastructure/daemon/control/reference', 'PhabricatorDaemonTimelineConsoleController' => 'applications/daemon/controller/timeline', 'PhabricatorDaemonTimelineEventController' => 'applications/daemon/controller/timelineevent', 'PhabricatorDefaultFileStorageEngineSelector' => 'applications/files/engineselector/default', 'PhabricatorDefaultSearchEngineSelector' => 'applications/search/selector/default', 'PhabricatorDifferenceEngine' => 'infrastructure/diff/engine', 'PhabricatorDirectoryCategory' => 'applications/directory/storage/category', 'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete', 'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit', 'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist', 'PhabricatorDirectoryController' => 'applications/directory/controller/base', 'PhabricatorDirectoryDAO' => 'applications/directory/storage/base', 'PhabricatorDirectoryItem' => 'applications/directory/storage/item', 'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete', 'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit', 'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist', 'PhabricatorDirectoryMainController' => 'applications/directory/controller/main', 'PhabricatorDisabledUserController' => 'applications/auth/controller/disabled', 'PhabricatorDraft' => 'applications/draft/storage/draft', 'PhabricatorDraftDAO' => 'applications/draft/storage/base', 'PhabricatorEmailLoginController' => 'applications/auth/controller/email', 'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken', 'PhabricatorEnv' => 'infrastructure/env', 'PhabricatorFeedConstants' => 'applications/feed/constants/base', 'PhabricatorFeedController' => 'applications/feed/controller/base', 'PhabricatorFeedDAO' => 'applications/feed/storage/base', 'PhabricatorFeedPublicStreamController' => 'applications/feed/controller/publicstream', 'PhabricatorFeedQuery' => 'applications/feed/query', 'PhabricatorFeedStory' => 'applications/feed/story/base', 'PhabricatorFeedStoryData' => 'applications/feed/storage/story', 'PhabricatorFeedStoryDifferential' => 'applications/feed/story/differential', 'PhabricatorFeedStoryPhriction' => 'applications/feed/story/phriction', 'PhabricatorFeedStoryPublisher' => 'applications/feed/publisher', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/storyreference', 'PhabricatorFeedStoryStatus' => 'applications/feed/story/status', 'PhabricatorFeedStoryTypeConstants' => 'applications/feed/constants/story', 'PhabricatorFeedStoryUnknown' => 'applications/feed/story/unknown', 'PhabricatorFeedStoryView' => 'applications/feed/view/story', 'PhabricatorFeedStreamController' => 'applications/feed/controller/stream', 'PhabricatorFeedView' => 'applications/feed/view/base', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileAltViewController' => 'applications/files/controller/altview', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', 'PhabricatorFileDropUploadController' => 'applications/files/controller/dropupload', 'PhabricatorFileImageMacro' => 'applications/files/storage/imagemacro', 'PhabricatorFileListController' => 'applications/files/controller/list', 'PhabricatorFileMacroDeleteController' => 'applications/files/controller/macrodelete', 'PhabricatorFileMacroEditController' => 'applications/files/controller/macroedit', 'PhabricatorFileMacroListController' => 'applications/files/controller/macrolist', 'PhabricatorFileProxyController' => 'applications/files/controller/proxy', 'PhabricatorFileProxyImage' => 'applications/files/storage/proxyimage', 'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob', 'PhabricatorFileStorageEngine' => 'applications/files/engine/base', 'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/base', 'PhabricatorFileTransformController' => 'applications/files/controller/transform', 'PhabricatorFileURI' => 'applications/files/uri', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileUploadException' => 'applications/files/exception/upload', 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector', 'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', 'PhabricatorHelpController' => 'applications/help/controller/base', 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/keyboardshortcut', 'PhabricatorIRCBot' => 'infrastructure/daemon/irc/bot', 'PhabricatorIRCHandler' => 'infrastructure/daemon/irc/handler/base', 'PhabricatorIRCMessage' => 'infrastructure/daemon/irc/message', 'PhabricatorIRCObjectNameHandler' => 'infrastructure/daemon/irc/handler/objectname', 'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/irc/handler/protocol', 'PhabricatorImageTransformer' => 'applications/files/transform', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/javelin', 'PhabricatorLintEngine' => 'infrastructure/lint/engine', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/localdisk', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/sendgrid', 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/test', 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/base', 'PhabricatorMarkupEngine' => 'applications/markup/engine', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTADaemon' => 'applications/metamta/daemon/mta', 'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__', 'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/mail/__tests__', 'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist', 'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTAReceiveController' => 'applications/metamta/controller/receive', 'PhabricatorMetaMTAReceivedListController' => 'applications/metamta/controller/receivedlist', 'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/receivedmail', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/sendgridreceive', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/mysql', 'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default', 'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics', 'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure', 'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth', 'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base', 'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook', 'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github', 'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base', 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink', 'PhabricatorObjectGraph' => 'applications/phid/graph', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleConstants' => 'applications/phid/handle/const/base', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorObjectHandleStatus' => 'applications/phid/handle/const/status', 'PhabricatorObjectSelectorDialog' => 'view/control/objectselector', 'PhabricatorOwnersController' => 'applications/owners/controller/base', 'PhabricatorOwnersDAO' => 'applications/owners/storage/base', 'PhabricatorOwnersDeleteController' => 'applications/owners/controller/delete', 'PhabricatorOwnersDetailController' => 'applications/owners/controller/detail', 'PhabricatorOwnersEditController' => 'applications/owners/controller/edit', 'PhabricatorOwnersListController' => 'applications/owners/controller/list', 'PhabricatorOwnersOwner' => 'applications/owners/storage/owner', 'PhabricatorOwnersPackage' => 'applications/owners/storage/package', 'PhabricatorOwnersPath' => 'applications/owners/storage/path', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDConstants' => 'applications/phid/constants', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDListController' => 'applications/phid/controller/list', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPaste' => 'applications/paste/storage/paste', 'PhabricatorPasteController' => 'applications/paste/controller/base', 'PhabricatorPasteCreateController' => 'applications/paste/controller/create', 'PhabricatorPasteDAO' => 'applications/paste/storage/base', 'PhabricatorPasteListController' => 'applications/paste/controller/list', 'PhabricatorPasteViewController' => 'applications/paste/controller/view', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleLogsController' => 'applications/people/controller/logs', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorProfileView' => 'view/layout/profile', 'PhabricatorProject' => 'applications/project/storage/project', 'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation', 'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation', 'PhabricatorProjectController' => 'applications/project/controller/base', 'PhabricatorProjectCreateController' => 'applications/project/controller/create', 'PhabricatorProjectDAO' => 'applications/project/storage/base', 'PhabricatorProjectListController' => 'applications/project/controller/list', 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit', 'PhabricatorProjectStatus' => 'applications/project/constants/status', 'PhabricatorProjectSubproject' => 'applications/project/storage/subproject', 'PhabricatorRedirectController' => 'applications/base/controller/redirect', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/refresh', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleDifferentialHandle' => 'infrastructure/markup/remarkup/markuprule/handle/differential', 'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion', 'PhabricatorRemarkupRuleEmbedFile' => 'infrastructure/markup/remarkup/markuprule/embedobject', 'PhabricatorRemarkupRuleImageMacro' => 'infrastructure/markup/remarkup/markuprule/imagemacro', 'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest', 'PhabricatorRemarkupRuleManiphestHandle' => 'infrastructure/markup/remarkup/markuprule/handle/maniphest', 'PhabricatorRemarkupRuleMention' => 'infrastructure/markup/remarkup/markuprule/mention', 'PhabricatorRemarkupRuleObjectHandle' => 'infrastructure/markup/remarkup/markuprule/handle', 'PhabricatorRemarkupRuleObjectName' => 'infrastructure/markup/remarkup/markuprule/objectname', 'PhabricatorRemarkupRulePaste' => 'infrastructure/markup/remarkup/markuprule/paste', 'PhabricatorRemarkupRulePhriction' => 'infrastructure/markup/remarkup/markuprule/phriction', 'PhabricatorRemarkupRuleProxyImage' => 'infrastructure/markup/remarkup/markuprule/proxyimage', 'PhabricatorRemarkupRuleYoutube' => 'infrastructure/markup/remarkup/markuprule/youtube', 'PhabricatorRepository' => 'applications/repository/storage/repository', 'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/arcanistproject', 'PhabricatorRepositoryArcanistProjectEditController' => 'applications/repository/controller/arcansistprojectedit', 'PhabricatorRepositoryCommit' => 'applications/repository/storage/commit', 'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/base', 'PhabricatorRepositoryCommitData' => 'applications/repository/storage/commitdata', 'PhabricatorRepositoryCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/base', 'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/herald', 'PhabricatorRepositoryCommitMessageDetailParser' => 'applications/repository/parser/base', 'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/base', 'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/base', 'PhabricatorRepositoryCommitTaskDaemon' => 'applications/repository/daemon/committask', 'PhabricatorRepositoryController' => 'applications/repository/controller/base', 'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/base', 'PhabricatorRepositoryDaemon' => 'applications/repository/daemon/base', 'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'applications/repository/parser/default', 'PhabricatorRepositoryDeleteController' => 'applications/repository/controller/delete', 'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/git', 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git', 'PhabricatorRepositoryGitFetchDaemon' => 'applications/repository/daemon/gitfetch', 'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification', 'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive', 'PhabricatorRepositoryListController' => 'applications/repository/controller/list', 'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn', 'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/svn', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/svn', 'PhabricatorRepositoryType' => 'applications/repository/constants/repositorytype', 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/s3', 'PhabricatorSQLPatchList' => 'infrastructure/setup/sql', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument', 'PhabricatorSearchAttachController' => 'applications/search/controller/attach', 'PhabricatorSearchBaseController' => 'applications/search/controller/base', 'PhabricatorSearchCommitIndexer' => 'applications/search/index/indexer/repository', 'PhabricatorSearchController' => 'applications/search/controller/search', 'PhabricatorSearchDAO' => 'applications/search/storage/base', 'PhabricatorSearchDifferentialIndexer' => 'applications/search/index/indexer/differential', 'PhabricatorSearchDocument' => 'applications/search/storage/document/document', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/field', 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/indexer/base', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/relationship', 'PhabricatorSearchEngine' => 'applications/search/engine/base', 'PhabricatorSearchEngineMySQL' => 'applications/search/engine/mysql', 'PhabricatorSearchEngineSelector' => 'applications/search/selector/base', 'PhabricatorSearchField' => 'applications/search/constants/field', 'PhabricatorSearchIndexController' => 'applications/search/controller/index', 'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest', 'PhabricatorSearchPhrictionIndexer' => 'applications/search/index/indexer/phriction', 'PhabricatorSearchQuery' => 'applications/search/storage/query', 'PhabricatorSearchRelationship' => 'applications/search/constants/relationship', 'PhabricatorSearchResultView' => 'applications/search/view/searchresult', 'PhabricatorSearchSelectController' => 'applications/search/controller/select', 'PhabricatorSearchUserIndexer' => 'applications/search/index/indexer/user', 'PhabricatorSetup' => 'infrastructure/setup', 'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/choice', 'PhabricatorSlowvoteComment' => 'applications/slowvote/storage/comment', 'PhabricatorSlowvoteController' => 'applications/slowvote/controller/base', 'PhabricatorSlowvoteCreateController' => 'applications/slowvote/controller/create', 'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/base', 'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/list', 'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/option', 'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/poll', 'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/poll', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorStatusController' => 'applications/status/base', 'PhabricatorSyntaxHighlighter' => 'applications/markup/syntax', 'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/taskmaster', 'PhabricatorTestCase' => 'infrastructure/testing/testcase', 'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/cursor', 'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/base', 'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/event', 'PhabricatorTimelineEventData' => 'infrastructure/daemon/timeline/storage/eventdata', 'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/iterator', 'PhabricatorTimer' => 'applications/countdown/storage/timer', 'PhabricatorTransformedFile' => 'applications/files/storage/transformed', 'PhabricatorTrivialTestCase' => 'infrastructure/testing/testcase/__tests__', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUIExample' => 'applications/uiexample/examples/base', 'PhabricatorUIExampleController' => 'applications/uiexample/controller/base', 'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/render', 'PhabricatorUIListFilterExample' => 'applications/uiexample/examples/listfilter', 'PhabricatorUIPagerExample' => 'applications/uiexample/examples/pager', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account', 'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit', 'PhabricatorUserDAO' => 'applications/people/storage/base', 'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email', 'PhabricatorUserLog' => 'applications/people/storage/log', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo', 'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/oauth', 'PhabricatorUserPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/preferences', 'PhabricatorUserPreferences' => 'applications/people/storage/preferences', 'PhabricatorUserProfile' => 'applications/people/storage/profile', 'PhabricatorUserProfileSettingsPanelController' => 'applications/people/controller/settings/panels/profile', 'PhabricatorUserSSHKey' => 'applications/people/storage/usersshkey', 'PhabricatorUserSSHKeysSettingsPanelController' => 'applications/people/controller/settings/panels/sshkeys', 'PhabricatorUserSettingsController' => 'applications/people/controller/settings', 'PhabricatorUserSettingsPanelController' => 'applications/people/controller/settings/panels/base', 'PhabricatorWorker' => 'infrastructure/daemon/workers/worker', 'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/base', 'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/task', 'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/taskdata', 'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/workertaskdetail', 'PhabricatorXHPASTViewController' => 'applications/xhpastview/controller/base', 'PhabricatorXHPASTViewDAO' => 'applications/xhpastview/storage/base', 'PhabricatorXHPASTViewFrameController' => 'applications/xhpastview/controller/viewframe', 'PhabricatorXHPASTViewFramesetController' => 'applications/xhpastview/controller/viewframeset', 'PhabricatorXHPASTViewInputController' => 'applications/xhpastview/controller/viewinput', 'PhabricatorXHPASTViewPanelController' => 'applications/xhpastview/controller/viewpanel', 'PhabricatorXHPASTViewParseTree' => 'applications/xhpastview/storage/parsetree', 'PhabricatorXHPASTViewRunController' => 'applications/xhpastview/controller/run', 'PhabricatorXHPASTViewStreamController' => 'applications/xhpastview/controller/viewstream', 'PhabricatorXHPASTViewTreeController' => 'applications/xhpastview/controller/viewtree', 'PhabricatorXHProfController' => 'applications/xhprof/controller/base', 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile', 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol', 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel', 'PhrictionActionConstants' => 'applications/phriction/constants/action', 'PhrictionConstants' => 'applications/phriction/constants/base', 'PhrictionContent' => 'applications/phriction/storage/content', 'PhrictionController' => 'applications/phriction/controller/base', 'PhrictionDAO' => 'applications/phriction/storage/base', 'PhrictionDiffController' => 'applications/phriction/controller/diff', 'PhrictionDocument' => 'applications/phriction/storage/document', 'PhrictionDocumentController' => 'applications/phriction/controller/document', 'PhrictionDocumentPreviewController' => 'applications/phriction/controller/documentpreview', 'PhrictionDocumentTestCase' => 'applications/phriction/storage/document/__tests__', 'PhrictionEditController' => 'applications/phriction/controller/edit', 'PhrictionHistoryController' => 'applications/phriction/controller/history', 'PhrictionListController' => 'applications/phriction/controller/list', ), 'function' => array( '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infrastructure/celerity/api', 'celerity_register_resource_map' => 'infrastructure/celerity/map', 'javelin_render_tag' => 'infrastructure/javelin/markup', 'phabricator_date' => 'view/utils', 'phabricator_datetime' => 'view/utils', 'phabricator_format_relative_time' => 'view/utils', 'phabricator_format_timestamp' => 'view/utils', 'phabricator_format_units_generic' => 'view/utils', 'phabricator_render_form' => 'infrastructure/javelin/markup', 'phabricator_time' => 'view/utils', 'qsprintf' => 'storage/qsprintf', 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', 'require_celerity_resource' => 'infrastructure/celerity/api', 'vqsprintf' => 'storage/qsprintf', 'vqueryfx' => 'storage/queryfx', 'vqueryfx_all' => 'storage/queryfx', 'xsprintf_query' => 'storage/qsprintf', ), 'requires_class' => array( 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontAttachedFileView' => 'AphrontView', 'AphrontCSRFException' => 'AphrontException', 'AphrontCalendarMonthView' => 'AphrontView', 'AphrontContextBarView' => 'AphrontView', 'AphrontCrumbsView' => 'AphrontView', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFilePreviewView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormLayoutView' => 'AphrontView', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormToggleButtonsControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontHeadsupActionListView' => 'AphrontView', 'AphrontHeadsupActionView' => 'AphrontView', 'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase', 'AphrontKeyboardShortcutsAvailableView' => 'AphrontView', 'AphrontListFilterView' => 'AphrontView', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontNullView' => 'AphrontView', 'AphrontPageView' => 'AphrontView', 'AphrontPagerView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontRedirectException' => 'AphrontException', 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontReloadResponse' => 'AphrontRedirectResponse', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView', 'AphrontTypeaheadTemplateView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_getcertificate_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_launched_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_log_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getalldiffs_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitpaths_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getdiff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getrevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getrevisionfeedback_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_markcommitted_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updaterevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updateunitresults_Method' => 'ConduitAPIMethod', 'ConduitAPI_diffusion_getcommits_Method' => 'ConduitAPIMethod', 'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'ConduitAPIMethod', 'ConduitAPI_feed_publish_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_download_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_info_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_maniphest_info_Method' => 'ConduitAPIMethod', 'ConduitAPI_paste_Method' => 'ConduitAPIMethod', 'ConduitAPI_paste_create_Method' => 'ConduitAPI_paste_Method', 'ConduitAPI_paste_info_Method' => 'ConduitAPI_paste_Method', 'ConduitAPI_path_getowners_Method' => 'ConduitAPIMethod', 'ConduitAPI_slowvote_info_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_whoami_Method' => 'ConduitAPIMethod', 'DarkConsoleConfigPlugin' => 'DarkConsolePlugin', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialApplyPatchFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialArcanistProjectFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialAuthorFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialAuxiliaryField' => 'DifferentialDAO', 'DifferentialBlameRevisionFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialCCsFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', 'DifferentialCommentMail' => 'DifferentialMail', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialCommitsFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDefaultFieldSelector' => 'DifferentialFieldSelector', 'DifferentialDependenciesFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialExceptionMail' => 'DifferentialMail', 'DifferentialExportPatchFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialGitSVNIDFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialHostFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialInlineComment' => 'DifferentialDAO', 'DifferentialInlineCommentEditController' => 'DifferentialController', 'DifferentialInlineCommentPreviewController' => 'DifferentialController', 'DifferentialInlineCommentView' => 'AphrontView', 'DifferentialLinesFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialLintFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialManiphestTasksFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialPathFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialPrimaryPaneView' => 'AphrontView', 'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler', 'DifferentialRevertPlanFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialReviewedByFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialReviewersFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionStatusFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialSubscribeController' => 'DifferentialController', 'DifferentialSummaryFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialTestPlanFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialTitleFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialUnitFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialViewTime' => 'DifferentialDAO', 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', 'DiffusionBrowseFileController' => 'DiffusionController', 'DiffusionBrowseTableView' => 'DiffusionView', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionCommitChangeTableView' => 'DiffusionView', 'DiffusionCommitController' => 'DiffusionController', 'DiffusionCommitListController' => 'DiffusionController', 'DiffusionController' => 'PhabricatorController', 'DiffusionDiffController' => 'DiffusionController', 'DiffusionGitBranchQuery' => 'DiffusionBranchQuery', 'DiffusionGitBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionGitDiffQuery' => 'DiffusionDiffQuery', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionGitHistoryQuery' => 'DiffusionHistoryQuery', 'DiffusionGitLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionGitRequest' => 'DiffusionRequest', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryTableView' => 'DiffusionView', 'DiffusionHomeController' => 'DiffusionController', 'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionPathCompleteController' => 'DiffusionController', 'DiffusionPathValidateController' => 'DiffusionController', 'DiffusionRepositoryController' => 'DiffusionController', 'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionSvnDiffQuery' => 'DiffusionDiffQuery', 'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionSvnHistoryQuery' => 'DiffusionHistoryQuery', 'DiffusionSvnLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionSvnRequest' => 'DiffusionRequest', 'DiffusionView' => 'AphrontView', 'HeraldAction' => 'HeraldDAO', 'HeraldApplyTranscript' => 'HeraldDAO', 'HeraldCommitAdapter' => 'HeraldObjectAdapter', 'HeraldCondition' => 'HeraldDAO', 'HeraldController' => 'PhabricatorController', 'HeraldDAO' => 'PhabricatorLiskDAO', 'HeraldDeleteController' => 'HeraldController', 'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter', 'HeraldDryRunAdapter' => 'HeraldObjectAdapter', 'HeraldHomeController' => 'HeraldController', 'HeraldNewController' => 'HeraldController', 'HeraldRule' => 'HeraldDAO', 'HeraldRuleController' => 'HeraldController', 'HeraldTestConsoleController' => 'HeraldController', 'HeraldTranscript' => 'HeraldDAO', 'HeraldTranscriptController' => 'HeraldController', 'HeraldTranscriptListController' => 'HeraldController', 'LiskIsolationTestCase' => 'PhabricatorTestCase', 'LiskIsolationTestDAO' => 'LiskDAO', 'ManiphestAuxiliaryFieldDefaultSpecification' => 'ManiphestAuxiliaryFieldSpecification', 'ManiphestController' => 'PhabricatorController', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions', 'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler', 'ManiphestTask' => 'ManiphestDAO', 'ManiphestTaskAuxiliaryStorage' => 'ManiphestDAO', 'ManiphestTaskDescriptionChangeController' => 'ManiphestController', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskOwner' => 'ManiphestConstants', 'ManiphestTaskPriority' => 'ManiphestConstants', 'ManiphestTaskProject' => 'ManiphestDAO', 'ManiphestTaskStatus' => 'ManiphestConstants', 'ManiphestTaskSubscriber' => 'ManiphestDAO', 'ManiphestTaskSummaryView' => 'ManiphestView', 'ManiphestTransaction' => 'ManiphestDAO', 'ManiphestTransactionDetailView' => 'ManiphestView', 'ManiphestTransactionListView' => 'ManiphestView', 'ManiphestTransactionPreviewController' => 'ManiphestController', 'ManiphestTransactionSaveController' => 'ManiphestController', 'ManiphestTransactionType' => 'ManiphestConstants', 'ManiphestView' => 'AphrontView', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorCalendarBrowseController' => 'PhabricatorCalendarController', 'PhabricatorCalendarController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 'PhabricatorController' => 'AphrontController', 'PhabricatorCountdownController' => 'PhabricatorController', 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController', 'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonController' => 'PhabricatorController', 'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO', 'PhabricatorDaemonLog' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogEventsView' => 'AphrontView', 'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController', 'PhabricatorDaemonLogListView' => 'AphrontView', 'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineEventController' => 'PhabricatorDaemonController', 'PhabricatorDefaultFileStorageEngineSelector' => 'PhabricatorFileStorageEngineSelector', 'PhabricatorDefaultSearchEngineSelector' => 'PhabricatorSearchEngineSelector', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorDisabledUserController' => 'PhabricatorAuthController', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', 'PhabricatorFeedController' => 'PhabricatorController', 'PhabricatorFeedDAO' => 'PhabricatorLiskDAO', 'PhabricatorFeedPublicStreamController' => 'PhabricatorFeedController', 'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryStatus' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryTypeConstants' => 'PhabricatorFeedConstants', 'PhabricatorFeedStoryUnknown' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryView' => 'PhabricatorFeedView', 'PhabricatorFeedStreamController' => 'PhabricatorFeedController', 'PhabricatorFeedView' => 'AphrontView', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileAltViewController' => 'PhabricatorFileController', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 'PhabricatorFileImageMacro' => 'PhabricatorFileDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileMacroDeleteController' => 'PhabricatorFileController', 'PhabricatorFileMacroEditController' => 'PhabricatorFileController', 'PhabricatorFileMacroListController' => 'PhabricatorFileController', 'PhabricatorFileProxyController' => 'PhabricatorFileController', 'PhabricatorFileProxyImage' => 'PhabricatorFileDAO', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileTransformController' => 'PhabricatorFileController', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon', 'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker', 'PhabricatorHelpController' => 'PhabricatorController', 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', 'PhabricatorIRCBot' => 'PhabricatorDaemon', 'PhabricatorIRCObjectNameHandler' => 'PhabricatorIRCHandler', 'PhabricatorIRCProtocolHandler' => 'PhabricatorIRCHandler', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorLintEngine' => 'PhutilLintEngine', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTADaemon' => 'PhabricatorDaemon', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAReceiveController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAReceivedListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController', 'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorOAuthFailureView' => 'AphrontView', 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController', 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorObjectGraph' => 'AbstractDirectedGraph', 'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants', 'PhabricatorOwnersController' => 'PhabricatorController', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', 'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', 'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPackage' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDListController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPaste' => 'PhabricatorPasteDAO', 'PhabricatorPasteController' => 'PhabricatorController', 'PhabricatorPasteCreateController' => 'PhabricatorPasteController', 'PhabricatorPasteDAO' => 'PhabricatorLiskDAO', 'PhabricatorPasteListController' => 'PhabricatorPasteController', 'PhabricatorPasteViewController' => 'PhabricatorPasteController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorProfileView' => 'AphrontView', 'PhabricatorProject' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController', 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectCreateController' => 'PhabricatorProjectController', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileEditController' => 'PhabricatorProjectController', 'PhabricatorProjectSubproject' => 'PhabricatorProjectDAO', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName', 'PhabricatorRemarkupRuleDifferentialHandle' => 'PhabricatorRemarkupRuleObjectHandle', 'PhabricatorRemarkupRuleDiffusion' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleEmbedFile' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleManiphest' => 'PhabricatorRemarkupRuleObjectName', 'PhabricatorRemarkupRuleManiphestHandle' => 'PhabricatorRemarkupRuleObjectHandle', 'PhabricatorRemarkupRuleMention' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleObjectHandle' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleObjectName' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRulePaste' => 'PhabricatorRemarkupRuleObjectName', 'PhabricatorRemarkupRulePhriction' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleProxyImage' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleYoutube' => 'PhutilRemarkupRule', 'PhabricatorRepository' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryArcanistProject' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitDiscoveryDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryCommitTaskDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryController' => 'PhabricatorController', 'PhabricatorRepositoryCreateController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryDaemon' => 'PhabricatorDaemon', 'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'PhabricatorRepositoryCommitMessageDetailParser', 'PhabricatorRepositoryDeleteController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryEditController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryGitFetchDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorSearchAttachController' => 'PhabricatorSearchController', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchCommitIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchEngineMySQL' => 'PhabricatorSearchEngine', 'PhabricatorSearchIndexController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchPhrictionIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchQuery' => 'PhabricatorSearchDAO', 'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchSelectController' => 'PhabricatorSearchController', 'PhabricatorSearchUserIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteComment' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteController' => 'PhabricatorController', 'PhabricatorSlowvoteCreateController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO', 'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvotePoll' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorStatusController' => 'PhabricatorController', 'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon', 'PhabricatorTestCase' => 'ArcanistPhutilTestCase', 'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO', 'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO', 'PhabricatorTimer' => 'PhabricatorCountdownDAO', 'PhabricatorTransformedFile' => 'PhabricatorFileDAO', 'PhabricatorTrivialTestCase' => 'PhabricatorTestCase', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUIExampleController' => 'PhabricatorController', 'PhabricatorUIExampleRenderController' => 'PhabricatorUIExampleController', 'PhabricatorUIListFilterExample' => 'PhabricatorUIExample', 'PhabricatorUIPagerExample' => 'PhabricatorUIExample', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserLog' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserProfileSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserSSHKey' => 'PhabricatorUserDAO', 'PhabricatorUserSSHKeysSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserSettingsController' => 'PhabricatorPeopleController', 'PhabricatorUserSettingsPanelController' => 'PhabricatorPeopleController', 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', 'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController', 'PhabricatorXHPASTViewController' => 'PhabricatorController', 'PhabricatorXHPASTViewDAO' => 'PhabricatorLiskDAO', 'PhabricatorXHPASTViewFrameController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewFramesetController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewInputController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHPASTViewPanelController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewParseTree' => 'PhabricatorXHPASTViewDAO', 'PhabricatorXHPASTViewRunController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHProfController' => 'PhabricatorController', 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', 'PhabricatorXHProfProfileSymbolView' => 'AphrontView', 'PhabricatorXHProfProfileTopLevelView' => 'AphrontView', 'PhrictionActionConstants' => 'PhrictionConstants', 'PhrictionContent' => 'PhrictionDAO', 'PhrictionController' => 'PhabricatorController', 'PhrictionDAO' => 'PhabricatorLiskDAO', 'PhrictionDiffController' => 'PhrictionController', 'PhrictionDocument' => 'PhrictionDAO', 'PhrictionDocumentController' => 'PhrictionController', 'PhrictionDocumentPreviewController' => 'PhrictionController', 'PhrictionDocumentTestCase' => 'PhabricatorTestCase', 'PhrictionEditController' => 'PhrictionController', 'PhrictionHistoryController' => 'PhrictionController', 'PhrictionListController' => 'PhrictionController', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/writeguard/AphrontWriteGuard.php b/src/aphront/writeguard/AphrontWriteGuard.php new file mode 100644 index 0000000000..b192f77270 --- /dev/null +++ b/src/aphront/writeguard/AphrontWriteGuard.php @@ -0,0 +1,256 @@ +dispose(); + * + * Normally, you do not need to manage guards yourself -- the Aphront stack + * handles it for you. + * + * @param AphrontRequest Request to read CSRF token information from. + * @return this + * @task manage + */ + public function __construct(AphrontRequest $request) { + if (self::$instance) { + throw new Exception( + "An AphrontWriteGuard already exists. Dispose of the previous guard ". + "before creating a new one."); + } + if (self::$allowUnguardedWrites) { + throw new Exception( + "An AphrontWriteGuard is being created in a context which permits ". + "unguarded writes unconditionally. This is not allowed and indicates ". + "a serious error."); + } + $this->request = $request; + self::$instance = $this; + } + + + /** + * Dispose of the active write guard. You must call this method when you are + * done with a write guard. You do not normally need to call this yourself. + * + * @return void + * @task manage + */ + public function dispose() { + if ($this->allowDepth > 0) { + throw new Exception( + "Imbalanced AphrontWriteGuard: more beginUnguardedWrites() calls than ". + "endUnguardedWrites() calls."); + } + self::$instance = null; + } + + +/* -( Protecting Writes )-------------------------------------------------- */ + + + /** + * Declare intention to perform a write, validating that writes are allowed. + * You should call this method before executing a write whenever you implement + * a new storage engine where information can be permanently kept. + * + * Writes are permitted if: + * + * - The request has valid CSRF tokens. + * - Unguarded writes have been temporarily enabled by a call to + * @{method:beginUnguardedWrites}. + * - All write guarding has been disabled with + * @{method:allowDangerousUnguardedWrites}. + * + * If none of these conditions are true, this method will throw and prevent + * the write. + * + * @return void + * @task protect + */ + public static function willWrite() { + if (!self::$instance) { + if (!self::$allowUnguardedWrites) { + throw new Exception( + "Unguarded write! There must be an active AphrontWriteGuard to ". + "perform writes."); + } else { + // Unguarded writes are being allowed unconditionally. + return; + } + } + + $instance = self::$instance; + + if ($instance->allowDepth == 0) { + $instance->request->validateCSRF(); + } + } + + +/* -( Disabling Write Protection )----------------------------------------- */ + + + /** + * Enter a scope which permits unguarded writes. This works like + * @{method:beginUnguardedWrites} but returns an object which will end + * the unguarded write scope when its __destruct() method is called. This + * is useful to more easily handle exceptions correctly in unguarded write + * blocks: + * + * // Restores the guard even if do_logging() throws. + * function unguarded_scope() { + * $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + * do_logging(); + * } + * + * @return AphrontScopedUnguardedWriteCapability Object which ends unguarded + * writes when it leaves scope. + * @task disable + */ + public static function beginScopedUnguardedWrites() { + self::beginUnguardedWrites(); + return new AphrontScopedUnguardedWriteCapability(); + } + + + /** + * Begin a block which permits unguarded writes. You should use this very + * sparingly, and only for things like logging where CSRF is not a concern. + * + * You must pair every call to @{method:beginUnguardedWrites} with a call to + * @{method:endUnguardedWrites}: + * + * AphrontWriteGuard::beginUnguardedWrites(); + * do_logging(); + * AphrontWriteGuard::endUnguardedWrites(); + * + * @return void + * @task disable + */ + public static function beginUnguardedWrites() { + if (!self::$instance) { + return; + } + self::$instance->allowDepth++; + } + + /** + * Declare that you have finished performing unguarded writes. You must + * call this exactly once for each call to @{method:beginUnguardedWrites}. + * + * @return void + * @task disable + */ + public static function endUnguardedWrites() { + if (!self::$instance) { + return; + } + if (self::$instance->allowDepth <= 0) { + throw new Exception( + "Imbalanced AphrontWriteGuard: more endUnguardedWrites() calls than ". + "beginUnguardedWrites() calls."); + } + self::$instance->allowDepth--; + } + + + /** + * Allow execution of unguarded writes. This is ONLY appropriate for use in + * script contexts or other contexts where you are guaranteed to never be + * vulnerable to CSRF concerns. Calling this method is EXTREMELY DANGEROUS + * if you do not understand the consequences. + * + * If you need to perform unguarded writes on an otherwise guarded workflow + * which is vulnerable to CSRF, use @{method:beginUnguardedWrites}. + * + * @return void + * @task disable + */ + public static function allowDangerousUnguardedWrites($allow) { + if (self::$instance) { + throw new Exception( + "You can not unconditionally disable AphrontWriteGuard by calling ". + "allowDangerousUnguardedWrites() while a write guard is active. Use ". + "beginUnguardedWrites() to temporarily allow unguarded writes."); + } + self::$allowUnguardedWrites = true; + } + + +/* -( Internals )---------------------------------------------------------- */ + + + /** + * When the object is destroyed, make sure @{method:dispose} was called. + */ + public function __destruct() { + if (isset(self::$instance)) { + throw new Exception( + "AphrontWriteGuard was not properly disposed of! Call dispose() on ". + "every AphrontWriteGuard object you instantiate."); + } + } +} diff --git a/src/aphront/writeguard/__init__.php b/src/aphront/writeguard/__init__.php new file mode 100644 index 0000000000..e90cf76d3e --- /dev/null +++ b/src/aphront/writeguard/__init__.php @@ -0,0 +1,12 @@ +provider = PhabricatorOAuthProvider::newProvider($data['provider']); } public function processRequest() { $current_user = $this->getRequest()->getUser(); $provider = $this->provider; if (!$provider->isProviderEnabled()) { return new Aphront400Response(); } $provider_name = $provider->getProviderName(); $provider_key = $provider->getProviderKey(); $request = $this->getRequest(); if ($request->getStr('error')) { $error_view = id(new PhabricatorOAuthFailureView()) ->setRequest($request); return $this->buildErrorResponse($error_view); } $error_response = $this->retrieveAccessToken($provider); if ($error_response) { return $error_response; } $userinfo_uri = new PhutilURI($provider->getUserInfoURI()); $userinfo_uri->setQueryParams( array( 'access_token' => $this->accessToken, )); $user_json = @file_get_contents($userinfo_uri); $user_data = json_decode($user_json, true); $provider->setUserData($user_data); $provider->setAccessToken($this->accessToken); $user_id = $provider->retrieveUserID(); $provider_key = $provider->getProviderKey(); $oauth_info = $this->retrieveOAuthInfo($provider); if ($current_user->getPHID()) { if ($oauth_info->getID()) { if ($oauth_info->getUserID() != $current_user->getID()) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Already Linked to Another Account'); $dialog->appendChild( '

The '.$provider_name.' account you just authorized '. 'is already linked to another Phabricator account. Before you can '. 'associate your '.$provider_name.' account with this Phabriactor '. 'account, you must unlink it from the Phabricator account it is '. 'currently linked to.

'); $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); return id(new AphrontDialogResponse())->setDialog($dialog); } else { return id(new AphrontRedirectResponse()) ->setURI('/settings/page/'.$provider_key.'/'); } } $existing_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'userID = %d AND oauthProvider = %s', $current_user->getID(), $provider_key); if ($existing_oauth) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Already Linked to an Account From This Provider'); $dialog->appendChild( '

The account you are logged in with is already linked to a '. $provider_name.' account. Before you can link it to a different '. $provider_name.' account, you must unlink the old account.

'); $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); return id(new AphrontDialogResponse())->setDialog($dialog); } if (!$request->isDialogFormPost()) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Link '.$provider_name.' Account'); $dialog->appendChild( '

Link your '.$provider_name.' account to your Phabricator '. 'account?

'); $dialog->addHiddenInput('token', $provider->getAccessToken()); $dialog->addHiddenInput('expires', $oauth_info->getTokenExpires()); $dialog->addHiddenInput('state', $this->oauthState); $dialog->addSubmitButton('Link Accounts'); $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); return id(new AphrontDialogResponse())->setDialog($dialog); } $oauth_info->setUserID($current_user->getID()); - $oauth_info->save(); + + $this->saveOAuthInfo($oauth_info); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/'.$provider_key.'/'); } $next_uri = $request->getCookie('next_uri', '/'); // Login with known auth. if ($oauth_info->getID()) { $known_user = id(new PhabricatorUser())->load($oauth_info->getUserID()); $request->getApplicationConfiguration()->willAuthenticateUserWithOAuth( $known_user, $oauth_info, $provider); $session_key = $known_user->establishSession('web'); - $oauth_info->save(); + $this->saveOAuthInfo($oauth_info); $request->setCookie('phusr', $known_user->getUsername()); $request->setCookie('phsid', $session_key); $request->clearCookie('next_uri'); return id(new AphrontRedirectResponse()) ->setURI($next_uri); } $oauth_email = $provider->retrieveUserEmail(); if ($oauth_email) { $known_email = id(new PhabricatorUser()) ->loadOneWhere('email = %s', $oauth_email); if ($known_email) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Already Linked to Another Account'); $dialog->appendChild( '

The '.$provider_name.' account you just authorized has an '. 'email address which is already in use by another Phabricator '. 'account. To link the accounts, log in to your Phabricator '. 'account and then go to Settings.

'); $dialog->addCancelButton('/login/'); return id(new AphrontDialogResponse())->setDialog($dialog); } } if (!$provider->isProviderRegistrationEnabled()) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('No Account Registration With '.$provider_name); $dialog->appendChild( '

You can not register a new account using '.$provider_name.'; '. 'you can only use your '.$provider_name.' account to log into an '. 'existing Phabricator account which you have registered through '. 'other means.

'); $dialog->addCancelButton('/login/'); return id(new AphrontDialogResponse())->setDialog($dialog); } $class = PhabricatorEnv::getEnvConfig('controller.oauth-registration'); PhutilSymbolLoader::loadClass($class); $controller = newv($class, array($this->getRequest())); $controller->setOAuthProvider($provider); $controller->setOAuthInfo($oauth_info); $controller->setOAuthState($this->oauthState); return $this->delegateToController($controller); } private function buildErrorResponse(PhabricatorOAuthFailureView $view) { $provider = $this->provider; $provider_name = $provider->getProviderName(); $view->setOAuthProvider($provider); return $this->buildStandardPageResponse( $view, array( 'title' => $provider_name.' Auth Failed', )); } private function retrieveAccessToken(PhabricatorOAuthProvider $provider) { $request = $this->getRequest(); $token = $request->getStr('token'); if ($token) { $this->tokenExpires = $request->getInt('expires'); $this->accessToken = $token; $this->oauthState = $request->getStr('state'); return null; } $client_id = $provider->getClientID(); $client_secret = $provider->getClientSecret(); $redirect_uri = $provider->getRedirectURI(); $auth_uri = $provider->getTokenURI(); $code = $request->getStr('code'); $query_data = array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'code' => $code, ); $post_data = http_build_query($query_data); $post_length = strlen($post_data); $stream_context = stream_context_create( array( 'http' => array( 'method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded\r\n". "Content-Length: {$post_length}\r\n", 'content' => $post_data, ), )); $stream = fopen($auth_uri, 'r', false, $stream_context); $response = false; $meta = null; if ($stream) { $meta = stream_get_meta_data($stream); $response = stream_get_contents($stream); fclose($stream); } if ($response === false) { return $this->buildErrorResponse(new PhabricatorOAuthFailureView()); } $data = array(); parse_str($response, $data); $token = idx($data, 'access_token'); if (!$token) { return $this->buildErrorResponse(new PhabricatorOAuthFailureView()); } if (idx($data, 'expires')) { $this->tokenExpires = time() + $data['expires']; } $this->accessToken = $token; $this->oauthState = $request->getStr('state'); return null; } private function retrieveOAuthInfo(PhabricatorOAuthProvider $provider) { $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'oauthProvider = %s and oauthUID = %s', $provider->getProviderKey(), $provider->retrieveUserID()); if (!$oauth_info) { $oauth_info = new PhabricatorUserOAuthInfo(); $oauth_info->setOAuthProvider($provider->getProviderKey()); $oauth_info->setOAuthUID($provider->retrieveUserID()); } $oauth_info->setAccountURI($provider->retrieveUserAccountURI()); $oauth_info->setAccountName($provider->retrieveUserAccountName()); $oauth_info->setToken($provider->getAccessToken()); $oauth_info->setTokenStatus(PhabricatorUserOAuthInfo::TOKEN_STATUS_GOOD); // If we have out-of-date expiration info, just clear it out. Then replace // it with good info if the provider gave it to us. $expires = $oauth_info->getTokenExpires(); if ($expires <= time()) { $expires = null; } if ($this->tokenExpires) { $expires = $this->tokenExpires; } $oauth_info->setTokenExpires($expires); return $oauth_info; } + private function saveOAuthInfo(PhabricatorUserOAuthInfo $info) { + // UNGUARDED WRITES: Logging-in users don't have their CSRF set up yet. + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $info->save(); + } + + + } diff --git a/src/applications/auth/controller/oauth/__init__.php b/src/applications/auth/controller/oauth/__init__.php index b3ee8dda26..034e422097 100644 --- a/src/applications/auth/controller/oauth/__init__.php +++ b/src/applications/auth/controller/oauth/__init__.php @@ -1,25 +1,26 @@ method = $data['method']; return $this; } public function processRequest() { $time_start = microtime(true); $request = $this->getRequest(); $method = $this->method; $method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method); $api_request = null; $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); $metadata = array(); try { if (!class_exists($method_class)) { throw new Exception( "Unable to load the implementation class for method '{$method}'. ". "You may have misspelled the method, need to define ". "'{$method_class}', or need to run 'arc build'."); } $class_info = new ReflectionClass($method_class); if ($class_info->isAbstract()) { throw new Exception( "Method '{$method}' is not valid; the implementation is an abstract ". "base class."); } $method_handler = newv($method_class, array()); if (isset($_REQUEST['params']) && is_array($_REQUEST['params'])) { $params_post = $request->getArr('params'); foreach ($params_post as $key => $value) { $params_post[$key] = json_decode($value, true); } $params = $params_post; } else { $params_json = $request->getStr('params'); if (!strlen($params_json)) { $params = array(); } else { $params = json_decode($params_json, true); if (!is_array($params)) { throw new Exception( "Invalid parameter information was passed to method ". "'{$method}', could not decode JSON serialization."); } } } $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); $result = null; $api_request = new ConduitAPIRequest($params); + $allow_unguarded_writes = false; $auth_error = null; if ($method_handler->shouldRequireAuthentication()) { $auth_error = $this->authenticateUser($api_request, $metadata); + // If we've explicitly authenticated the user here and either done + // CSRF validation or are using a non-web authentication mechanism. + $allow_unguarded_writes = true; + } + + if ($method_handler->shouldAllowUnguardedWrites()) { + $allow_unguarded_writes = true; } if ($auth_error === null) { + if ($allow_unguarded_writes) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + } try { $result = $method_handler->executeMethod($api_request); $error_code = null; $error_info = null; } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); $error_info = $method_handler->getErrorDescription($error_code); } + if ($allow_unguarded_writes) { + unset($unguarded); + } } else { list($error_code, $error_info) = $auth_error; } } catch (Exception $ex) { $result = null; $error_code = 'ERR-CONDUIT-CORE'; $error_info = $ex->getMessage(); } $time_end = microtime(true); $connection_id = null; if (idx($metadata, 'connectionID')) { $connection_id = $metadata['connectionID']; } else if (($method == 'conduit.connect') && $result) { $connection_id = idx($result, 'connectionID'); } $log->setConnectionID($connection_id); $log->setError((string)$error_code); $log->setDuration(1000000 * ($time_end - $time_start)); // TODO: This is a hack, but the insert is comparatively expensive and // we only really care about having these logs for real CLI clients, if // even that. if (empty($metadata['authToken'])) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $log->save(); + unset($unguarded); } $result = array( 'result' => $result, 'error_code' => $error_code, 'error_info' => $error_info, ); switch ($request->getStr('output')) { case 'human': return $this->buildHumanReadableResponse( $method, $api_request, $result); case 'json': default: return id(new AphrontFileResponse()) ->setMimeType('application/json') ->setContent('for(;;);'.json_encode($result)); } } /** * Authenticate the client making the request to a Phabricator user account. * * @param ConduitAPIRequest Request being executed. * @param dict Request metadata. * @return null|pair Null to indicate successful authentication, or * an error code and error message pair. */ private function authenticateUser( ConduitAPIRequest $api_request, array $metadata) { $request = $this->getRequest(); if ($request->getUser()->getPHID()) { + $request->validateCSRF(); $api_request->setUser($request->getUser()); return null; } // Handle sessionless auth. TOOD: This is super messy. if (isset($metadata['authUser'])) { $user = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $metadata['authUser']); if (!$user) { return array( 'ERR-INVALID-AUTH', 'Authentication is invalid.', ); } $token = idx($metadata, 'authToken'); $signature = idx($metadata, 'authSignature'); $certificate = $user->getConduitCertificate(); if (sha1($token.$certificate) !== $signature) { return array( 'ERR-INVALID-AUTH', 'Authentication is invalid.', ); } $api_request->setUser($user); return null; } $session_key = idx($metadata, 'sessionKey'); if (!$session_key) { return array( 'ERR-INVALID-SESSION', 'Session key is not present.' ); } $session = queryfx_one( id(new PhabricatorUser())->establishConnection('r'), 'SELECT * FROM %T WHERE sessionKey = %s', PhabricatorUser::SESSION_TABLE, $session_key); if (!$session) { return array( 'ERR-INVALID-SESSION', 'Session key is invalid.', ); } // TODO: Make sessions timeout. // TODO: When we pull a session, read connectionID from the session table. $user = id(new PhabricatorUser())->loadOneWhere( 'phid = %s', $session['userPHID']); if (!$user) { return array( 'ERR-INVALID-SESSION', 'Session is for nonexistent user.', ); } $api_request->setUser($user); return null; } private function buildHumanReadableResponse( $method, ConduitAPIRequest $request = null, $result = null) { $param_rows = array(); $param_rows[] = array('Method', $this->renderAPIValue($method)); if ($request) { foreach ($request->getAllParameters() as $key => $value) { $param_rows[] = array( phutil_escape_html($key), $this->renderAPIValue($value), ); } } $param_table = new AphrontTableView($param_rows); $param_table->setColumnClasses( array( 'header', 'wide', )); $result_rows = array(); foreach ($result as $key => $value) { $result_rows[] = array( phutil_escape_html($key), $this->renderAPIValue($value), ); } $result_table = new AphrontTableView($result_rows); $result_table->setColumnClasses( array( 'header', 'wide', )); $param_panel = new AphrontPanelView(); $param_panel->setHeader('Method Parameters'); $param_panel->appendChild($param_table); $result_panel = new AphrontPanelView(); $result_panel->setHeader('Method Result'); $result_panel->appendChild($result_table); return $this->buildStandardPageResponse( array( $param_panel, $result_panel, ), array( 'title' => 'Method Call Result', )); } private function renderAPIValue($value) { $json = new PhutilJSON(); if (is_array($value)) { $value = $json->encodeFormatted($value); $value = phutil_escape_html($value); } else { $value = phutil_escape_html($value); } $value = '
'.$value.'
'; return $value; } } diff --git a/src/applications/conduit/controller/api/__init__.php b/src/applications/conduit/controller/api/__init__.php index efb0757303..706e380ddf 100644 --- a/src/applications/conduit/controller/api/__init__.php +++ b/src/applications/conduit/controller/api/__init__.php @@ -1,24 +1,25 @@ defineErrorTypes(), $error_code, 'Unknown Error'); } public function executeMethod(ConduitAPIRequest $request) { return $this->execute($request); } public function getAPIMethodName() { return self::getAPIMethodNameFromClassName(get_class($this)); } public static function getClassNameFromAPIMethodName($method_name) { $method_fragment = str_replace('.', '_', $method_name); return 'ConduitAPI_'.$method_fragment.'_Method'; } public function shouldRequireAuthentication() { return true; } + public function shouldAllowUnguardedWrites() { + return false; + } + public static function getAPIMethodNameFromClassName($class_name) { $match = null; $is_valid = preg_match( '/^ConduitAPI_(.*)_Method$/', $class_name, $match); if (!$is_valid) { throw new Exception( "Parameter '{$class_name}' is not a valid Conduit API method class."); } $method_fragment = $match[1]; return str_replace('_', '.', $method_fragment); } protected function validateHost($host) { if (!$host) { // If the client doesn't send a host key, don't complain. We should in // the future, but this change isn't severe enough to bump the protocol // version. // TODO: Remove this once the protocol version gets bumped past 2 (i.e., // require the host key be present and valid). return; } $host = new PhutilURI($host); $host->setPath('/'); $host = (string)$host; $self = PhabricatorEnv::getProductionURI('/'); if ($self !== $host) { throw new Exception( "Your client is connecting to this install as '{$host}', but it is ". "configured as '{$self}'. The client and server must use the exact ". "same URI to identify the install. Edit your .arcconfig or ". "phabricator/conf so they agree on the URI for the install."); } } } diff --git a/src/applications/conduit/method/conduit/connect/ConduitAPI_conduit_connect_Method.php b/src/applications/conduit/method/conduit/connect/ConduitAPI_conduit_connect_Method.php index 6e3e04d813..f9cf7c6be3 100644 --- a/src/applications/conduit/method/conduit/connect/ConduitAPI_conduit_connect_Method.php +++ b/src/applications/conduit/method/conduit/connect/ConduitAPI_conduit_connect_Method.php @@ -1,137 +1,141 @@ 'required string', 'clientVersion' => 'required int', 'clientDescription' => 'optional string', 'user' => 'optional string', 'authToken' => 'optional int', 'authSignature' => 'optional string', 'host' => 'required string', ); } public function defineReturnType() { return 'dict'; } public function defineErrorTypes() { return array( "ERR-BAD-VERSION" => "Client/server version mismatch. Update your client.", "ERR-UNKNOWN-CLIENT" => "Client is unknown.", "ERR-UPDATE-ARC" => "Arcanist is now open source! Update your scripts/aliases to use ". "'/home/engshare/devtools/arcanist/bin/arc' if you're running from ". "a Facebook host, or see ". " for ". "laptop instructions.", "ERR-INVALID-USER" => "The username you are attempting to authenticate with is not valid.", "ERR-INVALID-CERTIFICATE" => "Your authentication certificate for this server is invalid.", "ERR-INVALID-TOKEN" => "The challenge token you are authenticating with is outside of the ". "allowed time range. Either your system clock is out of whack or ". "you're executing a replay attack.", "ERR-NO-CERTIFICATE" => "This server requires authentication.", ); } protected function execute(ConduitAPIRequest $request) { $this->validateHost($request->getValue('host')); $client = $request->getValue('client'); $client_version = (int)$request->getValue('clientVersion'); $client_description = (string)$request->getValue('clientDescription'); $username = (string)$request->getValue('user'); // Log the connection, regardless of the outcome of checks below. $connection = new PhabricatorConduitConnectionLog(); $connection->setClient($client); $connection->setClientVersion($client_version); $connection->setClientDescription($client_description); $connection->setUsername($username); $connection->save(); switch ($client) { case 'arc': $server_version = 2; switch ($client_version) { case 1: throw new ConduitException('ERR-UPDATE-ARC'); case $server_version: break; default: throw new ConduitException('ERR-BAD-VERSION'); } break; default: // Allow new clients by default. break; } $token = $request->getValue('authToken'); $signature = $request->getValue('authSignature'); $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $username); if (!$user) { throw new ConduitException('ERR-INVALID-USER'); } $session_key = null; if ($token && $signature) { if (abs($token - time()) > 60 * 15) { throw new ConduitException('ERR-INVALID-TOKEN'); } $valid = sha1($token.$user->getConduitCertificate()); if ($valid != $signature) { throw new ConduitException('ERR-INVALID-CERTIFICATE'); } $session_key = $user->establishSession('conduit'); } else { throw new ConduitException('ERR-NO-CERTIFICATE'); } return array( 'connectionID' => $connection->getID(), 'sessionKey' => $session_key, 'userPHID' => $user->getPHID(), ); } } diff --git a/src/applications/conduit/method/daemon/launched/ConduitAPI_daemon_launched_Method.php b/src/applications/conduit/method/daemon/launched/ConduitAPI_daemon_launched_Method.php index 6c7b3e02e2..40de95cf49 100644 --- a/src/applications/conduit/method/daemon/launched/ConduitAPI_daemon_launched_Method.php +++ b/src/applications/conduit/method/daemon/launched/ConduitAPI_daemon_launched_Method.php @@ -1,64 +1,68 @@ 'required string', 'host' => 'required string', 'pid' => 'required int', 'argv' => 'required string', ); } public function defineReturnType() { return 'string'; } public function defineErrorTypes() { return array( ); } protected function execute(ConduitAPIRequest $request) { $daemon_log = new PhabricatorDaemonLog(); $daemon_log->setDaemon($request->getValue('daemon')); $daemon_log->setHost($request->getValue('host')); $daemon_log->setPID($request->getValue('pid')); $daemon_log->setArgv(json_decode($request->getValue('argv'))); $daemon_log->save(); return $daemon_log->getID(); } } diff --git a/src/applications/conduit/method/daemon/log/ConduitAPI_daemon_log_Method.php b/src/applications/conduit/method/daemon/log/ConduitAPI_daemon_log_Method.php index f7ba004d09..c89469d77c 100644 --- a/src/applications/conduit/method/daemon/log/ConduitAPI_daemon_log_Method.php +++ b/src/applications/conduit/method/daemon/log/ConduitAPI_daemon_log_Method.php @@ -1,63 +1,67 @@ 'required int', 'type' => 'required string', 'message' => 'optional string', ); } public function defineReturnType() { return 'void'; } public function defineErrorTypes() { return array( ); } protected function execute(ConduitAPIRequest $request) { $daemon_event = new PhabricatorDaemonLogEvent(); $daemon_event->setLogID($request->getValue('daemonLogID')); $daemon_event->setLogType($request->getValue('type')); $daemon_event->setMessage((string)$request->getValue('message')); $daemon_event->setEpoch(time()); $daemon_event->save(); return; } } diff --git a/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php b/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php index 5e90dcaf79..0b40eb8eca 100644 --- a/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php +++ b/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php @@ -1,219 +1,222 @@ inlineComment = $comment; return $this; } public function setOnRight($on_right) { $this->onRight = $on_right; return $this; } public function setBuildScaffolding($scaffold) { $this->buildScaffolding = $scaffold; return $this; } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function setMarkupEngine(PhutilMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function setEditable($editable) { $this->editable = $editable; return $this; } public function setPreview($preview) { $this->preview = $preview; } public function render() { $inline = $this->inlineComment; $start = $inline->getLineNumber(); $length = $inline->getLineLength(); if ($length) { $end = $start + $length; $line = 'Lines '.number_format($start).'-'.number_format($end); } else { $line = 'Line '.number_format($start); } $metadata = array( 'id' => $inline->getID(), 'number' => $inline->getLineNumber(), 'length' => $inline->getLineLength(), 'on_right' => $this->onRight, 'original' => $inline->getContent(), ); $sigil = 'differential-inline-comment'; $content = $inline->getContent(); $handles = $this->handles; $links = array(); $is_draft = false; if (!$inline->getCommentID()) { $links[] = 'Not Submitted Yet'; $is_draft = true; } if (!$this->preview) { $links[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-inline-prev', ), 'Previous'); $links[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-inline-next', ), 'Next'); $links[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-inline-reply', ), 'Reply'); } if ($this->editable && !$this->preview) { $links[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-inline-edit', ), 'Edit'); $links[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-inline-delete', ), 'Delete'); } if ($links) { $links = ''. implode(' · ', $links). ''; } else { $links = null; } $cache = $inline->getCache(); if (strlen($cache)) { $content = $cache; } else { $content = $this->markupEngine->markupText($content); if ($inline->getID()) { $inline->setCache($content); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $inline->save(); + unset($unguarded); } } $anchor = phutil_render_tag( 'a', array( 'name' => 'inline-'.$inline->getID(), ), ''); $classes = array( 'differential-inline-comment', ); if ($is_draft) { $classes[] = 'differential-inline-comment-unsaved-draft'; } $classes = implode(' ', $classes); $markup = javelin_render_tag( 'div', array( 'class' => $classes, 'sigil' => $sigil, 'meta' => $metadata, ), '
'. $anchor. $links. ''.$line.''. phutil_escape_html($handles[$inline->getAuthorPHID()]->getName()). '
'. '
'. $content. '
'); return $this->scaffoldMarkup($markup); } private function scaffoldMarkup($markup) { if (!$this->buildScaffolding) { return $markup; } $left_markup = !$this->onRight ? $markup : ''; $right_markup = $this->onRight ? $markup : ''; return ''. ''. ''. ''. ''. ''. ''. '
'.$left_markup.''.$right_markup.'
'; } } diff --git a/src/applications/differential/view/inlinecomment/__init__.php b/src/applications/differential/view/inlinecomment/__init__.php index 71452b2ed7..aee6d7ccc2 100644 --- a/src/applications/differential/view/inlinecomment/__init__.php +++ b/src/applications/differential/view/inlinecomment/__init__.php @@ -1,15 +1,16 @@ comment = $comment; return $this; } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function setMarkupEngine($markup_engine) { $this->markupEngine = $markup_engine; return $this; } public function setPreview($preview) { $this->preview = $preview; return $this; } public function setInlineComments(array $inline_comments) { $this->inlines = $inline_comments; return $this; } public function setChangesets(array $changesets) { // Ship these in sorted by getSortKey() and keyed by ID... or else! $this->changesets = $changesets; return $this; } public function setTargetDiff($target) { $this->target = $target; } public function setCommentNumber($comment_number) { $this->commentNumber = $comment_number; return $this; } public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } public function render() { if (!$this->user) { throw new Exception("Call setUser() before rendering!"); } require_celerity_resource('phabricator-remarkup-css'); require_celerity_resource('differential-revision-comment-css'); $comment = $this->comment; $action = $comment->getAction(); $action_class = 'differential-comment-action-'.phutil_escape_html($action); if ($this->preview) { $date = 'COMMENT PREVIEW'; } else { $date = phabricator_datetime($comment->getDateCreated(), $this->user); } $info = array($date); $comment_anchor = null; $num = $this->commentNumber; if ($num && !$this->preview) { Javelin::initBehavior('phabricator-watch-anchor'); $info[] = phutil_render_tag( 'a', array( 'name' => 'comment-'.$num, 'href' => '#comment-'.$num, ), 'Comment D'.$comment->getRevisionID().'#'.$num); $comment_anchor = 'anchor-comment-'.$num; } $info = implode(' · ', $info); $author = $this->handles[$comment->getAuthorPHID()]; $author_link = $author->renderLink(); $verb = DifferentialAction::getActionPastTenseVerb($comment->getAction()); $verb = phutil_escape_html($verb); $content = $comment->getContent(); $head_content = null; if (strlen(rtrim($content))) { $title = "{$author_link} {$verb} this revision:"; $cache = $comment->getCache(); if (strlen($cache)) { $content = $cache; } else { $content = $this->markupEngine->markupText($content); if ($comment->getID()) { $comment->setCache($content); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $comment->save(); + unset($unguarded); } } $content = '
'. $content. '
'; } else { $title = null; $head_content = '
'. "

{$author_link} {$verb} this revision.

". '
'; $content = null; } if ($this->inlines) { $inline_render = array(); $inlines = $this->inlines; $changesets = $this->changesets; $inlines_by_changeset = mgroup($inlines, 'getChangesetID'); $inlines_by_changeset = array_select_keys( $inlines_by_changeset, array_keys($this->changesets)); $inline_render[] = ''; foreach ($inlines_by_changeset as $changeset_id => $inlines) { $changeset = $changesets[$changeset_id]; $inlines = msort($inlines, 'getLineNumber'); $inline_render[] = ''. ''. ''; foreach ($inlines as $inline) { if (!$inline->getLineLength()) { $lines = $inline->getLineNumber(); } else { $lines = $inline->getLineNumber()."\xE2\x80\x93". ($inline->getLineNumber() + $inline->getLineLength()); } if (!$this->target || $changeset->getDiffID() === $this->target->getID()) { $lines = phutil_render_tag( 'a', array( 'href' => '#inline-'.$inline->getID(), 'class' => 'num', ), $lines); } $inline_content = $inline->getContent(); if (strlen($inline_content)) { $inline_cache = $inline->getCache(); if ($inline_cache) { $inline_content = $inline_cache; } else { $inline_content = $this->markupEngine->markupText( $inline_content); if ($inline->getID()) { $inline->setCache($inline_content); $inline->save(); } } } $inline_render[] = ''. ''. ''. ''; } } $inline_render[] = '
'. phutil_escape_html($changeset->getFileName()). '
'.$lines.''. '
'. $inline_content. '
'. '
'; $inline_render = implode("\n", $inline_render); $inline_render = '
'. 'Inline Comments'. '
'. $inline_render; } else { $inline_render = null; } $background = null; $uri = $author->getImageURI(); if ($uri) { $background = "background-image: url('{$uri}');"; } $metadata_blocks = array(); $metadata = $comment->getMetadata(); $added_reviewers = idx( $metadata, DifferentialComment::METADATA_ADDED_REVIEWERS); if ($added_reviewers) { $reviewers = 'Added reviewers: '.$this->renderHandleList( $added_reviewers); $metadata_blocks[] = $reviewers; } $added_ccs = idx( $metadata, DifferentialComment::METADATA_ADDED_CCS); if ($added_ccs) { $ccs = 'Added CCs: '.$this->renderHandleList($added_ccs); $metadata_blocks[] = $ccs; } if ($metadata_blocks) { $metadata_blocks = ''; } else { $metadata_blocks = null; } return phutil_render_tag( 'div', array( 'class' => "differential-comment {$action_class}", 'id' => $comment_anchor, ), '
'. ''.$info.''. ''.$title.''. '
'. '
'. '
'. '
'. $head_content. $metadata_blocks. '
'. $content. '
'. $inline_render. '
'. '
'); } private function renderHandleList(array $phids) { $result = array(); foreach ($phids as $phid) { $result[] = $this->handles[$phid]->renderLink(); } return implode(', ', $result); } } diff --git a/src/applications/differential/view/revisioncomment/__init__.php b/src/applications/differential/view/revisioncomment/__init__.php index f105a69222..8dee4bedd6 100644 --- a/src/applications/differential/view/revisioncomment/__init__.php +++ b/src/applications/differential/view/revisioncomment/__init__.php @@ -1,20 +1,21 @@ getLocalDiskFileStorageRoot(); // Generate a random, unique file path like "ab/29/1f918a9ac39201ff". We // put a couple of subdirectories up front to avoid a situation where we // have one directory with a zillion files in it, since this is generally // bad news. do { $name = md5(mt_rand()); $name = preg_replace('/^(..)(..)(.*)$/', '\\1/\\2/\\3', $name); if (!Filesystem::pathExists($root.'/'.$name)) { break; } } while (true); $parent = $root.'/'.dirname($name); if (!Filesystem::pathExists($parent)) { execx('mkdir -p %s', $parent); } + AphrontWriteGuard::willWrite(); Filesystem::writeFile($root.'/'.$name, $data); return $name; } /** * Read the file data off local disk. * @task impl */ public function readFile($handle) { $path = $this->getLocalDiskFileStorageFullPath($handle); return Filesystem::readFile($path); } /** * Deletes the file from local disk, if it exists. * @task impl */ public function deleteFile($handle) { $path = $this->getLocalDiskFileStorageFullPath($handle); if (Filesystem::pathExists($path)) { + AphrontWriteGuard::willWrite(); Filesystem::remove($path); } } /* -( Internals )---------------------------------------------------------- */ /** * Get the configured local disk path for file storage. * * @return string Absolute path to somewhere that files can be stored. * @task internal */ private function getLocalDiskFileStorageRoot() { $root = PhabricatorEnv::getEnvConfig('storage.local-disk.path'); if (!$root || $root == '/' || $root[0] != '/') { throw new Exception( "Malformed local disk storage root. You must provide an absolute ". "path, and can not use '/' as the root."); } return rtrim($root, '/'); } /** * Convert a handle into an absolute local disk path. * * @param string File data handle. * @return string Absolute path to the corresponding file. * @task internal */ private function getLocalDiskFileStorageFullPath($handle) { // Make sure there's no funny business going on here. Users normally have // no ability to affect the content of handles, but double-check that // we're only accessing local storage just in case. if (!preg_match('@^[a-f0-9]{2}/[a-f0-9]{2}/[a-f0-9]{28}$@', $handle)) { throw new Exception( "Local disk filesystem handle '{$handle}' is malformed!"); } $root = $this->getLocalDiskFileStorageRoot(); return $root.'/'.$handle; } } diff --git a/src/applications/files/engine/localdisk/__init__.php b/src/applications/files/engine/localdisk/__init__.php index b6639e48e0..354e96acea 100644 --- a/src/applications/files/engine/localdisk/__init__.php +++ b/src/applications/files/engine/localdisk/__init__.php @@ -1,16 +1,17 @@ newS3API(); $name = 'phabricator/'.sha1(Filesystem::readRandomBytes(20)); + AphrontWriteGuard::willWrite(); $s3->putObject( $data, $this->getBucketName(), $name, $acl = 'private'); return $name; } /** * Load a stored blob from S3. * @task impl */ public function readFile($handle) { $result = $this->newS3API()->getObject( $this->getBucketName(), $handle); return $result->body; } /** * Delete a blob from S3. * @task impl */ public function deleteFile($handle) { + + AphrontWriteGuard::willWrite(); $this->newS3API()->deleteObject( $this->getBucketName(), $handle); } /* -( Internals )---------------------------------------------------------- */ /** * Retrieve the S3 bucket name. * * @task internal */ private function getBucketName() { $bucket = PhabricatorEnv::getEnvConfig('storage.s3.bucket'); if (!$bucket) { throw new Exception("No 'storage.s3.bucket' specified!"); } return $bucket; } /** * Create a new S3 API object. * * @task internal */ private function newS3API() { $libroot = dirname(phutil_get_library_root('phabricator')); require_once $libroot.'/externals/s3/S3.php'; $access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key'); $secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key'); if (!$access_key || !$secret_key) { throw new Exception( "Specify 'amazon-s3.access-key' and 'amazon-s3.secret-key'!"); } $s3 = newv( 'S3', array( $access_key, $secret_key, $use_ssl = true, )); $s3->setExceptions(true); return $s3; } } diff --git a/src/applications/files/engine/s3/__init__.php b/src/applications/files/engine/s3/__init__.php index 80acf0c1d2..e2e88309c4 100644 --- a/src/applications/files/engine/s3/__init__.php +++ b/src/applications/files/engine/s3/__init__.php @@ -1,17 +1,18 @@ transactions = $transactions; return $this; } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function setMarkupEngine(PhutilMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function setPreview($preview) { $this->preview = $preview; return $this; } public function setRenderSummaryOnly($render_summary_only) { $this->renderSummaryOnly = $render_summary_only; return $this; } public function getRenderSummaryOnly() { return $this->renderSummaryOnly; } public function setRenderFullSummary($render_full_summary) { $this->renderFullSummary = $render_full_summary; return $this; } public function getRenderFullSummary() { return $this->renderFullSummary; } public function setCommentNumber($comment_number) { $this->commentNumber = $comment_number; return $this; } public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } public function renderForEmail($with_date) { $this->forEmail = true; $transaction = reset($this->transactions); $author = $this->renderHandles(array($transaction->getAuthorPHID())); $action = null; $descs = array(); $comments = null; foreach ($this->transactions as $transaction) { list($verb, $desc, $classes) = $this->describeAction($transaction); if ($action === null) { $action = $verb; } $desc = $author.' '.$desc.'.'; if ($with_date) { // NOTE: This is going into a (potentially multi-recipient) email so // we can't use a single user's timezone preferences. Use the server's // instead, but make the timezone explicit. $datetime = date('M jS \a\t g:i A T', $transaction->getDateCreated()); $desc = "On {$datetime}, {$desc}"; } $descs[] = $desc; if ($transaction->hasComments()) { $comments = $transaction->getComments(); } } $descs = implode("\n", $descs); if ($comments) { $descs .= "\n".$comments; } foreach ($this->transactions as $transaction) { $supplemental = $this->renderSupplementalInfoForEmail($transaction); if ($supplemental) { $descs .= "\n\n".$supplemental; } } $this->forEmail = false; return array($action, $descs); } public function render() { if (!$this->user) { throw new Exception("Call setUser() before render()!"); } $handles = $this->handles; $transactions = $this->transactions; require_celerity_resource('maniphest-transaction-detail-css'); $comment_transaction = null; foreach ($this->transactions as $transaction) { if ($transaction->hasComments()) { $comment_transaction = $transaction; break; } } $any_transaction = reset($transactions); $author = $this->handles[$any_transaction->getAuthorPHID()]; $more_classes = array(); $descs = array(); foreach ($transactions as $transaction) { list($verb, $desc, $classes) = $this->describeAction($transaction); $more_classes = array_merge($more_classes, $classes); $full_summary = null; if ($this->getRenderFullSummary()) { $full_summary = $this->renderFullSummary($transaction); } $descs[] = javelin_render_tag( 'div', array( 'sigil' => 'maniphest-transaction-description', ), $author->renderLink().' '.$desc.'.'.$full_summary); } $descs = implode("\n", $descs); if ($this->getRenderSummaryOnly()) { return $descs; } $more_classes = implode(' ', $more_classes); if ($comment_transaction && $comment_transaction->hasComments()) { $comments = $comment_transaction->getCache(); if (!strlen($comments)) { $comments = $comment_transaction->getComments(); if (strlen($comments)) { $comments = $this->markupEngine->markupText($comments); $comment_transaction->setCache($comments); if ($comment_transaction->getID() && !$this->preview) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $comment_transaction->save(); + unset($unguarded); } } } $comment_block = '
'. $comments. '
'; } else { $comment_block = null; } if ($this->preview) { $timestamp = 'COMMENT PREVIEW'; } else { $timestamp = phabricator_datetime( $transaction->getDateCreated(), $this->user); } $info = array(); $info[] = $timestamp; $comment_anchor = null; $num = $this->commentNumber; if ($num && !$this->preview) { Javelin::initBehavior('phabricator-watch-anchor'); $info[] = javelin_render_tag( 'a', array( 'name' => 'comment-'.$num, 'href' => '#comment-'.$num, ), 'Comment T'.$any_transaction->getTaskID().'#'.$num); $comment_anchor = 'anchor-comment-'.$num; } $info = implode(' · ', $info); return phutil_render_tag( 'div', array( 'class' => "maniphest-transaction-detail-container", 'style' => "background-image: url('".$author->getImageURI()."')", 'id' => $comment_anchor, ), '
'. '
'. '
'. $info. '
'. $descs. '
'. $comment_block. '
'); } private function renderSupplementalInfoForEmail($transaction) { $handles = $this->handles; $type = $transaction->getTransactionType(); $new = $transaction->getNewValue(); $old = $transaction->getOldValue(); switch ($type) { case ManiphestTransactionType::TYPE_DESCRIPTION: return "NEW DESCRIPTION\n ".trim($new)."\n\n". "PREVIOUS DESCRIPTION\n ".trim($old); case ManiphestTransactionType::TYPE_ATTACH: $old_raw = nonempty($old, array()); $new_raw = nonempty($new, array()); $attach_types = array( PhabricatorPHIDConstants::PHID_TYPE_DREV, PhabricatorPHIDConstants::PHID_TYPE_FILE, ); foreach ($attach_types as $type) { $old = array_keys(idx($old_raw, $type, array())); $new = array_keys(idx($new_raw, $type, array())); if ($old != $new) { break; } } $added = array_diff($new, $old); if (!$added) { break; } $links = array(); foreach (array_select_keys($handles, $added) as $handle) { $links[] = ' '.PhabricatorEnv::getProductionURI($handle->getURI()); } $links = implode("\n", $links); switch ($type) { case PhabricatorPHIDConstants::PHID_TYPE_DREV: $title = 'ATTACHED REVISIONS'; break; case PhabricatorPHIDConstants::PHID_TYPE_FILE: $title = 'ATTACHED FILES'; break; } return $title."\n".$links; default: break; } return null; } private function describeAction($transaction) { $verb = null; $desc = null; $classes = array(); $handles = $this->handles; $type = $transaction->getTransactionType(); $author_phid = $transaction->getAuthorPHID(); $new = $transaction->getNewValue(); $old = $transaction->getOldValue(); switch ($type) { case ManiphestTransactionType::TYPE_TITLE: $verb = 'Retitled'; $desc = 'changed the title from '.$this->renderString($old). ' to '.$this->renderString($new); break; case ManiphestTransactionType::TYPE_DESCRIPTION: $verb = 'Edited'; if ($this->forEmail || $this->getRenderFullSummary()) { $desc = 'updated the task description'; } else { $desc = 'updated the task description; '. $this->renderExpandLink($transaction); } break; case ManiphestTransactionType::TYPE_NONE: $verb = 'Commented On'; $desc = 'added a comment'; break; case ManiphestTransactionType::TYPE_OWNER: if ($transaction->getAuthorPHID() == $new) { $verb = 'Claimed'; $desc = 'claimed this task'; $classes[] = 'claimed'; } else if (!$new) { $verb = 'Up For Grabs'; $desc = 'placed this task up for grabs'; $classes[] = 'upforgrab'; } else if (!$old) { $verb = 'Assigned'; $desc = 'assigned this task to '.$this->renderHandles(array($new)); $classes[] = 'assigned'; } else { $verb = 'Reassigned'; $desc = 'reassigned this task from '. $this->renderHandles(array($old)). ' to '. $this->renderHandles(array($new)); $classes[] = 'reassigned'; } break; case ManiphestTransactionType::TYPE_CCS: if ($this->preview) { $verb = 'Changed CC'; $desc = 'changed CCs..'; break; } $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { $verb = 'Added CC'; if (count($added) == 1) { $desc = 'added '.$this->renderHandles($added).' to CC'; } else { $desc = 'added CCs: '.$this->renderHandles($added); } } else if ($removed && !$added) { $verb = 'Removed CC'; if (count($removed) == 1) { $desc = 'removed '.$this->renderHandles($removed).' from CC'; } else { $desc = 'removed CCs: '.$this->renderHandles($removed); } } else { $verb = 'Changed CC'; $desc = 'changed CCs, added: '.$this->renderHandles($added).'; '. 'removed: '.$this->renderHandles($removed); } break; case ManiphestTransactionType::TYPE_PROJECTS: if ($this->preview) { $verb = 'Changed Projects'; $desc = 'changed projects..'; break; } $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { $verb = 'Added Project'; if (count($added) == 1) { $desc = 'added project '.$this->renderHandles($added); } else { $desc = 'added projects: '.$this->renderHandles($added); } } else if ($removed && !$added) { $verb = 'Removed Project'; if (count($removed) == 1) { $desc = 'removed project '.$this->renderHandles($removed); } else { $desc = 'removed projectss: '.$this->renderHandles($removed); } } else { $verb = 'Changed Projects'; $desc = 'changed projects, added: '.$this->renderHandles($added).'; '. 'removed: '.$this->renderHandles($removed); } break; case ManiphestTransactionType::TYPE_STATUS: if ($new == ManiphestTaskStatus::STATUS_OPEN) { if ($old) { $verb = 'Reopened'; $desc = 'reopened this task'; $classes[] = 'reopened'; } else { $verb = 'Created'; $desc = 'created this task'; $classes[] = 'created'; } } else if ($new == ManiphestTaskStatus::STATUS_CLOSED_SPITE) { $verb = 'Spited'; $desc = 'closed this task out of spite'; $classes[] = 'spited'; } else if ($new == ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE) { $verb = 'Merged'; $desc = 'closed this task as a duplicate'; $classes[] = 'duplicate'; } else { $verb = 'Closed'; $full = idx(ManiphestTaskStatus::getTaskStatusMap(), $new, '???'); $desc = 'closed this task as "'.$full.'"'; $classes[] = 'closed'; } break; case ManiphestTransactionType::TYPE_PRIORITY: $old_name = ManiphestTaskPriority::getTaskPriorityName($old); $new_name = ManiphestTaskPriority::getTaskPriorityName($new); if ($old == ManiphestTaskPriority::PRIORITY_TRIAGE) { $verb = 'Triaged'; $desc = 'triaged this task as "'.$new_name.'" priority'; } else if ($old > $new) { $verb = 'Lowered Priority'; $desc = 'lowered the priority of this task from "'.$old_name.'" to '. '"'.$new_name.'"'; } else { $verb = 'Raised Priority'; $desc = 'raised the priority of this task from "'.$old_name.'" to '. '"'.$new_name.'"'; } if ($new == ManiphestTaskPriority::PRIORITY_UNBREAK_NOW) { $classes[] = 'unbreaknow'; } break; case ManiphestTransactionType::TYPE_ATTACH: if ($this->preview) { $verb = 'Changed Attached'; $desc = 'changed attachments..'; break; } $old_raw = nonempty($old, array()); $new_raw = nonempty($new, array()); foreach (array( PhabricatorPHIDConstants::PHID_TYPE_DREV, PhabricatorPHIDConstants::PHID_TYPE_TASK, PhabricatorPHIDConstants::PHID_TYPE_FILE) as $type) { $old = array_keys(idx($old_raw, $type, array())); $new = array_keys(idx($new_raw, $type, array())); if ($old != $new) { break; } } $added = array_diff($new, $old); $removed = array_diff($old, $new); $add_desc = $this->renderHandles($added); $rem_desc = $this->renderHandles($removed); switch ($type) { case PhabricatorPHIDConstants::PHID_TYPE_DREV: $singular = 'Differential Revision'; $plural = 'Differential Revisions'; break; case PhabricatorPHIDConstants::PHID_TYPE_FILE: $singular = 'file'; $plural = 'files'; break; case PhabricatorPHIDConstants::PHID_TYPE_TASK: $singular = 'Maniphest Task'; $plural = 'Maniphest Tasks'; $dependency = true; break; } if ($added && !$removed) { $verb = 'Attached'; if (count($added) == 1) { $desc = 'attached '.$singular.': '.$add_desc; } else { $desc = 'attached '.$plural.': '.$add_desc; } } else if ($removed && !$added) { $verb = 'Detached'; if (count($removed) == 1) { $desc = 'detached '.$singular.': '.$rem_desc; } else { $desc = 'detached '.$plural.': '.$rem_desc; } } else { $verb = 'Changed Attached'; $desc = 'changed attached '.$plural.', added: '.$add_desc. 'removed: '.$rem_desc; } break; default: return array($type, ' brazenly '.$type."'d", $classes); } return array($verb, $desc, $classes); } private function renderFullSummary($transaction) { switch ($transaction->getTransactionType()) { case ManiphestTransactionType::TYPE_DESCRIPTION: $engine = $this->markupEngine; $old = $transaction->getOldValue(); $new = $transaction->getNewValue(); $table = '
Previous Description New Description
'. $engine->markupText($old). '
'. $engine->markupText($new). '
'; return $table; } return null; } private function renderExpandLink($transaction) { $id = $transaction->getID(); Javelin::initBehavior('maniphest-transaction-expand'); return javelin_render_tag( 'a', array( 'href' => '/maniphest/task/descriptionchange/'.$id.'/', 'sigil' => 'maniphest-expand-transaction', 'mustcapture' => true, ), 'show details'); } private function renderHandles($phids) { $links = array(); foreach ($phids as $phid) { if ($this->forEmail) { $links[] = $this->handles[$phid]->getName(); } else { $links[] = $this->handles[$phid]->renderLink(); } } return implode(', ', $links); } private function renderString($string) { if ($this->forEmail) { return '"'.$string.'"'; } else { return '"'.phutil_escape_html($string).'"'; } } } diff --git a/src/applications/maniphest/view/transactiondetail/__init__.php b/src/applications/maniphest/view/transactiondetail/__init__.php index bbb604b5bc..b1c34a4630 100644 --- a/src/applications/maniphest/view/transactiondetail/__init__.php +++ b/src/applications/maniphest/view/transactiondetail/__init__.php @@ -1,24 +1,25 @@ profileImagePHID, PhabricatorEnv::getEnvConfig('user.default-profile-image-phid')); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_USER); } public function setPassword($password) { if (!$this->getPHID()) { throw new Exception( "You can not set a password for an unsaved user because their PHID ". "is a salt component in the password hash."); } if (!strlen($password)) { $this->setPasswordHash(''); } else { $this->setPasswordSalt(md5(mt_rand())); $hash = $this->hashPassword($password); $this->setPasswordHash($hash); } return $this; } public function save() { if (!$this->conduitCertificate) { $this->conduitCertificate = $this->generateConduitCertificate(); } $result = parent::save(); PhabricatorSearchUserIndexer::indexUser($this); return $result; } private function generateConduitCertificate() { $entropy = Filesystem::readRandomBytes(256); $entropy = base64_encode($entropy); $entropy = substr($entropy, 0, 255); return $entropy; } public function comparePassword($password) { if (!strlen($password)) { return false; } if (!strlen($this->getPasswordHash())) { return false; } $password = $this->hashPassword($password); return ($password === $this->getPasswordHash()); } private function hashPassword($password) { $password = $this->getUsername(). $password. $this->getPHID(). $this->getPasswordSalt(); for ($ii = 0; $ii < 1000; $ii++) { $password = md5($password); } return $password; } const CSRF_CYCLE_FREQUENCY = 3600; const CSRF_TOKEN_LENGTH = 16; const EMAIL_CYCLE_FREQUENCY = 86400; const EMAIL_TOKEN_LENGTH = 24; public function getCSRFToken($offset = 0) { return $this->generateToken( time() + (self::CSRF_CYCLE_FREQUENCY * $offset), self::CSRF_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key'), self::CSRF_TOKEN_LENGTH); } public function validateCSRFToken($token) { // When the user posts a form, we check that it contains a valid CSRF token. // Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept // either the current token, the next token (users can submit a "future" // token if you have two web frontends that have some clock skew) or any of // the last 6 tokens. This means that pages are valid for up to 7 hours. // There is also some Javascript which periodically refreshes the CSRF // tokens on each page, so theoretically pages should be valid indefinitely. // However, this code may fail to run (if the user loses their internet // connection, or there's a JS problem, or they don't have JS enabled). // Choosing the size of the window in which we accept old CSRF tokens is // an issue of balancing concerns between security and usability. We could // choose a very narrow (e.g., 1-hour) window to reduce vulnerability to // attacks using captured CSRF tokens, but it's also more likely that real // users will be affected by this, e.g. if they close their laptop for an // hour, open it back up, and try to submit a form before the CSRF refresh // can kick in. Since the user experience of submitting a form with expired // CSRF is often quite bad (you basically lose data, or it's a big pain to // recover at least) and I believe we gain little additional protection // by keeping the window very short (the overwhelming value here is in // preventing blind attacks, and most attacks which can capture CSRF tokens // can also just capture authentication information [sniffing networks] // or act as the user [xss]) the 7 hour default seems like a reasonable // balance. Other major platforms have much longer CSRF token lifetimes, // like Rails (session duration) and Django (forever), which suggests this // is a reasonable analysis. $csrf_window = 6; for ($ii = -$csrf_window; $ii <= 1; $ii++) { $valid = $this->getCSRFToken($ii); if ($token == $valid) { return true; } } return false; } private function generateToken($epoch, $frequency, $key, $len) { $time_block = floor($epoch / $frequency); $vec = $this->getPHID().$this->passwordHash.$key.$time_block; return substr(sha1($vec), 0, $len); } /** * Issue a new session key to this user. Phabricator supports different * types of sessions (like "web" and "conduit") and each session type may * have multiple concurrent sessions (this allows a user to be logged in on * multiple browsers at the same time, for instance). * * Note that this method is transport-agnostic and does not set cookies or * issue other types of tokens, it ONLY generates a new session key. * * You can configure the maximum number of concurrent sessions for various * session types in the Phabricator configuration. * * @param string Session type, like "web". * @return string Newly generated session key. */ public function establishSession($session_type) { $conn_w = $this->establishConnection('w'); if (strpos($session_type, '-') !== false) { throw new Exception("Session type must not contain hyphen ('-')!"); } // We allow multiple sessions of the same type, so when a caller requests // a new session of type "web", we give them the first available session in // "web-1", "web-2", ..., "web-N", up to some configurable limit. If none // of these sessions is available, we overwrite the oldest session and // reissue a new one in its place. $session_limit = 1; switch ($session_type) { case 'web': $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.web'); break; case 'conduit': $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.conduit'); break; default: throw new Exception("Unknown session type '{$session_type}'!"); } $session_limit = (int)$session_limit; if ($session_limit <= 0) { throw new Exception( "Session limit for '{$session_type}' must be at least 1!"); } // Load all the currently active sessions. $sessions = queryfx_all( $conn_w, 'SELECT type, sessionStart FROM %T WHERE userPHID = %s AND type LIKE %>', PhabricatorUser::SESSION_TABLE, $this->getPHID(), $session_type.'-'); // Choose which 'type' we'll actually establish, i.e. what number we're // going to append to the basic session type. To do this, just check all // the numbers sequentially until we find an available session. $establish_type = null; $sessions = ipull($sessions, null, 'type'); for ($ii = 1; $ii <= $session_limit; $ii++) { if (empty($sessions[$session_type.'-'.$ii])) { $establish_type = $session_type.'-'.$ii; break; } } // If we didn't find an available session, choose the oldest session and // overwrite it. if (!$establish_type) { $sessions = isort($sessions, 'sessionStart'); $oldest = reset($sessions); $establish_type = $oldest['type']; } // Consume entropy to generate a new session key, forestalling the eventual // heat death of the universe. $entropy = Filesystem::readRandomBytes(20); $session_key = sha1($entropy); + // UNGUARDED WRITES: Logging-in users don't have CSRF stuff yet. + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + queryfx( $conn_w, 'INSERT INTO %T '. '(userPHID, type, sessionKey, sessionStart)'. ' VALUES '. '(%s, %s, %s, UNIX_TIMESTAMP()) '. 'ON DUPLICATE KEY UPDATE '. 'sessionKey = VALUES(sessionKey), '. 'sessionStart = VALUES(sessionStart)', self::SESSION_TABLE, $this->getPHID(), $establish_type, $session_key); $log = PhabricatorUserLog::newLog( $this, $this, PhabricatorUserLog::ACTION_LOGIN); $log->setDetails( array( 'session_type' => $session_type, 'session_issued' => $establish_type, )); $log->setSession($session_key); $log->save(); return $session_key; } private function generateEmailToken($offset = 0) { return $this->generateToken( time() + ($offset * self::EMAIL_CYCLE_FREQUENCY), self::EMAIL_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(), self::EMAIL_TOKEN_LENGTH); } public function validateEmailToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->generateEmailToken($ii); if ($token == $valid) { return true; } } return false; } public function getEmailLoginURI() { $token = $this->generateEmailToken(); $uri = PhabricatorEnv::getProductionURI('/login/etoken/'.$token.'/'); $uri = new PhutilURI($uri); return $uri->alter('email', $this->getEmail()); } public function loadPreferences() { if ($this->preferences) { return $this->preferences; } $preferences = id(new PhabricatorUserPreferences())->loadOneWhere( 'userPHID = %s', $this->getPHID()); if (!$preferences) { $preferences = new PhabricatorUserPreferences(); $preferences->setUserPHID($this->getPHID()); $default_dict = array( PhabricatorUserPreferences::PREFERENCE_TITLES => 'glyph', PhabricatorUserPreferences::PREFERENCE_MONOSPACED => ''); $preferences->setPreferences($default_dict); } $this->preferences = $preferences; return $preferences; } public function getTimezoneIdentifier() { // If the user hasn't set one, guess the server's time. return nonempty( $this->timezoneIdentifier, date_default_timezone_get()); } } diff --git a/src/applications/people/storage/user/__init__.php b/src/applications/people/storage/user/__init__.php index 2f78f50a24..d7fd517a2d 100644 --- a/src/applications/people/storage/user/__init__.php +++ b/src/applications/people/storage/user/__init__.php @@ -1,23 +1,24 @@ configuration = $configuration; } public function escapeString($string) { $this->requireConnection(); return mysql_real_escape_string($string, $this->connection); } public function escapeColumnName($name) { return '`'.str_replace('`', '\\`', $name).'`'; } public function escapeMultilineComment($comment) { // These can either terminate a comment, confuse the hell out of the parser, // make MySQL execute the comment as a query, or, in the case of semicolon, // are quasi-dangerous because the semicolon could turn a broken query into // a working query plus an ignored query. static $map = array( '--' => '(DOUBLEDASH)', '*/' => '(STARSLASH)', '//' => '(SLASHSLASH)', '#' => '(HASH)', '!' => '(BANG)', ';' => '(SEMICOLON)', ); $comment = str_replace( array_keys($map), array_values($map), $comment); // For good measure, kill anything else that isn't a nice printable // character. $comment = preg_replace('/[^\x20-\x7F]+/', ' ', $comment); return '/* '.$comment.' */'; } public function escapeStringForLikeClause($value) { $value = $this->escapeString($value); // Ideally the query shouldn't be modified after safely escaping it, // but we need to escape _ and % within LIKE terms. $value = str_replace( // Even though we've already escaped, we need to replace \ with \\ // because MYSQL unescapes twice inside a LIKE clause. See note // at mysql.com. However, if the \ is being used to escape a single // quote ('), then the \ should not be escaped. Thus, after all \ // are replaced with \\, we need to revert instances of \\' back to // \'. array('\\', '\\\\\'', '_', '%'), array('\\\\', '\\\'', '\_', '\%'), $value); return $value; } private function getConfiguration($key, $default = null) { return idx($this->configuration, $key, $default); } private function closeConnection() { if ($this->connection) { $this->connection = null; $key = $this->getConnectionCacheKey(); unset(self::$connectionCache[$key]); } } private function getConnectionCacheKey() { $user = $this->getConfiguration('user'); $host = $this->getConfiguration('host'); $database = $this->getConfiguration('database'); return "{$user}:{$host}:{$database}"; } private function establishConnection() { $this->closeConnection(); $user = $this->getConfiguration('user'); $host = $this->getConfiguration('host'); $database = $this->getConfiguration('database'); $key = $this->getConnectionCacheKey(); if (isset(self::$connectionCache[$key])) { $this->connection = self::$connectionCache[$key]; return; } $start = microtime(true); if (!function_exists('mysql_connect')) { // We have to '@' the actual call since it can spew all sorts of silly // noise, but it will also silence fatals caused by not having MySQL // installed, which has bitten me on three separate occasions. Make sure // such failures are explicit and loud. throw new Exception( "About to call mysql_connect(), but the PHP MySQL extension is not ". "available!"); } $profiler = PhutilServiceProfiler::getInstance(); $call_id = $profiler->beginServiceCall( array( 'type' => 'connect', 'host' => $host, 'database' => $database, )); try { $conn = @mysql_connect( $host, $user, $this->getConfiguration('pass'), $new_link = true, $flags = 0); if (!$conn) { $errno = mysql_errno(); $error = mysql_error(); throw new AphrontQueryConnectionException( "Attempt to connect to {$user}@{$host} failed with error #{$errno}: ". "{$error}."); } if ($database !== null) { $ret = @mysql_select_db($database, $conn); if (!$ret) { $this->throwQueryException($conn); } } $profiler->endServiceCall($call_id, array()); } catch (Exception $ex) { $profiler->endServiceCall($call_id, array()); throw $ex; } self::$connectionCache[$key] = $conn; $this->connection = $conn; } public function getInsertID() { return mysql_insert_id($this->requireConnection()); } public function getAffectedRows() { return mysql_affected_rows($this->requireConnection()); } public function getTransactionKey() { return (int)$this->requireConnection(); } private function requireConnection() { if (!$this->connection) { $this->establishConnection(); } return $this->connection; } public function selectAllResults() { $result = array(); $res = $this->lastResult; if ($res == null) { throw new Exception('No query result to fetch from!'); } while (($row = mysql_fetch_assoc($res)) !== false) { $result[] = $row; } return $result; } public function executeRawQuery($raw_query) { $this->lastResult = null; $retries = 3; while ($retries--) { try { $this->requireConnection(); + // TODO: Do we need to include transactional statements here? + $is_write = !preg_match('/^(SELECT|SHOW)\s/', $raw_query); + if ($is_write) { + AphrontWriteGuard::willWrite(); + } + $start = microtime(true); $profiler = PhutilServiceProfiler::getInstance(); $call_id = $profiler->beginServiceCall( array( 'type' => 'query', 'config' => $this->configuration, 'query' => $raw_query, + 'write' => $is_write, )); $result = @mysql_query($raw_query, $this->connection); $profiler->endServiceCall($call_id, array()); if ($result) { $this->lastResult = $result; break; } $this->throwQueryException($this->connection); } catch (AphrontQueryConnectionLostException $ex) { if (!$retries) { throw $ex; } if ($this->isInsideTransaction()) { throw $ex; } $this->closeConnection(); } } } private function throwQueryException($connection) { $errno = mysql_errno($connection); $error = mysql_error($connection); switch ($errno) { case 2013: // Connection Dropped case 2006: // Gone Away throw new AphrontQueryConnectionLostException("#{$errno}: {$error}"); case 1213: // Deadlock case 1205: // Lock wait timeout exceeded throw new AphrontQueryRecoverableException("#{$errno}: {$error}"); case 1062: // Duplicate Key // NOTE: In some versions of MySQL we get a key name back here, but // older versions just give us a key index ("key 2") so it's not // portable to parse the key out of the error and attach it to the // exception. throw new AphrontQueryDuplicateKeyException("{$errno}: {$error}"); case 1044: // Access denied to database case 1045: // Access denied (auth) case 1142: // Access denied to table case 1143: // Access denied to column throw new AphrontQueryAccessDeniedException("#{$errno}: {$error}"); default: // TODO: 1064 is syntax error, and quite terrible in production. throw new AphrontQueryException("#{$errno}: {$error}"); } } } diff --git a/src/storage/connection/mysql/__init__.php b/src/storage/connection/mysql/__init__.php index f1ec809cb5..7b181925c5 100644 --- a/src/storage/connection/mysql/__init__.php +++ b/src/storage/connection/mysql/__init__.php @@ -1,21 +1,22 @@ ', where '' ". "is one of 'development', 'production', or a custom environment."); } if (!function_exists('mysql_connect')) { phabricator_fatal_config_error( "The PHP MySQL extension is not installed. This extension is required."); } if (!isset($_REQUEST['__path__'])) { phabricator_fatal_config_error( "__path__ is not set. Your rewrite rules are not configured correctly."); } if (get_magic_quotes_gpc()) { phabricator_fatal_config_error( "Your server is configured with PHP 'magic_quotes_gpc' enabled. This ". "feature is 'highly discouraged' by PHP's developers and you must ". "disable it to run Phabricator. Consult the PHP manual for instructions."); } register_shutdown_function('phabricator_shutdown'); require_once dirname(dirname(__FILE__)).'/conf/__init_conf__.php'; try { setup_aphront_basics(); $conf = phabricator_read_config_file($env); $conf['phabricator.env'] = $env; phutil_require_module('phabricator', 'infrastructure/env'); PhabricatorEnv::setEnvConfig($conf); phutil_require_module('phabricator', 'aphront/console/plugin/xhprof/api'); DarkConsoleXHProfPluginAPI::hookProfiler(); phutil_require_module('phabricator', 'aphront/console/plugin/errorlog/api'); PhutilErrorHandler::initialize(); } catch (Exception $ex) { phabricator_fatal("[Initialization Exception] ".$ex->getMessage()); } $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone'); if ($tz) { date_default_timezone_set($tz); } phutil_require_module('phabricator', 'aphront/console/plugin/errorlog/api'); phutil_require_module('phutil', 'error'); PhutilErrorHandler::setErrorListener( array('DarkConsoleErrorLogPluginAPI', 'handleErrors')); foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) { phutil_load_library($library); } if (PhabricatorEnv::getEnvConfig('phabricator.setup')) { PhabricatorSetup::runSetup(); return; } $host = $_SERVER['HTTP_HOST']; $path = $_REQUEST['__path__']; switch ($host) { default: $config_key = 'aphront.default-application-configuration-class'; $config_class = PhabricatorEnv::getEnvConfig($config_key); PhutilSymbolLoader::loadClass($config_class); $application = newv($config_class, array()); break; } $application->setHost($host); $application->setPath($path); $application->willBuildRequest(); $request = $application->buildRequest(); + +$write_guard = new AphrontWriteGuard($request); + $application->setRequest($request); list($controller, $uri_data) = $application->buildController(); try { $response = $controller->willBeginExecution(); if (!$response) { $controller->willProcessRequest($uri_data); $response = $controller->processRequest(); } } catch (AphrontRedirectException $ex) { $response = id(new AphrontRedirectResponse()) ->setURI($ex->getURI()); } catch (Exception $ex) { $response = $application->handleException($ex); } try { $response = $application->willSendResponse($response); $response->setRequest($request); $response_string = $response->buildResponseString(); } catch (Exception $ex) { + $write_guard->dispose(); phabricator_fatal('[Rendering Exception] '.$ex->getMessage()); } +$write_guard->dispose(); + + $code = $response->getHTTPResponseCode(); if ($code != 200) { header("HTTP/1.0 {$code}"); } $headers = $response->getCacheHeaders(); $headers = array_merge($headers, $response->getHeaders()); foreach ($headers as $header) { list($header, $value) = $header; header("{$header}: {$value}"); } // TODO: This shouldn't be possible in a production-configured environment. if (isset($_REQUEST['__profile__']) && ($_REQUEST['__profile__'] == 'all')) { $profile = DarkConsoleXHProfPluginAPI::stopProfiler(); $profile = '
'. ''. '>>> View Profile <<<'. ''. '
'; if (strpos($response_string, '') !== false) { $response_string = str_replace( '', ''.$profile, $response_string); } else { echo $profile; } } echo $response_string; /** * @group aphront */ function setup_aphront_basics() { $aphront_root = dirname(dirname(__FILE__)); $libraries_root = dirname($aphront_root); $root = null; if (!empty($_SERVER['PHUTIL_LIBRARY_ROOT'])) { $root = $_SERVER['PHUTIL_LIBRARY_ROOT']; } ini_set('include_path', $libraries_root.':'.ini_get('include_path')); @include_once $root.'libphutil/src/__phutil_library_init__.php'; if (!@constant('__LIBPHUTIL__')) { echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ". "include the parent directory of libphutil/.\n"; exit(1); } // Load Phabricator itself using the absolute path, so we never end up doing // anything surprising (loading index.php and libraries from different // directories). phutil_load_library($aphront_root.'/src'); phutil_load_library('arcanist/src'); } function phabricator_fatal_config_error($msg) { phabricator_fatal("CONFIG ERROR: ".$msg."\n"); die(); } function phabricator_detect_insane_memory_limit() { $memory_limit = ini_get('memory_limit'); $char_limit = 12; if (strlen($memory_limit) <= $char_limit) { return; } // colmdoyle ran into an issue on an Ubuntu box with Suhosin where his // 'memory_limit' was set to: // // 3232323232323232323232323232323232323232323232323232323232323232M // // Not a typo. A wizard did it. // // Anyway, with this 'memory_limit', the machine would immediately fatal // when executing the ini_set() later. I wasn't able to reproduce this on my // EC2 Ubuntu + Suhosin box, but verified that it caused the problem on his // machine and that setting it to a more sensible value fixed it. Since I // have no idea how to actually trigger the issue, we look for a coarse // approximation of it (a memory_limit setting more than 12 characters in // length). phabricator_fatal_config_error( "Your PHP 'memory_limit' is set to something ridiculous ". "(\"{$memory_limit}\"). Set it to a more reasonable value (it must be no ". "more than {$char_limit} characters long)."); } function phabricator_shutdown() { $event = error_get_last(); if (!$event) { return; } if ($event['type'] != E_ERROR) { return; } $msg = ">>> UNRECOVERABLE FATAL ERROR <<<\n\n"; if ($event) { // Even though we should be emitting this as text-plain, escape things just // to be sure since we can't really be sure what the program state is when // we get here. $msg .= phutil_escape_html($event['message'])."\n\n"; $msg .= phutil_escape_html($event['file'].':'.$event['line']); } // flip dem tables $msg .= "\n\n\n"; $msg .= "\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb\x20\xef\xb8\xb5\x20\xc2\xaf". "\x5c\x5f\x28\xe3\x83\x84\x29\x5f\x2f\xc2\xaf\x20\xef\xb8\xb5\x20". "\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb"; phabricator_fatal($msg); } function phabricator_fatal($msg) { header( 'Content-Type: text/plain; charset=utf-8', $replace = true, $http_error = 500); error_log($msg); echo $msg; exit(1); }