$this->setOptions($options);
// set default headers
- $this->addHeader('User-Agent', "HTTP.PHP (HTTPRequest.class.php; WoltLab Community Framework/".WCF_VERSION."; ".WCF::getLanguage()->languageCode.")");
- $this->addHeader('Accept', '*/*');
- $this->addHeader('Accept-Language', WCF::getLanguage()->getFixedLanguageCode());
+ $this->addHeader('user-agent', "HTTP.PHP (HTTPRequest.class.php; WoltLab Community Framework/".WCF_VERSION."; ".WCF::getLanguage()->languageCode.")");
+ $this->addHeader('accept', '*/*');
+ $this->addHeader('accept-language', WCF::getLanguage()->getFixedLanguageCode());
- $this->addHeader('Range', 'bytes=0-'.$this->options['maxLength']);
+ if (isset($this->options['maxLength'])) {
++ $this->addHeader('Range', 'bytes=0-'.($this->options['maxLength'] - 1));
+ }
+
if ($this->options['method'] !== 'GET') {
if (empty($this->files)) {
if (is_array($postParameters)) {
$inHeader = true;
$this->replyHeaders = array();
$this->replyBody = '';
+ $chunkLength = 0;
++ $bodyLength = 0;
// read http response.
while (!$remoteFile->eof()) {
- $line = $remoteFile->gets();
+ if ($chunkLength) {
++ if (isset($this->options['maxLength'])) $chunkLength = min($chunkLength, $this->options['maxLength'] - $bodyLength);
+ $line = $remoteFile->read($chunkLength);
+ }
+ else {
+ $line = $remoteFile->gets();
+ }
+
if ($inHeader) {
if (rtrim($line) === '') {
$inHeader = false;
$this->replyHeaders[] = $line;
}
else {
- $this->replyBody .= $line;
- $bodyLength = strlen($line);
+ $chunkedTransferRegex = new Regex('(^|,)[ \t]*chunked[ \t]*$', Regex::CASE_INSENSITIVE);
+ if (isset($this->replyHeaders['transfer-encoding']) && $chunkedTransferRegex->match(end($this->replyHeaders['transfer-encoding']))) {
+ // remove chunked from transfer-encoding
+ $this->replyHeaders['transfer-encoding'] = array_filter(array_map(function ($element) use ($chunkedTransferRegex) {
+ return $chunkedTransferRegex->replace($element, '');
+ }, $this->replyHeaders['transfer-encoding']), 'trim');
+ if (empty($this->replyHeaders['transfer-encoding'])) unset($this->replyHeaders['transfer-encoding']);
+
+ // last chunk finished
+ if ($chunkLength === 0) {
+ // read hex data and trash chunk-extension
+ list($hex) = explode(';', $line, 2);
+ $chunkLength = hexdec($hex);
+
+ // $chunkLength === 0 -> no more data
+ if ($chunkLength === 0) {
+ // clear remaining response
+ while (!$remoteFile->gets());
+
+ // break out of main reading loop
+ break;
+ }
+ }
+ else {
+ $this->replyBody .= $line;
+ $chunkLength -= strlen($line);
++ $bodyLength = +strlen($line);
+ $remoteFile->read(2); // CRLF
+ }
+ }
+ else {
+ $this->replyBody .= $line;
++ $bodyLength = +strlen($line);
++ }
+
+ if (isset($this->options['maxLength']) && $bodyLength >= $this->options['maxLength']) {
+ break;
}
}
}
++ if (isset($this->options['maxLength'])) $this->replyBody = substr($this->replyBody, 0, $this->options['maxLength']);
++
+ $remoteFile->close();
+
$this->parseReply();
}
// get status code
$statusLine = reset($this->replyHeaders);
- $regex = new Regex('^HTTP/1.[01] (\d{3})');
- if (!$regex->match($statusLine)) throw new SystemException("Unexpected status '".$statusLine."'");
+ $regex = new Regex('^HTTP/1.\d+ +(\d{3})');
+ if (!$regex->match($statusLine[0])) throw new SystemException("Unexpected status '".$statusLine."'");
$matches = $regex->getMatches();
$this->statusCode = $matches[1];
-
- // validate length
- if (isset($this->replyHeaders['Content-Length']) && !isset($this->options['maxLength'])) {
- if (strlen($this->replyBody) != $this->replyHeaders['Content-Length']) {
+ }
+
+ /**
+ * Parses the reply.
+ */
+ private function parseReply() {
+ // 4.4 Messages MUST NOT include both a Content-Length header field and a
+ // non-identity transfer-coding. If the message does include a non-
+ // 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')) {
++ 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) != $this->replyHeaders['content-length']) {
throw new SystemException('Body length does not match length given in header');
}
}
return;
break;
- case '200':
- case '204':
- // we are fine
- break;
-
+ case '206':
+ // check, if partial content was expected
- if (!isset($this->headers['Range'])) {
++ if (!isset($this->headers['range'])) {
+ throw new HTTPServerErrorException("Received unexpected status code '206' from server");
+ }
- else if (!isset($this->replyHeaders['Content-Range'])) {
++ else if (!isset($this->replyHeaders['content-range'])) {
+ throw new HTTPServerErrorException("Content-Range is missing in reply header");
+ }
+ break;
+
case '401':
case '403':
throw new HTTPUnauthorizedException("Received status code '".$this->statusCode."' from server");