/** * Downloads a package archive from an http URL. * * @param string $httpUrl * @param string $prefix * @param array $options * @param array $postParameters * @param array $headers Should be either an empty array or a not initialized variable. * @return string path to the downloaded file */ public static function downloadFileFromHttp($httpUrl, $prefix = 'package', array $options = array(), array $postParameters = array(), &$headers = array()) { $newFileName = self::getTemporaryFilename($prefix . '_'); $localFile = new File($newFileName); // the file to write. if (!isset($options['timeout'])) { $options['timeout'] = 30; } if (!isset($options['method'])) { $options['method'] = !empty($postParameters) ? 'POST' : 'GET'; } // parse URL $parsedUrl = parse_url($httpUrl); $port = $parsedUrl['scheme'] == 'https' ? 443 : 80; $host = $parsedUrl['host']; $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '/'; // proxy is set if (PROXY_SERVER_HTTP) { $parsedProxy = parse_url(PROXY_SERVER_HTTP); $host = $parsedProxy['host']; $port = $parsedProxy['scheme'] == 'https' ? 443 : 80; $path = $httpUrl; $parsedUrl['query'] = ''; } // build parameter-string $parameterString = ''; foreach ($postParameters as $key => $value) { if (is_array($value)) { foreach ($value as $value2) { $parameterString .= urlencode($key) . '[]=' . urlencode($value2) . '&'; } } else { $parameterString .= urlencode($key) . '=' . urlencode($value) . '&'; } } $parameterString = rtrim($parameterString, '&'); // connect try { $remoteFile = new RemoteFile(($port === 443 ? 'ssl://' : '') . $host, $port, $options['timeout']); } catch (SystemException $e) { $localFile->close(); unlink($newFileName); throw $e; } // build and send the http request. $request = $options['method'] . " " . $path . (!empty($parsedUrl['query']) ? '?' . $parsedUrl['query'] : '') . " HTTP/1.0\r\n"; $request .= "User-Agent: HTTP.PHP (FileUtil.class.php; WoltLab Community Framework/" . WCF_VERSION . "; " . WCF::getLanguage()->languageCode . ")\r\n"; $request .= "Accept: */*\r\n"; $request .= "Accept-Language: " . WCF::getLanguage()->languageCode . "\r\n"; if ($options['method'] !== 'GET') { $request .= "Content-length: " . strlen($parameterString) . "\r\n"; $request .= "Content-Type: application/x-www-form-urlencoded\r\n"; } $request .= "Host: " . $host . "\r\n"; $request .= "Connection: Close\r\n\r\n"; if ($options['method'] !== 'GET') { $request .= $parameterString . "\r\n\r\n"; } $remoteFile->puts($request); $inHeader = true; $readResponse = array(); // read http response. while (!$remoteFile->eof()) { $readResponse[] = $remoteFile->gets(); if ($inHeader) { if (rtrim(end($readResponse)) == '') { $inHeader = false; } } else { // look if the webserver sent an error http statuscode // This has still to be checked if really sufficient! $arrayHeader = array('201', '301', '302', '303', '307', '404'); foreach ($arrayHeader as $code) { $error = strpos($readResponse[0], $code); } if ($error !== false) { $localFile->close(); unlink($newFileName); throw new SystemException("file " . $path . " not found at host '" . $host . "'"); } // write to the target system. $localFile->write(end($readResponse)); } } foreach ($readResponse as $line) { if (rtrim($line) == '') { break; } if (strpos($line, ':') === false) { $headers[trim($line)] = trim($line); continue; } list($key, $value) = explode(':', $line, 2); $headers[$key] = trim($value); } $remoteFile->close(); $localFile->close(); return $newFileName; }
/** * Sends a request to a remote (update) server. * * @param string $url * @param array $values * @param array $authData * @return array $response */ public static function sendRequest($url, array $values = array(), array $authData = array()) { // default values $host = ''; $path = '/'; $port = 80; $postString = ''; // parse url $parsedURL = parse_url($url); if (!empty($parsedURL['host'])) { $host = $parsedURL['host']; } if (!empty($parsedURL['path'])) { $path = $parsedURL['path']; } if (!empty($parsedURL['query'])) { $postString = $parsedURL['query']; } if (!empty($parsedURL['port'])) { $port = $parsedURL['port']; } // connect to server if (PROXY_SERVER_HTTP) { $parsedProxyURL = parse_url(PROXY_SERVER_HTTP); $remoteFile = new RemoteFile($parsedProxyURL['host'], $parsedProxyURL['port'], 30); $path = $url; $host = $parsedProxyURL['host']; } else { $remoteFile = new RemoteFile($host, $port, 30); } // Build and send the http request $request = "POST " . $path . " HTTP/1.0\r\n"; if (isset($authData['authType'])) { $request .= "Authorization: Basic " . base64_encode($authData['loginUsername'] . ":" . $authData['loginPassword']) . "\r\n"; } $request .= "User-Agent: HTTP.PHP (PackageUpdateDispatcher.class.php; WoltLab Community Framework/" . WCF_VERSION . "; " . WCF::getLanguage()->languageCode . ")\r\n"; $request .= "Accept: */*\r\n"; $request .= "Accept-Language: " . WCF::getLanguage()->languageCode . "\r\n"; $request .= "Host: " . $host . "\r\n"; // build post string foreach ($values as $name => $value) { if (!empty($postString)) { $postString .= '&'; } $postString .= $name . '=' . $value; } // send content type and length $request .= "Content-Type: application/x-www-form-urlencoded\r\n"; $request .= "Content-Length: " . strlen($postString) . "\r\n"; // if it is a POST request, there MUST be a blank line before the POST data, but there MUST NOT be // another blank line before, and of course there must be another blank line at the end of the request! $request .= "\r\n"; if (!empty($postString)) { $request .= $postString . "\r\n"; } // send close $request .= "Connection: Close\r\n\r\n"; // send request $remoteFile->puts($request); unset($request, $postString); // define response vars $header = $content = ''; // fetch the response. while (!$remoteFile->eof()) { $line = $remoteFile->gets(); if (rtrim($line) != '') { $header .= $line; } else { break; } } while (!$remoteFile->eof()) { $content .= $remoteFile->gets(); } // clean up and return the server's response. $remoteFile->close(); // get http status code / line $httpStatusCode = 0; $httpStatusLine = ''; if (preg_match('%http/\\d\\.\\d (\\d{3})[^\\n]*%i', $header, $match)) { $httpStatusLine = trim($match[0]); $httpStatusCode = $match[1]; } // catch http 301 Moved Permanently // catch http 302 Found // catch http 303 See Other if ($httpStatusCode == 301 || $httpStatusCode == 302 || $httpStatusCode == 303) { // find location if (preg_match('/location:([^\\n]*)/i', $header, $match)) { $location = trim($match[1]); if ($location != $url) { return self::sendRequest($location, $values, $authData); } } } // catch other http codes here return array('httpStatusLine' => $httpStatusLine, 'httpStatusCode' => $httpStatusCode, 'header' => $header, 'content' => $content); }
/** * Downloads and imports a language file from a language server. * * @param string $location * @param array<string> $packageList */ protected function importLanguageFile($location, array $packageList) { // get proxy $options = array(); if (PROXY_SERVER_HTTP) { $options['http']['proxy'] = PROXY_SERVER_HTTP; $options['http']['request_fulluri'] = true; } // parse url $parsedURL = parse_url($location); $port = $parsedURL['scheme'] == 'https' ? 443 : 80; $host = $parsedURL['host']; $path = isset($parsedURL['path']) ? $parsedURL['path'] : '/'; $remoteFile = new RemoteFile(($parsedURL['scheme'] == 'https' ? 'ssl://' : '') . $host, $port, 30, $options); // the file to read. if (!isset($remoteFile)) { throw new SystemException("cannot connect to http host '" . $host . "'"); } // build and send the http request $request = "POST " . $path . " HTTP/1.0\r\n"; $request .= "User-Agent: HTTP.PHP (LanguageServerProcessor.class.php; WoltLab Community Framework/" . WCF_VERSION . "; " . WCF::getLanguage()->languageCode . ")\r\n"; $request .= "Accept: */*\r\n"; $request .= "Accept-Language: " . WCF::getLanguage()->languageCode . "\r\n"; $request .= "Host: " . $host . "\r\n"; // build post string $postString = 'languageCode=' . $this->language->languageCode; foreach ($packageList as $package => $packageVersion) { $postString .= '&packages[' . urlencode($package) . ']=' . urlencode($packageVersion); } // send content type and length $request .= "Content-Type: application/x-www-form-urlencoded\r\n"; $request .= "Content-Length: " . strlen($postString) . "\r\n"; // if it is a POST request, there MUST be a blank line before the POST data, but there MUST NOT be // another blank line before, and of course there must be another blank line at the end of the request! $request .= "\r\n"; if (!empty($postString)) { $request .= $postString . "\r\n"; } // send close $request .= "Connection: Close\r\n\r\n"; // send request $remoteFile->puts($request); // define response vars $header = $content = ''; // fetch the response. while (!$remoteFile->eof()) { $line = $remoteFile->gets(); if (rtrim($line) != '') { $header .= $line; } else { break; } } while (!$remoteFile->eof()) { $content .= $remoteFile->gets(); } // clean up and return the server's response. $remoteFile->close(); // get http status code / line $httpStatusCode = 0; $httpStatusLine = ''; if (preg_match('%http/\\d\\.\\d (\\d{3})[^\\n]*%i', $header, $match)) { $httpStatusLine = trim($match[0]); $httpStatusCode = $match[1]; } // catch http 301 Moved Permanently // catch http 302 Found // catch http 303 See Other if ($httpStatusCode == 301 || $httpStatusCode == 302 || $httpStatusCode == 303) { // find location if (preg_match('/location:([^\\n]*)/i', $header, $match)) { $newLocation = trim($match[1]); if ($newLocation != $location) { $this->importLanguageFile($location, $packageList); return; } } } $this->parseResponse($content); }
/** * Executes the HTTP request. */ public function execute() { // connect $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"; // add headers foreach ($this->headers as $name => $values) { foreach ($values as $value) { $request .= $name . ": " . $value . "\r\n"; } } $request .= "\r\n"; // add post parameters if ($this->options['method'] !== 'GET') { $request .= $this->body . "\r\n\r\n"; } $remoteFile->puts($request); $inHeader = true; $this->replyHeaders = array(); $this->replyBody = ''; $chunkLength = 0; $bodyLength = 0; $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(); } } if ($inHeader) { if (rtrim($line) === '') { $inHeader = false; $this->parseReplyHeaders(); continue; } $this->replyHeaders[] = $line; } else { if (isset($this->replyHeaders['transfer-encoding']) && $chunkedTransferRegex->match(end($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(1024)) { } // 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']); } // break out of main reading loop break; } } else { $this->replyBody .= $line; $chunkLength -= strlen($line); $bodyLength += strlen($line); if ($chunkLength === 0) { $remoteFile->read(2); } // CRLF } } else { $this->replyBody .= $line; $bodyLength += strlen($line); } } } if (isset($this->options['maxLength'])) { $this->replyBody = substr($this->replyBody, 0, $this->options['maxLength']); } $remoteFile->close(); $this->parseReply(); }
/** * Executes the HTTP request. */ public function execute() { // connect $remoteFile = new RemoteFile(($this->useSSL ? 'ssl://' : '').$this->host, $this->port, $this->options['timeout']); $request = $this->options['method']." ".$this->path.($this->query ? '?'.$this->query : '')." HTTP/1.0\r\n"; // add headers foreach ($this->headers as $name => $values) { foreach ($values as $value) { $request .= $name.": ".$value."\r\n"; } } $request .= "\r\n"; // add post parameters if ($this->options['method'] !== 'GET') $request .= http_build_query($this->postParameters)."\r\n\r\n"; $remoteFile->puts($request); $inHeader = true; $this->replyHeaders = array(); $this->replyBody = ''; // read http response. while (!$remoteFile->eof()) { $line = $remoteFile->gets(); if ($inHeader) { if (rtrim($line) === '') { $inHeader = false; continue; } $this->replyHeaders[] = $line; } else { $this->replyBody .= $line; } } $this->parseReply(); }