diff --git a/src/aphront/response/AphrontResponse.php b/src/aphront/response/AphrontResponse.php index 2e6513c7a8..0eab91b2af 100644 --- a/src/aphront/response/AphrontResponse.php +++ b/src/aphront/response/AphrontResponse.php @@ -1,425 +1,434 @@ request = $request; return $this; } public function getRequest() { return $this->request; } final public function addContentSecurityPolicyURI($kind, $uri) { if ($this->contentSecurityPolicyURIs === null) { $this->contentSecurityPolicyURIs = array( 'script-src' => array(), 'connect-src' => array(), 'frame-src' => array(), 'form-action' => array(), 'object-src' => array(), ); } if (!isset($this->contentSecurityPolicyURIs[$kind])) { throw new Exception( pht( 'Unknown Content-Security-Policy URI kind "%s".', $kind)); } $this->contentSecurityPolicyURIs[$kind][] = (string)$uri; return $this; } final public function setDisableContentSecurityPolicy($disable) { $this->disableContentSecurityPolicy = $disable; return $this; } /* -( Content )------------------------------------------------------------ */ public function getContentIterator() { - return array($this->buildResponseString()); + // By default, make sure responses are truly returning a string, not some + // kind of object that behaves like a string. + + // We're going to remove the execution time limit before dumping the + // response into the sink, and want any rendering that's going to occur + // to happen BEFORE we release the limit. + + return array( + (string)$this->buildResponseString(), + ); } public function buildResponseString() { throw new PhutilMethodNotImplementedException(); } /* -( Metadata )----------------------------------------------------------- */ public function getHeaders() { $headers = array(); if (!$this->frameable) { $headers[] = array('X-Frame-Options', 'Deny'); } if ($this->getRequest() && $this->getRequest()->isHTTPS()) { $hsts_key = 'security.strict-transport-security'; $use_hsts = PhabricatorEnv::getEnvConfig($hsts_key); if ($use_hsts) { $duration = phutil_units('365 days in seconds'); } else { // If HSTS has been disabled, tell browsers to turn it off. This may // not be effective because we can only disable it over a valid HTTPS // connection, but it best represents the configured intent. $duration = 0; } $headers[] = array( 'Strict-Transport-Security', "max-age={$duration}; includeSubdomains; preload", ); } $csp = $this->newContentSecurityPolicyHeader(); if ($csp !== null) { $headers[] = array('Content-Security-Policy', $csp); } $headers[] = array('Referrer-Policy', 'no-referrer'); return $headers; } private function newContentSecurityPolicyHeader() { if ($this->disableContentSecurityPolicy) { return null; } // NOTE: We may return a response during preflight checks (for example, // if a user has a bad version of PHP). // In this case, setup isn't complete yet and we can't access environmental // configuration. If we aren't able to read the environment, just decline // to emit a Content-Security-Policy header. try { $cdn = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); $base_uri = PhabricatorEnv::getURI('/'); } catch (Exception $ex) { return null; } $csp = array(); if ($cdn) { $default = $this->newContentSecurityPolicySource($cdn); } else { // If an alternate file domain is not configured and the user is viewing // a Phame blog on a custom domain or some other custom site, we'll still // serve resources from the main site. Include the main site explicitly. $base_uri = $this->newContentSecurityPolicySource($base_uri); $default = "'self' {$base_uri}"; } $csp[] = "default-src {$default}"; // We use "data:" URIs to inline small images into CSS. This policy allows // "data:" URIs to be used anywhere, but there doesn't appear to be a way // to say that "data:" URIs are okay in CSS files but not in the document. $csp[] = "img-src {$default} data:"; // We use inline style="..." attributes in various places, many of which // are legitimate. We also currently use a