public function testGetHTTPHeader() { $server_data = array('HTTP_ACCEPT_ENCODING' => 'duck/quack', 'CONTENT_TYPE' => 'cow/moo'); $this->assertEqual('duck/quack', AphrontRequest::getHTTPHeader('AcCePt-EncOdING', null, $server_data)); $this->assertEqual('cow/moo', AphrontRequest::getHTTPHeader('cONTent-TyPE', null, $server_data)); $this->assertEqual(null, AphrontRequest::getHTTPHeader('Pie-Flavor', null, $server_data)); }
public static function isProfilerRequested() { if (!empty($_REQUEST['__profile__'])) { return $_REQUEST['__profile__']; } $header = AphrontRequest::getHTTPHeader(self::getProfilerHeader()); if ($header) { return $header; } return false; }
public static function isQueryAnalyzerRequested() { if (!empty($_REQUEST['__analyze__'])) { return true; } $header = AphrontRequest::getHTTPHeader(self::getQueryAnalyzerHeader()); if ($header) { return true; } return false; }
protected function executeChecks() { $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); if (strpos(AphrontRequest::getHTTPHeader('Host'), '.') === false) { $summary = pht('The domain does not contain a dot. This is necessary for some web ' . 'browsers to be able to set cookies.'); $message = pht('The domain in the base URI must contain a dot ("."), e.g. ' . '"http://example.com", not just a bare name like "http://example/". ' . 'Some web browsers will not set cookies on domains with no TLD.'); $this->newIssue('config.phabricator.domain')->setShortName(pht('Dotless Domain'))->setName(pht('No Dot Character in Domain'))->setSummary($summary)->setMessage($message)->setIsFatal(true); } if ($base_uri) { return; } $base_uri_guess = PhabricatorEnv::getRequestBaseURI(); $summary = pht('The base URI for this install is not configured. Configuring it will ' . 'improve security and enable features.'); $message = pht('The base URI for this install is not configured. Configuring it will ' . 'improve security and allow background processes (like daemons and ' . 'scripts) to generate links.' . "\n\n" . 'You should set the base URI to the URI you will use to access ' . 'Phabricator, like "http://phabricator.example.com/".' . "\n\n" . 'Include the protocol (http or https), domain name, and port number if ' . 'you are using a port other than 80 (http) or 443 (https).' . "\n\n" . 'Based on this request, it appears that the correct setting is:' . "\n\n" . '%s' . "\n\n" . 'To configure the base URI, run the command shown below.', $base_uri_guess); $this->newIssue('config.phabricator.base-uri')->setShortName(pht('No Base URI'))->setName(pht('Base URI Not Configured'))->setSummary($summary)->setMessage($message)->addCommand(hsprintf('<tt>phabricator/ $</tt> %s', csprintf('./bin/config set phabricator.base-uri %s', $base_uri_guess))); }
public function __construct($method, array $params) { $this->method = $method; $this->handler = $this->buildMethodHandler($method); $this->servers = PhabricatorEnv::getEnvConfig('conduit.servers'); $this->forceLocal = false; $invalid_params = array_diff_key($params, $this->handler->defineParamTypes()); if ($invalid_params) { throw new ConduitException("Method '{$method}' doesn't define these parameters: '" . implode("', '", array_keys($invalid_params)) . "'."); } if ($this->servers) { $current_host = AphrontRequest::getHTTPHeader('HOST'); foreach ($this->servers as $server) { if ($current_host === id(new PhutilURI($server))->getDomain()) { $this->forceLocal = true; break; } } } $this->request = new ConduitAPIRequest($params); }
protected function executeChecks() { $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $host_header = AphrontRequest::getHTTPHeader('Host'); if (strpos($host_header, '.') === false) { if (!strlen(trim($host_header))) { $name = pht('No "Host" Header'); $summary = pht('No "Host" header present in request.'); $message = pht('This request did not include a "Host" header. This may mean that ' . 'your webserver (like nginx or apache) is misconfigured so the ' . '"Host" header is not making it to Phabricator, or that you are ' . 'making a raw request without a "Host" header using a tool or ' . 'library.' . "\n\n" . 'If you are using a web browser, check your webserver ' . 'configuration. If you are using a tool or library, check how the ' . 'request is being constructed.' . "\n\n" . 'It is also possible (but very unlikely) that some other network ' . 'device (like a load balancer) is stripping the header.' . "\n\n" . 'Requests must include a valid "Host" header.'); } else { $name = pht('Bad "Host" Header'); $summary = pht('Request has bad "Host" header.'); $message = pht('This request included an invalid "Host" header, with value "%s". ' . 'Host headers must contain a dot ("."), like "example.com". This ' . 'is required for some browsers to be able to set cookies.' . "\n\n" . 'This may mean the base URI is configured incorrectly. You must ' . 'serve Phabricator from a base URI with a dot (like ' . '"https://phabricator.mycompany.com"), not a bare domain ' . '(like "https://phabricator/"). If you are trying to use a bare ' . 'domain, change your configuration to use a full domain with a dot ' . 'in it instead.' . "\n\n" . 'This might also mean that your webserver (or some other network ' . 'device, like a load balancer) is mangling the "Host" header, or ' . 'you are using a tool or library to issue a request manually and ' . 'setting the wrong "Host" header.' . "\n\n" . 'Requests must include a valid "Host" header.', $host_header); } $this->newIssue('request.host')->setName($name)->setSummary($summary)->setMessage($message)->setIsFatal(true); } if ($base_uri) { return; } $base_uri_guess = PhabricatorEnv::getRequestBaseURI(); $summary = pht('The base URI for this install is not configured. Many major features ' . 'will not work properly until you configure it.'); $message = pht('The base URI for this install is not configured, and major features ' . 'will not work properly until you configure it.' . "\n\n" . 'You should set the base URI to the URI you will use to access ' . 'Phabricator, like "http://phabricator.example.com/".' . "\n\n" . 'Include the protocol (http or https), domain name, and port number if ' . 'you are using a port other than 80 (http) or 443 (https).' . "\n\n" . 'Based on this request, it appears that the correct setting is:' . "\n\n" . '%s' . "\n\n" . 'To configure the base URI, run the command shown below.', $base_uri_guess); $this->newIssue('config.phabricator.base-uri')->setShortName(pht('No Base URI'))->setName(pht('Base URI Not Configured'))->setSummary($summary)->setMessage($message)->addCommand(hsprintf('<tt>phabricator/ $</tt> %s', csprintf('./bin/config set phabricator.base-uri %s', $base_uri_guess))); }
protected function serveResource(array $spec) { $path = $spec['path']; $hash = idx($spec, 'hash'); // Sanity checking to keep this from exposing anything sensitive, since it // ultimately boils down to disk reads. if (preg_match('@(//|\\.\\.)@', $path)) { return new Aphront400Response(); } $type = CelerityResourceTransformer::getResourceType($path); $type_map = self::getSupportedResourceTypes(); if (empty($type_map[$type])) { throw new Exception(pht('Only static resources may be served.')); } $dev_mode = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); $map = $this->getCelerityResourceMap(); $expect_hash = $map->getHashForName($path); // Test if the URI hash is correct for our current resource map. If it // is not, refuse to cache this resource. This avoids poisoning caches // and CDNs if we're getting a request for a new resource to an old node // shortly after a push. $is_cacheable = $hash === $expect_hash; $is_locally_cacheable = $this->isLocallyCacheableResourceType($type); if (AphrontRequest::getHTTPHeader('If-Modified-Since') && $is_cacheable) { // Return a "304 Not Modified". We don't care about the value of this // field since we never change what resource is served by a given URI. return $this->makeResponseCacheable(new Aphront304Response()); } $cache = null; $data = null; if ($is_cacheable && $is_locally_cacheable && !$dev_mode) { $cache = PhabricatorCaches::getImmutableCache(); $request_path = $this->getRequest()->getPath(); $cache_key = $this->getCacheKey($request_path); $data = $cache->getKey($cache_key); } if ($data === null) { if ($map->isPackageResource($path)) { $resource_names = $map->getResourceNamesForPackageName($path); if (!$resource_names) { return new Aphront404Response(); } try { $data = array(); foreach ($resource_names as $resource_name) { $data[] = $map->getResourceDataForName($resource_name); } $data = implode("\n\n", $data); } catch (Exception $ex) { return new Aphront404Response(); } } else { try { $data = $map->getResourceDataForName($path); } catch (Exception $ex) { return new Aphront404Response(); } } $xformer = $this->buildResourceTransformer(); if ($xformer) { $data = $xformer->transformResource($path, $data); } if ($cache) { $cache->setKey($cache_key, $data); } } $response = new AphrontFileResponse(); $response->setContent($data); $response->setMimeType($type_map[$type]); // NOTE: This is a piece of magic required to make WOFF fonts work in // Firefox and IE. Possibly we should generalize this more. $cross_origin_types = array('woff' => true, 'woff2' => true, 'eot' => true); if (isset($cross_origin_types[$type])) { // We could be more tailored here, but it's not currently trivial to // generate a comprehensive list of valid origins (an install may have // arbitrarily many Phame blogs, for example), and we lose nothing by // allowing access from anywhere. $response->addAllowOrigin('*'); } if ($is_cacheable) { $response = $this->makeResponseCacheable($response); } return $response; }
protected function getBodyClasses() { $classes = array(); if (!$this->getShowChrome()) { $classes[] = 'phabricator-chromeless-page'; } $agent = AphrontRequest::getHTTPHeader('User-Agent'); // Try to guess the device resolution based on UA strings to avoid a flash // of incorrectly-styled content. $device_guess = 'device-desktop'; if (preg_match('@iPhone|iPod|(Android.*Chrome/[.0-9]* Mobile)@', $agent)) { $device_guess = 'device-phone device'; } else { if (preg_match('@iPad|(Android.*Chrome/)@', $agent)) { $device_guess = 'device-tablet device'; } } $classes[] = $device_guess; if (preg_match('@Windows@', $agent)) { $classes[] = 'platform-windows'; } else { if (preg_match('@Macintosh@', $agent)) { $classes[] = 'platform-mac'; } else { if (preg_match('@X11@', $agent)) { $classes[] = 'platform-linux'; } } } if ($this->getRequest()->getStr('__print__')) { $classes[] = 'printable'; } if ($this->getRequest()->getStr('__aural__')) { $classes[] = 'audible'; } $classes[] = 'phui-theme-' . PhabricatorEnv::getEnvConfig('ui.header-color'); foreach ($this->classes as $class) { $classes[] = $class; } return implode(' ', $classes); }
/** * @phutil-external-symbol class PhabricatorStartup */ public static function runHTTPRequest(AphrontHTTPSink $sink) { $multimeter = MultimeterControl::newInstance(); $multimeter->setEventContext('<http-init>'); $multimeter->setEventViewer('<none>'); // Build a no-op write guard for the setup phase. We'll replace this with a // real write guard later on, but we need to survive setup and build a // request object first. $write_guard = new AphrontWriteGuard('id'); PhabricatorEnv::initializeWebEnvironment(); $multimeter->setSampleRate(PhabricatorEnv::getEnvConfig('debug.sample-rate')); $debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit'); if ($debug_time_limit) { PhabricatorStartup::setDebugTimeLimit($debug_time_limit); } // This is the earliest we can get away with this, we need env config first. PhabricatorAccessLog::init(); $access_log = PhabricatorAccessLog::getLog(); PhabricatorStartup::setAccessLog($access_log); $access_log->setData(array('R' => AphrontRequest::getHTTPHeader('Referer', '-'), 'r' => idx($_SERVER, 'REMOTE_ADDR', '-'), 'M' => idx($_SERVER, 'REQUEST_METHOD', '-'))); DarkConsoleXHProfPluginAPI::hookProfiler(); DarkConsoleErrorLogPluginAPI::registerErrorHandler(); $response = PhabricatorSetupCheck::willProcessRequest(); if ($response) { PhabricatorStartup::endOutputCapture(); $sink->writeResponse($response); return; } $host = AphrontRequest::getHTTPHeader('Host'); $path = $_REQUEST['__path__']; switch ($host) { default: $config_key = 'aphront.default-application-configuration-class'; $application = PhabricatorEnv::newObjectFromConfig($config_key); break; } $application->setHost($host); $application->setPath($path); $application->willBuildRequest(); $request = $application->buildRequest(); // Now that we have a request, convert the write guard into one which // actually checks CSRF tokens. $write_guard->dispose(); $write_guard = new AphrontWriteGuard(array($request, 'validateCSRF')); // Build the server URI implied by the request headers. If an administrator // has not configured "phabricator.base-uri" yet, we'll use this to generate // links. $request_protocol = $request->isHTTPS() ? 'https' : 'http'; $request_base_uri = "{$request_protocol}://{$host}/"; PhabricatorEnv::setRequestBaseURI($request_base_uri); $access_log->setData(array('U' => (string) $request->getRequestURI()->getPath())); $processing_exception = null; try { $response = $application->processRequest($request, $access_log, $sink, $multimeter); $response_code = $response->getHTTPResponseCode(); } catch (Exception $ex) { $processing_exception = $ex; $response_code = 500; } $write_guard->dispose(); $access_log->setData(array('c' => $response_code, 'T' => PhabricatorStartup::getMicrosecondsSinceStart())); $multimeter->newEvent(MultimeterEvent::TYPE_REQUEST_TIME, $multimeter->getEventContext(), PhabricatorStartup::getMicrosecondsSinceStart()); $access_log->write(); $multimeter->saveEvents(); DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log); // Add points to the rate limits for this request. if (isset($_SERVER['REMOTE_ADDR'])) { $user_ip = $_SERVER['REMOTE_ADDR']; // The base score for a request allows users to make 30 requests per // minute. $score = 1000 / 30; // If the user was logged in, let them make more requests. if ($request->getUser() && $request->getUser()->getPHID()) { $score = $score / 5; } PhabricatorStartup::addRateLimitScore($user_ip, $score); } if ($processing_exception) { throw $processing_exception; } }
private function processAjaxRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); // We end up here if the user clicks a workflow link that they need to // login to use. We give them a dialog saying "You need to login...". if ($request->isDialogFormPost()) { return id(new AphrontRedirectResponse())->setURI($request->getRequestURI()); } // Often, users end up here by clicking a disabled action link in the UI // (for example, they might click "Edit Blocking Tasks" on a Maniphest // task page). After they log in we want to send them back to that main // object page if we can, since it's confusing to end up on a standalone // page with only a dialog (particularly if that dialog is another error, // like a policy exception). $via_header = AphrontRequest::getViaHeaderName(); $via_uri = AphrontRequest::getHTTPHeader($via_header); if (strlen($via_uri)) { PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true); } return $this->newDialog()->setTitle(pht('Login Required'))->appendParagraph(pht('You must login to take this action.'))->addSubmitButton(pht('Login'))->addCancelButton('/'); }
public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $this->phid = $request->getURIData('phid'); $this->key = $request->getURIData('key'); $this->token = $request->getURIData('token'); $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $alt_uri = new PhutilURI($alt); $alt_domain = $alt_uri->getDomain(); $req_domain = $request->getHost(); $main_domain = id(new PhutilURI($base_uri))->getDomain(); $cache_response = true; if (empty($alt) || $main_domain == $alt_domain) { // Alternate files domain isn't configured or it's set // to the same as the default domain $response = $this->loadFile($viewer); if ($response) { return $response; } $file = $this->getFile(); // when the file is not CDNable, don't allow cache $cache_response = $file->getCanCDN(); } else { if ($req_domain != $alt_domain) { // Alternate domain is configured but this request isn't using it $response = $this->loadFile($viewer); if ($response) { return $response; } $file = $this->getFile(); // if the user can see the file, generate a token; // redirect to the alt domain with the token; $token_uri = $file->getCDNURIWithToken(); $token_uri = new PhutilURI($token_uri); $token_uri = $this->addURIParameters($token_uri); return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($token_uri); } else { // We are using the alternate domain. We don't have authentication // on this domain, so we bypass policy checks when loading the file. $bypass_policies = PhabricatorUser::getOmnipotentUser(); $response = $this->loadFile($bypass_policies); if ($response) { return $response; } $file = $this->getFile(); $acquire_token_uri = id(new PhutilURI($file->getViewURI()))->setDomain($main_domain); $acquire_token_uri = $this->addURIParameters($acquire_token_uri); if ($this->token) { // validate the token, if it is valid, continue $validated_token = $file->validateOneTimeToken($this->token); if (!$validated_token) { $dialog = $this->newDialog()->setShortTitle(pht('Expired File'))->setTitle(pht('File Link Has Expired'))->appendParagraph(pht('The link you followed to view this file is invalid or ' . 'expired.'))->appendParagraph(pht('Continue to generate a new link to the file. You may be ' . 'required to log in.'))->addCancelButton($acquire_token_uri, pht('Continue')); // Build an explicit response so we can respond with HTTP/403 instead // of HTTP/200. $response = id(new AphrontDialogResponse())->setDialog($dialog)->setHTTPResponseCode(403); return $response; } // return the file data without cache headers $cache_response = false; } else { if (!$file->getCanCDN()) { // file cannot be served via cdn, and no token given // redirect to the main domain to aquire a token // This is marked as an "external" URI because it is fully qualified. return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($acquire_token_uri); } } } } $response = new AphrontFileResponse(); if ($cache_response) { $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); } $begin = null; $end = null; // NOTE: It's important to accept "Range" requests when playing audio. // If we don't, Safari has difficulty figuring out how long sounds are // and glitches when trying to loop them. In particular, Safari sends // an initial request for bytes 0-1 of the audio file, and things go south // if we can't respond with a 206 Partial Content. $range = $request->getHTTPHeader('range'); if ($range) { $matches = null; if (preg_match('/^bytes=(\\d+)-(\\d+)$/', $range, $matches)) { // Note that the "Range" header specifies bytes differently than // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1). $begin = (int) $matches[1]; $end = (int) $matches[2] + 1; $response->setHTTPResponseCode(206); $response->setRange($begin, $end - 1); } } else { if (isset($validated_token)) { // We set this on the response, and the response deletes it after the // transfer completes. This allows transfers to be resumed, in theory. $response->setTemporaryFileToken($validated_token); } } $is_viewable = $file->isViewableInBrowser(); $force_download = $request->getExists('download'); if ($is_viewable && !$force_download) { $response->setMimeType($file->getViewableMimeType()); } else { if (!$request->isHTTPPost() && !$alt_domain) { // NOTE: Require POST to download files from the primary domain. We'd // rather go full-bore and do a real CSRF check, but can't currently // authenticate users on the file domain. This should blunt any // attacks based on iframes, script tags, applet tags, etc., at least. // Send the user to the "info" page if they're using some other method. // This is marked as "external" because it is fully qualified. return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); } $response->setMimeType($file->getMimeType()); $response->setDownload($file->getName()); } $iterator = $file->getFileDataIterator($begin, $end); $response->setContentLength($file->getByteSize()); $response->setContentIterator($iterator); return $response; }
public function save() { $this->details['host'] = php_uname('n'); $this->details['user_agent'] = AphrontRequest::getHTTPHeader('User-Agent'); return parent::save(); }
$show_unexpected_traces = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); // This is the earliest we can get away with this, we need env config first. PhabricatorAccessLog::init(); $access_log = PhabricatorAccessLog::getLog(); PhabricatorStartup::setGlobal('log.access', $access_log); $access_log->setData(array('R' => AphrontRequest::getHTTPHeader('Referer', '-'), 'r' => idx($_SERVER, 'REMOTE_ADDR', '-'), 'M' => idx($_SERVER, 'REQUEST_METHOD', '-'))); DarkConsoleXHProfPluginAPI::hookProfiler(); DarkConsoleErrorLogPluginAPI::registerErrorHandler(); $sink = new AphrontPHPHTTPSink(); $response = PhabricatorSetupCheck::willProcessRequest(); if ($response) { PhabricatorStartup::endOutputCapture(); $sink->writeResponse($response); return; } $host = AphrontRequest::getHTTPHeader('Host'); $path = $_REQUEST['__path__']; switch ($host) { default: $config_key = 'aphront.default-application-configuration-class'; $application = PhabricatorEnv::newObjectFromConfig($config_key); break; } $application->setHost($host); $application->setPath($path); $application->willBuildRequest(); $request = $application->buildRequest(); // Until an administrator sets "phabricator.base-uri", assume it is the same // as the request URI. This will work fine in most cases, it just breaks down // when daemons need to do things. $request_protocol = $request->isHTTPS() ? 'https' : 'http';
public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $this->phid = $request->getURIData('phid'); $this->key = $request->getURIData('key'); $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $alt_uri = new PhutilURI($alt); $alt_domain = $alt_uri->getDomain(); $req_domain = $request->getHost(); $main_domain = id(new PhutilURI($base_uri))->getDomain(); if (!strlen($alt) || $main_domain == $alt_domain) { // No alternate domain. $should_redirect = false; $is_alternate_domain = false; } else { if ($req_domain != $alt_domain) { // Alternate domain, but this request is on the main domain. $should_redirect = true; $is_alternate_domain = false; } else { // Alternate domain, and on the alternate domain. $should_redirect = false; $is_alternate_domain = true; } } $response = $this->loadFile(); if ($response) { return $response; } $file = $this->getFile(); if ($should_redirect) { return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($file->getCDNURI()); } $response = new AphrontFileResponse(); $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); $response->setCanCDN($file->getCanCDN()); $begin = null; $end = null; // NOTE: It's important to accept "Range" requests when playing audio. // If we don't, Safari has difficulty figuring out how long sounds are // and glitches when trying to loop them. In particular, Safari sends // an initial request for bytes 0-1 of the audio file, and things go south // if we can't respond with a 206 Partial Content. $range = $request->getHTTPHeader('range'); if ($range) { $matches = null; if (preg_match('/^bytes=(\\d+)-(\\d+)$/', $range, $matches)) { // Note that the "Range" header specifies bytes differently than // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1). $begin = (int) $matches[1]; $end = (int) $matches[2] + 1; $response->setHTTPResponseCode(206); $response->setRange($begin, $end - 1); } } $is_viewable = $file->isViewableInBrowser(); $force_download = $request->getExists('download'); $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); $is_lfs = $request_type == 'git-lfs'; if ($is_viewable && !$force_download) { $response->setMimeType($file->getViewableMimeType()); } else { if (!$request->isHTTPPost() && !$is_alternate_domain && !$is_lfs) { // NOTE: Require POST to download files from the primary domain. We'd // rather go full-bore and do a real CSRF check, but can't currently // authenticate users on the file domain. This should blunt any // attacks based on iframes, script tags, applet tags, etc., at least. // Send the user to the "info" page if they're using some other method. // This is marked as "external" because it is fully qualified. return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); } $response->setMimeType($file->getMimeType()); $response->setDownload($file->getName()); } $iterator = $file->getFileDataIterator($begin, $end); $response->setContentLength($file->getByteSize()); $response->setContentIterator($iterator); return $response; }