summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2012-03-22 21:04:56 +0100
committerPierre Schmitz <pierre@archlinux.de>2012-03-22 21:04:56 +0100
commit81be3ba123fa26c29ab157288530ffaec9d0930f (patch)
tree8054ad0536e27b20838d85a05884ca47752537dc /includes
parentba0fc4fa20067528effd4802e53ceeb959640825 (diff)
Update to MediaWiki 1.18.2
Diffstat (limited to 'includes')
-rw-r--r--includes/AutoLoader.php1
-rw-r--r--includes/CryptRand.php463
-rw-r--r--includes/DefaultSettings.php9
-rw-r--r--includes/GlobalFunctions.php29
-rw-r--r--includes/OutputPage.php10
-rw-r--r--includes/Skin.php4
-rw-r--r--includes/User.php77
-rw-r--r--includes/UserMailer.php2
-rw-r--r--includes/WikiPage.php2
-rw-r--r--includes/api/ApiMain.php2
-rw-r--r--includes/api/ApiParse.php7
-rw-r--r--includes/api/ApiQueryRevisions.php2
-rw-r--r--includes/installer/Installer.php29
-rw-r--r--includes/installer/OracleInstaller.php4
-rw-r--r--includes/parser/CoreParserFunctions.php40
-rw-r--r--includes/parser/Parser.php10
-rw-r--r--includes/parser/StripState.php10
-rw-r--r--includes/resourceloader/ResourceLoader.php42
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php31
-rw-r--r--includes/specials/SpecialUpload.php9
-rw-r--r--includes/specials/SpecialUserlogin.php6
-rw-r--r--includes/specials/SpecialWatchlist.php2
-rw-r--r--includes/specials/SpecialWhatlinkshere.php1
-rw-r--r--includes/upload/UploadFromStash.php83
-rw-r--r--includes/upload/UploadStash.php118
25 files changed, 726 insertions, 267 deletions
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 134e53ea..d8263ba9 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -150,6 +150,7 @@ $wgAutoloadLocalClasses = array(
'Message' => 'includes/Message.php',
'MessageBlobStore' => 'includes/MessageBlobStore.php',
'MimeMagic' => 'includes/MimeMagic.php',
+ 'MWCryptRand' => 'includes/CryptRand.php',
'MWException' => 'includes/Exception.php',
'MWExceptionHandler' => 'includes/Exception.php',
'MWFunction' => 'includes/MWFunction.php',
diff --git a/includes/CryptRand.php b/includes/CryptRand.php
new file mode 100644
index 00000000..10f379cb
--- /dev/null
+++ b/includes/CryptRand.php
@@ -0,0 +1,463 @@
+<?php
+/**
+ * A cryptographic random generator class used for generating secret keys
+ *
+ * This is based in part on Drupal code as well as what we used in our own code
+ * prior to introduction of this class.
+ *
+ * @author Daniel Friesen
+ * @file
+ */
+
+class MWCryptRand {
+
+ /**
+ * Minimum number of iterations we want to make in our drift calculations.
+ */
+ const MIN_ITERATIONS = 1000;
+
+ /**
+ * Number of milliseconds we want to spend generating each separate byte
+ * of the final generated bytes.
+ * This is used in combination with the hash length to determine the duration
+ * we should spend doing drift calculations.
+ */
+ const MSEC_PER_BYTE = 0.5;
+
+ /**
+ * Singleton instance for public use
+ */
+ protected static $singleton = null;
+
+ /**
+ * The hash algorithm being used
+ */
+ protected $algo = null;
+
+ /**
+ * The number of bytes outputted by the hash algorithm
+ */
+ protected $hashLength = null;
+
+ /**
+ * A boolean indicating whether the previous random generation was done using
+ * cryptographically strong random number generator or not.
+ */
+ protected $strong = null;
+
+ /**
+ * Initialize an initial random state based off of whatever we can find
+ */
+ protected function initialRandomState() {
+ // $_SERVER contains a variety of unstable user and system specific information
+ // It'll vary a little with each page, and vary even more with separate users
+ // It'll also vary slightly across different machines
+ $state = serialize( $_SERVER );
+
+ // To try and vary the system information of the state a bit more
+ // by including the system's hostname into the state
+ $state .= wfHostname();
+
+ // Try to gather a little entropy from the different php rand sources
+ $state .= rand() . uniqid( mt_rand(), true );
+
+ // Include some information about the filesystem's current state in the random state
+ $files = array();
+ // We know this file is here so grab some info about ourself
+ $files[] = __FILE__;
+ // The config file is likely the most often edited file we know should be around
+ // so if the constant with it's location is defined include it's stat info into the state
+ if ( defined( 'MW_CONFIG_FILE' ) ) {
+ $files[] = MW_CONFIG_FILE;
+ }
+ foreach ( $files as $file ) {
+ wfSuppressWarnings();
+ $stat = stat( $file );
+ wfRestoreWarnings();
+ if ( $stat ) {
+ // stat() duplicates data into numeric and string keys so kill off all the numeric ones
+ foreach ( $stat as $k => $v ) {
+ if ( is_numeric( $k ) ) {
+ unset( $k );
+ }
+ }
+ // The absolute filename itself will differ from install to install so don't leave it out
+ $state .= realpath( $file );
+ $state .= implode( '', $stat );
+ } else {
+ // The fact that the file isn't there is worth at least a
+ // minuscule amount of entropy.
+ $state .= '0';
+ }
+ }
+
+ // Try and make this a little more unstable by including the varying process
+ // id of the php process we are running inside of if we are able to access it
+ if ( function_exists( 'getmypid' ) ) {
+ $state .= getmypid();
+ }
+
+ // If available try to increase the instability of the data by throwing in
+ // the precise amount of memory that we happen to be using at the moment.
+ if ( function_exists( 'memory_get_usage' ) ) {
+ $state .= memory_get_usage( true );
+ }
+
+ // It's mostly worthless but throw the wiki's id into the data for a little more variance
+ $state .= wfWikiID();
+
+ // If we have a secret key or proxy key set then throw it into the state as well
+ global $wgSecretKey, $wgProxyKey;
+ if ( $wgSecretKey ) {
+ $state .= $wgSecretKey;
+ } elseif ( $wgProxyKey ) {
+ $state .= $wgProxyKey;
+ }
+
+ return $state;
+ }
+
+ /**
+ * Randomly hash data while mixing in clock drift data for randomness
+ *
+ * @param $data The data to randomly hash.
+ * @return String The hashed bytes
+ * @author Tim Starling
+ */
+ protected function driftHash( $data ) {
+ // Minimum number of iterations (to avoid slow operations causing the loop to gather little entropy)
+ $minIterations = self::MIN_ITERATIONS;
+ // Duration of time to spend doing calculations (in seconds)
+ $duration = ( self::MSEC_PER_BYTE / 1000 ) * $this->hashLength();
+ // Create a buffer to use to trigger memory operations
+ $bufLength = 10000000;
+ $buffer = str_repeat( ' ', $bufLength );
+ $bufPos = 0;
+
+ // Iterate for $duration seconds or at least $minIerations number of iterations
+ $iterations = 0;
+ $startTime = microtime( true );
+ $currentTime = $startTime;
+ while ( $iterations < $minIterations || $currentTime - $startTime < $duration ) {
+ // Trigger some memory writing to trigger some bus activity
+ // This may create variance in the time between iterations
+ $bufPos = ( $bufPos + 13 ) % $bufLength;
+ $buffer[$bufPos] = ' ';
+ // Add the drift between this iteration and the last in as entropy
+ $nextTime = microtime( true );
+ $delta = (int)( ( $nextTime - $currentTime ) * 1000000 );
+ $data .= $delta;
+ // Every 100 iterations hash the data and entropy
+ if ( $iterations % 100 === 0 ) {
+ $data = sha1( $data );
+ }
+ $currentTime = $nextTime;
+ $iterations++;
+ }
+ $timeTaken = $currentTime - $startTime;
+ $data = $this->hash( $data );
+
+ wfDebug( __METHOD__ . ": Clock drift calculation " .
+ "(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
+ "iterations=$iterations, " .
+ "time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)\n" );
+ return $data;
+ }
+
+ /**
+ * Return a rolling random state initially build using data from unstable sources
+ * @return A new weak random state
+ */
+ protected function randomState() {
+ static $state = null;
+ if ( is_null( $state ) ) {
+ // Initialize the state with whatever unstable data we can find
+ // It's important that this data is hashed right afterwards to prevent
+ // it from being leaked into the output stream
+ $state = $this->hash( $this->initialRandomState() );
+ }
+ // Generate a new random state based on the initial random state or previous
+ // random state by combining it with clock drift
+ $state = $this->driftHash( $state );
+ return $state;
+ }
+
+ /**
+ * Decide on the best acceptable hash algorithm we have available for hash()
+ * @return String A hash algorithm
+ */
+ protected function hashAlgo() {
+ if ( !is_null( $this->algo ) ) {
+ return $this->algo;
+ }
+
+ $algos = hash_algos();
+ $preference = array( 'whirlpool', 'sha256', 'sha1', 'md5' );
+
+ foreach ( $preference as $algorithm ) {
+ if ( in_array( $algorithm, $algos ) ) {
+ $this->algo = $algorithm;
+ wfDebug( __METHOD__ . ": Using the {$this->algo} hash algorithm.\n" );
+ return $this->algo;
+ }
+ }
+
+ // We only reach here if no acceptable hash is found in the list, this should
+ // be a technical impossibility since most of php's hash list is fixed and
+ // some of the ones we list are available as their own native functions
+ // But since we already require at least 5.2 and hash() was default in
+ // 5.1.2 we don't bother falling back to methods like sha1 and md5.
+ throw new MWException( "Could not find an acceptable hashing function in hash_algos()" );
+ }
+
+ /**
+ * Return the byte-length output of the hash algorithm we are
+ * using in self::hash and self::hmac.
+ *
+ * @return int Number of bytes the hash outputs
+ */
+ protected function hashLength() {
+ if ( is_null( $this->hashLength ) ) {
+ $this->hashLength = strlen( $this->hash( '' ) );
+ }
+ return $this->hashLength;
+ }
+
+ /**
+ * Generate an acceptably unstable one-way-hash of some text
+ * making use of the best hash algorithm that we have available.
+ *
+ * @return String A raw hash of the data
+ */
+ protected function hash( $data ) {
+ return hash( $this->hashAlgo(), $data, true );
+ }
+
+ /**
+ * Generate an acceptably unstable one-way-hmac of some text
+ * making use of the best hash algorithm that we have available.
+ *
+ * @return String A raw hash of the data
+ */
+ protected function hmac( $data, $key ) {
+ return hash_hmac( $this->hashAlgo(), $data, $key, true );
+ }
+
+ /**
+ * @see self::wasStrong()
+ */
+ public function realWasStrong() {
+ if ( is_null( $this->strong ) ) {
+ throw new MWException( __METHOD__ . ' called before generation of random data' );
+ }
+ return $this->strong;
+ }
+
+ /**
+ * @see self::generate()
+ */
+ public function realGenerate( $bytes, $forceStrong = false ) {
+ wfProfileIn( __METHOD__ );
+
+ wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " . wfGetAllCallers( 5 ) . "\n" );
+
+ $bytes = floor( $bytes );
+ static $buffer = '';
+ if ( is_null( $this->strong ) ) {
+ // Set strength to false initially until we know what source data is coming from
+ $this->strong = true;
+ }
+
+ if ( strlen( $buffer ) < $bytes ) {
+ // If available make use of mcrypt_create_iv URANDOM source to generate randomness
+ // On unix-like systems this reads from /dev/urandom but does it without any buffering
+ // and bypasses openbasdir restrictions so it's preferable to reading directly
+ // On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate
+ // entropy so this is also preferable to just trying to read urandom because it may work
+ // on Windows systems as well.
+ if ( function_exists( 'mcrypt_create_iv' ) ) {
+ wfProfileIn( __METHOD__ . '-mcrypt' );
+ $rem = $bytes - strlen( $buffer );
+ $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
+ if ( $iv === false ) {
+ wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" );
+ } else {
+ $bytes .= $iv;
+ wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" );
+ }
+ wfProfileOut( __METHOD__ . '-mcrypt' );
+ }
+ }
+
+ if ( strlen( $buffer ) < $bytes ) {
+ // If available make use of openssl's random_pesudo_bytes method to attempt to generate randomness.
+ // However don't do this on Windows with PHP < 5.3.4 due to a bug:
+ // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
+ if ( function_exists( 'openssl_random_pseudo_bytes' )
+ && ( !wfIsWindows() || version_compare( PHP_VERSION, '5.3.4', '>=' ) )
+ ) {
+ wfProfileIn( __METHOD__ . '-openssl' );
+ $rem = $bytes - strlen( $buffer );
+ $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
+ if ( $openssl_bytes === false ) {
+ wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes returned false.\n" );
+ } else {
+ $buffer .= $openssl_bytes;
+ wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes generated " . strlen( $openssl_bytes ) . " bytes of " . ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" );
+ }
+ if ( strlen( $buffer ) >= $bytes ) {
+ // openssl tells us if the random source was strong, if some of our data was generated
+ // using it use it's say on whether the randomness is strong
+ $this->strong = !!$openssl_strong;
+ }
+ wfProfileOut( __METHOD__ . '-openssl' );
+ }
+ }
+
+ // Only read from urandom if we can control the buffer size or were passed forceStrong
+ if ( strlen( $buffer ) < $bytes && ( function_exists( 'stream_set_read_buffer' ) || $forceStrong ) ) {
+ wfProfileIn( __METHOD__ . '-fopen-urandom' );
+ $rem = $bytes - strlen( $buffer );
+ if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
+ wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom without control over the buffer size.\n" );
+ }
+ // /dev/urandom is generally considered the best possible commonly
+ // available random source, and is available on most *nix systems.
+ wfSuppressWarnings();
+ $urandom = fopen( "/dev/urandom", "rb" );
+ wfRestoreWarnings();
+
+ // Attempt to read all our random data from urandom
+ // php's fread always does buffered reads based on the stream's chunk_size
+ // so in reality it will usually read more than the amount of data we're
+ // asked for and not storing that risks depleting the system's random pool.
+ // If stream_set_read_buffer is available set the chunk_size to the amount
+ // of data we need. Otherwise read 8k, php's default chunk_size.
+ if ( $urandom ) {
+ // php's default chunk_size is 8k
+ $chunk_size = 1024 * 8;
+ if ( function_exists( 'stream_set_read_buffer' ) ) {
+ // If possible set the chunk_size to the amount of data we need
+ stream_set_read_buffer( $urandom, $rem );
+ $chunk_size = $rem;
+ }
+ $random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
+ $buffer .= $random_bytes;
+ fclose( $urandom );
+ wfDebug( __METHOD__ . ": /dev/urandom generated " . strlen( $random_bytes ) . " bytes of randomness.\n" );
+ if ( strlen( $buffer ) >= $bytes ) {
+ // urandom is always strong, set to true if all our data was generated using it
+ $this->strong = true;
+ }
+ } else {
+ wfDebug( __METHOD__ . ": /dev/urandom could not be opened.\n" );
+ }
+ wfProfileOut( __METHOD__ . '-fopen-urandom' );
+ }
+
+ // If we cannot use or generate enough data from a secure source
+ // use this loop to generate a good set of pseudo random data.
+ // This works by initializing a random state using a pile of unstable data
+ // and continually shoving it through a hash along with a variable salt.
+ // We hash the random state with more salt to avoid the state from leaking
+ // out and being used to predict the /randomness/ that follows.
+ if ( strlen( $buffer ) < $bytes ) {
+ wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
+ }
+ while ( strlen( $buffer ) < $bytes ) {
+ wfProfileIn( __METHOD__ . '-fallback' );
+ $buffer .= $this->hmac( $this->randomState(), mt_rand() );
+ // This code is never really cryptographically strong, if we use it
+ // at all, then set strong to false.
+ $this->strong = false;
+ wfProfileOut( __METHOD__ . '-fallback' );
+ }
+
+ // Once the buffer has been filled up with enough random data to fulfill
+ // the request shift off enough data to handle the request and leave the
+ // unused portion left inside the buffer for the next request for random data
+ $generated = substr( $buffer, 0, $bytes );
+ $buffer = substr( $buffer, $bytes );
+
+ wfDebug( __METHOD__ . ": " . strlen( $buffer ) . " bytes of randomness leftover in the buffer.\n" );
+
+ wfProfileOut( __METHOD__ );
+ return $generated;
+ }
+
+ /**
+ * @see self::generateHex()
+ */
+ public function realGenerateHex( $chars, $forceStrong = false ) {
+ // hex strings are 2x the length of raw binary so we divide the length in half
+ // odd numbers will result in a .5 that leads the generate() being 1 character
+ // short, so we use ceil() to ensure that we always have enough bytes
+ $bytes = ceil( $chars / 2 );
+ // Generate the data and then convert it to a hex string
+ $hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
+ // A bit of paranoia here, the caller asked for a specific length of string
+ // here, and it's possible (eg when given an odd number) that we may actually
+ // have at least 1 char more than they asked for. Just in case they made this
+ // call intending to insert it into a database that does truncation we don't
+ // want to give them too much and end up with their database and their live
+ // code having two different values because part of what we gave them is truncated
+ // hence, we strip out any run of characters longer than what we were asked for.
+ return substr( $hex, 0, $chars );
+ }
+
+ /** Publicly exposed static methods **/
+
+ /**
+ * Return a singleton instance of MWCryptRand
+ */
+ protected static function singleton() {
+ if ( is_null( self::$singleton ) ) {
+ self::$singleton = new self;
+ }
+ return self::$singleton;
+ }
+
+ /**
+ * Return a boolean indicating whether or not the source used for cryptographic
+ * random bytes generation in the previously run generate* call
+ * was cryptographically strong.
+ *
+ * @return bool Returns true if the source was strong, false if not.
+ */
+ public static function wasStrong() {
+ return self::singleton()->realWasStrong();
+ }
+
+ /**
+ * Generate a run of (ideally) cryptographically random data and return
+ * it in raw binary form.
+ * You can use MWCryptRand::wasStrong() if you wish to know if the source used
+ * was cryptographically strong.
+ *
+ * @param $bytes int the number of bytes of random data to generate
+ * @param $forceStrong bool Pass true if you want generate to prefer cryptographically
+ * strong sources of entropy even if reading from them may steal
+ * more entropy from the system than optimal.
+ * @return String Raw binary random data
+ */
+ public static function generate( $bytes, $forceStrong = false ) {
+ return self::singleton()->realGenerate( $bytes, $forceStrong );
+ }
+
+ /**
+ * Generate a run of (ideally) cryptographically random data and return
+ * it in hexadecimal string format.
+ * You can use MWCryptRand::wasStrong() if you wish to know if the source used
+ * was cryptographically strong.
+ *
+ * @param $chars int the number of hex chars of random data to generate
+ * @param $forceStrong bool Pass true if you want generate to prefer cryptographically
+ * strong sources of entropy even if reading from them may steal
+ * more entropy from the system than optimal.
+ * @return String Hexadecimal random data
+ */
+ public static function generateHex( $chars, $forceStrong = false ) {
+ return self::singleton()->realGenerateHex( $chars, $forceStrong );
+ }
+
+}
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 9d387fb5..7cea30f6 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -33,7 +33,7 @@ $wgConf = new SiteConfiguration;
/** @endcond */
/** MediaWiki version number */
-$wgVersion = '1.18.1';
+$wgVersion = '1.18.2';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
@@ -2494,13 +2494,6 @@ $wgResourceLoaderMaxage = array(
);
/**
- * Whether to embed private modules inline with HTML output or to bypass
- * caching and check the user parameter against $wgUser to prevent
- * unauthorized access to private modules.
- */
-$wgResourceLoaderInlinePrivateModules = true;
-
-/**
* The default debug mode (on/off) for of ResourceLoader requests. This will still
* be overridden when the debug URL parameter is used.
*/
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 3424211f..8ed79c40 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -3065,6 +3065,33 @@ function wfHttpOnlySafe() {
}
/**
+ * Override session_id before session startup if php's built-in
+ * session generation code is not secure.
+ */
+function wfFixSessionID() {
+ // If the cookie or session id is already set we already have a session and should abort
+ if ( isset( $_COOKIE[ session_name() ] ) || session_id() ) {
+ return;
+ }
+
+ // PHP's built-in session entropy is enabled if:
+ // - entropy_file is set or you're on Windows with php 5.3.3+
+ // - AND entropy_length is > 0
+ // We treat it as disabled if it doesn't have an entropy length of at least 32
+ $entropyEnabled = (
+ ( wfIsWindows() && version_compare( PHP_VERSION, '5.3.3', '>=' ) )
+ || ini_get( 'session.entropy_file' )
+ )
+ && intval( ini_get( 'session.entropy_length' ) ) >= 32;
+
+ // If built-in entropy is not enabled or not sufficient override php's built in session id generation code
+ if ( !$entropyEnabled ) {
+ wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, overriding session id generation using our cryptrand source.\n" );
+ session_id( MWCryptRand::generateHex( 32 ) );
+ }
+}
+
+/**
* Initialise php session
*
* @param $sessionId Bool
@@ -3103,6 +3130,8 @@ function wfSetupSession( $sessionId = false ) {
session_cache_limiter( 'private, must-revalidate' );
if ( $sessionId ) {
session_id( $sessionId );
+ } else {
+ wfFixSessionID();
}
wfSuppressWarnings();
session_start();
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index a3a9592d..68c771bc 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -2359,8 +2359,7 @@ $templates
* @return string html <script> and <style> tags
*/
protected function makeResourceLoaderLink( Skin $skin, $modules, $only, $useESI = false ) {
- global $wgLoadScript, $wgResourceLoaderUseESI,
- $wgResourceLoaderInlinePrivateModules;
+ global $wgLoadScript, $wgResourceLoaderUseESI;
// Lazy-load ResourceLoader
// TODO: Should this be a static function of ResourceLoader instead?
$baseQuery = array(
@@ -2444,8 +2443,11 @@ $templates
$query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $modules ) );
- // Support inlining of private modules if configured as such
- if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) {
+ // Inline private modules. These can't be loaded through load.php for security
+ // reasons, see bug 34907. Note that these modules should be loaded from
+ // getHeadScripts() before the first loader call. Otherwise other modules can't
+ // properly use them as dependencies (bug 30914)
+ if ( $group === 'private' ) {
if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
$links .= Html::inlineStyle(
$resourceLoader->makeModuleResponse( $context, $modules )
diff --git a/includes/Skin.php b/includes/Skin.php
index 62a63ebe..83e9ee98 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -1516,7 +1516,7 @@ abstract class Skin extends ContextSource {
if ( !is_null( $tooltip ) ) {
# Bug 25462: undo double-escaping.
$tooltip = Sanitizer::decodeCharReferences( $tooltip );
- $attribs['title'] = wfMsgExt( 'editsectionhint', array( 'language' => $lang, 'parsemag' ), $tooltip );
+ $attribs['title'] = wfMsgExt( 'editsectionhint', array( 'language' => $lang, 'parsemag', 'replaceafter' ), $tooltip );
}
$link = Linker::link( $nt, wfMsgExt( 'editsection', array( 'language' => $lang ) ),
$attribs,
@@ -1528,7 +1528,7 @@ abstract class Skin extends ContextSource {
# we can rid of it someday.
$attribs = '';
if ( $tooltip ) {
- $attribs = wfMsgExt( 'editsectionhint', array( 'language' => $lang, 'parsemag', 'escape' ), $tooltip );
+ $attribs = wfMsgExt( 'editsectionhint', array( 'language' => $lang, 'parsemag', 'escape', 'replaceafter' ), $tooltip );
$attribs = " title=\"$attribs\"";
}
$result = null;
diff --git a/includes/User.php b/includes/User.php
index cebcf604..705fd291 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -831,23 +831,20 @@ class User {
}
/**
- * Return a random password. Sourced from mt_rand, so it's not particularly secure.
- * @todo hash random numbers to improve security, like generateToken()
+ * Return a random password.
*
* @return String new random password
*/
public static function randomPassword() {
global $wgMinimalPasswordLength;
- $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
- $l = strlen( $pwchars ) - 1;
-
- $pwlength = max( 7, $wgMinimalPasswordLength );
- $digit = mt_rand( 0, $pwlength - 1 );
- $np = '';
- for ( $i = 0; $i < $pwlength; $i++ ) {
- $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars[ mt_rand( 0, $l ) ];
- }
- return $np;
+ // Decide the final password length based on our min password length, stopping at a minimum of 10 chars
+ $length = max( 10, $wgMinimalPasswordLength );
+ // Multiply by 1.25 to get the number of hex characters we need
+ $length = $length * 1.25;
+ // Generate random hex chars
+ $hex = MWCryptRand::generateHex( $length );
+ // Convert from base 16 to base 32 to get a proper password like string
+ return wfBaseConvert( $hex, 16, 32 );
}
/**
@@ -877,7 +874,7 @@ class User {
$this->mTouched = '0'; # Allow any pages to be cached
}
- $this->setToken(); # Random
+ $this->mToken = null; // Don't run cryptographic functions till we need a token
$this->mEmailAuthenticated = null;
$this->mEmailToken = '';
$this->mEmailTokenExpires = null;
@@ -984,11 +981,11 @@ class User {
return false;
}
- if ( $request->getSessionData( 'wsToken' ) !== null ) {
- $passwordCorrect = $proposedUser->getToken() === $request->getSessionData( 'wsToken' );
+ if ( $request->getSessionData( 'wsToken' ) ) {
+ $passwordCorrect = $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' );
$from = 'session';
- } elseif ( $request->getCookie( 'Token' ) !== null ) {
- $passwordCorrect = $proposedUser->getToken() === $request->getCookie( 'Token' );
+ } elseif ( $request->getCookie( 'Token' ) ) {
+ $passwordCorrect = $proposedUser->getToken( false ) === $request->getCookie( 'Token' );
$from = 'cookie';
} else {
# No session or persistent login cookie
@@ -1083,6 +1080,9 @@ class User {
$this->decodeOptions( $row->user_options );
$this->mTouched = wfTimestamp(TS_MW,$row->user_touched);
$this->mToken = $row->user_token;
+ if ( $this->mToken == '' ) {
+ $this->mToken = null;
+ }
$this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
$this->mEmailToken = $row->user_email_token;
$this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
@@ -1989,10 +1989,14 @@ class User {
/**
* Get the user's current token.
+ * @param $forceCreation Force the generation of a new token if the user doesn't have one (default=true for backwards compatibility)
* @return String Token
*/
- public function getToken() {
+ public function getToken( $forceCreation = true ) {
$this->load();
+ if ( !$this->mToken && $forceCreation ) {
+ $this->setToken();
+ }
return $this->mToken;
}
@@ -2006,14 +2010,7 @@ class User {
global $wgSecretKey, $wgProxyKey;
$this->load();
if ( !$token ) {
- if ( $wgSecretKey ) {
- $key = $wgSecretKey;
- } elseif ( $wgProxyKey ) {
- $key = $wgProxyKey;
- } else {
- $key = microtime();
- }
- $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . wfWikiID() . $this->mId );
+ $this->mToken = MWCryptRand::generateHex( USER_TOKEN_LENGTH );
} else {
$this->mToken = $token;
}
@@ -2718,6 +2715,14 @@ class User {
$this->load();
if ( 0 == $this->mId ) return;
+ if ( !$this->mToken ) {
+ // When token is empty or NULL generate a new one and then save it to the database
+ // This allows a wiki to re-secure itself after a leak of it's user table or $wgSecretKey
+ // Simply by setting every cell in the user_token column to NULL and letting them be
+ // regenerated as users log back into the wiki.
+ $this->setToken();
+ $this->saveSettings();
+ }
$session = array(
'wsUserID' => $this->mId,
'wsToken' => $this->mToken,
@@ -2795,7 +2800,7 @@ class User {
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_options' => '',
'user_touched' => $dbw->timestamp( $this->mTouched ),
- 'user_token' => $this->mToken,
+ 'user_token' => strval( $this->mToken ),
'user_email_token' => $this->mEmailToken,
'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
), array( /* WHERE */
@@ -2862,7 +2867,7 @@ class User {
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
'user_real_name' => $user->mRealName,
'user_options' => '',
- 'user_token' => $user->mToken,
+ 'user_token' => strval( $user->mToken ),
'user_registration' => $dbw->timestamp( $user->mRegistration ),
'user_editcount' => 0,
);
@@ -2896,7 +2901,7 @@ class User {
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
'user_options' => '',
- 'user_token' => $this->mToken,
+ 'user_token' => strval( $this->mToken ),
'user_registration' => $dbw->timestamp( $this->mRegistration ),
'user_editcount' => 0,
), __METHOD__
@@ -3144,7 +3149,7 @@ class User {
} else {
$token = $request->getSessionData( 'wsEditToken' );
if ( $token === null ) {
- $token = self::generateToken();
+ $token = MWCryptRand::generateHex( 32 );
$request->setSessionData( 'wsEditToken', $token );
}
if( is_array( $salt ) ) {
@@ -3161,8 +3166,7 @@ class User {
* @return String The new random token
*/
public static function generateToken( $salt = '' ) {
- $token = dechex( mt_rand() ) . dechex( mt_rand() );
- return md5( $token . $salt );
+ return MWCryptRand::generateHex( 32 );
}
/**
@@ -3268,12 +3272,11 @@ class User {
global $wgUserEmailConfirmationTokenExpiry;
$now = time();
$expires = $now + $wgUserEmailConfirmationTokenExpiry;
- $expiration = wfTimestamp( TS_MW, $expires );
- $token = self::generateToken( $this->mId . $this->mEmail . $expires );
- $hash = md5( $token );
$this->load();
+ $token = MWCryptRand::generateHex( 32 );
+ $hash = md5( $token );
$this->mEmailToken = $hash;
- $this->mEmailTokenExpires = $expiration;
+ $this->mEmailTokenExpires = wfTimestamp( TS_MW, $expires );
return $token;
}
@@ -3828,7 +3831,7 @@ class User {
if( $wgPasswordSalt ) {
if ( $salt === false ) {
- $salt = substr( wfGenerateToken(), 0, 8 );
+ $salt = MWCryptRand::generateHex( 8 );
}
return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) );
} else {
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index b1300367..63f5bf2e 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -67,6 +67,8 @@ class MailAddress {
} else {
return $this->address;
}
+ } else {
+ return '';
}
}
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
index 9a0d9714..ae6e4408 100644
--- a/includes/WikiPage.php
+++ b/includes/WikiPage.php
@@ -665,7 +665,7 @@ class WikiPage extends Page {
if ( $dbr->implicitGroupby() ) {
$realNameField = 'user_real_name';
} else {
- $realNameField = 'FIRST(user_real_name) AS user_real_name';
+ $realNameField = 'MIN(user_real_name) AS user_real_name';
}
$tables = array( 'revision', 'user' );
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index d24e1df2..85a43aba 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -581,7 +581,7 @@ class ApiMain extends ApiBase {
// Die if token required, but not provided (unless there is a gettoken parameter)
$salt = $module->getTokenSalt();
- if ( $salt !== false && !isset( $moduleParams['gettoken'] ) ) {
+ if ( $salt !== false && !$moduleParams['gettoken'] ) {
if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
} else {
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index 6212b4ad..fbbd881b 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -52,6 +52,7 @@ class ApiParse extends ApiBase {
if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
$this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
}
+
$prop = array_flip( $params['prop'] );
if ( isset( $params['section'] ) ) {
@@ -164,6 +165,9 @@ class ApiParse extends ApiBase {
}
} else { // Not $oldid, $pageid, $page. Hence based on $text
+ if ( is_null( $text ) ) {
+ $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
+ }
$this->text = $text;
$titleObj = Title::newFromText( $title );
if ( !$titleObj ) {
@@ -570,6 +574,7 @@ class ApiParse extends ApiBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ),
+ array( 'code' => 'params', 'info' => 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?' ),
array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ),
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ),
array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
@@ -590,6 +595,6 @@ class ApiParse extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiParse.php 104449 2011-11-28 15:52:04Z reedy $';
+ return __CLASS__ . ': $Id: ApiParse.php 109694 2012-01-21 21:44:21Z reedy $';
}
}
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index 9a1f1ba0..de7d2c2d 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -673,6 +673,6 @@ class ApiQueryRevisions extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRevisions.php 104449 2011-11-28 15:52:04Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryRevisions.php 108687 2012-01-11 21:59:55Z reedy $';
}
}
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index ef484a8f..dc31dfea 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -1347,8 +1347,7 @@ abstract class Installer {
}
/**
- * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of
- * /dev/urandom
+ * Generate $wgSecretKey. Will warn if we had to use an insecure random source.
*
* @return Status
*/
@@ -1361,8 +1360,8 @@ abstract class Installer {
}
/**
- * Generate a secret value for variables using either
- * /dev/urandom or mt_rand(). Produce a warning in the later case.
+ * Generate a secret value for variables using our CryptRand generator.
+ * Produce a warning if the random source was insecure.
*
* @param $keys Array
* @return Status
@@ -1370,28 +1369,18 @@ abstract class Installer {
protected function doGenerateKeys( $keys ) {
$status = Status::newGood();
- wfSuppressWarnings();
- $file = fopen( "/dev/urandom", "r" );
- wfRestoreWarnings();
-
+ $strong = true;
foreach ( $keys as $name => $length ) {
- if ( $file ) {
- $secretKey = bin2hex( fread( $file, $length / 2 ) );
- } else {
- $secretKey = '';
-
- for ( $i = 0; $i < $length / 8; $i++ ) {
- $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) );
- }
+ $secretKey = MWCryptRand::generateHex( $length, true );
+ if ( !MWCryptRand::wasStrong() ) {
+ $strong = false;
}
$this->setVar( $name, $secretKey );
}
- if ( $file ) {
- fclose( $file );
- } else {
- $names = array_keys ( $keys );
+ if ( !$strong ) {
+ $names = array_keys( $keys );
$names = preg_replace( '/^(.*)$/', '\$$1', $names );
global $wgLang;
$status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
index 175baf0b..a8015832 100644
--- a/includes/installer/OracleInstaller.php
+++ b/includes/installer/OracleInstaller.php
@@ -226,6 +226,8 @@ class OracleInstaller extends DatabaseInstaller {
// user created or already existing, switching back to a normal connection
// as the new user has all needed privileges to setup the rest of the schema
// i will be using that user as _InstallUser from this point on
+ $this->db->close();
+ $this->db = false;
$this->parent->setVar( '_InstallUser', $this->getVar( 'wgDBuser' ) );
$this->parent->setVar( '_InstallPassword', $this->getVar( 'wgDBpassword' ) );
$this->parent->setVar( '_InstallDBname', $this->getVar( 'wgDBuser' ) );
@@ -240,8 +242,8 @@ class OracleInstaller extends DatabaseInstaller {
*/
public function createTables() {
$this->setupSchemaVars();
- $this->db->selectDB( $this->getVar( 'wgDBuser' ) );
$this->db->setFlag( DBO_DDLMODE );
+ $this->parent->setVar( 'wgDBname', $this->getVar( 'wgDBuser' ) );
$status = parent::createTables();
$this->db->clearFlag( DBO_DDLMODE );
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index eebed44c..080da602 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -164,17 +164,21 @@ class CoreParserFunctions {
// Encode as though it's a wiki page, '_' for ' '.
case 'url_wiki':
- return wfUrlencode( str_replace( ' ', '_', $s ) );
+ $func = 'wfUrlencode';
+ $s = str_replace( ' ', '_', $s );
+ break;
// Encode for an HTTP Path, '%20' for ' '.
case 'url_path':
- return rawurlencode( $s );
+ $func = 'rawurlencode';
+ break;
// Encode for HTTP query, '+' for ' '.
case 'url_query':
default:
- return urlencode( $s );
+ $func = 'urlencode';
}
+ return $parser->markerSkipCallback( $s, $func );
}
static function lcfirst( $parser, $s = '' ) {
@@ -194,11 +198,7 @@ class CoreParserFunctions {
*/
static function lc( $parser, $s = '' ) {
global $wgContLang;
- if ( is_callable( array( $parser, 'markerSkipCallback' ) ) ) {
- return $parser->markerSkipCallback( $s, array( $wgContLang, 'lc' ) );
- } else {
- return $wgContLang->lc( $s );
- }
+ return $parser->markerSkipCallback( $s, array( $wgContLang, 'lc' ) );
}
/**
@@ -208,11 +208,7 @@ class CoreParserFunctions {
*/
static function uc( $parser, $s = '' ) {
global $wgContLang;
- if ( is_callable( array( $parser, 'markerSkipCallback' ) ) ) {
- return $parser->markerSkipCallback( $s, array( $wgContLang, 'uc' ) );
- } else {
- return $wgContLang->uc( $s );
- }
+ return $parser->markerSkipCallback( $s, array( $wgContLang, 'uc' ) );
}
static function localurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getLocalURL', $s, $arg ); }
@@ -252,12 +248,13 @@ class CoreParserFunctions {
* @param null $raw
* @return
*/
- static function formatNum( $parser, $num = '', $raw = null) {
- if ( self::israw( $raw ) ) {
- return $parser->getFunctionLang()->parseFormattedNumber( $num );
+ static function formatnum( $parser, $num = '', $raw = null) {
+ if ( self::isRaw( $raw ) ) {
+ $func = array( $parser->getFunctionLang(), 'parseFormattedNumber' );
} else {
- return $parser->getFunctionLang()->formatNum( $num );
+ $func = array( $parser->getFunctionLang(), 'formatNum' );
}
+ return $parser->markerSkipCallback( $num, $func );
}
/**
@@ -267,6 +264,7 @@ class CoreParserFunctions {
* @return
*/
static function grammar( $parser, $case = '', $word = '' ) {
+ $word = $parser->killMarkers( $word );
return $parser->getFunctionLang()->convertGrammar( $word, $case );
}
@@ -624,7 +622,8 @@ class CoreParserFunctions {
/**
* Unicode-safe str_pad with the restriction that $length is forced to be <= 500
*/
- static function pad( $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) {
+ static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) {
+ $padding = $parser->killMarkers( $padding );
$lengthOfPadding = mb_strlen( $padding );
if ( $lengthOfPadding == 0 ) return $string;
@@ -648,11 +647,11 @@ class CoreParserFunctions {
}
static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
- return self::pad( $string, $length, $padding, STR_PAD_LEFT );
+ return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT );
}
static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
- return self::pad( $string, $length, $padding );
+ return self::pad( $parser, $string, $length, $padding );
}
/**
@@ -661,6 +660,7 @@ class CoreParserFunctions {
* @return string
*/
static function anchorencode( $parser, $text ) {
+ $text = $parser->killMarkers( $text );
return substr( $parser->guessSectionNameFromWikiText( $text ), 1);
}
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index b6443fdb..939d9e3f 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -5403,6 +5403,16 @@ class Parser {
}
/**
+ * Remove any strip markers found in the given text.
+ *
+ * @param $text Input string
+ * @return string
+ */
+ function killMarkers( $text ) {
+ return $this->mStripState->killMarkers( $text );
+ }
+
+ /**
* Save the parser state required to convert the given half-parsed text to
* HTML. "Half-parsed" in this context means the output of
* recursiveTagParse() or internalParse(). This output has strip markers
diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php
index 357dc2c8..a6eff70e 100644
--- a/includes/parser/StripState.php
+++ b/includes/parser/StripState.php
@@ -174,5 +174,15 @@ class StripState {
$key = $m[1];
return "{$this->prefix}{$this->tempMergePrefix}-$key" . Parser::MARKER_SUFFIX;
}
+
+ /**
+ * Remove any strip markers found in the given text.
+ *
+ * @param $text Input string
+ * @return string
+ */
+ function killMarkers( $text ) {
+ return preg_replace( $this->regex, '', $text );
+ }
}
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 2a2e2981..01b70e8e 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -164,7 +164,7 @@ class ResourceLoader {
$cache->set( $key, $result );
} catch ( Exception $exception ) {
// Return exception as a comment
- $result = "/*\n{$exception->__toString()}\n*/\n";
+ $result = $this->makeComment( $exception->__toString() );
}
wfProfileOut( __METHOD__ );
@@ -309,13 +309,20 @@ class ResourceLoader {
ob_start();
wfProfileIn( __METHOD__ );
- $exceptions = '';
+ $errors = '';
// Split requested modules into two groups, modules and missing
$modules = array();
$missing = array();
foreach ( $context->getModules() as $name ) {
if ( isset( $this->moduleInfos[$name] ) ) {
+ $module = $this->getModule( $name );
+ // Do not allow private modules to be loaded from the web.
+ // This is a security issue, see bug 34907.
+ if ( $module->getGroup() === 'private' ) {
+ $errors .= $this->makeComment( "Cannot show private module \"$name\"" );
+ continue;
+ }
$modules[$name] = $this->getModule( $name );
} else {
$missing[] = $name;
@@ -340,26 +347,21 @@ class ResourceLoader {
$this->preloadModuleInfo( array_keys( $modules ), $context );
} catch( Exception $e ) {
// Add exception to the output as a comment
- $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+ $errors .= $this->makeComment( $e->__toString() );
}
wfProfileIn( __METHOD__.'-getModifiedTime' );
- $private = false;
// To send Last-Modified and support If-Modified-Since, we need to detect
// the last modified time
$mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch );
foreach ( $modules as $module ) {
try {
- // Bypass Squid and other shared caches if the request includes any private modules
- if ( $module->getGroup() === 'private' ) {
- $private = true;
- }
// Calculate maximum modified time
$mtime = max( $mtime, $module->getModifiedTime( $context ) );
} catch ( Exception $e ) {
// Add exception to the output as a comment
- $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+ $errors .= $this->makeComment( $e->__toString() );
}
}
@@ -376,13 +378,8 @@ class ResourceLoader {
header( 'Cache-Control: private, no-cache, must-revalidate' );
header( 'Pragma: no-cache' );
} else {
- if ( $private ) {
- header( "Cache-Control: private, max-age=$maxage" );
- $exp = $maxage;
- } else {
- header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
- $exp = min( $maxage, $smaxage );
- }
+ header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
+ $exp = min( $maxage, $smaxage );
header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
}
@@ -422,12 +419,12 @@ class ResourceLoader {
$response = $this->makeModuleResponse( $context, $modules, $missing );
// Prepend comments indicating exceptions
- $response = $exceptions . $response;
+ $response = $errors . $response;
// Capture any PHP warnings from the output buffer and append them to the
// response in a comment if we're in debug mode.
if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
- $response = "/*\n$warnings\n*/\n" . $response;
+ $response = $this->makeComment( $warnings ) . $response;
}
// Remove the output buffer and output the response
@@ -437,6 +434,11 @@ class ResourceLoader {
wfProfileOut( __METHOD__ );
}
+ protected function makeComment( $text ) {
+ $encText = str_replace( '*/', '* /', $text );
+ return "/*\n$encText\n*/\n";
+ }
+
/**
* Generates code for a response
*
@@ -461,7 +463,7 @@ class ResourceLoader {
$blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
// Add exception to the output as a comment
- $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+ $exceptions .= $this->makeComment( $e->__toString() );
}
} else {
$blobs = array();
@@ -534,7 +536,7 @@ class ResourceLoader {
}
} catch ( Exception $e ) {
// Add exception to the output as a comment
- $exceptions .= "/*\n{$e->__toString()}\n*/\n";
+ $exceptions .= $this->makeComment( $e->__toString() );
// Register module as missing
$missing[] = $name;
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index 8f28eb8d..78548416 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -45,29 +45,7 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
global $wgUser;
- if ( $context->getUser() === $wgUser->getName() ) {
- return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
- } else {
- return 1;
- }
- }
-
- /**
- * Fetch the context's user options, or if it doesn't match current user,
- * the default options.
- *
- * @param $context ResourceLoaderContext: Context object
- * @return Array: List of user options keyed by option name
- */
- protected function contextUserOptions( ResourceLoaderContext $context ) {
- global $wgUser;
-
- // Verify identity -- this is a private module
- if ( $context->getUser() === $wgUser->getName() ) {
- return $wgUser->getOptions();
- } else {
- return User::getDefaultOptions();
- }
+ return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
}
/**
@@ -75,8 +53,9 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
+ global $wgUser;
return Xml::encodeJsCall( 'mw.user.options.set',
- array( $this->contextUserOptions( $context ) ) );
+ array( $wgUser->getOptions() ) );
}
/**
@@ -84,10 +63,10 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
* @return array
*/
public function getStyles( ResourceLoaderContext $context ) {
- global $wgAllowUserCssPrefs;
+ global $wgAllowUserCssPrefs, $wgUser;
if ( $wgAllowUserCssPrefs ) {
- $options = $this->contextUserOptions( $context );
+ $options = $wgUser->getOptions();
// Build CSS rules
$rules = array();
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 8eeca5d5..33013e08 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -119,14 +119,7 @@ class SpecialUpload extends SpecialPage {
// If it was posted check for the token (no remote POST'ing with user credentials)
$token = $request->getVal( 'wpEditToken' );
- if( $this->mSourceType == 'file' && $token == null ) {
- // Skip token check for file uploads as that can't be faked via JS...
- // Some client-side tools don't expect to need to send wpEditToken
- // with their submissions, as that's new in 1.16.
- $this->mTokenOk = true;
- } else {
- $this->mTokenOk = $wgUser->matchEditToken( $token );
- }
+ $this->mTokenOk = $wgUser->matchEditToken( $token );
$this->uploadFormTextTop = '';
$this->uploadFormTextAfterSummary = '';
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 01dc9a1c..0e5baa2d 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -1114,9 +1114,9 @@ class LoginForm extends SpecialPage {
*/
public static function setLoginToken() {
global $wgRequest;
- // Use User::generateToken() instead of $user->editToken()
+ // Generate a token directly instead of using $user->editToken()
// because the latter reuses $_SESSION['wsEditToken']
- $wgRequest->setSessionData( 'wsLoginToken', User::generateToken() );
+ $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) );
}
/**
@@ -1140,7 +1140,7 @@ class LoginForm extends SpecialPage {
*/
public static function setCreateaccountToken() {
global $wgRequest;
- $wgRequest->setSessionData( 'wsCreateaccountToken', User::generateToken() );
+ $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) );
}
/**
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index 51086bb1..fd562be4 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -43,7 +43,7 @@ class SpecialWatchlist extends SpecialPage {
// Add feed links
$wlToken = $user->getOption( 'watchlisttoken' );
if ( !$wlToken ) {
- $wlToken = sha1( mt_rand() . microtime( true ) );
+ $wlToken = MWCryptRand::generateHex( 40 );
$user->setOption( 'watchlisttoken', $wlToken );
$user->saveSettings();
}
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index 5cdaad6a..f7d7bfef 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -50,6 +50,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$out = $this->getOutput();
$this->setHeaders();
+ $this->outputHeader();
$opts = new FormOptions();
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index feb14a87..f34f156d 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -9,13 +9,18 @@
class UploadFromStash extends UploadBase {
protected $mFileKey, $mVirtualTempPath, $mFileProps, $mSourceType;
-
+
// an instance of UploadStash
private $stash;
-
+
//LocalFile repo
private $repo;
-
+
+ /**
+ * @param $user User
+ * @param $stash UploadStash
+ * @param $repo FileRepo
+ */
public function __construct( $user = false, $stash = false, $repo = false ) {
// user object. sometimes this won't exist, as when running from cron.
$this->user = $user;
@@ -29,16 +34,25 @@ class UploadFromStash extends UploadBase {
if( $stash ) {
$this->stash = $stash;
} else {
- wfDebug( __METHOD__ . " creating new UploadStash instance for " . $user->getId() . "\n" );
+ if( $user ) {
+ wfDebug( __METHOD__ . " creating new UploadStash instance for " . $user->getId() . "\n" );
+ } else {
+ wfDebug( __METHOD__ . " creating new UploadStash instance with no user\n" );
+ }
+
$this->stash = new UploadStash( $this->repo, $this->user );
}
return true;
}
-
+
+ /**
+ * @param $key string
+ * @return bool
+ */
public static function isValidKey( $key ) {
// this is checked in more detail in UploadStash
- return preg_match( UploadStash::KEY_FORMAT_REGEX, $key );
+ return (bool)preg_match( UploadStash::KEY_FORMAT_REGEX, $key );
}
/**
@@ -47,16 +61,23 @@ class UploadFromStash extends UploadBase {
* @return Boolean
*/
public static function isValidRequest( $request ) {
- return self::isValidKey( $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' ) );
+ // this passes wpSessionKey to getText() as a default when wpFileKey isn't set.
+ // wpSessionKey has no default which guarantees failure if both are missing
+ // (though that should have been caught earlier)
+ return self::isValidKey( $request->getText( 'wpFileKey', $request->getText( 'wpSessionKey' ) ) );
}
+ /**
+ * @param $key string
+ * @param $name string
+ */
public function initialize( $key, $name = 'upload_file' ) {
/**
* Confirming a temporarily stashed upload.
* We don't want path names to be forged, so we keep
* them in the session on the server and just give
* an opaque key to the user agent.
- */
+ */
$metadata = $this->stash->getMetadata( $key );
$this->initializePathInfo( $name,
$this->getRealPath ( $metadata['us_path'] ),
@@ -74,17 +95,20 @@ class UploadFromStash extends UploadBase {
* @param $request WebRequest
*/
public function initializeFromRequest( &$request ) {
- $fileKey = $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' );
+ // sends wpSessionKey as a default when wpFileKey is missing
+ $fileKey = $request->getText( 'wpFileKey', $request->getText( 'wpSessionKey' ) );
+
+ // chooses one of wpDestFile, wpUploadFile, filename in that order.
+ $desiredDestName = $request->getText( 'wpDestFile', $request->getText( 'wpUploadFile', $request->getText( 'filename' ) ) );
- $desiredDestName = $request->getText( 'wpDestFile' );
- if( !$desiredDestName ) {
- $desiredDestName = $request->getText( 'wpUploadFile' ) || $request->getText( 'filename' );
- }
return $this->initialize( $fileKey, $desiredDestName );
}
- public function getSourceType() {
- return $this->mSourceType;
+ /**
+ * @return string
+ */
+ public function getSourceType() {
+ return $this->mSourceType;
}
/**
@@ -97,20 +121,23 @@ class UploadFromStash extends UploadBase {
}
/**
- * There is no need to stash the image twice
+ * Stash the file.
+ *
+ * @return UploadStashFile
*/
- public function stashFile( $key = null ) {
- if ( !empty( $this->mLocalFile ) ) {
- return $this->mLocalFile;
- }
- return parent::stashFile( $key );
+ public function stashFile() {
+ // replace mLocalFile with an instance of UploadStashFile, which adds some methods
+ // that are useful for stashed files.
+ $this->mLocalFile = parent::stashFile();
+ return $this->mLocalFile;
}
/**
- * Alias for stashFile
+ * This should return the key instead of the UploadStashFile instance, for backward compatibility.
+ * @return String
*/
- public function stashSession( $key = null ) {
- return $this->stashFile( $key );
+ public function stashSession() {
+ return $this->stashFile()->getFileKey();
}
/**
@@ -123,11 +150,15 @@ class UploadFromStash extends UploadBase {
/**
* Perform the upload, then remove the database record afterward.
+ * @param $comment string
+ * @param $pageText string
+ * @param $watch bool
+ * @param $user User
+ * @return Status
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
$rv = parent::performUpload( $comment, $pageText, $watch, $user );
$this->unsaveUploadedFile();
return $rv;
}
-
-} \ No newline at end of file
+}
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index 9304ce5f..217b84dc 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -10,6 +10,9 @@
* We accomplish this using a database table, with ownership checking as you might expect. See SpecialUploadStash, which
* implements a web interface to some files stored this way.
*
+ * UploadStash right now is *mostly* intended to show you one user's slice of the entire stash. The user parameter is only optional
+ * because there are few cases where we clean out the stash from an automated script. In the future we might refactor this.
+ *
* UploadStash represents the entire stash of temporary files.
* UploadStashFile is a filestore for the actual physical disk files.
* UploadFromStash extends UploadBase, and represents a single stashed file as it is moved from the stash to the regular file repository
@@ -19,13 +22,6 @@ class UploadStash {
// Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg)
const KEY_FORMAT_REGEX = '/^[\w-\.]+\.\w*$/';
- // When a given stashed file can't be loaded, wait for the slaves to catch up. If they're more than MAX_LAG
- // behind, throw an exception instead. (at what point is broken better than slow?)
- const MAX_LAG = 30;
-
- // Age of the repository in hours. That is, after how long will files be assumed abandoned and deleted?
- const REPO_AGE = 6;
-
/**
* repository that this uses to store temp files
* public because we sometimes need to get a LocalFile within the same repo.
@@ -73,6 +69,7 @@ class UploadStash {
/**
* Get a file and its metadata from the stash.
+ * The noAuth param is a bit janky but is required for automated scripts which clean out the stash.
*
* @param $key String: key under which file information is stored
* @param $noAuth Boolean (optional) Don't check authentication. Used by maintenance scripts.
@@ -94,26 +91,10 @@ class UploadStash {
}
}
- $dbr = $this->repo->getSlaveDb();
-
if ( !isset( $this->fileMetadata[$key] ) ) {
- // try this first. if it fails to find the row, check for lag, wait, try again. if its still missing, throw an exception.
- // this more complex solution keeps things moving for page loads with many requests
- // (ie. validating image ownership) when replag is high
if ( !$this->fetchFileMetadata( $key ) ) {
- $lag = $dbr->getLag();
- if ( $lag > 0 && $lag <= self::MAX_LAG ) {
- // if there's not too much replication lag, just wait for the slave to catch up to our last insert.
- sleep( ceil( $lag ) );
- } elseif ( $lag > self::MAX_LAG ) {
- // that's a lot of lag to introduce into the middle of the UI.
- throw new UploadStashMaxLagExceededException(
- 'Couldn\'t load stashed file metadata, and replication lag is above threshold: (MAX_LAG=' . self::MAX_LAG . ')'
- );
- }
-
- // now that the waiting has happened, try again
- $this->fetchFileMetadata( $key );
+ // If nothing was received, it's likely due to replication lag. Check the master to see if the record is there.
+ $this->fetchFileMetadata( $key, DB_MASTER );
}
if ( !isset( $this->fileMetadata[$key] ) ) {
@@ -172,13 +153,12 @@ class UploadStash {
*
* @param $path String: path to file you want stashed
* @param $sourceType String: the type of upload that generated this file (currently, I believe, 'file' or null)
- * @param $key String: optional, unique key for this file. Used for directory hashing when storing, otherwise not important
* @throws UploadStashBadPathException
* @throws UploadStashFileException
* @throws UploadStashNotLoggedInException
* @return UploadStashFile: file, or null on failure
*/
- public function stashFile( $path, $sourceType = null, $key = null ) {
+ public function stashFile( $path, $sourceType = null ) {
if ( ! file_exists( $path ) ) {
wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" );
throw new UploadStashBadPathException( "path doesn't exist" );
@@ -198,17 +178,16 @@ class UploadStash {
}
// If no key was supplied, make one. a mysql insertid would be totally reasonable here, except
- // that some users of this function might expect to supply the key instead of using the generated one.
- if ( is_null( $key ) ) {
- // some things that when combined will make a suitably unique key.
- // see: http://www.jwz.org/doc/mid.html
- list ($usec, $sec) = explode( ' ', microtime() );
- $usec = substr($usec, 2);
- $key = wfBaseConvert( $sec . $usec, 10, 36 ) . '.' .
- wfBaseConvert( mt_rand(), 10, 36 ) . '.'.
- $this->userId . '.' .
- $extension;
- }
+ // that for historical reasons, the key is this random thing instead. At least it's not guessable.
+ //
+ // some things that when combined will make a suitably unique key.
+ // see: http://www.jwz.org/doc/mid.html
+ list ($usec, $sec) = explode( ' ', microtime() );
+ $usec = substr($usec, 2);
+ $key = wfBaseConvert( $sec . $usec, 10, 36 ) . '.' .
+ wfBaseConvert( mt_rand(), 10, 36 ) . '.'.
+ $this->userId . '.' .
+ $extension;
$this->fileProps[$key] = $fileProps;
@@ -249,31 +228,8 @@ class UploadStash {
wfDebug( __METHOD__ . " inserting $stashPath under $key\n" );
$dbw = $this->repo->getMasterDb();
- // select happens on the master so this can all be in a transaction, which
- // avoids a race condition that's likely with multiple people uploading from the same
- // set of files
- $dbw->begin();
- // first, check to see if it's already there.
- $row = $dbw->selectRow(
- 'uploadstash',
- 'us_user, us_timestamp',
- array( 'us_key' => $key ),
- __METHOD__
- );
-
- // The current user can't have this key if:
- // - the key is owned by someone else and
- // - the age of the key is less than REPO_AGE
- if ( is_object( $row ) ) {
- if ( $row->us_user != $this->userId &&
- $row->wfTimestamp( TS_UNIX, $row->us_timestamp ) > time() - UploadStash::REPO_AGE * 3600
- ) {
- $dbw->rollback();
- throw new UploadStashWrongOwnerException( "Attempting to upload a duplicate of a file that someone else has stashed" );
- }
- }
-
$this->fileMetadata[$key] = array(
+ 'us_id' => $dbw->nextSequenceValue( 'uploadstash_us_id_seq' ),
'us_user' => $this->userId,
'us_key' => $key,
'us_orig_path' => $path,
@@ -290,14 +246,11 @@ class UploadStash {
'us_status' => 'finished'
);
- // if a row exists but previous checks on it passed, let the current user take over this key.
- $dbw->replace(
+ $dbw->insert(
'uploadstash',
- 'us_key',
$this->fileMetadata[$key],
__METHOD__
);
- $dbw->commit();
// store the insertid in the class variable so immediate retrieval (possibly laggy) isn't necesary.
$this->fileMetadata[$key]['us_id'] = $dbw->insertId();
@@ -320,7 +273,7 @@ class UploadStash {
throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
}
- wfDebug( __METHOD__ . " clearing all rows for user $userId\n" );
+ wfDebug( __METHOD__ . ' clearing all rows for user ' . $this->userId . "\n" );
$dbw = $this->repo->getMasterDb();
$dbw->delete(
'uploadstash',
@@ -414,7 +367,7 @@ class UploadStash {
$res = $dbr->select(
'uploadstash',
'us_key',
- array( 'us_key' => $key ),
+ array( 'us_user' => $this->userId ),
__METHOD__
);
@@ -468,9 +421,16 @@ class UploadStash {
* @param $key String: key
* @return boolean
*/
- protected function fetchFileMetadata( $key ) {
+ protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) {
// populate $fileMetadata[$key]
- $dbr = $this->repo->getSlaveDb();
+ $dbr = null;
+ if( $readFromDB === DB_MASTER ) {
+ // sometimes reading from the master is necessary, if there's replication lag.
+ $dbr = $this->repo->getMasterDb();
+ } else {
+ $dbr = $this->repo->getSlaveDb();
+ }
+
$row = $dbr->selectRow(
'uploadstash',
'*',
@@ -483,22 +443,7 @@ class UploadStash {
return false;
}
- $this->fileMetadata[$key] = array(
- 'us_user' => $row->us_user,
- 'us_key' => $row->us_key,
- 'us_orig_path' => $row->us_orig_path,
- 'us_path' => $row->us_path,
- 'us_size' => $row->us_size,
- 'us_sha1' => $row->us_sha1,
- 'us_mime' => $row->us_mime,
- 'us_media_type' => $row->us_media_type,
- 'us_image_width' => $row->us_image_width,
- 'us_image_height' => $row->us_image_height,
- 'us_image_bits' => $row->us_image_bits,
- 'us_source_type' => $row->us_source_type,
- 'us_timestamp' => $row->us_timestamp,
- 'us_status' => $row->us_status
- );
+ $this->fileMetadata[$key] = (array)$row;
return true;
}
@@ -698,5 +643,4 @@ class UploadStashFileException extends MWException {};
class UploadStashZeroLengthFileException extends MWException {};
class UploadStashNotLoggedInException extends MWException {};
class UploadStashWrongOwnerException extends MWException {};
-class UploadStashMaxLagExceededException extends MWException {};
class UploadStashNoSuchKeyException extends MWException {};