* It supports POST, SSL, Basic Auth etc.
*
* @author Tim Duesterhus
- * @copyright 2001-2014 WoltLab GmbH
+ * @copyright 2001-2015 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
* @subpackage util
*/
private $useSSL = false;
+ /**
+ * indicates if the connection to the proxy target will be made via SSL
+ * @var boolean
+ */
+ private $originUseSSL = false;
+
/**
* target host
* @var string
*/
private $host;
+ /**
+ * target host if a proxy is used
+ * @var string
+ */
+ private $originHost;
+
/**
* target port
* @var integer
*/
private $port;
+ /**
+ * target port if a proxy is used
+ * @var integer
+ */
+ private $originPort;
+
/**
* target path
* @var string
if (isset($this->options['auth'])) {
$this->addHeader('authorization', "Basic ".base64_encode($options['auth']['username'].":".$options['auth']['password']));
}
- $this->addHeader('host', $this->host.($this->port != ($this->useSSL ? 443 : 80) ? ':'.$this->port : ''));
$this->addHeader('connection', 'Close');
}
* @param string $url
*/
private function setURL($url) {
- if (PROXY_SERVER_HTTP) {
- $parsedUrl = parse_url(PROXY_SERVER_HTTP);
+ $parsedUrl = $originUrl = parse_url($url);
+
+ $this->originUseSSL = $originUrl['scheme'] === 'https';
+ $this->originHost = $originUrl['host'];
+ $this->originPort = isset($originUrl['port']) ? $originUrl['port'] : ($this->originUseSSL ? 443 : 80);
+
+ if (PROXY_SERVER_HTTP && !$this->originUseSSL) {
$this->path = $url;
}
else {
- $parsedUrl = parse_url($url);
$this->path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '/';
}
+ if (PROXY_SERVER_HTTP) {
+ $parsedUrl = parse_url(PROXY_SERVER_HTTP);
+ }
+
$this->useSSL = $parsedUrl['scheme'] === 'https';
$this->host = $parsedUrl['host'];
$this->port = isset($parsedUrl['port']) ? $parsedUrl['port'] : ($this->useSSL ? 443 : 80);
$this->query = isset($parsedUrl['query']) ? $parsedUrl['query'] : '';
// update the 'Host:' header if URL has changed
- if (!empty($this->url) && $this->url != $url) {
- $this->addHeader('host', $this->host.($this->port != ($this->useSSL ? 443 : 80) ? ':'.$this->port : ''));
+ if ($this->url != $url) {
+ $this->addHeader('host', $this->originHost.($this->originPort != ($this->originUseSSL ? 443 : 80) ? ':'.$this->originPort : ''));
}
$this->url = $url;
*/
public function execute() {
// connect
- $remoteFile = new RemoteFile(($this->useSSL ? 'ssl://' : '').$this->host, $this->port, $this->options['timeout']);
+ $remoteFile = new RemoteFile(($this->useSSL ? 'ssl://' : '').$this->host, $this->port, $this->options['timeout'], array(
+ 'ssl' => array(
+ 'peer_name' => $this->originHost
+ )
+ ));
+
+ if ($this->originUseSSL && PROXY_SERVER_HTTP) {
+ if ($this->useSSL) throw new SystemException("Unable to proxy HTTPS when using TLS for proxy connection");
+
+ $request = "CONNECT ".$this->originHost.":".$this->originPort." HTTP/1.0\r\n";
+ if (isset($this->headers['user-agent'])) {
+ $request .= 'user-agent: '.reset($this->headers['user-agent'])."\r\n";
+ }
+ $request .= "Host: ".$this->originHost.":".$this->originPort."\r\n";
+ $request .= "\r\n";
+ $remoteFile->puts($request);
+ $this->replyHeaders = array();
+ while (!$remoteFile->eof()) {
+ $line = $remoteFile->gets();
+ if (rtrim($line) === '') {
+ $this->parseReplyHeaders();
+
+ break;
+ }
+ $this->replyHeaders[] = $line;
+ }
+ if ($this->statusCode != 200) throw new SystemException("Expected 200 Ok as reply to my CONNECT, got '".$this->statusCode."'");
+ $remoteFile->setTLS(true);
+ }
$request = $this->options['method']." ".$this->path.($this->query ? '?'.$this->query : '')." HTTP/1.1\r\n";
$chunkLength = 0;
$bodyLength = 0;
- // read http response.
- while (!$remoteFile->eof()) {
+ $chunkedTransferRegex = new Regex('(^|,)[ \t]*chunked[ \t]*$', Regex::CASE_INSENSITIVE);
+ // read http response, until one of is true
+ // a) EOF is reached
+ // b) bodyLength is at least maxLength
+ // c) bodyLength is at least Content-Length
+ while (!(
+ $remoteFile->eof() ||
+ (isset($this->options['maxLength']) && $bodyLength >= $this->options['maxLength']) ||
+ (isset($this->replyHeaders['content-length']) && $bodyLength >= end($this->replyHeaders['content-length']))
+ )) {
+
if ($chunkLength) {
if (isset($this->options['maxLength'])) $chunkLength = min($chunkLength, $this->options['maxLength'] - $bodyLength);
$line = $remoteFile->read($chunkLength);
}
+ else if (!$inHeader && (!isset($this->replyHeaders['transfer-encoding']) || !$chunkedTransferRegex->match(end($this->replyHeaders['transfer-encoding'])))) {
+ $length = 1024;
+ if (isset($this->options['maxLength'])) $length = min($length, $this->options['maxLength'] - $bodyLength);
+ if (isset($this->replyHeaders['content-length'])) $length = min($length, end($this->replyHeaders['content-length']) - $bodyLength);
+
+ $line = $remoteFile->read($length);
+ }
else {
$line = $remoteFile->gets();
}
$this->replyHeaders[] = $line;
}
else {
- $chunkedTransferRegex = new Regex('(^|,)[ \t]*chunked[ \t]*$', Regex::CASE_INSENSITIVE);
if (isset($this->replyHeaders['transfer-encoding']) && $chunkedTransferRegex->match(end($this->replyHeaders['transfer-encoding']))) {
// last chunk finished
if ($chunkLength === 0) {
// $chunkLength === 0 -> no more data
if ($chunkLength === 0) {
// clear remaining response
- while (!$remoteFile->gets());
+ while (!$remoteFile->gets(1024));
// remove chunked from transfer-encoding
$this->replyHeaders['transfer-encoding'] = array_filter(array_map(function ($element) use ($chunkedTransferRegex) {
$this->replyBody .= $line;
$bodyLength += strlen($line);
}
-
- if (isset($this->options['maxLength']) && $bodyLength >= $this->options['maxLength']) {
- break;
- }
}
}
$this->url = $newRequest->url;
$this->statusCode = $newRequest->statusCode;
$this->replyHeaders = $newRequest->replyHeaders;
+ $this->legacyHeaders = $newRequest->legacyHeaders;
$this->replyBody = $newRequest->replyBody;
}
catch (SystemException $e) {
$this->url = $newRequest->url;
$this->statusCode = $newRequest->statusCode;
$this->replyHeaders = $newRequest->replyHeaders;
+ $this->legacyHeaders = $newRequest->legacyHeaders;
$this->replyBody = $newRequest->replyBody;
throw $e;