Index: COPYING =================================================================== --- COPYING (revision 46670) +++ COPYING (working copy) @@ -1,33 +1,8 @@ -The ParserFunctions extension may be copied and redistributed under either the -DWTFYWWI license or the GNU General Public License, at the option of the -licensee. The text of both licenses is given below. +The ParserFunctions extension may be copied and redistributed under the GNU +General Public License. -The majority of this extension is written by (and copyright) Tim Starling. Minor -modifications have been made by various members of the MediaWiki development -team. - ------------------------------------------------------------------------------- - DWTFYWWI LICENSE - Version 1, January 2006 - - Copyright (C) 2006 Ævar Arnfjörð Bjarmason - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the DWTFYWWI or Do -Whatever The Fuck You Want With It license is intended to guarantee -your freedom to share and change the software--to make sure the -software is free for all its users. - - DWTFYWWI LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION -0. The author grants everyone permission to do whatever the fuck they -want with the software, whatever the fuck that may be. - -------------------------------------------------------------------------------- - GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Index: ParserFunctions.i18n.magic.php =================================================================== --- ParserFunctions.i18n.magic.php (revision 46670) +++ ParserFunctions.i18n.magic.php (working copy) @@ -25,6 +25,12 @@ 'timel' => array( 0, 'timel' ), 'rel2abs' => array( 0, 'rel2abs' ), 'titleparts' => array( 0, 'titleparts' ), + 'len' => array( 0, 'len' ), + 'pos' => array( 0, 'pos' ), + 'rpos' => array( 0, 'rpos' ), + 'sub' => array( 0, 'sub' ), + 'replace' => array( 0, 'replace' ), + 'explode' => array( 0, 'explode' ), ); /** Index: ParserFunctions.i18n.php =================================================================== --- ParserFunctions.i18n.php (revision 46670) +++ ParserFunctions.i18n.php (working copy) @@ -26,6 +26,7 @@ 'pfunc_expr_invalid_argument_ln' => 'Invalid argument for ln: <= 0', 'pfunc_expr_unknown_error' => 'Expression error: Unknown error ($1)', 'pfunc_expr_not_a_number' => 'In $1: result is not a number', + 'pfunc_string_too_long' => 'Error: string exceeds $1 character limit', ); /** Message documentation (Message documentation) Index: ParserFunctions.php =================================================================== --- ParserFunctions.php (revision 49109) +++ ParserFunctions.php (working copy) @@ -7,24 +7,33 @@ $wgExtensionFunctions[] = 'wfSetupParserFunctions'; $wgExtensionCredits['parserhook'][] = array( 'name' => 'ParserFunctions', - 'version' => '1.1.1', + 'version' => '2.0.0', 'url' => 'http://www.mediawiki.org/wiki/Extension:ParserFunctions', - 'author' => 'Tim Starling', + 'author' => array('Tim Starling', 'Robert Rohde', 'Ross McClure', 'Juraj Simlovic'), 'description' => 'Enhance parser with logical functions', 'descriptionmsg' => 'pfunc_desc', ); $wgExtensionMessagesFiles['ParserFunctions'] = dirname(__FILE__) . '/ParserFunctions.i18n.php'; $wgHooks['LanguageGetMagic'][] = 'wfParserFunctionsLanguageGetMagic'; +$wgHooks['ParserAfterStrip'][] = 'ExtParserFunctions::loadRegex'; $wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt"; +//Defines the maximum length of a string that string functions are allowed to operate on +//Prevention against DOS by string function. +if( !isset($wgStringFunctionsLimit) ) { + $wgStringFunctionsLimit = 1000; +} + class ExtParserFunctions { var $mExprParser; var $mTimeCache = array(); var $mTimeChars = 0; var $mMaxTimeChars = 6000; # ~10 seconds + static $markerRegex = false; + function registerParser( &$parser ) { if ( defined( get_class( $parser ) . '::SFH_OBJECT_ARGS' ) ) { // These functions accept DOM-style arguments @@ -49,6 +58,14 @@ $parser->setFunctionHook( 'rel2abs', array( &$this, 'rel2abs' ) ); $parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) ); + //String Functions + $parser->setFunctionHook( 'len', array(&$this, 'runLen' )); + $parser->setFunctionHook( 'pos', array(&$this, 'runPos' )); + $parser->setFunctionHook( 'rpos', array(&$this, 'runRPos' )); + $parser->setFunctionHook( 'sub', array(&$this, 'runSub' )); + $parser->setFunctionHook( 'replace', array(&$this, 'runReplace' )); + $parser->setFunctionHook( 'explode', array(&$this, 'runExplode' )); + return true; } @@ -58,6 +75,36 @@ return true; } + /* Called by ParserAfterStrip. Preloads the syntax for unique markers + so that we can avoid reconstructing it on every operation. */ + static function loadRegex( &$parser ) { + wfProfileIn( __METHOD__ ); + + $prefix = preg_quote( $parser->mUniqPrefix, '/' ); + + if ( defined('Parser::MARKER_SUFFIX') ) + $suffix = preg_quote( Parser::MARKER_SUFFIX, '/' ); + elseif ( isset($parser->mMarkerSuffix) ) + $suffix = preg_quote( $parser->mMarkerSuffix, '/' ); + elseif ( defined('MW_PARSER_VERSION') && strcmp( MW_PARSER_VERSION, '1.6.1' ) > 0 ) + $suffix = "QINU\x07"; + else $suffix = 'QINU'; + + self::$markerRegex = '/' .$prefix. '(?:(?!' .$suffix. ').)*' . $suffix . '/us'; + + wfProfileOut( __METHOD__ ); + return true; + } + + // Removes unique markers from passed parameters, used by string functions. + private function killMarkers ( $text ) { + if( self::$markerRegex ) { + return preg_replace( self::$markerRegex , '' , $text ); + } else { + return $text; + } + } + function &getExprParser() { if ( !isset( $this->mExprParser ) ) { if ( !class_exists( 'ExprParser' ) ) { @@ -516,6 +563,188 @@ return $title; } } + + // Verifies parameter is less than max string length. + private function checkLength( $text ) { + global $wgStringFunctionsLimit; + return ( mb_strlen( $text ) < $wgStringFunctionsLimit ); + } + + // Generates error message. Called when string is too long. + private function tooLongError() { + global $wgStringFunctionsLimit; + return wfMsg( 'pfunc_string_too_long', $wgStringFunctionsLimit ); + } + + /** + * {{#len:string}} + */ + function runLen ( &$parser, $inStr = '' ) { + wfProfileIn( __METHOD__ ); + + $inStr = $this->killMarkers( (string)$inStr ); + $len = mb_strlen( $inStr ); + + wfProfileOut( __METHOD__ ); + return $len; + } + + /** + * {{#pos:value|key|offset}} + * Note: If the needle is an empty string, single space is used instead. + * Note: If the needle is not found, -1 is returned. + */ + function runPos ( &$parser, $inStr = '', $inNeedle = '', $inOffset = 0 ) { + wfProfileIn( __METHOD__ ); + + $inStr = $this->killMarkers( (string)$inStr ); + $inNeedle = $this->killMarkers( (string)$inNeedle ); + + if( !$this->checkLength( $inStr ) || + !$this->checkLength( $inNeedle ) ) { + wfProfileOut( __METHOD__ ); + return $this->tooLongError(); + } + + if( $inNeedle == '' ) { $inNeedle = ' '; } + + $pos = mb_strpos( $inStr, $inNeedle, $inOffset ); + if( $pos === false ) { $pos = -1; } + + wfProfileOut( __METHOD__ ); + return $pos; + } + + /** + * {{#rpos:value|key}} + * Note: If the needle is an empty string, single space is used instead. + * Note: If the needle is not found, -1 is returned. + */ + function runRPos ( &$parser, $inStr = '', $inNeedle = '' ) { + wfProfileIn( __METHOD__ ); + + $inStr = $this->killMarkers( (string)$inStr ); + $inNeedle = $this->killMarkers( (string)$inNeedle ); + + if( !$this->checkLength( $inStr ) || + !$this->checkLength( $inNeedle ) ) { + wfProfileOut( __METHOD__ ); + return $this->tooLongError(); + } + + if( $inNeedle == '' ) { $inNeedle = ' '; } + + $pos = mb_strrpos( $inStr, $inNeedle ); + if( $pos === false ) { $pos = -1; } + + wfProfileOut( __METHOD__ ); + return $pos; + } + + /** + * {{#sub:value|start|length}} + * Note: If length is zero, the rest of the input is returned. + */ + function runSub ( &$parser, $inStr = '', $inStart = 0, $inLength = 0 ) { + wfProfileIn( __METHOD__ ); + + $inStr = $this->killMarkers( (string)$inStr ); + + if( !$this->checkLength( $inStr ) ) { + wfProfileOut( __METHOD__ ); + return $this->tooLongError(); + } + + if ( intval($inLength) == 0 ) { + $result = mb_substr( $inStr, $inStart ); + } else { + $result = mb_substr( $inStr, $inStart, $inLength ); + } + + wfProfileOut( __METHOD__ ); + return $result; + } + + /** + * {{#replace:value|from|to}} + * Note: If "from" is an empty string, single space is used instead. + */ + function runReplace( &$parser, $inStr = '', $inReplaceFrom = '', $inReplaceTo = '' ) { + global $wgStringFunctionsLimit; + wfProfileIn( __METHOD__ ); + + $inStr = $this->killMarkers( (string)$inStr ); + $inReplaceFrom = $this->killMarkers( (string)$inReplaceFrom ); + $inReplaceTo = $this->killMarkers( (string)$inReplaceTo ); + + if( !$this->checkLength( $inStr ) || + !$this->checkLength( $inReplaceFrom ) || + !$this->checkLength( $inReplaceTo ) ) { + wfProfileOut( __METHOD__ ); + return $this->tooLongError(); + } + + if( $inReplaceFrom == '' ) { $inReplaceFrom = ' '; } + + // Precompute limit to avoid generating enormous string: + $diff = mb_strlen( $inReplaceTo ) - mb_strlen( $inReplaceFrom ); + if( $diff > 0 ) { + $limit = ( ( $wgStringFunctionsLimit - mb_strlen( $inStr ) ) / $diff ) + 1; + } else { + $limit = -1; + } + + $inReplaceFrom = preg_quote( $inReplaceFrom, '/' ); + $inReplaceTo = preg_quote( $inReplaceTo, '/' ); + + $result = preg_replace( '/' . $inReplaceFrom . '/u', $inReplaceTo, $inStr, $limit); + + if( !$this->checkLength( $result ) ) { + wfProfileOut( __METHOD__ ); + return $this->tooLongError(); + } + + wfProfileOut( __METHOD__ ); + return $result; + } + + + /** + * {{#explode:value|delimiter|position}} + * Note: Negative position can be used to specify tokens from the end. + * Note: If the divider is an empty string, single space is used instead. + * Note: Empty string is returned, if there is not enough exploded chunks. + */ + function runExplode ( &$parser, $inStr = '', $inDiv = '', $inPos = 0 ) { + wfProfileIn( __METHOD__ ); + + $inStr = $this->killMarkers( (string)$inStr ); + $inDiv = $this->killMarkers( (string)$inDiv ); + + if( $inDiv == '' ) { $inDiv = ' '; } + + $inStr = preg_quote( $inStr, '/' ); + $inDiv = preg_quote( $inDiv, '/' ); + + if( !$this->checkLength( $inStr ) || + !$this->checkLength( $inDiv ) ) { + wfProfileOut( __METHOD__ ); + return $this->tooLongError(); + } + + $matches = preg_split( '/'.$inDiv.'/u', $inStr ); + + if( $inPos >= 0 && isset( $matches[$inPos] ) ) { + $result = $matches[$inPos]; + } elseif ( $inPos < 0 && isset( $matches[count($matches) + $inPos] ) ) { + $result = $matches[count($matches) + $inPos]; + } else { + $result = ''; + } + + wfProfileOut( __METHOD__ ); + return $result; + } } function wfSetupParserFunctions() {