use Liuggio\StatsdClient\Sender\SocketSender;
use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\ProcOpenError;
use MediaWiki\Session\SessionManager;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Shell\Shell;
use Wikimedia\ScopedCallback;
use Wikimedia\Rdbms\DBReplicationWaitError;
} else {
reset( $a );
reset( $b );
- while ( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
+ while ( key( $a ) !== null && key( $b ) !== null ) {
+ $valueA = current( $a );
+ $valueB = current( $b );
$cmp = strcmp( $valueA, $valueB );
if ( $cmp !== 0 ) {
return $cmp;
}
+ next( $a );
+ next( $b );
}
return 0;
}
* @param string $args,... strings to escape and glue together,
* or a single array of strings parameter
* @return string
+ * @deprecated since 1.30 use MediaWiki\Shell::escape()
*/
function wfEscapeShellArg( /*...*/ ) {
$args = func_get_args();
- if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
- // If only one argument has been passed, and that argument is an array,
- // treat it as a list of arguments
- $args = reset( $args );
- }
- $first = true;
- $retVal = '';
- foreach ( $args as $arg ) {
- if ( !$first ) {
- $retVal .= ' ';
- } else {
- $first = false;
- }
-
- if ( wfIsWindows() ) {
- // Escaping for an MSVC-style command line parser and CMD.EXE
- // @codingStandardsIgnoreStart For long URLs
- // Refs:
- // * https://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
- // * https://technet.microsoft.com/en-us/library/cc723564.aspx
- // * T15518
- // * CR r63214
- // Double the backslashes before any double quotes. Escape the double quotes.
- // @codingStandardsIgnoreEnd
- $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
- $arg = '';
- $iteration = 0;
- foreach ( $tokens as $token ) {
- if ( $iteration % 2 == 1 ) {
- // Delimiter, a double quote preceded by zero or more slashes
- $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
- } elseif ( $iteration % 4 == 2 ) {
- // ^ in $token will be outside quotes, need to be escaped
- $arg .= str_replace( '^', '^^', $token );
- } else { // $iteration % 4 == 0
- // ^ in $token will appear inside double quotes, so leave as is
- $arg .= $token;
- }
- $iteration++;
- }
- // Double the backslashes before the end of the string, because
- // we will soon add a quote
- $m = [];
- if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
- $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
- }
-
- // Add surrounding quotes
- $retVal .= '"' . $arg . '"';
- } else {
- $retVal .= escapeshellarg( $arg );
- }
- }
- return $retVal;
+ return call_user_func_array( Shell::class . '::escape', $args );
}
/**
*
* @return bool|string False or 'disabled'
* @since 1.22
+ * @deprecated since 1.30 use MediaWiki\Shell::isDisabled()
*/
function wfShellExecDisabled() {
- static $disabled = null;
- if ( is_null( $disabled ) ) {
- if ( !function_exists( 'proc_open' ) ) {
- wfDebug( "proc_open() is disabled\n" );
- $disabled = 'disabled';
- } else {
- $disabled = false;
- }
- }
- return $disabled;
+ return Shell::isDisabled() ? 'disabled' : false;
}
/**
* method. Set this to a string for an alternative method to profile from
*
* @return string Collected stdout as a string
+ * @deprecated since 1.30 use class MediaWiki\Shell\Shell
*/
function wfShellExec( $cmd, &$retval = null, $environ = [],
$limits = [], $options = []
) {
- global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
- $wgMaxShellWallClockTime, $wgShellCgroup;
-
- $disabled = wfShellExecDisabled();
- if ( $disabled ) {
+ if ( Shell::isDisabled() ) {
$retval = 1;
+ // Backwards compatibility be upon us...
return 'Unable to run external programs, proc_open() is disabled.';
}
- $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
- $profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller();
-
- $envcmd = '';
- foreach ( $environ as $k => $v ) {
- if ( wfIsWindows() ) {
- /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
- * appear in the environment variable, so we must use carat escaping as documented in
- * https://technet.microsoft.com/en-us/library/cc723564.aspx
- * Note however that the quote isn't listed there, but is needed, and the parentheses
- * are listed there but doesn't appear to need it.
- */
- $envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
- } else {
- /* Assume this is a POSIX shell, thus required to accept variable assignments before the command
- * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
- */
- $envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
- }
- }
if ( is_array( $cmd ) ) {
- $cmd = wfEscapeShellArg( $cmd );
+ $cmd = Shell::escape( $cmd );
}
- $cmd = $envcmd . $cmd;
+ $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
+ $profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller();
- $useLogPipe = false;
- if ( is_executable( '/bin/bash' ) ) {
- $time = intval( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
- if ( isset( $limits['walltime'] ) ) {
- $wallTime = intval( $limits['walltime'] );
- } elseif ( isset( $limits['time'] ) ) {
- $wallTime = $time;
- } else {
- $wallTime = intval( $wgMaxShellWallClockTime );
- }
- $mem = intval( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
- $filesize = intval( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
-
- if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
- $cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
- escapeshellarg( $cmd ) . ' ' .
- escapeshellarg(
- "MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
- "MW_CPU_LIMIT=$time; " .
- 'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
- "MW_MEM_LIMIT=$mem; " .
- "MW_FILE_SIZE_LIMIT=$filesize; " .
- "MW_WALL_CLOCK_LIMIT=$wallTime; " .
- "MW_USE_LOG_PIPE=yes"
- );
- $useLogPipe = true;
- } elseif ( $includeStderr ) {
- $cmd .= ' 2>&1';
- }
- } elseif ( $includeStderr ) {
- $cmd .= ' 2>&1';
- }
- wfDebug( "wfShellExec: $cmd\n" );
-
- // Don't try to execute commands that exceed Linux's MAX_ARG_STRLEN.
- // Other platforms may be more accomodating, but we don't want to be
- // accomodating, because very long commands probably include user
- // input. See T129506.
- if ( strlen( $cmd ) > SHELL_MAX_ARG_STRLEN ) {
- throw new Exception( __METHOD__ .
- '(): total length of $cmd must not exceed SHELL_MAX_ARG_STRLEN' );
- }
-
- $desc = [
- 0 => [ 'file', 'php://stdin', 'r' ],
- 1 => [ 'pipe', 'w' ],
- 2 => [ 'file', 'php://stderr', 'w' ] ];
- if ( $useLogPipe ) {
- $desc[3] = [ 'pipe', 'w' ];
- }
- $pipes = null;
- $scoped = Profiler::instance()->scopedProfileIn( __FUNCTION__ . '-' . $profileMethod );
- $proc = proc_open( $cmd, $desc, $pipes );
- if ( !$proc ) {
- wfDebugLog( 'exec', "proc_open() failed: $cmd" );
+ try {
+ $result = Shell::command( [] )
+ ->unsafeParams( (array)$cmd )
+ ->environment( $environ )
+ ->limits( $limits )
+ ->includeStderr( $includeStderr )
+ ->profileMethod( $profileMethod )
+ ->execute();
+ } catch ( ProcOpenError $ex ) {
$retval = -1;
return '';
}
- $outBuffer = $logBuffer = '';
- $emptyArray = [];
- $status = false;
- $logMsg = false;
- /* According to the documentation, it is possible for stream_select()
- * to fail due to EINTR. I haven't managed to induce this in testing
- * despite sending various signals. If it did happen, the error
- * message would take the form:
- *
- * stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
- *
- * where [4] is the value of the macro EINTR and "Interrupted system
- * call" is string which according to the Linux manual is "possibly"
- * localised according to LC_MESSAGES.
- */
- $eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
- $eintrMessage = "stream_select(): unable to select [$eintr]";
-
- $running = true;
- $timeout = null;
- $numReadyPipes = 0;
-
- while ( $running === true || $numReadyPipes !== 0 ) {
- if ( $running ) {
- $status = proc_get_status( $proc );
- // If the process has terminated, switch to nonblocking selects
- // for getting any data still waiting to be read.
- if ( !$status['running'] ) {
- $running = false;
- $timeout = 0;
- }
- }
-
- $readyPipes = $pipes;
+ $retval = $result->getExitCode();
- // Clear last error
- // @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged
- @trigger_error( '' );
- $numReadyPipes = @stream_select( $readyPipes, $emptyArray, $emptyArray, $timeout );
- if ( $numReadyPipes === false ) {
- // @codingStandardsIgnoreEnd
- $error = error_get_last();
- if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
- continue;
- } else {
- trigger_error( $error['message'], E_USER_WARNING );
- $logMsg = $error['message'];
- break;
- }
- }
- foreach ( $readyPipes as $fd => $pipe ) {
- $block = fread( $pipe, 65536 );
- if ( $block === '' ) {
- // End of file
- fclose( $pipes[$fd] );
- unset( $pipes[$fd] );
- if ( !$pipes ) {
- break 2;
- }
- } elseif ( $block === false ) {
- // Read error
- $logMsg = "Error reading from pipe";
- break 2;
- } elseif ( $fd == 1 ) {
- // From stdout
- $outBuffer .= $block;
- } elseif ( $fd == 3 ) {
- // From log FD
- $logBuffer .= $block;
- if ( strpos( $block, "\n" ) !== false ) {
- $lines = explode( "\n", $logBuffer );
- $logBuffer = array_pop( $lines );
- foreach ( $lines as $line ) {
- wfDebugLog( 'exec', $line );
- }
- }
- }
- }
- }
-
- foreach ( $pipes as $pipe ) {
- fclose( $pipe );
- }
-
- // Use the status previously collected if possible, since proc_get_status()
- // just calls waitpid() which will not return anything useful the second time.
- if ( $running ) {
- $status = proc_get_status( $proc );
- }
-
- if ( $logMsg !== false ) {
- // Read/select error
- $retval = -1;
- proc_close( $proc );
- } elseif ( $status['signaled'] ) {
- $logMsg = "Exited with signal {$status['termsig']}";
- $retval = 128 + $status['termsig'];
- proc_close( $proc );
- } else {
- if ( $status['running'] ) {
- $retval = proc_close( $proc );
- } else {
- $retval = $status['exitcode'];
- proc_close( $proc );
- }
- if ( $retval == 127 ) {
- $logMsg = "Possibly missing executable file";
- } elseif ( $retval >= 129 && $retval <= 192 ) {
- $logMsg = "Probably exited with signal " . ( $retval - 128 );
- }
- }
-
- if ( $logMsg !== false ) {
- wfDebugLog( 'exec', "$logMsg: $cmd" );
- }
-
- return $outBuffer;
+ return $result->getStdout();
}
/**
* @param array $limits Optional array with limits(filesize, memory, time, walltime)
* this overwrites the global wgMaxShell* limits.
* @return string Collected stdout and stderr as a string
+ * @deprecated since 1.30 use class MediaWiki\Shell\Shell
*/
function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = [] ) {
return wfShellExec( $cmd, $retval, $environ, $limits,
* @see $wgShellLocale
*/
function wfInitShellLocale() {
+ wfDeprecated( __FUNCTION__, '1.30' );
}
/**
}
$cmd[] = $script;
// Escape each parameter for shell
- return wfEscapeShellArg( array_merge( $cmd, $parameters ) );
+ return Shell::escape( array_merge( $cmd, $parameters ) );
}
/**
fclose( $yourtextFile );
# Check for a conflict
- $cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName,
+ $cmd = Shell::escape( $wgDiff3, '-a', '--overlap-only', $mytextName,
$oldtextName, $yourtextName );
$handle = popen( $cmd, 'r' );
pclose( $handle );
# Merge differences
- $cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName,
+ $cmd = Shell::escape( $wgDiff3, '-a', '-e', '--merge', $mytextName,
$oldtextName, $yourtextName );
$handle = popen( $cmd, 'r' );
$result = '';
fclose( $newtextFile );
// Get the diff of the two files
- $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
+ $cmd = "$wgDiff " . $params . ' ' . Shell::escape( $oldtextName, $newtextName );
$h = popen( $cmd, 'r' );
if ( !$h ) {
* @see perldoc -f use
*
* @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
+ *
+ * @deprecated since 1.30
+ *
* @throws MWException
*/
function wfUsePHP( $req_ver ) {
*
* @see perldoc -f use
*
- * @deprecated since 1.26, use the "requires' property of extension.json
+ * @deprecated since 1.26, use the "requires" property of extension.json
* @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
* @throws MWException
*/
return Wikimedia\base_convert( $input, $sourceBase, $destBase, $pad, $lowercase, $engine );
}
-/**
- * @deprecated since 1.27, PHP's session generation isn't used with
- * MediaWiki\Session\SessionManager
- */
-function wfFixSessionID() {
- wfDeprecated( __FUNCTION__, '1.27' );
-}
-
/**
* Reset the session id
*
/**
* Get the normalised IETF language tag
* See unit test for examples.
+ * See mediawiki.language.bcp47 for the JavaScript implementation.
+ *
+ * @deprecated since 1.31, use LanguageCode::bcp47() directly.
*
* @param string $code The language code.
* @return string The language code which complying with BCP 47 standards.
*/
function wfBCP47( $code ) {
- $codeSegment = explode( '-', $code );
- $codeBCP = [];
- foreach ( $codeSegment as $segNo => $seg ) {
- // when previous segment is x, it is a private segment and should be lc
- if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
- $codeBCP[$segNo] = strtolower( $seg );
- // ISO 3166 country code
- } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
- $codeBCP[$segNo] = strtoupper( $seg );
- // ISO 15924 script code
- } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
- $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
- // Use lowercase for other cases
- } else {
- $codeBCP[$segNo] = strtolower( $seg );
- }
- }
- $langCode = implode( '-', $codeBCP );
- return $langCode;
+ return LanguageCode::bcp47( $code );
}
/**