/** * @param string $realServer * @return bool|mysqli * @throws DBConnectionError */ protected function mysqlConnect($realServer) { global $wgDBmysql5; # Fail now # Otherwise we get a suppressed fatal error, which is very hard to track down if (!function_exists('mysqli_init')) { throw new DBConnectionError($this, "MySQLi functions missing," . " have you compiled PHP with the --with-mysqli option?\n"); } // Other than mysql_connect, mysqli_real_connect expects an explicit port // and socket parameters. So we need to parse the port and socket out of // $realServer $port = null; $socket = null; $hostAndPort = IP::splitHostAndPort($realServer); if ($hostAndPort) { $realServer = $hostAndPort[0]; if ($hostAndPort[1]) { $port = $hostAndPort[1]; } } elseif (substr_count($realServer, ':') == 1) { // If we have a colon and something that's not a port number // inside the hostname, assume it's the socket location $hostAndSocket = explode(':', $realServer); $realServer = $hostAndSocket[0]; $socket = $hostAndSocket[1]; } $connFlags = 0; if ($this->mFlags & DBO_SSL) { $connFlags |= MYSQLI_CLIENT_SSL; } if ($this->mFlags & DBO_COMPRESS) { $connFlags |= MYSQLI_CLIENT_COMPRESS; } if ($this->mFlags & DBO_PERSISTENT) { $realServer = 'p:' . $realServer; } $mysqli = mysqli_init(); if ($wgDBmysql5) { // Tell the server we're communicating with it in UTF-8. // This may engage various charset conversions. $mysqli->options(MYSQLI_SET_CHARSET_NAME, 'utf8'); } else { $mysqli->options(MYSQLI_SET_CHARSET_NAME, 'binary'); } $numAttempts = 2; for ($i = 0; $i < $numAttempts; $i++) { if ($i > 1) { usleep(1000); } if ($mysqli->real_connect($realServer, $this->mUser, $this->mPassword, $this->mDBname, $port, $socket, $connFlags)) { return $mysqli; } } return false; }
/** * @param string $realServer * @return bool|mysqli * @throws DBConnectionError */ protected function mysqlConnect($realServer) { # Avoid suppressed fatal error, which is very hard to track down if (!function_exists('mysqli_init')) { throw new DBConnectionError($this, "MySQLi functions missing," . " have you compiled PHP with the --with-mysqli option?\n"); } // Other than mysql_connect, mysqli_real_connect expects an explicit port // and socket parameters. So we need to parse the port and socket out of // $realServer $port = null; $socket = null; $hostAndPort = IP::splitHostAndPort($realServer); if ($hostAndPort) { $realServer = $hostAndPort[0]; if ($hostAndPort[1]) { $port = $hostAndPort[1]; } } elseif (substr_count($realServer, ':') == 1) { // If we have a colon and something that's not a port number // inside the hostname, assume it's the socket location $hostAndSocket = explode(':', $realServer); $realServer = $hostAndSocket[0]; $socket = $hostAndSocket[1]; } $mysqli = mysqli_init(); $connFlags = 0; if ($this->mFlags & self::DBO_SSL) { $connFlags |= MYSQLI_CLIENT_SSL; $mysqli->ssl_set($this->sslKeyPath, $this->sslCertPath, null, $this->sslCAPath, $this->sslCiphers); } if ($this->mFlags & self::DBO_COMPRESS) { $connFlags |= MYSQLI_CLIENT_COMPRESS; } if ($this->mFlags & self::DBO_PERSISTENT) { $realServer = 'p:' . $realServer; } if ($this->utf8Mode) { // Tell the server we're communicating with it in UTF-8. // This may engage various charset conversions. $mysqli->options(MYSQLI_SET_CHARSET_NAME, 'utf8'); } else { $mysqli->options(MYSQLI_SET_CHARSET_NAME, 'binary'); } $mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 3); if ($mysqli->real_connect($realServer, $this->mUser, $this->mPassword, $this->mDBname, $port, $socket, $connFlags)) { return $mysqli; } return false; }
protected function mysqlConnect( $realServer ) { # Fail now # Otherwise we get a suppressed fatal error, which is very hard to track down if ( !function_exists( 'mysqli_init' ) ) { throw new DBConnectionError( $this, "MySQLi functions missing," . " have you compiled PHP with the --with-mysqli option?\n" ); } // Other than mysql_connect, mysqli_real_connect expects an explicit port // parameter. So we need to parse the port out of $realServer $port = null; $hostAndPort = IP::splitHostAndPort( $realServer ); if ( $hostAndPort ) { $realServer = $hostAndPort[0]; if ( $hostAndPort[1] ) { $port = $hostAndPort[1]; } } $connFlags = 0; if ( $this->mFlags & DBO_SSL ) { $connFlags |= MYSQLI_CLIENT_SSL; } if ( $this->mFlags & DBO_COMPRESS ) { $connFlags |= MYSQLI_CLIENT_COMPRESS; } if ( $this->mFlags & DBO_PERSISTENT ) { $realServer = 'p:' . $realServer; } $mysqli = mysqli_init(); $numAttempts = 2; for ( $i = 0; $i < $numAttempts; $i++ ) { if ( $i > 1 ) { usleep( 1000 ); } if ( $mysqli->real_connect( $realServer, $this->mUser, $this->mPassword, $this->mDBname, $port, null, $connFlags ) ) { return $mysqli; } } return false; }
/** * Work out an appropriate URL prefix containing scheme and host, based on * information detected from $_SERVER * * @return string */ public static function detectServer() { list($proto, $stdPort) = self::detectProtocolAndStdPort(); $varNames = array('HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR'); $host = 'localhost'; $port = $stdPort; foreach ($varNames as $varName) { if (!isset($_SERVER[$varName])) { continue; } $parts = IP::splitHostAndPort($_SERVER[$varName]); if (!$parts) { // Invalid, do not use continue; } $host = $parts[0]; if ($parts[1] === false) { if (isset($_SERVER['SERVER_PORT'])) { $port = $_SERVER['SERVER_PORT']; } // else leave it as $stdPort } else { $port = $parts[1]; } break; } return $proto . '://' . IP::combineHostAndPort($host, $port, $stdPort); }
/** * Test for IP::splitHostAndPort(). * @dataProvider provideSplitHostAndPort */ function testSplitHostAndPort($expected, $input, $description) { $this->assertEquals($expected, IP::splitHostAndPort($input), $description); }
/** * Get a connection to a redis server. Based on code in RedisBagOStuff.php. * * @param string $server A hostname/port combination or the absolute path of a UNIX socket. * If a hostname is specified but no port, port 6379 will be used. * @return RedisConnRef|bool Returns false on failure * @throws MWException */ public function getConnection($server) { // Check the listing "dead" servers which have had a connection errors. // Servers are marked dead for a limited period of time, to // avoid excessive overhead from repeated connection timeouts. if (isset($this->downServers[$server])) { $now = time(); if ($now > $this->downServers[$server]) { // Dead time expired unset($this->downServers[$server]); } else { // Server is dead $this->logger->debug('Server "{redis_server}" is marked down for another ' . ($this->downServers[$server] - $now) . 'seconds', array('redis_server' => $server)); return false; } } // Check if a connection is already free for use if (isset($this->connections[$server])) { foreach ($this->connections[$server] as &$connection) { if ($connection['free']) { $connection['free'] = false; --$this->idlePoolSize; return new RedisConnRef($this, $server, $connection['conn'], $this->logger); } } } if (substr($server, 0, 1) === '/') { // UNIX domain socket // These are required by the redis extension to start with a slash, but // we still need to set the port to a special value to make it work. $host = $server; $port = 0; } else { // TCP connection $hostPort = IP::splitHostAndPort($server); if (!$server || !$hostPort) { throw new MWException(__CLASS__ . ": invalid configured server \"{$server}\""); } list($host, $port) = $hostPort; if ($port === false) { $port = 6379; } } $conn = new Redis(); try { if ($this->persistent) { $result = $conn->pconnect($host, $port, $this->connectTimeout); } else { $result = $conn->connect($host, $port, $this->connectTimeout); } if (!$result) { $this->logger->error('Could not connect to server "{redis_server}"', array('redis_server' => $server)); // Mark server down for some time to avoid further timeouts $this->downServers[$server] = time() + self::SERVER_DOWN_TTL; return false; } if ($this->password !== null) { if (!$conn->auth($this->password)) { $this->logger->error('Authentication error connecting to "{redis_server}"', array('redis_server' => $server)); } } } catch (RedisException $e) { $this->downServers[$server] = time() + self::SERVER_DOWN_TTL; $this->logger->error('Redis exception connecting to "{redis_server}"', array('redis_server' => $server, 'exception' => $e)); return false; } if ($conn) { $conn->setOption(Redis::OPT_READ_TIMEOUT, $this->readTimeout); $conn->setOption(Redis::OPT_SERIALIZER, $this->serializer); $this->connections[$server][] = array('conn' => $conn, 'free' => false); return new RedisConnRef($this, $server, $conn, $this->logger); } else { return false; } }
/** * Work out an appropriate URL prefix containing scheme and host, based on * information detected from $_SERVER * * @return string */ public static function detectServer() { global $wgAssumeProxiesUseDefaultProtocolPorts; $proto = self::detectProtocol(); $stdPort = $proto === 'https' ? 443 : 80; $varNames = array('HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR'); $host = 'localhost'; $port = $stdPort; foreach ($varNames as $varName) { if (!isset($_SERVER[$varName])) { continue; } $parts = IP::splitHostAndPort($_SERVER[$varName]); if (!$parts) { // Invalid, do not use continue; } $host = $parts[0]; if ($wgAssumeProxiesUseDefaultProtocolPorts && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { // Bug 70021: Assume that upstream proxy is running on the default // port based on the protocol. We have no reliable way to determine // the actual port in use upstream. $port = $stdPort; } elseif ($parts[1] === false) { if (isset($_SERVER['SERVER_PORT'])) { $port = $_SERVER['SERVER_PORT']; } // else leave it as $stdPort } else { $port = $parts[1]; } break; } return $proto . '://' . IP::combineHostAndPort($host, $port, $stdPort); }
/** * Get a connection to the server with the specified name. Connections * are cached, and failures are persistent to avoid multiple timeouts. * * @param $server * @throws MWException * @return Redis object, or false on failure */ protected function getConnectionToServer($server) { if (isset($this->deadServers[$server])) { $now = time(); if ($now > $this->deadServers[$server]) { // Dead time expired unset($this->deadServers[$server]); } else { // Server is dead $this->debug("server {$server} is marked down for another " . ($this->deadServers[$server] - $now) . " seconds, can't get connection"); return false; } } if (isset($this->conns[$server])) { return $this->conns[$server]; } if (substr($server, 0, 1) === '/') { // UNIX domain socket // These are required by the redis extension to start with a slash, but // we still need to set the port to a special value to make it work. $host = $server; $port = 0; } else { // TCP connection $hostPort = IP::splitHostAndPort($server); if (!$hostPort) { throw new MWException(__CLASS__ . ": invalid configured server \"{$server}\""); } list($host, $port) = $hostPort; if ($port === false) { $port = 6379; } } $conn = new Redis(); try { if ($this->persistent) { $this->debug("opening persistent connection to {$host}:{$port}"); $result = $conn->pconnect($host, $port, $this->connectTimeout); } else { $this->debug("opening non-persistent connection to {$host}:{$port}"); $result = $conn->connect($host, $port, $this->connectTimeout); } if (!$result) { $this->logError("could not connect to server {$server}"); // Mark server down for 30s to avoid further timeouts $this->deadServers[$server] = time() + 30; return false; } if ($this->password !== null) { if (!$conn->auth($this->password)) { $this->logError("authentication error connecting to {$server}"); } } } catch (RedisException $e) { $this->deadServers[$server] = time() + 30; wfDebugLog('redis', "Redis exception: " . $e->getMessage() . "\n"); return false; } $conn->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); $this->conns[$server] = $conn; return $conn; }
/** * Constructor * * Available parameters are: * - servers: The list of IP:port combinations holding the memcached servers. * - persistent: Whether to use a persistent connection * - compress_threshold: The minimum size an object must be before it is compressed * - timeout: The read timeout in microseconds * - connect_timeout: The connect timeout in seconds * - retry_timeout: Time in seconds to wait before retrying a failed connect attempt * - server_failure_limit: Limit for server connect failures before it is removed * - serializer: May be either "php" or "igbinary". Igbinary produces more compact * values, but serialization is much slower unless the php.ini option * igbinary.compact_strings is off. * @param array $params * @throws MWException */ function __construct($params) { parent::__construct($params); $params = $this->applyDefaultParams($params); if ($params['persistent']) { // The pool ID must be unique to the server/option combination. // The Memcached object is essentially shared for each pool ID. // We can only reuse a pool ID if we keep the config consistent. $this->client = new Memcached(md5(serialize($params))); if (count($this->client->getServerList())) { $this->logger->debug(__METHOD__ . ": persistent Memcached object already loaded."); return; // already initialized; don't add duplicate servers } } else { $this->client = new Memcached(); } if (!isset($params['serializer'])) { $params['serializer'] = 'php'; } if (isset($params['retry_timeout'])) { $this->client->setOption(Memcached::OPT_RETRY_TIMEOUT, $params['retry_timeout']); } if (isset($params['server_failure_limit'])) { $this->client->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, $params['server_failure_limit']); } // The compression threshold is an undocumented php.ini option for some // reason. There's probably not much harm in setting it globally, for // compatibility with the settings for the PHP client. ini_set('memcached.compression_threshold', $params['compress_threshold']); // Set timeouts $this->client->setOption(Memcached::OPT_CONNECT_TIMEOUT, $params['connect_timeout'] * 1000); $this->client->setOption(Memcached::OPT_SEND_TIMEOUT, $params['timeout']); $this->client->setOption(Memcached::OPT_RECV_TIMEOUT, $params['timeout']); $this->client->setOption(Memcached::OPT_POLL_TIMEOUT, $params['timeout'] / 1000); // Set libketama mode since it's recommended by the documentation and // is as good as any. There's no way to configure libmemcached to use // hashes identical to the ones currently in use by the PHP client, and // even implementing one of the libmemcached hashes in pure PHP for // forwards compatibility would require MWMemcached::get_sock() to be // rewritten. $this->client->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true); // Set the serializer switch ($params['serializer']) { case 'php': $this->client->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP); break; case 'igbinary': if (!Memcached::HAVE_IGBINARY) { throw new MWException(__CLASS__ . ': the igbinary extension is not available ' . 'but igbinary serialization was requested.'); } $this->client->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY); break; default: throw new MWException(__CLASS__ . ': invalid value for serializer parameter'); } $servers = array(); foreach ($params['servers'] as $host) { $servers[] = IP::splitHostAndPort($host); // (ip, port) } $this->client->addServers($servers); }
/** * */ protected function streamThumbnail() { global $wgVipsThumbnailerHost, $wgVipsTestExpiry; $request = $this->getRequest(); # Validate title and file existance $title = Title::makeTitleSafe( NS_FILE, $request->getText( 'thumb' ) ); if ( is_null( $title ) ) { $this->streamError( 404, "VipsScaler: invalid title\n" ); return; } $file = wfFindFile( $title ); if ( !$file || !$file->exists() ) { $this->streamError( 404, "VipsScaler: file not found\n" ); return; } # Check if vips can handle this file if ( VipsScaler::getVipsHandler( $file ) === false ) { $this->streamError( 500, "VipsScaler: VIPS cannot handle this file type\n" ); return; } # Validate param string $handler = $file->getHandler(); $params = array( 'width' => $request->getInt( 'width' ) ); if ( !$handler->normaliseParams( $file, $params ) ) { $this->streamError( 500, "VipsScaler: invalid parameters\n" ); return; } # Get the thumbnail if ( is_null( $wgVipsThumbnailerHost ) || $request->getBool( 'noproxy' ) ) { # No remote scaler, need to do it ourselves. # Emulate the BitmapHandlerTransform hook $dstPath = VipsCommand::makeTemp( $file->getExtension() ); $dstUrl = ''; wfDebug( __METHOD__ . ": Creating vips thumbnail at $dstPath\n" ); $scalerParams = array( # The size to which the image will be resized 'physicalWidth' => $params['physicalWidth'], 'physicalHeight' => $params['physicalHeight'], 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}", # The size of the image on the page 'clientWidth' => $params['width'], 'clientHeight' => $params['height'], # Comment as will be added to the EXIF of the thumbnail 'comment' => isset( $params['descriptionUrl'] ) ? "File source: {$params['descriptionUrl']}" : '', # Properties of the original image 'srcWidth' => $file->getWidth(), 'srcHeight' => $file->getHeight(), 'mimeType' => $file->getMimeType(), 'srcPath' => $file->getPath(), 'dstPath' => $dstPath, 'dstUrl' => $dstUrl, ); $options = array(); if ( $request->getBool( 'bilinear' ) ) { $options['bilinear'] = true; wfDebug( __METHOD__ . ": using bilinear scaling\n" ); } if ( $request->getVal( 'sharpen' ) && $request->getVal( 'sharpen' ) < 5 ) { # Limit sharpen sigma to 5, otherwise we have to write huge convolution matrices $options['sharpen'] = array( 'sigma' => floatval( $request->getVal( 'sharpen' ) ) ); wfDebug( __METHOD__ . ": sharpening with radius {$options['sharpen']}\n" ); } # Call the hook $mto = null; VipsScaler::doTransform( $handler, $file, $scalerParams, $options, $mto ); if ( $mto && !$mto->isError() ) { wfDebug( __METHOD__ . ": streaming thumbnail...\n" ); $this->getOutput()->disable(); StreamFile::stream( $dstPath, array( "Cache-Control: public, max-age=$wgVipsTestExpiry, s-maxage=$wgVipsTestExpiry", 'Expires: ' . gmdate( 'r ', time() + $wgVipsTestExpiry ) ) ); } else { $this->streamError( 500, $mto->getHtmlMsg() ); } # Cleanup the temporary file wfSuppressWarnings(); unlink( $dstPath ); wfRestoreWarnings(); } else { # Request the thumbnail at a remote scaler $url = wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ); $url = wfAppendQuery( $url, array( 'noproxy' => '1' ) ); wfDebug( __METHOD__ . ": Getting vips thumb from remote url $url\n" ); $bits = IP::splitHostAndPort( $wgVipsThumbnailerHost ); if ( !$bits ) { throw new MWException( __METHOD__.': $wgVipsThumbnailerHost is not set to a valid host' ); } list( $host, $port ) = $bits; if ( $port === false ) { $port = 80; } $proxy = IP::combineHostAndPort( $host, $port ); $options = array( 'method' => 'GET', 'proxy' => $proxy, ); $req = MWHttpRequest::factory( $url, $options ); $status = $req->execute(); if ( $status->isOk() ) { # Disable output and stream the file $this->getOutput()->disable(); wfResetOutputBuffers(); header( 'Content-Type: ' . $file->getMimeType() ); header( 'Content-Length: ' . strlen( $req->getContent() ) ); header( "Cache-Control: public, max-age=$wgVipsTestExpiry, s-maxage=$wgVipsTestExpiry" ); header( 'Expires: ' . gmdate( 'r ', time() + $wgVipsTestExpiry ) ); print $req->getContent(); } elseif ( $status->hasMessage( 'http-bad-status' ) ) { $this->streamError( 500, $req->getContent() ); return; } else { global $wgOut; $this->streamError( 500, $wgOut->parse( $status->getWikiText() ) ); return; } } }