diff --git a/src/aphront/response/AphrontResponse.php b/src/aphront/response/AphrontResponse.php --- a/src/aphront/response/AphrontResponse.php +++ b/src/aphront/response/AphrontResponse.php @@ -54,7 +54,16 @@ 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() { diff --git a/src/aphront/sink/AphrontHTTPSink.php b/src/aphront/sink/AphrontHTTPSink.php --- a/src/aphront/sink/AphrontHTTPSink.php +++ b/src/aphront/sink/AphrontHTTPSink.php @@ -112,6 +112,23 @@ $response->getHTTPResponseMessage()); $this->writeHeaders($all_headers); + // Allow clients an unlimited amount of time to download the response. + + // This allows clients to perform a "slow loris" attack, where they + // download a large response very slowly to tie up process slots. However, + // concurrent connection limits and "RequestReadTimeout" already prevent + // this attack. We could add our own minimum download rate here if we want + // to make this easier to configure eventually. + + // For normal page responses, we've fully rendered the page into a string + // already so all that's left is writing it to the client. + + // For unusual responses (like large file downloads) we may still be doing + // some meaningful work, but in theory that work is intrinsic to streaming + // the response. + + set_time_limit(0); + $abort = false; foreach ($data as $block) { if (!$this->isWritable()) {