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() {
