diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5c4e9bc..9558815 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,723 +1,723 @@ 2, 'class' => array( 'AASTNode' => 'parser/aast/api/AASTNode.php', 'AASTNodeList' => 'parser/aast/api/AASTNodeList.php', 'AASTToken' => 'parser/aast/api/AASTToken.php', 'AASTTree' => 'parser/aast/api/AASTTree.php', 'AbstractDirectedGraph' => 'utils/AbstractDirectedGraph.php', 'AbstractDirectedGraphTestCase' => 'utils/__tests__/AbstractDirectedGraphTestCase.php', 'AphrontDatabaseConnection' => 'aphront/storage/connection/AphrontDatabaseConnection.php', 'AphrontDatabaseTransactionState' => 'aphront/storage/connection/AphrontDatabaseTransactionState.php', 'AphrontIsolatedDatabaseConnection' => 'aphront/storage/connection/AphrontIsolatedDatabaseConnection.php', 'AphrontMySQLDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php', 'AphrontMySQLDatabaseConnectionBase' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnectionBase.php', 'AphrontMySQLiDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php', 'AphrontQueryAccessDeniedException' => 'aphront/storage/exception/AphrontQueryAccessDeniedException.php', 'AphrontQueryCharacterSetException' => 'aphront/storage/exception/AphrontQueryCharacterSetException.php', 'AphrontQueryConnectionException' => 'aphront/storage/exception/AphrontQueryConnectionException.php', 'AphrontQueryConnectionLostException' => 'aphront/storage/exception/AphrontQueryConnectionLostException.php', 'AphrontQueryCountException' => 'aphront/storage/exception/AphrontQueryCountException.php', 'AphrontQueryDeadlockException' => 'aphront/storage/exception/AphrontQueryDeadlockException.php', 'AphrontQueryDuplicateKeyException' => 'aphront/storage/exception/AphrontQueryDuplicateKeyException.php', 'AphrontQueryException' => 'aphront/storage/exception/AphrontQueryException.php', 'AphrontQueryNotSupportedException' => 'aphront/storage/exception/AphrontQueryNotSupportedException.php', 'AphrontQueryObjectMissingException' => 'aphront/storage/exception/AphrontQueryObjectMissingException.php', 'AphrontQueryParameterException' => 'aphront/storage/exception/AphrontQueryParameterException.php', 'AphrontQueryRecoverableException' => 'aphront/storage/exception/AphrontQueryRecoverableException.php', 'AphrontQuerySchemaException' => 'aphront/storage/exception/AphrontQuerySchemaException.php', 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/AphrontScopedUnguardedWriteCapability.php', 'AphrontWriteGuard' => 'aphront/writeguard/AphrontWriteGuard.php', 'AphrontWriteGuardExitEventListener' => 'aphront/writeguard/event/AphrontWriteGuardExitEventListener.php', 'BaseHTTPFuture' => 'future/http/BaseHTTPFuture.php', 'CommandException' => 'future/exec/CommandException.php', 'ConduitClient' => 'conduit/ConduitClient.php', 'ConduitClientException' => 'conduit/ConduitClientException.php', 'ConduitFuture' => 'conduit/ConduitFuture.php', 'ExecFuture' => 'future/exec/ExecFuture.php', 'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php', 'ExecPassthruTestCase' => 'future/exec/__tests__/ExecPassthruTestCase.php', 'FileFinder' => 'filesystem/FileFinder.php', 'FileFinderTestCase' => 'filesystem/__tests__/FileFinderTestCase.php', 'FileList' => 'filesystem/FileList.php', 'Filesystem' => 'filesystem/Filesystem.php', 'FilesystemException' => 'filesystem/FilesystemException.php', 'FilesystemTestCase' => 'filesystem/__tests__/FilesystemTestCase.php', 'Future' => 'future/Future.php', 'FutureIterator' => 'future/FutureIterator.php', 'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php', 'FutureProxy' => 'future/FutureProxy.php', 'HTTPFuture' => 'future/http/HTTPFuture.php', + 'HTTPFutureCURLesponseStatus' => 'future/http/status/HTTPFutureCURLResponseStatus.php', + 'HTTPFutureHTTPResponseStatus' => 'future/http/status/HTTPFutureHTTPResponseStatus.php', + 'HTTPFutureParseResponseStatus' => 'future/http/status/HTTPFutureParseResponseStatus.php', 'HTTPFutureResponseStatus' => 'future/http/status/HTTPFutureResponseStatus.php', - 'HTTPFutureResponseStatusCURL' => 'future/http/status/HTTPFutureResponseStatusCURL.php', - 'HTTPFutureResponseStatusHTTP' => 'future/http/status/HTTPFutureResponseStatusHTTP.php', - 'HTTPFutureResponseStatusParse' => 'future/http/status/HTTPFutureResponseStatusParse.php', - 'HTTPFutureResponseStatusTransport' => 'future/http/status/HTTPFutureResponseStatusTransport.php', + 'HTTPFutureTransportResponseStatus' => 'future/http/status/HTTPFutureTransportResponseStatus.php', 'HTTPSFuture' => 'future/http/HTTPSFuture.php', 'ImmediateFuture' => 'future/ImmediateFuture.php', 'LinesOfALarge' => 'filesystem/linesofalarge/LinesOfALarge.php', 'LinesOfALargeExecFuture' => 'filesystem/linesofalarge/LinesOfALargeExecFuture.php', 'LinesOfALargeExecFutureTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php', 'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php', 'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php', 'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php', 'PHPASTParserTestCase' => 'parser/xhpast/__tests__/PHPASTParserTestCase.php', 'PhageAgentBootloader' => 'phage/bootloader/PhageAgentBootloader.php', 'PhageAgentTestCase' => 'phage/__tests__/PhageAgentTestCase.php', 'PhagePHPAgent' => 'phage/agent/PhagePHPAgent.php', 'PhagePHPAgentBootloader' => 'phage/bootloader/PhagePHPAgentBootloader.php', 'Phobject' => 'object/Phobject.php', 'PhutilAWSEC2Future' => 'future/aws/PhutilAWSEC2Future.php', 'PhutilAWSException' => 'future/aws/PhutilAWSException.php', 'PhutilAWSFuture' => 'future/aws/PhutilAWSFuture.php', 'PhutilAWSS3Future' => 'future/aws/PhutilAWSS3Future.php', 'PhutilAggregateException' => 'error/PhutilAggregateException.php', 'PhutilAmazonAuthAdapter' => 'auth/PhutilAmazonAuthAdapter.php', 'PhutilArgumentParser' => 'parser/argument/PhutilArgumentParser.php', 'PhutilArgumentParserException' => 'parser/argument/exception/PhutilArgumentParserException.php', 'PhutilArgumentParserTestCase' => 'parser/argument/__tests__/PhutilArgumentParserTestCase.php', 'PhutilArgumentSpecification' => 'parser/argument/PhutilArgumentSpecification.php', 'PhutilArgumentSpecificationException' => 'parser/argument/exception/PhutilArgumentSpecificationException.php', 'PhutilArgumentSpecificationTestCase' => 'parser/argument/__tests__/PhutilArgumentSpecificationTestCase.php', 'PhutilArgumentUsageException' => 'parser/argument/exception/PhutilArgumentUsageException.php', 'PhutilArgumentWorkflow' => 'parser/argument/workflow/PhutilArgumentWorkflow.php', 'PhutilArray' => 'utils/PhutilArray.php', 'PhutilArrayTestCase' => 'utils/__tests__/PhutilArrayTestCase.php', 'PhutilArrayWithDefaultValue' => 'utils/PhutilArrayWithDefaultValue.php', 'PhutilAsanaAuthAdapter' => 'auth/PhutilAsanaAuthAdapter.php', 'PhutilAsanaFuture' => 'future/asana/PhutilAsanaFuture.php', 'PhutilAuthAdapter' => 'auth/PhutilAuthAdapter.php', 'PhutilAuthConfigurationException' => 'auth/exception/PhutilAuthConfigurationException.php', 'PhutilAuthCredentialException' => 'auth/exception/PhutilAuthCredentialException.php', 'PhutilAuthException' => 'auth/exception/PhutilAuthException.php', 'PhutilAuthUserAbortedException' => 'auth/exception/PhutilAuthUserAbortedException.php', 'PhutilBallOfPHP' => 'phage/util/PhutilBallOfPHP.php', 'PhutilBitbucketAuthAdapter' => 'auth/PhutilBitbucketAuthAdapter.php', 'PhutilBootloader' => 'moduleutils/PhutilBootloader.php', 'PhutilBootloaderException' => 'moduleutils/PhutilBootloaderException.php', 'PhutilBufferedIterator' => 'utils/PhutilBufferedIterator.php', 'PhutilBufferedIteratorTestCase' => 'utils/__tests__/PhutilBufferedIteratorTestCase.php', 'PhutilBugtraqParser' => 'parser/PhutilBugtraqParser.php', 'PhutilBugtraqParserTestCase' => 'parser/__tests__/PhutilBugtraqParserTestCase.php', 'PhutilCLikeCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilCLikeCodeSnippetContextFreeGrammar.php', 'PhutilCallbackFilterIterator' => 'utils/PhutilCallbackFilterIterator.php', 'PhutilChannel' => 'channel/PhutilChannel.php', 'PhutilChannelChannel' => 'channel/PhutilChannelChannel.php', 'PhutilChannelTestCase' => 'channel/__tests__/PhutilChannelTestCase.php', 'PhutilChunkedIterator' => 'utils/PhutilChunkedIterator.php', 'PhutilChunkedIteratorTestCase' => 'utils/__tests__/PhutilChunkedIteratorTestCase.php', 'PhutilCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilCodeSnippetContextFreeGrammar.php', 'PhutilCommandString' => 'xsprintf/PhutilCommandString.php', 'PhutilConsole' => 'console/PhutilConsole.php', 'PhutilConsoleFormatter' => 'console/PhutilConsoleFormatter.php', 'PhutilConsoleMessage' => 'console/PhutilConsoleMessage.php', 'PhutilConsoleProgressBar' => 'console/PhutilConsoleProgressBar.php', 'PhutilConsoleServer' => 'console/PhutilConsoleServer.php', 'PhutilConsoleServerChannel' => 'console/PhutilConsoleServerChannel.php', 'PhutilConsoleStdinNotInteractiveException' => 'console/PhutilConsoleStdinNotInteractiveException.php', 'PhutilConsoleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php', 'PhutilConsoleTable' => 'console/PhutilConsoleTable.php', 'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php', 'PhutilContextFreeGrammar' => 'grammar/PhutilContextFreeGrammar.php', 'PhutilCsprintfTestCase' => 'xsprintf/__tests__/PhutilCsprintfTestCase.php', 'PhutilDaemon' => 'daemon/PhutilDaemon.php', 'PhutilDaemonOverseer' => 'daemon/PhutilDaemonOverseer.php', 'PhutilDefaultSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php', 'PhutilDefaultSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php', 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'markup/syntax/highlighter/pygments/PhutilDefaultSyntaxHighlighterEnginePygmentsFuture.php', 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php', 'PhutilDeferredLog' => 'filesystem/PhutilDeferredLog.php', 'PhutilDeferredLogTestCase' => 'filesystem/__tests__/PhutilDeferredLogTestCase.php', 'PhutilDirectedScalarGraph' => 'utils/PhutilDirectedScalarGraph.php', 'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php', 'PhutilDisqusAuthAdapter' => 'auth/PhutilDisqusAuthAdapter.php', 'PhutilDivinerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php', 'PhutilDocblockParser' => 'parser/PhutilDocblockParser.php', 'PhutilDocblockParserTestCase' => 'parser/__tests__/PhutilDocblockParserTestCase.php', 'PhutilEditDistanceMatrix' => 'utils/PhutilEditDistanceMatrix.php', 'PhutilEditDistanceMatrixTestCase' => 'utils/__tests__/PhutilEditDistanceMatrixTestCase.php', 'PhutilEmailAddress' => 'parser/PhutilEmailAddress.php', 'PhutilEmailAddressTestCase' => 'parser/__tests__/PhutilEmailAddressTestCase.php', 'PhutilEmptyAuthAdapter' => 'auth/PhutilEmptyAuthAdapter.php', 'PhutilErrorHandler' => 'error/PhutilErrorHandler.php', 'PhutilErrorHandlerTestCase' => 'error/__tests__/PhutilErrorHandlerTestCase.php', 'PhutilErrorTrap' => 'error/PhutilErrorTrap.php', 'PhutilEvent' => 'events/PhutilEvent.php', 'PhutilEventConstants' => 'events/constant/PhutilEventConstants.php', 'PhutilEventEngine' => 'events/PhutilEventEngine.php', 'PhutilEventListener' => 'events/PhutilEventListener.php', 'PhutilEventType' => 'events/constant/PhutilEventType.php', 'PhutilExampleBufferedIterator' => 'utils/PhutilExampleBufferedIterator.php', 'PhutilExcessiveServiceCallsDaemon' => 'daemon/torture/PhutilExcessiveServiceCallsDaemon.php', 'PhutilExecChannel' => 'channel/PhutilExecChannel.php', 'PhutilExecPassthru' => 'future/exec/PhutilExecPassthru.php', 'PhutilExtensionsTestCase' => 'moduleutils/__tests__/PhutilExtensionsTestCase.php', 'PhutilFacebookAuthAdapter' => 'auth/PhutilFacebookAuthAdapter.php', 'PhutilFatalDaemon' => 'daemon/torture/PhutilFatalDaemon.php', 'PhutilFileLock' => 'filesystem/PhutilFileLock.php', 'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php', 'PhutilFileTree' => 'filesystem/PhutilFileTree.php', 'PhutilGitHubAuthAdapter' => 'auth/PhutilGitHubAuthAdapter.php', 'PhutilGitURI' => 'parser/PhutilGitURI.php', 'PhutilGitURITestCase' => 'parser/__tests__/PhutilGitURITestCase.php', 'PhutilGoogleAuthAdapter' => 'auth/PhutilGoogleAuthAdapter.php', 'PhutilHangForeverDaemon' => 'daemon/torture/PhutilHangForeverDaemon.php', 'PhutilHelpArgumentWorkflow' => 'parser/argument/workflow/PhutilHelpArgumentWorkflow.php', 'PhutilHgsprintfTestCase' => 'xsprintf/__tests__/PhutilHgsprintfTestCase.php', 'PhutilInfrastructureTestCase' => '__tests__/PhutilInfrastructureTestCase.php', 'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php', 'PhutilInvisibleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php', 'PhutilJIRAAuthAdapter' => 'auth/PhutilJIRAAuthAdapter.php', 'PhutilJSON' => 'parser/PhutilJSON.php', 'PhutilJSONParser' => 'parser/PhutilJSONParser.php', 'PhutilJSONParserException' => 'parser/exception/PhutilJSONParserException.php', 'PhutilJSONParserTestCase' => 'parser/__tests__/PhutilJSONParserTestCase.php', 'PhutilJSONProtocolChannel' => 'channel/PhutilJSONProtocolChannel.php', 'PhutilJSONProtocolChannelTestCase' => 'channel/__tests__/PhutilJSONProtocolChannelTestCase.php', 'PhutilJSONTestCase' => 'parser/__tests__/PhutilJSONTestCase.php', 'PhutilJavaCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilJavaCodeSnippetContextFreeGrammar.php', 'PhutilKeyValueCache' => 'cache/PhutilKeyValueCache.php', 'PhutilKeyValueCacheAPC' => 'cache/PhutilKeyValueCacheAPC.php', 'PhutilKeyValueCacheDirectory' => 'cache/PhutilKeyValueCacheDirectory.php', 'PhutilKeyValueCacheInRequest' => 'cache/PhutilKeyValueCacheInRequest.php', 'PhutilKeyValueCacheMemcache' => 'cache/PhutilKeyValueCacheMemcache.php', 'PhutilKeyValueCacheNamespace' => 'cache/PhutilKeyValueCacheNamespace.php', 'PhutilKeyValueCacheOnDisk' => 'cache/PhutilKeyValueCacheOnDisk.php', 'PhutilKeyValueCacheProfiler' => 'cache/PhutilKeyValueCacheProfiler.php', 'PhutilKeyValueCacheProxy' => 'cache/PhutilKeyValueCacheProxy.php', 'PhutilKeyValueCacheStack' => 'cache/PhutilKeyValueCacheStack.php', 'PhutilKeyValueCacheTestCase' => 'cache/__tests__/PhutilKeyValueCacheTestCase.php', 'PhutilLDAPAuthAdapter' => 'auth/PhutilLDAPAuthAdapter.php', 'PhutilLanguageGuesser' => 'parser/PhutilLanguageGuesser.php', 'PhutilLanguageGuesserTestCase' => 'parser/__tests__/PhutilLanguageGuesserTestCase.php', 'PhutilLexer' => 'lexer/PhutilLexer.php', 'PhutilLexerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php', 'PhutilLibraryConflictException' => 'moduleutils/PhutilLibraryConflictException.php', 'PhutilLibraryMapBuilder' => 'moduleutils/PhutilLibraryMapBuilder.php', 'PhutilLipsumContextFreeGrammar' => 'grammar/PhutilLipsumContextFreeGrammar.php', 'PhutilLock' => 'filesystem/PhutilLock.php', 'PhutilLockException' => 'filesystem/PhutilLockException.php', 'PhutilLogfileChannel' => 'channel/PhutilLogfileChannel.php', 'PhutilLunarPhase' => 'utils/PhutilLunarPhase.php', 'PhutilLunarPhaseTestCase' => 'utils/__tests__/PhutilLunarPhaseTestCase.php', 'PhutilMarkupEngine' => 'markup/PhutilMarkupEngine.php', 'PhutilMarkupTestCase' => 'markup/__tests__/PhutilMarkupTestCase.php', 'PhutilMethodNotImplementedException' => 'error/PhutilMethodNotImplementedException.php', 'PhutilMetricsChannel' => 'channel/PhutilMetricsChannel.php', 'PhutilMissingSymbolException' => 'symbols/exception/PhutilMissingSymbolException.php', 'PhutilModuleUtilsTestCase' => 'moduleutils/__tests__/PhutilModuleUtilsTestCase.php', 'PhutilNiceDaemon' => 'daemon/torture/PhutilNiceDaemon.php', 'PhutilNumber' => 'internationalization/PhutilNumber.php', 'PhutilOAuth1AuthAdapter' => 'auth/PhutilOAuth1AuthAdapter.php', 'PhutilOAuth1Future' => 'future/oauth/PhutilOAuth1Future.php', 'PhutilOAuth1FutureTestCase' => 'future/oauth/__tests__/PhutilOAuth1FutureTestCase.php', 'PhutilOAuthAuthAdapter' => 'auth/PhutilOAuthAuthAdapter.php', 'PhutilOpaqueEnvelope' => 'error/PhutilOpaqueEnvelope.php', 'PhutilOpaqueEnvelopeKey' => 'error/PhutilOpaqueEnvelopeKey.php', 'PhutilOpaqueEnvelopeTestCase' => 'error/__tests__/PhutilOpaqueEnvelopeTestCase.php', 'PhutilPHPCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilPHPCodeSnippetContextFreeGrammar.php', 'PhutilPHPFragmentLexer' => 'lexer/PhutilPHPFragmentLexer.php', 'PhutilPHPFragmentLexerHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php', 'PhutilPHPFragmentLexerTestCase' => 'lexer/__tests__/PhutilPHPFragmentLexerTestCase.php', 'PhutilPHPObjectProtocolChannel' => 'channel/PhutilPHPObjectProtocolChannel.php', 'PhutilPHPObjectProtocolChannelTestCase' => 'channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php', 'PhutilPHTTestCase' => 'internationalization/__tests__/PhutilPHTTestCase.php', 'PhutilParserGenerator' => 'parser/PhutilParserGenerator.php', 'PhutilParserGeneratorException' => 'parser/generator/exception/PhutilParserGeneratorException.php', 'PhutilParserGeneratorInvalidRuleException' => 'parser/generator/exception/PhutilParserGeneratorInvalidRuleException.php', 'PhutilParserGeneratorIrreducibleRuleException' => 'parser/generator/exception/PhutilParserGeneratorIrreducibleRuleException.php', 'PhutilParserGeneratorTestCase' => 'parser/__tests__/PhutilParserGeneratorTestCase.php', 'PhutilParserGeneratorUnknownSymbolException' => 'parser/generator/exception/PhutilParserGeneratorUnknownSymbolException.php', 'PhutilParserGeneratorUnreachableRuleException' => 'parser/generator/exception/PhutilParserGeneratorUnreachableRuleException.php', 'PhutilParserGeneratorUnreachableTerminalException' => 'parser/generator/exception/PhutilParserGeneratorUnreachableTerminalException.php', 'PhutilPayPalAPIFuture' => 'future/paypal/PhutilPayPalAPIFuture.php', 'PhutilPerson' => 'internationalization/PhutilPerson.php', 'PhutilPersonTest' => 'internationalization/__tests__/PhutilPersonTest.php', 'PhutilPersonaAuthAdapter' => 'auth/PhutilPersonaAuthAdapter.php', 'PhutilPhobjectTestCase' => 'object/__tests__/PhutilPhobjectTestCase.php', 'PhutilProcessGroupDaemon' => 'daemon/torture/PhutilProcessGroupDaemon.php', 'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php', 'PhutilProxyException' => 'error/PhutilProxyException.php', 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', 'PhutilPythonFragmentLexer' => 'lexer/PhutilPythonFragmentLexer.php', 'PhutilQsprintfInterface' => 'xsprintf/PhutilQsprintfInterface.php', 'PhutilQueryStringParser' => 'parser/PhutilQueryStringParser.php', 'PhutilQueryStringParserTestCase' => 'parser/__tests__/PhutilQueryStringParserTestCase.php', 'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php', 'PhutilReadableSerializer' => 'readableserializer/PhutilReadableSerializer.php', 'PhutilReadableSerializerTestCase' => 'readableserializer/__tests__/PhutilReadableSerializerTestCase.php', 'PhutilRealnameContextFreeGrammar' => 'grammar/PhutilRealnameContextFreeGrammar.php', 'PhutilRemarkupBlockInterpreter' => 'markup/engine/remarkup/blockrule/PhutilRemarkupBlockInterpreter.php', 'PhutilRemarkupBlockStorage' => 'markup/engine/remarkup/PhutilRemarkupBlockStorage.php', 'PhutilRemarkupEngine' => 'markup/engine/PhutilRemarkupEngine.php', 'PhutilRemarkupEngineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php', 'PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php', 'PhutilRemarkupEngineRemarkupInterpreterRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php', 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php', 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php', 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php', 'PhutilRemarkupEngineRemarkupReplyBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php', 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php', 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php', 'PhutilRemarkupEngineRemarkupTestInterpreterRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTestInterpreterRule.php', 'PhutilRemarkupEngineTestCase' => 'markup/engine/__tests__/PhutilRemarkupEngineTestCase.php', 'PhutilRemarkupRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRule.php', 'PhutilRemarkupRuleBold' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleBold.php', 'PhutilRemarkupRuleDel' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDel.php', 'PhutilRemarkupRuleDocumentLink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDocumentLink.php', 'PhutilRemarkupRuleEscapeRemarkup' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeRemarkup.php', 'PhutilRemarkupRuleHyperlink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleHyperlink.php', 'PhutilRemarkupRuleItalic' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php', 'PhutilRemarkupRuleLinebreaks' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php', 'PhutilRemarkupRuleMonospace' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php', 'PhutilRemarkupRuleUnderline' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleUnderline.php', 'PhutilRope' => 'utils/PhutilRope.php', 'PhutilRopeTestCase' => 'utils/__tests__/PhutilRopeTestCase.php', 'PhutilSafeHTML' => 'markup/PhutilSafeHTML.php', 'PhutilSafeHTMLProducerInterface' => 'markup/PhutilSafeHTMLProducerInterface.php', 'PhutilSafeHTMLTestCase' => 'markup/__tests__/PhutilSafeHTMLTestCase.php', 'PhutilSaturateStdoutDaemon' => 'daemon/torture/PhutilSaturateStdoutDaemon.php', 'PhutilServiceProfiler' => 'serviceprofiler/PhutilServiceProfiler.php', 'PhutilShellLexer' => 'lexer/PhutilShellLexer.php', 'PhutilShellLexerTestCase' => 'lexer/__tests__/PhutilShellLexerTestCase.php', 'PhutilSimpleOptions' => 'parser/PhutilSimpleOptions.php', 'PhutilSimpleOptionsLexer' => 'lexer/PhutilSimpleOptionsLexer.php', 'PhutilSimpleOptionsLexerTestCase' => 'lexer/__tests__/PhutilSimpleOptionsLexerTestCase.php', 'PhutilSimpleOptionsTestCase' => 'parser/__tests__/PhutilSimpleOptionsTestCase.php', 'PhutilSocketChannel' => 'channel/PhutilSocketChannel.php', 'PhutilSprite' => 'sprites/PhutilSprite.php', 'PhutilSpriteSheet' => 'sprites/PhutilSpriteSheet.php', 'PhutilSymbolLoader' => 'symbols/PhutilSymbolLoader.php', 'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php', 'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php', 'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php', 'PhutilTestCase' => 'infrastructure/testing/PhutilTestCase.php', 'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php', 'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php', 'PhutilTranslator' => 'internationalization/PhutilTranslator.php', 'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php', 'PhutilTwitchAuthAdapter' => 'auth/PhutilTwitchAuthAdapter.php', 'PhutilTwitchFuture' => 'future/twitch/PhutilTwitchFuture.php', 'PhutilTwitterAuthAdapter' => 'auth/PhutilTwitterAuthAdapter.php', 'PhutilTypeCheckException' => 'parser/exception/PhutilTypeCheckException.php', 'PhutilTypeExtraParametersException' => 'parser/exception/PhutilTypeExtraParametersException.php', 'PhutilTypeLexer' => 'lexer/PhutilTypeLexer.php', 'PhutilTypeMissingParametersException' => 'parser/exception/PhutilTypeMissingParametersException.php', 'PhutilTypeSpec' => 'parser/PhutilTypeSpec.php', 'PhutilTypeSpecTestCase' => 'parser/__tests__/PhutilTypeSpecTestCase.php', 'PhutilURI' => 'parser/PhutilURI.php', 'PhutilURITestCase' => 'parser/__tests__/PhutilURITestCase.php', 'PhutilUTF8StringTruncator' => 'utils/PhutilUTF8StringTruncator.php', 'PhutilUTF8TestCase' => 'utils/__tests__/PhutilUTF8TestCase.php', 'PhutilUrisprintfTestCase' => 'xsprintf/__tests__/PhutilUrisprintfTestCase.php', 'PhutilUtilsTestCase' => 'utils/__tests__/PhutilUtilsTestCase.php', 'PhutilWordPressAuthAdapter' => 'auth/PhutilWordPressAuthAdapter.php', 'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php', 'PhutilXHPASTSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php', 'PhutilXHPASTSyntaxHighlighterFuture' => 'markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php', 'QueryFuture' => 'future/query/QueryFuture.php', 'TempFile' => 'filesystem/TempFile.php', 'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php', 'XHPASTNode' => 'parser/xhpast/api/XHPASTNode.php', 'XHPASTNodeTestCase' => 'parser/xhpast/api/__tests__/XHPASTNodeTestCase.php', 'XHPASTSyntaxErrorException' => 'parser/xhpast/api/XHPASTSyntaxErrorException.php', 'XHPASTToken' => 'parser/xhpast/api/XHPASTToken.php', 'XHPASTTree' => 'parser/xhpast/api/XHPASTTree.php', 'XHPASTTreeTestCase' => 'parser/xhpast/api/__tests__/XHPASTTreeTestCase.php', 'XsprintfUnknownConversionException' => 'xsprintf/exception/XsprintfUnknownConversionException.php', ), 'function' => array( 'Futures' => 'future/functions.php', '_phutil_date_format' => 'utils/viewutils.php', '_qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php', '_qsprintf_check_type' => 'xsprintf/qsprintf.php', 'array_fuse' => 'utils/utils.php', 'array_interleave' => 'utils/utils.php', 'array_mergev' => 'utils/utils.php', 'array_select_keys' => 'utils/utils.php', 'assert_instances_of' => 'utils/utils.php', 'assert_stringlike' => 'utils/utils.php', 'coalesce' => 'utils/utils.php', 'csprintf' => 'xsprintf/csprintf.php', 'exec_manual' => 'future/exec/execx.php', 'execx' => 'future/exec/execx.php', 'head' => 'utils/utils.php', 'head_key' => 'utils/utils.php', 'hgsprintf' => 'xsprintf/hgsprintf.php', 'hsprintf' => 'markup/render.php', 'id' => 'utils/utils.php', 'idx' => 'utils/utils.php', 'ifilter' => 'utils/utils.php', 'igroup' => 'utils/utils.php', 'ipull' => 'utils/utils.php', 'isort' => 'utils/utils.php', 'jsprintf' => 'xsprintf/jsprintf.php', 'last' => 'utils/utils.php', 'last_key' => 'utils/utils.php', 'ldap_sprintf' => 'xsprintf/ldapsprintf.php', 'mfilter' => 'utils/utils.php', 'mgroup' => 'utils/utils.php', 'mpull' => 'utils/utils.php', 'msort' => 'utils/utils.php', 'newv' => 'utils/utils.php', 'nonempty' => 'utils/utils.php', 'phlog' => 'error/phlog.php', 'pht' => 'internationalization/pht.php', 'phutil_censor_credentials' => 'utils/utils.php', 'phutil_console_confirm' => 'console/format.php', 'phutil_console_format' => 'console/format.php', 'phutil_console_get_terminal_width' => 'console/format.php', 'phutil_console_prompt' => 'console/format.php', 'phutil_console_require_tty' => 'console/format.php', 'phutil_console_wrap' => 'console/format.php', 'phutil_deprecated' => 'moduleutils/moduleutils.php', 'phutil_error_listener_example' => 'error/phlog.php', 'phutil_escape_html' => 'markup/render.php', 'phutil_escape_html_newlines' => 'markup/render.php', 'phutil_escape_uri' => 'markup/render.php', 'phutil_escape_uri_path_component' => 'markup/render.php', 'phutil_exit' => 'utils/utils.php', 'phutil_format_bytes' => 'utils/viewutils.php', 'phutil_format_relative_time' => 'utils/viewutils.php', 'phutil_format_relative_time_detailed' => 'utils/viewutils.php', 'phutil_format_units_generic' => 'utils/viewutils.php', 'phutil_fwrite_nonblocking_stream' => 'utils/utils.php', 'phutil_get_current_library_name' => 'moduleutils/moduleutils.php', 'phutil_get_library_name_for_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php', 'phutil_get_signal_name' => 'future/exec/execx.php', 'phutil_implode_html' => 'markup/render.php', 'phutil_is_hiphop_runtime' => 'utils/utils.php', 'phutil_is_utf8' => 'utils/utf8.php', 'phutil_is_utf8_slowly' => 'utils/utf8.php', 'phutil_is_utf8_with_only_bmp_characters' => 'utils/utf8.php', 'phutil_is_windows' => 'utils/utils.php', 'phutil_json_decode' => 'utils/utils.php', 'phutil_load_library' => 'moduleutils/core.php', 'phutil_loggable_string' => 'utils/utils.php', 'phutil_parse_bytes' => 'utils/viewutils.php', 'phutil_passthru' => 'future/exec/execx.php', 'phutil_register_library' => 'moduleutils/core.php', 'phutil_register_library_map' => 'moduleutils/core.php', 'phutil_safe_html' => 'markup/render.php', 'phutil_split_lines' => 'utils/utils.php', 'phutil_tag' => 'markup/render.php', 'phutil_tag_div' => 'markup/render.php', 'phutil_unescape_uri_path_component' => 'markup/render.php', 'phutil_units' => 'utils/utils.php', 'phutil_utf8_console_strlen' => 'utils/utf8.php', 'phutil_utf8_convert' => 'utils/utf8.php', 'phutil_utf8_hard_wrap' => 'utils/utf8.php', 'phutil_utf8_hard_wrap_html' => 'utils/utf8.php', 'phutil_utf8_is_combining_character' => 'utils/utf8.php', 'phutil_utf8_shorten' => 'utils/utf8.php', 'phutil_utf8_strlen' => 'utils/utf8.php', 'phutil_utf8_strtolower' => 'utils/utf8.php', 'phutil_utf8_strtoupper' => 'utils/utf8.php', 'phutil_utf8_strtr' => 'utils/utf8.php', 'phutil_utf8_ucwords' => 'utils/utf8.php', 'phutil_utf8ize' => 'utils/utf8.php', 'phutil_utf8v' => 'utils/utf8.php', 'phutil_utf8v_codepoints' => 'utils/utf8.php', 'phutil_utf8v_combined' => 'utils/utf8.php', 'phutil_var_export' => 'utils/utils.php', 'ppull' => 'utils/utils.php', 'qsprintf' => 'xsprintf/qsprintf.php', 'queryfx' => 'xsprintf/queryfx.php', 'queryfx_all' => 'xsprintf/queryfx.php', 'queryfx_one' => 'xsprintf/queryfx.php', 'urisprintf' => 'xsprintf/urisprintf.php', 'vcsprintf' => 'xsprintf/csprintf.php', 'vjsprintf' => 'xsprintf/jsprintf.php', 'vqsprintf' => 'xsprintf/qsprintf.php', 'vqueryfx' => 'xsprintf/queryfx.php', 'vqueryfx_all' => 'xsprintf/queryfx.php', 'vurisprintf' => 'xsprintf/urisprintf.php', 'xhp_parser_node_constants' => 'parser/xhpast/parser_nodes.php', 'xhpast_build' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_binary_path' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_build_instructions' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_parser_future' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_is_available' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_parser_token_constants' => 'parser/xhpast/parser_tokens.php', 'xsprintf' => 'xsprintf/xsprintf.php', 'xsprintf_callback_example' => 'xsprintf/xsprintf.php', 'xsprintf_command' => 'xsprintf/csprintf.php', 'xsprintf_javascript' => 'xsprintf/jsprintf.php', 'xsprintf_ldap' => 'xsprintf/ldapsprintf.php', 'xsprintf_mercurial' => 'xsprintf/hgsprintf.php', 'xsprintf_query' => 'xsprintf/qsprintf.php', 'xsprintf_uri' => 'xsprintf/urisprintf.php', ), 'xmap' => array( 'AASTNodeList' => array( 'Iterator', 'Countable', ), 'AbstractDirectedGraphTestCase' => 'PhutilTestCase', 'AphrontDatabaseConnection' => 'PhutilQsprintfInterface', 'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontMySQLDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontMySQLDatabaseConnectionBase' => 'AphrontDatabaseConnection', 'AphrontMySQLiDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryCharacterSetException' => 'AphrontQueryException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDeadlockException' => 'AphrontQueryRecoverableException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryException' => 'Exception', 'AphrontQueryNotSupportedException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontQuerySchemaException' => 'AphrontQueryException', 'AphrontWriteGuardExitEventListener' => 'PhutilEventListener', 'BaseHTTPFuture' => 'Future', 'CommandException' => 'Exception', 'ConduitClientException' => 'Exception', 'ConduitFuture' => 'FutureProxy', 'ExecFuture' => 'Future', 'ExecFutureTestCase' => 'PhutilTestCase', 'ExecPassthruTestCase' => 'PhutilTestCase', 'FileFinderTestCase' => 'PhutilTestCase', 'FilesystemException' => 'Exception', 'FilesystemTestCase' => 'PhutilTestCase', 'FutureIterator' => 'Iterator', 'FutureIteratorTestCase' => 'PhutilTestCase', 'FutureProxy' => 'Future', 'HTTPFuture' => 'BaseHTTPFuture', + 'HTTPFutureCURLesponseStatus' => 'HTTPFutureResponseStatus', + 'HTTPFutureHTTPResponseStatus' => 'HTTPFutureResponseStatus', + 'HTTPFutureParseResponseStatus' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatus' => 'Exception', - 'HTTPFutureResponseStatusCURL' => 'HTTPFutureResponseStatus', - 'HTTPFutureResponseStatusHTTP' => 'HTTPFutureResponseStatus', - 'HTTPFutureResponseStatusParse' => 'HTTPFutureResponseStatus', - 'HTTPFutureResponseStatusTransport' => 'HTTPFutureResponseStatus', + 'HTTPFutureTransportResponseStatus' => 'HTTPFutureResponseStatus', 'HTTPSFuture' => 'BaseHTTPFuture', 'ImmediateFuture' => 'Future', 'LinesOfALarge' => 'Iterator', 'LinesOfALargeExecFuture' => 'LinesOfALarge', 'LinesOfALargeExecFutureTestCase' => 'PhutilTestCase', 'LinesOfALargeFile' => 'LinesOfALarge', 'LinesOfALargeFileTestCase' => 'PhutilTestCase', 'PHPASTParserTestCase' => 'PhutilTestCase', 'PhageAgentTestCase' => 'PhutilTestCase', 'PhagePHPAgentBootloader' => 'PhageAgentBootloader', 'Phobject' => 'Iterator', 'PhutilAWSEC2Future' => 'PhutilAWSFuture', 'PhutilAWSException' => 'Exception', 'PhutilAWSFuture' => 'FutureProxy', 'PhutilAWSS3Future' => 'PhutilAWSFuture', 'PhutilAggregateException' => 'Exception', 'PhutilAmazonAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilArgumentParserException' => 'Exception', 'PhutilArgumentParserTestCase' => 'PhutilTestCase', 'PhutilArgumentSpecificationException' => 'PhutilArgumentParserException', 'PhutilArgumentSpecificationTestCase' => 'PhutilTestCase', 'PhutilArgumentUsageException' => 'PhutilArgumentParserException', 'PhutilArray' => array( 'Phobject', 'Countable', 'ArrayAccess', 'Iterator', ), 'PhutilArrayTestCase' => 'PhutilTestCase', 'PhutilArrayWithDefaultValue' => 'PhutilArray', 'PhutilAsanaAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilAsanaFuture' => 'FutureProxy', 'PhutilAuthConfigurationException' => 'PhutilAuthException', 'PhutilAuthCredentialException' => 'PhutilAuthException', 'PhutilAuthException' => 'Exception', 'PhutilAuthUserAbortedException' => 'PhutilAuthException', 'PhutilBitbucketAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilBootloaderException' => 'Exception', 'PhutilBufferedIterator' => 'Iterator', 'PhutilBufferedIteratorTestCase' => 'PhutilTestCase', 'PhutilBugtraqParserTestCase' => 'PhutilTestCase', 'PhutilCLikeCodeSnippetContextFreeGrammar' => 'PhutilCodeSnippetContextFreeGrammar', 'PhutilCallbackFilterIterator' => 'FilterIterator', 'PhutilChannelChannel' => 'PhutilChannel', 'PhutilChannelTestCase' => 'PhutilTestCase', 'PhutilChunkedIterator' => 'Iterator', 'PhutilChunkedIteratorTestCase' => 'PhutilTestCase', 'PhutilCodeSnippetContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhutilCommandString' => 'Phobject', 'PhutilConsoleProgressBar' => 'Phobject', 'PhutilConsoleServerChannel' => 'PhutilChannelChannel', 'PhutilConsoleStdinNotInteractiveException' => 'Exception', 'PhutilConsoleTable' => 'Phobject', 'PhutilConsoleWrapTestCase' => 'PhutilTestCase', 'PhutilCsprintfTestCase' => 'PhutilTestCase', 'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine', 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy', 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase', 'PhutilDeferredLogTestCase' => 'PhutilTestCase', 'PhutilDirectedScalarGraph' => 'AbstractDirectedGraph', 'PhutilDisqusAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilDocblockParserTestCase' => 'PhutilTestCase', 'PhutilEditDistanceMatrixTestCase' => 'PhutilTestCase', 'PhutilEmailAddressTestCase' => 'PhutilTestCase', 'PhutilEmptyAuthAdapter' => 'PhutilAuthAdapter', 'PhutilErrorHandlerTestCase' => 'PhutilTestCase', 'PhutilErrorTrap' => 'Phobject', 'PhutilEventType' => 'PhutilEventConstants', 'PhutilExampleBufferedIterator' => 'PhutilBufferedIterator', 'PhutilExcessiveServiceCallsDaemon' => 'PhutilTortureTestDaemon', 'PhutilExecChannel' => 'PhutilChannel', 'PhutilExecPassthru' => 'Phobject', 'PhutilExtensionsTestCase' => 'PhutilTestCase', 'PhutilFacebookAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilFatalDaemon' => 'PhutilTortureTestDaemon', 'PhutilFileLock' => 'PhutilLock', 'PhutilFileLockTestCase' => 'PhutilTestCase', 'PhutilGitHubAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilGitURITestCase' => 'PhutilTestCase', 'PhutilGoogleAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilHangForeverDaemon' => 'PhutilTortureTestDaemon', 'PhutilHelpArgumentWorkflow' => 'PhutilArgumentWorkflow', 'PhutilHgsprintfTestCase' => 'PhutilTestCase', 'PhutilInfrastructureTestCase' => 'PhutilTestCase', 'PhutilJIRAAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilJSONParserException' => 'Exception', 'PhutilJSONParserTestCase' => 'PhutilTestCase', 'PhutilJSONProtocolChannel' => 'PhutilProtocolChannel', 'PhutilJSONProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilJSONTestCase' => 'PhutilTestCase', 'PhutilJavaCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar', 'PhutilKeyValueCacheAPC' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheDirectory' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheInRequest' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheMemcache' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheNamespace' => 'PhutilKeyValueCacheProxy', 'PhutilKeyValueCacheOnDisk' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheProfiler' => 'PhutilKeyValueCacheProxy', 'PhutilKeyValueCacheProxy' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheStack' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheTestCase' => 'PhutilTestCase', 'PhutilLDAPAuthAdapter' => 'PhutilAuthAdapter', 'PhutilLanguageGuesserTestCase' => 'PhutilTestCase', 'PhutilLexerSyntaxHighlighter' => 'PhutilSyntaxHighlighter', 'PhutilLibraryConflictException' => 'Exception', 'PhutilLipsumContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhutilLockException' => 'Exception', 'PhutilLogfileChannel' => 'PhutilChannelChannel', 'PhutilLunarPhaseTestCase' => 'PhutilTestCase', 'PhutilMarkupTestCase' => 'PhutilTestCase', 'PhutilMethodNotImplementedException' => 'Exception', 'PhutilMetricsChannel' => 'PhutilChannelChannel', 'PhutilMissingSymbolException' => 'Exception', 'PhutilModuleUtilsTestCase' => 'PhutilTestCase', 'PhutilNiceDaemon' => 'PhutilTortureTestDaemon', 'PhutilOAuth1AuthAdapter' => 'PhutilAuthAdapter', 'PhutilOAuth1Future' => 'FutureProxy', 'PhutilOAuth1FutureTestCase' => 'PhutilTestCase', 'PhutilOAuthAuthAdapter' => 'PhutilAuthAdapter', 'PhutilOpaqueEnvelopeTestCase' => 'PhutilTestCase', 'PhutilPHPCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar', 'PhutilPHPFragmentLexer' => 'PhutilLexer', 'PhutilPHPFragmentLexerHighlighterTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexerTestCase' => 'PhutilTestCase', 'PhutilPHPObjectProtocolChannel' => 'PhutilProtocolChannel', 'PhutilPHPObjectProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilPHTTestCase' => 'PhutilTestCase', 'PhutilParserGeneratorException' => 'Exception', 'PhutilParserGeneratorInvalidRuleException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorIrreducibleRuleException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorTestCase' => 'PhutilTestCase', 'PhutilParserGeneratorUnknownSymbolException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorUnreachableRuleException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorUnreachableTerminalException' => 'PhutilParserGeneratorException', 'PhutilPayPalAPIFuture' => 'FutureProxy', 'PhutilPersonTest' => 'PhutilPerson', 'PhutilPersonaAuthAdapter' => 'PhutilAuthAdapter', 'PhutilPhobjectTestCase' => 'PhutilTestCase', 'PhutilProcessGroupDaemon' => 'PhutilTortureTestDaemon', 'PhutilProtocolChannel' => 'PhutilChannelChannel', 'PhutilProxyException' => 'Exception', 'PhutilPythonFragmentLexer' => 'PhutilLexer', 'PhutilQueryStringParserTestCase' => 'PhutilTestCase', 'PhutilReadableSerializerTestCase' => 'PhutilTestCase', 'PhutilRealnameContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhutilRemarkupEngine' => 'PhutilMarkupEngine', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupInterpreterRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupReplyBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupTestInterpreterRule' => 'PhutilRemarkupBlockInterpreter', 'PhutilRemarkupEngineTestCase' => 'PhutilTestCase', 'PhutilRemarkupRuleBold' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleDel' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleDocumentLink' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleEscapeRemarkup' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleHyperlink' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleItalic' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleLinebreaks' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleMonospace' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleUnderline' => 'PhutilRemarkupRule', 'PhutilRope' => 'Phobject', 'PhutilRopeTestCase' => 'PhutilTestCase', 'PhutilSafeHTMLTestCase' => 'PhutilTestCase', 'PhutilSaturateStdoutDaemon' => 'PhutilTortureTestDaemon', 'PhutilShellLexer' => 'PhutilLexer', 'PhutilShellLexerTestCase' => 'PhutilTestCase', 'PhutilSimpleOptionsLexer' => 'PhutilLexer', 'PhutilSimpleOptionsLexerTestCase' => 'PhutilTestCase', 'PhutilSimpleOptionsTestCase' => 'PhutilTestCase', 'PhutilSocketChannel' => 'PhutilChannel', 'PhutilSyntaxHighlighterException' => 'Exception', 'PhutilTestCase' => 'ArcanistPhutilTestCase', 'PhutilTestPhobject' => 'Phobject', 'PhutilTortureTestDaemon' => 'PhutilDaemon', 'PhutilTranslatorTestCase' => 'PhutilTestCase', 'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilTwitchFuture' => 'FutureProxy', 'PhutilTwitterAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilTypeCheckException' => 'Exception', 'PhutilTypeExtraParametersException' => 'Exception', 'PhutilTypeLexer' => 'PhutilLexer', 'PhutilTypeMissingParametersException' => 'Exception', 'PhutilTypeSpecTestCase' => 'PhutilTestCase', 'PhutilURITestCase' => 'PhutilTestCase', 'PhutilUTF8StringTruncator' => 'Phobject', 'PhutilUTF8TestCase' => 'PhutilTestCase', 'PhutilUrisprintfTestCase' => 'PhutilTestCase', 'PhutilUtilsTestCase' => 'PhutilTestCase', 'PhutilWordPressAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilWordPressFuture' => 'FutureProxy', 'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase', 'QueryFuture' => 'Future', 'TestAbstractDirectedGraph' => 'AbstractDirectedGraph', 'XHPASTNode' => 'AASTNode', 'XHPASTNodeTestCase' => 'PhutilTestCase', 'XHPASTSyntaxErrorException' => 'Exception', 'XHPASTToken' => 'AASTToken', 'XHPASTTree' => 'AASTTree', 'XHPASTTreeTestCase' => 'PhutilTestCase', 'XsprintfUnknownConversionException' => 'InvalidArgumentException', ), )); diff --git a/src/auth/PhutilGoogleAuthAdapter.php b/src/auth/PhutilGoogleAuthAdapter.php index afa1dab..8462730 100644 --- a/src/auth/PhutilGoogleAuthAdapter.php +++ b/src/auth/PhutilGoogleAuthAdapter.php @@ -1,170 +1,170 @@ getOAuthAccountData('emails', array()); foreach ($emails as $email) { if (idx($email, 'type') == 'account') { return idx($email, 'value'); } } throw new Exception( pht( 'Expected to retrieve an "account" email from Google Plus API call ', 'to identify account, but failed.')); } public function getAccountEmail() { return $this->getAccountID(); } public function getAccountName() { // Guess account name from email address, this is just a hint anyway. $email = $this->getAccountEmail(); $email = explode('@', $email); $email = head($email); return $email; } public function getAccountImageURI() { return $this->getOAuthAccountData('picture'); } public function getAccountURI() { return 'https://plus.google.com/'.$this->getOAuthAccountData('id'); } public function getAccountRealName() { $name = $this->getOAuthAccountData('name', array()); // TODO: This could probably be made cleaner by looking up the API, but // this should work to unbreak logins. $parts = array(); $parts[] = idx($name, 'givenName'); unset($name['givenName']); $parts[] = idx($name, 'familyName'); unset($name['familyName']); $parts = array_merge($parts, $name); $parts = array_filter($parts); return implode(' ', $parts); } protected function getAuthenticateBaseURI() { return 'https://accounts.google.com/o/oauth2/auth'; } protected function getTokenBaseURI() { return 'https://accounts.google.com/o/oauth2/token'; } public function getScope() { $scopes = array( 'email', 'profile', ); return implode(' ', $scopes); } public function getExtraAuthenticateParameters() { return array( 'response_type' => 'code', ); } public function getExtraTokenParameters() { return array( 'grant_type' => 'authorization_code', ); } protected function loadOAuthAccountData() { $uri = new PhutilURI('https://www.googleapis.com/plus/v1/people/me'); $uri->setQueryParam('access_token', $this->getAccessToken()); $future = new HTTPSFuture($uri); list($status, $body) = $future->resolve(); if ($status->isError()) { $this->tryToThrowSpecializedError($status, $body); throw $status; } $data = json_decode($body, true); if (!is_array($data)) { throw new Exception( 'Expected valid JSON response from Google account data request, '. 'got: '.$body); } return $data; } private function tryToThrowSpecializedError($status, $raw_body) { - if (!($status instanceof HTTPFutureResponseStatusHTTP)) { + if (!($status instanceof HTTPFutureHTTPResponseStatus)) { return; } if ($status->getStatusCode() != 403) { return; } $body = phutil_json_decode($raw_body); if (!$body) { return; } if (empty($body['error']['errors'][0])) { return; } $error = $body['error']['errors'][0]; $domain = idx($error, 'domain'); $reason = idx($error, 'reason'); if ($domain == 'usageLimits' && $reason == 'accessNotConfigured') { throw new PhutilAuthConfigurationException( pht( 'Google returned an "accessNotConfigured" error. This usually means '. 'you need to enable the "Google+ API" in your Google Cloud Console, '. 'under "APIs".'. "\n\n". 'Around March 2014, Google made some API changes which require this '. 'configuration adjustment.'. "\n\n". 'Normally, you can resolve this issue by going to %s, then '. 'clicking "API Project", then "APIs & auth", then turning the '. '"Google+ API" on. The names you see on the console may be '. 'different depending on how your integration is set up. If you '. 'are not sure, you can hunt through the projects until you find '. 'the one associated with the right Application ID under '. '"Credentials". The Application ID this install is using is "%s".'. "\n\n". '(If you are unable to log into Phabricator, you can use '. '"bin/auth recover" to recover access to an administrator '. 'account.)'. "\n\n". 'Full HTTP Response'. "\n\n%s", 'https://console.developers.google.com/', $this->getClientID(), $raw_body)); } } } diff --git a/src/future/aws/PhutilAWSFuture.php b/src/future/aws/PhutilAWSFuture.php index 915b904..e481c45 100644 --- a/src/future/aws/PhutilAWSFuture.php +++ b/src/future/aws/PhutilAWSFuture.php @@ -1,141 +1,141 @@ awsAccessKey = $access; $this->awsPrivateKey = $private; return $this; } public function getAWSAccessKey() { return $this->awsAccessKey; } public function getAWSPrivateKey() { return $this->awsPrivateKey; } public function getAWSRegion() { return $this->awsRegion; } public function setAWSRegion($region) { $this->awsRegion = $region; return $this; } public function getHost() { $host = $this->getServiceName().'.'.$this->awsRegion.'.amazonaws.com'; return $host; } public function setRawAWSQuery($action, array $params = array()) { $this->params = $params; $this->params['Action'] = $action; return $this; } protected function getProxiedFuture() { if (!$this->future) { $params = $this->params; if (!$this->params) { throw new Exception('You must setRawAWSQuery()!'); } if (!$this->getAWSAccessKey()) { throw new Exception('You must setAWSKeys()!'); } $params['AWSAccessKeyId'] = $this->getAWSAccessKey(); $params['Version'] = '2011-12-15'; $params['Timestamp'] = date('c'); $params = $this->sign($params); $uri = new PhutilURI('http://'.$this->getHost().'/'); $uri->setQueryParams($params); $this->future = new HTTPFuture($uri); } return $this->future; } protected function didReceiveResult($result) { list($status, $body, $headers) = $result; try { $xml = @(new SimpleXMLElement($body)); } catch (Exception $ex) { $xml = null; } if ($status->isError() || !$xml) { - if (!($status instanceof HTTPFutureResponseStatusHTTP)) { + if (!($status instanceof HTTPFutureHTTPResponseStatus)) { throw $status; } $params = array( 'body' => $body, ); if ($xml) { $params['RequestID'] = $xml->RequestID[0]; foreach ($xml->Errors[0] as $error) { $params['Errors'][] = array($error->Code, $error->Message); } } throw new PhutilAWSException($status->getStatusCode(), $params); } return $xml; } /** * http://bit.ly/wU0JFh */ private function sign(array $params) { $params['SignatureMethod'] = 'HmacSHA256'; $params['SignatureVersion'] = '2'; ksort($params); $pstr = array(); foreach ($params as $key => $value) { $pstr[] = rawurlencode($key).'='.rawurlencode($value); } $pstr = implode('&', $pstr); $sign = "GET"."\n". strtolower($this->getHost())."\n". "/"."\n". $pstr; $hash = hash_hmac( 'sha256', $sign, $this->getAWSPrivateKey(), $raw_ouput = true); $params['Signature'] = base64_encode($hash); return $params; } } diff --git a/src/future/http/BaseHTTPFuture.php b/src/future/http/BaseHTTPFuture.php index 8066ee2..c860f24 100644 --- a/src/future/http/BaseHTTPFuture.php +++ b/src/future/http/BaseHTTPFuture.php @@ -1,413 +1,413 @@ resolve(); * * This is an abstract base class which defines the API that HTTP futures * conform to. Concrete implementations are available in @{class:HTTPFuture} * and @{class:HTTPSFuture}. All futures return a tuple * when resolved; status is an object of class @{class:HTTPFutureResponseStatus} * and may represent any of a wide variety of errors in the transport layer, * a support library, or the actual HTTP exchange. * * @task create Creating a New Request * @task config Configuring the Request * @task resolve Resolving the Request * @task internal Internals */ abstract class BaseHTTPFuture extends Future { private $method = 'GET'; private $timeout = 300.0; private $headers = array(); private $uri; private $data; private $expect; /* -( Creating a New Request )--------------------------------------------- */ /** * Build a new future which will make an HTTP request to a given URI, with * some optional data payload. Since this class is abstract you can't actually * instantiate it; instead, build a new @{class:HTTPFuture} or * @{class:HTTPSFuture}. * * @param string Fully-qualified URI to send a request to. * @param mixed String or array to include in the request. Strings will be * transmitted raw; arrays will be encoded and sent as * 'application/x-www-form-urlencoded'. * @task create */ final public function __construct($uri, $data = array()) { $this->setURI((string)$uri); $this->setData($data); } /* -( Configuring the Request )-------------------------------------------- */ /** * Set a timeout for the service call. If the request hasn't resolved yet, * the future will resolve with a status that indicates the request timed * out. You can determine if a status is a timeout status by calling * isTimeout() on the status object. * * @param float Maximum timeout, in seconds. * @return this * @task config */ public function setTimeout($timeout) { $this->timeout = $timeout; return $this; } /** * Get the currently configured timeout. * * @return float Maximum number of seconds the request will execute for. * @task config */ public function getTimeout() { return $this->timeout; } /** * Select the HTTP method (e.g., "GET", "POST", "PUT") to use for the request. * By default, requests use "GET". * * @param string HTTP method name. * @return this * @task config */ final public function setMethod($method) { static $supported_methods = array( 'GET' => true, 'POST' => true, 'PUT' => true, 'DELETE' => true, ); if (empty($supported_methods[$method])) { $method_list = implode(', ', array_keys($supported_methods)); throw new Exception( "The HTTP method '{$method}' is not supported. Supported HTTP methods ". "are: {$method_list}."); } $this->method = $method; return $this; } /** * Get the HTTP method the request will use. * * @return string HTTP method name, like "GET". * @task config */ final public function getMethod() { return $this->method; } /** * Set the URI to send the request to. Note that this is also a constructor * parameter. * * @param string URI to send the request to. * @return this * @task config */ public function setURI($uri) { $this->uri = (string)$uri; return $this; } /** * Get the fully-qualified URI the request will be made to. * * @return string URI the request will be sent to. * @task config */ public function getURI() { return $this->uri; } /** * Provide data to send along with the request. Note that this is also a * constructor parameter; it may be more convenient to provide it there. Data * must be a string (in which case it will be sent raw) or an array (in which * case it will be encoded and sent as 'application/x-www-form-urlencoded'). * * @param mixed Data to send with the request. * @return this * @task config */ public function setData($data) { if (!is_string($data) && !is_array($data)) { throw new Exception('Data parameter must be an array or string.'); } $this->data = $data; return $this; } /** * Get the data which will be sent with the request. * * @return mixed Data which will be sent. * @task config */ public function getData() { return $this->data; } /** * Add an HTTP header to the request. The same header name can be specified * more than once, which will cause multiple headers to be sent. * * @param string Header name, like "Accept-Language". * @param string Header value, like "en-us". * @return this * @task config */ public function addHeader($name, $value) { $this->headers[] = array($name, $value); return $this; } /** * Get headers which will be sent with the request. Optionally, you can * provide a filter, which will return only headers with that name. For * example: * * $all_headers = $future->getHeaders(); * $just_user_agent = $future->getHeaders('User-Agent'); * * In either case, an array with all (or all matching) headers is returned. * * @param string|null Optional filter, which selects only headers with that * name if provided. * @return array List of all (or all matching) headers. * @task config */ public function getHeaders($filter = null) { $filter = strtolower($filter); $result = array(); foreach ($this->headers as $header) { list($name, $value) = $header; if (!$filter || ($filter == strtolower($name))) { $result[] = $header; } } return $result; } /** * Set the status codes that are expected in the response. * If set, isError on the status object will return true for status codes * that are not in the input array. Otherwise, isError will be true for any * HTTP status code outside the 2xx range (notwithstanding other errors such * as connection or transport issues). * * @param array|null List of expected HTTP status codes. * * @return this * @task config */ public function setExpectStatus($status_codes) { $this->expect = $status_codes; return $this; } /** * Return list of expected status codes, or null if not set. * * @return array|null List of expected status codes. */ public function getExpectStatus() { return $this->expect; } /** * Add a HTTP basic authentication header to the request. * * @param string Username to authenticate with. * @param PhutilOpaqueEnvelope Password to authenticate with. * @return this * @task config */ public function setHTTPBasicAuthCredentials( $username, PhutilOpaqueEnvelope $password) { $password_plaintext = $password->openEnvelope(); $credentials = base64_encode($username.':'.$password_plaintext); return $this->addHeader('Authorization', 'Basic '.$credentials); } /* -( Resolving the Request )---------------------------------------------- */ /** * Exception-oriented @{method:resolve}. Throws if the status indicates an * error occurred. * * @return tuple HTTP request result tuple. * @task resolve */ final public function resolvex() { $result = $this->resolve(); list($status, $body, $headers) = $result; if ($status->isError()) { throw $status; } return array($body, $headers); } /* -( Internals )---------------------------------------------------------- */ /** * Parse a raw HTTP response into a tuple. * * @param string Raw HTTP response. * @return tuple Valid resolution tuple. * @task internal */ protected function parseRawHTTPResponse($raw_response) { $rex_base = "@^(?P.*?)\r?\n\r?\n(?P.*)$@s"; $rex_head = "@^HTTP/\S+ (?P\d+) (?P.*?)". "(?:\r?\n(?P.*))?$@s"; // We need to parse one or more header blocks in case we got any // "HTTP/1.X 100 Continue" nonsense back as part of the response. This // happens with HTTPS requests, at the least. $response = $raw_response; while (true) { $matches = null; if (!preg_match($rex_base, $response, $matches)) { return $this->buildMalformedResult($raw_response); } $head = $matches['head']; $body = $matches['body']; if (!preg_match($rex_head, $head, $matches)) { return $this->buildMalformedResult($raw_response); } $response_code = (int)$matches['code']; $response_status = strtolower($matches['status']); if ($response_code == 100) { // This is HTTP/1.X 100 Continue, so this whole chunk is moot. $response = $body; } else if (($response_code == 200) && ($response_status == 'connection established')) { // When tunneling through an HTTPS proxy, we get an initial header // block like "HTTP/1.X 200 Connection established", then newlines, // then the normal response. Drop this chunk. $response = $body; } else { $headers = $this->parseHeaders(idx($matches, 'headers')); break; } } - $status = new HTTPFutureResponseStatusHTTP( + $status = new HTTPFutureHTTPResponseStatus( $response_code, $body, $headers, $this->expect); return array($status, $body, $headers); } /** * Parse an HTTP header block. * * @param string Raw HTTP headers. * @return list List of HTTP header tuples. * @task internal */ protected function parseHeaders($head_raw) { $rex_header = '@^(?P.*?):\s*(?P.*)$@'; $headers = array(); if (!$head_raw) { return $headers; } $headers_raw = preg_split("/\r?\n/", $head_raw); foreach ($headers_raw as $header) { $m = null; if (preg_match($rex_header, $header, $m)) { $headers[] = array($m['name'], $m['value']); } else { $headers[] = array($header, null); } } return $headers; } /** * Find value of the first header with given name. * * @param list List of headers from `resolve()`. * @param string Case insensitive header name. * @return string Value of the header or null if not found. * @task resolve */ public static function getHeader(array $headers, $search) { assert_instances_of($headers, 'array'); foreach ($headers as $header) { list($name, $value) = $header; if (strcasecmp($name, $search) == 0) { return $value; } } return null; } /** * Build a result tuple indicating a parse error resulting from a malformed * HTTP response. * * @return tuple Valid resolution tuple. * @task internal */ protected function buildMalformedResult($raw_response) { $body = null; $headers = array(); - $status = new HTTPFutureResponseStatusParse( - HTTPFutureResponseStatusParse::ERROR_MALFORMED_RESPONSE, + $status = new HTTPFutureParseResponseStatus( + HTTPFutureParseResponseStatus::ERROR_MALFORMED_RESPONSE, $raw_response); return array($status, $body, $headers); } } diff --git a/src/future/http/HTTPFuture.php b/src/future/http/HTTPFuture.php index a511077..6487595 100644 --- a/src/future/http/HTTPFuture.php +++ b/src/future/http/HTTPFuture.php @@ -1,294 +1,294 @@ resolvex(); * * Or * * $future = new HTTPFuture('http://www.example.com/'); * list($http_response_status_object, * $response_body, * $headers) = $future->resolve(); * * Prefer @{method:resolvex} to @{method:resolve} as the former throws - * @{class:HTTPFutureResponseStatusHTTP} on failures, which includes an + * @{class:HTTPFutureHTTPResponseStatus} on failures, which includes an * informative exception message. */ final class HTTPFuture extends BaseHTTPFuture { private $host; private $port = 80; private $fullRequestPath; private $socket; private $writeBuffer; private $response; private $stateConnected = false; private $stateWriteComplete = false; private $stateReady = false; private $stateStartTime; private $profilerCallID; public function setURI($uri) { $parts = parse_url($uri); if (!$parts) { throw new Exception("Could not parse URI '{$uri}'."); } if (empty($parts['scheme']) || $parts['scheme'] !== 'http') { throw new Exception( "URI '{$uri}' must be fully qualified with 'http://' scheme."); } if (!isset($parts['host'])) { throw new Exception( "URI '{$uri}' must be fully qualified and include host name."); } $this->host = $parts['host']; if (!empty($parts['port'])) { $this->port = $parts['port']; } if (isset($parts['user']) || isset($parts['pass'])) { throw new Exception( 'HTTP Basic Auth is not supported by HTTPFuture.'); } if (isset($parts['path'])) { $this->fullRequestPath = $parts['path']; } else { $this->fullRequestPath = '/'; } if (isset($parts['query'])) { $this->fullRequestPath .= '?'.$parts['query']; } return parent::setURI($uri); } public function __destruct() { if ($this->socket) { @fclose($this->socket); $this->socket = null; } } public function getReadSockets() { if ($this->socket) { return array($this->socket); } return array(); } public function getWriteSockets() { if (strlen($this->writeBuffer)) { return array($this->socket); } return array(); } public function isWriteComplete() { return $this->stateWriteComplete; } private function getDefaultUserAgent() { return 'HTTPFuture/1.0'; } public function isReady() { if ($this->stateReady) { return true; } if (!$this->socket) { $this->stateStartTime = microtime(true); $this->socket = $this->buildSocket(); if (!$this->socket) { return $this->stateReady; } $profiler = PhutilServiceProfiler::getInstance(); $this->profilerCallID = $profiler->beginServiceCall( array( 'type' => 'http', 'uri' => $this->getURI(), )); } if (!$this->stateConnected) { $read = array(); $write = array($this->socket); $except = array(); $select = stream_select($read, $write, $except, $tv_sec = 0); if ($write) { $this->stateConnected = true; } } if ($this->stateConnected) { if (strlen($this->writeBuffer)) { $bytes = @fwrite($this->socket, $this->writeBuffer); if ($bytes === false) { throw new Exception('Failed to write to buffer.'); } else if ($bytes) { $this->writeBuffer = substr($this->writeBuffer, $bytes); } } if (!strlen($this->writeBuffer)) { $this->stateWriteComplete = true; } while (($data = fread($this->socket, 32768)) || strlen($data)) { $this->response .= $data; } if ($data === false) { throw new Exception('Failed to read socket.'); } } return $this->checkSocket(); } private function buildSocket() { $errno = null; $errstr = null; $socket = @stream_socket_client( 'tcp://'.$this->host.':'.$this->port, $errno, $errstr, $ignored_connection_timeout = 1.0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT); if (!$socket) { $this->stateReady = true; $this->result = $this->buildErrorResult( - HTTPFutureResponseStatusTransport::ERROR_CONNECTION_FAILED); + HTTPFutureTransportResponseStatus::ERROR_CONNECTION_FAILED); return null; } $ok = stream_set_blocking($socket, 0); if (!$ok) { throw new Exception('Failed to set stream nonblocking.'); } $this->writeBuffer = $this->buildHTTPRequest(); return $socket; } private function checkSocket() { $timeout = false; $now = microtime(true); if (($now - $this->stateStartTime) > $this->getTimeout()) { $timeout = true; } if (!feof($this->socket) && !$timeout) { return false; } $this->stateReady = true; if ($timeout) { $this->result = $this->buildErrorResult( - HTTPFutureResponseStatusTransport::ERROR_TIMEOUT); + HTTPFutureTransportResponseStatus::ERROR_TIMEOUT); } else if (!$this->stateConnected) { $this->result = $this->buildErrorResult( - HTTPFutureResponseStatusTransport::ERROR_CONNECTION_REFUSED); + HTTPFutureTransportResponseStatus::ERROR_CONNECTION_REFUSED); } else if (!$this->stateWriteComplete) { $this->result = $this->buildErrorResult( - HTTPFutureResponseStatusTransport::ERROR_CONNECTION_FAILED); + HTTPFutureTransportResponseStatus::ERROR_CONNECTION_FAILED); } else { $this->result = $this->parseRawHTTPResponse($this->response); } $profiler = PhutilServiceProfiler::getInstance(); $profiler->endServiceCall($this->profilerCallID, array()); return true; } private function buildErrorResult($error) { return array( - $status = new HTTPFutureResponseStatusTransport($error, $this->getURI()), + $status = new HTTPFutureTransportResponseStatus($error, $this->getURI()), $body = null, $headers = array()); } private function buildHTTPRequest() { $data = $this->getData(); $method = $this->getMethod(); $uri = $this->fullRequestPath; $add_headers = array(); if ($this->getMethod() == 'GET') { if (is_array($data)) { $data = http_build_query($data, '', '&'); if (strpos($uri, '?') !== false) { $uri .= '&'.$data; } else { $uri .= '?'.$data; } $data = ''; } } else { if (is_array($data)) { $data = http_build_query($data, '', '&')."\r\n"; $add_headers[] = array( 'Content-Type', 'application/x-www-form-urlencoded'); } } $length = strlen($data); $add_headers[] = array( 'Content-Length', $length); if (!$this->getHeaders('User-Agent')) { $add_headers[] = array( 'User-Agent', $this->getDefaultUserAgent()); } if (!$this->getHeaders('Host')) { $add_headers[] = array( 'Host', $this->host); } $headers = array_merge($this->getHeaders(), $add_headers); foreach ($headers as $key => $header) { list($name, $value) = $header; if (strlen($value)) { $value = ': '.$value; } $headers[$key] = $name.$value."\r\n"; } return "{$method} {$uri} HTTP/1.0\r\n". implode('', $headers). "\r\n". $data; } } diff --git a/src/future/http/HTTPSFuture.php b/src/future/http/HTTPSFuture.php index 5bb5769..1369260 100644 --- a/src/future/http/HTTPSFuture.php +++ b/src/future/http/HTTPSFuture.php @@ -1,581 +1,581 @@ cabundle = $temp; return $this; } /** * Set the SSL certificate to use for this session, given a path. * * @param string The path to a valid SSL certificate for this session * @return this */ public function setCABundleFromPath($path) { $this->cabundle = $path; return $this; } /** * Get the path to the SSL certificate for this session. * * @return string|null */ public function getCABundle() { return $this->cabundle; } /** * Set whether Location headers in the response will be respected. * The default is true. * * @param boolean true to follow any Location header present in the response, * false to return the request directly * @return this */ public function setFollowLocation($follow) { $this->followLocation = $follow; return $this; } /** * Get whether Location headers in the response will be respected. * * @return boolean */ public function getFollowLocation() { return $this->followLocation; } /** * Set the fallback CA certificate if one is not specified * for the session, given a path. * * @param string The path to a valid SSL certificate * @return void */ public static function setGlobalCABundleFromPath($path) { self::$globalCABundle = $path; } /** * Set the fallback CA certificate if one is not specified * for the session, given a string. * * @param string The certificate * @return void */ public static function setGlobalCABundleFromString($certificate) { $temp = new TempFile(); Filesystem::writeFile($temp, $certificate); self::$globalCABundle = $temp; } /** * Get the fallback global CA certificate * * @return string */ public static function getGlobalCABundle() { return self::$globalCABundle; } /** * Set a list of domains to blindly trust. Certificates for these domains * will not be validated. * * @param list List of domain names to trust blindly. * @return void */ public static function setBlindlyTrustDomains(array $domains) { self::$blindTrustDomains = array_fuse($domains); } /** * Load contents of remote URI. Behaves pretty much like * `@file_get_contents($uri)` but doesn't require `allow_url_fopen`. * * @param string * @param float * @return string|false */ public static function loadContent($uri, $timeout = null) { $future = new HTTPSFuture($uri); if ($timeout !== null) { $future->setTimeout($timeout); } try { list($body) = $future->resolvex(); return $body; } catch (HTTPFutureResponseStatus $ex) { return false; } } /** * Attach a file to the request. * * @param string HTTP parameter name. * @param string File content. * @param string File name. * @param string File mime type. * @return this */ public function attachFileData($key, $data, $name, $mime_type) { if (isset($this->files[$key])) { throw new Exception( pht( 'HTTPSFuture currently supports only one file attachment for each '. 'parameter name. You are trying to attach two different files with '. 'the same parameter, "%s".', $key)); } $this->files[$key] = array( 'data' => $data, 'name' => $name, 'mime' => $mime_type, ); return $this; } public function isReady() { if (isset($this->result)) { return true; } $uri = $this->getURI(); $domain = id(new PhutilURI($uri))->getDomain(); if (!$this->handle) { $profiler = PhutilServiceProfiler::getInstance(); $this->profilerCallID = $profiler->beginServiceCall( array( 'type' => 'http', 'uri' => $uri, )); if (!self::$multi) { self::$multi = curl_multi_init(); if (!self::$multi) { throw new Exception('curl_multi_init() failed!'); } } if (!empty(self::$pool[$domain])) { $curl = array_pop(self::$pool[$domain]); } else { $curl = curl_init(); if (!$curl) { throw new Exception('curl_init() failed!'); } } $this->handle = $curl; curl_multi_add_handle(self::$multi, $curl); curl_setopt($curl, CURLOPT_URL, $uri); if (defined('CURLOPT_PROTOCOLS')) { // cURL supports a lot of protocols, and by default it will honor // redirects across protocols (for instance, from HTTP to POP3). Beyond // being very silly, this also has security implications: // // http://blog.volema.com/curl-rce.html // // Disable all protocols other than HTTP and HTTPS. $allowed_protocols = CURLPROTO_HTTPS | CURLPROTO_HTTP; curl_setopt($curl, CURLOPT_PROTOCOLS, $allowed_protocols); curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, $allowed_protocols); } $data = $this->formatRequestDataForCURL(); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); $headers = $this->getHeaders(); $saw_expect = false; for ($ii = 0; $ii < count($headers); $ii++) { list($name, $value) = $headers[$ii]; $headers[$ii] = $name.': '.$value; if (!strncasecmp($name, 'Expect', strlen('Expect'))) { $saw_expect = true; } } if (!$saw_expect) { // cURL sends an "Expect" header by default for certain requests. While // there is some reasoning behind this, it causes a practical problem // in that lighttpd servers reject these requests with a 417. Both sides // are locked in an eternal struggle (lighttpd has introduced a // 'server.reject-expect-100-with-417' option to deal with this case). // // The ostensibly correct way to suppress this behavior on the cURL side // is to add an empty "Expect:" header. If we haven't seen some other // explicit "Expect:" header, do so. // // See here, for example, although this issue is fairly widespread: // http://curl.haxx.se/mail/archive-2009-07/0008.html $headers[] = 'Expect:'; } curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); // Set the requested HTTP method, e.g. GET / POST / PUT. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->getMethod()); // Make sure we get the headers and data back. curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_WRITEFUNCTION, array($this, 'didReceiveDataCallback')); if ($this->followLocation) { curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($curl, CURLOPT_MAXREDIRS, 20); } if (defined('CURLOPT_TIMEOUT_MS')) { // If CURLOPT_TIMEOUT_MS is available, use the higher-precision timeout. $timeout = max(1, ceil(1000 * $this->getTimeout())); curl_setopt($curl, CURLOPT_TIMEOUT_MS, $timeout); } else { // Otherwise, fall back to the lower-precision timeout. $timeout = max(1, ceil($this->getTimeout())); curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); } // Try some decent fallbacks here: // - First, check if a bundle is set explicit for this request, via // `setCABundle()` or similar. // - Then, check if a global bundle is set explicitly for all requests, // via `setGlobalCABundle()` or similar. // - Then, if a local custom.pem exists, use that, because it probably // means that the user wants to override everything (also because the // user might not have access to change the box's php.ini to add // curl.cainfo). // - Otherwise, try using curl.cainfo. If it's set explicitly, it's // probably reasonable to try using it before we fall back to what // libphutil ships with. // - Lastly, try the default that libphutil ships with. If it doesn't // work, give up and yell at the user. if (!$this->getCABundle()) { $caroot = dirname(phutil_get_library_root('phutil')).'/resources/ssl/'; $ini_val = ini_get('curl.cainfo'); if (self::getGlobalCABundle()) { $this->setCABundleFromPath(self::getGlobalCABundle()); } else if (Filesystem::pathExists($caroot.'custom.pem')) { $this->setCABundleFromPath($caroot.'custom.pem'); } else if ($ini_val) { // TODO: We can probably do a pathExists() here, even. $this->setCABundleFromPath($ini_val); } else { $this->setCABundleFromPath($caroot.'default.pem'); } } curl_setopt($curl, CURLOPT_CAINFO, $this->getCABundle()); $domain = id(new PhutilURI($uri))->getDomain(); if (!empty(self::$blindTrustDomains[$domain])) { // Disable peer verification for domains that we blindly trust. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); } else { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); } curl_setopt($curl, CURLOPT_SSLVERSION, 0); } else { $curl = $this->handle; if (!self::$results) { // NOTE: In curl_multi_select(), PHP calls curl_multi_fdset() but does // not check the return value of &maxfd for -1 until recent versions // of PHP (5.4.8 and newer). cURL may return -1 as maxfd in some unusual // situations; if it does, PHP enters select() with nfds=0, which blocks // until the timeout is reached. // // We could try to guess whether this will happen or not by examining // the version identifier, but we can also just sleep for only a short // period of time. curl_multi_select(self::$multi, 0.01); } } do { $active = null; $result = curl_multi_exec(self::$multi, $active); } while ($result == CURLM_CALL_MULTI_PERFORM); while ($info = curl_multi_info_read(self::$multi)) { if ($info['msg'] == CURLMSG_DONE) { self::$results[(int)$info['handle']] = $info; } } if (!array_key_exists((int)$curl, self::$results)) { return false; } // The request is complete, so release any temporary files we wrote // earlier. $this->temporaryFiles = array(); $info = self::$results[(int)$curl]; $result = $this->responseBuffer; $err_code = $info['result']; if ($err_code) { - $status = new HTTPFutureResponseStatusCURL($err_code, $uri); + $status = new HTTPFutureCURLesponseStatus($err_code, $uri); $body = null; $headers = array(); $this->result = array($status, $body, $headers); } else { // cURL returns headers of all redirects, we strip all but the final one. $redirects = curl_getinfo($curl, CURLINFO_REDIRECT_COUNT); $result = preg_replace('/^(.*\r\n\r\n){'.$redirects.'}/sU', '', $result); $this->result = $this->parseRawHTTPResponse($result); } curl_multi_remove_handle(self::$multi, $curl); unset(self::$results[(int)$curl]); // NOTE: We want to use keepalive if possible. Return the handle to a // pool for the domain; don't close it. self::$pool[$domain][] = $curl; $profiler = PhutilServiceProfiler::getInstance(); $profiler->endServiceCall($this->profilerCallID, array()); return true; } /** * Callback invoked by cURL as it reads HTTP data from the response. We save * the data to a buffer. */ public function didReceiveDataCallback($handle, $data) { $this->responseBuffer .= $data; return strlen($data); } /** * Read data from the response buffer. * * NOTE: Like @{class:ExecFuture}, this method advances a read cursor but * does not discard the data. The data will still be buffered, and it will * all be returned when the future resolves. To discard the data after * reading it, call @{method:discardBuffers}. * * @return string Response data, if available. */ public function read() { $result = substr($this->responseBuffer, $this->responseBufferPos); $this->responseBufferPos = strlen($this->responseBuffer); return $result; } /** * Discard any buffered data. Normally, you call this after reading the * data with @{method:read}. * * @return this */ public function discardBuffers() { $this->responseBuffer = ''; $this->responseBufferPos = 0; return $this; } /** * Produces a value safe to pass to `CURLOPT_POSTFIELDS`. * * @return wild Some value, suitable for use in `CURLOPT_POSTFIELDS`. */ private function formatRequestDataForCURL() { // We're generating a value to hand to cURL as CURLOPT_POSTFIELDS. The way // cURL handles this value has some tricky caveats. // First, we can return either an array or a query string. If we return // an array, we get a "multipart/form-data" request. If we return a // query string, we get an "application/x-www-form-urlencoded" request. // Second, if we return an array we can't duplicate keys. The user might // want to send the same parameter multiple times. // Third, if we return an array and any of the values start with "@", // cURL includes arbitrary files off disk and sends them to an untrusted // remote server. For example, an array like: // // array('name' => '@/usr/local/secret') // // ...will attempt to read that file off disk and transmit its contents with // the request. This behavior is pretty surprising, and it can easily // become a relatively severe security vulnerability which allows an // attacker to read any file the HTTP process has access to. Since this // feature is very dangerous and not particularly useful, we prevent its // use. Broadly, this means we must reject some requests because they // contain an "@" in an inconvenient place. // Generally, to avoid the "@" case and because most servers usually // expect "application/x-www-form-urlencoded" data, we try to return a // string unless there are files attached to this request. $data = $this->getData(); $files = $this->files; $any_data = ($data || (is_string($data) && strlen($data))); $any_files = (bool)$this->files; if (!$any_data && !$any_files) { // No files or data, so just bail. return null; } if (!$any_files) { // If we don't have any files, just encode the data as a query string, // make sure it's not including any files, and we're good to go. if (is_array($data)) { $data = http_build_query($data, '', '&'); } $this->checkForDangerousCURLMagic($data, $is_query_string = true); return $data; } // If we've made it this far, we have some files, so we need to return // an array. First, convert the other data into an array if it isn't one // already. if (is_string($data)) { // NOTE: We explicitly don't want fancy array parsing here, so just // do a basic parse and then convert it into a dictionary ourselves. $parser = new PhutilQueryStringParser(); $pairs = $parser->parseQueryStringToPairList($data); $map = array(); foreach ($pairs as $pair) { list($key, $value) = $pair; if (array_key_exists($key, $map)) { throw new Exception( pht( 'Request specifies two values for key "%s", but parameter '. 'names must be unique if you are posting file data due to '. 'limitations with cURL.')); } $map[$key] = $value; } $data = $map; } foreach ($data as $key => $value) { $this->checkForDangerousCURLMagic($value, $is_query_string = false); } foreach ($this->files as $name => $info) { if (array_key_exists($name, $data)) { throw new Exception( pht( 'Request specifies a file with key "%s", but that key is '. 'also defined by normal request data. Due to limitations '. 'with cURL, requests that post file data must use unique '. 'keys.')); } $tmp = new TempFile($info['name']); Filesystem::writeFile($tmp, $info['data']); $this->temporaryFiles[] = $tmp; // In 5.5.0 and later, we can use CURLFile. Prior to that, we have to // use this "@" stuff. if (class_exists('CURLFile')) { $file_value = new CURLFile((string)$tmp, $info['mime'], $info['name']); } else { $file_value = '@'.(string)$tmp; } $data[$name] = $file_value; } return $data; } /** * Detect strings which will cause cURL to do horrible, insecure things. * * @param string Possibly dangerous string. * @param bool True if this string is being used as part of a query string. * @return void */ private function checkForDangerousCURLMagic($string, $is_query_string) { if (empty($string[0]) || ($string[0] != '@')) { // This isn't an "@..." string, so it's fine. return; } if ($is_query_string) { if (version_compare(phpversion(), '5.2.0', '<')) { throw new Exception( pht( 'Attempting to make an HTTP request, but query string data begins '. 'with "@". Prior to PHP 5.2.0 this reads files off disk, which '. 'creates a wide attack window for security vulnerabilities. '. 'Upgrade PHP or avoid making cURL requests which begin with "@".')); } // This is safe if we're on PHP 5.2.0 or newer. return; } throw new Exception( pht( 'Attempting to make an HTTP request which includes file data, but '. 'the value of a query parameter begins with "@". PHP interprets '. 'these values to mean that it should read arbitrary files off disk '. 'and transmit them to remote servers. Declining to make this '. 'request.')); } } diff --git a/src/future/http/status/HTTPFutureResponseStatusCURL.php b/src/future/http/status/HTTPFutureCURLResponseStatus.php similarity index 97% rename from src/future/http/status/HTTPFutureResponseStatusCURL.php rename to src/future/http/status/HTTPFutureCURLResponseStatus.php index e0e5af4..327c46f 100644 --- a/src/future/http/status/HTTPFutureResponseStatusCURL.php +++ b/src/future/http/status/HTTPFutureCURLResponseStatus.php @@ -1,83 +1,82 @@ getStatusCode() == CURLE_OPERATION_TIMEOUTED); } protected function getErrorCodeDescription($code) { $constants = get_defined_constants(); $constant_name = null; foreach ($constants as $constant => $value) { if ($value == $code && preg_match('/^CURLE_/', $constant)) { $constant_name = '<'.$constant.'> '; break; } } $map = array( CURLE_COULDNT_RESOLVE_HOST => 'There was an error resolving the server hostname. Check that you are '. 'connected to the internet and that DNS is correctly configured. (Did '. 'you add the domain to `/etc/hosts` on some other machine, but not '. 'this one?)', CURLE_SSL_CACERT => 'There was an error verifying the SSL Certificate Authority while '. 'negotiating the SSL connection. This usually indicates that you are '. 'using a self-signed certificate but have not added your CA to the '. 'CA bundle. See instructions in "libphutil/resources/ssl/README".', // Apparently there's no error constant for this? In cURL it's // CURLE_SSL_CACERT_BADFILE but there's no corresponding constant in // PHP. 77 => 'The SSL CA Bundles that we tried to use could not be read or are '. 'not formatted correctly.', CURLE_SSL_CONNECT_ERROR => 'There was an error negotiating the SSL connection. This usually '. 'indicates that the remote host has a bad SSL certificate, or your '. 'local host has some sort of SSL misconfiguration which prevents it '. 'from accepting the CA. If you are using a self-signed certificate, '. 'see instructions in "libphutil/resources/ssl/README".', CURLE_OPERATION_TIMEOUTED => 'The request took too long to complete.', CURLE_SSL_PEER_CERTIFICATE => 'There was an error verifying the SSL connection. This usually '. 'indicates that the remote host has an SSL certificate for a '. 'different domain name than you are connecting with. Make sure the '. 'certificate you have installed is signed for the correct domain.', - ); $default_message = "The cURL library raised an error while making a request. You may be ". "able to find more information about this error (error code: {$code}) ". "on the cURL site: http://curl.haxx.se/libcurl/c/libcurl-errors.html#". preg_replace('/[^A-Z]/', '', $constant_name); $detailed_message = idx($map, $code, $default_message); return $constant_name.$detailed_message; } } diff --git a/src/future/http/status/HTTPFutureResponseStatusHTTP.php b/src/future/http/status/HTTPFutureHTTPResponseStatus.php similarity index 95% rename from src/future/http/status/HTTPFutureResponseStatusHTTP.php rename to src/future/http/status/HTTPFutureHTTPResponseStatus.php index df33bda..f2ba2d9 100644 --- a/src/future/http/status/HTTPFutureResponseStatusHTTP.php +++ b/src/future/http/status/HTTPFutureHTTPResponseStatus.php @@ -1,62 +1,63 @@ 512) { $excerpt = substr($body, 0, 512).'...'; } else { $excerpt = $body; } $content_type = BaseHTTPFuture::getHeader($headers, 'Content-Type'); $match = null; if (preg_match('/;\s*charset=([^;]+)/', $content_type, $match)) { $encoding = trim($match[1], "\"'"); try { $excerpt = phutil_utf8_convert($excerpt, 'UTF-8', $encoding); } catch (Exception $ex) { } } $this->excerpt = phutil_utf8ize($excerpt); $this->expect = $expect; parent::__construct($status_code); } protected function getErrorCodeType($code) { return 'HTTP'; } public function isError() { if ($this->expect === null) { return ($this->getStatusCode() < 200) || ($this->getStatusCode() > 299); } return !in_array($this->getStatusCode(), $this->expect, true); } public function isTimeout() { return false; } protected function getErrorCodeDescription($code) { static $map = array( 404 => 'Not Found', 500 => 'Internal Server Error', ); return idx($map, $code)."\n".$this->excerpt."\n"; } } diff --git a/src/future/http/status/HTTPFutureResponseStatusParse.php b/src/future/http/status/HTTPFutureParseResponseStatus.php similarity index 89% rename from src/future/http/status/HTTPFutureResponseStatusParse.php rename to src/future/http/status/HTTPFutureParseResponseStatus.php index ec5fc00..e3305ed 100644 --- a/src/future/http/status/HTTPFutureResponseStatusParse.php +++ b/src/future/http/status/HTTPFutureParseResponseStatus.php @@ -1,30 +1,30 @@ rawResponse = $raw_response; parent::__construct($code); } protected function getErrorCodeType($code) { return 'Parse'; } public function isError() { return true; } public function isTimeout() { return false; } protected function getErrorCodeDescription($code) { return 'The remote host returned something other than an HTTP response: '. $this->rawResponse; } } diff --git a/src/future/http/status/HTTPFutureResponseStatus.php b/src/future/http/status/HTTPFutureResponseStatus.php index 4c60126..c704e4a 100644 --- a/src/future/http/status/HTTPFutureResponseStatus.php +++ b/src/future/http/status/HTTPFutureResponseStatus.php @@ -1,40 +1,39 @@ statusCode = $status_code; $this->uri = (string)$uri; $type = $this->getErrorCodeType($status_code); $description = $this->getErrorCodeDescription($status_code); $uri_info = ''; if ($this->uri) { $uri_info = ' ('.$this->uri.')'; } $message = rtrim("[{$type}/{$status_code}]{$uri_info} {$description}"); parent::__construct($message); } - final public function getStatusCode() { return $this->statusCode; } final public function getURI() { return $this->uri; } abstract public function isError(); abstract public function isTimeout(); abstract protected function getErrorCodeType($code); abstract protected function getErrorCodeDescription($code); } diff --git a/src/future/http/status/HTTPFutureResponseStatusTransport.php b/src/future/http/status/HTTPFutureTransportResponseStatus.php similarity index 95% rename from src/future/http/status/HTTPFutureResponseStatusTransport.php rename to src/future/http/status/HTTPFutureTransportResponseStatus.php index 4718cd1..b8f3206 100644 --- a/src/future/http/status/HTTPFutureResponseStatusTransport.php +++ b/src/future/http/status/HTTPFutureTransportResponseStatus.php @@ -1,43 +1,43 @@ getStatusCode() == self::ERROR_TIMEOUT); } protected function getErrorCodeDescription($code) { $map = array( self::ERROR_TIMEOUT => 'The request took too long to complete.', self::ERROR_CONNECTION_ABORTED => 'The remote host closed the connection before the request completed.', self::ERROR_CONNECTION_REFUSED => 'The remote host refused the connection. This usually means the '. 'host is not running an HTTP server, or the network is blocking '. 'connections from this machine. Verify you can connect to the '. 'remote host from this host.', self::ERROR_CONNECTION_FAILED => 'Connection could not be initiated. This usually indicates a DNS '. 'problem: verify the domain name is correct, that you can '. 'perform a DNS lookup for it from this machine. (Did you add the '. 'domain to `/etc/hosts` on some other machine, but not this one?) '. 'This might also indicate that you specified the wrong port.', ); return idx($map, $code); } }