From 3502a7f58e821807ecaba12406c7ddceb913aa1e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 17 Oct 2016 13:18:41 +0200 Subject: [PATCH] Add HTTPException for use in HTTPRequest This exception allows us to easily dump the first few bytes of the response. This can be helpful in cases where the body explains what exactly is wrong with our request. Backwards compatibility is preserved by Exception chaining. --- .../files/lib/util/HTTPRequest.class.php | 29 ++++++------ .../util/exception/HTTPException.class.php | 46 +++++++++++++++++++ 2 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 wcfsetup/install/files/lib/util/exception/HTTPException.class.php diff --git a/wcfsetup/install/files/lib/util/HTTPRequest.class.php b/wcfsetup/install/files/lib/util/HTTPRequest.class.php index 2a5cbc7e00..0587e4eda9 100644 --- a/wcfsetup/install/files/lib/util/HTTPRequest.class.php +++ b/wcfsetup/install/files/lib/util/HTTPRequest.class.php @@ -7,6 +7,7 @@ use wcf\system\exception\SystemException; use wcf\system\io\RemoteFile; use wcf\system\Regex; use wcf\system\WCF; +use wcf\util\exception\HTTPException; /** * Sends HTTP/1.1 requests. @@ -284,7 +285,7 @@ final class HTTPRequest { } $this->replyHeaders[] = $line; } - if ($this->statusCode != 200) throw new SystemException("Expected 200 Ok as reply to my CONNECT, got '".$this->statusCode."'"); + if ($this->statusCode != 200) throw new HTTPException($this, "Expected 200 Ok as reply to my CONNECT", $this->statusCode); $remoteFile->setTLS(true); } @@ -422,7 +423,7 @@ final class HTTPRequest { // get status code $statusLine = reset($this->replyHeaders); $regex = new Regex('^HTTP/1.\d+ +(\d{3})'); - if (!$regex->match($statusLine[0])) throw new SystemException("Unexpected status '".$statusLine."'"); + if (!$regex->match($statusLine[0])) throw new HTTPException($this, "Unexpected status '".$statusLine."'"); $matches = $regex->getMatches(); $this->statusCode = $matches[1]; } @@ -436,7 +437,7 @@ final class HTTPRequest { // identity transfer-coding, the Content-Length MUST be ignored. if (isset($this->replyHeaders['content-length']) && (!isset($this->replyHeaders['transfer-encoding']) || strtolower(end($this->replyHeaders['transfer-encoding'])) !== 'identity') && !isset($this->options['maxLength'])) { if (strlen($this->replyBody) != end($this->replyHeaders['content-length'])) { - throw new SystemException('Body length does not match length given in header'); + throw new HTTPException($this, 'Body length does not match length given in header'); } } @@ -447,7 +448,7 @@ final class HTTPRequest { case '303': case '307': // redirect - if ($this->options['maxDepth'] <= 0) throw new SystemException("Received status code '".$this->statusCode."' from server, but recursion level is exhausted"); + if ($this->options['maxDepth'] <= 0) throw new HTTPException($this, "Received status code '".$this->statusCode."' from server, but recursion level is exhausted", $this->statusCode); $newRequest = clone $this; $newRequest->options['maxDepth']--; @@ -465,7 +466,7 @@ final class HTTPRequest { $newRequest->setURL(end($this->replyHeaders['location'])); } catch (SystemException $e) { - throw new SystemException("Received 'Location: ".end($this->replyHeaders['location'])."' from server, which is invalid.", 0, $e); + throw new HTTPException($this, "Received 'Location: ".end($this->replyHeaders['location'])."' from server, which is invalid.", 0, $e); } try { @@ -486,21 +487,21 @@ final class HTTPRequest { case '206': // check, if partial content was expected if (!isset($this->headers['range'])) { - throw new HTTPServerErrorException("Received unexpected status code '206' from server"); + throw new HTTPServerErrorException("Received unexpected status code '206' from server", 0, '', new HTTPException($this, 'Received partial response, without sending a range header', 206)); } else if (!isset($this->replyHeaders['content-range'])) { - throw new HTTPServerErrorException("Content-Range is missing in reply header"); + throw new HTTPServerErrorException("Content-Range is missing in reply header", 0, '', new HTTPException($this, 'Server replied with 206 Partial Content, without sending a Content-Range header', 206)); } break; case '401': case '402': case '403': - throw new HTTPUnauthorizedException("Received status code '".$this->statusCode."' from server"); + throw new HTTPUnauthorizedException("Received status code '".$this->statusCode."' from server", 0, '', new HTTPException($this, "Received status code '".$this->statusCode."' from server", $this->statusCode)); break; case '404': - throw new HTTPNotFoundException("Received status code '404' from server"); + throw new HTTPNotFoundException("Received status code '404' from server", 0, '', new HTTPException($this, "Received status code '".$this->statusCode."' from server", $this->statusCode)); break; default: @@ -515,10 +516,10 @@ final class HTTPRequest { // we are fine break; case '5': // 500 and unknown 5XX - throw new HTTPServerErrorException("Received status code '".$this->statusCode."' from server"); + throw new HTTPServerErrorException("Received status code '".$this->statusCode."' from server", 0, '', new HTTPException($this, "Received status code '".$this->statusCode."' from server", $this->statusCode)); break; default: - throw new SystemException("Received unhandled status code '".$this->statusCode."' from server"); + throw new HTTPException($this, "Received unhandled status code '".$this->statusCode."' from server", $this->statusCode); break; } break; @@ -545,7 +546,7 @@ final class HTTPRequest { * Sets options and applies default values when an option is omitted. * * @param array $options - * @throws SystemException + * @throws \InvalidArgumentException */ private function setOptions(array $options) { if (!isset($options['timeout'])) { @@ -562,10 +563,10 @@ final class HTTPRequest { if (isset($options['auth'])) { if (!isset($options['auth']['username'])) { - throw new SystemException('Username is missing in authentification data.'); + throw new \InvalidArgumentException('Username is missing in authentication data.'); } if (!isset($options['auth']['password'])) { - throw new SystemException('Password is missing in authentification data.'); + throw new \InvalidArgumentException('Password is missing in authentication data.'); } } diff --git a/wcfsetup/install/files/lib/util/exception/HTTPException.class.php b/wcfsetup/install/files/lib/util/exception/HTTPException.class.php new file mode 100644 index 0000000000..c203cd04cb --- /dev/null +++ b/wcfsetup/install/files/lib/util/exception/HTTPException.class.php @@ -0,0 +1,46 @@ + + * @package WoltLabSuite\Core\Util\Exception + * @since 3.0 + */ +class HTTPException extends SystemException implements IExtraInformationException { + /** + * The HTTP request that lead to this Exception. + * + * @param HTTPRequest + */ + protected $http = null; + + /** + * @inheritDoc + */ + public function __construct(HTTPRequest $http, $message, $code = 0, $previous = null) { + parent::__construct($message, $code, '', $previous); + + $this->http = $http; + } + + /** + * @inheritDoc + */ + public function getExtraInformation() { + $reply = $this->http->getReply(); + $body = StringUtil::truncate(preg_replace('/[\x00-\x1F\x80-\xFF]/', '.', $reply['body']), 80, StringUtil::HELLIP, true); + + return [ + ['Body', $body], + ['Status Code', $reply['statusCode']] + ]; + } +} -- 2.20.1