/** * Searches for purchased items in the WoltLab Plugin-Store. * * @return array<string> */ public function searchForPurchasedItems() { if (!RemoteFile::supportsSSL()) { return array('noSSL' => WCF::getLanguage()->get('wcf.acp.pluginStore.api.noSSL')); } if (empty($this->parameters['username']) || empty($this->parameters['password'])) { return array('template' => $this->renderAuthorizationDialog(false)); } $request = new HTTPRequest('https://api.woltlab.com/1.0/customer/purchases/list.json', array('method' => 'POST'), array('username' => $this->parameters['username'], 'password' => $this->parameters['password'], 'wcfVersion' => WCF_VERSION)); $request->execute(); $reply = $request->getReply(); $response = JSON::decode($reply['body']); $code = isset($response['status']) ? $response['status'] : 500; switch ($code) { case 200: if (empty($response['products'])) { return array('noResults' => WCF::getLanguage()->get('wcf.acp.pluginStore.purchasedItems.noResults')); } else { WCF::getSession()->register('__pluginStoreProducts', $response['products']); WCF::getSession()->register('__pluginStoreWcfMajorReleases', $response['wcfMajorReleases']); return array('redirectURL' => LinkHandler::getInstance()->getLink('PluginStorePurchasedItems')); } break; // authentication error // authentication error case 401: return array('template' => $this->renderAuthorizationDialog(true)); break; // any other kind of errors // any other kind of errors default: throw new SystemException(WCF::getLanguage()->getDynamicVariable('wcf.acp.pluginStore.api.error', array('status' => $code))); break; } }
/** * 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); }
/** * Returns true if a request to this server would make use of a secure connection. * * @return boolean */ public function attemptSecureConnection() { if ($this->apiVersion == '2.0') { return false; } $metaData = $this->getMetaData(); if (RemoteFile::supportsSSL() && $metaData['ssl']) { return true; } return false; }
/** * 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(); }
/** * 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); }
/** * Gets the package_update.xml from an update server. * * @param \wcf\data\package\update\server\PackageUpdateServer $updateServer * @param boolean $forceHTTP */ protected function getPackageUpdateXML(PackageUpdateServer $updateServer, $forceHTTP = false) { $settings = array(); $authData = $updateServer->getAuthData(); if ($authData) { $settings['auth'] = $authData; } $secureConnection = $updateServer->attemptSecureConnection(); if ($secureConnection && !$forceHTTP) { $settings['timeout'] = 5; } $request = new HTTPRequest($updateServer->getListURL($forceHTTP), $settings); if ($updateServer->apiVersion == '2.1') { $metaData = $updateServer->getMetaData(); if (isset($metaData['list']['etag'])) { $request->addHeader('if-none-match', $metaData['list']['etag']); } if (isset($metaData['list']['lastModified'])) { $request->addHeader('if-modified-since', $metaData['list']['lastModified']); } } try { $request->execute(); $reply = $request->getReply(); } catch (HTTPUnauthorizedException $e) { throw new PackageUpdateUnauthorizedException($request, $updateServer); } catch (SystemException $e) { $reply = $request->getReply(); $statusCode = is_array($reply['statusCode']) ? reset($reply['statusCode']) : $reply['statusCode']; // status code 0 is a connection timeout if (!$statusCode && $secureConnection) { if (preg_match('~https?://(?:update|store)\\.woltlab\\.com~', $updateServer->serverURL)) { // woltlab.com servers are most likely to be available, thus we assume that SSL connections are dropped RemoteFile::disableSSL(); } // retry via http $this->getPackageUpdateXML($updateServer, true); return; } throw new SystemException(WCF::getLanguage()->get('wcf.acp.package.update.error.listNotFound') . ' (' . $statusCode . ')'); } // parse given package update xml $allNewPackages = false; if ($updateServer->apiVersion == '2.0' || $reply['statusCode'] != 304) { $allNewPackages = $this->parsePackageUpdateXML($reply['body']); } $data = array('lastUpdateTime' => TIME_NOW, 'status' => 'online', 'errorMessage' => ''); // check if server indicates support for a newer API if ($updateServer->apiVersion == '2.0' && !empty($reply['httpHeaders']['wcf-update-server-api'])) { $apiVersions = explode(' ', reset($reply['httpHeaders']['wcf-update-server-api'])); if (in_array('2.1', $apiVersions)) { $data['apiVersion'] = '2.1'; } } $metaData = array(); if ($updateServer->apiVersion == '2.1' || isset($data['apiVersion']) && $data['apiVersion'] == '2.1') { if (empty($reply['httpHeaders']['etag']) && empty($reply['httpHeaders']['last-modified'])) { throw new SystemException("Missing required HTTP headers 'etag' and 'last-modified'."); } else { if (empty($reply['httpHeaders']['wcf-update-server-ssl'])) { throw new SystemException("Missing required HTTP header 'wcf-update-server-ssl'."); } } $metaData['list'] = array(); if (!empty($reply['httpHeaders']['etag'])) { $metaData['list']['etag'] = reset($reply['httpHeaders']['etag']); } if (!empty($reply['httpHeaders']['last-modified'])) { $metaData['list']['lastModified'] = reset($reply['httpHeaders']['last-modified']); } $metaData['ssl'] = reset($reply['httpHeaders']['wcf-update-server-ssl']) == 'true' ? true : false; } $data['metaData'] = serialize($metaData); unset($request, $reply); if ($allNewPackages !== false) { // purge package list $sql = "DELETE FROM\twcf" . WCF_N . "_package_update\n\t\t\t\tWHERE\t\tpackageUpdateServerID = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute(array($updateServer->packageUpdateServerID)); // save packages if (!empty($allNewPackages)) { $this->savePackageUpdates($allNewPackages, $updateServer->packageUpdateServerID); } unset($allNewPackages); } // update server status $updateServerEditor = new PackageUpdateServerEditor($updateServer); $updateServerEditor->update($data); }
/** * 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(); }
/** * Sends Information to the smtp-Server * * @param string $data */ protected function write($data) { $this->connection->puts($data . Mail::$lineEnding); }