protected function loadOAuthAccountData() { $uri = new PhutilURI('https://api.amazon.com/user/profile'); $uri->setQueryParam('access_token', $this->getAccessToken()); $future = new HTTPSFuture($uri); list($body) = $future->resolvex(); try { return phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht('Expected valid JSON response from Amazon account data request.'), $ex); } }
protected function loadOAuthAccountData() { $uri = new PhutilURI('https://api.amazon.com/user/profile'); $uri->setQueryParam('access_token', $this->getAccessToken()); $future = new HTTPSFuture($uri); list($body) = $future->resolvex(); $data = json_decode($body, true); if (!is_array($data)) { throw new Exception("Expected valid JSON response from Amazon account data request, " . "got: " . $body); } return $data; }
protected function loadOAuthAccountData() { $uri = new PhutilURI('https://api.github.com/user'); $uri->setQueryParam('access_token', $this->getAccessToken()); $future = new HTTPSFuture($uri); // NOTE: GitHub requires a User-Agent string. $future->addHeader('User-Agent', __CLASS__); list($body) = $future->resolvex(); try { return phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht('Expected valid JSON response from GitHub account data request.'), $ex); } }
protected function loadOAuthAccountData() { $uri = new PhutilURI('https://api.github.com/user'); $uri->setQueryParam('access_token', $this->getAccessToken()); $future = new HTTPSFuture($uri); // NOTE: GitHub requires a User-Agent string. $future->addHeader('User-Agent', 'PhutilAuthAdapterOAuthGitHub'); list($body) = $future->resolvex(); $data = json_decode($body, true); if (!is_array($data)) { throw new Exception("Expected valid JSON response from GitHub account data request, " . "got: " . $body); } return $data; }
protected function loadOAuthAccountData() { $uri = new PhutilURI('https://disqus.com/api/3.0/users/details.json'); $uri->setQueryParam('api_key', $this->getClientID()); $uri->setQueryParam('access_token', $this->getAccessToken()); $uri = (string) $uri; $future = new HTTPSFuture($uri); $future->setMethod('GET'); list($body) = $future->resolvex(); $data = json_decode($body, true); if (!is_array($data)) { throw new Exception("Expected valid JSON response from Disqus account data request, " . "got: " . $body); } return $data['response']; }
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; } try { return phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht('Expected valid JSON response from Google account data request.'), $ex); } }
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; }
protected function loadOAuthAccountData() { $uri = new PhutilURI('https://disqus.com/api/3.0/users/details.json'); $uri->setQueryParam('api_key', $this->getClientID()); $uri->setQueryParam('access_token', $this->getAccessToken()); $uri = (string) $uri; $future = new HTTPSFuture($uri); $future->setMethod('GET'); list($body) = $future->resolvex(); try { $data = phutil_json_decode($body); return $data['response']; } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht('Expected valid JSON response from Disqus account data request.'), $ex); } }
protected function getProxiedFuture() { if (!$this->future) { $params = $this->params; if (!$this->action) { throw new Exception(pht('You must %s!', 'setRawSlackQuery()')); } if (!$this->accessToken) { throw new Exception(pht('You must %s!', 'setAccessToken()')); } $uri = new PhutilURI('https://slack.com/'); $uri->setPath('/api/' . $this->action); $uri->setQueryParam('token', $this->accessToken); $future = new HTTPSFuture($uri); $future->setData($this->params); $future->setMethod($this->method); $this->future = $future; } return $this->future; }
protected function getProxiedFuture() { if (!$this->future) { $params = $this->params; if (!$this->action) { throw new Exception(pht('You must %s!', 'setRawAsanaQuery()')); } if (!$this->accessToken) { throw new Exception(pht('You must %s!', 'setAccessToken()')); } $uri = new PhutilURI('https://app.asana.com/'); $uri->setPath('/api/1.0/' . ltrim($this->action, '/')); $future = new HTTPSFuture($uri); $future->setData($this->params); $future->addHeader('Authorization', 'Bearer ' . $this->accessToken); $future->setMethod($this->method); $this->future = $future; } return $this->future; }
protected function getProxiedFuture() { if (!$this->future) { $params = $this->params; if (!$this->action) { throw new Exception("You must setRawWordPressQuery()!"); } if (!$this->accessToken) { throw new Exception("You must setAccessToken()!"); } $uri = new PhutilURI('https://public-api.wordpress.com/'); $uri->setPath('/rest/v1/' . ltrim($this->action, '/')); $future = new HTTPSFuture($uri); $future->setData($this->params); $future->setMethod($this->method); // NOTE: This is how WordPress.com REST API authenticates $future->addHeader('Authorization', 'Bearer ' . $this->accessToken); $this->future = $future; } return $this->future; }
protected function getProxiedFuture() { if (!$this->future) { $params = $this->params; if (!$this->action) { throw new Exception(pht('You must %s!', 'setRawGitHubQuery()')); } if (!$this->accessToken) { throw new Exception(pht('You must %s!', 'setAccessToken()')); } $uri = new PhutilURI('https://api.github.com/'); $uri->setPath('/' . ltrim($this->action, '/')); $future = new HTTPSFuture($uri); $future->setData($this->params); $future->addHeader('Authorization', 'token ' . $this->accessToken); // NOTE: GitHub requires a 'User-Agent' header. $future->addHeader('User-Agent', __CLASS__); $future->setMethod($this->method); foreach ($this->headers as $header) { list($key, $value) = $header; $future->addHeader($key, $value); } $this->future = $future; } return $this->future; }
protected function run() { $argv = $this->getArgv(); if (count($argv) !== 1) { throw new Exception(pht('Usage: %s %s', __CLASS__, '<json_config_file>')); } $json_raw = Filesystem::readFile($argv[0]); try { $config = phutil_json_decode($json_raw); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht("File '%s' is not valid JSON!", $argv[0]), $ex); } $nick = idx($config, 'nick', 'phabot'); $handlers = idx($config, 'handlers', array()); $protocol_adapter_class = idx($config, 'protocol-adapter', 'PhabricatorIRCProtocolAdapter'); $this->pollFrequency = idx($config, 'poll-frequency', 1); $this->config = $config; foreach ($handlers as $handler) { $obj = newv($handler, array($this)); $this->handlers[] = $obj; } $ca_bundle = idx($config, 'https.cabundle'); if ($ca_bundle) { HTTPSFuture::setGlobalCABundleFromPath($ca_bundle); } $conduit_uri = idx($config, 'conduit.uri'); if ($conduit_uri) { $conduit_token = idx($config, 'conduit.token'); // Normalize the path component of the URI so users can enter the // domain without the "/api/" part. $conduit_uri = new PhutilURI($conduit_uri); $conduit_host = (string) $conduit_uri->setPath('/'); $conduit_uri = (string) $conduit_uri->setPath('/api/'); $conduit = new ConduitClient($conduit_uri); if ($conduit_token) { $conduit->setConduitToken($conduit_token); } else { $conduit_user = idx($config, 'conduit.user'); $conduit_cert = idx($config, 'conduit.cert'); $response = $conduit->callMethodSynchronous('conduit.connect', array('client' => __CLASS__, 'clientVersion' => '1.0', 'clientDescription' => php_uname('n') . ':' . $nick, 'host' => $conduit_host, 'user' => $conduit_user, 'certificate' => $conduit_cert)); } $this->conduit = $conduit; } // Instantiate Protocol Adapter, for now follow same technique as // handler instantiation $this->protocolAdapter = newv($protocol_adapter_class, array()); $this->protocolAdapter->setConfig($this->config)->connect(); $this->runLoop(); $this->protocolAdapter->disconnect(); }
public function callMethod($method, array $params) { $meta = array(); if ($this->sessionKey) { $meta['sessionKey'] = $this->sessionKey; } if ($this->connectionID) { $meta['connectionID'] = $this->connectionID; } if ($method == 'conduit.connect') { $certificate = idx($params, 'certificate'); if ($certificate) { $token = time(); $params['authToken'] = $token; $params['authSignature'] = sha1($token . $certificate); } unset($params['certificate']); } if ($meta) { $params['__conduit__'] = $meta; } $uri = id(clone $this->uri)->setPath('/api/' . $method); $data = array('params' => json_encode($params), 'output' => 'json', '__conduit__' => true); // Always use the cURL-based HTTPSFuture, for proxy support and other // protocol edge cases that HTTPFuture does not support. $core_future = new HTTPSFuture($uri, $data); $core_future->setMethod('POST'); $core_future->setTimeout($this->timeout); if ($this->username !== null) { $core_future->setHTTPBasicAuthCredentials($this->username, $this->password); } $conduit_future = new ConduitFuture($core_future); $conduit_future->setClient($this, $method); $conduit_future->beginProfile($data); $conduit_future->isReady(); return $conduit_future; }
protected function getProxiedFuture() { if (!$this->future) { $params = $this->params; if (!$this->action) { throw new Exception('You must setRawTwitchQuery()!'); } if (!$this->accessToken) { throw new Exception('You must setAccessToken()!'); } $uri = new PhutilURI('https://api.twitch.tv/'); $uri->setPath('/kraken/' . ltrim($this->action, '/')); $uri->setQueryParam('oauth_token', $this->accessToken); $future = new HTTPSFuture($uri); $future->setData($this->params); $future->setMethod($this->method); // NOTE: This is how the Twitch API is versioned. $future->addHeader('Accept', 'application/vnd.twitchtv.2+json'); // NOTE: This is required to avoid rate limiting. $future->addHeader('Client-ID', $this->clientID); $this->future = $future; } return $this->future; }
public function send() { $user = PhabricatorEnv::getEnvConfig('sendgrid.api-user'); $key = PhabricatorEnv::getEnvConfig('sendgrid.api-key'); if (!$user || !$key) { throw new Exception("Configure 'sendgrid.api-user' and 'sendgrid.api-key' to use " . "SendGrid for mail delivery."); } $params = array(); $ii = 0; foreach (idx($this->params, 'tos', array()) as $to) { $params['to[' . $ii++ . ']'] = $to; } $params['subject'] = idx($this->params, 'subject'); $params['text'] = idx($this->params, 'body'); if (idx($this->params, 'html-body')) { $params['html'] = idx($this->params, 'html-body'); } $params['from'] = idx($this->params, 'from'); if (idx($this->params, 'from-name')) { $params['fromname'] = $this->params['from-name']; } if (idx($this->params, 'reply-to')) { $replyto = $this->params['reply-to']; // Pick off the email part, no support for the name part in this API. $params['replyto'] = $replyto[0]['email']; } foreach (idx($this->params, 'files', array()) as $name => $data) { $params['files[' . $name . ']'] = $data; } $headers = idx($this->params, 'headers', array()); // See SendGrid Support Ticket #29390; there's no explicit REST API support // for CC right now but it works if you add a generic "Cc" header. // // SendGrid said this is supported: // "You can use CC as you are trying to do there [by adding a generic // header]. It is supported despite our limited documentation to this // effect, I am glad you were able to figure it out regardless. ..." if (idx($this->params, 'ccs')) { $headers[] = array('Cc', implode(', ', $this->params['ccs'])); } if ($headers) { // Convert to dictionary. $headers = ipull($headers, 1, 0); $headers = json_encode($headers); $params['headers'] = $headers; } $params['api_user'] = $user; $params['api_key'] = $key; $future = new HTTPSFuture('https://sendgrid.com/api/mail.send.json', $params); $future->setMethod('POST'); list($body) = $future->resolvex(); $response = json_decode($body, true); if (!is_array($response)) { throw new Exception("Failed to JSON decode response: {$body}"); } if ($response['message'] !== 'success') { $errors = implode(';', $response['errors']); throw new Exception("Request failed with errors: {$errors}."); } return true; }
/** * 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 (Status_HTTPFutureResponseStatus $ex) { return false; } }
public function send() { $key = PhabricatorEnv::getEnvConfig('mailgun.api-key'); $domain = PhabricatorEnv::getEnvConfig('mailgun.domain'); $params = array(); $params['to'] = implode(', ', idx($this->params, 'tos', array())); $params['subject'] = idx($this->params, 'subject'); $params['text'] = idx($this->params, 'body'); if (idx($this->params, 'html-body')) { $params['html'] = idx($this->params, 'html-body'); } $from = idx($this->params, 'from'); if (idx($this->params, 'from-name')) { $params['from'] = "{$this->params['from-name']} <{$from}>"; } else { $params['from'] = $from; } if (idx($this->params, 'reply-to')) { $replyto = $this->params['reply-to']; $params['h:reply-to'] = implode(', ', $replyto); } if (idx($this->params, 'ccs')) { $params['cc'] = implode(', ', $this->params['ccs']); } foreach (idx($this->params, 'headers', array()) as $header) { list($name, $value) = $header; $params['h:' . $name] = $value; } $future = new HTTPSFuture("https://*****:*****@api.mailgun.net/v2/{$domain}/messages", $params); $future->setMethod('POST'); foreach ($this->attachments as $attachment) { $future->attachFileData('attachment', $attachment['data'], $attachment['name'], $attachment['type']); } list($body) = $future->resolvex(); $response = json_decode($body, true); if (!is_array($response)) { throw new Exception("Failed to JSON decode response: {$body}"); } if (!idx($response, 'id')) { $message = $response['message']; throw new Exception("Request failed with errors: {$message}."); } return true; }
// to some degree, e.g. "arc install-certificate" does it for you. $conduit_uri = new PhutilURI($conduit_uri); $conduit_uri->setPath('/api/'); $conduit_uri = (string) $conduit_uri; } $workflow->setConduitURI($conduit_uri); // Apply global CA bundle from configs. $ca_bundle = $configuration_manager->getConfigFromAnySource('https.cabundle'); if ($ca_bundle) { $ca_bundle = Filesystem::resolvePath($ca_bundle, $working_copy->getProjectRoot()); HTTPSFuture::setGlobalCABundleFromPath($ca_bundle); } $blind_key = 'https.blindly-trust-domains'; $blind_trust = $configuration_manager->getConfigFromAnySource($blind_key); if ($blind_trust) { HTTPSFuture::setBlindlyTrustDomains($blind_trust); } if ($need_conduit) { if (!$conduit_uri) { $message = phutil_console_format("This command requires arc to connect to a Phabricator install, but " . "no Phabricator installation is configured. To configure a " . "Phabricator URI:\n\n" . " - set a default location with `arc set-config default <uri>`; or\n" . " - specify '--conduit-uri=uri' explicitly; or\n" . " - run 'arc' in a working copy with an '.arcconfig'.\n"); $message = phutil_console_wrap($message); throw new ArcanistUsageException($message); } $workflow->establishConduit(); } $hosts_config = idx($user_config, 'hosts', array()); $host_config = idx($hosts_config, $conduit_uri, array()); $user_name = idx($host_config, 'user'); $certificate = idx($host_config, 'cert'); $description = implode(' ', $original_argv); $credentials = array('user' => $user_name, 'certificate' => $certificate, 'description' => $description);
public function isReady() { if (isset($this->result)) { return true; } $uri = $this->getURI(); $data = $this->getData(); if ($data) { // NOTE: PHP's cURL implementation has a piece of magic which treats // parameters as file paths if they begin with '@'. This means that // an array like "array('name' => '@/usr/local/secret')" will attempt to // read that file off disk and send it to the remote server. 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. // // After PHP 5.2.0, it is sufficient to pass a string to avoid this // "feature" (it is only invoked in the array version). Prior to // PHP 5.2.0, we block any request which have string data beginning with // '@' (they would not work anyway). if (is_array($data)) { // Explicitly build a query string to prevent "@" security problems. $data = http_build_query($data, '', '&'); } if ($data[0] == '@' && version_compare(phpversion(), '5.2.0', '<')) { throw new Exception("Attempting to make an HTTP request including string data that " . "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 '@'."); } } else { $data = null; } $profiler = PhutilServiceProfiler::getInstance(); $this->profilerCallID = $profiler->beginServiceCall(array('type' => 'http', 'uri' => $uri)); // NOTE: If possible, we reuse the handle so we can take advantage of // keepalives. This means every option has to be set every time, because // cURL will not clear the settings between requests. if (!self::$handle) { self::$handle = curl_init(); } $curl = self::$handle; curl_setopt($curl, CURLOPT_URL, $uri); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); $headers = $this->getHeaders(); if ($headers) { for ($ii = 0; $ii < count($headers); $ii++) { list($name, $value) = $headers[$ii]; $headers[$ii] = $name . ': ' . $value; } curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } else { curl_setopt($curl, CURLOPT_HTTPHEADER, array()); } // 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_RETURNTRANSFER, true); 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); } $ini_val = ini_get('curl.cainfo'); if (!$ini_val) { $caroot = dirname(phutil_get_library_root('phutil')) . '/resources/ssl/'; if (Filesystem::pathExists($caroot . 'custom.pem')) { $cabundle = $caroot . 'custom.pem'; } else { $cabundle = $caroot . 'default.pem'; } curl_setopt($curl, CURLOPT_CAINFO, $cabundle); } curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); $result = curl_exec($curl); $err_code = curl_errno($curl); if ($err_code) { $status = new HTTPFutureResponseStatusCURL($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); } // NOTE: Don't call curl_close(), we want to use keepalive if possible. $profiler = PhutilServiceProfiler::getInstance(); $profiler->endServiceCall($this->profilerCallID, array()); return true; }
public function retrieveUserProfileImage() { $uri = $this->userData['image']; return HTTPSFuture::loadContent($uri); }
private function executeRequest($path, array $data, $method = 'GET') { $uri = new PhutilURI($this->uri); $uri->setPath($this->index); $uri->appendPath($path); $data = json_encode($data); $future = new HTTPSFuture($uri, $data); if ($method != 'GET') { $future->setMethod($method); } if ($this->getTimeout()) { $future->setTimeout($this->getTimeout()); } list($body) = $future->resolvex(); if ($method != 'GET') { return null; } try { return phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht('ElasticSearch server returned invalid JSON!'), $ex); } }
public function processRequest() { $provider = $this->provider; $auth_enabled = $provider->isProviderEnabled(); $client_id = $provider->getClientID(); $client_secret = $provider->getClientSecret(); $key = $provider->getProviderKey(); $name = phutil_escape_html($provider->getProviderName()); $res_ok = '<strong style="color: #00aa00;">OK</strong>'; $res_no = '<strong style="color: #aa0000;">NO</strong>'; $res_na = '<strong style="color: #999999;">N/A</strong>'; $results = array(); $auth_key = $key . '.auth-enabled'; if (!$auth_enabled) { $results[$auth_key] = array($res_no, 'false', $name . ' authentication is disabled in the configuration. Edit the ' . 'Phabricator configuration to enable "' . $auth_key . '".'); } else { $results[$auth_key] = array($res_ok, 'true', $name . ' authentication is enabled.'); } $client_id_key = $key . '.application-id'; if (!$client_id) { $results[$client_id_key] = array($res_no, null, 'No ' . $name . ' Application ID is configured. Edit the Phabricator ' . 'configuration to specify an application ID in ' . '"' . $client_id_key . '". ' . $provider->renderGetClientIDHelp()); } else { $results[$client_id_key] = array($res_ok, $client_id, 'Application ID is set.'); } $client_secret_key = $key . '.application-secret'; if (!$client_secret) { $results[$client_secret_key] = array($res_no, null, 'No ' . $name . ' Application secret is configured. Edit the ' . 'Phabricator configuration to specify an Application Secret, in ' . '"' . $client_secret_key . '". ' . $provider->renderGetClientSecretHelp()); } else { $results[$client_secret_key] = array($res_ok, "It's a secret!", 'Application secret is set.'); } $timeout = 5; $internet = HTTPSFuture::loadContent("http://google.com/", $timeout); if ($internet === false) { $results['internet'] = array($res_no, null, 'Unable to make an HTTP request to Google. Check your outbound ' . 'internet connection and firewall/filtering settings.'); } else { $results['internet'] = array($res_ok, null, 'Internet seems OK.'); } $test_uris = $provider->getTestURIs(); foreach ($test_uris as $uri) { $success = HTTPSFuture::loadContent($uri, $timeout); if ($success === false) { $results[$uri] = array($res_no, null, "Unable to make an HTTP request to {$uri}. {$name} may be " . 'down or inaccessible.'); } else { $results[$uri] = array($res_ok, null, 'Made a request to ' . $uri . '.'); } } if ($provider->shouldDiagnoseAppLogin()) { $test_uri = new PhutilURI($provider->getTokenURI()); $test_uri->setQueryParams(array('client_id' => $client_id, 'client_secret' => $client_secret, 'grant_type' => 'client_credentials')); $future = new HTTPSFuture($test_uri); $future->setTimeout($timeout); try { list($body) = $future->resolvex(); $results['App Login'] = array($res_ok, '(A Valid Token)', "Raw application login to {$name} works."); } catch (Exception $ex) { if ($ex instanceof HTTPFutureResponseStatusCURL) { $results['App Login'] = array($res_no, null, "Unable to perform an application login with your Application ID " . "and Application Secret. You may have mistyped or misconfigured " . "them; {$name} may have revoked your authorization; or {$name} " . "may be having technical problems."); } else { $data = json_decode($token_value, true); if (!is_array($data)) { $results['App Login'] = array($res_no, $token_value, "Application Login failed but the provider did not respond " . "with valid JSON error information. {$name} may be experiencing " . "technical problems."); } else { $results['App Login'] = array($res_no, null, "Application Login failed with error: " . $token_value); } } } } return $this->renderResults($results); }
private function assertSignature($expect, HTTPSFuture $signed) { $authorization = null; foreach ($signed->getHeaders() as $header) { list($key, $value) = $header; if (phutil_utf8_strtolower($key) === 'authorization') { $authorization = $value; break; } } $expect = str_replace("\n\n", ' ', $expect); $expect = str_replace("\n", '', $expect); $this->assertEqual($expect, $authorization); }
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->getData(); if ($data) { // NOTE: PHP's cURL implementation has a piece of magic which treats // parameters as file paths if they begin with '@'. This means that // an array like "array('name' => '@/usr/local/secret')" will attempt to // read that file off disk and send it to the remote server. 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. // // After PHP 5.2.0, it is sufficient to pass a string to avoid this // "feature" (it is only invoked in the array version). Prior to // PHP 5.2.0, we block any request which have string data beginning with // '@' (they would not work anyway). if (is_array($data)) { // Explicitly build a query string to prevent "@" security problems. $data = http_build_query($data, '', '&'); } if ($data[0] == '@' && version_compare(phpversion(), '5.2.0', '<')) { throw new Exception("Attempting to make an HTTP request including string data that " . "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 '@'."); } curl_setopt($curl, CURLOPT_POSTFIELDS, $data); } else { curl_setopt($curl, CURLOPT_POSTFIELDS, null); } $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')) { $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_RETURNTRANSFER, true); 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 cabundle path is already set (e.g. externally). // - 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 (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()); 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; } $info = self::$results[(int) $curl]; $result = curl_multi_getcontent($curl); $err_code = $info['result']; if ($err_code) { $status = new HTTPFutureResponseStatusCURL($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; }
public function isReady() { if (isset($this->result)) { return true; } $uri = $this->getURI(); $domain = id(new PhutilURI($uri))->getDomain(); if (!$this->handle) { $uri_object = new PhutilURI($uri); $proxy = PhutilHTTPEngineExtension::buildHTTPProxyURI($uri_object); $profiler = PhutilServiceProfiler::getInstance(); $this->profilerCallID = $profiler->beginServiceCall(array('type' => 'http', 'uri' => $uri, 'proxy' => (string) $proxy)); if (!self::$multi) { self::$multi = curl_multi_init(); if (!self::$multi) { throw new Exception(pht('%s failed!', 'curl_multi_init()')); } } if (!empty(self::$pool[$domain])) { $curl = array_pop(self::$pool[$domain]); } else { $curl = curl_init(); if (!$curl) { throw new Exception(pht('%s failed!', 'curl_init()')); } } $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); } if (strlen($this->rawBody)) { if ($this->getData()) { throw new Exception(pht('You can not execute an HTTP future with both a raw request ' . 'body and structured request data.')); } // We aren't actually going to use this file handle, since we are // just pushing data through the callback, but cURL gets upset if // we don't hand it a real file handle. $tmp = new TempFile(); $this->fileHandle = fopen($tmp, 'r'); // NOTE: We must set CURLOPT_PUT here to make cURL use CURLOPT_INFILE. // We'll possibly overwrite the method later on, unless this is really // a PUT request. curl_setopt($curl, CURLOPT_PUT, true); curl_setopt($curl, CURLOPT_INFILE, $this->fileHandle); curl_setopt($curl, CURLOPT_INFILESIZE, strlen($this->rawBody)); curl_setopt($curl, CURLOPT_READFUNCTION, array($this, 'willWriteBody')); } else { $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); } // We're going to try to set CAINFO below. This doesn't work at all on // OSX around Yosemite (see T5913). On these systems, we'll use the // system CA and then try to tell the user that their settings were // ignored and how to fix things if we encounter a CA-related error. // Assume we have custom CA settings to start with; we'll clear this // flag if we read the default CA info below. // Try some decent fallbacks here: // - First, check if a bundle is set explicitly 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'); } } } } if ($this->canSetCAInfo()) { curl_setopt($curl, CURLOPT_CAINFO, $this->getCABundle()); } $verify_peer = 1; $verify_host = 2; $extensions = PhutilHTTPEngineExtension::getAllExtensions(); foreach ($extensions as $extension) { if ($extension->shouldTrustAnySSLAuthorityForURI($uri_object)) { $verify_peer = 0; } if ($extension->shouldTrustAnySSLHostnameForURI($uri_object)) { $verify_host = 0; } } curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $verify_peer); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $verify_host); curl_setopt($curl, CURLOPT_SSLVERSION, 0); if ($proxy) { curl_setopt($curl, CURLOPT_PROXY, (string) $proxy); } } 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) { if ($err_code == CURLE_SSL_CACERT && !$this->canSetCAInfo()) { $status = new HTTPFutureCertificateResponseStatus(HTTPFutureCertificateResponseStatus::ERROR_IMMUTABLE_CERTIFICATES, $uri); } else { $status = new HTTPFutureCURLResponseStatus($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. if ($this->shouldReuseHandles()) { self::$pool[$domain][] = $curl; } $profiler = PhutilServiceProfiler::getInstance(); $profiler->endServiceCall($this->profilerCallID, array()); return true; }
private function refreshProfileImage(PhabricatorUserOAuthInfo $oauth_info) { $user = $this->getRequest()->getUser(); $provider = $this->provider; $error = false; $userinfo_uri = new PhutilURI($provider->getUserInfoURI()); $token = $oauth_info->getToken(); try { $userinfo_uri->setQueryParam('access_token', $token); $user_data = HTTPSFuture::loadContent($userinfo_uri); $provider->setUserData($user_data); $provider->setAccessToken($token); $image = $provider->retrieveUserProfileImage(); if ($image) { $file = PhabricatorFile::newFromFileData($image, array('name' => $provider->getProviderKey() . '-profile.jpg', 'authorPHID' => $user->getPHID())); $xformer = new PhabricatorImageTransformer(); // Resize OAuth image to a reasonable size $small_xformed = $xformer->executeProfileTransform($file, $width = 50, $min_height = 50, $max_height = 50); $user->setProfileImagePHID($small_xformed->getPHID()); $user->save(); } else { $error = 'Unable to retrieve image.'; } } catch (Exception $e) { if ($e instanceof PhabricatorOAuthProviderException) { $error = sprintf('Unable to retrieve image from %s', $provider->getProviderName()); } else { $error = 'Unable to save image.'; } } $notice = new AphrontErrorView(); if ($error) { $notice->setTitle('Error Refreshing Profile Picture')->setErrors(array($error)); } else { $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE)->setTitle('Successfully Refreshed Profile Picture'); } return $notice; }
public function retrieveUserProfileImage() { $uri = idx($this->userData, 'avatar_url'); if ($uri) { return HTTPSFuture::loadContent($uri); } return null; }
private function executeRequest($path, array $data, $is_write = false) { $uri = new PhutilURI($this->uri); $data = json_encode($data); $uri->setPath($path); $future = new HTTPSFuture($uri, $data); if ($is_write) { $future->setMethod('PUT'); } if ($this->getTimeout()) { $future->setTimeout($this->getTimeout()); } list($body) = $future->resolvex(); if ($is_write) { return null; } $body = json_decode($body, true); if (!is_array($body)) { throw new Exception("elasticsearch server returned invalid JSON!"); } return $body; }
private function makeTokenRequest(array $params) { $uri = $this->getTokenBaseURI(); $query_data = array('client_id' => $this->getClientID(), 'client_secret' => $this->getClientSecret()->openEnvelope(), 'redirect_uri' => $this->getRedirectURI()) + $params; $future = new HTTPSFuture($uri, $query_data); $future->setMethod('POST'); list($body) = $future->resolvex(); $data = $this->readAccessTokenResponse($body); if (isset($data['expires_in'])) { $data['expires_epoch'] = $data['expires_in']; } else { if (isset($data['expires'])) { $data['expires_epoch'] = $data['expires']; } } // If we got some "expires" value back, interpret it as an epoch timestamp // if it's after the year 2010 and as a relative number of seconds // otherwise. if (isset($data['expires_epoch'])) { if ($data['expires_epoch'] < 60 * 60 * 24 * 365 * 40) { $data['expires_epoch'] += time(); } } if (isset($data['error'])) { throw new Exception('Access token error: ' . $data['error']); } return $data; }